Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
24 changes: 12 additions & 12 deletions src/ir/intrinsics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,29 +101,29 @@ std::vector<Name> Intrinsics::getConfigureAllFunctions(Call* call) {
return ret;
}

std::vector<Name> Intrinsics::getConfigureAllFunctions() {
// ConfigureAll in a start function makes its functions callable.
std::vector<Name> Intrinsics::getJSCalledFunctions() {
std::vector<Name> ret;
for (auto& func : module.functions) {
if (getAnnotations(func.get()).jsCalled) {
ret.push_back(func->name);
}
}

// ConfigureAlls in a start function make their functions callable.
if (module.start) {
auto* start = module.getFunction(module.start);
if (!start->imported()) {
FindAll<Call> calls(start->body);
// Look for the (single) configureAll.
Call* configureAll = nullptr;
for (auto* call : calls.list) {
if (isConfigureAll(call)) {
if (configureAll) {
Fatal() << "Multiple configureAlls";
} else {
configureAll = call;
for (auto name : getConfigureAllFunctions(call)) {
ret.push_back(name);
}
}
}
if (configureAll) {
return getConfigureAllFunctions(configureAll);
}
}
}
return {};
return ret;
}

} // namespace wasm
10 changes: 8 additions & 2 deletions src/ir/intrinsics.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,11 @@ class Intrinsics {
//
// where the segment $seg is of size N.
std::vector<Name> getConfigureAllFunctions(Call* call);
// As above, but looks through the module to find the configureAll.
std::vector<Name> getConfigureAllFunctions();

// Returns the names of all functions that are JS-called. That includes ones
// in configureAll (which we look through the module for), and also those
Copy link
Member

Choose a reason for hiding this comment

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

Are we still planning to eventually phase out this special handling for configureAll? I think we should to make the usage more consistent across different configureAll usage patterns.

// annotated with @binaryen.js.called.
std::vector<Name> getJSCalledFunctions();

// Get the code annotations for an expression in a function.
CodeAnnotation getAnnotations(Expression* curr, Function* func) {
Expand Down Expand Up @@ -149,6 +152,9 @@ class Intrinsics {
if (!ret.removableIfUnused) {
ret.removableIfUnused = funcAnnotations.removableIfUnused;
}
if (!ret.jsCalled) {
ret.jsCalled = funcAnnotations.jsCalled;
}
}

return ret;
Expand Down
4 changes: 2 additions & 2 deletions src/ir/possible-contents.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2454,8 +2454,8 @@ Flower::Flower(Module& wasm, const PassOptions& options)
}
}

// configureAll functions are called from outside the module, as if exported.
for (auto func : Intrinsics(wasm).getConfigureAllFunctions()) {
// JS-called functions are called from outside the module, as if exported.
for (auto func : Intrinsics(wasm).getJSCalledFunctions()) {
calledFromOutside.insert(func);
}

Expand Down
2 changes: 2 additions & 0 deletions src/parser/contexts.h
Original file line number Diff line number Diff line change
Expand Up @@ -1317,6 +1317,8 @@ struct AnnotationParserCtx {
inlineHint = &a;
} else if (a.kind == Annotations::RemovableIfUnusedHint) {
ret.removableIfUnused = true;
} else if (a.kind == Annotations::JSCalledHint) {
ret.jsCalled = true;
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/passes/GlobalTypeOptimization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ struct GlobalTypeOptimization : public Pass {
if (!wasm.features.hasCustomDescriptors()) {
return;
}
for (auto func : Intrinsics(wasm).getConfigureAllFunctions()) {
for (auto func : Intrinsics(wasm).getJSCalledFunctions()) {
// Look at the result types being returned to JS and make sure we preserve
// any configured prototypes they might expose.
for (auto type : wasm.getFunction(func)->getResults()) {
Expand Down
6 changes: 6 additions & 0 deletions src/passes/Print.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2797,6 +2797,12 @@ void PrintSExpression::printCodeAnnotations(Expression* curr) {
restoreNormalColor(o);
doIndent(o, indent);
}
if (annotation.jsCalled) {
Colors::grey(o);
o << "(@" << Annotations::JSCalledHint << ")\n";
restoreNormalColor(o);
doIndent(o, indent);
}
}
}

Expand Down
11 changes: 10 additions & 1 deletion src/passes/RemoveUnusedModuleElements.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,16 @@ struct Noter : public PostWalker<Noter, UnifiedExpressionVisitor<Noter>> {
noteCallRef(curr->target->type.getHeapType());
}

void visitRefFunc(RefFunc* curr) { noteRefFunc(curr->func); }
void visitRefFunc(RefFunc* curr) {
// If the target is js-called then a reference is as strong as a use.
auto target = curr->func;
Intrinsics intrinsics(*getModule());
if (intrinsics.getAnnotations(getModule()->getFunction(target)).jsCalled) {
use({ModuleElementKind::Function, target});
} else {
noteRefFunc(target);
}
}

void visitStructGet(StructGet* curr) {
if (curr->ref->type == Type::unreachable || curr->ref->type.isNull()) {
Expand Down
5 changes: 2 additions & 3 deletions src/passes/SignaturePruning.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -176,9 +176,8 @@ struct SignaturePruning : public Pass {
allInfo[tag->type].optimizable = false;
}

// configureAll functions are signature-called, and must also not be
// modified.
for (auto func : Intrinsics(*module).getConfigureAllFunctions()) {
// Signature-called functions must also not be modified.
Copy link
Member

Choose a reason for hiding this comment

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

In principle we could still remove a suffix of the parameters, although I'm not sure whether that comes with a performance overhead. Maybe worth checking on that and adding a TODO if there would be no downside.

for (auto func : Intrinsics(*module).getJSCalledFunctions()) {
allInfo[module->getFunction(func)->type.getHeapType()].optimizable =
false;
}
Expand Down
5 changes: 2 additions & 3 deletions src/passes/SignatureRefining.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,8 @@ struct SignatureRefining : public Pass {
}
}

// configureAll functions are signature-called, which means their params
// must not be refined.
for (auto func : Intrinsics(*module).getConfigureAllFunctions()) {
// Signature-called functions must not have params refined.
Copy link
Member

Choose a reason for hiding this comment

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

Would it make sense to retire the "signature-called" terminology?

for (auto func : Intrinsics(*module).getJSCalledFunctions()) {
allInfo[module->getFunction(func)->type.getHeapType()].canModifyParams =
false;
}
Expand Down
1 change: 1 addition & 0 deletions src/passes/StripToolchainAnnotations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ struct StripToolchainAnnotations
// Remove the toolchain-specific annotations.
auto& annotation = iter->second;
annotation.removableIfUnused = false;
annotation.jsCalled = false;

// If nothing remains, remove the entire annotation.
if (annotation == CodeAnnotation()) {
Expand Down
2 changes: 1 addition & 1 deletion src/passes/Unsubtyping.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -612,7 +612,7 @@ struct Unsubtyping : Pass, Noter<Unsubtyping> {
return;
}
Type anyref(HeapType::any, Nullable);
for (auto func : Intrinsics(wasm).getConfigureAllFunctions()) {
for (auto func : Intrinsics(wasm).getJSCalledFunctions()) {
// Parameter types flow into Wasm and are implicitly cast from any.
for (auto type : wasm.getFunction(func)->getParams()) {
if (Type::isSubType(type, anyref)) {
Expand Down
1 change: 1 addition & 0 deletions src/wasm-annotations.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ namespace wasm::Annotations {
extern const Name BranchHint;
extern const Name InlineHint;
extern const Name RemovableIfUnusedHint;
extern const Name JSCalledHint;

} // namespace wasm::Annotations

Expand Down
4 changes: 3 additions & 1 deletion src/wasm-binary.h
Original file line number Diff line number Diff line change
Expand Up @@ -1442,6 +1442,7 @@ class WasmBinaryWriter {
std::optional<BufferWithRandomAccess> getBranchHintsBuffer();
std::optional<BufferWithRandomAccess> getInlineHintsBuffer();
std::optional<BufferWithRandomAccess> getRemovableIfUnusedHintsBuffer();
std::optional<BufferWithRandomAccess> getJSCalledHintsBuffer();

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

void readBranchHints(size_t payloadLen);
void readInlineHints(size_t payloadLen);
void readremovableIfUnusedHints(size_t payloadLen);
void readRemovableIfUnusedHints(size_t payloadLen);
void readJSCalledHints(size_t payloadLen);

std::tuple<Address, Address, Index, MemoryOrder>
readMemoryAccess(bool isAtomic, bool isRMW);
Expand Down
16 changes: 12 additions & 4 deletions src/wasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -2245,15 +2245,23 @@ 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. See
//
// Toolchain hints, see
// https://github.com/WebAssembly/binaryen/wiki/Optimizer-Cookbook#intrinsics

// If this expression's result is unused, then the entire thing can be
// considered dead and removable.
bool removableIfUnused = false;

// This should be assumed to be called from JS, even in closed world. Being
// called from JS means that the call happens in a non-typed way, with only
// the signature mattering ("signature-called"). In particular, rec group type
// identity does not matter for such functions.
bool jsCalled = false;

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

Expand Down
34 changes: 31 additions & 3 deletions src/wasm/wasm-binary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1628,6 +1628,7 @@ std::optional<BufferWithRandomAccess> WasmBinaryWriter::writeCodeAnnotations() {
append(getBranchHintsBuffer());
append(getInlineHintsBuffer());
append(getRemovableIfUnusedHintsBuffer());
append(getJSCalledHintsBuffer());
return ret;
}

Expand Down Expand Up @@ -1783,6 +1784,17 @@ WasmBinaryWriter::getRemovableIfUnusedHintsBuffer() {
});
}

std::optional<BufferWithRandomAccess>
WasmBinaryWriter::getJSCalledHintsBuffer() {
return writeExpressionHints(
Annotations::JSCalledHint,
[](const CodeAnnotation& annotation) { return annotation.jsCalled; },
[](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 @@ -2055,7 +2067,8 @@ void WasmBinaryReader::preScan() {

if (sectionName == Annotations::BranchHint ||
sectionName == Annotations::InlineHint ||
sectionName == Annotations::RemovableIfUnusedHint) {
sectionName == Annotations::RemovableIfUnusedHint ||
sectionName == Annotations::JSCalledHint) {
// Code annotations require code locations.
// TODO: We could note which functions require code locations, as an
// optimization.
Expand Down Expand Up @@ -2215,8 +2228,11 @@ void WasmBinaryReader::readCustomSection(size_t payloadLen) {
} else if (sectionName == Annotations::RemovableIfUnusedHint) {
deferredAnnotationSections.push_back(
AnnotationSectionInfo{pos, [this, payloadLen]() {
this->readremovableIfUnusedHints(payloadLen);
this->readRemovableIfUnusedHints(payloadLen);
}});
} else if (sectionName == Annotations::JSCalledHint) {
deferredAnnotationSections.push_back(AnnotationSectionInfo{
pos, [this, payloadLen]() { this->readJSCalledHints(payloadLen); }});
} else {
// an unfamiliar custom section
if (sectionName.equals(BinaryConsts::CustomSections::Linking)) {
Expand Down Expand Up @@ -5527,7 +5543,7 @@ void WasmBinaryReader::readInlineHints(size_t payloadLen) {
});
}

void WasmBinaryReader::readremovableIfUnusedHints(size_t payloadLen) {
void WasmBinaryReader::readRemovableIfUnusedHints(size_t payloadLen) {
readExpressionHints(Annotations::RemovableIfUnusedHint,
payloadLen,
[&](CodeAnnotation& annotation) {
Expand All @@ -5540,6 +5556,18 @@ void WasmBinaryReader::readremovableIfUnusedHints(size_t payloadLen) {
});
}

void WasmBinaryReader::readJSCalledHints(size_t payloadLen) {
readExpressionHints(
Annotations::JSCalledHint, payloadLen, [&](CodeAnnotation& annotation) {
auto size = getU32LEB();
if (size != 0) {
throwError("bad jsCalledHint size");
}

annotation.jsCalled = true;
});
}

std::tuple<Address, Address, Index, MemoryOrder>
WasmBinaryReader::readMemoryAccess(bool isAtomic, bool isRMW) {
auto rawAlignment = getU32LEB();
Expand Down
7 changes: 5 additions & 2 deletions src/wasm/wasm-ir-builder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2664,16 +2664,19 @@ void IRBuilder::applyAnnotations(Expression* expr,
}

if (annotation.inline_) {
// Only possible inside functions.
assert(func);
func->codeAnnotations[expr].inline_ = annotation.inline_;
}

if (annotation.removableIfUnused) {
// Only possible inside functions.
assert(func);
func->codeAnnotations[expr].removableIfUnused = true;
}

if (annotation.jsCalled) {
assert(func);
func->codeAnnotations[expr].jsCalled = 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 @@ -70,6 +70,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 JSCalledHint = "binaryen.js.called";

} // namespace Annotations

Expand Down
24 changes: 24 additions & 0 deletions test/lit/js-called.wast
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
;; RUN: wasm-opt -all %s -S -o - | filecheck %s
Copy link
Member

Choose a reason for hiding this comment

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

This can use the update script now.

;; RUN: wasm-opt -all --roundtrip %s -S -o - | filecheck %s

;; Test text and binary handling of @binaryen.js.called.

(module
(@binaryen.js.called)
(func $func-annotation
(drop
(i32.const 0)
)
)
)

;; CHECK: (module
;; CHECK-NEXT: (type $0 (func))
;; CHECK-NEXT: (@binaryen.js.called)
;; CHECK-NEXT: (func $func-annotation
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )

38 changes: 38 additions & 0 deletions test/lit/passes/remove-unused-module-elements-js-called.wast
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.

;; RUN: foreach %s %t wasm-opt --remove-unused-module-elements --closed-world -all -S -o - | filecheck %s

(module
(@binaryen.js.called)
Copy link
Member

Choose a reason for hiding this comment

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

I think the latest changes to the update script should put this annotation down with the function.

;; CHECK: (type $0 (func))

;; CHECK: (elem declare func $js.called.referred)

;; CHECK: (export "export" (func $export))

;; CHECK: (func $js.called.referred (type $0)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $js.called.referred
;; This is jsCalled, and referred below, so it is kept.
(drop (i32.const 10))
)

;; CHECK: (func $export (type $0)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.func $js.called.referred)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $export (export "export")
(drop (ref.func $js.called.referred))
)

(@binaryen.js.called)
(func $js.called.unreferred
;; This is jsCalled, and not referred anywhere. The annotation does not
;; stop the function from being removed entirely.
(drop (i32.const 20))
)
)
Loading
Loading