From 29c1c0522aa0faa63d5d722fd36a2193557ae6ec Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 28 Oct 2025 16:10:06 -0700 Subject: [PATCH 1/9] start --- src/passes/GlobalStructInference.cpp | 93 +++++++++++++++++----------- 1 file changed, 57 insertions(+), 36 deletions(-) diff --git a/src/passes/GlobalStructInference.cpp b/src/passes/GlobalStructInference.cpp index e2b728fc10b..c3522334667 100644 --- a/src/passes/GlobalStructInference.cpp +++ b/src/passes/GlobalStructInference.cpp @@ -45,6 +45,10 @@ // comparison. But we can compare structs, so if the function references are in // vtables, and the vtables follow the above pattern, then we can optimize. // +// This also optimizes some related things - reads from structs created in +// globals - that benefit from the infrastructure here (see unnesting, below), +// even with this type-based approach. +// // TODO: Only do the case with a select when shrinkLevel == 0? // @@ -203,11 +207,6 @@ struct GlobalStructInference : public Pass { } } - if (typeGlobals.empty()) { - // We found nothing we can optimize. - return; - } - // The above loop on typeGlobalsCopy is on an unsorted data structure, and // that can lead to nondeterminism in typeGlobals. Sort the vectors there to // ensure determinism. @@ -328,18 +327,12 @@ struct GlobalStructInference : public Pass { // We must ignore the case of a non-struct heap type, that is, a bottom // type (which is all that is left after we've already ruled out - // unreachable). Such things will not be in typeGlobals, which we are - // checking now anyhow. + // unreachable). auto heapType = type.getHeapType(); - auto iter = parent.typeGlobals.find(heapType); - if (iter == parent.typeGlobals.end()) { + if (!heapType.isStruct()) { return; } - // This cannot be a bottom type as we found it in the typeGlobals map, - // which only contains types of struct.news. - assert(heapType.isStruct()); - // The field must be immutable. std::optional field; if (fieldIndex != DescriptorIndex) { @@ -349,12 +342,36 @@ struct GlobalStructInference : public Pass { } } + auto& wasm = *getModule(); + + // This is a read of an immutable field. See if it is a trivial case, of + // a read from an immutable global. + if (auto* get = ref->dynCast()) { + auto* global = wasm.getGlobal(get->name); + if (!global->mutable_) { + if (auto* structNew = global->init->dynCast()) { + auto value = readFromStructNew(structNew, fieldIndex, field); + // TODO: handle non-constant values using unnesting, as below. + if (value.isConstant()) { + replaceCurrent(value.getConstant().makeExpression(wasm)); + return; + } + // Otherwise, continue to try the main type-based optimization + // below. + } + } + } + + auto iter = parent.typeGlobals.find(heapType); + if (iter == parent.typeGlobals.end()) { + return; + } + const auto& globals = iter->second; if (globals.size() == 0) { return; } - auto& wasm = *getModule(); Builder builder(wasm); if (globals.size() == 1) { @@ -388,28 +405,8 @@ struct GlobalStructInference : public Pass { for (Index i = 0; i < globals.size(); i++) { Name global = globals[i]; auto* structNew = wasm.getGlobal(global)->init->cast(); - // The value that is read from this struct.new. - Value value; - - // Find the value read from the struct and represent it as a Value. - PossibleConstantValues constant; - if (field && structNew->isWithDefault()) { - constant.note(Literal::makeZero(field->type)); - value.content = constant; - } else { - Expression* operand; - if (field) { - operand = structNew->operands[fieldIndex]; - } else { - operand = structNew->desc; - } - constant.note(operand, wasm); - if (constant.isConstant()) { - value.content = constant; - } else { - value.content = operand; - } - } + // Find the value read from the struct.new. + auto value = readFromStructNew(structNew, fieldIndex, field); // If the value is constant, it may be grouped as mentioned before. // See if it matches anything we've seen before. @@ -533,6 +530,30 @@ struct GlobalStructInference : public Pass { ReFinalize().walkFunctionInModule(func, getModule()); } } + + Value readFromStructNew(StructNew* structNew, Index fieldIndex, std::optional& field) { + // Find the value read from the struct and represent it as a Value. + Value value; + PossibleConstantValues constant; + if (field && structNew->isWithDefault()) { + constant.note(Literal::makeZero(field->type)); + value.content = constant; + } else { + Expression* operand; + if (field) { + operand = structNew->operands[fieldIndex]; + } else { + operand = structNew->desc; + } + constant.note(operand, *getModule()); + if (constant.isConstant()) { + value.content = constant; + } else { + value.content = operand; + } + } + return value; + } }; // Find the optimization opportunitites in parallel. From 3239afb435230a4bd32251c947ac6fd53ede46dc Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 28 Oct 2025 16:10:13 -0700 Subject: [PATCH 2/9] start --- test/lit/passes/gsi-nontype.wast | 46 ++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 test/lit/passes/gsi-nontype.wast diff --git a/test/lit/passes/gsi-nontype.wast b/test/lit/passes/gsi-nontype.wast new file mode 100644 index 00000000000..24c7c4df791 --- /dev/null +++ b/test/lit/passes/gsi-nontype.wast @@ -0,0 +1,46 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; RUN: foreach %s %t wasm-opt --gsi -all --closed-world -S -o - | filecheck %s + +;; Non-type-based optimizations in --gsi + +(module + ;; Create an immutable vtable in an immutable global, which we can optimize + ;; with. + + ;; CHECK: (type $vtable (struct (field funcref))) + (type $vtable (struct funcref)) + ;; CHECK: (type $object (struct (field (ref $vtable)))) + (type $object (struct (ref $vtable))) + + (import "a" "b" (global $imported funcref)) + + ;; CHECK: (global $vtable (ref $vtable) (struct.new $vtable + ;; CHECK-NEXT: (ref.func $nested-creations) + ;; CHECK-NEXT: )) + (global $vtable (ref $vtable) + (struct.new $vtable + (global.get $imported) + ) + ) + + ;; CHECK: (func $nested-creations (type $2) + ;; CHECK-NEXT: (local $ref (ref null $object)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $object + ;; CHECK-NEXT: (global.get $vtable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $helper + ;; CHECK-NEXT: (ref.func $nested-creations) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $nested-creations + ;; This get reads $import. + (drop + (struct.get $vtable 0 + (global.get $vtable) + ) + ) + ) +) + From 3ab6d6715cbec2b6369ebba838d115527ff63522 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 28 Oct 2025 16:12:39 -0700 Subject: [PATCH 3/9] work --- test/lit/passes/gsi-nontype.wast | 26 ++++++++++---------------- test/lit/passes/gsi.wast | 10 +++++----- 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/test/lit/passes/gsi-nontype.wast b/test/lit/passes/gsi-nontype.wast index 24c7c4df791..4d8aa310bef 100644 --- a/test/lit/passes/gsi-nontype.wast +++ b/test/lit/passes/gsi-nontype.wast @@ -3,19 +3,19 @@ ;; Non-type-based optimizations in --gsi +;; Create an immutable vtable in an immutable global, which we can optimize +;; with. (module - ;; Create an immutable vtable in an immutable global, which we can optimize - ;; with. - ;; CHECK: (type $vtable (struct (field funcref))) (type $vtable (struct funcref)) - ;; CHECK: (type $object (struct (field (ref $vtable)))) - (type $object (struct (ref $vtable))) + ;; CHECK: (type $1 (func)) + + ;; CHECK: (import "a" "b" (global $imported funcref)) (import "a" "b" (global $imported funcref)) ;; CHECK: (global $vtable (ref $vtable) (struct.new $vtable - ;; CHECK-NEXT: (ref.func $nested-creations) + ;; CHECK-NEXT: (global.get $imported) ;; CHECK-NEXT: )) (global $vtable (ref $vtable) (struct.new $vtable @@ -23,18 +23,12 @@ ) ) - ;; CHECK: (func $nested-creations (type $2) - ;; CHECK-NEXT: (local $ref (ref null $object)) - ;; CHECK-NEXT: (local.set $ref - ;; CHECK-NEXT: (struct.new $object - ;; CHECK-NEXT: (global.get $vtable) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (call $helper - ;; CHECK-NEXT: (ref.func $nested-creations) + ;; CHECK: (func $nested-creations (type $1) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (global.get $imported) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $nested-creations + (func $test ;; This get reads $import. (drop (struct.get $vtable 0 diff --git a/test/lit/passes/gsi.wast b/test/lit/passes/gsi.wast index 7ff7bd4cc6d..dd3f6a6d4ef 100644 --- a/test/lit/passes/gsi.wast +++ b/test/lit/passes/gsi.wast @@ -1901,7 +1901,7 @@ ;; CHECK: (type $struct (struct (field i8))) (type $struct (struct (field i8))) - ;; CHECK: (type $1 (func (result i32))) + ;; CHECK: (type $1 (func (param (ref $struct)) (result i32))) ;; CHECK: (global $A (ref $struct) (struct.new $struct ;; CHECK-NEXT: (i32.const 257) @@ -1917,7 +1917,7 @@ (i32.const 258) )) - ;; CHECK: (func $test (type $1) (result i32) + ;; CHECK: (func $test (type $1) (param $x (ref $struct)) (result i32) ;; CHECK-NEXT: (select ;; CHECK-NEXT: (i32.and ;; CHECK-NEXT: (i32.const 257) @@ -1929,18 +1929,18 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.eq ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (global.get $A) + ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (global.get $A) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $test (result i32) + (func $test (param $x (ref $struct)) (result i32) ;; We can infer this value is one of two things since only two objects exist ;; of this type. We must emit the proper truncated value for them, as the ;; values are truncated into i8. (struct.get_u $struct 0 - (global.get $A) + (local.get $x) ) ) ) From 1759b36d3a66a90b6aebb095dc4f7cca965046e9 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 28 Oct 2025 16:14:50 -0700 Subject: [PATCH 4/9] work --- test/lit/passes/gsi-nontype.wast | 38 +++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/test/lit/passes/gsi-nontype.wast b/test/lit/passes/gsi-nontype.wast index 4d8aa310bef..43fa142fbcc 100644 --- a/test/lit/passes/gsi-nontype.wast +++ b/test/lit/passes/gsi-nontype.wast @@ -23,7 +23,7 @@ ) ) - ;; CHECK: (func $nested-creations (type $1) + ;; CHECK: (func $test (type $1) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (global.get $imported) ;; CHECK-NEXT: ) @@ -38,3 +38,39 @@ ) ) +;; As above, but the global is not immutable, so we cannot optimize. +(module + ;; CHECK: (type $vtable (struct (field funcref))) + (type $vtable (struct funcref)) + + ;; CHECK: (type $1 (func)) + + ;; CHECK: (import "a" "b" (global $imported funcref)) + (import "a" "b" (global $imported funcref)) + + ;; CHECK: (global $vtable (mut (ref $vtable)) (struct.new $vtable + ;; CHECK-NEXT: (global.get $imported) + ;; CHECK-NEXT: )) + (global $vtable (mut (ref $vtable)) + (struct.new $vtable + (global.get $imported) + ) + ) + + ;; CHECK: (func $test (type $1) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $vtable 0 + ;; CHECK-NEXT: (global.get $vtable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + ;; This get reads $import. + (drop + (struct.get $vtable 0 + (global.get $vtable) + ) + ) + ) +) + From 8562d4560825a333e0da85c638ae550dc0fecb2f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 28 Oct 2025 16:18:28 -0700 Subject: [PATCH 5/9] done --- test/lit/passes/gsi-nontype.wast | 67 +++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/test/lit/passes/gsi-nontype.wast b/test/lit/passes/gsi-nontype.wast index 43fa142fbcc..cd082c8826f 100644 --- a/test/lit/passes/gsi-nontype.wast +++ b/test/lit/passes/gsi-nontype.wast @@ -65,7 +65,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $test - ;; This get reads $import. (drop (struct.get $vtable 0 (global.get $vtable) @@ -74,3 +73,69 @@ ) ) +;; As above, but the global does not contain a struct.new, so we cannot +;; optimize. +(module + ;; CHECK: (type $vtable (struct (field funcref))) + (type $vtable (struct funcref)) + + ;; CHECK: (type $1 (func)) + + ;; CHECK: (import "a" "b" (global $imported funcref)) + (import "a" "b" (global $imported funcref)) + + ;; CHECK: (global $vtable (ref null $vtable) (ref.null none)) + (global $vtable (ref null $vtable) (ref.null $vtable)) + + ;; CHECK: (func $test (type $1) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $vtable 0 + ;; CHECK-NEXT: (global.get $vtable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + (drop + (struct.get $vtable 0 + (global.get $vtable) + ) + ) + ) +) + +;; As above, but the value in the struct.new is not constant, so we cannot +;; optimize. +(module + ;; CHECK: (type $table (struct (field anyref))) + (type $table (struct anyref)) + + ;; CHECK: (type $1 (func)) + + ;; CHECK: (import "a" "b" (global $imported funcref)) + (import "a" "b" (global $imported funcref)) + + ;; CHECK: (global $table (ref $table) (struct.new $table + ;; CHECK-NEXT: (struct.new_default $table) + ;; CHECK-NEXT: )) + (global $table (ref $table) + (struct.new $table + (struct.new_default $table) + ) + ) + + ;; CHECK: (func $test (type $1) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $table 0 + ;; CHECK-NEXT: (global.get $table) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + (drop + (struct.get $table 0 + (global.get $table) + ) + ) + ) +) + From 9887800348ae8c3e1c14c6c31be9faaedb1ff2f2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 28 Oct 2025 16:18:33 -0700 Subject: [PATCH 6/9] format --- src/passes/GlobalStructInference.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/passes/GlobalStructInference.cpp b/src/passes/GlobalStructInference.cpp index c3522334667..ba6dd2b1e24 100644 --- a/src/passes/GlobalStructInference.cpp +++ b/src/passes/GlobalStructInference.cpp @@ -531,7 +531,9 @@ struct GlobalStructInference : public Pass { } } - Value readFromStructNew(StructNew* structNew, Index fieldIndex, std::optional& field) { + Value readFromStructNew(StructNew* structNew, + Index fieldIndex, + std::optional& field) { // Find the value read from the struct and represent it as a Value. Value value; PossibleConstantValues constant; From 900e75f1ee0dca9cd5d364a41d4a1635c48819c7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 28 Oct 2025 16:19:05 -0700 Subject: [PATCH 7/9] format --- src/passes/GlobalStructInference.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/passes/GlobalStructInference.cpp b/src/passes/GlobalStructInference.cpp index ba6dd2b1e24..9a732bceab4 100644 --- a/src/passes/GlobalStructInference.cpp +++ b/src/passes/GlobalStructInference.cpp @@ -47,7 +47,7 @@ // // This also optimizes some related things - reads from structs created in // globals - that benefit from the infrastructure here (see unnesting, below), -// even with this type-based approach. +// even without this type-based approach. // // TODO: Only do the case with a select when shrinkLevel == 0? // From 36bfc9692c4bfe55162d4eca9594057c8685f7a7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 28 Oct 2025 16:23:36 -0700 Subject: [PATCH 8/9] format --- test/lit/passes/gsi-nontype.wast | 39 ++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/test/lit/passes/gsi-nontype.wast b/test/lit/passes/gsi-nontype.wast index cd082c8826f..d0017393280 100644 --- a/test/lit/passes/gsi-nontype.wast +++ b/test/lit/passes/gsi-nontype.wast @@ -139,3 +139,42 @@ ) ) +;; Test we can optimize a descriptor read. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $struct (sub (descriptor $desc (struct)))) + (type $struct (sub (descriptor $desc (struct)))) + ;; CHECK: (type $desc (sub (describes $struct (struct)))) + (type $desc (sub (describes $struct (struct)))) + ) + + ;; CHECK: (type $2 (func)) + + ;; CHECK: (import "a" "b" (global $imported (ref (exact $desc)))) + (import "a" "b" (global $imported (ref (exact $desc)))) + + ;; CHECK: (global $struct (ref $struct) (struct.new_default $struct + ;; CHECK-NEXT: (global.get $imported) + ;; CHECK-NEXT: )) + (global $struct (ref $struct) + (struct.new $struct + (global.get $imported) + ) + ) + + ;; CHECK: (func $test (type $2) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (global.get $imported) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + ;; This get reads $import. + (drop + (ref.get_desc $struct + (global.get $struct) + ) + ) + ) +) + From e914ce4e645cc203a8616b46371d0d4aa532b398 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 29 Oct 2025 07:52:13 -0700 Subject: [PATCH 9/9] fix --- src/passes/GlobalStructInference.cpp | 2 +- test/lit/passes/gsi-nontype.wast | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/passes/GlobalStructInference.cpp b/src/passes/GlobalStructInference.cpp index 9a732bceab4..166c3f58fee 100644 --- a/src/passes/GlobalStructInference.cpp +++ b/src/passes/GlobalStructInference.cpp @@ -348,7 +348,7 @@ struct GlobalStructInference : public Pass { // a read from an immutable global. if (auto* get = ref->dynCast()) { auto* global = wasm.getGlobal(get->name); - if (!global->mutable_) { + if (!global->mutable_ && !global->imported()) { if (auto* structNew = global->init->dynCast()) { auto value = readFromStructNew(structNew, fieldIndex, field); // TODO: handle non-constant values using unnesting, as below. diff --git a/test/lit/passes/gsi-nontype.wast b/test/lit/passes/gsi-nontype.wast index d0017393280..efe9082de4e 100644 --- a/test/lit/passes/gsi-nontype.wast +++ b/test/lit/passes/gsi-nontype.wast @@ -178,3 +178,29 @@ ) ) +;; Check we do not error on a global.get of an imported global. +(module + ;; CHECK: (type $vtable (struct (field funcref))) + (type $vtable (struct funcref)) + + ;; CHECK: (type $1 (func)) + + ;; CHECK: (import "a" "b" (global $imported (ref $vtable))) + (import "a" "b" (global $imported (ref $vtable))) + + ;; CHECK: (func $test (type $1) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $vtable 0 + ;; CHECK-NEXT: (global.get $imported) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + (drop + (struct.get $vtable 0 + (global.get $imported) + ) + ) + ) +) +