Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
5daee4c
go
kripken Jan 21, 2026
7ce3151
work
kripken Jan 21, 2026
477e77b
work
kripken Jan 21, 2026
83583cb
work
kripken Jan 21, 2026
073b39e
fmt
kripken Jan 21, 2026
f129e51
test
kripken Jan 21, 2026
075fd15
work
kripken Jan 21, 2026
d540a96
work
kripken Jan 21, 2026
4d8b7a5
builds
kripken Jan 21, 2026
e38e29c
progress
kripken Jan 21, 2026
c78dc41
finish
kripken Jan 21, 2026
8ad0110
works
kripken Jan 21, 2026
d876266
UNDO effects.h
kripken Jan 21, 2026
31f6e5c
yolo
kripken Jan 21, 2026
879dc79
works
kripken Jan 21, 2026
eadd3dc
rename
kripken Jan 21, 2026
a49fe26
rename
kripken Jan 21, 2026
c2c1d9d
Merge remote-tracking branch 'origin/main' into callsIfMoved
kripken Jan 27, 2026
dce83d8
refactor
kripken Jan 27, 2026
93234e2
fix
kripken Jan 27, 2026
eeba297
fmrt
kripken Jan 27, 2026
902a233
work
kripken Jan 28, 2026
2e287a2
work
kripken Jan 28, 2026
45650e1
work
kripken Jan 28, 2026
017f8e5
work
kripken Jan 28, 2026
4097eec
work
kripken Jan 28, 2026
694e4f9
work
kripken Jan 28, 2026
a815fb7
simpl
kripken Jan 28, 2026
8214606
Merge remote-tracking branch 'origin/main' into callsIfMoved
kripken Jan 29, 2026
6a88e93
fmrt
kripken Jan 29, 2026
c4ec997
UNDO
kripken Jan 30, 2026
4080447
renamings
kripken Jan 30, 2026
817dc64
finish
kripken Jan 30, 2026
130e5a1
finish
kripken Jan 30, 2026
d1eec83
finish
kripken Jan 30, 2026
a266a05
work
kripken Jan 30, 2026
e474857
work
kripken Jan 30, 2026
8c24e14
Merge remote-tracking branch 'origin/main' into callsIfMoved
kripken Feb 3, 2026
8e89cb4
Merge remote-tracking branch 'origin/main' into callsIfMoved
kripken Feb 4, 2026
0b4d4f9
hhow?
kripken Feb 4, 2026
7cac194
hhow?
kripken Feb 4, 2026
0658143
finish
kripken Feb 5, 2026
a2aea65
fix
kripken Feb 5, 2026
a8857b5
fix
kripken Feb 5, 2026
9b33ec1
fuzz
kripken Feb 5, 2026
ed1466f
test roundtripping of function-level annotation
kripken Feb 5, 2026
dafbdbe
simpler
kripken Feb 5, 2026
708f8b2
just use bool
kripken Feb 5, 2026
f3c73c6
fix
kripken Feb 5, 2026
1ad327c
unfuzz
kripken Feb 5, 2026
6544c89
default
kripken Feb 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions scripts/test/fuzzing.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@
# 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',
'dead-if-unused-func.wast',
'vacuum-dead-if-unused.wast',
'vacuum-dead-if-unused-func.wast',
]


Expand Down
42 changes: 42 additions & 0 deletions src/ir/intrinsics.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,48 @@ class Intrinsics {
std::vector<Name> getConfigureAllFunctions(Call* call);
// As above, but looks through the module to find the configureAll.
std::vector<Name> 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
Expand Down
2 changes: 2 additions & 0 deletions src/parser/contexts.h
Original file line number Diff line number Diff line change
Expand Up @@ -1315,6 +1315,8 @@ struct AnnotationParserCtx {
branchHint = &a;
} else if (a.kind == Annotations::InlineHint) {
inlineHint = &a;
} else if (a.kind == Annotations::DeadIfUnusedHint) {
ret.deadIfUnused = true;
}
}

Expand Down
6 changes: 6 additions & 0 deletions src/passes/Print.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}

Expand Down
27 changes: 23 additions & 4 deletions src/passes/Vacuum.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <ir/branch-hints.h>
#include <ir/drop.h>
#include <ir/effects.h>
#include <ir/intrinsics.h>
#include <ir/iteration.h>
#include <ir/literal-utils.h>
#include <ir/utils.h>
Expand Down Expand Up @@ -91,10 +92,8 @@ struct Vacuum : public WalkerPass<ExpressionStackWalker<Vacuum>> {
curr->is<Loop>() || curr->is<Try>() || curr->is<TryTable>()) {
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
Expand Down Expand Up @@ -130,6 +129,26 @@ struct Vacuum : public WalkerPass<ExpressionStackWalker<Vacuum>> {
}
}

// 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. (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<Call>()) {
// 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 (Intrinsics(*getModule())
.getCallAnnotations(call, getFunction())
.deadIfUnused) {
return false;
}
}
ShallowEffectAnalyzer self(getPassOptions(), *getModule(), curr);
return self.hasUnremovableSideEffects();
}

void visitBlock(Block* curr) {
auto& list = curr->list;

Expand Down
1 change: 1 addition & 0 deletions src/wasm-annotations.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ namespace wasm::Annotations {

extern const Name BranchHint;
extern const Name InlineHint;
extern const Name DeadIfUnusedHint;

} // namespace wasm::Annotations

Expand Down
2 changes: 2 additions & 0 deletions src/wasm-binary.h
Original file line number Diff line number Diff line change
Expand Up @@ -1441,6 +1441,7 @@ class WasmBinaryWriter {

std::optional<BufferWithRandomAccess> getBranchHintsBuffer();
std::optional<BufferWithRandomAccess> getInlineHintsBuffer();
std::optional<BufferWithRandomAccess> getDeadIfUnusedHintsBuffer();

// helpers
void writeInlineString(std::string_view name);
Expand Down Expand Up @@ -1733,6 +1734,7 @@ class WasmBinaryReader {

void readBranchHints(size_t payloadLen);
void readInlineHints(size_t payloadLen);
void readDeadIfUnusedHints(size_t payloadLen);

std::tuple<Address, Address, Index, MemoryOrder>
readMemoryAccess(bool isAtomic, bool isRMW);
Expand Down
12 changes: 10 additions & 2 deletions src/wasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -2233,7 +2233,9 @@ struct BinaryLocations {
// Forward declaration for FuncEffectsMap.
class EffectAnalyzer;

// Code annotations for VMs.
// Annotation for a particular piece of code. This includes std::optionals for
// 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<bool> branchLikely;
Expand All @@ -2243,8 +2245,14 @@ struct CodeAnnotation {
static const uint8_t AlwaysInline = 127;
std::optional<uint8_t> 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
bool deadIfUnused = false;

bool operator==(const CodeAnnotation& other) const {
return branchLikely == other.branchLikely && inline_ == other.inline_;
return branchLikely == other.branchLikely && inline_ == other.inline_ &&
deadIfUnused == other.deadIfUnused;
}
};

Expand Down
30 changes: 29 additions & 1 deletion src/wasm/wasm-binary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1627,6 +1627,7 @@ std::optional<BufferWithRandomAccess> WasmBinaryWriter::writeCodeAnnotations() {

append(getBranchHintsBuffer());
append(getInlineHintsBuffer());
append(getDeadIfUnusedHintsBuffer());
return ret;
}

Expand Down Expand Up @@ -1769,6 +1770,17 @@ std::optional<BufferWithRandomAccess> WasmBinaryWriter::getInlineHintsBuffer() {
});
}

std::optional<BufferWithRandomAccess>
WasmBinaryWriter::getDeadIfUnusedHintsBuffer() {
return writeExpressionHints(
Annotations::DeadIfUnusedHint,
[](const CodeAnnotation& annotation) { return annotation.deadIfUnused; },
[](const CodeAnnotation& annotation, BufferWithRandomAccess& buffer) {
// Hint size, always empty.
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]);
Expand Down Expand Up @@ -2040,7 +2052,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.
Expand Down Expand Up @@ -2197,6 +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) {
deferredAnnotationSections.push_back(AnnotationSectionInfo{
pos, [this, payloadLen]() { this->readDeadIfUnusedHints(payloadLen); }});
} else {
// an unfamiliar custom section
if (sectionName.equals(BinaryConsts::CustomSections::Linking)) {
Expand Down Expand Up @@ -5507,6 +5523,18 @@ 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 = true;
});
}

std::tuple<Address, Address, Index, MemoryOrder>
WasmBinaryReader::readMemoryAccess(bool isAtomic, bool isRMW) {
auto rawAlignment = getU32LEB();
Expand Down
6 changes: 6 additions & 0 deletions src/wasm/wasm-ir-builder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2661,6 +2661,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 = true;
}
}

} // namespace wasm
1 change: 1 addition & 0 deletions src/wasm/wasm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ namespace Annotations {

const Name BranchHint = "metadata.code.branch_hint";
const Name InlineHint = "metadata.code.inline";
const Name DeadIfUnusedHint = "binaryen.dead.if.unused";

} // namespace Annotations

Expand Down
23 changes: 23 additions & 0 deletions test/lit/dead-if-unused-func.wast
Original file line number Diff line number Diff line change
@@ -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: )

31 changes: 31 additions & 0 deletions test/lit/dead-if-unused.wast
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we check round-tripping of the function-level annotation as well?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.

Original file line number Diff line number Diff line change
@@ -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.dead.if.unused)
;; 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.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.dead.if.unused)
(call $func)
(call $func)
)
)
Loading
Loading