diff options
-rw-r--r-- | src/ir/possible-contents.cpp | 31 | ||||
-rw-r--r-- | src/passes/ConstantFieldPropagation.cpp | 11 | ||||
-rw-r--r-- | test/lit/passes/cfp.wast | 90 | ||||
-rw-r--r-- | test/lit/passes/gufa-vs-cfp.wast | 56 |
4 files changed, 188 insertions, 0 deletions
diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 83762a458..d63830c40 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -17,8 +17,10 @@ #include <optional> #include <variant> +#include "ir/bits.h" #include "ir/branch-utils.h" #include "ir/eh-utils.h" +#include "ir/gc-type-utils.h" #include "ir/local-graph.h" #include "ir/module-utils.h" #include "ir/possible-contents.h" @@ -1430,6 +1432,8 @@ private: bool& worthSendingMore); void filterGlobalContents(PossibleContents& contents, const GlobalLocation& globalLoc); + void filterDataContents(PossibleContents& contents, + const DataLocation& dataLoc); // Reads from GC data: a struct.get or array.get. This is given the type of // the read operation, the field that is read on that type, the known contents @@ -1739,6 +1743,9 @@ bool Flower::updateContents(LocationIndex locationIndex, } else if (auto* globalLoc = std::get_if<GlobalLocation>(&location)) { filterGlobalContents(contents, *globalLoc); filtered = true; + } else if (auto* dataLoc = std::get_if<DataLocation>(&location)) { + filterDataContents(contents, *dataLoc); + filtered = true; } // Check if anything changed after filtering, if we did so. @@ -1971,6 +1978,30 @@ void Flower::filterGlobalContents(PossibleContents& contents, } } +void Flower::filterDataContents(PossibleContents& contents, + const DataLocation& dataLoc) { + auto field = GCTypeUtils::getField(dataLoc.type, dataLoc.index); + assert(field); + if (field->isPacked()) { + // We must handle packed fields carefully. + if (contents.isLiteral()) { + // This is a constant. We can truncate it and use that value. + auto mask = Literal(int32_t(Bits::lowBitMask(field->getByteSize() * 8))); + contents = PossibleContents::literal(contents.getLiteral().and_(mask)); + } else { + // This is not a constant. We can't even handle a global here, as we'd + // need to track that this global's value must be truncated before it is + // used, and we don't do that atm. Leave only the type. + // TODO Consider tracking packing on GlobalInfo alongside the type. + // Another option is to make GUFA.cpp apply packing on the read, + // like CFP does - but that can only be done when replacing a + // StructGet of a packed field, and not anywhere else we saw that + // value reach. + contents = PossibleContents::fromType(contents.getType()); + } + } +} + void Flower::readFromData(Type declaredType, Index fieldIndex, const PossibleContents& refContents, diff --git a/src/passes/ConstantFieldPropagation.cpp b/src/passes/ConstantFieldPropagation.cpp index c5b95b15a..f97b440ea 100644 --- a/src/passes/ConstantFieldPropagation.cpp +++ b/src/passes/ConstantFieldPropagation.cpp @@ -27,6 +27,8 @@ // wasm GC programs we need to check for type escaping. // +#include "ir/bits.h" +#include "ir/gc-type-utils.h" #include "ir/module-utils.h" #include "ir/possible-constant.h" #include "ir/struct-utils.h" @@ -105,6 +107,15 @@ struct FunctionOptimizer : public WalkerPass<PostWalker<FunctionOptimizer>> { // constant value. (Leave it to further optimizations to get rid of the // ref.) Expression* value = info.makeExpression(*getModule()); + auto field = GCTypeUtils::getField(type, curr->index); + assert(field); + if (field->isPacked()) { + // We cannot just pass through a value that is packed, as the input gets + // truncated. + auto mask = Bits::lowBitMask(field->getByteSize() * 8); + value = + builder.makeBinary(AndInt32, value, builder.makeConst(int32_t(mask))); + } replaceCurrent(builder.makeSequence( builder.makeDrop(builder.makeRefAs(RefAsNonNull, curr->ref)), value)); changed = true; diff --git a/test/lit/passes/cfp.wast b/test/lit/passes/cfp.wast index 0aeb1f41b..4647599e0 100644 --- a/test/lit/passes/cfp.wast +++ b/test/lit/passes/cfp.wast @@ -2135,3 +2135,93 @@ ) ) ) + +;; Test we handle packed fields properly. +(module + ;; CHECK: (type $none_=>_none (func)) + + ;; CHECK: (type $A_8 (struct (field i8))) + (type $A_8 (struct (field i8))) + ;; CHECK: (type $A_16 (struct (field i16))) + (type $A_16 (struct (field i16))) + ;; CHECK: (type $B_16 (struct (field i16))) + (type $B_16 (struct (field i16))) + + ;; CHECK: (import "a" "b" (global $g i32)) + (import "a" "b" (global $g i32)) + + ;; CHECK: (func $test (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (struct.new $A_8 + ;; CHECK-NEXT: (i32.const 305419896) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (i32.const 305419896) + ;; CHECK-NEXT: (i32.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (struct.new $A_16 + ;; CHECK-NEXT: (i32.const 305419896) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (i32.const 305419896) + ;; CHECK-NEXT: (i32.const 65535) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (struct.new $B_16 + ;; CHECK-NEXT: (global.get $g) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (global.get $g) + ;; CHECK-NEXT: (i32.const 65535) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + ;; We can infer values here, but must add proper masks, as the inputs get + ;; truncated during packing. + (drop + (struct.get_u $A_8 0 + (struct.new $A_8 + (i32.const 0x12345678) + ) + ) + ) + (drop + (struct.get_u $A_16 0 + (struct.new $A_16 + (i32.const 0x12345678) + ) + ) + ) + ;; Also test reading a value from an imported global, which is an unknown + ;; value at compile time, but which we know must be masked as well. + (drop + (struct.get_u $B_16 0 + (struct.new $B_16 + (global.get $g) + ) + ) + ) + ) +) diff --git a/test/lit/passes/gufa-vs-cfp.wast b/test/lit/passes/gufa-vs-cfp.wast index a2fb8f5df..4c3d2e25c 100644 --- a/test/lit/passes/gufa-vs-cfp.wast +++ b/test/lit/passes/gufa-vs-cfp.wast @@ -2665,3 +2665,59 @@ ) ) ) + +;; Test we handle packed fields properly. +(module + (type $A_8 (struct (field i8))) + (type $A_16 (struct (field i16))) + ;; CHECK: (type $B_16 (struct (field i16))) + (type $B_16 (struct (field i16))) + + ;; CHECK: (type $none_=>_none (func)) + + ;; CHECK: (import "a" "b" (global $g i32)) + (import "a" "b" (global $g i32)) + + ;; CHECK: (func $test (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 120) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 22136) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get_u $B_16 0 + ;; CHECK-NEXT: (struct.new $B_16 + ;; CHECK-NEXT: (global.get $g) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + ;; We can infer values here, but must mask them. + (drop + (struct.get_u $A_8 0 + (struct.new $A_8 + (i32.const 0x12345678) + ) + ) + ) + (drop + (struct.get_u $A_16 0 + (struct.new $A_16 + (i32.const 0x12345678) + ) + ) + ) + ;; Also test reading a value from an imported global, which is an unknown + ;; value at compile time, but which we know must be masked as well. Atm + ;; GUFA does not handle this, unlike CFP (see TODO in filterDataContents). + (drop + (struct.get_u $B_16 0 + (struct.new $B_16 + (global.get $g) + ) + ) + ) + ) +) |