From ccc5be08d60b6549e977e8eb48921414be4cc121 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 18 Sep 2025 09:41:01 -0700 Subject: [PATCH 01/10] start --- src/passes/GlobalStructInference.cpp | 55 ++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/src/passes/GlobalStructInference.cpp b/src/passes/GlobalStructInference.cpp index e2b728fc10b..14162d9bc90 100644 --- a/src/passes/GlobalStructInference.cpp +++ b/src/passes/GlobalStructInference.cpp @@ -79,6 +79,8 @@ struct GlobalStructInference : public Pass { // optimizable it will have an entry here, and not if not. std::unordered_map> typeGlobals; + std::unique_ptr subTypes; + void run(Module* module) override { if (!module->features.hasGC()) { return; @@ -208,6 +210,12 @@ struct GlobalStructInference : public Pass { return; } + // When CD is enabled, we can optimize to ref.get_desc, depending on the + // presence of subtypes. + if (module->features.hasCustomDescriptors()) { + subTypes = std::make_unique(*module); + } + // 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. @@ -528,6 +536,53 @@ struct GlobalStructInference : public Pass { right)); } + void visitRefCast(RefCast* curr) { + // When we see (ref.cast $T), and the type has a descriptor, and that + // desceriptor only has a single global, then we can do (ref.cast_desc) + // using the descriptor. Descriptor XXX + // casts are usually more efficient than normal ones (and even more so + // if we get lucky and are in a loop, where the global.get of the + // descriptor can be hoisted). + + // Check if we have a descriptor. + auto type = curr->type; + if (type == Type::unreachable) { + return; + } + auto heapType = type.getHeapType(); + auto desc = heapType.getDescriptorType(); + if (!desc) { + return; + } + + // Check if the type has no subtypes, as a ref.cast_desc will find + // precisely that type and nothing else. + if (!parent.subTypes->getStrictSubTypes(heapType).empty()) { + return; + } + + // Check if we have a single global for the descriptor. + auto iter = parent.typeGlobals.find(*desc); + if (iter == parent.typeGlobals.end()) { + return; + } + const auto& globals = iter->second; + if (globals.size() != 1) { + return; + } + + // We can optimize! + auto global = globals[0]; + auto& wasm = *getModule(); + Builder builder(wasm); + auto* getGlobal = + builder.makeGlobalGet(global, wasm.getGlobal(global)->type); + auto* castDesc = builder.makeRefCast(curr->ref, getGlobal, curr->type); + replaceCurrent(castDesc); + + // TODO nullable cast? + } + void visitFunction(Function* func) { if (refinalize) { ReFinalize().walkFunctionInModule(func, getModule()); From f4b9e45db3dc91971c25d2f99df335d120fe42c2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 18 Sep 2025 10:33:43 -0700 Subject: [PATCH 02/10] test --- src/passes/GlobalStructInference.cpp | 1 + test/lit/passes/gsi-desc.wast | 52 ++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/src/passes/GlobalStructInference.cpp b/src/passes/GlobalStructInference.cpp index 14162d9bc90..4183405fac4 100644 --- a/src/passes/GlobalStructInference.cpp +++ b/src/passes/GlobalStructInference.cpp @@ -543,6 +543,7 @@ struct GlobalStructInference : public Pass { // casts are usually more efficient than normal ones (and even more so // if we get lucky and are in a loop, where the global.get of the // descriptor can be hoisted). + // TODO: only do this when shrinkLevel == 0? // Check if we have a descriptor. auto type = curr->type; diff --git a/test/lit/passes/gsi-desc.wast b/test/lit/passes/gsi-desc.wast index 0ca1806b568..5ffb4902434 100644 --- a/test/lit/passes/gsi-desc.wast +++ b/test/lit/passes/gsi-desc.wast @@ -186,3 +186,55 @@ ) ) +;; Two types with descriptors and subtyping between them. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (descriptor $super.desc (struct)))) + (type $super (sub (descriptor $super.desc (struct)))) + ;; CHECK: (type $super.desc (sub (describes $super (struct)))) + (type $super.desc (sub (describes $super (struct)))) + + ;; CHECK: (type $sub (sub $super (descriptor $sub.desc (struct)))) + (type $sub (sub $super (descriptor $sub.desc (struct)))) + ;; CHECK: (type $sub.desc (sub $super.desc (describes $sub (struct)))) + (type $sub.desc (sub $super.desc (describes $sub (struct)))) + ) + + ;; CHECK: (type $4 (func (param anyref))) + + ;; CHECK: (global $sub.desc (ref $sub.desc) (struct.new_default $sub.desc)) + (global $sub.desc (ref $sub.desc) (struct.new $sub.desc)) + + ;; CHECK: (global $super.desc (ref $super.desc) (struct.new_default $super.desc)) + (global $super.desc (ref $super.desc) (struct.new $super.desc)) + + ;; CHECK: (func $test (type $4) (param $any anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (ref $super) + ;; CHECK-NEXT: (local.get $any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast_desc (ref $sub) + ;; CHECK-NEXT: (local.get $any) + ;; CHECK-NEXT: (global.get $sub.desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $any anyref) + ;; The second cast here is optimizable: it can only be to a single type with + ;; no subtypes, so we can use ref.cast_desc. + (drop + (ref.cast (ref $super) + (local.get $any) + ) + ) + (drop + (ref.cast (ref $sub) + (local.get $any) + ) + ) + ) +) + From 76916738151c604ac173b519efd4c4403f8df0cf Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 18 Sep 2025 10:34:20 -0700 Subject: [PATCH 03/10] test --- test/lit/passes/gsi-desc.wast | 36 +++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/test/lit/passes/gsi-desc.wast b/test/lit/passes/gsi-desc.wast index 5ffb4902434..38a425b8484 100644 --- a/test/lit/passes/gsi-desc.wast +++ b/test/lit/passes/gsi-desc.wast @@ -190,35 +190,35 @@ (module (rec ;; CHECK: (rec - ;; CHECK-NEXT: (type $super (sub (descriptor $super.desc (struct)))) - (type $super (sub (descriptor $super.desc (struct)))) - ;; CHECK: (type $super.desc (sub (describes $super (struct)))) - (type $super.desc (sub (describes $super (struct)))) - - ;; CHECK: (type $sub (sub $super (descriptor $sub.desc (struct)))) - (type $sub (sub $super (descriptor $sub.desc (struct)))) - ;; CHECK: (type $sub.desc (sub $super.desc (describes $sub (struct)))) - (type $sub.desc (sub $super.desc (describes $sub (struct)))) + ;; CHECK-NEXT: (type $A (sub (descriptor $A.desc (struct)))) + (type $A (sub (descriptor $A.desc (struct)))) + ;; CHECK: (type $A.desc (sub (describes $A (struct)))) + (type $A.desc (sub (describes $A (struct)))) + + ;; CHECK: (type $B (sub $A (descriptor $B.desc (struct)))) + (type $B (sub $A (descriptor $B.desc (struct)))) + ;; CHECK: (type $B.desc (sub $A.desc (describes $B (struct)))) + (type $B.desc (sub $A.desc (describes $B (struct)))) ) ;; CHECK: (type $4 (func (param anyref))) - ;; CHECK: (global $sub.desc (ref $sub.desc) (struct.new_default $sub.desc)) - (global $sub.desc (ref $sub.desc) (struct.new $sub.desc)) + ;; CHECK: (global $B.desc (ref $B.desc) (struct.new_default $B.desc)) + (global $B.desc (ref $B.desc) (struct.new $B.desc)) - ;; CHECK: (global $super.desc (ref $super.desc) (struct.new_default $super.desc)) - (global $super.desc (ref $super.desc) (struct.new $super.desc)) + ;; CHECK: (global $A.desc (ref $A.desc) (struct.new_default $A.desc)) + (global $A.desc (ref $A.desc) (struct.new $A.desc)) ;; CHECK: (func $test (type $4) (param $any anyref) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast (ref $super) + ;; CHECK-NEXT: (ref.cast (ref $A) ;; CHECK-NEXT: (local.get $any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast_desc (ref $sub) + ;; CHECK-NEXT: (ref.cast_desc (ref $B) ;; CHECK-NEXT: (local.get $any) - ;; CHECK-NEXT: (global.get $sub.desc) + ;; CHECK-NEXT: (global.get $B.desc) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -226,12 +226,12 @@ ;; The second cast here is optimizable: it can only be to a single type with ;; no subtypes, so we can use ref.cast_desc. (drop - (ref.cast (ref $super) + (ref.cast (ref $A) (local.get $any) ) ) (drop - (ref.cast (ref $sub) + (ref.cast (ref $B) (local.get $any) ) ) From 10d80a7e0316abc99ec2ab681e11beee1dd1db4d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 18 Sep 2025 10:45:02 -0700 Subject: [PATCH 04/10] test --- test/lit/passes/gsi-desc.wast | 108 +++++++++++++++++++++++++++++++++- 1 file changed, 106 insertions(+), 2 deletions(-) diff --git a/test/lit/passes/gsi-desc.wast b/test/lit/passes/gsi-desc.wast index 38a425b8484..482f495e1a0 100644 --- a/test/lit/passes/gsi-desc.wast +++ b/test/lit/passes/gsi-desc.wast @@ -203,12 +203,64 @@ ;; CHECK: (type $4 (func (param anyref))) + ;; CHECK: (global $A.desc (ref $A.desc) (struct.new_default $A.desc)) + (global $A.desc (ref $A.desc) (struct.new $A.desc)) + ;; CHECK: (global $B.desc (ref $B.desc) (struct.new_default $B.desc)) (global $B.desc (ref $B.desc) (struct.new $B.desc)) + ;; CHECK: (func $test (type $4) (param $any anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (ref $A) + ;; CHECK-NEXT: (local.get $any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast_desc (ref $B) + ;; CHECK-NEXT: (local.get $any) + ;; CHECK-NEXT: (global.get $B.desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $any anyref) + ;; The second cast here is optimizable: it can only be to a single type with + ;; no subtypes, so we can use ref.cast_desc. + (drop + (ref.cast (ref $A) + (local.get $any) + ) + ) + (drop + (ref.cast (ref $B) + (local.get $any) + ) + ) + ) +) + +;; As above, but without subtyping between $A and $B. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (sub (descriptor $A.desc (struct)))) + (type $A (sub (descriptor $A.desc (struct)))) + ;; CHECK: (type $A.desc (sub (describes $A (struct)))) + (type $A.desc (sub (describes $A (struct)))) + + ;; CHECK: (type $B (sub (descriptor $B.desc (struct)))) + (type $B (sub (descriptor $B.desc (struct)))) + ;; CHECK: (type $B.desc (sub $A.desc (describes $B (struct)))) + (type $B.desc (sub $A.desc (describes $B (struct)))) + ) + + ;; CHECK: (type $4 (func (param anyref))) + ;; CHECK: (global $A.desc (ref $A.desc) (struct.new_default $A.desc)) (global $A.desc (ref $A.desc) (struct.new $A.desc)) + ;; CHECK: (global $B.desc (ref $B.desc) (struct.new_default $B.desc)) + (global $B.desc (ref $B.desc) (struct.new $B.desc)) + ;; CHECK: (func $test (type $4) (param $any anyref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $A) @@ -223,8 +275,60 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $test (param $any anyref) - ;; The second cast here is optimizable: it can only be to a single type with - ;; no subtypes, so we can use ref.cast_desc. + ;; We still cannot optimize $A: while $A has no subtypes, the descriptor + ;; $A.desc has a subtype. We could optimize this TODO + (drop + (ref.cast (ref $A) + (local.get $any) + ) + ) + (drop + (ref.cast (ref $B) + (local.get $any) + ) + ) + ) +) + +;; As above, but without subtyping between $A.desc and $B.desc. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (sub (descriptor $A.desc (struct)))) + (type $A (sub (descriptor $A.desc (struct)))) + ;; CHECK: (type $A.desc (sub (describes $A (struct)))) + (type $A.desc (sub (describes $A (struct)))) + + ;; CHECK: (type $B (sub (descriptor $B.desc (struct)))) + (type $B (sub (descriptor $B.desc (struct)))) + ;; CHECK: (type $B.desc (sub (describes $B (struct)))) + (type $B.desc (sub (describes $B (struct)))) + ) + + ;; CHECK: (type $4 (func (param anyref))) + + ;; CHECK: (global $A.desc (ref $A.desc) (struct.new_default $A.desc)) + (global $A.desc (ref $A.desc) (struct.new $A.desc)) + + ;; CHECK: (global $B.desc (ref $B.desc) (struct.new_default $B.desc)) + (global $B.desc (ref $B.desc) (struct.new $B.desc)) + + ;; CHECK: (func $test (type $4) (param $any anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast_desc (ref $A) + ;; CHECK-NEXT: (local.get $any) + ;; CHECK-NEXT: (global.get $A.desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast_desc (ref $B) + ;; CHECK-NEXT: (local.get $any) + ;; CHECK-NEXT: (global.get $B.desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $any anyref) + ;; We can fully optimize these two independent cases. (drop (ref.cast (ref $A) (local.get $any) From 8bebd6ba3f4adb0316a1f68168707b9a75188894 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 18 Sep 2025 10:46:03 -0700 Subject: [PATCH 05/10] test --- test/lit/passes/gsi-desc.wast | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/test/lit/passes/gsi-desc.wast b/test/lit/passes/gsi-desc.wast index 482f495e1a0..325592b1f48 100644 --- a/test/lit/passes/gsi-desc.wast +++ b/test/lit/passes/gsi-desc.wast @@ -342,3 +342,38 @@ ) ) +;; Two descriptor instances in globals. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (sub (descriptor $A.desc (struct)))) + (type $A (sub (descriptor $A.desc (struct)))) + ;; CHECK: (type $A.desc (sub (describes $A (struct)))) + (type $A.desc (sub (describes $A (struct)))) + ) + + ;; CHECK: (type $2 (func (param anyref))) + + ;; CHECK: (global $A.desc (ref $A.desc) (struct.new_default $A.desc)) + (global $A.desc (ref $A.desc) (struct.new $A.desc)) + + ;; CHECK: (global $A.desc2 (ref $A.desc) (struct.new_default $A.desc)) + (global $A.desc2 (ref $A.desc) (struct.new $A.desc)) + + ;; CHECK: (func $test (type $2) (param $any anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (ref $A) + ;; CHECK-NEXT: (local.get $any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $any anyref) + ;; We do not optimize here. TODO: we could with a select + (drop + (ref.cast (ref $A) + (local.get $any) + ) + ) + ) +) + From 4475e505f1169d7cea0674be47b244dd16ccc1d2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 18 Sep 2025 10:46:42 -0700 Subject: [PATCH 06/10] test --- test/lit/passes/gsi-desc.wast | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/lit/passes/gsi-desc.wast b/test/lit/passes/gsi-desc.wast index 325592b1f48..1cfe3916b36 100644 --- a/test/lit/passes/gsi-desc.wast +++ b/test/lit/passes/gsi-desc.wast @@ -366,6 +366,14 @@ ;; CHECK-NEXT: (local.get $any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block ;; (replaces unreachable RefCast we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $test (param $any anyref) ;; We do not optimize here. TODO: we could with a select @@ -374,6 +382,12 @@ (local.get $any) ) ) + ;; We do not error on unreachable casts. + (drop + (ref.cast (ref $A) + (unreachable) + ) + ) ) ) From 891f616cd06ccdd631c11755e799ef60264e9f07 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 18 Sep 2025 10:47:59 -0700 Subject: [PATCH 07/10] test --- test/lit/passes/gsi-desc.wast | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test/lit/passes/gsi-desc.wast b/test/lit/passes/gsi-desc.wast index 1cfe3916b36..33c16f48c79 100644 --- a/test/lit/passes/gsi-desc.wast +++ b/test/lit/passes/gsi-desc.wast @@ -391,3 +391,26 @@ ) ) +(module + ;; CHECK: (type $0 (func (param anyref))) + + ;; CHECK: (type $A (sub (struct))) + (type $A (sub (struct))) + + ;; CHECK: (func $test (type $0) (param $any anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (ref $A) + ;; CHECK-NEXT: (local.get $any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $any anyref) + ;; We do not handle casts to types without descriptors. + (drop + (ref.cast (ref $A) + (local.get $any) + ) + ) + ) +) + From c3d3c5aa7a6ffe2bc0dfca3c4f39f91f41c48d39 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 18 Sep 2025 10:51:33 -0700 Subject: [PATCH 08/10] test --- test/lit/passes/gsi-desc.wast | 63 +++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/test/lit/passes/gsi-desc.wast b/test/lit/passes/gsi-desc.wast index 33c16f48c79..dfbc520962c 100644 --- a/test/lit/passes/gsi-desc.wast +++ b/test/lit/passes/gsi-desc.wast @@ -342,6 +342,35 @@ ) ) +;; Zero descriptor instances in globals. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (sub (descriptor $A.desc (struct)))) + (type $A (sub (descriptor $A.desc (struct)))) + ;; CHECK: (type $A.desc (sub (describes $A (struct)))) + (type $A.desc (sub (describes $A (struct)))) + ) + + ;; CHECK: (type $2 (func (param anyref))) + + ;; CHECK: (func $test (type $2) (param $any anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (ref $A) + ;; CHECK-NEXT: (local.get $any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $any anyref) + ;; We do not optimize here. TODO: we could make this trap + (drop + (ref.cast (ref $A) + (local.get $any) + ) + ) + ) +) + ;; Two descriptor instances in globals. (module (rec @@ -414,3 +443,37 @@ ) ) +;; Nullable cast. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (sub (descriptor $A.desc (struct)))) + (type $A (sub (descriptor $A.desc (struct)))) + ;; CHECK: (type $A.desc (sub (describes $A (struct)))) + (type $A.desc (sub (describes $A (struct)))) + ) + + ;; CHECK: (type $2 (func (param anyref))) + + ;; CHECK: (global $A.desc (ref $A.desc) (struct.new_default $A.desc)) + (global $A.desc (ref $A.desc) (struct.new $A.desc)) + + ;; CHECK: (func $test (type $2) (param $any anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast_desc (ref null $A) + ;; CHECK-NEXT: (local.get $any) + ;; CHECK-NEXT: (global.get $A.desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $any anyref) + ;; The cast is nullable, which we can still optimize: null will succeed as + ;; expected. + (drop + (ref.cast (ref null $A) + (local.get $any) + ) + ) + ) +) + From 46b37d348c4025c7e618dd4507d96b569df5f5ea Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 18 Sep 2025 11:01:30 -0700 Subject: [PATCH 09/10] test --- src/passes/GlobalStructInference.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/passes/GlobalStructInference.cpp b/src/passes/GlobalStructInference.cpp index 4183405fac4..879c9e05dc4 100644 --- a/src/passes/GlobalStructInference.cpp +++ b/src/passes/GlobalStructInference.cpp @@ -557,7 +557,7 @@ struct GlobalStructInference : public Pass { } // Check if the type has no subtypes, as a ref.cast_desc will find - // precisely that type and nothing else. + // precisely that type and nothing else. TODO: exact types too if (!parent.subTypes->getStrictSubTypes(heapType).empty()) { return; } @@ -580,8 +580,6 @@ struct GlobalStructInference : public Pass { builder.makeGlobalGet(global, wasm.getGlobal(global)->type); auto* castDesc = builder.makeRefCast(curr->ref, getGlobal, curr->type); replaceCurrent(castDesc); - - // TODO nullable cast? } void visitFunction(Function* func) { From 9a9c1221e9372ffd94f0839d328a795d85652a9a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 19 Sep 2025 09:44:49 -0700 Subject: [PATCH 10/10] TODO for exact casts --- src/passes/GlobalStructInference.cpp | 16 ++++++++-------- test/lit/passes/gsi-desc.wast | 28 ++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/passes/GlobalStructInference.cpp b/src/passes/GlobalStructInference.cpp index 879c9e05dc4..6f3514275b4 100644 --- a/src/passes/GlobalStructInference.cpp +++ b/src/passes/GlobalStructInference.cpp @@ -538,11 +538,10 @@ struct GlobalStructInference : public Pass { void visitRefCast(RefCast* curr) { // When we see (ref.cast $T), and the type has a descriptor, and that - // desceriptor only has a single global, then we can do (ref.cast_desc) - // using the descriptor. Descriptor XXX - // casts are usually more efficient than normal ones (and even more so - // if we get lucky and are in a loop, where the global.get of the - // descriptor can be hoisted). + // descriptor only has a single global, then we can do (ref.cast_desc) + // using the descriptor. Descriptor casts are usually more efficient + // than normal ones (and even more so if we get lucky and are in a loop, + // where the global.get of the descriptor can be hoisted). // TODO: only do this when shrinkLevel == 0? // Check if we have a descriptor. @@ -556,9 +555,10 @@ struct GlobalStructInference : public Pass { return; } - // Check if the type has no subtypes, as a ref.cast_desc will find - // precisely that type and nothing else. TODO: exact types too - if (!parent.subTypes->getStrictSubTypes(heapType).empty()) { + // Check if the type has no (relevant) subtypes, as a ref.cast_desc will + // find precisely that type and nothing else. + if (!type.isExact() && + !parent.subTypes->getStrictSubTypes(heapType).empty()) { return; } diff --git a/test/lit/passes/gsi-desc.wast b/test/lit/passes/gsi-desc.wast index dfbc520962c..c109bd5ca5e 100644 --- a/test/lit/passes/gsi-desc.wast +++ b/test/lit/passes/gsi-desc.wast @@ -236,6 +236,34 @@ ) ) ) + + ;; CHECK: (func $test-exact (type $4) (param $any anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (ref (exact $A)) + ;; CHECK-NEXT: (local.get $any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast_desc (ref $B) + ;; CHECK-NEXT: (local.get $any) + ;; CHECK-NEXT: (global.get $B.desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test-exact (param $any anyref) + ;; When using exact casts, we can optimize both. TODO: atm we do not + ;; optimize $A, as we propagate on |typeGlobals|. + (drop + (ref.cast (ref (exact $A)) + (local.get $any) + ) + ) + (drop + (ref.cast (ref (exact $B)) + (local.get $any) + ) + ) + ) ) ;; As above, but without subtyping between $A and $B.