From da067871ee87632e600de42c9a44060fa98e61c7 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Wed, 11 Nov 2026 00:25:46 +0000 Subject: [PATCH] Relax bodies of functions when their signature can't be changed --- src/pass.h | 2 +- src/passes/RelaxNullability.cpp | 36 +++++++++++------ src/passes/pass-utils.h | 2 +- test/lit/passes/relax-nullability.wast | 55 ++++++++++++++++++++++---- 4 files changed, 73 insertions(+), 22 deletions(-) diff --git a/src/pass.h b/src/pass.h index 3e700c19a90..9b72ddf21d7 100644 --- a/src/pass.h +++ b/src/pass.h @@ -486,7 +486,7 @@ class Pass { // Note: This is called on the registered pass instance concurrently across // threads, so implementations must be thread-safe and cannot rely on // getPassRunner() or getPassOptions(). - virtual bool runsOnFunction(Function* func) { return true; } + virtual bool runsOnFunction(Function* func) const { return true; } // Many passes can remove effects, for example, by finding some path is not // reached and removing a throw or a call there. The few passes that *add* diff --git a/src/passes/RelaxNullability.cpp b/src/passes/RelaxNullability.cpp index e328df15798..1333078695e 100644 --- a/src/passes/RelaxNullability.cpp +++ b/src/passes/RelaxNullability.cpp @@ -53,8 +53,6 @@ bool isRelaxable(Function* func, // preserve the API boundary. Exported functions are implicitly 'referenced' // (i.e. they may be the target of a call_ref even if they never appear in a // ref.func), so no need to check that a second time. - // - // TODO: Relax private referenced funcs + bodies of public referenced funcs. return !func->imported() && !referencedFuncs.contains(func); } @@ -160,6 +158,11 @@ struct LocalsToNullable : WalkerPass> { // false if only dead local definitions were changed. bool changed = false; + // true iff the callee relaxed the params of the particular function that + // we're looking at. If not, then we know not to update the types of + // local.get or local.tee on those params. + bool signatureWasUpdated = false; + LocalsToNullable(const std::unordered_set& referencedFuncs) : referencedFuncs(referencedFuncs) {} @@ -169,14 +172,12 @@ struct LocalsToNullable : WalkerPass> { bool isFunctionParallel() override { return true; } - bool runsOnFunction(Function* func) override { - // If we didn't relax the params, don't update local.gets / local.tee which - // might touch unrelaxed params. - // TODO: Relax locals even if the params weren't relaxed. - return isRelaxable(func, referencedFuncs); + bool runsOnFunction(Function* func) const override { + return func->vars.size() > 0 || isRelaxable(func, referencedFuncs); } void doWalkFunction(Function* func) { + signatureWasUpdated = isRelaxable(func, referencedFuncs); // 1. Make all local definitions nullable and update gets and sets. // 2. Refinalize to fixup block types // 3. Insert ref.as_non_null where a parent demands it, e.g. return values, @@ -185,13 +186,14 @@ struct LocalsToNullable : WalkerPass> { // Refinalize is not needed after step 3 because a block never demands a // non-null and thus step 3 makes no difference to block types. + bool changedLocals = false; for (Index i = 0; i < func->vars.size(); ++i) { - std::ignore = makeNullable(func->vars[i]); + changedLocals |= makeNullable(func->vars[i]); + } + if (!changedLocals && !signatureWasUpdated) { + return; } - // TODO: could consider doing this only if locals changed - // but a function param could have changed and we can't tell that easily - // from here. Super::doWalkFunction(func); if (!changed) { @@ -203,9 +205,19 @@ struct LocalsToNullable : WalkerPass> { fixupReturn(&func->body, getModule(), func); } - void visitLocalGet(LocalGet* curr) { changed |= makeNullable(curr->type); } + void visitLocalGet(LocalGet* curr) { + if (!signatureWasUpdated && getFunction()->isParam(curr->index)) { + return; + } + + changed |= makeNullable(curr->type); + } void visitLocalSet(LocalSet* curr) { + if (!signatureWasUpdated && getFunction()->isParam(curr->index)) { + return; + } + if (curr->isTee()) { changed |= makeNullable(curr->type); } diff --git a/src/passes/pass-utils.h b/src/passes/pass-utils.h index 221ca2f58db..d7b1b59481a 100644 --- a/src/passes/pass-utils.h +++ b/src/passes/pass-utils.h @@ -66,7 +66,7 @@ struct FilteredPass : public Pass { return pass->requiresNonNullableLocalFixups(); } - bool runsOnFunction(Function* func) override { + bool runsOnFunction(Function* func) const override { return relevantFuncs.contains(func) && pass->runsOnFunction(func); } diff --git a/test/lit/passes/relax-nullability.wast b/test/lit/passes/relax-nullability.wast index 5069fc18c2a..11a4bd50b8a 100644 --- a/test/lit/passes/relax-nullability.wast +++ b/test/lit/passes/relax-nullability.wast @@ -213,26 +213,65 @@ ;; CHECK: (export "exported-not-relaxed" (func $exported-not-relaxed)) ;; CHECK: (func $exported-not-relaxed (type $shared-sig) (param $ref (ref any)) (result (ref any)) - ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (local $l anyref) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (block (result anyref) + ;; CHECK-NEXT: (local.set $l + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $l + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $l) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $exported-not-relaxed (export "exported-not-relaxed") (param $ref (ref any)) (result (ref any)) - (local.get $ref) + ;; Function is exported, so we can't mutate its signature. Relax its locals + ;; but not its params. + (local $l (ref any)) + (local.set $l (local.get $ref)) + (drop (local.tee $l (local.get $ref))) + (local.get $l) ) ;; CHECK: (func $referenced-not-relaxed (type $shared-sig) (param $ref (ref any)) (result (ref any)) - ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (local $l anyref) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (block (result anyref) + ;; CHECK-NEXT: (local.set $l + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $l) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $referenced-not-relaxed (type $shared-sig) (param $ref (ref any)) (result (ref any)) - ;; Function referenced via ref.func: cannot be relaxed - (local.get $ref) + ;; Function referenced via ref.func. Relax its locals but not its params. + (local $l (ref any)) + (local.set $l (local.get $ref)) + (local.get $l) ) ;; CHECK: (func $referenced-via-table-not-relaxed (type $shared-sig) (param $ref (ref any)) (result (ref any)) - ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (local $l anyref) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (block (result anyref) + ;; CHECK-NEXT: (local.set $l + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $l) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $referenced-via-table-not-relaxed (type $shared-sig) (param $ref (ref any)) (result (ref any)) - ;; Function referenced via elem declare: cannot be relaxed - (local.get $ref) + ;; Function referenced via elem declare. Relax its locals but not its + ;; params. + (local $l (ref any)) + (local.set $l (local.get $ref)) + (local.get $l) ) (elem (i32.const 0) $referenced-via-table-not-relaxed)