From 019fe4cee669002934d1003f3d84387a002de85b Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Thu, 23 Jul 2026 21:27:47 +0000 Subject: [PATCH] TypeSSA for ref.func --- src/passes/TypeSSA.cpp | 24 +++++++ .../passes/global-effects-typessa-vacuum.wast | 65 +++++++++++++++++++ test/lit/passes/type-ssa-func.wast | 43 ++++++++++++ 3 files changed, 132 insertions(+) create mode 100644 test/lit/passes/global-effects-typessa-vacuum.wast create mode 100644 test/lit/passes/type-ssa-func.wast diff --git a/src/passes/TypeSSA.cpp b/src/passes/TypeSSA.cpp index a913faa6c2b..710caf31325 100644 --- a/src/passes/TypeSSA.cpp +++ b/src/passes/TypeSSA.cpp @@ -127,6 +127,10 @@ struct Analyzer news.push_back(curr); visitExpression(curr); } + void visitRefFunc(RefFunc* curr) { + news.push_back(curr); + visitExpression(curr); + } // Find casts to exact types. Allocations of these types will not be able to // be optimized. @@ -298,6 +302,10 @@ struct TypeSSA : public Pass { News newsToModify; + // Track which functions we have already queued for modification, so we only + // create one new type per function, satisfying validation rules. + std::unordered_set processedFunctions; + // As we generate new names, use a consistent index. Index nameCounter = 0; @@ -308,6 +316,11 @@ struct TypeSSA : public Pass { disallowed = disallowedTypes.contains(curr->type.getHeapType()); } if (!disallowed && isInteresting(curr)) { + if (auto* refFunc = curr->dynCast()) { + if (!processedFunctions.insert(refFunc->func).second) { + continue; + } + } newsToModify.push_back(curr); } } @@ -347,6 +360,8 @@ struct TypeSSA : public Pass { builder[i] = oldType.getArray(); break; case HeapTypeKind::Func: + builder[i] = oldType.getSignature(); + break; case HeapTypeKind::Cont: case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); @@ -380,6 +395,10 @@ struct TypeSSA : public Pass { auto newType = newTypes[i]; curr->type = Type(newType, NonNullable, Exact); + if (auto* refFunc = curr->dynCast()) { + module->getFunction(refFunc->func)->type = curr->type; + } + // If the old type has a nice name, make a nice name for the new one. if (typeNames.contains(oldType)) { auto intendedName = typeNames[oldType].name.toString() + '_' + @@ -491,6 +510,11 @@ struct TypeSSA : public Pass { } } return true; + } else if (curr->is()) { + // Any ref.func is interesting to type-ssa as assigning it a unique + // type lets passes distinguish references to this function from others + // with the same signature. + return true; } else { WASM_UNREACHABLE("unknown new"); } diff --git a/test/lit/passes/global-effects-typessa-vacuum.wast b/test/lit/passes/global-effects-typessa-vacuum.wast new file mode 100644 index 00000000000..ae11020e134 --- /dev/null +++ b/test/lit/passes/global-effects-typessa-vacuum.wast @@ -0,0 +1,65 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: foreach %s %t wasm-opt -all --closed-world --generate-global-effects --vacuum -S -o - | filecheck %s +;; RUN: foreach %s %t wasm-opt -all --closed-world --type-ssa --generate-global-effects --vacuum -S -o - | filecheck %s --check-prefix=TYPESSA + +(module + ;; CHECK: (type $sig (sub (func))) + ;; TYPESSA: (type $sig (sub (func))) + (type $sig (sub (func))) + + (table 1 1 funcref) + (elem (i32.const 0) $f2) + + ;; CHECK: (func $f1 (type $sig) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; TYPESSA: (func $f1 (type $sig_2) + ;; TYPESSA-NEXT: (nop) + ;; TYPESSA-NEXT: ) + (func $f1 (type $sig) + (drop (i32.const 1)) + ) + ;; CHECK: (func $f2 (type $sig) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; TYPESSA: (func $f2 (type $sig_1) + ;; TYPESSA-NEXT: (unreachable) + ;; TYPESSA-NEXT: ) + (func $f2 (type $sig) + (unreachable) + ) + + ;; CHECK: (func $test-call_ref (type $1) + ;; CHECK-NEXT: (call_ref $sig + ;; CHECK-NEXT: (ref.func $f1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; TYPESSA: (func $test-call_ref (type $3) + ;; TYPESSA-NEXT: (nop) + ;; TYPESSA-NEXT: ) + (func $test-call_ref + (drop (ref.func $f2)) + ;; Without TypeSSA, this calls $f1 which has no effects, but GlobalEffects + ;; sees it has type $sig, and $f2 is also taken and has type $sig. + ;; With TypeSSA, this call_ref becomes typed as a unique subtype for $f1, + ;; so it is known to have no side effects and is vacuumed! + (call_ref $sig (ref.func $f1)) + ) + + ;; CHECK: (func $test-call_indirect (type $2) (param $0 i32) + ;; CHECK-NEXT: (call_indirect $0 (type $sig) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; TYPESSA: (func $test-call_indirect (type $4) (param $0 i32) + ;; TYPESSA-NEXT: (call_indirect $0 (type $sig) + ;; TYPESSA-NEXT: (local.get $0) + ;; TYPESSA-NEXT: ) + ;; TYPESSA-NEXT: ) + (func $test-call_indirect (param i32) + ;; For call_indirect, TypeSSA doesn't change the signature of the call. + ;; It will still target $sig. Since both unique subtypes for $f1 and $f2 + ;; are subtypes of $sig, it will still see the trapping effect of $f2. + (call_indirect (type $sig) (local.get 0)) + ) +) diff --git a/test/lit/passes/type-ssa-func.wast b/test/lit/passes/type-ssa-func.wast new file mode 100644 index 00000000000..15deaf734bc --- /dev/null +++ b/test/lit/passes/type-ssa-func.wast @@ -0,0 +1,43 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; RUN: foreach %s %t wasm-opt --type-ssa -all -S -o - | filecheck %s + +(module + ;; CHECK: (type $sig (sub (func))) + (type $sig (sub (func))) + + ;; CHECK: (rec + ;; CHECK-NEXT: (type $sig_1 (sub $sig (func))) + + ;; CHECK: (type $sig_2 (sub $sig (func))) + + ;; CHECK: (type $3 (func)) + + ;; CHECK: (elem declare func $f1 $f2) + + ;; CHECK: (func $f1 (type $sig_1) + ;; CHECK-NEXT: ) + (func $f1 (type $sig) + ) + + ;; CHECK: (func $f2 (type $sig_2) + ;; CHECK-NEXT: ) + (func $f2 (type $sig) + ) + + ;; CHECK: (func $test (type $3) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.func $f1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.func $f2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.func $f1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + (drop (ref.func $f1)) + (drop (ref.func $f2)) + (drop (ref.func $f1)) + ) +)