summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/ir/possible-contents.cpp31
-rw-r--r--src/passes/ConstantFieldPropagation.cpp11
-rw-r--r--test/lit/passes/cfp.wast90
-rw-r--r--test/lit/passes/gufa-vs-cfp.wast56
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)
+ )
+ )
+ )
+ )
+)