summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/ir/possible-constant.h38
-rw-r--r--src/passes/ConstantFieldPropagation.cpp26
-rw-r--r--src/passes/param-utils.cpp18
-rw-r--r--test/lit/passes/dae-gc-refine-params.wast56
-rw-r--r--test/lit/passes/dae-gc.wast161
-rw-r--r--test/lit/passes/dae_all-features.wast112
6 files changed, 354 insertions, 57 deletions
diff --git a/src/ir/possible-constant.h b/src/ir/possible-constant.h
index 24f236a3c..c75669dab 100644
--- a/src/ir/possible-constant.h
+++ b/src/ir/possible-constant.h
@@ -19,6 +19,7 @@
#include <variant>
+#include "ir/properties.h"
#include "wasm-builder.h"
#include "wasm.h"
@@ -47,9 +48,29 @@ private:
public:
PossibleConstantValues() : value(None()) {}
- // Note a written value as we see it, and update our internal knowledge based
- // on it and all previous values noted. This can be called using either a
- // Literal or a Name, so it uses a template.
+ // Notes the contents of an expression and update our internal knowledge based
+ // on it and all previous values noted.
+ void note(Expression* expr, Module& wasm) {
+ // If this is a constant literal value, note that.
+ if (Properties::isConstantExpression(expr)) {
+ note(Properties::getLiteral(expr));
+ return;
+ }
+
+ // If this is an immutable global that we get, note that.
+ if (auto* get = expr->dynCast<GlobalGet>()) {
+ auto* global = wasm.getGlobal(get->name);
+ if (global->mutable_ == Immutable) {
+ note(get->name);
+ return;
+ }
+ }
+
+ // Otherwise, this is not something we can reason about.
+ noteUnknown();
+ }
+
+ // Note either a Literal or a Name.
template<typename T> void note(T curr) {
if (std::get_if<None>(&value)) {
// This is the first value.
@@ -120,6 +141,17 @@ public:
return std::get<Name>(value);
}
+ // Assuming we have a single value, make an expression containing that value.
+ Expression* makeExpression(Module& wasm) {
+ Builder builder(wasm);
+ if (isConstantLiteral()) {
+ return builder.makeConstantExpression(getConstantLiteral());
+ } else {
+ auto name = getConstantGlobal();
+ return builder.makeGlobalGet(name, wasm.getGlobal(name)->type);
+ }
+ }
+
// Returns whether we have ever noted a value.
bool hasNoted() const { return !std::get_if<None>(&value); }
diff --git a/src/passes/ConstantFieldPropagation.cpp b/src/passes/ConstantFieldPropagation.cpp
index 44e74f2d4..1755c97f3 100644
--- a/src/passes/ConstantFieldPropagation.cpp
+++ b/src/passes/ConstantFieldPropagation.cpp
@@ -29,7 +29,6 @@
#include "ir/module-utils.h"
#include "ir/possible-constant.h"
-#include "ir/properties.h"
#include "ir/struct-utils.h"
#include "ir/utils.h"
#include "pass.h"
@@ -101,12 +100,7 @@ struct FunctionOptimizer : public WalkerPass<PostWalker<FunctionOptimizer>> {
// ref.as_non_null (we need to trap as the get would have done so), plus the
// constant value. (Leave it to further optimizations to get rid of the
// ref.)
- Expression* value;
- if (info.isConstantLiteral()) {
- value = builder.makeConstantExpression(info.getConstantLiteral());
- } else {
- value = builder.makeGlobalGet(info.getConstantGlobal(), curr->type);
- }
+ Expression* value = info.makeExpression(*getModule());
replaceCurrent(builder.makeSequence(
builder.makeDrop(builder.makeRefAs(RefAsNonNull, curr->ref)), value));
changed = true;
@@ -145,23 +139,7 @@ struct PCVScanner
HeapType type,
Index index,
PossibleConstantValues& info) {
- // If this is a constant literal value, note that.
- if (Properties::isConstantExpression(expr)) {
- info.note(Properties::getLiteral(expr));
- return;
- }
-
- // If this is an immutable global that we get, note that.
- if (auto* get = expr->dynCast<GlobalGet>()) {
- auto* global = getModule()->getGlobal(get->name);
- if (global->mutable_ == Immutable) {
- info.note(get->name);
- return;
- }
- }
-
- // Otherwise, this is not something we can reason about.
- info.noteUnknown();
+ info.note(expr, *getModule());
}
void noteDefault(Type fieldType,
diff --git a/src/passes/param-utils.cpp b/src/passes/param-utils.cpp
index ae641fd26..cf7873dc0 100644
--- a/src/passes/param-utils.cpp
+++ b/src/passes/param-utils.cpp
@@ -200,25 +200,14 @@ SortedVector applyConstantValues(const std::vector<Function*>& funcs,
auto numParams = first->getNumParams();
for (Index i = 0; i < numParams; i++) {
PossibleConstantValues value;
-
- // Processes one operand.
- auto processOperand = [&](Expression* operand) {
- if (auto* c = operand->dynCast<Const>()) {
- value.note(c->value);
- return;
- }
- // TODO: refnull, immutable globals, etc.
- // Not a constant, give up
- value.noteUnknown();
- };
for (auto* call : calls) {
- processOperand(call->operands[i]);
+ value.note(call->operands[i], *module);
if (!value.isConstant()) {
break;
}
}
for (auto* call : callRefs) {
- processOperand(call->operands[i]);
+ value.note(call->operands[i], *module);
if (!value.isConstant()) {
break;
}
@@ -232,8 +221,7 @@ SortedVector applyConstantValues(const std::vector<Function*>& funcs,
Builder builder(*module);
for (auto* func : funcs) {
func->body = builder.makeSequence(
- builder.makeLocalSet(i, builder.makeConst(value.getConstantLiteral())),
- func->body);
+ builder.makeLocalSet(i, value.makeExpression(*module)), func->body);
}
optimized.insert(i);
}
diff --git a/test/lit/passes/dae-gc-refine-params.wast b/test/lit/passes/dae-gc-refine-params.wast
index e7059b0e0..767c2d716 100644
--- a/test/lit/passes/dae-gc-refine-params.wast
+++ b/test/lit/passes/dae-gc-refine-params.wast
@@ -27,34 +27,36 @@
;; CHECK: (func $call-various-params-no
;; CHECK-NEXT: (call $various-params-no
- ;; CHECK-NEXT: (ref.null ${})
- ;; CHECK-NEXT: (ref.null ${i32})
+ ;; CHECK-NEXT: (call $get_{})
+ ;; CHECK-NEXT: (call $get_{i32})
;; CHECK-NEXT: )
;; CHECK-NEXT: (call $various-params-no
- ;; CHECK-NEXT: (ref.null ${i32})
- ;; CHECK-NEXT: (ref.null ${f64})
+ ;; CHECK-NEXT: (call $get_{i32})
+ ;; CHECK-NEXT: (call $get_{f64})
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; NOMNL: (func $call-various-params-no (type $none_=>_none)
;; NOMNL-NEXT: (call $various-params-no
- ;; NOMNL-NEXT: (ref.null ${})
- ;; NOMNL-NEXT: (ref.null ${i32})
+ ;; NOMNL-NEXT: (call $get_{})
+ ;; NOMNL-NEXT: (call $get_{i32})
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (call $various-params-no
- ;; NOMNL-NEXT: (ref.null ${i32})
- ;; NOMNL-NEXT: (ref.null ${f64})
+ ;; NOMNL-NEXT: (call $get_{i32})
+ ;; NOMNL-NEXT: (call $get_{f64})
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
(func $call-various-params-no
;; The first argument gets {} and {i32}; the second {i32} and {f64}; none of
- ;; those pairs can be optimized.
+ ;; those pairs can be optimized. Note that we do not pass in all nulls, as
+ ;; all nulls are identical and we could do other optimization work due to
+ ;; that.
(call $various-params-no
- (ref.null ${})
- (ref.null ${i32})
+ (call $get_{})
+ (call $get_{i32})
)
(call $various-params-no
- (ref.null ${i32})
- (ref.null ${f64})
+ (call $get_{i32})
+ (call $get_{f64})
)
)
;; This function is called in ways that do not allow us to alter the types of
@@ -81,6 +83,34 @@
(drop (local.get $y))
)
+ ;; CHECK: (func $get_{} (result (ref null ${}))
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $get_{} (type $none_=>_ref?|${}|) (result (ref null ${}))
+ ;; NOMNL-NEXT: (unreachable)
+ ;; NOMNL-NEXT: )
+ (func $get_{} (result (ref null ${}))
+ (unreachable)
+ )
+ ;; CHECK: (func $get_{i32} (result (ref null ${i32}))
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $get_{i32} (type $none_=>_ref?|${i32}|) (result (ref null ${i32}))
+ ;; NOMNL-NEXT: (unreachable)
+ ;; NOMNL-NEXT: )
+ (func $get_{i32} (result (ref null ${i32}))
+ (unreachable)
+ )
+ ;; CHECK: (func $get_{f64} (result (ref null ${f64}))
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $get_{f64} (type $none_=>_ref?|${f64}|) (result (ref null ${f64}))
+ ;; NOMNL-NEXT: (unreachable)
+ ;; NOMNL-NEXT: )
+ (func $get_{f64} (result (ref null ${f64}))
+ (unreachable)
+ )
+
;; CHECK: (func $call-various-params-yes
;; CHECK-NEXT: (call $various-params-yes
;; CHECK-NEXT: (call $get_null_{i32})
diff --git a/test/lit/passes/dae-gc.wast b/test/lit/passes/dae-gc.wast
index bcf177f74..1d12150eb 100644
--- a/test/lit/passes/dae-gc.wast
+++ b/test/lit/passes/dae-gc.wast
@@ -1,6 +1,6 @@
;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
-;; RUN: wasm-opt %s -all --dae -S -o - | filecheck %s
-;; RUN: wasm-opt %s -all --dae --nominal -S -o - | filecheck %s --check-prefix=NOMNL
+;; RUN: foreach %s %t wasm-opt -all --dae -S -o - | filecheck %s
+;; RUN: foreach %s %t wasm-opt -all --dae --nominal -S -o - | filecheck %s --check-prefix=NOMNL
(module
;; CHECK: (type ${} (struct ))
@@ -96,3 +96,160 @@
)
)
)
+
+;; Test ref.func and ref.null optimization of constant parameter values.
+(module
+ ;; CHECK: (func $foo (param $0 (ref $none_=>_none))
+ ;; CHECK-NEXT: (local $1 (ref null $none_=>_none))
+ ;; CHECK-NEXT: (local.set $1
+ ;; CHECK-NEXT: (ref.func $a)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $foo (type $ref|none_->_none|_=>_none) (param $0 (ref $none_=>_none))
+ ;; NOMNL-NEXT: (local $1 (ref null $none_=>_none))
+ ;; NOMNL-NEXT: (local.set $1
+ ;; NOMNL-NEXT: (ref.func $a)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (block
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (ref.as_non_null
+ ;; NOMNL-NEXT: (local.get $1)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (local.get $0)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ (func $foo (param $x (ref func)) (param $y (ref func))
+ ;; "Use" the params to avoid other optimizations kicking in.
+ (drop (local.get $x))
+ (drop (local.get $y))
+ )
+
+ ;; CHECK: (func $call-foo
+ ;; CHECK-NEXT: (call $foo
+ ;; CHECK-NEXT: (ref.func $b)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $foo
+ ;; CHECK-NEXT: (ref.func $c)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $call-foo (type $none_=>_none)
+ ;; NOMNL-NEXT: (call $foo
+ ;; NOMNL-NEXT: (ref.func $b)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (call $foo
+ ;; NOMNL-NEXT: (ref.func $c)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ (func $call-foo
+ ;; Call $foo with a constant function in the first param, which we
+ ;; can optimize, but different ones in the second.
+ (call $foo
+ (ref.func $a)
+ (ref.func $b)
+ )
+ (call $foo
+ (ref.func $a)
+ (ref.func $c)
+ )
+ )
+
+ ;; CHECK: (func $bar (param $0 (ref null $none_=>_none))
+ ;; CHECK-NEXT: (local $1 anyref)
+ ;; CHECK-NEXT: (local.set $1
+ ;; CHECK-NEXT: (ref.null func)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $bar (type $ref?|none_->_none|_=>_none) (param $0 (ref null $none_=>_none))
+ ;; NOMNL-NEXT: (local $1 anyref)
+ ;; NOMNL-NEXT: (local.set $1
+ ;; NOMNL-NEXT: (ref.null func)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (block
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (local.get $1)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (local.get $0)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ (func $bar (param $x (ref null any)) (param $y (ref null any))
+ ;; "Use" the params to avoid other optimizations kicking in.
+ (drop (local.get $x))
+ (drop (local.get $y))
+ )
+
+ ;; CHECK: (func $call-bar
+ ;; CHECK-NEXT: (call $bar
+ ;; CHECK-NEXT: (ref.null $none_=>_none)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $bar
+ ;; CHECK-NEXT: (ref.func $a)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $call-bar (type $none_=>_none)
+ ;; NOMNL-NEXT: (call $bar
+ ;; NOMNL-NEXT: (ref.null $none_=>_none)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (call $bar
+ ;; NOMNL-NEXT: (ref.func $a)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ (func $call-bar
+ ;; Call with nulls. Mixing nulls is fine as they all have the same value, and
+ ;; we can optimize. However, mixing a null with a reference stops us in the
+ ;; second param.
+ (call $bar
+ (ref.null func)
+ (ref.null func)
+ )
+ (call $bar
+ (ref.null any)
+ (ref.func $a)
+ )
+ )
+
+ ;; Helper functions so we have something to take the reference of.
+ ;; CHECK: (func $a
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $a (type $none_=>_none)
+ ;; NOMNL-NEXT: (nop)
+ ;; NOMNL-NEXT: )
+ (func $a)
+ ;; CHECK: (func $b
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $b (type $none_=>_none)
+ ;; NOMNL-NEXT: (nop)
+ ;; NOMNL-NEXT: )
+ (func $b)
+ ;; CHECK: (func $c
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $c (type $none_=>_none)
+ ;; NOMNL-NEXT: (nop)
+ ;; NOMNL-NEXT: )
+ (func $c)
+)
diff --git a/test/lit/passes/dae_all-features.wast b/test/lit/passes/dae_all-features.wast
index 42a47eb68..41655d849 100644
--- a/test/lit/passes/dae_all-features.wast
+++ b/test/lit/passes/dae_all-features.wast
@@ -538,3 +538,115 @@
)
)
)
+
+;; Arguments that read an immutable global can be optimized, as that is a
+;; constant value.
+(module
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (type $i32_=>_none (func (param i32)))
+
+ ;; CHECK: (type $i32_i32_=>_none (func (param i32 i32)))
+
+ ;; CHECK: (global $immut i32 (i32.const 42))
+ (global $immut i32 (i32.const 42))
+
+ ;; CHECK: (global $immut2 i32 (i32.const 43))
+ (global $immut2 i32 (i32.const 43))
+
+ ;; CHECK: (global $mut (mut i32) (i32.const 1337))
+ (global $mut (mut i32) (i32.const 1337))
+
+ ;; CHECK: (func $foo (param $0 i32)
+ ;; CHECK-NEXT: (local $1 i32)
+ ;; CHECK-NEXT: (local.set $1
+ ;; CHECK-NEXT: (global.get $immut)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $foo (param $x i32) (param $y i32)
+ ;; "Use" the params to avoid other optimizations kicking in.
+ (drop (local.get $x))
+ (drop (local.get $y))
+ )
+
+ ;; CHECK: (func $foo-caller
+ ;; CHECK-NEXT: (global.set $mut
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $foo
+ ;; CHECK-NEXT: (global.get $mut)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $mut
+ ;; CHECK-NEXT: (i32.const 2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $foo
+ ;; CHECK-NEXT: (global.get $mut)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $foo-caller
+ ;; Note how the mutable param has a different value in each call, which shows
+ ;; the reason that we cannot optimize in this case. But we can optimize the
+ ;; immutable param.
+ (global.set $mut (i32.const 1))
+ (call $foo
+ (global.get $immut)
+ (global.get $mut)
+ )
+ (global.set $mut (i32.const 2))
+ (call $foo
+ (global.get $immut)
+ (global.get $mut)
+ )
+ )
+
+ ;; CHECK: (func $bar (param $x i32) (param $y i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $y)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $bar (param $x i32) (param $y i32)
+ (drop (local.get $x))
+ (drop (local.get $y))
+ )
+
+ ;; CHECK: (func $bar-caller
+ ;; CHECK-NEXT: (global.set $mut
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $bar
+ ;; CHECK-NEXT: (global.get $immut)
+ ;; CHECK-NEXT: (global.get $immut)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $mut
+ ;; CHECK-NEXT: (i32.const 2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $bar
+ ;; CHECK-NEXT: (global.get $mut)
+ ;; CHECK-NEXT: (global.get $immut2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $bar-caller
+ ;; Corner cases of mixing mutable with immutable and mixing two immutables.
+ (global.set $mut (i32.const 1))
+ (call $bar
+ (global.get $immut)
+ (global.get $immut)
+ )
+ (global.set $mut (i32.const 2))
+ (call $bar
+ (global.get $mut)
+ (global.get $immut2)
+ )
+ )
+)