diff options
-rw-r--r-- | src/ir/possible-constant.h | 38 | ||||
-rw-r--r-- | src/passes/ConstantFieldPropagation.cpp | 26 | ||||
-rw-r--r-- | src/passes/param-utils.cpp | 18 | ||||
-rw-r--r-- | test/lit/passes/dae-gc-refine-params.wast | 56 | ||||
-rw-r--r-- | test/lit/passes/dae-gc.wast | 161 | ||||
-rw-r--r-- | test/lit/passes/dae_all-features.wast | 112 |
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) + ) + ) +) |