summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlon Zakai <azakai@google.com>2021-08-16 15:57:04 -0700
committerGitHub <noreply@github.com>2021-08-16 22:57:04 +0000
commiteeb864a593f08d1bebbbda5f6fbc21fa93c5b8af (patch)
tree9466c7dcd9a718c1eedbdabbb425f387b9f0bbdf
parenta8c929df4ce3aafc933078380acb2c9a6d655ca6 (diff)
downloadbinaryen-eeb864a593f08d1bebbbda5f6fbc21fa93c5b8af.tar.gz
binaryen-eeb864a593f08d1bebbbda5f6fbc21fa93c5b8af.tar.bz2
binaryen-eeb864a593f08d1bebbbda5f6fbc21fa93c5b8af.zip
[Wasm GC] ConstantFieldPropagation: Ignore copies (#4084)
When looking for all values written to a field, we can ignore values that are loaded from that same field, i.e., are copied from something already present there. Such operations never introduce new values. This helps by a small but non-zero amount on j2cl.
-rw-r--r--src/passes/ConstantFieldPropagation.cpp42
-rw-r--r--test/lit/passes/cfp.wast161
2 files changed, 196 insertions, 7 deletions
diff --git a/src/passes/ConstantFieldPropagation.cpp b/src/passes/ConstantFieldPropagation.cpp
index f73a9b380..8ec868a2f 100644
--- a/src/passes/ConstantFieldPropagation.cpp
+++ b/src/passes/ConstantFieldPropagation.cpp
@@ -238,11 +238,10 @@ struct Scanner : public WalkerPass<PostWalker<Scanner>> {
auto& values = functionNewInfos[getFunction()][heapType];
auto& fields = heapType.getStruct().fields;
for (Index i = 0; i < fields.size(); i++) {
- auto& fieldValues = values[i];
if (curr->isWithDefault()) {
- fieldValues.note(Literal::makeZero(fields[i].type));
+ values[i].note(Literal::makeZero(fields[i].type));
} else {
- noteExpression(curr->operands[i], fieldValues);
+ noteExpression(curr->operands[i], heapType, i, functionNewInfos);
}
}
}
@@ -254,9 +253,8 @@ struct Scanner : public WalkerPass<PostWalker<Scanner>> {
}
// Note a write to this field of the struct.
- auto heapType = type.getHeapType();
- auto& values = functionSetInfos[getFunction()][heapType];
- noteExpression(curr->value, values[curr->index]);
+ noteExpression(
+ curr->value, type.getHeapType(), curr->index, functionSetInfos);
}
private:
@@ -264,9 +262,39 @@ private:
FunctionStructValuesMap& functionSetInfos;
// Note a value, checking whether it is a constant or not.
- void noteExpression(Expression* expr, PossibleConstantValues& info) {
+ void noteExpression(Expression* expr,
+ HeapType type,
+ Index index,
+ FunctionStructValuesMap& valuesMap) {
expr =
Properties::getFallthrough(expr, getPassOptions(), getModule()->features);
+
+ // Ignore copies: when we set a value to a field from that same field, no
+ // new values are actually introduced.
+ //
+ // Note that this is only sound by virtue of the overall analysis in this
+ // pass: the object read from may be of a subclass, and so subclass values
+ // may be actually written here. But as our analysis considers subclass
+ // values too (as it must) then that is safe. That is, if a subclass of $A
+ // adds a value X that can be loaded from (struct.get $A $b), then consider
+ // a copy
+ //
+ // (struct.set $A $b (struct.get $A $b))
+ //
+ // Our analysis will figure out that X can appear in that copy's get, and so
+ // the copy itself does not add any information about values.
+ //
+ // TODO: This may be extensible to a copy from a subtype by the above
+ // analysis (but this is already entering the realm of diminishing
+ // returns).
+ if (auto* get = expr->dynCast<StructGet>()) {
+ if (get->index == index && get->ref->type != Type::unreachable &&
+ get->ref->type.getHeapType() == type) {
+ return;
+ }
+ }
+
+ auto& info = valuesMap[getFunction()][type][index];
if (!Properties::isConstantExpression(expr)) {
info.noteUnknown();
} else {
diff --git a/test/lit/passes/cfp.wast b/test/lit/passes/cfp.wast
index fce78d126..db6e108bf 100644
--- a/test/lit/passes/cfp.wast
+++ b/test/lit/passes/cfp.wast
@@ -1711,3 +1711,164 @@
)
)
)
+
+;; Copies of a field to itself can be ignored. As a result, we can optimize both
+;; of the gets here.
+(module
+ ;; CHECK: (type $struct (struct (field (mut i32))))
+ (type $struct (struct (mut i32)))
+
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (func $test
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new_default_with_rtt $struct
+ ;; CHECK-NEXT: (rtt.canon $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (struct.set $struct 0
+ ;; CHECK-NEXT: (ref.null $struct)
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (ref.null $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (ref.null $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test
+ (drop
+ (struct.new_default_with_rtt $struct
+ (rtt.canon $struct)
+ )
+ )
+ ;; This copy does not actually introduce any new possible values, and so it
+ ;; remains true that the only possible value is the default.
+ (struct.set $struct 0
+ (ref.null $struct)
+ (struct.get $struct 0
+ (ref.null $struct)
+ )
+ )
+ (drop
+ (struct.get $struct 0
+ (ref.null $struct)
+ )
+ )
+ )
+)
+
+;; Test of a near-copy, of a similar looking field (same index, and same field
+;; type) but in a different struct.
+(module
+ ;; CHECK: (type $struct (struct (field (mut f32)) (field (mut i32))))
+ (type $struct (struct (mut f32) (mut i32)))
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (type $other (struct (field (mut f64)) (field (mut i32))))
+ (type $other (struct (mut f64) (mut i32)))
+
+ ;; CHECK: (func $test
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new_default_with_rtt $struct
+ ;; CHECK-NEXT: (rtt.canon $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (struct.set $struct 1
+ ;; CHECK-NEXT: (ref.null $struct)
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.null $other)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.get $struct 1
+ ;; CHECK-NEXT: (ref.null $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test
+ (drop
+ (struct.new_default_with_rtt $struct
+ (rtt.canon $struct)
+ )
+ )
+ ;; As this is not a copy, we cannot optimize struct.1's get lower down.
+ (struct.set $struct 1
+ (ref.null $struct)
+ (struct.get $other 1
+ (ref.null $other)
+ )
+ )
+ (drop
+ (struct.get $struct 1
+ (ref.null $struct)
+ )
+ )
+ )
+)
+
+;; Test of a near-copy, of a different index.
+(module
+ ;; CHECK: (type $struct (struct (field (mut i32)) (field (mut i32))))
+ (type $struct (struct (mut i32) (mut i32)))
+
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (func $test
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new_default_with_rtt $struct
+ ;; CHECK-NEXT: (rtt.canon $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (struct.set $struct 0
+ ;; CHECK-NEXT: (ref.null $struct)
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (ref.null $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.get $struct 0
+ ;; CHECK-NEXT: (ref.null $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test
+ (drop
+ (struct.new_default_with_rtt $struct
+ (rtt.canon $struct)
+ )
+ )
+ ;; As this is not a copy, we cannot optimize struct.0's get lower down.
+ (struct.set $struct 0
+ (ref.null $struct)
+ (struct.get $struct 1
+ (ref.null $struct)
+ )
+ )
+ (drop
+ (struct.get $struct 0
+ (ref.null $struct)
+ )
+ )
+ )
+)