From 5daee4c23a8da306780237eb2baaabf5627c003f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 20 Jan 2026 16:12:58 -0800 Subject: [PATCH 01/58] go --- src/ir/effects.h | 41 ++++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/src/ir/effects.h b/src/ir/effects.h index b9e07a87bf8..ae55b99e728 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -171,6 +171,16 @@ class EffectAnalyzer { // more here.) bool hasReturnCallThrow = false; + // Similar to calls (an effect where anything can happen), but only if this is + // moved. That is, this has no effects where it is presently located, but if + // moved, it will have effects. As a result, this can be removed from its + // current location, but if we want to move it around, we must consider it as + // having the same effects as a call. + // + // Concretely, this does not influence has*Effects(), but it does influence + // invalidates(), which compares effects between things that might move. + bool callsIfMoved = false; + // Helper functions to check for various effect types bool accessesLocal() const { @@ -246,7 +256,8 @@ class EffectAnalyzer { bool hasExternalBreakTargets() const { return !breakTargets.empty(); } // Checks if these effects would invalidate another set of effects (e.g., if - // we write, we invalidate someone that reads). + // we write, we invalidate someone that reads). This checks if we can move one + // set of effects past another, and similar situations. // // This assumes the things whose effects we are comparing will both execute, // at least if neither of them transfers control flow away. That is, we assume @@ -279,16 +290,22 @@ class EffectAnalyzer { // can reorder them even if B traps (even if A has a global effect like a // global.set, since we assume B does not trap in traps-never-happen). bool invalidates(const EffectAnalyzer& other) { + // For purposes of the following comparisons, callsIfMoved is the same as + // calls: We are comparing one set of effects to another, possibly because + // we are moving them across each other, and callsIfMoved has effects if we + // move it. + auto thisCalls = calls || callsIfMoved; + auto otherCalls = other.calls || other.callsIfMoved; if ((transfersControlFlow() && other.hasSideEffects()) || (other.transfersControlFlow() && hasSideEffects()) || - ((writesMemory || calls) && other.accessesMemory()) || - ((other.writesMemory || other.calls) && accessesMemory()) || - ((writesTable || calls) && other.accessesTable()) || - ((other.writesTable || other.calls) && accessesTable()) || - ((writesStruct || calls) && other.accessesMutableStruct()) || - ((other.writesStruct || other.calls) && accessesMutableStruct()) || - ((writesArray || calls) && other.accessesArray()) || - ((other.writesArray || other.calls) && accessesArray()) || + ((writesMemory || thisCalls) && other.accessesMemory()) || + ((other.writesMemory || otherCalls) && accessesMemory()) || + ((writesTable || thisCalls) && other.accessesTable()) || + ((other.writesTable || otherCalls) && accessesTable()) || + ((writesStruct || thisCalls) && other.accessesMutableStruct()) || + ((other.writesStruct || otherCalls) && accessesMutableStruct()) || + ((writesArray || thisCalls) && other.accessesArray()) || + ((other.writesArray || otherCalls) && accessesArray()) || (danglingPop || other.danglingPop)) { return true; } @@ -308,8 +325,8 @@ class EffectAnalyzer { return true; } } - if ((other.calls && accessesMutableGlobal()) || - (calls && other.accessesMutableGlobal())) { + if ((otherCalls && accessesMutableGlobal()) || + (thisCalls && other.accessesMutableGlobal())) { return true; } for (auto global : globalsWritten) { @@ -363,6 +380,8 @@ class EffectAnalyzer { throws_ = throws_ || other.throws_; danglingPop = danglingPop || other.danglingPop; mayNotReturn = mayNotReturn || other.mayNotReturn; + hasReturnCallThrow = hasReturnCallThrow || other.hasReturnCallThrow; + callsIfMoved = callsIfMoved || other.callsIfMoved; for (auto i : other.localsRead) { localsRead.insert(i); } From 7ce31517c0bb1fd0fb0be7882f4da7f504d44197 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 20 Jan 2026 16:20:53 -0800 Subject: [PATCH 02/58] work --- src/wasm.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/wasm.h b/src/wasm.h index ab6323b5f35..92567cbb0c3 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2296,8 +2296,13 @@ class Function : public Importable { static const uint8_t AlwaysInline = 127; std::optional inline_; + // Binaryen intrinsic: Mark as having side effects if moved, but having no + // effects in the current position. See |callsIfMoved| in effects.h. + std::optional effectsIfMoved; + bool operator==(const CodeAnnotation& other) const { - return branchLikely == other.branchLikely && inline_ == other.inline_; + return branchLikely == other.branchLikely && inline_ == other.inline_ && + effectsIfMoved == other.effectsIfMoved; } }; From 477e77b43a66fa632d97d182f53b6988889802d1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 20 Jan 2026 16:47:10 -0800 Subject: [PATCH 03/58] work --- src/ir/effects.h | 7 +++++++ src/passes/Print.cpp | 6 ++++++ src/wasm-annotations.h | 1 + src/wasm-binary.h | 5 +++++ src/wasm-ir-builder.h | 8 ++++++-- src/wasm.h | 3 ++- src/wasm/wasm-binary.cpp | 36 +++++++++++++++++++++++++++++++++++- src/wasm/wasm-ir-builder.cpp | 13 +++++++++++-- src/wasm/wasm.cpp | 1 + 9 files changed, 74 insertions(+), 6 deletions(-) diff --git a/src/ir/effects.h b/src/ir/effects.h index ae55b99e728..80bb74004fc 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -179,6 +179,13 @@ class EffectAnalyzer { // // Concretely, this does not influence has*Effects(), but it does influence // invalidates(), which compares effects between things that might move. + // + // This implements effectsIfMoved from wasm.h. The concrete effect we apply is + // a call. (The difference in name is to avoid ambiguity: callsIfMoved in + // wasm.h might suggest that the actual call does not happen - but it does, + // just it is marked as having no effects unless moved. Put another way, we + // implement the side effects from the user-facing spec using a call effect; + // we could call the wasm.h field "callEffectsIfMoved", but prefer brevity.) bool callsIfMoved = false; // Helper functions to check for various effect types diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index e54d3eebdff..526915a159f 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -2787,6 +2787,12 @@ void PrintSExpression::printCodeAnnotations(Expression* curr) { restoreNormalColor(o); doIndent(o, indent); } + if (annotation.effectsIfMoved) { + Colors::grey(o); + o << "(@" << Annotations::EffectsIfMoved << ""\")\n"; + restoreNormalColor(o); + doIndent(o, indent); + } } } diff --git a/src/wasm-annotations.h b/src/wasm-annotations.h index 888c356dcb9..cc4ee5083d9 100644 --- a/src/wasm-annotations.h +++ b/src/wasm-annotations.h @@ -27,6 +27,7 @@ namespace wasm::Annotations { extern const Name BranchHint; extern const Name InlineHint; +extern const Name EffectsIfMovedHint; } // namespace wasm::Annotations diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 5a1f618da3d..673ce408cde 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1441,6 +1441,7 @@ class WasmBinaryWriter { std::optional getBranchHintsBuffer(); std::optional getInlineHintsBuffer(); + std::optional getEffectsIfMovedHintsBuffer(); // helpers void writeInlineString(std::string_view name); @@ -1732,6 +1733,10 @@ class WasmBinaryReader { size_t inlineHintsLen = 0; void readInlineHints(size_t payloadLen); + size_t callsIfMovedHintsPos = 0; + size_t callsIfMovedHintsLen = 0; + void readCallsIfMovedHints(size_t payloadLen); + std::tuple readMemoryAccess(bool isAtomic, bool isRMW); std::tuple getAtomicMemarg(); diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index bbc21afb083..b35263c20a6 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -138,7 +138,8 @@ class IRBuilder : public UnifiedExpressionVisitor> { // Unlike Builder::makeCall, this assumes the function already exists. Result<> makeCall(Name func, bool isReturn, - std::optional inline_ = std::nullopt); + std::optional inline_ = std::nullopt, + std::optional effectsIfMoved = std::nullopt); Result<> makeCallIndirect(Name table, HeapType type, bool isReturn, @@ -734,9 +735,12 @@ class IRBuilder : public UnifiedExpressionVisitor> { // Add a branch hint, if |likely| is present. void addBranchHint(Expression* expr, std::optional likely); - // Add an inlining hint, if |inline_| is present. + // Add an inlining hint, if present. void addInlineHint(Expression* expr, std::optional inline_); + // Add an effectsIfMoved hint, if present. + void addEffectsIfMovedHint(Expression* expr, std::optional effectsIfMoved); + void dump(); }; diff --git a/src/wasm.h b/src/wasm.h index 92567cbb0c3..3a6f41e7793 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2298,7 +2298,8 @@ class Function : public Importable { // Binaryen intrinsic: Mark as having side effects if moved, but having no // effects in the current position. See |callsIfMoved| in effects.h. - std::optional effectsIfMoved; + // TODO: link to spec + std::optional effectsIfMoved; bool operator==(const CodeAnnotation& other) const { return branchLikely == other.branchLikely && inline_ == other.inline_ && diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 96fd205f908..f7e3ee67a96 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1764,6 +1764,19 @@ std::optional WasmBinaryWriter::getInlineHintsBuffer() { }); } +std::optional WasmBinaryWriter::getEffectsIfMovedHintsBuffer() { + return writeExpressionHints( + Annotations::EffectsIfMovedHint, + [](const Function::CodeAnnotation& annotation) { + return annotation.effectsIfMoved; + }, + [](const Function::CodeAnnotation& annotation, + BufferWithRandomAccess& buffer) { + // Hint size, always 0 for now. + buffer << U32LEB(0); + }); +} + void WasmBinaryWriter::writeData(const char* data, size_t size) { for (size_t i = 0; i < size; i++) { o << int8_t(data[i]); @@ -2035,7 +2048,8 @@ void WasmBinaryReader::preScan() { auto sectionName = getInlineString(); if (sectionName == Annotations::BranchHint || - sectionName == Annotations::InlineHint) { + sectionName == Annotations::InlineHint || + sectionName == Annotations::EffectsIfMovedHint) { // Code annotations require code locations. // TODO: We could note which functions require code locations, as an // optimization. @@ -2167,6 +2181,10 @@ void WasmBinaryReader::read() { pos = inlineHintsPos; readInlineHints(inlineHintsLen); } + if (effectsIfMovedHintsPos) { + pos = effectsIfMovedHintsPos; + readEffectsIfMovedHints(effectsIfMovedHintsLen); + } validateBinary(); } @@ -2195,6 +2213,9 @@ void WasmBinaryReader::readCustomSection(size_t payloadLen) { } else if (sectionName == Annotations::InlineHint) { inlineHintsPos = pos; inlineHintsLen = payloadLen; + } else if (sectionName == Annotations::EffectsIfMovedHint) { + effectsIfMovedHintPos = pos; + effectsIfMovedHintLen = payloadLen; } else { // an unfamiliar custom section if (sectionName.equals(BinaryConsts::CustomSections::Linking)) { @@ -5484,6 +5505,19 @@ void WasmBinaryReader::readInlineHints(size_t payloadLen) { }); } +void WasmBinaryReader::readEffectsIfMovedHints(size_t payloadLen) { + readExpressionHints(Annotations::EffectsIfMovedHint, + payloadLen, + [&](Function::CodeAnnotation& annotation) { + auto size = getU32LEB(); + if (size != 0) { + throwError("bad EffectsIfMovedHint size"); + } + + annotation.effectsIfMoved.emplace(); + }); +} + std::tuple WasmBinaryReader::readMemoryAccess(bool isAtomic, bool isRMW) { auto rawAlignment = getU32LEB(); diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 57a2469dce0..aa57ae3d154 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -1376,7 +1376,8 @@ Result<> IRBuilder::makeSwitch(const std::vector& labels, Result<> IRBuilder::makeCall(Name func, bool isReturn, - std::optional inline_) { + std::optional inline_, + std::optional effectsIfMoved) { auto sig = wasm.getFunction(func)->getSig(); Call curr(wasm.allocator); curr.target = func; @@ -1386,6 +1387,7 @@ Result<> IRBuilder::makeCall(Name func, builder.makeCall(curr.target, curr.operands, sig.results, isReturn); push(call); addInlineHint(call, inline_); + addEffectsIfMovedHint(call, effectsIfMoved); return Ok{}; } @@ -2649,10 +2651,17 @@ void IRBuilder::addBranchHint(Expression* expr, std::optional likely) { void IRBuilder::addInlineHint(Expression* expr, std::optional inline_) { if (inline_) { - // Branches are only possible inside functions. + // Only possible inside functions. assert(func); func->codeAnnotations[expr].inline_ = inline_; } } +void IRBuilder::addEffectsIfMovedHint(Expression* expr, + std::optional effectsIfMoved) { + // Only possible inside functions. + assert(func); + func->codeAnnotations[expr].effectsIfMoved.emplace(); +} + } // namespace wasm diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 447847c18d7..c3edb53fc98 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -69,6 +69,7 @@ namespace Annotations { const Name BranchHint = "metadata.code.branch_hint"; const Name InlineHint = "metadata.code.inline"; +const Name EffectsIfMovedHint = "binaryen.effects_if_moved"; } // namespace Annotations From 83583cb398666f14697be47de33dd2044e79c240 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 20 Jan 2026 16:51:14 -0800 Subject: [PATCH 04/58] work --- src/parser/contexts.h | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 1ac3a7c525f..6e1e1c22453 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -1330,6 +1330,18 @@ struct AnnotationParserCtx { return value; } + + // Return the inline hint for a call instruction, if there is one. + std::optional + getEffectsIfMovedHint(const std::vector& annotations) { + std::optional hint; + for (auto& a : annotations) { + if (a.kind == Annotations::EffectsIfMovedHint) { + hint.emplace(); + } + } + return hint; + } }; // Phase 4: Parse and set the types of module elements. @@ -2421,7 +2433,8 @@ struct ParseDefsCtx : TypeParserCtx, AnnotationParserCtx { Name func, bool isReturn) { auto inline_ = getInlineHint(annotations); - return withLoc(pos, irBuilder.makeCall(func, isReturn, inline_)); + auto effectsIfMoved = getEffectsIfMovedHint(annotations); + return withLoc(pos, irBuilder.makeCall(func, isReturn, inline_, effectsIfMoved)); } Result<> makeCallIndirect(Index pos, From 073b39e1fef4f8930dfaf12c3f0528e28c12fc1a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 20 Jan 2026 16:51:23 -0800 Subject: [PATCH 05/58] fmt --- src/parser/contexts.h | 3 ++- src/wasm-ir-builder.h | 12 +++++++----- src/wasm/wasm-binary.cpp | 3 ++- src/wasm/wasm-ir-builder.cpp | 2 +- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 6e1e1c22453..cd8e6a4003d 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -2434,7 +2434,8 @@ struct ParseDefsCtx : TypeParserCtx, AnnotationParserCtx { bool isReturn) { auto inline_ = getInlineHint(annotations); auto effectsIfMoved = getEffectsIfMovedHint(annotations); - return withLoc(pos, irBuilder.makeCall(func, isReturn, inline_, effectsIfMoved)); + return withLoc(pos, + irBuilder.makeCall(func, isReturn, inline_, effectsIfMoved)); } Result<> makeCallIndirect(Index pos, diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index b35263c20a6..acdc1aced95 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -136,10 +136,11 @@ class IRBuilder : public UnifiedExpressionVisitor> { std::optional likely = std::nullopt); Result<> makeSwitch(const std::vector& labels, Index defaultLabel); // Unlike Builder::makeCall, this assumes the function already exists. - Result<> makeCall(Name func, - bool isReturn, - std::optional inline_ = std::nullopt, - std::optional effectsIfMoved = std::nullopt); + Result<> + makeCall(Name func, + bool isReturn, + std::optional inline_ = std::nullopt, + std::optional effectsIfMoved = std::nullopt); Result<> makeCallIndirect(Name table, HeapType type, bool isReturn, @@ -739,7 +740,8 @@ class IRBuilder : public UnifiedExpressionVisitor> { void addInlineHint(Expression* expr, std::optional inline_); // Add an effectsIfMoved hint, if present. - void addEffectsIfMovedHint(Expression* expr, std::optional effectsIfMoved); + void addEffectsIfMovedHint(Expression* expr, + std::optional effectsIfMoved); void dump(); }; diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index f7e3ee67a96..29cfdf3a88b 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1764,7 +1764,8 @@ std::optional WasmBinaryWriter::getInlineHintsBuffer() { }); } -std::optional WasmBinaryWriter::getEffectsIfMovedHintsBuffer() { +std::optional +WasmBinaryWriter::getEffectsIfMovedHintsBuffer() { return writeExpressionHints( Annotations::EffectsIfMovedHint, [](const Function::CodeAnnotation& annotation) { diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index aa57ae3d154..737db370847 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -2658,7 +2658,7 @@ void IRBuilder::addInlineHint(Expression* expr, } void IRBuilder::addEffectsIfMovedHint(Expression* expr, - std::optional effectsIfMoved) { + std::optional effectsIfMoved) { // Only possible inside functions. assert(func); func->codeAnnotations[expr].effectsIfMoved.emplace(); From f129e510f2b1d11a058d39ca8c8e009777de42ba Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 20 Jan 2026 16:53:19 -0800 Subject: [PATCH 06/58] test --- test/lit/effects-if-moved-hints.wast | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 test/lit/effects-if-moved-hints.wast diff --git a/test/lit/effects-if-moved-hints.wast b/test/lit/effects-if-moved-hints.wast new file mode 100644 index 00000000000..eb14ce9994a --- /dev/null +++ b/test/lit/effects-if-moved-hints.wast @@ -0,0 +1,14 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-opt -all %s -S -o - | filecheck %s + +;; RUN: wasm-opt -all --roundtrip %s -S -o - | filecheck %s --check-prefix=RTRIP + +(module + (func $func + ;; Three calls, one annotated. + (call $func) + (@binaryen.efects.if.moved) + (call $func) + ) +) From 075fd1569a80cbceb369570c48b8ac9e0962580c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 20 Jan 2026 16:53:50 -0800 Subject: [PATCH 07/58] work --- src/passes/Print.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 526915a159f..c089dedcafb 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -2789,7 +2789,7 @@ void PrintSExpression::printCodeAnnotations(Expression* curr) { } if (annotation.effectsIfMoved) { Colors::grey(o); - o << "(@" << Annotations::EffectsIfMoved << ""\")\n"; + o << "(@" << Annotations::EffectsIfMovedHint << ""\")\n"; restoreNormalColor(o); doIndent(o, indent); } From d540a9643da8df49f36ef7aac2240fe8bd435d9f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 20 Jan 2026 16:54:26 -0800 Subject: [PATCH 08/58] work --- src/passes/Print.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index c089dedcafb..b6bf7eb190d 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -2789,7 +2789,7 @@ void PrintSExpression::printCodeAnnotations(Expression* curr) { } if (annotation.effectsIfMoved) { Colors::grey(o); - o << "(@" << Annotations::EffectsIfMovedHint << ""\")\n"; + o << "(@" << Annotations::EffectsIfMovedHint << ")\n"; restoreNormalColor(o); doIndent(o, indent); } From 4d8b7a512c2798b5a357d4d88e3f4cc03a8ba0d1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 21 Jan 2026 09:45:21 -0800 Subject: [PATCH 09/58] builds --- src/wasm-binary.h | 6 +++--- src/wasm/wasm-binary.cpp | 4 ++-- src/wasm/wasm-ir-builder.cpp | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 673ce408cde..449fa3fc09c 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1733,9 +1733,9 @@ class WasmBinaryReader { size_t inlineHintsLen = 0; void readInlineHints(size_t payloadLen); - size_t callsIfMovedHintsPos = 0; - size_t callsIfMovedHintsLen = 0; - void readCallsIfMovedHints(size_t payloadLen); + size_t effectsIfMovedHintsPos = 0; + size_t effectsIfMovedHintsLen = 0; + void readEffectsIfMovedHints(size_t payloadLen); std::tuple readMemoryAccess(bool isAtomic, bool isRMW); diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 29cfdf3a88b..f776d35b153 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -2215,8 +2215,8 @@ void WasmBinaryReader::readCustomSection(size_t payloadLen) { inlineHintsPos = pos; inlineHintsLen = payloadLen; } else if (sectionName == Annotations::EffectsIfMovedHint) { - effectsIfMovedHintPos = pos; - effectsIfMovedHintLen = payloadLen; + effectsIfMovedHintsPos = pos; + effectsIfMovedHintsLen = payloadLen; } else { // an unfamiliar custom section if (sectionName.equals(BinaryConsts::CustomSections::Linking)) { diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 737db370847..00a791c72f7 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -2658,7 +2658,7 @@ void IRBuilder::addInlineHint(Expression* expr, } void IRBuilder::addEffectsIfMovedHint(Expression* expr, - std::optional effectsIfMoved) { + std::optional effectsIfMoved) { // Only possible inside functions. assert(func); func->codeAnnotations[expr].effectsIfMoved.emplace(); From e38e29c39d66997d4837c9c9d8297897fcb1bb80 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 21 Jan 2026 10:05:30 -0800 Subject: [PATCH 10/58] progress --- src/parser/contexts.h | 1 + src/wasm/wasm-ir-builder.cpp | 8 +++++--- src/wasm/wasm.cpp | 2 +- test/lit/effects-if-moved-hints.wast | 20 ++++++++++++++++++-- 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/parser/contexts.h b/src/parser/contexts.h index cd8e6a4003d..3012a7b51e1 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -1338,6 +1338,7 @@ struct AnnotationParserCtx { for (auto& a : annotations) { if (a.kind == Annotations::EffectsIfMovedHint) { hint.emplace(); + break; } } return hint; diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 00a791c72f7..735b8b23bf2 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -2659,9 +2659,11 @@ void IRBuilder::addInlineHint(Expression* expr, void IRBuilder::addEffectsIfMovedHint(Expression* expr, std::optional effectsIfMoved) { - // Only possible inside functions. - assert(func); - func->codeAnnotations[expr].effectsIfMoved.emplace(); + if (effectsIfMoved) { + // Only possible inside functions. + assert(func); + func->codeAnnotations[expr].effectsIfMoved.emplace(); + } } } // namespace wasm diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index c3edb53fc98..e5b7aea7919 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -69,7 +69,7 @@ namespace Annotations { const Name BranchHint = "metadata.code.branch_hint"; const Name InlineHint = "metadata.code.inline"; -const Name EffectsIfMovedHint = "binaryen.effects_if_moved"; +const Name EffectsIfMovedHint = "binaryen.effects.if.moved"; } // namespace Annotations diff --git a/test/lit/effects-if-moved-hints.wast b/test/lit/effects-if-moved-hints.wast index eb14ce9994a..7b8dae8ac44 100644 --- a/test/lit/effects-if-moved-hints.wast +++ b/test/lit/effects-if-moved-hints.wast @@ -5,10 +5,26 @@ ;; RUN: wasm-opt -all --roundtrip %s -S -o - | filecheck %s --check-prefix=RTRIP (module + ;; CHECK: (type $0 (func)) + + ;; CHECK: (func $func (type $0) + ;; CHECK-NEXT: (call $func) + ;; CHECK-NEXT: (@binaryen.effects.if.moved) + ;; CHECK-NEXT: (call $func) + ;; CHECK-NEXT: (call $func) + ;; CHECK-NEXT: ) + ;; RTRIP: (type $0 (func)) + + ;; RTRIP: (func $func (type $0) + ;; RTRIP-NEXT: (call $func) + ;; RTRIP-NEXT: (call $func) + ;; RTRIP-NEXT: (call $func) + ;; RTRIP-NEXT: ) (func $func - ;; Three calls, one annotated. + ;; Three calls, one annotated in the middle. + (call $func) + (@binaryen.effects.if.moved) (call $func) - (@binaryen.efects.if.moved) (call $func) ) ) From c78dc413dc3c995b37f1ac67fee140f964e4ce85 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 21 Jan 2026 10:07:31 -0800 Subject: [PATCH 11/58] finish --- src/wasm/wasm-binary.cpp | 1 + test/lit/effects-if-moved-hints.wast | 1 + 2 files changed, 2 insertions(+) diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index f776d35b153..e6582b307e0 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1623,6 +1623,7 @@ std::optional WasmBinaryWriter::writeCodeAnnotations() { append(getBranchHintsBuffer()); append(getInlineHintsBuffer()); + append(getEffectsIfMovedHintsBuffer()); return ret; } diff --git a/test/lit/effects-if-moved-hints.wast b/test/lit/effects-if-moved-hints.wast index 7b8dae8ac44..eadde670a4b 100644 --- a/test/lit/effects-if-moved-hints.wast +++ b/test/lit/effects-if-moved-hints.wast @@ -17,6 +17,7 @@ ;; RTRIP: (func $func (type $0) ;; RTRIP-NEXT: (call $func) + ;; RTRIP-NEXT: (@binaryen.effects.if.moved) ;; RTRIP-NEXT: (call $func) ;; RTRIP-NEXT: (call $func) ;; RTRIP-NEXT: ) From 8ad0110fa8f9eaf45faee23cc73773c4432cd06d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 21 Jan 2026 10:07:38 -0800 Subject: [PATCH 12/58] works --- src/wasm/wasm-ir-builder.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 735b8b23bf2..7d013f57070 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -2657,8 +2657,8 @@ void IRBuilder::addInlineHint(Expression* expr, } } -void IRBuilder::addEffectsIfMovedHint(Expression* expr, - std::optional effectsIfMoved) { +void IRBuilder::addEffectsIfMovedHint( + Expression* expr, std::optional effectsIfMoved) { if (effectsIfMoved) { // Only possible inside functions. assert(func); From d876266599b58141443738f7c5b8a059e8d062d7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 21 Jan 2026 10:09:50 -0800 Subject: [PATCH 13/58] UNDO effects.h --- src/ir/effects.h | 48 +++++++++++------------------------------------- 1 file changed, 11 insertions(+), 37 deletions(-) diff --git a/src/ir/effects.h b/src/ir/effects.h index 80bb74004fc..b9e07a87bf8 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -171,23 +171,6 @@ class EffectAnalyzer { // more here.) bool hasReturnCallThrow = false; - // Similar to calls (an effect where anything can happen), but only if this is - // moved. That is, this has no effects where it is presently located, but if - // moved, it will have effects. As a result, this can be removed from its - // current location, but if we want to move it around, we must consider it as - // having the same effects as a call. - // - // Concretely, this does not influence has*Effects(), but it does influence - // invalidates(), which compares effects between things that might move. - // - // This implements effectsIfMoved from wasm.h. The concrete effect we apply is - // a call. (The difference in name is to avoid ambiguity: callsIfMoved in - // wasm.h might suggest that the actual call does not happen - but it does, - // just it is marked as having no effects unless moved. Put another way, we - // implement the side effects from the user-facing spec using a call effect; - // we could call the wasm.h field "callEffectsIfMoved", but prefer brevity.) - bool callsIfMoved = false; - // Helper functions to check for various effect types bool accessesLocal() const { @@ -263,8 +246,7 @@ class EffectAnalyzer { bool hasExternalBreakTargets() const { return !breakTargets.empty(); } // Checks if these effects would invalidate another set of effects (e.g., if - // we write, we invalidate someone that reads). This checks if we can move one - // set of effects past another, and similar situations. + // we write, we invalidate someone that reads). // // This assumes the things whose effects we are comparing will both execute, // at least if neither of them transfers control flow away. That is, we assume @@ -297,22 +279,16 @@ class EffectAnalyzer { // can reorder them even if B traps (even if A has a global effect like a // global.set, since we assume B does not trap in traps-never-happen). bool invalidates(const EffectAnalyzer& other) { - // For purposes of the following comparisons, callsIfMoved is the same as - // calls: We are comparing one set of effects to another, possibly because - // we are moving them across each other, and callsIfMoved has effects if we - // move it. - auto thisCalls = calls || callsIfMoved; - auto otherCalls = other.calls || other.callsIfMoved; if ((transfersControlFlow() && other.hasSideEffects()) || (other.transfersControlFlow() && hasSideEffects()) || - ((writesMemory || thisCalls) && other.accessesMemory()) || - ((other.writesMemory || otherCalls) && accessesMemory()) || - ((writesTable || thisCalls) && other.accessesTable()) || - ((other.writesTable || otherCalls) && accessesTable()) || - ((writesStruct || thisCalls) && other.accessesMutableStruct()) || - ((other.writesStruct || otherCalls) && accessesMutableStruct()) || - ((writesArray || thisCalls) && other.accessesArray()) || - ((other.writesArray || otherCalls) && accessesArray()) || + ((writesMemory || calls) && other.accessesMemory()) || + ((other.writesMemory || other.calls) && accessesMemory()) || + ((writesTable || calls) && other.accessesTable()) || + ((other.writesTable || other.calls) && accessesTable()) || + ((writesStruct || calls) && other.accessesMutableStruct()) || + ((other.writesStruct || other.calls) && accessesMutableStruct()) || + ((writesArray || calls) && other.accessesArray()) || + ((other.writesArray || other.calls) && accessesArray()) || (danglingPop || other.danglingPop)) { return true; } @@ -332,8 +308,8 @@ class EffectAnalyzer { return true; } } - if ((otherCalls && accessesMutableGlobal()) || - (thisCalls && other.accessesMutableGlobal())) { + if ((other.calls && accessesMutableGlobal()) || + (calls && other.accessesMutableGlobal())) { return true; } for (auto global : globalsWritten) { @@ -387,8 +363,6 @@ class EffectAnalyzer { throws_ = throws_ || other.throws_; danglingPop = danglingPop || other.danglingPop; mayNotReturn = mayNotReturn || other.mayNotReturn; - hasReturnCallThrow = hasReturnCallThrow || other.hasReturnCallThrow; - callsIfMoved = callsIfMoved || other.callsIfMoved; for (auto i : other.localsRead) { localsRead.insert(i); } From 31f6e5c2703a3cae5b52be53d9fc52395a482a09 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 21 Jan 2026 10:18:38 -0800 Subject: [PATCH 14/58] yolo --- src/passes/Vacuum.cpp | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/passes/Vacuum.cpp b/src/passes/Vacuum.cpp index b5e223d9f21..f648f0ad26e 100644 --- a/src/passes/Vacuum.cpp +++ b/src/passes/Vacuum.cpp @@ -92,9 +92,7 @@ struct Vacuum : public WalkerPass> { return curr; } // Check if this expression itself has side effects, ignoring children. - EffectAnalyzer self(getPassOptions(), *getModule()); - self.visit(curr); - if (self.hasUnremovableSideEffects()) { + if (hasShallowEffectsAssumingResultUnused(curr)) { return curr; } // The result isn't used, and this has no side effects itself, so we can @@ -130,6 +128,25 @@ struct Vacuum : public WalkerPass> { } } + // Given an expression whose result is unused, see if it has effects (ignoring + // children, so only a shallow check). This is basically just a call to + // ShallowEffectAnalyzer, except that the knowledge of the result being unused + // lets us check the relevant hint. + bool hasShallowEffectsAssumingResultUnused(Expression* curr) { // canberemoved, to justifiy hasUnremovable + if (auto* call = curr->dynCast()) { + auto iter = getFunction()->codeAnnotations.find(call); + if iter != currFunction->codeAnnotations.end()) { + auto& annotation = iter->second; + if (annotation.effectsIfMoved) { + // No need to check effects, this can be removed. + return false; + } + } + } + ShallowEffectAnalyzer self(getPassOptions(), *getModule(), curr); + return self.hasUnremovableSideEffects(); + } + void visitBlock(Block* curr) { auto& list = curr->list; From 879dc79852a6446c39c2e69d9b6afdc7ce3d7267 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 21 Jan 2026 10:27:18 -0800 Subject: [PATCH 15/58] works --- src/passes/Vacuum.cpp | 5 +- test/lit/passes/vacuum-effects-if-moved.wast | 89 ++++++++++++++++++++ 2 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 test/lit/passes/vacuum-effects-if-moved.wast diff --git a/src/passes/Vacuum.cpp b/src/passes/Vacuum.cpp index f648f0ad26e..f6aae6b6cb8 100644 --- a/src/passes/Vacuum.cpp +++ b/src/passes/Vacuum.cpp @@ -134,8 +134,9 @@ struct Vacuum : public WalkerPass> { // lets us check the relevant hint. bool hasShallowEffectsAssumingResultUnused(Expression* curr) { // canberemoved, to justifiy hasUnremovable if (auto* call = curr->dynCast()) { - auto iter = getFunction()->codeAnnotations.find(call); - if iter != currFunction->codeAnnotations.end()) { + auto& annotations = getFunction()->codeAnnotations; + auto iter = annotations.find(call); + if (iter != annotations.end()) { auto& annotation = iter->second; if (annotation.effectsIfMoved) { // No need to check effects, this can be removed. diff --git a/test/lit/passes/vacuum-effects-if-moved.wast b/test/lit/passes/vacuum-effects-if-moved.wast new file mode 100644 index 00000000000..19766013c2d --- /dev/null +++ b/test/lit/passes/vacuum-effects-if-moved.wast @@ -0,0 +1,89 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: wasm-opt %s --vacuum -all -S -o - | filecheck %s + +(module + ;; CHECK: (func $calls-dropped (type $0) (param $x i32) (result i32) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $calls-dropped + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + (func $calls-dropped (param $x i32) (result i32) + ;; This call is dropped, and marked with the hint, so we can remove it. + (drop + (@binaryen.effects.if.moved) + (call $calls-dropped + (i32.const 0) + ) + ) + ;; As above, but a parameter has effects. We must keep that around, without + ;; the call. + (drop + (@binaryen.effects.if.moved) + (call $calls-dropped + (local.tee $x + (i32.const 1) + ) + ) + ) + ;; Now we lack the hint, so we keep the call. + (drop + (call $calls-dropped + (i32.const 2) + ) + ) + (i32.const 3) + ) + + ;; CHECK: (func $calls-used (type $0) (param $x i32) (result i32) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (@binaryen.effects.if.moved) + ;; CHECK-NEXT: (call $calls-used + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (@binaryen.effects.if.moved) + ;; CHECK-NEXT: (call $calls-used + ;; CHECK-NEXT: (local.tee $x + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $calls-used + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + (func $calls-used (param $x i32) (result i32) + ;; As above, but the calls are not dropped, so we keep them. + (local.set $x + (@binaryen.effects.if.moved) + (call $calls-used + (i32.const 0) + ) + ) + (local.set $x + (@binaryen.effects.if.moved) + (call $calls-used + (local.tee $x + (i32.const 1) + ) + ) + ) + (drop + (call $calls-used + (i32.const 2) + ) + ) + (i32.const 3) + ) +) + From eadd3dc6c1273a0cc145ec3bdd1dd5eb2e22ef33 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 21 Jan 2026 10:29:14 -0800 Subject: [PATCH 16/58] rename --- src/passes/Vacuum.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/passes/Vacuum.cpp b/src/passes/Vacuum.cpp index f6aae6b6cb8..dcdd48de68a 100644 --- a/src/passes/Vacuum.cpp +++ b/src/passes/Vacuum.cpp @@ -91,8 +91,8 @@ struct Vacuum : public WalkerPass> { curr->is() || curr->is() || curr->is()) { return curr; } - // Check if this expression itself has side effects, ignoring children. - if (hasShallowEffectsAssumingResultUnused(curr)) { + // Check if this expression itself must be kept. + if (mustKeepUnusedParent(curr)) { return curr; } // The result isn't used, and this has no side effects itself, so we can @@ -128,11 +128,11 @@ struct Vacuum : public WalkerPass> { } } - // Given an expression whose result is unused, see if it has effects (ignoring - // children, so only a shallow check). This is basically just a call to - // ShallowEffectAnalyzer, except that the knowledge of the result being unused - // lets us check the relevant hint. - bool hasShallowEffectsAssumingResultUnused(Expression* curr) { // canberemoved, to justifiy hasUnremovable + // Check if a parent expression must be kept around, given the knowledge that + // its result is unused (dropped). This is basically just a call to + // ShallowEffectAnalyzer to see if we can remove it, except that given the + // result is unused, the relevant hint may help us. + bool mustKeepUnusedParent(Expression* curr) { if (auto* call = curr->dynCast()) { auto& annotations = getFunction()->codeAnnotations; auto iter = annotations.find(call); From a49fe2643d687596159564ed10f08dfd08ef7df1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 21 Jan 2026 10:29:42 -0800 Subject: [PATCH 17/58] rename --- src/passes/Vacuum.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/passes/Vacuum.cpp b/src/passes/Vacuum.cpp index dcdd48de68a..7d29d1d3ee9 100644 --- a/src/passes/Vacuum.cpp +++ b/src/passes/Vacuum.cpp @@ -131,7 +131,7 @@ struct Vacuum : public WalkerPass> { // Check if a parent expression must be kept around, given the knowledge that // its result is unused (dropped). This is basically just a call to // ShallowEffectAnalyzer to see if we can remove it, except that given the - // result is unused, the relevant hint may help us. + // result is unused, the relevant hint may help us. bool mustKeepUnusedParent(Expression* curr) { if (auto* call = curr->dynCast()) { auto& annotations = getFunction()->codeAnnotations; From dce83d867d84f86edcabc386332f05f3971de795 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 27 Jan 2026 12:13:29 -0800 Subject: [PATCH 18/58] refactor --- src/parser/contexts.h | 82 ++++++++++++++++-------------------- src/wasm-ir-builder.h | 27 ++++-------- src/wasm.h | 46 ++++++++++---------- src/wasm/wasm-ir-builder.cpp | 50 ++++++++++------------ 4 files changed, 92 insertions(+), 113 deletions(-) diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 69818043bef..510d7b46185 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -1297,52 +1297,45 @@ struct ParseImplicitTypeDefsCtx : TypeParserCtx { }; struct AnnotationParserCtx { - // Return the inline hint for a call instruction, if there is one. - std::optional - getInlineHint(const std::vector& annotations) { - // Find and apply (the last) inline hint. - const Annotation* hint = nullptr; + // Parse annotations into IR. + CodeAnnotation parseAnnotations(const std::vector& annotations) { + CodeAnnotation ret; + + // Find the hints. For hints with content we must find the last one, which + // overrides the others. + const Annotation* inlineHint = nullptr; for (auto& a : annotations) { if (a.kind == Annotations::InlineHint) { - hint = &a; + inlineHint = &a; + } else if (a.kind == Annotations::EffectsIfMovedHint) { + ret.effectsIfMovedHint.emplace(); } } - if (!hint) { - return std::nullopt; - } - - Lexer lexer(hint->contents); - if (lexer.empty()) { - std::cerr << "warning: empty InlineHint\n"; - return std::nullopt; - } - - auto str = lexer.takeString(); - if (!str || str->size() != 1) { - std::cerr << "warning: invalid InlineHint string\n"; - return std::nullopt; - } - uint8_t value = (*str)[0]; - if (value > 127) { - std::cerr << "warning: invalid InlineHint value\n"; - return std::nullopt; - } + // Apply the last inline hint, if any. + if (inlineHint) { + Lexer lexer(inlineHint->contents); + if (lexer.empty()) { + std::cerr << "warning: empty InlineHint\n"; + return std::nullopt; + } - return value; - } + auto str = lexer.takeString(); + if (!str || str->size() != 1) { + std::cerr << "warning: invalid InlineHint string\n"; + return std::nullopt; + } - // Return the inline hint for a call instruction, if there is one. - std::optional - getEffectsIfMovedHint(const std::vector& annotations) { - std::optional hint; - for (auto& a : annotations) { - if (a.kind == Annotations::EffectsIfMovedHint) { - hint.emplace(); - break; + uint8_t value = (*str)[0]; + if (value > 127) { + std::cerr << "warning: invalid InlineHint value\n"; + return std::nullopt; } + + ret.inline_ = value; } - return hint; + + return ret; } }; @@ -2435,10 +2428,8 @@ struct ParseDefsCtx : TypeParserCtx, AnnotationParserCtx { const std::vector& annotations, Name func, bool isReturn) { - auto inline_ = getInlineHint(annotations); - auto effectsIfMoved = getEffectsIfMovedHint(annotations); - return withLoc(pos, - irBuilder.makeCall(func, isReturn, inline_, effectsIfMoved)); + return withLoc( + pos, irBuilder.makeCall(func, isReturn, parseAnnotations(annotations))); } Result<> makeCallIndirect(Index pos, @@ -2448,9 +2439,9 @@ struct ParseDefsCtx : TypeParserCtx, AnnotationParserCtx { bool isReturn) { auto t = getTable(pos, table); CHECK_ERR(t); - auto inline_ = getInlineHint(annotations); return withLoc(pos, - irBuilder.makeCallIndirect(*t, type, isReturn, inline_)); + irBuilder.makeCallIndirect( + *t, type, isReturn, parseAnnotations(annotations))); } // Return the branch hint for a branching instruction, if there is one. @@ -2632,8 +2623,9 @@ struct ParseDefsCtx : TypeParserCtx, AnnotationParserCtx { const std::vector& annotations, HeapType type, bool isReturn) { - auto inline_ = getInlineHint(annotations); - return withLoc(pos, irBuilder.makeCallRef(type, isReturn, inline_)); + return withLoc( + pos, + irBuilder.makeCallRef(type, isReturn, parseAnnotations(annotations))); } Result<> makeRefI31(Index pos, diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index c6f5254ded9..952d22a7257 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -136,15 +136,14 @@ class IRBuilder : public UnifiedExpressionVisitor> { std::optional likely = std::nullopt); Result<> makeSwitch(const std::vector& labels, Index defaultLabel); // Unlike Builder::makeCall, this assumes the function already exists. + Result<> makeCall(Name func, + bool isReturn, + const CodeAnnotations& annotations = CodeAnnotations()); Result<> - makeCall(Name func, - bool isReturn, - std::optional inline_ = std::nullopt, - std::optional effectsIfMoved = std::nullopt); - Result<> makeCallIndirect(Name table, - HeapType type, - bool isReturn, - std::optional inline_ = std::nullopt); + makeCallIndirect(Name table, + HeapType type, + bool isReturn, + const CodeAnnotations& annotations = CodeAnnotations()); Result<> makeLocalGet(Index local); Result<> makeLocalSet(Index local); Result<> makeLocalTee(Index local); @@ -228,7 +227,7 @@ class IRBuilder : public UnifiedExpressionVisitor> { Result<> makeI31Get(bool signed_); Result<> makeCallRef(HeapType type, bool isReturn, - std::optional inline_ = std::nullopt); + const CodeAnnotations& annotations = CodeAnnotations()); Result<> makeRefTest(Type type); Result<> makeRefCast(Type type, bool isDesc); Result<> makeRefGetDesc(HeapType type); @@ -737,15 +736,7 @@ class IRBuilder : public UnifiedExpressionVisitor> { Expression* fixExtraOutput(ScopeCtx& scope, Name label, Expression* expr); void fixLoopWithInput(Loop* loop, Type inputType, Index scratch); - // Add a branch hint, if |likely| is present. - void addBranchHint(Expression* expr, std::optional likely); - - // Add an inlining hint, if present. - void addInlineHint(Expression* expr, std::optional inline_); - - // Add an effectsIfMoved hint, if present. - void addEffectsIfMovedHint(Expression* expr, - std::optional effectsIfMoved); + void applyAnnotations(Expression* expr, const CodeAnnotations& annotations); void dump(); }; diff --git a/src/wasm.h b/src/wasm.h index d2456187c12..c239ab1e737 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2232,6 +2232,27 @@ struct BinaryLocations { // Forward declaration for FuncEffectsMap. class EffectAnalyzer; +// Code annotations for VMs. +struct CodeAnnotation { + // Branch Hinting proposal: Whether the branch is likely, or unlikely. + std::optional branchLikely; + + // Compilation Hints proposal. + static const uint8_t NeverInline = 0; + static const uint8_t AlwaysInline = 127; + std::optional inline_; + + // Binaryen intrinsic: Mark as having side effects if moved, but having no + // effects in the current position. See |callsIfMoved| in effects.h. + // TODO: link to spec + std::optional effectsIfMoved; + + bool operator==(const CodeAnnotation& other) const { + return branchLikely == other.branchLikely && inline_ == other.inline_ && + effectsIfMoved == other.effectsIfMoved; + } +}; + class Function : public Importable { public: // A non-nullable reference to a function type. Exact for defined functions. @@ -2285,31 +2306,10 @@ class Function : public Importable { delimiterLocations; BinaryLocations::FunctionLocations funcLocation; - // Code annotations for VMs. As with debug info, we do not store these on + // Function-level annotations are implemented with a key of nullptr, matching + // the 0 byte offset in the spec. As with debug info, we do not store these on // Expressions as we assume most instances are unannotated, and do not want to // add constant memory overhead. - struct CodeAnnotation { - // Branch Hinting proposal: Whether the branch is likely, or unlikely. - std::optional branchLikely; - - // Compilation Hints proposal. - static const uint8_t NeverInline = 0; - static const uint8_t AlwaysInline = 127; - std::optional inline_; - - // Binaryen intrinsic: Mark as having side effects if moved, but having no - // effects in the current position. See |callsIfMoved| in effects.h. - // TODO: link to spec - std::optional effectsIfMoved; - - bool operator==(const CodeAnnotation& other) const { - return branchLikely == other.branchLikely && inline_ == other.inline_ && - effectsIfMoved == other.effectsIfMoved; - } - }; - - // Function-level annotations are implemented with a key of nullptr, matching - // the 0 byte offset in the spec. std::unordered_map codeAnnotations; // The effects for this function, if they have been computed. We use a shared diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index a801d71db97..9c8af4ac3d4 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -1374,10 +1374,10 @@ Result<> IRBuilder::makeSwitch(const std::vector& labels, return Ok{}; } -Result<> IRBuilder::makeCall(Name func, - bool isReturn, - std::optional inline_, - std::optional effectsIfMoved) { +Result<> +IRBuilder::makeCall(Name func, + bool isReturn, + const CodeAnnotations& annotations = CodeAnnotations()) { auto sig = wasm.getFunction(func)->getSig(); Call curr(wasm.allocator); curr.target = func; @@ -1386,15 +1386,15 @@ Result<> IRBuilder::makeCall(Name func, auto* call = builder.makeCall(curr.target, curr.operands, sig.results, isReturn); push(call); - addInlineHint(call, inline_); - addEffectsIfMovedHint(call, effectsIfMoved); + applyAnnotations(call, annotations); return Ok{}; } -Result<> IRBuilder::makeCallIndirect(Name table, - HeapType type, - bool isReturn, - std::optional inline_) { +Result<> IRBuilder::makeCallIndirect( + Name table, + HeapType type, + bool isReturn, + const CodeAnnotations& annotations = CodeAnnotations()) { if (!type.isSignature()) { return Err{"expected function type annotation on call_indirect"}; } @@ -1405,7 +1405,7 @@ Result<> IRBuilder::makeCallIndirect(Name table, auto* call = builder.makeCallIndirect(table, curr.target, curr.operands, type, isReturn); push(call); - addInlineHint(call, inline_); + applyAnnotations(call, annotations); return Ok{}; } @@ -1917,9 +1917,10 @@ Result<> IRBuilder::makeI31Get(bool signed_) { return Ok{}; } -Result<> IRBuilder::makeCallRef(HeapType type, - bool isReturn, - std::optional inline_) { +Result<> +IRBuilder::makeCallRef(HeapType type, + bool isReturn, + const CodeAnnotations& annotations = CodeAnnotations()) { if (!type.isSignature()) { return Err{"expected function type annotation on call_ref"}; } @@ -1934,7 +1935,7 @@ Result<> IRBuilder::makeCallRef(HeapType type, auto* call = builder.makeCallRef(curr.target, curr.operands, sig.results, isReturn); push(call); - addInlineHint(call, inline_); + applyAnnotations(call, annotations); return Ok{}; } @@ -2644,26 +2645,21 @@ Result<> IRBuilder::makeStackSwitch(HeapType ct, Name tag) { return Ok{}; } -void IRBuilder::addBranchHint(Expression* expr, std::optional likely) { - if (likely) { +void IRBuilder::applyAnnotations(Expression* expr, + const CodeAnnotations& annotations) { + if (annotations.branchLikely) { // Branches are only possible inside functions. assert(func); - func->codeAnnotations[expr].branchLikely = likely; + func->codeAnnotations[expr].branchLikely = annotations.branchLikely; } -} -void IRBuilder::addInlineHint(Expression* expr, - std::optional inline_) { - if (inline_) { + if (annotations.inline_) { // Only possible inside functions. assert(func); - func->codeAnnotations[expr].inline_ = inline_; + func->codeAnnotations[expr].inline_ = annotations.inline_; } -} -void IRBuilder::addEffectsIfMovedHint( - Expression* expr, std::optional effectsIfMoved) { - if (effectsIfMoved) { + if (annotations.effectsIfMoved) { // Only possible inside functions. assert(func); func->codeAnnotations[expr].effectsIfMoved.emplace(); From 93234e2f8fcfb6cbe7bbca9040875fb30c059553 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 27 Jan 2026 12:49:53 -0800 Subject: [PATCH 19/58] fix --- src/wasm-ir-builder.h | 8 ++++---- src/wasm/wasm-ir-builder.cpp | 18 +++++++++--------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index 952d22a7257..479ce58338d 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -138,12 +138,12 @@ class IRBuilder : public UnifiedExpressionVisitor> { // Unlike Builder::makeCall, this assumes the function already exists. Result<> makeCall(Name func, bool isReturn, - const CodeAnnotations& annotations = CodeAnnotations()); + const CodeAnnotation& annotations = CodeAnnotation()); Result<> makeCallIndirect(Name table, HeapType type, bool isReturn, - const CodeAnnotations& annotations = CodeAnnotations()); + const CodeAnnotation& annotations = CodeAnnotation()); Result<> makeLocalGet(Index local); Result<> makeLocalSet(Index local); Result<> makeLocalTee(Index local); @@ -227,7 +227,7 @@ class IRBuilder : public UnifiedExpressionVisitor> { Result<> makeI31Get(bool signed_); Result<> makeCallRef(HeapType type, bool isReturn, - const CodeAnnotations& annotations = CodeAnnotations()); + const CodeAnnotation& annotations = CodeAnnotation()); Result<> makeRefTest(Type type); Result<> makeRefCast(Type type, bool isDesc); Result<> makeRefGetDesc(HeapType type); @@ -736,7 +736,7 @@ class IRBuilder : public UnifiedExpressionVisitor> { Expression* fixExtraOutput(ScopeCtx& scope, Name label, Expression* expr); void fixLoopWithInput(Loop* loop, Type inputType, Index scratch); - void applyAnnotations(Expression* expr, const CodeAnnotations& annotations); + void applyAnnotations(Expression* expr, const CodeAnnotation& annotation); void dump(); }; diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 9c8af4ac3d4..5b6d2864864 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -1377,7 +1377,7 @@ Result<> IRBuilder::makeSwitch(const std::vector& labels, Result<> IRBuilder::makeCall(Name func, bool isReturn, - const CodeAnnotations& annotations = CodeAnnotations()) { + const CodeAnnotation& annotations) { auto sig = wasm.getFunction(func)->getSig(); Call curr(wasm.allocator); curr.target = func; @@ -1394,7 +1394,7 @@ Result<> IRBuilder::makeCallIndirect( Name table, HeapType type, bool isReturn, - const CodeAnnotations& annotations = CodeAnnotations()) { + const CodeAnnotation& annotations) { if (!type.isSignature()) { return Err{"expected function type annotation on call_indirect"}; } @@ -1920,7 +1920,7 @@ Result<> IRBuilder::makeI31Get(bool signed_) { Result<> IRBuilder::makeCallRef(HeapType type, bool isReturn, - const CodeAnnotations& annotations = CodeAnnotations()) { + const CodeAnnotation& annotations) { if (!type.isSignature()) { return Err{"expected function type annotation on call_ref"}; } @@ -2646,20 +2646,20 @@ Result<> IRBuilder::makeStackSwitch(HeapType ct, Name tag) { } void IRBuilder::applyAnnotations(Expression* expr, - const CodeAnnotations& annotations) { - if (annotations.branchLikely) { + const CodeAnnotation& annotation) { + if (annotation.branchLikely) { // Branches are only possible inside functions. assert(func); - func->codeAnnotations[expr].branchLikely = annotations.branchLikely; + func->codeAnnotations[expr].branchLikely = annotation.branchLikely; } - if (annotations.inline_) { + if (annotation.inline_) { // Only possible inside functions. assert(func); - func->codeAnnotations[expr].inline_ = annotations.inline_; + func->codeAnnotations[expr].inline_ = annotation.inline_; } - if (annotations.effectsIfMoved) { + if (annotation.effectsIfMoved) { // Only possible inside functions. assert(func); func->codeAnnotations[expr].effectsIfMoved.emplace(); From eeba29786e005bba73a7b719b5f11b3f11495ee4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 27 Jan 2026 12:50:02 -0800 Subject: [PATCH 20/58] fmrt --- src/wasm/wasm-ir-builder.cpp | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 5b6d2864864..567ef67290c 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -1374,10 +1374,9 @@ Result<> IRBuilder::makeSwitch(const std::vector& labels, return Ok{}; } -Result<> -IRBuilder::makeCall(Name func, - bool isReturn, - const CodeAnnotation& annotations) { +Result<> IRBuilder::makeCall(Name func, + bool isReturn, + const CodeAnnotation& annotations) { auto sig = wasm.getFunction(func)->getSig(); Call curr(wasm.allocator); curr.target = func; @@ -1390,11 +1389,10 @@ IRBuilder::makeCall(Name func, return Ok{}; } -Result<> IRBuilder::makeCallIndirect( - Name table, - HeapType type, - bool isReturn, - const CodeAnnotation& annotations) { +Result<> IRBuilder::makeCallIndirect(Name table, + HeapType type, + bool isReturn, + const CodeAnnotation& annotations) { if (!type.isSignature()) { return Err{"expected function type annotation on call_indirect"}; } @@ -1917,10 +1915,9 @@ Result<> IRBuilder::makeI31Get(bool signed_) { return Ok{}; } -Result<> -IRBuilder::makeCallRef(HeapType type, - bool isReturn, - const CodeAnnotation& annotations) { +Result<> IRBuilder::makeCallRef(HeapType type, + bool isReturn, + const CodeAnnotation& annotations) { if (!type.isSignature()) { return Err{"expected function type annotation on call_ref"}; } From 902a233ddb31bffd116906de094fa38e23f450c4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 27 Jan 2026 16:45:40 -0800 Subject: [PATCH 21/58] work --- src/ir/metadata.cpp | 2 +- src/parser/contexts.h | 9 +++++---- src/wasm-ir-builder.h | 6 +++--- src/wasm/wasm-ir-builder.cpp | 14 +++++++------- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/ir/metadata.cpp b/src/ir/metadata.cpp index f69927ecc10..61c5da9bc06 100644 --- a/src/ir/metadata.cpp +++ b/src/ir/metadata.cpp @@ -130,7 +130,7 @@ bool equal(Function* a, Function* b) { bList.list[i], a->codeAnnotations, b->codeAnnotations, - Function::CodeAnnotation())) { + CodeAnnotation())) { return false; } } diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 510d7b46185..82327d2c3c3 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -1298,6 +1298,7 @@ struct ParseImplicitTypeDefsCtx : TypeParserCtx { struct AnnotationParserCtx { // Parse annotations into IR. + // TODO add branch hints CodeAnnotation parseAnnotations(const std::vector& annotations) { CodeAnnotation ret; @@ -1308,7 +1309,7 @@ struct AnnotationParserCtx { if (a.kind == Annotations::InlineHint) { inlineHint = &a; } else if (a.kind == Annotations::EffectsIfMovedHint) { - ret.effectsIfMovedHint.emplace(); + ret.effectsIfMoved.emplace(); } } @@ -1317,19 +1318,19 @@ struct AnnotationParserCtx { Lexer lexer(inlineHint->contents); if (lexer.empty()) { std::cerr << "warning: empty InlineHint\n"; - return std::nullopt; + return ret; } auto str = lexer.takeString(); if (!str || str->size() != 1) { std::cerr << "warning: invalid InlineHint string\n"; - return std::nullopt; + return ret; } uint8_t value = (*str)[0]; if (value > 127) { std::cerr << "warning: invalid InlineHint value\n"; - return std::nullopt; + return ret; } ret.inline_ = value; diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index 479ce58338d..b270f746506 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -129,11 +129,11 @@ class IRBuilder : public UnifiedExpressionVisitor> { Result<> makeNop(); Result<> makeBlock(Name label, Signature sig); Result<> - makeIf(Name label, Signature sig, std::optional likely = std::nullopt); + makeIf(Name label, Signature sig, const CodeAnnotation& annotations = CodeAnnotation()); Result<> makeLoop(Name label, Signature sig); Result<> makeBreak(Index label, bool isConditional, - std::optional likely = std::nullopt); + const CodeAnnotation& annotations = CodeAnnotation()); Result<> makeSwitch(const std::vector& labels, Index defaultLabel); // Unlike Builder::makeCall, this assumes the function already exists. Result<> makeCall(Name func, @@ -235,7 +235,7 @@ class IRBuilder : public UnifiedExpressionVisitor> { BrOnOp op, Type in = Type::none, Type out = Type::none, - std::optional likely = std::nullopt); + const CodeAnnotation& annotations = CodeAnnotation()); Result<> makeStructNew(HeapType type, bool isDesc); Result<> makeStructNewDefault(HeapType type, bool isDesc); Result<> diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 567ef67290c..30fc2ae93a2 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -1314,10 +1314,10 @@ Result<> IRBuilder::makeBlock(Name label, Signature sig) { } Result<> -IRBuilder::makeIf(Name label, Signature sig, std::optional likely) { +IRBuilder::makeIf(Name label, Signature sig, const CodeAnnotation& annotations) { auto* iff = wasm.allocator.alloc(); iff->type = sig.results; - addBranchHint(iff, likely); + applyAnnotations(annotations); return visitIfStart(iff, label, sig.params); } @@ -1330,7 +1330,7 @@ Result<> IRBuilder::makeLoop(Name label, Signature sig) { Result<> IRBuilder::makeBreak(Index label, bool isConditional, - std::optional likely) { + const CodeAnnotation& annotations) { auto name = getLabelName(label); CHECK_ERR(name); auto labelType = getLabelType(label); @@ -1342,7 +1342,7 @@ Result<> IRBuilder::makeBreak(Index label, curr.condition = isConditional ? &curr : nullptr; CHECK_ERR(ChildPopper{*this}.visitBreak(&curr, *labelType)); auto* br = builder.makeBreak(curr.name, curr.value, curr.condition); - addBranchHint(br, likely); + applyAnnotations(annotations); push(br); return Ok{}; @@ -1981,7 +1981,7 @@ Result<> IRBuilder::makeRefGetDesc(HeapType type) { } Result<> IRBuilder::makeBrOn( - Index label, BrOnOp op, Type in, Type out, std::optional likely) { + Index label, BrOnOp op, Type in, Type out, const CodeAnnotation& annotations) { std::optional descriptor; if (op == BrOnCastDesc || op == BrOnCastDescFail) { assert(out.isRef()); @@ -2062,7 +2062,7 @@ Result<> IRBuilder::makeBrOn( CHECK_ERR(name); auto* br = builder.makeBrOn(op, *name, curr.ref, out, curr.desc); - addBranchHint(br, likely); + applyAnnotations(annotations); push(br); return Ok{}; } @@ -2086,7 +2086,7 @@ Result<> IRBuilder::makeBrOn( // Perform the branch. CHECK_ERR(visitBrOn(&curr)); auto* br = builder.makeBrOn(op, extraLabel, curr.ref, out, curr.desc); - addBranchHint(br, likely); + applyAnnotations(annotations); push(br); // If the branch wasn't taken, we need to leave the extra values on the From 2e287a2c27c64fa59b83f5fcf759661884920730 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 27 Jan 2026 16:51:50 -0800 Subject: [PATCH 22/58] work --- src/parser/contexts.h | 99 +++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 59 deletions(-) diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 82327d2c3c3..48c36394a1a 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -1298,42 +1298,61 @@ struct ParseImplicitTypeDefsCtx : TypeParserCtx { struct AnnotationParserCtx { // Parse annotations into IR. - // TODO add branch hints CodeAnnotation parseAnnotations(const std::vector& annotations) { CodeAnnotation ret; // Find the hints. For hints with content we must find the last one, which // overrides the others. + const Annotation* branchHint = nullptr; const Annotation* inlineHint = nullptr; for (auto& a : annotations) { - if (a.kind == Annotations::InlineHint) { + if (a.kind == Annotations::BranchHint) { + branchHint = &a; + } else if (a.kind == Annotations::InlineHint) { inlineHint = &a; } else if (a.kind == Annotations::EffectsIfMovedHint) { ret.effectsIfMoved.emplace(); } } - // Apply the last inline hint, if any. + // Apply the last branch hint, if valid. + if (branchHint) { + Lexer lexer(branchHint->contents); + if (lexer.empty()) { + std::cerr << "warning: empty BranchHint\n"; + } else { + auto str = lexer.takeString(); + if (!str || str->size() != 1) { + std::cerr << "warning: invalid BranchHint string\n"; + } else { + auto value = (*str)[0]; + if (value != 0 && value != 1) { + std::cerr << "warning: invalid BranchHint value\n"; + } else { + return ret.branchLikely = bool(value); + } + } + } + } + + // Apply the last inline hint, if valid. if (inlineHint) { Lexer lexer(inlineHint->contents); if (lexer.empty()) { std::cerr << "warning: empty InlineHint\n"; - return ret; - } - - auto str = lexer.takeString(); - if (!str || str->size() != 1) { - std::cerr << "warning: invalid InlineHint string\n"; - return ret; - } - - uint8_t value = (*str)[0]; - if (value > 127) { - std::cerr << "warning: invalid InlineHint value\n"; - return ret; + } else { + auto str = lexer.takeString(); + if (!str || str->size() != 1) { + std::cerr << "warning: invalid InlineHint string\n"; + } else { + uint8_t value = (*str)[0]; + if (value > 127) { + std::cerr << "warning: invalid InlineHint value\n"; + } else { + ret.inline_ = value; + } + } } - - ret.inline_ = value; } return ret; @@ -2007,10 +2026,9 @@ struct ParseDefsCtx : TypeParserCtx, AnnotationParserCtx { if (!type.isSignature()) { return in.err(pos, "expected function type"); } - auto likely = getBranchHint(annotations); return withLoc( pos, - irBuilder.makeIf(label ? *label : Name{}, type.getSignature(), likely)); + irBuilder.makeIf(label ? *label : Name{}, type.getSignature(), parseAnnotations(annotations))); } Result<> visitElse() { return withLoc(irBuilder.visitElse()); } @@ -2445,47 +2463,11 @@ struct ParseDefsCtx : TypeParserCtx, AnnotationParserCtx { *t, type, isReturn, parseAnnotations(annotations))); } - // Return the branch hint for a branching instruction, if there is one. - std::optional - getBranchHint(const std::vector& annotations) { - // Find and apply (the last) branch hint. - const Annotation* hint = nullptr; - for (auto& a : annotations) { - if (a.kind == Annotations::BranchHint) { - hint = &a; - } - } - if (!hint) { - return std::nullopt; - } - - Lexer lexer(hint->contents); - if (lexer.empty()) { - std::cerr << "warning: empty BranchHint\n"; - return std::nullopt; - } - - auto str = lexer.takeString(); - if (!str || str->size() != 1) { - std::cerr << "warning: invalid BranchHint string\n"; - return std::nullopt; - } - - auto value = (*str)[0]; - if (value != 0 && value != 1) { - std::cerr << "warning: invalid BranchHint value\n"; - return std::nullopt; - } - - return bool(value); - } - Result<> makeBreak(Index pos, const std::vector& annotations, Index label, bool isConditional) { - auto likely = getBranchHint(annotations); - return withLoc(pos, irBuilder.makeBreak(label, isConditional, likely)); + return withLoc(pos, irBuilder.makeBreak(label, isConditional, parseAnnotations(annotations))); } Result<> makeSwitch(Index pos, @@ -2666,8 +2648,7 @@ struct ParseDefsCtx : TypeParserCtx, AnnotationParserCtx { BrOnOp op, Type in = Type::none, Type out = Type::none) { - auto likely = getBranchHint(annotations); - return withLoc(pos, irBuilder.makeBrOn(label, op, in, out, likely)); + return withLoc(pos, irBuilder.makeBrOn(label, op, in, out, parseAnnotations(annotations))); } Result<> makeStructNew(Index pos, From 45650e17de968245a6d8cef4ce4da03d08e7ce04 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 27 Jan 2026 16:52:25 -0800 Subject: [PATCH 23/58] work --- src/parser/contexts.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 48c36394a1a..30fb29429d9 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -1329,7 +1329,7 @@ struct AnnotationParserCtx { if (value != 0 && value != 1) { std::cerr << "warning: invalid BranchHint value\n"; } else { - return ret.branchLikely = bool(value); + ret.branchLikely = bool(value); } } } From 017f8e5689049d101e2c82731b56ece964c4ea1e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 27 Jan 2026 16:53:24 -0800 Subject: [PATCH 24/58] work --- src/wasm/wasm-ir-builder.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 30fc2ae93a2..a610642ae61 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -1317,7 +1317,7 @@ Result<> IRBuilder::makeIf(Name label, Signature sig, const CodeAnnotation& annotations) { auto* iff = wasm.allocator.alloc(); iff->type = sig.results; - applyAnnotations(annotations); + applyAnnotations(iff, annotations); return visitIfStart(iff, label, sig.params); } @@ -1342,7 +1342,7 @@ Result<> IRBuilder::makeBreak(Index label, curr.condition = isConditional ? &curr : nullptr; CHECK_ERR(ChildPopper{*this}.visitBreak(&curr, *labelType)); auto* br = builder.makeBreak(curr.name, curr.value, curr.condition); - applyAnnotations(annotations); + applyAnnotations(br, annotations); push(br); return Ok{}; @@ -2062,7 +2062,7 @@ Result<> IRBuilder::makeBrOn( CHECK_ERR(name); auto* br = builder.makeBrOn(op, *name, curr.ref, out, curr.desc); - applyAnnotations(annotations); + applyAnnotations(br, annotations); push(br); return Ok{}; } @@ -2086,7 +2086,7 @@ Result<> IRBuilder::makeBrOn( // Perform the branch. CHECK_ERR(visitBrOn(&curr)); auto* br = builder.makeBrOn(op, extraLabel, curr.ref, out, curr.desc); - applyAnnotations(annotations); + applyAnnotations(br, annotations); push(br); // If the branch wasn't taken, we need to leave the extra values on the From 4097eeceab67d859ec5cad5cd60ffd00dcecffb2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 27 Jan 2026 16:54:45 -0800 Subject: [PATCH 25/58] work --- src/wasm/wasm-binary.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 6f0c3190c0e..2c898cec6f5 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1635,7 +1635,7 @@ std::optional WasmBinaryWriter::writeExpressionHints( Expression* expr; // The offset we will write in the custom section. BinaryLocation offset; - Function::CodeAnnotation* hint; + CodeAnnotation* hint; }; struct FuncHints { @@ -1727,10 +1727,10 @@ std::optional WasmBinaryWriter::writeExpressionHints( std::optional WasmBinaryWriter::getBranchHintsBuffer() { return writeExpressionHints( Annotations::BranchHint, - [](const Function::CodeAnnotation& annotation) { + [](const CodeAnnotation& annotation) { return annotation.branchLikely; }, - [](const Function::CodeAnnotation& annotation, + [](const CodeAnnotation& annotation, BufferWithRandomAccess& buffer) { // Hint size, always 1 for now. buffer << U32LEB(1); @@ -1746,10 +1746,10 @@ std::optional WasmBinaryWriter::getBranchHintsBuffer() { std::optional WasmBinaryWriter::getInlineHintsBuffer() { return writeExpressionHints( Annotations::InlineHint, - [](const Function::CodeAnnotation& annotation) { + [](const CodeAnnotation& annotation) { return annotation.inline_; }, - [](const Function::CodeAnnotation& annotation, + [](const CodeAnnotation& annotation, BufferWithRandomAccess& buffer) { // Hint size, always 1 for now. buffer << U32LEB(1); @@ -1769,10 +1769,10 @@ std::optional WasmBinaryWriter::getEffectsIfMovedHintsBuffer() { return writeExpressionHints( Annotations::EffectsIfMovedHint, - [](const Function::CodeAnnotation& annotation) { + [](const CodeAnnotation& annotation) { return annotation.effectsIfMoved; }, - [](const Function::CodeAnnotation& annotation, + [](const CodeAnnotation& annotation, BufferWithRandomAccess& buffer) { // Hint size, always 0 for now. buffer << U32LEB(0); @@ -5481,7 +5481,7 @@ void WasmBinaryReader::readExpressionHints(Name sectionName, void WasmBinaryReader::readBranchHints(size_t payloadLen) { readExpressionHints(Annotations::BranchHint, payloadLen, - [&](Function::CodeAnnotation& annotation) { + [&](CodeAnnotation& annotation) { auto size = getU32LEB(); if (size != 1) { throwError("bad BranchHint size"); @@ -5499,7 +5499,7 @@ void WasmBinaryReader::readBranchHints(size_t payloadLen) { void WasmBinaryReader::readInlineHints(size_t payloadLen) { readExpressionHints(Annotations::InlineHint, payloadLen, - [&](Function::CodeAnnotation& annotation) { + [&](CodeAnnotation& annotation) { auto size = getU32LEB(); if (size != 1) { throwError("bad InlineHint size"); @@ -5517,7 +5517,7 @@ void WasmBinaryReader::readInlineHints(size_t payloadLen) { void WasmBinaryReader::readEffectsIfMovedHints(size_t payloadLen) { readExpressionHints(Annotations::EffectsIfMovedHint, payloadLen, - [&](Function::CodeAnnotation& annotation) { + [&](CodeAnnotation& annotation) { auto size = getU32LEB(); if (size != 0) { throwError("bad EffectsIfMovedHint size"); From 694e4f99354a302106fdbe8ff975035dc045cbc0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 27 Jan 2026 17:00:39 -0800 Subject: [PATCH 26/58] work --- src/parser/contexts.h | 15 +++++--- src/wasm-ir-builder.h | 5 ++- src/wasm/wasm-binary.cpp | 71 +++++++++++++++--------------------- src/wasm/wasm-ir-builder.cpp | 12 ++++-- 4 files changed, 51 insertions(+), 52 deletions(-) diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 30fb29429d9..e486de94002 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -2026,9 +2026,10 @@ struct ParseDefsCtx : TypeParserCtx, AnnotationParserCtx { if (!type.isSignature()) { return in.err(pos, "expected function type"); } - return withLoc( - pos, - irBuilder.makeIf(label ? *label : Name{}, type.getSignature(), parseAnnotations(annotations))); + return withLoc(pos, + irBuilder.makeIf(label ? *label : Name{}, + type.getSignature(), + parseAnnotations(annotations))); } Result<> visitElse() { return withLoc(irBuilder.visitElse()); } @@ -2467,7 +2468,9 @@ struct ParseDefsCtx : TypeParserCtx, AnnotationParserCtx { const std::vector& annotations, Index label, bool isConditional) { - return withLoc(pos, irBuilder.makeBreak(label, isConditional, parseAnnotations(annotations))); + return withLoc( + pos, + irBuilder.makeBreak(label, isConditional, parseAnnotations(annotations))); } Result<> makeSwitch(Index pos, @@ -2648,7 +2651,9 @@ struct ParseDefsCtx : TypeParserCtx, AnnotationParserCtx { BrOnOp op, Type in = Type::none, Type out = Type::none) { - return withLoc(pos, irBuilder.makeBrOn(label, op, in, out, parseAnnotations(annotations))); + return withLoc( + pos, + irBuilder.makeBrOn(label, op, in, out, parseAnnotations(annotations))); } Result<> makeStructNew(Index pos, diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index b270f746506..b54b40e8933 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -128,8 +128,9 @@ class IRBuilder : public UnifiedExpressionVisitor> { // signatures ensure that there are no missing fields. Result<> makeNop(); Result<> makeBlock(Name label, Signature sig); - Result<> - makeIf(Name label, Signature sig, const CodeAnnotation& annotations = CodeAnnotation()); + Result<> makeIf(Name label, + Signature sig, + const CodeAnnotation& annotations = CodeAnnotation()); Result<> makeLoop(Name label, Signature sig); Result<> makeBreak(Index label, bool isConditional, diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 2c898cec6f5..43bf2d667df 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1727,11 +1727,8 @@ std::optional WasmBinaryWriter::writeExpressionHints( std::optional WasmBinaryWriter::getBranchHintsBuffer() { return writeExpressionHints( Annotations::BranchHint, - [](const CodeAnnotation& annotation) { - return annotation.branchLikely; - }, - [](const CodeAnnotation& annotation, - BufferWithRandomAccess& buffer) { + [](const CodeAnnotation& annotation) { return annotation.branchLikely; }, + [](const CodeAnnotation& annotation, BufferWithRandomAccess& buffer) { // Hint size, always 1 for now. buffer << U32LEB(1); @@ -1746,11 +1743,8 @@ std::optional WasmBinaryWriter::getBranchHintsBuffer() { std::optional WasmBinaryWriter::getInlineHintsBuffer() { return writeExpressionHints( Annotations::InlineHint, - [](const CodeAnnotation& annotation) { - return annotation.inline_; - }, - [](const CodeAnnotation& annotation, - BufferWithRandomAccess& buffer) { + [](const CodeAnnotation& annotation) { return annotation.inline_; }, + [](const CodeAnnotation& annotation, BufferWithRandomAccess& buffer) { // Hint size, always 1 for now. buffer << U32LEB(1); @@ -1769,11 +1763,8 @@ std::optional WasmBinaryWriter::getEffectsIfMovedHintsBuffer() { return writeExpressionHints( Annotations::EffectsIfMovedHint, - [](const CodeAnnotation& annotation) { - return annotation.effectsIfMoved; - }, - [](const CodeAnnotation& annotation, - BufferWithRandomAccess& buffer) { + [](const CodeAnnotation& annotation) { return annotation.effectsIfMoved; }, + [](const CodeAnnotation& annotation, BufferWithRandomAccess& buffer) { // Hint size, always 0 for now. buffer << U32LEB(0); }); @@ -5479,39 +5470,37 @@ void WasmBinaryReader::readExpressionHints(Name sectionName, } void WasmBinaryReader::readBranchHints(size_t payloadLen) { - readExpressionHints(Annotations::BranchHint, - payloadLen, - [&](CodeAnnotation& annotation) { - auto size = getU32LEB(); - if (size != 1) { - throwError("bad BranchHint size"); - } + readExpressionHints( + Annotations::BranchHint, payloadLen, [&](CodeAnnotation& annotation) { + auto size = getU32LEB(); + if (size != 1) { + throwError("bad BranchHint size"); + } - auto likely = getU32LEB(); - if (likely != 0 && likely != 1) { - throwError("bad BranchHint value"); - } + auto likely = getU32LEB(); + if (likely != 0 && likely != 1) { + throwError("bad BranchHint value"); + } - annotation.branchLikely = likely; - }); + annotation.branchLikely = likely; + }); } void WasmBinaryReader::readInlineHints(size_t payloadLen) { - readExpressionHints(Annotations::InlineHint, - payloadLen, - [&](CodeAnnotation& annotation) { - auto size = getU32LEB(); - if (size != 1) { - throwError("bad InlineHint size"); - } + readExpressionHints( + Annotations::InlineHint, payloadLen, [&](CodeAnnotation& annotation) { + auto size = getU32LEB(); + if (size != 1) { + throwError("bad InlineHint size"); + } - uint8_t inline_ = getInt8(); - if (inline_ > 127) { - throwError("bad InlineHint value"); - } + uint8_t inline_ = getInt8(); + if (inline_ > 127) { + throwError("bad InlineHint value"); + } - annotation.inline_ = inline_; - }); + annotation.inline_ = inline_; + }); } void WasmBinaryReader::readEffectsIfMovedHints(size_t payloadLen) { diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index a610642ae61..139fdbca1dc 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -1313,8 +1313,9 @@ Result<> IRBuilder::makeBlock(Name label, Signature sig) { return visitBlockStart(block, sig.params); } -Result<> -IRBuilder::makeIf(Name label, Signature sig, const CodeAnnotation& annotations) { +Result<> IRBuilder::makeIf(Name label, + Signature sig, + const CodeAnnotation& annotations) { auto* iff = wasm.allocator.alloc(); iff->type = sig.results; applyAnnotations(iff, annotations); @@ -1980,8 +1981,11 @@ Result<> IRBuilder::makeRefGetDesc(HeapType type) { return Ok{}; } -Result<> IRBuilder::makeBrOn( - Index label, BrOnOp op, Type in, Type out, const CodeAnnotation& annotations) { +Result<> IRBuilder::makeBrOn(Index label, + BrOnOp op, + Type in, + Type out, + const CodeAnnotation& annotations) { std::optional descriptor; if (op == BrOnCastDesc || op == BrOnCastDescFail) { assert(out.isRef()); From a815fb7180ef12ee72ba1c3c54d137ad48775b97 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 28 Jan 2026 11:49:12 -0800 Subject: [PATCH 27/58] simpl --- src/wasm-binary.h | 23 +++++++++++------------ src/wasm/wasm-binary.cpp | 31 ++++++++++++------------------- 2 files changed, 23 insertions(+), 31 deletions(-) diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 449fa3fc09c..f324e533d17 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1720,21 +1720,20 @@ class WasmBinaryReader { void readDylink(size_t payloadLen); void readDylink0(size_t payloadLen); - // We read branch hints *after* the code section, even though they appear + // We read code annotations *after* the code section, even though they appear // earlier. That is simpler for us as we note expression locations as we scan - // code, and then just need to match them up. To do this, we note the branch - // hint position and size in the first pass, and handle it later. - size_t branchHintsPos = 0; - size_t branchHintsLen = 0; - void readBranchHints(size_t payloadLen); + // code, and then just need to match them up. To do this, we note the + // positions of annotation sections in the first pass, and handle them later. + struct AnnotationSectionInfo { + // The start position of the section. We will rewind to there to read it. + size_t pos; + // A lambda that will read the section, from that position. + std::function read; + }; + std::vector deferredAnnotationSections; - // Like branch hints, we note where the section is to read it later. - size_t inlineHintsPos = 0; - size_t inlineHintsLen = 0; + void readBranchHints(size_t payloadLen); void readInlineHints(size_t payloadLen); - - size_t effectsIfMovedHintsPos = 0; - size_t effectsIfMovedHintsLen = 0; void readEffectsIfMovedHints(size_t payloadLen); std::tuple diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 43bf2d667df..17d1a512ba6 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -2165,18 +2165,11 @@ void WasmBinaryReader::read() { } } - // Go back and parse things we deferred. - if (branchHintsPos) { - pos = branchHintsPos; - readBranchHints(branchHintsLen); - } - if (inlineHintsPos) { - pos = inlineHintsPos; - readInlineHints(inlineHintsLen); - } - if (effectsIfMovedHintsPos) { - pos = effectsIfMovedHintsPos; - readEffectsIfMovedHints(effectsIfMovedHintsLen); + // Go back and parse annotations we deferred. + for (auto& [annotationPos, read] : deferredAnnotationSections) { + // Rewind to the right position, and read. + pos = annotationPos; + read(); } validateBinary(); @@ -2200,15 +2193,15 @@ void WasmBinaryReader::readCustomSection(size_t payloadLen) { } else if (sectionName.equals(BinaryConsts::CustomSections::Dylink0)) { readDylink0(payloadLen); } else if (sectionName == Annotations::BranchHint) { - // Only note the position and length, we read this later. - branchHintsPos = pos; - branchHintsLen = payloadLen; + // Deferred. + deferredAnnotationSections.push_back( + AnnotationSectionInfo{pos, [=]() { readBranchHints(payloadLen); }}); } else if (sectionName == Annotations::InlineHint) { - inlineHintsPos = pos; - inlineHintsLen = payloadLen; + deferredAnnotationSections.push_back( + AnnotationSectionInfo{pos, [=]() { readInlineHints(payloadLen); }}); } else if (sectionName == Annotations::EffectsIfMovedHint) { - effectsIfMovedHintsPos = pos; - effectsIfMovedHintsLen = payloadLen; + deferredAnnotationSections.push_back(AnnotationSectionInfo{ + pos, [=]() { readEffectsIfMovedHints(payloadLen); }}); } else { // an unfamiliar custom section if (sectionName.equals(BinaryConsts::CustomSections::Linking)) { From 6a88e93e33f9d4826d8d1472aca4548940d8acef Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 29 Jan 2026 13:50:45 -0800 Subject: [PATCH 28/58] fmrt --- src/wasm/wasm-binary.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 42eb9188a49..5a2514b8596 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -2200,8 +2200,10 @@ void WasmBinaryReader::readCustomSection(size_t payloadLen) { deferredAnnotationSections.push_back(AnnotationSectionInfo{ pos, [this, payloadLen]() { this->readInlineHints(payloadLen); }}); } else if (sectionName == Annotations::EffectsIfMovedHint) { - deferredAnnotationSections.push_back(AnnotationSectionInfo{ - pos, [this, payloadLen]() { this->readEffectsIfMovedHints(payloadLen); }}); + deferredAnnotationSections.push_back( + AnnotationSectionInfo{pos, [this, payloadLen]() { + this->readEffectsIfMovedHints(payloadLen); + }}); } else { // an unfamiliar custom section if (sectionName.equals(BinaryConsts::CustomSections::Linking)) { From c4ec9977ef525f7ecd6df22dad4a3464b4a423c3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 29 Jan 2026 16:53:44 -0800 Subject: [PATCH 29/58] UNDO --- src/parser/contexts.h | 2 - src/passes/Print.cpp | 6 -- src/passes/Vacuum.cpp | 26 +----- src/wasm-annotations.h | 1 - src/wasm-binary.h | 2 - src/wasm.h | 8 +- src/wasm/wasm-binary.cpp | 33 +------- src/wasm/wasm-ir-builder.cpp | 6 -- src/wasm/wasm.cpp | 1 - test/lit/effects-if-moved-hints.wast | 31 ------- test/lit/passes/vacuum-effects-if-moved.wast | 89 -------------------- 11 files changed, 6 insertions(+), 199 deletions(-) delete mode 100644 test/lit/effects-if-moved-hints.wast delete mode 100644 test/lit/passes/vacuum-effects-if-moved.wast diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 2cd09e3fba5..f6cb63a3264 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -1315,8 +1315,6 @@ struct AnnotationParserCtx { branchHint = &a; } else if (a.kind == Annotations::InlineHint) { inlineHint = &a; - } else if (a.kind == Annotations::EffectsIfMovedHint) { - ret.effectsIfMoved.emplace(); } } diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 80d943da44e..623b5d884eb 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -2789,12 +2789,6 @@ void PrintSExpression::printCodeAnnotations(Expression* curr) { restoreNormalColor(o); doIndent(o, indent); } - if (annotation.effectsIfMoved) { - Colors::grey(o); - o << "(@" << Annotations::EffectsIfMovedHint << ")\n"; - restoreNormalColor(o); - doIndent(o, indent); - } } } diff --git a/src/passes/Vacuum.cpp b/src/passes/Vacuum.cpp index 7d29d1d3ee9..b5e223d9f21 100644 --- a/src/passes/Vacuum.cpp +++ b/src/passes/Vacuum.cpp @@ -91,8 +91,10 @@ struct Vacuum : public WalkerPass> { curr->is() || curr->is() || curr->is()) { return curr; } - // Check if this expression itself must be kept. - if (mustKeepUnusedParent(curr)) { + // Check if this expression itself has side effects, ignoring children. + EffectAnalyzer self(getPassOptions(), *getModule()); + self.visit(curr); + if (self.hasUnremovableSideEffects()) { return curr; } // The result isn't used, and this has no side effects itself, so we can @@ -128,26 +130,6 @@ struct Vacuum : public WalkerPass> { } } - // Check if a parent expression must be kept around, given the knowledge that - // its result is unused (dropped). This is basically just a call to - // ShallowEffectAnalyzer to see if we can remove it, except that given the - // result is unused, the relevant hint may help us. - bool mustKeepUnusedParent(Expression* curr) { - if (auto* call = curr->dynCast()) { - auto& annotations = getFunction()->codeAnnotations; - auto iter = annotations.find(call); - if (iter != annotations.end()) { - auto& annotation = iter->second; - if (annotation.effectsIfMoved) { - // No need to check effects, this can be removed. - return false; - } - } - } - ShallowEffectAnalyzer self(getPassOptions(), *getModule(), curr); - return self.hasUnremovableSideEffects(); - } - void visitBlock(Block* curr) { auto& list = curr->list; diff --git a/src/wasm-annotations.h b/src/wasm-annotations.h index cc4ee5083d9..888c356dcb9 100644 --- a/src/wasm-annotations.h +++ b/src/wasm-annotations.h @@ -27,7 +27,6 @@ namespace wasm::Annotations { extern const Name BranchHint; extern const Name InlineHint; -extern const Name EffectsIfMovedHint; } // namespace wasm::Annotations diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 43cc3167415..1706d657916 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1441,7 +1441,6 @@ class WasmBinaryWriter { std::optional getBranchHintsBuffer(); std::optional getInlineHintsBuffer(); - std::optional getEffectsIfMovedHintsBuffer(); // helpers void writeInlineString(std::string_view name); @@ -1734,7 +1733,6 @@ class WasmBinaryReader { void readBranchHints(size_t payloadLen); void readInlineHints(size_t payloadLen); - void readEffectsIfMovedHints(size_t payloadLen); std::tuple readMemoryAccess(bool isAtomic, bool isRMW); diff --git a/src/wasm.h b/src/wasm.h index f4fe79d4b9b..ecf0f664e59 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2243,14 +2243,8 @@ struct CodeAnnotation { static const uint8_t AlwaysInline = 127; std::optional inline_; - // Binaryen intrinsic: Mark as having side effects if moved, but having no - // effects in the current position. See |callsIfMoved| in effects.h. - // TODO: link to spec - std::optional effectsIfMoved; - bool operator==(const CodeAnnotation& other) const { - return branchLikely == other.branchLikely && inline_ == other.inline_ && - effectsIfMoved == other.effectsIfMoved; + return branchLikely == other.branchLikely && inline_ == other.inline_; } }; diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 5a2514b8596..b3f0a2eed36 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1623,7 +1623,6 @@ std::optional WasmBinaryWriter::writeCodeAnnotations() { append(getBranchHintsBuffer()); append(getInlineHintsBuffer()); - append(getEffectsIfMovedHintsBuffer()); return ret; } @@ -1759,17 +1758,6 @@ std::optional WasmBinaryWriter::getInlineHintsBuffer() { }); } -std::optional -WasmBinaryWriter::getEffectsIfMovedHintsBuffer() { - return writeExpressionHints( - Annotations::EffectsIfMovedHint, - [](const CodeAnnotation& annotation) { return annotation.effectsIfMoved; }, - [](const CodeAnnotation& annotation, BufferWithRandomAccess& buffer) { - // Hint size, always 0 for now. - buffer << U32LEB(0); - }); -} - void WasmBinaryWriter::writeData(const char* data, size_t size) { for (size_t i = 0; i < size; i++) { o << int8_t(data[i]); @@ -2041,8 +2029,7 @@ void WasmBinaryReader::preScan() { auto sectionName = getInlineString(); if (sectionName == Annotations::BranchHint || - sectionName == Annotations::InlineHint || - sectionName == Annotations::EffectsIfMovedHint) { + sectionName == Annotations::InlineHint) { // Code annotations require code locations. // TODO: We could note which functions require code locations, as an // optimization. @@ -2199,11 +2186,6 @@ void WasmBinaryReader::readCustomSection(size_t payloadLen) { } else if (sectionName == Annotations::InlineHint) { deferredAnnotationSections.push_back(AnnotationSectionInfo{ pos, [this, payloadLen]() { this->readInlineHints(payloadLen); }}); - } else if (sectionName == Annotations::EffectsIfMovedHint) { - deferredAnnotationSections.push_back( - AnnotationSectionInfo{pos, [this, payloadLen]() { - this->readEffectsIfMovedHints(payloadLen); - }}); } else { // an unfamiliar custom section if (sectionName.equals(BinaryConsts::CustomSections::Linking)) { @@ -5505,19 +5487,6 @@ void WasmBinaryReader::readInlineHints(size_t payloadLen) { }); } -void WasmBinaryReader::readEffectsIfMovedHints(size_t payloadLen) { - readExpressionHints(Annotations::EffectsIfMovedHint, - payloadLen, - [&](CodeAnnotation& annotation) { - auto size = getU32LEB(); - if (size != 0) { - throwError("bad EffectsIfMovedHint size"); - } - - annotation.effectsIfMoved.emplace(); - }); -} - std::tuple WasmBinaryReader::readMemoryAccess(bool isAtomic, bool isRMW) { auto rawAlignment = getU32LEB(); diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 52a91061614..a0eca5b35ad 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -2663,12 +2663,6 @@ void IRBuilder::applyAnnotations(Expression* expr, assert(func); func->codeAnnotations[expr].inline_ = annotation.inline_; } - - if (annotation.effectsIfMoved) { - // Only possible inside functions. - assert(func); - func->codeAnnotations[expr].effectsIfMoved.emplace(); - } } } // namespace wasm diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 38cc0b72762..ca20231aabd 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -69,7 +69,6 @@ namespace Annotations { const Name BranchHint = "metadata.code.branch_hint"; const Name InlineHint = "metadata.code.inline"; -const Name EffectsIfMovedHint = "binaryen.effects.if.moved"; } // namespace Annotations diff --git a/test/lit/effects-if-moved-hints.wast b/test/lit/effects-if-moved-hints.wast deleted file mode 100644 index eadde670a4b..00000000000 --- a/test/lit/effects-if-moved-hints.wast +++ /dev/null @@ -1,31 +0,0 @@ -;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. - -;; RUN: wasm-opt -all %s -S -o - | filecheck %s - -;; RUN: wasm-opt -all --roundtrip %s -S -o - | filecheck %s --check-prefix=RTRIP - -(module - ;; CHECK: (type $0 (func)) - - ;; CHECK: (func $func (type $0) - ;; CHECK-NEXT: (call $func) - ;; CHECK-NEXT: (@binaryen.effects.if.moved) - ;; CHECK-NEXT: (call $func) - ;; CHECK-NEXT: (call $func) - ;; CHECK-NEXT: ) - ;; RTRIP: (type $0 (func)) - - ;; RTRIP: (func $func (type $0) - ;; RTRIP-NEXT: (call $func) - ;; RTRIP-NEXT: (@binaryen.effects.if.moved) - ;; RTRIP-NEXT: (call $func) - ;; RTRIP-NEXT: (call $func) - ;; RTRIP-NEXT: ) - (func $func - ;; Three calls, one annotated in the middle. - (call $func) - (@binaryen.effects.if.moved) - (call $func) - (call $func) - ) -) diff --git a/test/lit/passes/vacuum-effects-if-moved.wast b/test/lit/passes/vacuum-effects-if-moved.wast deleted file mode 100644 index 19766013c2d..00000000000 --- a/test/lit/passes/vacuum-effects-if-moved.wast +++ /dev/null @@ -1,89 +0,0 @@ -;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. -;; RUN: wasm-opt %s --vacuum -all -S -o - | filecheck %s - -(module - ;; CHECK: (func $calls-dropped (type $0) (param $x i32) (result i32) - ;; CHECK-NEXT: (local.set $x - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (call $calls-dropped - ;; CHECK-NEXT: (i32.const 2) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (i32.const 3) - ;; CHECK-NEXT: ) - (func $calls-dropped (param $x i32) (result i32) - ;; This call is dropped, and marked with the hint, so we can remove it. - (drop - (@binaryen.effects.if.moved) - (call $calls-dropped - (i32.const 0) - ) - ) - ;; As above, but a parameter has effects. We must keep that around, without - ;; the call. - (drop - (@binaryen.effects.if.moved) - (call $calls-dropped - (local.tee $x - (i32.const 1) - ) - ) - ) - ;; Now we lack the hint, so we keep the call. - (drop - (call $calls-dropped - (i32.const 2) - ) - ) - (i32.const 3) - ) - - ;; CHECK: (func $calls-used (type $0) (param $x i32) (result i32) - ;; CHECK-NEXT: (local.set $x - ;; CHECK-NEXT: (@binaryen.effects.if.moved) - ;; CHECK-NEXT: (call $calls-used - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $x - ;; CHECK-NEXT: (@binaryen.effects.if.moved) - ;; CHECK-NEXT: (call $calls-used - ;; CHECK-NEXT: (local.tee $x - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (call $calls-used - ;; CHECK-NEXT: (i32.const 2) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (i32.const 3) - ;; CHECK-NEXT: ) - (func $calls-used (param $x i32) (result i32) - ;; As above, but the calls are not dropped, so we keep them. - (local.set $x - (@binaryen.effects.if.moved) - (call $calls-used - (i32.const 0) - ) - ) - (local.set $x - (@binaryen.effects.if.moved) - (call $calls-used - (local.tee $x - (i32.const 1) - ) - ) - ) - (drop - (call $calls-used - (i32.const 2) - ) - ) - (i32.const 3) - ) -) - From 4080447390ccc2738f29ae443589cf0d4a48157b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 29 Jan 2026 16:55:23 -0800 Subject: [PATCH 30/58] renamings --- src/parser/contexts.h | 2 ++ src/passes/Print.cpp | 6 ++++++ src/passes/Vacuum.cpp | 26 ++++++++++++++++++++++---- src/wasm-annotations.h | 1 + src/wasm-binary.h | 2 ++ src/wasm.h | 8 +++++++- src/wasm/wasm-binary.cpp | 33 ++++++++++++++++++++++++++++++++- src/wasm/wasm-ir-builder.cpp | 6 ++++++ src/wasm/wasm.cpp | 1 + 9 files changed, 79 insertions(+), 6 deletions(-) diff --git a/src/parser/contexts.h b/src/parser/contexts.h index f6cb63a3264..830d717bcfb 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -1315,6 +1315,8 @@ struct AnnotationParserCtx { branchHint = &a; } else if (a.kind == Annotations::InlineHint) { inlineHint = &a; + } else if (a.kind == Annotations::DeadIfUnusedHint) { + ret.deadIfUnused.emplace(); } } diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 623b5d884eb..bbe48a95248 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -2789,6 +2789,12 @@ void PrintSExpression::printCodeAnnotations(Expression* curr) { restoreNormalColor(o); doIndent(o, indent); } + if (annotation.deadIfUnused) { + Colors::grey(o); + o << "(@" << Annotations::DeadIfUnusedHint << ")\n"; + restoreNormalColor(o); + doIndent(o, indent); + } } } diff --git a/src/passes/Vacuum.cpp b/src/passes/Vacuum.cpp index b5e223d9f21..b42fa6bf2d5 100644 --- a/src/passes/Vacuum.cpp +++ b/src/passes/Vacuum.cpp @@ -91,10 +91,8 @@ struct Vacuum : public WalkerPass> { curr->is() || curr->is() || curr->is()) { return curr; } - // Check if this expression itself has side effects, ignoring children. - EffectAnalyzer self(getPassOptions(), *getModule()); - self.visit(curr); - if (self.hasUnremovableSideEffects()) { + // Check if this expression itself must be kept. + if (mustKeepUnusedParent(curr)) { return curr; } // The result isn't used, and this has no side effects itself, so we can @@ -130,6 +128,26 @@ struct Vacuum : public WalkerPass> { } } + // Check if a parent expression must be kept around, given the knowledge that + // its result is unused (dropped). This is basically just a call to + // ShallowEffectAnalyzer to see if we can remove it, except that given the + // result is unused, the relevant hint may help us. + bool mustKeepUnusedParent(Expression* curr) { + if (auto* call = curr->dynCast()) { + auto& annotations = getFunction()->codeAnnotations; + auto iter = annotations.find(call); + if (iter != annotations.end()) { + auto& annotation = iter->second; + if (annotation.deadIfUnused) { + // No need to check effects, this can be removed. + return false; + } + } + } + ShallowEffectAnalyzer self(getPassOptions(), *getModule(), curr); + return self.hasUnremovableSideEffects(); + } + void visitBlock(Block* curr) { auto& list = curr->list; diff --git a/src/wasm-annotations.h b/src/wasm-annotations.h index 888c356dcb9..afabb5c1e12 100644 --- a/src/wasm-annotations.h +++ b/src/wasm-annotations.h @@ -27,6 +27,7 @@ namespace wasm::Annotations { extern const Name BranchHint; extern const Name InlineHint; +extern const Name DeadIfUnusedHint; } // namespace wasm::Annotations diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 1706d657916..5eecb3f109d 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1441,6 +1441,7 @@ class WasmBinaryWriter { std::optional getBranchHintsBuffer(); std::optional getInlineHintsBuffer(); + std::optional getDeadIfUnusedHintsBuffer(); // helpers void writeInlineString(std::string_view name); @@ -1733,6 +1734,7 @@ class WasmBinaryReader { void readBranchHints(size_t payloadLen); void readInlineHints(size_t payloadLen); + void readDeadIfUnusedHints(size_t payloadLen); std::tuple readMemoryAccess(bool isAtomic, bool isRMW); diff --git a/src/wasm.h b/src/wasm.h index ecf0f664e59..91708c66e35 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2243,8 +2243,14 @@ struct CodeAnnotation { static const uint8_t AlwaysInline = 127; std::optional inline_; + // Binaryen intrinsic: Mark as having side effects if moved, but having no + // effects in the current position. See |deadIfUnused| in effects.h. + // TODO: link to spec + std::optional deadIfUnused; + bool operator==(const CodeAnnotation& other) const { - return branchLikely == other.branchLikely && inline_ == other.inline_; + return branchLikely == other.branchLikely && inline_ == other.inline_ && + deadIfUnused == other.deadIfUnused; } }; diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index b3f0a2eed36..af457a2f575 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1623,6 +1623,7 @@ std::optional WasmBinaryWriter::writeCodeAnnotations() { append(getBranchHintsBuffer()); append(getInlineHintsBuffer()); + append(getDeadIfUnusedHintsBuffer()); return ret; } @@ -1758,6 +1759,17 @@ std::optional WasmBinaryWriter::getInlineHintsBuffer() { }); } +std::optional +WasmBinaryWriter::getDeadIfUnusedHintsBuffer() { + return writeExpressionHints( + Annotations::DeadIfUnusedHint, + [](const CodeAnnotation& annotation) { return annotation.deadIfUnused; }, + [](const CodeAnnotation& annotation, BufferWithRandomAccess& buffer) { + // Hint size, always 0 for now. + buffer << U32LEB(0); + }); +} + void WasmBinaryWriter::writeData(const char* data, size_t size) { for (size_t i = 0; i < size; i++) { o << int8_t(data[i]); @@ -2029,7 +2041,8 @@ void WasmBinaryReader::preScan() { auto sectionName = getInlineString(); if (sectionName == Annotations::BranchHint || - sectionName == Annotations::InlineHint) { + sectionName == Annotations::InlineHint || + sectionName == Annotations::DeadIfUnusedHint) { // Code annotations require code locations. // TODO: We could note which functions require code locations, as an // optimization. @@ -2186,6 +2199,11 @@ void WasmBinaryReader::readCustomSection(size_t payloadLen) { } else if (sectionName == Annotations::InlineHint) { deferredAnnotationSections.push_back(AnnotationSectionInfo{ pos, [this, payloadLen]() { this->readInlineHints(payloadLen); }}); + } else if (sectionName == Annotations::DeadIfUnusedHint) { + deferredAnnotationSections.push_back( + AnnotationSectionInfo{pos, [this, payloadLen]() { + this->readDeadIfUnusedHints(payloadLen); + }}); } else { // an unfamiliar custom section if (sectionName.equals(BinaryConsts::CustomSections::Linking)) { @@ -5487,6 +5505,19 @@ void WasmBinaryReader::readInlineHints(size_t payloadLen) { }); } +void WasmBinaryReader::readDeadIfUnusedHints(size_t payloadLen) { + readExpressionHints(Annotations::DeadIfUnusedHint, + payloadLen, + [&](CodeAnnotation& annotation) { + auto size = getU32LEB(); + if (size != 0) { + throwError("bad DeadIfUnusedHint size"); + } + + annotation.deadIfUnused.emplace(); + }); +} + std::tuple WasmBinaryReader::readMemoryAccess(bool isAtomic, bool isRMW) { auto rawAlignment = getU32LEB(); diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index a0eca5b35ad..2c5823dc935 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -2663,6 +2663,12 @@ void IRBuilder::applyAnnotations(Expression* expr, assert(func); func->codeAnnotations[expr].inline_ = annotation.inline_; } + + if (annotation.deadIfUnused) { + // Only possible inside functions. + assert(func); + func->codeAnnotations[expr].deadIfUnused.emplace(); + } } } // namespace wasm diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index ca20231aabd..0c7c3269a88 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -69,6 +69,7 @@ namespace Annotations { const Name BranchHint = "metadata.code.branch_hint"; const Name InlineHint = "metadata.code.inline"; +const Name DeadIfUnusedHint = "binaryen.effects.if.moved"; } // namespace Annotations From 817dc647ea2cbcefbf160d3b119b11a550a33b60 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 29 Jan 2026 17:01:04 -0800 Subject: [PATCH 31/58] finish --- src/passes/Vacuum.cpp | 6 +- src/wasm.h | 10 ++- src/wasm/wasm-binary.cpp | 2 +- test/lit/effects-if-moved-hints.wast | 31 +++++++ test/lit/passes/vacuum-effects-if-moved.wast | 89 ++++++++++++++++++++ 5 files changed, 131 insertions(+), 7 deletions(-) create mode 100644 test/lit/effects-if-moved-hints.wast create mode 100644 test/lit/passes/vacuum-effects-if-moved.wast diff --git a/src/passes/Vacuum.cpp b/src/passes/Vacuum.cpp index b42fa6bf2d5..7ada662f5d1 100644 --- a/src/passes/Vacuum.cpp +++ b/src/passes/Vacuum.cpp @@ -131,7 +131,9 @@ struct Vacuum : public WalkerPass> { // Check if a parent expression must be kept around, given the knowledge that // its result is unused (dropped). This is basically just a call to // ShallowEffectAnalyzer to see if we can remove it, except that given the - // result is unused, the relevant hint may help us. + // result is unused, the relevant hint may help us. (This just checks the + // parent itself: it may have children that the caller must check and keep + // around if so.) bool mustKeepUnusedParent(Expression* curr) { if (auto* call = curr->dynCast()) { auto& annotations = getFunction()->codeAnnotations; @@ -139,7 +141,7 @@ struct Vacuum : public WalkerPass> { if (iter != annotations.end()) { auto& annotation = iter->second; if (annotation.deadIfUnused) { - // No need to check effects, this can be removed. + // This is unused, so it is dead - no need to even check effects. return false; } } diff --git a/src/wasm.h b/src/wasm.h index 91708c66e35..e5b4b89244f 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2233,7 +2233,7 @@ struct BinaryLocations { // Forward declaration for FuncEffectsMap. class EffectAnalyzer; -// Code annotations for VMs. +// Code annotations. struct CodeAnnotation { // Branch Hinting proposal: Whether the branch is likely, or unlikely. std::optional branchLikely; @@ -2243,9 +2243,11 @@ struct CodeAnnotation { static const uint8_t AlwaysInline = 127; std::optional inline_; - // Binaryen intrinsic: Mark as having side effects if moved, but having no - // effects in the current position. See |deadIfUnused| in effects.h. - // TODO: link to spec + // Toolchain hint: If this expression's result is unused, then the entire + // thing can be considered dead and removable. + // TODO: link to spec somewhere + // This is implemented as an optional of monostate (rather than a bool) for + // consistency with the other hints, all of whom are optionals. std::optional deadIfUnused; bool operator==(const CodeAnnotation& other) const { diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index af457a2f575..62ad50c2892 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1765,7 +1765,7 @@ WasmBinaryWriter::getDeadIfUnusedHintsBuffer() { Annotations::DeadIfUnusedHint, [](const CodeAnnotation& annotation) { return annotation.deadIfUnused; }, [](const CodeAnnotation& annotation, BufferWithRandomAccess& buffer) { - // Hint size, always 0 for now. + // Hint size, always empty. buffer << U32LEB(0); }); } diff --git a/test/lit/effects-if-moved-hints.wast b/test/lit/effects-if-moved-hints.wast new file mode 100644 index 00000000000..eadde670a4b --- /dev/null +++ b/test/lit/effects-if-moved-hints.wast @@ -0,0 +1,31 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-opt -all %s -S -o - | filecheck %s + +;; RUN: wasm-opt -all --roundtrip %s -S -o - | filecheck %s --check-prefix=RTRIP + +(module + ;; CHECK: (type $0 (func)) + + ;; CHECK: (func $func (type $0) + ;; CHECK-NEXT: (call $func) + ;; CHECK-NEXT: (@binaryen.effects.if.moved) + ;; CHECK-NEXT: (call $func) + ;; CHECK-NEXT: (call $func) + ;; CHECK-NEXT: ) + ;; RTRIP: (type $0 (func)) + + ;; RTRIP: (func $func (type $0) + ;; RTRIP-NEXT: (call $func) + ;; RTRIP-NEXT: (@binaryen.effects.if.moved) + ;; RTRIP-NEXT: (call $func) + ;; RTRIP-NEXT: (call $func) + ;; RTRIP-NEXT: ) + (func $func + ;; Three calls, one annotated in the middle. + (call $func) + (@binaryen.effects.if.moved) + (call $func) + (call $func) + ) +) diff --git a/test/lit/passes/vacuum-effects-if-moved.wast b/test/lit/passes/vacuum-effects-if-moved.wast new file mode 100644 index 00000000000..19766013c2d --- /dev/null +++ b/test/lit/passes/vacuum-effects-if-moved.wast @@ -0,0 +1,89 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: wasm-opt %s --vacuum -all -S -o - | filecheck %s + +(module + ;; CHECK: (func $calls-dropped (type $0) (param $x i32) (result i32) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $calls-dropped + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + (func $calls-dropped (param $x i32) (result i32) + ;; This call is dropped, and marked with the hint, so we can remove it. + (drop + (@binaryen.effects.if.moved) + (call $calls-dropped + (i32.const 0) + ) + ) + ;; As above, but a parameter has effects. We must keep that around, without + ;; the call. + (drop + (@binaryen.effects.if.moved) + (call $calls-dropped + (local.tee $x + (i32.const 1) + ) + ) + ) + ;; Now we lack the hint, so we keep the call. + (drop + (call $calls-dropped + (i32.const 2) + ) + ) + (i32.const 3) + ) + + ;; CHECK: (func $calls-used (type $0) (param $x i32) (result i32) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (@binaryen.effects.if.moved) + ;; CHECK-NEXT: (call $calls-used + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (@binaryen.effects.if.moved) + ;; CHECK-NEXT: (call $calls-used + ;; CHECK-NEXT: (local.tee $x + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $calls-used + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + (func $calls-used (param $x i32) (result i32) + ;; As above, but the calls are not dropped, so we keep them. + (local.set $x + (@binaryen.effects.if.moved) + (call $calls-used + (i32.const 0) + ) + ) + (local.set $x + (@binaryen.effects.if.moved) + (call $calls-used + (local.tee $x + (i32.const 1) + ) + ) + ) + (drop + (call $calls-used + (i32.const 2) + ) + ) + (i32.const 3) + ) +) + From 130e5a16f277ee3cf4daeb8792efa7e8ae65e490 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 29 Jan 2026 17:02:27 -0800 Subject: [PATCH 32/58] finish --- ...fects-if-moved-hints.wast => dead-if-unused.wast} | 6 +++--- ...ects-if-moved.wast => vacuum-dead-if-unused.wast} | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) rename test/lit/{effects-if-moved-hints.wast => dead-if-unused.wast} (85%) rename test/lit/passes/{vacuum-effects-if-moved.wast => vacuum-dead-if-unused.wast} (89%) diff --git a/test/lit/effects-if-moved-hints.wast b/test/lit/dead-if-unused.wast similarity index 85% rename from test/lit/effects-if-moved-hints.wast rename to test/lit/dead-if-unused.wast index eadde670a4b..ad0fb68fb67 100644 --- a/test/lit/effects-if-moved-hints.wast +++ b/test/lit/dead-if-unused.wast @@ -9,7 +9,7 @@ ;; CHECK: (func $func (type $0) ;; CHECK-NEXT: (call $func) - ;; CHECK-NEXT: (@binaryen.effects.if.moved) + ;; CHECK-NEXT: (@binaryen.dead.if.unused) ;; CHECK-NEXT: (call $func) ;; CHECK-NEXT: (call $func) ;; CHECK-NEXT: ) @@ -17,14 +17,14 @@ ;; RTRIP: (func $func (type $0) ;; RTRIP-NEXT: (call $func) - ;; RTRIP-NEXT: (@binaryen.effects.if.moved) + ;; RTRIP-NEXT: (@binaryen.dead.if.unused) ;; RTRIP-NEXT: (call $func) ;; RTRIP-NEXT: (call $func) ;; RTRIP-NEXT: ) (func $func ;; Three calls, one annotated in the middle. (call $func) - (@binaryen.effects.if.moved) + (@binaryen.dead.if.unused) (call $func) (call $func) ) diff --git a/test/lit/passes/vacuum-effects-if-moved.wast b/test/lit/passes/vacuum-dead-if-unused.wast similarity index 89% rename from test/lit/passes/vacuum-effects-if-moved.wast rename to test/lit/passes/vacuum-dead-if-unused.wast index 19766013c2d..b0d9a7b1d6c 100644 --- a/test/lit/passes/vacuum-effects-if-moved.wast +++ b/test/lit/passes/vacuum-dead-if-unused.wast @@ -16,7 +16,7 @@ (func $calls-dropped (param $x i32) (result i32) ;; This call is dropped, and marked with the hint, so we can remove it. (drop - (@binaryen.effects.if.moved) + (@binaryen.dead.if.unused) (call $calls-dropped (i32.const 0) ) @@ -24,7 +24,7 @@ ;; As above, but a parameter has effects. We must keep that around, without ;; the call. (drop - (@binaryen.effects.if.moved) + (@binaryen.dead.if.unused) (call $calls-dropped (local.tee $x (i32.const 1) @@ -42,13 +42,13 @@ ;; CHECK: (func $calls-used (type $0) (param $x i32) (result i32) ;; CHECK-NEXT: (local.set $x - ;; CHECK-NEXT: (@binaryen.effects.if.moved) + ;; CHECK-NEXT: (@binaryen.dead.if.unused) ;; CHECK-NEXT: (call $calls-used ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $x - ;; CHECK-NEXT: (@binaryen.effects.if.moved) + ;; CHECK-NEXT: (@binaryen.dead.if.unused) ;; CHECK-NEXT: (call $calls-used ;; CHECK-NEXT: (local.tee $x ;; CHECK-NEXT: (i32.const 1) @@ -65,13 +65,13 @@ (func $calls-used (param $x i32) (result i32) ;; As above, but the calls are not dropped, so we keep them. (local.set $x - (@binaryen.effects.if.moved) + (@binaryen.dead.if.unused) (call $calls-used (i32.const 0) ) ) (local.set $x - (@binaryen.effects.if.moved) + (@binaryen.dead.if.unused) (call $calls-used (local.tee $x (i32.const 1) From d1eec8387eba1c771cbec3a1d8356268a139bf95 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 29 Jan 2026 17:02:52 -0800 Subject: [PATCH 33/58] finish --- src/wasm/wasm.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 0c7c3269a88..b71cadfcd6a 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -69,7 +69,7 @@ namespace Annotations { const Name BranchHint = "metadata.code.branch_hint"; const Name InlineHint = "metadata.code.inline"; -const Name DeadIfUnusedHint = "binaryen.effects.if.moved"; +const Name DeadIfUnusedHint = "binaryen.dead.if.unused"; } // namespace Annotations From a266a055204a99dc6703fb09d51f04e206dbd7ce Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 30 Jan 2026 10:33:15 -0800 Subject: [PATCH 34/58] work --- src/wasm/wasm-binary.cpp | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 62ad50c2892..d421cf4592e 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -2200,10 +2200,8 @@ void WasmBinaryReader::readCustomSection(size_t payloadLen) { deferredAnnotationSections.push_back(AnnotationSectionInfo{ pos, [this, payloadLen]() { this->readInlineHints(payloadLen); }}); } else if (sectionName == Annotations::DeadIfUnusedHint) { - deferredAnnotationSections.push_back( - AnnotationSectionInfo{pos, [this, payloadLen]() { - this->readDeadIfUnusedHints(payloadLen); - }}); + deferredAnnotationSections.push_back(AnnotationSectionInfo{ + pos, [this, payloadLen]() { this->readDeadIfUnusedHints(payloadLen); }}); } else { // an unfamiliar custom section if (sectionName.equals(BinaryConsts::CustomSections::Linking)) { @@ -5506,16 +5504,15 @@ void WasmBinaryReader::readInlineHints(size_t payloadLen) { } void WasmBinaryReader::readDeadIfUnusedHints(size_t payloadLen) { - readExpressionHints(Annotations::DeadIfUnusedHint, - payloadLen, - [&](CodeAnnotation& annotation) { - auto size = getU32LEB(); - if (size != 0) { - throwError("bad DeadIfUnusedHint size"); - } - - annotation.deadIfUnused.emplace(); - }); + readExpressionHints( + Annotations::DeadIfUnusedHint, payloadLen, [&](CodeAnnotation& annotation) { + auto size = getU32LEB(); + if (size != 0) { + throwError("bad DeadIfUnusedHint size"); + } + + annotation.deadIfUnused.emplace(); + }); } std::tuple From e474857d206482a669b0012367a573fa3edb3976 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 30 Jan 2026 11:31:36 -0800 Subject: [PATCH 35/58] work --- src/wasm.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/wasm.h b/src/wasm.h index e5b4b89244f..0d1fec71b1e 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2233,7 +2233,8 @@ struct BinaryLocations { // Forward declaration for FuncEffectsMap. class EffectAnalyzer; -// Code annotations. +// Annotation for a particular piece of code. This includes std::optionals for +// all possible annotations, with the ones present being filled in. struct CodeAnnotation { // Branch Hinting proposal: Whether the branch is likely, or unlikely. std::optional branchLikely; @@ -2246,8 +2247,6 @@ struct CodeAnnotation { // Toolchain hint: If this expression's result is unused, then the entire // thing can be considered dead and removable. // TODO: link to spec somewhere - // This is implemented as an optional of monostate (rather than a bool) for - // consistency with the other hints, all of whom are optionals. std::optional deadIfUnused; bool operator==(const CodeAnnotation& other) const { From 0b4d4f9bc67a21af4b02a73faa51b56d2242828f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 4 Feb 2026 12:07:44 -0800 Subject: [PATCH 36/58] hhow? --- src/passes/Vacuum.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/passes/Vacuum.cpp b/src/passes/Vacuum.cpp index 7ada662f5d1..769b08e2208 100644 --- a/src/passes/Vacuum.cpp +++ b/src/passes/Vacuum.cpp @@ -136,6 +136,7 @@ struct Vacuum : public WalkerPass> { // around if so.) bool mustKeepUnusedParent(Expression* curr) { if (auto* call = curr->dynCast()) { + // Look for an annotation on the call. TODO howww auto& annotations = getFunction()->codeAnnotations; auto iter = annotations.find(call); if (iter != annotations.end()) { @@ -145,6 +146,17 @@ struct Vacuum : public WalkerPass> { return false; } } + + // Look for an annotation on the called function. + auto* func = getModule()->getFunction(call->target); + auto iter = annotations.find(nullptr); + if (iter != annotations.end()) { + auto& annotation = iter->second; + if (annotation.deadIfUnused) { + // This is unused, so it is dead - no need to even check effects. + return false; + } + } } ShallowEffectAnalyzer self(getPassOptions(), *getModule(), curr); return self.hasUnremovableSideEffects(); From 7cac1943797d4dcd5be7380c63b291a415a140d1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 4 Feb 2026 12:07:55 -0800 Subject: [PATCH 37/58] hhow? --- .../passes/vacuum-dead-if-unused-func.wast | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 test/lit/passes/vacuum-dead-if-unused-func.wast diff --git a/test/lit/passes/vacuum-dead-if-unused-func.wast b/test/lit/passes/vacuum-dead-if-unused-func.wast new file mode 100644 index 00000000000..e49c6e6d878 --- /dev/null +++ b/test/lit/passes/vacuum-dead-if-unused-func.wast @@ -0,0 +1,48 @@ +;; RUN: wasm-opt -all --vacuum %s -S -o - | filecheck %s + +;; Test the function-level annotation of dead.if.unused. +(module + (@binaryen.dead.if.unused) + (func $calls-marked (param $x i32) (result i32) + ;; The function is marked as dead if unused, and this is dropped, so optimize. + (drop + (call $calls-marked + (i32.const 0) + ) + ) + ;; Not dropped, so keep it. + (local.set $x + (call $calls-marked + (i32.const 1) + ) + ) + (i32.const 2) + ) + + (func $calls-unmarked (param $x i32) (result i32) + ;; As above, but unmarked with the hint. We change nothing here. + (drop + (call $calls-unmarked + (i32.const 0) + ) + ) + ;; Not dropped, so keep it. + (local.set $x + (call $calls-unmarked + (i32.const 1) + ) + ) + (i32.const 2) + ) +) + +;; CHECK: (module +;; CHECK-NEXT: (type $0 (func)) +;; CHECK-NEXT: (@metadata.code.inline "\12") +;; CHECK-NEXT: (func $func-annotation +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (i32.const 0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + From 06581432fc90fd92178207b483ae83afab99bab5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 5 Feb 2026 08:36:10 -0800 Subject: [PATCH 38/58] finish --- src/passes/Vacuum.cpp | 37 ++++++------- .../passes/vacuum-dead-if-unused-func.wast | 52 ++++++++++++++++--- 2 files changed, 65 insertions(+), 24 deletions(-) diff --git a/src/passes/Vacuum.cpp b/src/passes/Vacuum.cpp index 769b08e2208..8a02d6e5573 100644 --- a/src/passes/Vacuum.cpp +++ b/src/passes/Vacuum.cpp @@ -136,26 +136,27 @@ struct Vacuum : public WalkerPass> { // around if so.) bool mustKeepUnusedParent(Expression* curr) { if (auto* call = curr->dynCast()) { - // Look for an annotation on the call. TODO howww - auto& annotations = getFunction()->codeAnnotations; - auto iter = annotations.find(call); - if (iter != annotations.end()) { - auto& annotation = iter->second; - if (annotation.deadIfUnused) { - // This is unused, so it is dead - no need to even check effects. - return false; + // Return true if a target in a function is annotated as dead if unused. + // If |curr| is marked so, then it is dead without even checking effects. + auto checkDeadIfUnused = [](Function* func, Expression* target) { + auto& annotations = func->codeAnnotations; + auto iter = annotations.find(target); + if (iter != annotations.end()) { + auto& annotation = iter->second; + if (annotation.deadIfUnused) { + return true; + } } - } + return false; + }; - // Look for an annotation on the called function. - auto* func = getModule()->getFunction(call->target); - auto iter = annotations.find(nullptr); - if (iter != annotations.end()) { - auto& annotation = iter->second; - if (annotation.deadIfUnused) { - // This is unused, so it is dead - no need to even check effects. - return false; - } + // Look for an annotation on the call. + if (checkDeadIfUnused(getFunction(), call)) { + return false; + } + // Check on the called function. + if (checkDeadIfUnused(getModule()->getFunction(call->target), nullptr)) { + return false; } } ShallowEffectAnalyzer self(getPassOptions(), *getModule(), curr); diff --git a/test/lit/passes/vacuum-dead-if-unused-func.wast b/test/lit/passes/vacuum-dead-if-unused-func.wast index e49c6e6d878..b5f64e08aae 100644 --- a/test/lit/passes/vacuum-dead-if-unused-func.wast +++ b/test/lit/passes/vacuum-dead-if-unused-func.wast @@ -26,7 +26,6 @@ (i32.const 0) ) ) - ;; Not dropped, so keep it. (local.set $x (call $calls-unmarked (i32.const 1) @@ -34,15 +33,56 @@ ) (i32.const 2) ) + + (func $calls-other (param $x i32) (result i32) + ;; As above, but calling another function, to check we look for annotations in + ;; the right place. Both calls are dropped, and only the one to the marked + ;; function should be removed. + (drop + (call $calls-marked + (i32.const 0) + ) + ) + (drop + (call $calls-unmarked + (i32.const 1) + ) + ) + (i32.const 2) + ) ) -;; CHECK: (module -;; CHECK-NEXT: (type $0 (func)) -;; CHECK-NEXT: (@metadata.code.inline "\12") -;; CHECK-NEXT: (func $func-annotation +;; CHECK: (module +;; CHECK-NEXT: (type $0 (func (param i32) (result i32))) +;; CHECK-NEXT: (@binaryen.dead.if.unused) +;; CHECK-NEXT: (func $calls-marked (type $0) (param $x i32) (result i32) +;; CHECK-NEXT: (local.set $x +;; CHECK-NEXT: (call $calls-marked +;; CHECK-NEXT: (i32.const 1) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.const 2) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (func $calls-unmarked (type $0) (param $x i32) (result i32) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (call $calls-unmarked +;; CHECK-NEXT: (i32.const 0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.set $x +;; CHECK-NEXT: (call $calls-unmarked +;; CHECK-NEXT: (i32.const 1) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.const 2) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (func $calls-other (type $0) (param $x i32) (result i32) ;; CHECK-NEXT: (drop -;; CHECK-NEXT: (i32.const 0) +;; CHECK-NEXT: (call $calls-unmarked +;; CHECK-NEXT: (i32.const 1) +;; CHECK-NEXT: ) ;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) From a2aea656fd6a0b2e33598f01e8f22cfcee706d14 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 5 Feb 2026 08:43:51 -0800 Subject: [PATCH 39/58] fix --- test/lit/passes/vacuum-dead-if-unused.wast | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/lit/passes/vacuum-dead-if-unused.wast b/test/lit/passes/vacuum-dead-if-unused.wast index b0d9a7b1d6c..8cfdb52ac87 100644 --- a/test/lit/passes/vacuum-dead-if-unused.wast +++ b/test/lit/passes/vacuum-dead-if-unused.wast @@ -55,7 +55,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (call $calls-used ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) @@ -78,7 +78,7 @@ ) ) ) - (drop + (local.set $x (call $calls-used (i32.const 2) ) From a8857b5ad9fc2ee12d19f6edb0ad8b194af672e2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 5 Feb 2026 09:00:32 -0800 Subject: [PATCH 40/58] fix --- src/passes/Vacuum.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/passes/Vacuum.cpp b/src/passes/Vacuum.cpp index 8a02d6e5573..b8283e8a5be 100644 --- a/src/passes/Vacuum.cpp +++ b/src/passes/Vacuum.cpp @@ -154,9 +154,12 @@ struct Vacuum : public WalkerPass> { if (checkDeadIfUnused(getFunction(), call)) { return false; } - // Check on the called function. - if (checkDeadIfUnused(getModule()->getFunction(call->target), nullptr)) { - return false; + // Check on the called function, if it exists (it may not if the IR is + // still being built up). + if (auto* target = getModule()->getFunctionOrNull(call->target)) { + if (checkDeadIfUnused(target, nullptr)) { + return false; + } } } ShallowEffectAnalyzer self(getPassOptions(), *getModule(), curr); From 9b33ec1029b3022a377642c9dde724027d5d8e9d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 5 Feb 2026 09:17:49 -0800 Subject: [PATCH 41/58] fuzz --- scripts/test/fuzzing.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 9c0125a9206..aeae7a11623 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -109,6 +109,11 @@ # Contains a name with "__fuzz_split", indicating it is emitted by # wasm-split, confusing the fuzzer because it is in the initial content. 'fuzz_shell_second.wast', + # We cannot fuzz semantics-altering intrinsics, as when we optimize the + # behavior changes. + 'dead-if-unused.wast', + 'vacuum-dead-if-unused.wast', + 'vacuum-dead-if-unused-func.wast', ] From ed1466f2d1f6a184c5e00fa848d86713049534bc Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 5 Feb 2026 14:30:12 -0800 Subject: [PATCH 42/58] test roundtripping of function-level annotation --- test/lit/dead-if-unused-func.wast | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 test/lit/dead-if-unused-func.wast diff --git a/test/lit/dead-if-unused-func.wast b/test/lit/dead-if-unused-func.wast new file mode 100644 index 00000000000..9ea0cfeb262 --- /dev/null +++ b/test/lit/dead-if-unused-func.wast @@ -0,0 +1,23 @@ +;; RUN: wasm-opt -all %s -S -o - | filecheck %s +;; RUN: wasm-opt -all --roundtrip %s -S -o - | filecheck %s + +(module + (@binaryen.dead.if.unused) + (func $func-annotation + ;; The annotation here is on the function. + (drop + (i32.const 0) + ) + ) +) + +;; CHECK: (module +;; CHECK-NEXT: (type $0 (func)) +;; CHECK-NEXT: (@binaryen.dead.if.unused) +;; CHECK-NEXT: (func $func-annotation +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (i32.const 0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + From dafbdbe8e2f474d66e57b6f626603f4a1d7622ad Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 5 Feb 2026 15:18:32 -0800 Subject: [PATCH 43/58] simpler --- src/ir/intrinsics.h | 42 ++++++++++++++++++++++++++++++++++++++++++ src/passes/Vacuum.cpp | 25 ++++--------------------- 2 files changed, 46 insertions(+), 21 deletions(-) diff --git a/src/ir/intrinsics.h b/src/ir/intrinsics.h index 43e080b806b..ac679a4617d 100644 --- a/src/ir/intrinsics.h +++ b/src/ir/intrinsics.h @@ -111,6 +111,48 @@ class Intrinsics { std::vector getConfigureAllFunctions(Call* call); // As above, but looks through the module to find the configureAll. std::vector getConfigureAllFunctions(); + + // Get the code annotations for an expression in a function. + CodeAnnotation getAnnotations(Expression* curr, Function* func) { + auto& annotations = func->codeAnnotations; + auto iter = annotations.find(curr); + if (iter != annotations.end()) { + return iter->second; + } + return {}; + } + + // Get the code annotations for a function itself. + CodeAnnotation getAnnotations(Function* func) { + return getAnnotations(nullptr, func); + } + + // Given a call in a function, return all the annotations for it. The call may + // be annotated itself (which takes precedence), or the function it calls be + // annotated. + CodeAnnotation getCallAnnotations(Call* call, Function* func) { + // Combine annotations from the call itself and from the called function. + auto ret = getAnnotations(call, func); + + // Check on the called function, if it exists (it may not if the IR is still + // being built up). + if (auto* target = module.getFunctionOrNull(call->target)) { + auto funcAnnotations = getAnnotations(target); + + // Merge them, giving precedence for the call annotation. + if (!ret.branchLikely) { + ret.branchLikely = funcAnnotations.branchLikely; + } + if (!ret.inline_) { + ret.inline_ = funcAnnotations.inline_; + } + if (!ret.deadIfUnused) { + ret.deadIfUnused = funcAnnotations.deadIfUnused; + } + } + + return ret; + } }; } // namespace wasm diff --git a/src/passes/Vacuum.cpp b/src/passes/Vacuum.cpp index b8283e8a5be..f7ced825e08 100644 --- a/src/passes/Vacuum.cpp +++ b/src/passes/Vacuum.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -138,29 +139,11 @@ struct Vacuum : public WalkerPass> { if (auto* call = curr->dynCast()) { // Return true if a target in a function is annotated as dead if unused. // If |curr| is marked so, then it is dead without even checking effects. - auto checkDeadIfUnused = [](Function* func, Expression* target) { - auto& annotations = func->codeAnnotations; - auto iter = annotations.find(target); - if (iter != annotations.end()) { - auto& annotation = iter->second; - if (annotation.deadIfUnused) { - return true; - } - } - return false; - }; - - // Look for an annotation on the call. - if (checkDeadIfUnused(getFunction(), call)) { + if (Intrinsics(*getModule()) + .getCallAnnotations(call, getFunction()) + .deadIfUnused) { return false; } - // Check on the called function, if it exists (it may not if the IR is - // still being built up). - if (auto* target = getModule()->getFunctionOrNull(call->target)) { - if (checkDeadIfUnused(target, nullptr)) { - return false; - } - } } ShallowEffectAnalyzer self(getPassOptions(), *getModule(), curr); return self.hasUnremovableSideEffects(); From 708f8b2c048c9377ae2e29ea1c18baf068fe24b2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 5 Feb 2026 15:26:06 -0800 Subject: [PATCH 44/58] just use bool --- src/wasm.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/wasm.h b/src/wasm.h index df3129a4d8d..df70d090305 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2234,7 +2234,8 @@ struct BinaryLocations { class EffectAnalyzer; // Annotation for a particular piece of code. This includes std::optionals for -// all possible annotations, with the ones present being filled in. +// all possible annotations, with the ones present being filled in (or just a +// bool for an annotation with one possible value). struct CodeAnnotation { // Branch Hinting proposal: Whether the branch is likely, or unlikely. std::optional branchLikely; @@ -2247,7 +2248,7 @@ struct CodeAnnotation { // Toolchain hint: If this expression's result is unused, then the entire // thing can be considered dead and removable. // TODO: link to spec somewhere - std::optional deadIfUnused; + bool deadIfUnused; bool operator==(const CodeAnnotation& other) const { return branchLikely == other.branchLikely && inline_ == other.inline_ && From f3c73c66b5b2c9191e16e828880d8b724f67e4a1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 5 Feb 2026 15:30:37 -0800 Subject: [PATCH 45/58] fix --- src/parser/contexts.h | 2 +- src/wasm/wasm-binary.cpp | 2 +- src/wasm/wasm-ir-builder.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 952f1b0d8d1..481584f7770 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -1316,7 +1316,7 @@ struct AnnotationParserCtx { } else if (a.kind == Annotations::InlineHint) { inlineHint = &a; } else if (a.kind == Annotations::DeadIfUnusedHint) { - ret.deadIfUnused.emplace(); + ret.deadIfUnused = true; } } diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 2c3d635f392..5450575a932 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -5531,7 +5531,7 @@ void WasmBinaryReader::readDeadIfUnusedHints(size_t payloadLen) { throwError("bad DeadIfUnusedHint size"); } - annotation.deadIfUnused.emplace(); + annotation.deadIfUnused = true; }); } diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 06ecea8ccda..a04494dddee 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -2665,7 +2665,7 @@ void IRBuilder::applyAnnotations(Expression* expr, if (annotation.deadIfUnused) { // Only possible inside functions. assert(func); - func->codeAnnotations[expr].deadIfUnused.emplace(); + func->codeAnnotations[expr].deadIfUnused = true; } } From 1ad327c9dc42020b1502b5b975554a05e8f51603 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 5 Feb 2026 15:41:09 -0800 Subject: [PATCH 46/58] unfuzz --- scripts/test/fuzzing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index aeae7a11623..6f2769b3d5d 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -112,6 +112,7 @@ # We cannot fuzz semantics-altering intrinsics, as when we optimize the # behavior changes. 'dead-if-unused.wast', + 'dead-if-unused-func.wast', 'vacuum-dead-if-unused.wast', 'vacuum-dead-if-unused-func.wast', ] From 6544c89fb2490e3f2662508f9a4368b18f9775ce Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 5 Feb 2026 16:07:07 -0800 Subject: [PATCH 47/58] default --- src/wasm.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wasm.h b/src/wasm.h index df70d090305..18cda8042d2 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2248,7 +2248,7 @@ struct CodeAnnotation { // Toolchain hint: If this expression's result is unused, then the entire // thing can be considered dead and removable. // TODO: link to spec somewhere - bool deadIfUnused; + bool deadIfUnused = false; bool operator==(const CodeAnnotation& other) const { return branchLikely == other.branchLikely && inline_ == other.inline_ && From 3be3f481d2b0bbb23ae35d437eb5d8985172d406 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 9 Feb 2026 13:13:53 -0800 Subject: [PATCH 48/58] rename --- src/ir/intrinsics.h | 4 ++-- src/parser/contexts.h | 4 ++-- src/passes/Print.cpp | 4 ++-- src/passes/Vacuum.cpp | 2 +- src/wasm-binary.h | 4 ++-- src/wasm.h | 4 ++-- src/wasm/wasm-binary.cpp | 22 +++++++++---------- src/wasm/wasm-ir-builder.cpp | 4 ++-- src/wasm/wasm.cpp | 2 +- test/lit/dead-if-unused-func.wast | 4 ++-- test/lit/dead-if-unused.wast | 6 ++--- .../passes/vacuum-dead-if-unused-func.wast | 6 ++--- test/lit/passes/vacuum-dead-if-unused.wast | 12 +++++----- 13 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/ir/intrinsics.h b/src/ir/intrinsics.h index ac679a4617d..51ac3a6436b 100644 --- a/src/ir/intrinsics.h +++ b/src/ir/intrinsics.h @@ -146,8 +146,8 @@ class Intrinsics { if (!ret.inline_) { ret.inline_ = funcAnnotations.inline_; } - if (!ret.deadIfUnused) { - ret.deadIfUnused = funcAnnotations.deadIfUnused; + if (!ret.removableIfUnused) { + ret.removableIfUnused = funcAnnotations.removableIfUnused; } } diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 481584f7770..c750533b11a 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -1315,8 +1315,8 @@ struct AnnotationParserCtx { branchHint = &a; } else if (a.kind == Annotations::InlineHint) { inlineHint = &a; - } else if (a.kind == Annotations::DeadIfUnusedHint) { - ret.deadIfUnused = true; + } else if (a.kind == Annotations::removableIfUnusedHint) { + ret.removableIfUnused = true; } } diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index f34f96d85f3..4de3991f3a2 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -2789,9 +2789,9 @@ void PrintSExpression::printCodeAnnotations(Expression* curr) { restoreNormalColor(o); doIndent(o, indent); } - if (annotation.deadIfUnused) { + if (annotation.removableIfUnused) { Colors::grey(o); - o << "(@" << Annotations::DeadIfUnusedHint << ")\n"; + o << "(@" << Annotations::removableIfUnusedHint << ")\n"; restoreNormalColor(o); doIndent(o, indent); } diff --git a/src/passes/Vacuum.cpp b/src/passes/Vacuum.cpp index f7ced825e08..cd79cb95301 100644 --- a/src/passes/Vacuum.cpp +++ b/src/passes/Vacuum.cpp @@ -141,7 +141,7 @@ struct Vacuum : public WalkerPass> { // If |curr| is marked so, then it is dead without even checking effects. if (Intrinsics(*getModule()) .getCallAnnotations(call, getFunction()) - .deadIfUnused) { + .removableIfUnused) { return false; } } diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 5eecb3f109d..14ea90d6d8d 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1441,7 +1441,7 @@ class WasmBinaryWriter { std::optional getBranchHintsBuffer(); std::optional getInlineHintsBuffer(); - std::optional getDeadIfUnusedHintsBuffer(); + std::optional getremovableIfUnusedHintsBuffer(); // helpers void writeInlineString(std::string_view name); @@ -1734,7 +1734,7 @@ class WasmBinaryReader { void readBranchHints(size_t payloadLen); void readInlineHints(size_t payloadLen); - void readDeadIfUnusedHints(size_t payloadLen); + void readremovableIfUnusedHints(size_t payloadLen); std::tuple readMemoryAccess(bool isAtomic, bool isRMW); diff --git a/src/wasm.h b/src/wasm.h index 18cda8042d2..f21c0827efc 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2248,11 +2248,11 @@ struct CodeAnnotation { // Toolchain hint: If this expression's result is unused, then the entire // thing can be considered dead and removable. // TODO: link to spec somewhere - bool deadIfUnused = false; + bool removableIfUnused = false; bool operator==(const CodeAnnotation& other) const { return branchLikely == other.branchLikely && inline_ == other.inline_ && - deadIfUnused == other.deadIfUnused; + removableIfUnused == other.removableIfUnused; } }; diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 5450575a932..27af241c9f9 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1627,7 +1627,7 @@ std::optional WasmBinaryWriter::writeCodeAnnotations() { append(getBranchHintsBuffer()); append(getInlineHintsBuffer()); - append(getDeadIfUnusedHintsBuffer()); + append(getRemovableIfUnusedHintsBuffer()); return ret; } @@ -1771,10 +1771,10 @@ std::optional WasmBinaryWriter::getInlineHintsBuffer() { } std::optional -WasmBinaryWriter::getDeadIfUnusedHintsBuffer() { +WasmBinaryWriter::getRemovableIfUnusedHintsBuffer() { return writeExpressionHints( - Annotations::DeadIfUnusedHint, - [](const CodeAnnotation& annotation) { return annotation.deadIfUnused; }, + Annotations::removableIfUnusedHint, + [](const CodeAnnotation& annotation) { return annotation.removableIfUnused; }, [](const CodeAnnotation& annotation, BufferWithRandomAccess& buffer) { // Hint size, always empty. buffer << U32LEB(0); @@ -2053,7 +2053,7 @@ void WasmBinaryReader::preScan() { if (sectionName == Annotations::BranchHint || sectionName == Annotations::InlineHint || - sectionName == Annotations::DeadIfUnusedHint) { + sectionName == Annotations::removableIfUnusedHint) { // Code annotations require code locations. // TODO: We could note which functions require code locations, as an // optimization. @@ -2210,9 +2210,9 @@ void WasmBinaryReader::readCustomSection(size_t payloadLen) { } else if (sectionName == Annotations::InlineHint) { deferredAnnotationSections.push_back(AnnotationSectionInfo{ pos, [this, payloadLen]() { this->readInlineHints(payloadLen); }}); - } else if (sectionName == Annotations::DeadIfUnusedHint) { + } else if (sectionName == Annotations::removableIfUnusedHint) { deferredAnnotationSections.push_back(AnnotationSectionInfo{ - pos, [this, payloadLen]() { this->readDeadIfUnusedHints(payloadLen); }}); + pos, [this, payloadLen]() { this->readremovableIfUnusedHints(payloadLen); }}); } else { // an unfamiliar custom section if (sectionName.equals(BinaryConsts::CustomSections::Linking)) { @@ -5523,15 +5523,15 @@ void WasmBinaryReader::readInlineHints(size_t payloadLen) { }); } -void WasmBinaryReader::readDeadIfUnusedHints(size_t payloadLen) { +void WasmBinaryReader::readremovableIfUnusedHints(size_t payloadLen) { readExpressionHints( - Annotations::DeadIfUnusedHint, payloadLen, [&](CodeAnnotation& annotation) { + Annotations::removableIfUnusedHint, payloadLen, [&](CodeAnnotation& annotation) { auto size = getU32LEB(); if (size != 0) { - throwError("bad DeadIfUnusedHint size"); + throwError("bad removableIfUnusedHint size"); } - annotation.deadIfUnused = true; + annotation.removableIfUnused = true; }); } diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index a04494dddee..7c59ff7b2e9 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -2662,10 +2662,10 @@ void IRBuilder::applyAnnotations(Expression* expr, func->codeAnnotations[expr].inline_ = annotation.inline_; } - if (annotation.deadIfUnused) { + if (annotation.removableIfUnused) { // Only possible inside functions. assert(func); - func->codeAnnotations[expr].deadIfUnused = true; + func->codeAnnotations[expr].removableIfUnused = true; } } diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index b71cadfcd6a..4f43804f2ef 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -69,7 +69,7 @@ namespace Annotations { const Name BranchHint = "metadata.code.branch_hint"; const Name InlineHint = "metadata.code.inline"; -const Name DeadIfUnusedHint = "binaryen.dead.if.unused"; +const Name removableIfUnusedHint = "binaryen.removable.if.unused"; } // namespace Annotations diff --git a/test/lit/dead-if-unused-func.wast b/test/lit/dead-if-unused-func.wast index 9ea0cfeb262..1500327345a 100644 --- a/test/lit/dead-if-unused-func.wast +++ b/test/lit/dead-if-unused-func.wast @@ -2,7 +2,7 @@ ;; RUN: wasm-opt -all --roundtrip %s -S -o - | filecheck %s (module - (@binaryen.dead.if.unused) + (@binaryen.removable.if.unused) (func $func-annotation ;; The annotation here is on the function. (drop @@ -13,7 +13,7 @@ ;; CHECK: (module ;; CHECK-NEXT: (type $0 (func)) -;; CHECK-NEXT: (@binaryen.dead.if.unused) +;; CHECK-NEXT: (@binaryen.removable.if.unused) ;; CHECK-NEXT: (func $func-annotation ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 0) diff --git a/test/lit/dead-if-unused.wast b/test/lit/dead-if-unused.wast index ad0fb68fb67..9aa3398ae85 100644 --- a/test/lit/dead-if-unused.wast +++ b/test/lit/dead-if-unused.wast @@ -9,7 +9,7 @@ ;; CHECK: (func $func (type $0) ;; CHECK-NEXT: (call $func) - ;; CHECK-NEXT: (@binaryen.dead.if.unused) + ;; CHECK-NEXT: (@binaryen.removable.if.unused) ;; CHECK-NEXT: (call $func) ;; CHECK-NEXT: (call $func) ;; CHECK-NEXT: ) @@ -17,14 +17,14 @@ ;; RTRIP: (func $func (type $0) ;; RTRIP-NEXT: (call $func) - ;; RTRIP-NEXT: (@binaryen.dead.if.unused) + ;; RTRIP-NEXT: (@binaryen.removable.if.unused) ;; RTRIP-NEXT: (call $func) ;; RTRIP-NEXT: (call $func) ;; RTRIP-NEXT: ) (func $func ;; Three calls, one annotated in the middle. (call $func) - (@binaryen.dead.if.unused) + (@binaryen.removable.if.unused) (call $func) (call $func) ) diff --git a/test/lit/passes/vacuum-dead-if-unused-func.wast b/test/lit/passes/vacuum-dead-if-unused-func.wast index b5f64e08aae..586b346e6d1 100644 --- a/test/lit/passes/vacuum-dead-if-unused-func.wast +++ b/test/lit/passes/vacuum-dead-if-unused-func.wast @@ -1,8 +1,8 @@ ;; RUN: wasm-opt -all --vacuum %s -S -o - | filecheck %s -;; Test the function-level annotation of dead.if.unused. +;; Test the function-level annotation of removable.if.unused. (module - (@binaryen.dead.if.unused) + (@binaryen.removable.if.unused) (func $calls-marked (param $x i32) (result i32) ;; The function is marked as dead if unused, and this is dropped, so optimize. (drop @@ -54,7 +54,7 @@ ;; CHECK: (module ;; CHECK-NEXT: (type $0 (func (param i32) (result i32))) -;; CHECK-NEXT: (@binaryen.dead.if.unused) +;; CHECK-NEXT: (@binaryen.removable.if.unused) ;; CHECK-NEXT: (func $calls-marked (type $0) (param $x i32) (result i32) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (call $calls-marked diff --git a/test/lit/passes/vacuum-dead-if-unused.wast b/test/lit/passes/vacuum-dead-if-unused.wast index 8cfdb52ac87..3d0f147ad69 100644 --- a/test/lit/passes/vacuum-dead-if-unused.wast +++ b/test/lit/passes/vacuum-dead-if-unused.wast @@ -16,7 +16,7 @@ (func $calls-dropped (param $x i32) (result i32) ;; This call is dropped, and marked with the hint, so we can remove it. (drop - (@binaryen.dead.if.unused) + (@binaryen.removable.if.unused) (call $calls-dropped (i32.const 0) ) @@ -24,7 +24,7 @@ ;; As above, but a parameter has effects. We must keep that around, without ;; the call. (drop - (@binaryen.dead.if.unused) + (@binaryen.removable.if.unused) (call $calls-dropped (local.tee $x (i32.const 1) @@ -42,13 +42,13 @@ ;; CHECK: (func $calls-used (type $0) (param $x i32) (result i32) ;; CHECK-NEXT: (local.set $x - ;; CHECK-NEXT: (@binaryen.dead.if.unused) + ;; CHECK-NEXT: (@binaryen.removable.if.unused) ;; CHECK-NEXT: (call $calls-used ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $x - ;; CHECK-NEXT: (@binaryen.dead.if.unused) + ;; CHECK-NEXT: (@binaryen.removable.if.unused) ;; CHECK-NEXT: (call $calls-used ;; CHECK-NEXT: (local.tee $x ;; CHECK-NEXT: (i32.const 1) @@ -65,13 +65,13 @@ (func $calls-used (param $x i32) (result i32) ;; As above, but the calls are not dropped, so we keep them. (local.set $x - (@binaryen.dead.if.unused) + (@binaryen.removable.if.unused) (call $calls-used (i32.const 0) ) ) (local.set $x - (@binaryen.dead.if.unused) + (@binaryen.removable.if.unused) (call $calls-used (local.tee $x (i32.const 1) From 926f3a78b78edd6d284cf9b752b90ea5868b76b5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 9 Feb 2026 13:14:07 -0800 Subject: [PATCH 49/58] format --- src/wasm/wasm-binary.cpp | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 27af241c9f9..2e54bdc6b28 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1774,7 +1774,9 @@ std::optional WasmBinaryWriter::getRemovableIfUnusedHintsBuffer() { return writeExpressionHints( Annotations::removableIfUnusedHint, - [](const CodeAnnotation& annotation) { return annotation.removableIfUnused; }, + [](const CodeAnnotation& annotation) { + return annotation.removableIfUnused; + }, [](const CodeAnnotation& annotation, BufferWithRandomAccess& buffer) { // Hint size, always empty. buffer << U32LEB(0); @@ -2211,8 +2213,10 @@ void WasmBinaryReader::readCustomSection(size_t payloadLen) { deferredAnnotationSections.push_back(AnnotationSectionInfo{ pos, [this, payloadLen]() { this->readInlineHints(payloadLen); }}); } else if (sectionName == Annotations::removableIfUnusedHint) { - deferredAnnotationSections.push_back(AnnotationSectionInfo{ - pos, [this, payloadLen]() { this->readremovableIfUnusedHints(payloadLen); }}); + deferredAnnotationSections.push_back( + AnnotationSectionInfo{pos, [this, payloadLen]() { + this->readremovableIfUnusedHints(payloadLen); + }}); } else { // an unfamiliar custom section if (sectionName.equals(BinaryConsts::CustomSections::Linking)) { @@ -5524,15 +5528,16 @@ void WasmBinaryReader::readInlineHints(size_t payloadLen) { } void WasmBinaryReader::readremovableIfUnusedHints(size_t payloadLen) { - readExpressionHints( - Annotations::removableIfUnusedHint, payloadLen, [&](CodeAnnotation& annotation) { - auto size = getU32LEB(); - if (size != 0) { - throwError("bad removableIfUnusedHint size"); - } - - annotation.removableIfUnused = true; - }); + readExpressionHints(Annotations::removableIfUnusedHint, + payloadLen, + [&](CodeAnnotation& annotation) { + auto size = getU32LEB(); + if (size != 0) { + throwError("bad removableIfUnusedHint size"); + } + + annotation.removableIfUnused = true; + }); } std::tuple From 0746c4bb2c2b5234a92e643ff30ef4f18f4acef7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 9 Feb 2026 13:21:20 -0800 Subject: [PATCH 50/58] fix --- src/passes/Print.cpp | 2 +- src/wasm-annotations.h | 2 +- src/wasm/wasm-binary.cpp | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 4de3991f3a2..f00aff09d73 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -2791,7 +2791,7 @@ void PrintSExpression::printCodeAnnotations(Expression* curr) { } if (annotation.removableIfUnused) { Colors::grey(o); - o << "(@" << Annotations::removableIfUnusedHint << ")\n"; + o << "(@" << Annotations::RemovableIfUnusedHint << ")\n"; restoreNormalColor(o); doIndent(o, indent); } diff --git a/src/wasm-annotations.h b/src/wasm-annotations.h index afabb5c1e12..fec7f067795 100644 --- a/src/wasm-annotations.h +++ b/src/wasm-annotations.h @@ -27,7 +27,7 @@ namespace wasm::Annotations { extern const Name BranchHint; extern const Name InlineHint; -extern const Name DeadIfUnusedHint; +extern const Name RemovableIfUnusedHint; } // namespace wasm::Annotations diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 2e54bdc6b28..85768b55fbc 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1773,7 +1773,7 @@ std::optional WasmBinaryWriter::getInlineHintsBuffer() { std::optional WasmBinaryWriter::getRemovableIfUnusedHintsBuffer() { return writeExpressionHints( - Annotations::removableIfUnusedHint, + Annotations::RemovableIfUnusedHint, [](const CodeAnnotation& annotation) { return annotation.removableIfUnused; }, @@ -2055,7 +2055,7 @@ void WasmBinaryReader::preScan() { if (sectionName == Annotations::BranchHint || sectionName == Annotations::InlineHint || - sectionName == Annotations::removableIfUnusedHint) { + sectionName == Annotations::RemovableIfUnusedHint) { // Code annotations require code locations. // TODO: We could note which functions require code locations, as an // optimization. @@ -2212,7 +2212,7 @@ void WasmBinaryReader::readCustomSection(size_t payloadLen) { } else if (sectionName == Annotations::InlineHint) { deferredAnnotationSections.push_back(AnnotationSectionInfo{ pos, [this, payloadLen]() { this->readInlineHints(payloadLen); }}); - } else if (sectionName == Annotations::removableIfUnusedHint) { + } else if (sectionName == Annotations::RemovableIfUnusedHint) { deferredAnnotationSections.push_back( AnnotationSectionInfo{pos, [this, payloadLen]() { this->readremovableIfUnusedHints(payloadLen); @@ -5528,7 +5528,7 @@ void WasmBinaryReader::readInlineHints(size_t payloadLen) { } void WasmBinaryReader::readremovableIfUnusedHints(size_t payloadLen) { - readExpressionHints(Annotations::removableIfUnusedHint, + readExpressionHints(Annotations::RemovableIfUnusedHint, payloadLen, [&](CodeAnnotation& annotation) { auto size = getU32LEB(); From 6fcfc798f8ef3c9f921ee3724da5c0aa5adb01b7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 9 Feb 2026 13:21:55 -0800 Subject: [PATCH 51/58] fix --- src/parser/contexts.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parser/contexts.h b/src/parser/contexts.h index c750533b11a..3978afff45d 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -1315,7 +1315,7 @@ struct AnnotationParserCtx { branchHint = &a; } else if (a.kind == Annotations::InlineHint) { inlineHint = &a; - } else if (a.kind == Annotations::removableIfUnusedHint) { + } else if (a.kind == Annotations::RemovableIfUnusedHint) { ret.removableIfUnused = true; } } From c2d17c38114207266e907c2b1f95697ea551a3c6 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 9 Feb 2026 13:23:15 -0800 Subject: [PATCH 52/58] fix --- src/wasm-binary.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 14ea90d6d8d..3bd3199b67a 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1441,7 +1441,7 @@ class WasmBinaryWriter { std::optional getBranchHintsBuffer(); std::optional getInlineHintsBuffer(); - std::optional getremovableIfUnusedHintsBuffer(); + std::optional getRemovableIfUnusedHintsBuffer(); // helpers void writeInlineString(std::string_view name); From 871b46ebe3cbc31e849b1cd5a0f596db98257a5e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 9 Feb 2026 13:24:57 -0800 Subject: [PATCH 53/58] fix --- src/wasm/wasm.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 4f43804f2ef..4e901c152de 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -69,7 +69,7 @@ namespace Annotations { const Name BranchHint = "metadata.code.branch_hint"; const Name InlineHint = "metadata.code.inline"; -const Name removableIfUnusedHint = "binaryen.removable.if.unused"; +const Name RemovableIfUnusedHint = "binaryen.removable.if.unused"; } // namespace Annotations From c06f0e9ed1b50fac35b80317307ce6200caae38b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 9 Feb 2026 13:29:57 -0800 Subject: [PATCH 54/58] rename tests --- scripts/test/fuzzing.py | 8 ++++---- ...sed-func.wast => vacuum-removable-if-unused-func.wast} | 0 ...ead-if-unused.wast => vacuum-removable-if-unused.wast} | 0 ...-if-unused-func.wast => removable-if-unused-func.wast} | 0 .../lit/{dead-if-unused.wast => removable-if-unused.wast} | 0 5 files changed, 4 insertions(+), 4 deletions(-) rename test/lit/passes/{vacuum-dead-if-unused-func.wast => vacuum-removable-if-unused-func.wast} (100%) rename test/lit/passes/{vacuum-dead-if-unused.wast => vacuum-removable-if-unused.wast} (100%) rename test/lit/{dead-if-unused-func.wast => removable-if-unused-func.wast} (100%) rename test/lit/{dead-if-unused.wast => removable-if-unused.wast} (100%) diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 6f2769b3d5d..b273e5bfaf7 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -111,10 +111,10 @@ 'fuzz_shell_second.wast', # We cannot fuzz semantics-altering intrinsics, as when we optimize the # behavior changes. - 'dead-if-unused.wast', - 'dead-if-unused-func.wast', - 'vacuum-dead-if-unused.wast', - 'vacuum-dead-if-unused-func.wast', + 'removable-if-unused.wast', + 'removable-if-unused-func.wast', + 'vacuum-removable-if-unused.wast', + 'vacuum-removable-if-unused-func.wast', ] diff --git a/test/lit/passes/vacuum-dead-if-unused-func.wast b/test/lit/passes/vacuum-removable-if-unused-func.wast similarity index 100% rename from test/lit/passes/vacuum-dead-if-unused-func.wast rename to test/lit/passes/vacuum-removable-if-unused-func.wast diff --git a/test/lit/passes/vacuum-dead-if-unused.wast b/test/lit/passes/vacuum-removable-if-unused.wast similarity index 100% rename from test/lit/passes/vacuum-dead-if-unused.wast rename to test/lit/passes/vacuum-removable-if-unused.wast diff --git a/test/lit/dead-if-unused-func.wast b/test/lit/removable-if-unused-func.wast similarity index 100% rename from test/lit/dead-if-unused-func.wast rename to test/lit/removable-if-unused-func.wast diff --git a/test/lit/dead-if-unused.wast b/test/lit/removable-if-unused.wast similarity index 100% rename from test/lit/dead-if-unused.wast rename to test/lit/removable-if-unused.wast From a4b3b6f9095dcaf2989c6c407a8bd87ac075553f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 10 Feb 2026 14:28:29 -0800 Subject: [PATCH 55/58] Update src/passes/Vacuum.cpp Co-authored-by: Thomas Lively --- src/passes/Vacuum.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/passes/Vacuum.cpp b/src/passes/Vacuum.cpp index cd79cb95301..120afdf4853 100644 --- a/src/passes/Vacuum.cpp +++ b/src/passes/Vacuum.cpp @@ -137,8 +137,7 @@ struct Vacuum : public WalkerPass> { // around if so.) bool mustKeepUnusedParent(Expression* curr) { if (auto* call = curr->dynCast()) { - // Return true if a target in a function is annotated as dead if unused. - // If |curr| is marked so, then it is dead without even checking effects. + // If |curr| is marked as removable if unused, then it is removable without even checking effects. if (Intrinsics(*getModule()) .getCallAnnotations(call, getFunction()) .removableIfUnused) { From 7ff77950f9ccfc01e784c51228677c0dfd694346 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 10 Feb 2026 14:28:44 -0800 Subject: [PATCH 56/58] Update test/lit/passes/vacuum-removable-if-unused-func.wast Co-authored-by: Thomas Lively --- test/lit/passes/vacuum-removable-if-unused-func.wast | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/lit/passes/vacuum-removable-if-unused-func.wast b/test/lit/passes/vacuum-removable-if-unused-func.wast index 586b346e6d1..8bc34e2ea71 100644 --- a/test/lit/passes/vacuum-removable-if-unused-func.wast +++ b/test/lit/passes/vacuum-removable-if-unused-func.wast @@ -4,7 +4,7 @@ (module (@binaryen.removable.if.unused) (func $calls-marked (param $x i32) (result i32) - ;; The function is marked as dead if unused, and this is dropped, so optimize. + ;; The function is marked as removable if unused, and this is dropped, so optimize. (drop (call $calls-marked (i32.const 0) From e98bf88fe9ae179bc9e674ebba06edc87c67ed25 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 10 Feb 2026 14:29:16 -0800 Subject: [PATCH 57/58] fmrt --- src/passes/Vacuum.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/passes/Vacuum.cpp b/src/passes/Vacuum.cpp index 120afdf4853..0bcb3a387e6 100644 --- a/src/passes/Vacuum.cpp +++ b/src/passes/Vacuum.cpp @@ -137,7 +137,8 @@ struct Vacuum : public WalkerPass> { // around if so.) bool mustKeepUnusedParent(Expression* curr) { if (auto* call = curr->dynCast()) { - // If |curr| is marked as removable if unused, then it is removable without even checking effects. + // If |curr| is marked as removable if unused, then it is removable + // without even checking effects. if (Intrinsics(*getModule()) .getCallAnnotations(call, getFunction()) .removableIfUnused) { From 4c6fb3c9af80094c935ab449aff114a1922be275 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 10 Feb 2026 15:55:24 -0800 Subject: [PATCH 58/58] add link to wiki --- src/wasm.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/wasm.h b/src/wasm.h index f21c0827efc..aa7ed4d6110 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2246,8 +2246,9 @@ struct CodeAnnotation { std::optional inline_; // Toolchain hint: If this expression's result is unused, then the entire - // thing can be considered dead and removable. - // TODO: link to spec somewhere + // thing can be considered dead and removable. See + // + // https://github.com/WebAssembly/binaryen/wiki/Optimizer-Cookbook#intrinsics bool removableIfUnused = false; bool operator==(const CodeAnnotation& other) const {