diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 920b2edddf5..4596e64f9ea 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -82,6 +82,7 @@ 'cfp-rmw.wast', 'unsubtyping-cmpxchg.wast', 'struct-atomic-threads.wast', + 'type-refining-gufa-rmw.wast', # contains too many segments to run in a wasm VM 'limit-segments_disable-bulk-memory.wast', # https://github.com/WebAssembly/binaryen/issues/7176 diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 8758dae3325..201f413060e 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -1073,16 +1073,19 @@ struct InfoCollector if (curr->ref->type == Type::unreachable) { return; } - // TODO: Model the modification part of the RMW in addition to the read and - // the write. + addChildParentLink(curr->ref, curr); + addChildParentLink(curr->value, curr); + // TODO: Model the output addRoot(curr); } void visitStructCmpxchg(StructCmpxchg* curr) { if (curr->ref->type == Type::unreachable) { return; } - // TODO: Model the modification part of the RMW in addition to the read and - // the write. + addChildParentLink(curr->ref, curr); + addChildParentLink(curr->expected, curr); + addChildParentLink(curr->replacement, curr); + // TODO: Model the output addRoot(curr); } void visitStructWait(StructWait* curr) { addRoot(curr); } @@ -1171,16 +1174,19 @@ struct InfoCollector if (curr->ref->type == Type::unreachable) { return; } - // TODO: Model the modification part of the RMW in addition to the read and - // the write. + addChildParentLink(curr->ref, curr); + addChildParentLink(curr->value, curr); + // TODO: Model the output addRoot(curr); } void visitArrayCmpxchg(ArrayCmpxchg* curr) { if (curr->ref->type == Type::unreachable) { return; } - // TODO: Model the modification part of the RMW in addition to the read and - // the write. + addChildParentLink(curr->ref, curr); + addChildParentLink(curr->expected, curr); + addChildParentLink(curr->replacement, curr); + // TODO: Model the output addRoot(curr); } void visitStringNew(StringNew* curr) { @@ -2247,10 +2253,12 @@ struct Flower { Expression* read); // Similar to readFromData, but does a write for a struct.set or array.set. + void writeToData(Expression* ref, Expression* value, Index fieldIndex); + + // A write with given contents. void writeToData(Expression* ref, - Expression* value, - Index fieldIndex, - bool multibyte = false); + const PossibleContents& valueContents, + Index fieldIndex); // We will need subtypes during the flow, so compute them once ahead of time. std::unique_ptr subTypes; @@ -2839,6 +2847,18 @@ void Flower::flowAfterUpdate(LocationIndex locationIndex) { // |child| is either the reference or the value child of a struct.set. assert(set->ref == child || set->value == child); writeToData(set->ref, set->value, set->index); + } else if (auto* set = parent->dynCast()) { + assert(set->ref == child || set->value == child); + // TODO: model the stored value, depending on the actual operation. + writeToData( + set->ref, PossibleContents::fromType(set->value->type), set->index); + } else if (auto* set = parent->dynCast()) { + assert(set->ref == child || set->expected == child || + set->replacement == child); + // TODO: model the stored value, depending on the actual operation. + writeToData(set->ref, + PossibleContents::fromType(set->replacement->type), + set->index); } else if (auto* get = parent->dynCast()) { assert(get->ref == child); readFromData(get->ref->type, 0, contents, get); @@ -2847,9 +2867,21 @@ void Flower::flowAfterUpdate(LocationIndex locationIndex) { writeToData(set->ref, set->value, 0); } else if (auto* store = parent->dynCast()) { assert(store->ref == child || store->value == child); - writeToData(store->ref, store->value, 0, /*multibyte*/ true); + // TODO: model the stored value, and handle different but equal values in + // type, e.g. writing i16 0x1212 is the same as i8 0x12. + writeToData( + store->ref, PossibleContents::fromType(store->value->type), 0); + } else if (auto* set = parent->dynCast()) { + assert(set->ref == child || set->value == child); + // TODO: model the stored value, depending on the actual operation. + writeToData(set->ref, PossibleContents::fromType(set->value->type), 0); + } else if (auto* set = parent->dynCast()) { + assert(set->ref == child || set->expected == child || + set->replacement == child); + // TODO: model the stored value, depending on the actual operation. + writeToData( + set->ref, PossibleContents::fromType(set->replacement->type), 0); } else if (auto* get = parent->dynCast()) { - // Similar to struct.get. assert(get->ref == child); readFromData( get->ref->type, DataLocation::DescriptorIndex, contents, get); @@ -3214,23 +3246,7 @@ void Flower::readFromData(Type declaredType, connectDuringFlow(coneReadLocation, ExpressionLocation{read, 0}); } -void Flower::writeToData(Expression* ref, - Expression* value, - Index fieldIndex, - bool multibyte) { -#if defined(POSSIBLE_CONTENTS_DEBUG) && POSSIBLE_CONTENTS_DEBUG >= 2 - std::cout << " add special writes\n"; -#endif - - auto refContents = getContents(getIndex(ExpressionLocation{ref, 0})); - -#ifndef NDEBUG - // We must not have anything in the reference that is invalid for the wasm - // type there. - auto maximalContents = PossibleContents::coneType(ref->type); - assert(PossibleContents::isSubContents(refContents, maximalContents)); -#endif - +void Flower::writeToData(Expression* ref, Expression* value, Index fieldIndex) { // We could set up links here as we do for reads, but as we get to this code // in any case, we can just flow the values forward directly. This avoids // adding any links (edges) to the graph (and edges are what we want to avoid @@ -3253,6 +3269,24 @@ void Flower::writeToData(Expression* ref, // reference and value.) auto valueContents = getContents(getIndex(ExpressionLocation{value, 0})); + writeToData(ref, valueContents, fieldIndex); +} + +void Flower::writeToData(Expression* ref, + const PossibleContents& valueContents, + Index fieldIndex) { +#if defined(POSSIBLE_CONTENTS_DEBUG) && POSSIBLE_CONTENTS_DEBUG >= 2 + std::cout << " add special writes\n"; +#endif + + auto refContents = getContents(getIndex(ExpressionLocation{ref, 0})); + +#ifndef NDEBUG + // We must not have anything in the reference that is invalid for the wasm + // type there. + auto maximalContents = PossibleContents::coneType(ref->type); + assert(PossibleContents::isSubContents(refContents, maximalContents)); +#endif // See the related comment in readFromData() as to why these are the only // things we need to check, and why the assertion afterwards contains the only @@ -3265,9 +3299,6 @@ void Flower::writeToData(Expression* ref, // As in readFromData, normalize to the proper cone. auto cone = refContents.getCone(); auto normalizedDepth = getNormalizedConeDepth(cone.type, cone.depth); - if (multibyte) { - valueContents = PossibleContents::fromType(value->type); - } subTypes->iterSubTypes( cone.type.getHeapType(), normalizedDepth, [&](HeapType type, Index depth) { diff --git a/test/lit/passes/type-refining-gufa-rmw.wast b/test/lit/passes/type-refining-gufa-rmw.wast new file mode 100644 index 00000000000..5db729803ad --- /dev/null +++ b/test/lit/passes/type-refining-gufa-rmw.wast @@ -0,0 +1,250 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: foreach %s %t wasm-opt -all --closed-world --preserve-type-order \ +;; RUN: --type-refining -S -o - | filecheck %s --check-prefix=NRML +;; RUN: foreach %s %t wasm-opt -all --closed-world --preserve-type-order \ +;; RUN: --type-refining-gufa -S -o - | filecheck %s --check-prefix=GUFA + +(module + ;; NRML: (type $struct (struct (field (mut i32)) (field (mut eqref)))) + ;; GUFA: (type $struct (struct (field (mut i32)) (field (mut eqref)))) + (type $struct (struct (field (mut i32)) (field (mut eqref)))) + + ;; NRML: (type $1 (func (param (ref $struct) i32) (result i32))) + + ;; NRML: (type $2 (func (param (ref $struct) eqref) (result eqref))) + + ;; NRML: (type $3 (func)) + + ;; NRML: (func $atomic.rmw (type $1) (param $0 (ref $struct)) (param $1 i32) (result i32) + ;; NRML-NEXT: (struct.atomic.rmw.add $struct 0 + ;; NRML-NEXT: (local.get $0) + ;; NRML-NEXT: (local.get $1) + ;; NRML-NEXT: ) + ;; NRML-NEXT: ) + ;; GUFA: (type $1 (func (param (ref $struct) i32) (result i32))) + + ;; GUFA: (type $2 (func (param (ref $struct) eqref) (result eqref))) + + ;; GUFA: (type $3 (func)) + + ;; GUFA: (func $atomic.rmw (type $1) (param $0 (ref $struct)) (param $1 i32) (result i32) + ;; GUFA-NEXT: (struct.atomic.rmw.add $struct 0 + ;; GUFA-NEXT: (local.get $0) + ;; GUFA-NEXT: (local.get $1) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: ) + (func $atomic.rmw (param $0 (ref $struct)) (param $1 i32) (result i32) + ;; This RMW causes a write to the data, and reads something else after adding. + ;; We can infer no value here. + (struct.atomic.rmw.add $struct 0 + (local.get $0) + (local.get $1) + ) + ) + + ;; NRML: (func $atomic.rmw.cmpxchg (type $2) (param $0 (ref $struct)) (param $1 eqref) (result eqref) + ;; NRML-NEXT: (struct.atomic.rmw.cmpxchg $struct 1 + ;; NRML-NEXT: (local.get $0) + ;; NRML-NEXT: (local.get $1) + ;; NRML-NEXT: (local.get $1) + ;; NRML-NEXT: ) + ;; NRML-NEXT: ) + ;; GUFA: (func $atomic.rmw.cmpxchg (type $2) (param $0 (ref $struct)) (param $1 eqref) (result eqref) + ;; GUFA-NEXT: (struct.atomic.rmw.cmpxchg $struct 1 + ;; GUFA-NEXT: (local.get $0) + ;; GUFA-NEXT: (local.get $1) + ;; GUFA-NEXT: (local.get $1) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: ) + (func $atomic.rmw.cmpxchg (param $0 (ref $struct)) (param $1 eqref) (result eqref) + ;; This RMW causes a write to the data, preventing refining of the eqref + ;; field to nullref (which we would do if nothing was written). + (struct.atomic.rmw.cmpxchg $struct 1 + (local.get $0) + (local.get $1) + (local.get $1) + ) + ) + + ;; NRML: (func $call (type $3) + ;; NRML-NEXT: (drop + ;; NRML-NEXT: (call $atomic.rmw + ;; NRML-NEXT: (struct.new_default $struct) + ;; NRML-NEXT: (i32.const 42) + ;; NRML-NEXT: ) + ;; NRML-NEXT: ) + ;; NRML-NEXT: (drop + ;; NRML-NEXT: (call $atomic.rmw.cmpxchg + ;; NRML-NEXT: (struct.new_default $struct) + ;; NRML-NEXT: (ref.i31 + ;; NRML-NEXT: (i32.const 1337) + ;; NRML-NEXT: ) + ;; NRML-NEXT: ) + ;; NRML-NEXT: ) + ;; NRML-NEXT: ) + ;; GUFA: (func $call (type $3) + ;; GUFA-NEXT: (drop + ;; GUFA-NEXT: (call $atomic.rmw + ;; GUFA-NEXT: (struct.new_default $struct) + ;; GUFA-NEXT: (i32.const 42) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: (drop + ;; GUFA-NEXT: (call $atomic.rmw.cmpxchg + ;; GUFA-NEXT: (struct.new_default $struct) + ;; GUFA-NEXT: (ref.i31 + ;; GUFA-NEXT: (i32.const 1337) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: ) + (func $call + ;; Complete the testcases with a call, so there is some data at all. + (drop + (call $atomic.rmw + (struct.new_default $struct) + (i32.const 42) + ) + ) + (drop + (call $atomic.rmw.cmpxchg + (struct.new_default $struct) + (ref.i31 + (i32.const 1337) + ) + ) + ) + ) +) + +;; As above, but with arrays. +(module + ;; NRML: (type $array-i32 (array (mut i32))) + ;; GUFA: (type $array-i32 (array (mut i32))) + (type $array-i32 (array (mut i32))) + + ;; NRML: (type $array-eqref (array (mut eqref))) + ;; GUFA: (type $array-eqref (array (mut eqref))) + (type $array-eqref (array (mut eqref))) + + ;; NRML: (type $2 (func (param (ref $array-i32) i32) (result i32))) + + ;; NRML: (type $3 (func (param (ref $array-eqref) eqref) (result eqref))) + + ;; NRML: (type $4 (func)) + + ;; NRML: (func $atomic.rmw (type $2) (param $0 (ref $array-i32)) (param $1 i32) (result i32) + ;; NRML-NEXT: (array.atomic.rmw.add $array-i32 + ;; NRML-NEXT: (local.get $0) + ;; NRML-NEXT: (i32.const 10) + ;; NRML-NEXT: (local.get $1) + ;; NRML-NEXT: ) + ;; NRML-NEXT: ) + ;; GUFA: (type $2 (func (param (ref $array-i32) i32) (result i32))) + + ;; GUFA: (type $3 (func (param (ref $array-eqref) eqref) (result eqref))) + + ;; GUFA: (type $4 (func)) + + ;; GUFA: (func $atomic.rmw (type $2) (param $0 (ref $array-i32)) (param $1 i32) (result i32) + ;; GUFA-NEXT: (array.atomic.rmw.add $array-i32 + ;; GUFA-NEXT: (local.get $0) + ;; GUFA-NEXT: (i32.const 10) + ;; GUFA-NEXT: (local.get $1) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: ) + (func $atomic.rmw (param $0 (ref $array-i32)) (param $1 i32) (result i32) + (array.atomic.rmw.add $array-i32 + (local.get $0) + (i32.const 10) + (local.get $1) + ) + ) + + ;; NRML: (func $atomic.rmw.cmpxchg (type $3) (param $0 (ref $array-eqref)) (param $1 eqref) (result eqref) + ;; NRML-NEXT: (array.atomic.rmw.cmpxchg $array-eqref + ;; NRML-NEXT: (local.get $0) + ;; NRML-NEXT: (i32.const 20) + ;; NRML-NEXT: (local.get $1) + ;; NRML-NEXT: (local.get $1) + ;; NRML-NEXT: ) + ;; NRML-NEXT: ) + ;; GUFA: (func $atomic.rmw.cmpxchg (type $3) (param $0 (ref $array-eqref)) (param $1 eqref) (result eqref) + ;; GUFA-NEXT: (array.atomic.rmw.cmpxchg $array-eqref + ;; GUFA-NEXT: (local.get $0) + ;; GUFA-NEXT: (i32.const 20) + ;; GUFA-NEXT: (local.get $1) + ;; GUFA-NEXT: (local.get $1) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: ) + (func $atomic.rmw.cmpxchg (param $0 (ref $array-eqref)) (param $1 eqref) (result eqref) + (array.atomic.rmw.cmpxchg $array-eqref + (local.get $0) + (i32.const 20) + (local.get $1) + (local.get $1) + ) + ) + + ;; NRML: (func $call (type $4) + ;; NRML-NEXT: (drop + ;; NRML-NEXT: (call $atomic.rmw + ;; NRML-NEXT: (array.new_default $array-i32 + ;; NRML-NEXT: (i32.const 7) + ;; NRML-NEXT: ) + ;; NRML-NEXT: (i32.const 42) + ;; NRML-NEXT: ) + ;; NRML-NEXT: ) + ;; NRML-NEXT: (drop + ;; NRML-NEXT: (call $atomic.rmw.cmpxchg + ;; NRML-NEXT: (array.new_default $array-eqref + ;; NRML-NEXT: (i32.const 14) + ;; NRML-NEXT: ) + ;; NRML-NEXT: (ref.i31 + ;; NRML-NEXT: (i32.const 1337) + ;; NRML-NEXT: ) + ;; NRML-NEXT: ) + ;; NRML-NEXT: ) + ;; NRML-NEXT: ) + ;; GUFA: (func $call (type $4) + ;; GUFA-NEXT: (drop + ;; GUFA-NEXT: (call $atomic.rmw + ;; GUFA-NEXT: (array.new_default $array-i32 + ;; GUFA-NEXT: (i32.const 7) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: (i32.const 42) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: (drop + ;; GUFA-NEXT: (call $atomic.rmw.cmpxchg + ;; GUFA-NEXT: (array.new_default $array-eqref + ;; GUFA-NEXT: (i32.const 14) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: (ref.i31 + ;; GUFA-NEXT: (i32.const 1337) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: ) + (func $call + (drop + (call $atomic.rmw + (array.new_default $array-i32 + (i32.const 7) + ) + (i32.const 42) + ) + ) + (drop + (call $atomic.rmw.cmpxchg + (array.new_default $array-eqref + (i32.const 14) + ) + (ref.i31 + (i32.const 1337) + ) + ) + ) + ) +)