Skip to content
7 changes: 5 additions & 2 deletions src/ir/intrinsics.h
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,8 @@ class Intrinsics {
}

// 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.
// be annotated itself (which takes precedence), or the function it calls may
// be annotated.
CodeAnnotation getCallAnnotations(Call* call, Function* func) {
// Combine annotations from the call itself and from the called function.
auto ret = getAnnotations(call, func);
Expand All @@ -168,6 +168,9 @@ class Intrinsics {
if (!ret.idempotent) {
ret.idempotent = funcAnnotations.idempotent;
}
if (!ret.toolchainInline) {
ret.toolchainInline = funcAnnotations.toolchainInline;
}
}

return ret;
Expand Down
30 changes: 22 additions & 8 deletions src/parser/contexts.h
Original file line number Diff line number Diff line change
Expand Up @@ -1361,6 +1361,7 @@ struct AnnotationParserCtx {
// overrides the others.
const Annotation* branchHint = nullptr;
const Annotation* inlineHint = nullptr;
const Annotation* toolchainInlineHint = nullptr;
for (auto& a : annotations) {
if (a.kind == Annotations::BranchHint) {
branchHint = &a;
Expand All @@ -1372,6 +1373,8 @@ struct AnnotationParserCtx {
ret.jsCalled = true;
} else if (a.kind == Annotations::IdempotentHint) {
ret.idempotent = true;
} else if (a.kind == Annotations::ToolchainInlineHint) {
toolchainInlineHint = &a;
}
}

Expand All @@ -1395,25 +1398,36 @@ struct AnnotationParserCtx {
}
}

// Apply the last inline hint, if valid.
if (inlineHint) {
Lexer lexer(inlineHint->contents);
auto parseI7Hint = [&](const Annotation* hint,
const char* name) -> std::optional<uint8_t> {
if (!hint) {
return {};
}

Lexer lexer(hint->contents);
if (lexer.empty()) {
std::cerr << "warning: empty InlineHint\n";
std::cerr << "warning: empty " << name << "\n";
} else {
auto str = lexer.takeString();
if (!str || str->size() != 1) {
std::cerr << "warning: invalid InlineHint string\n";
std::cerr << "warning: invalid " << name << " string\n";
} else {
uint8_t value = (*str)[0];
if (value > 127) {
std::cerr << "warning: invalid InlineHint value\n";
std::cerr << "warning: invalid " << name << " value\n";
} else {
ret.inline_ = value;
return value;
}
}
}
}

return {};
};

// Apply the last inline hints, if valid.
ret.inline_ = parseI7Hint(inlineHint, "InlineHint");
ret.toolchainInline =
parseI7Hint(toolchainInlineHint, "Toolchain InlineHint");

return ret;
}
Expand Down
11 changes: 11 additions & 0 deletions src/passes/Inlining.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ struct FunctionInfo {
bool usedGlobally;
TrivialInstruction trivialInstruction;
InliningMode inliningMode;
std::optional<uint8_t> toolchainInlineHint;

FunctionInfo() { clear(); }

Expand All @@ -126,6 +127,7 @@ struct FunctionInfo {
usedGlobally = false;
trivialInstruction = TrivialInstruction::NotTrivial;
inliningMode = InliningMode::Unknown;
toolchainInlineHint = std::nullopt;
}

// Provide an explicit = operator as the |refs| field lacks one by default.
Expand All @@ -138,6 +140,7 @@ struct FunctionInfo {
usedGlobally = other.usedGlobally;
trivialInstruction = other.trivialInstruction;
inliningMode = other.inliningMode;
toolchainInlineHint = other.toolchainInlineHint;
return *this;
}

Expand All @@ -148,6 +151,13 @@ struct FunctionInfo {
if (hasTryDelegate) {
return false;
}
// Follow the toolchain hint, if present.
if (toolchainInlineHint == CodeAnnotation::NeverInline) {
return false;
}
if (toolchainInlineHint == CodeAnnotation::AlwaysInline) {
return true;
}
// If it's small enough that we always want to inline such things, do so.
if (size <= options.inlining.alwaysInlineMaxSize) {
return true;
Expand Down Expand Up @@ -236,6 +246,7 @@ struct FunctionInfoScanner
auto& info = infos[curr->name];

info.size = Measurer::measure(curr->body);
info.toolchainInlineHint = curr->funcAnnotations.toolchainInline;

// If the body is a simple instruction with roughly the same encoded size as
// a `call` instruction, and arguments are function locals read in order,
Expand Down
11 changes: 11 additions & 0 deletions src/passes/Print.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2838,6 +2838,17 @@ void PrintSExpression::printCodeAnnotations(const CodeAnnotation& annotation) {
restoreNormalColor(o);
doIndent(o, indent);
}
if (annotation.toolchainInline) {
Colors::grey(o);
std::ofstream saved;
saved.copyfmt(o);
o << "(@" << Annotations::ToolchainInlineHint << " \"\\" << std::hex
<< std::setfill('0') << std::setw(2) << int(*annotation.toolchainInline)
<< "\")\n";
o.copyfmt(saved);
restoreNormalColor(o);
doIndent(o, indent);
}
}

void PrintSExpression::printExpressionContents(Expression* curr) {
Expand Down
1 change: 1 addition & 0 deletions src/passes/StripToolchainAnnotations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ struct StripToolchainAnnotations
annotation.removableIfUnused = false;
annotation.jsCalled = false;
annotation.idempotent = false;
annotation.toolchainInline = std::nullopt;
}
};

Expand Down
1 change: 1 addition & 0 deletions src/wasm-annotations.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ extern const Name InlineHint;
extern const Name RemovableIfUnusedHint;
extern const Name JSCalledHint;
extern const Name IdempotentHint;
extern const Name ToolchainInlineHint;

} // 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 @@ -1480,6 +1480,7 @@ class WasmBinaryWriter {
std::optional<BufferWithRandomAccess> getRemovableIfUnusedHintsBuffer();
std::optional<BufferWithRandomAccess> getJSCalledHintsBuffer();
std::optional<BufferWithRandomAccess> getIdempotentHintsBuffer();
std::optional<BufferWithRandomAccess> getToolchainInlineHintsBuffer();

// helpers
void writeInlineString(std::string_view name);
Expand Down Expand Up @@ -1780,6 +1781,7 @@ class WasmBinaryReader {
void readRemovableIfUnusedHints(size_t payloadLen);
void readJSCalledHints(size_t payloadLen);
void readIdempotentHints(size_t payloadLen);
void readToolchainInlineHints(size_t payloadLen);

std::tuple<Address, Address, Index, MemoryOrder, BackingType>
readMemoryAccess(bool isAtomic, bool isRMW);
Expand Down
8 changes: 6 additions & 2 deletions src/wasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -2347,8 +2347,7 @@ struct CodeAnnotation {
std::optional<bool> branchLikely;

// Compilation Hints proposal.
static const uint8_t NeverInline = 0;
static const uint8_t AlwaysInline = 127;
enum { NeverInline = 0, AlwaysInline = 127 };
std::optional<uint8_t> inline_;

// Toolchain hints, see
Expand Down Expand Up @@ -2378,6 +2377,11 @@ struct CodeAnnotation {
// optimize things like Java class constructors.
bool idempotent = false;

// An inlining hint at the toolchain level, in contrast to inline_, above,
// which is for VMs. (E.g., one may want to not inline at the toolchain level
// to keep size small, and tell VMs to inline at runtime.)
std::optional<uint8_t> toolchainInline;

bool operator==(const CodeAnnotation& other) const {
return equalOnSemanticsPreserving(other) && equalOnSemanticsAltering(other);
}
Expand Down
80 changes: 49 additions & 31 deletions src/wasm/wasm-binary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1662,6 +1662,7 @@ std::optional<BufferWithRandomAccess> WasmBinaryWriter::writeCodeAnnotations() {
append(getRemovableIfUnusedHintsBuffer());
append(getJSCalledHintsBuffer());
append(getIdempotentHintsBuffer());
append(getToolchainInlineHintsBuffer());
return ret;
}

Expand Down Expand Up @@ -1783,23 +1784,24 @@ std::optional<BufferWithRandomAccess> WasmBinaryWriter::getBranchHintsBuffer() {
});
}

std::optional<BufferWithRandomAccess> WasmBinaryWriter::getInlineHintsBuffer() {
return writeExpressionHints(
Annotations::InlineHint,
[](const CodeAnnotation& annotation) { return annotation.inline_; },
[](const CodeAnnotation& annotation, BufferWithRandomAccess& buffer) {
// Hint size, always 1 for now.
buffer << U32LEB(1);

// We must only emit hints that are present.
assert(annotation.inline_);

// Hint must fit in one byte.
assert(*annotation.inline_ <= 127);

// Hint contents: inline frequency count
buffer << U32LEB(*annotation.inline_);
// Writes a simple i7 hint, in the range [0..127].
#define WRITE_I7_HINT(code, field) \

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Not a strong preference, but you can specify a class field this way too

auto getUsingSecondaries = [&](const Name& name, auto UsedNames::* field) {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Oh nice, I didn't think of that. I'll land the current version as it follows the existing code which also uses macros, but maybe we should really refactor it all to the non-macro version later...

return writeExpressionHints( \
code, \
[](const CodeAnnotation& annotation) { return annotation.field; }, \
[](const CodeAnnotation& annotation, BufferWithRandomAccess& buffer) { \
/* Hint size, always 1 for now. */ \
buffer << U32LEB(1); \
/* We must only emit hints that are present. */ \
assert(annotation.field); \
/* Hint must fit in one byte. */ \
assert(*annotation.field <= 127); \
/* Hint contents: inline frequency count. */ \
buffer << U32LEB(*annotation.field); \
});

std::optional<BufferWithRandomAccess> WasmBinaryWriter::getInlineHintsBuffer() {
WRITE_I7_HINT(Annotations::InlineHint, inline_);
}

// Writes a simple boolean hint of size 0. Receives the code and the field name
Expand Down Expand Up @@ -1827,6 +1829,11 @@ WasmBinaryWriter::getIdempotentHintsBuffer() {
WRITE_BOOLEAN_HINT(Annotations::IdempotentHint, idempotent);
}

std::optional<BufferWithRandomAccess>
WasmBinaryWriter::getToolchainInlineHintsBuffer() {
WRITE_I7_HINT(Annotations::ToolchainInlineHint, toolchainInline);
}

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 @@ -2103,7 +2110,8 @@ void WasmBinaryReader::preScan() {
sectionName == Annotations::InlineHint ||
sectionName == Annotations::RemovableIfUnusedHint ||
sectionName == Annotations::JSCalledHint ||
sectionName == Annotations::IdempotentHint) {
sectionName == Annotations::IdempotentHint ||
sectionName == Annotations::ToolchainInlineHint) {
// Code annotations require code locations.
// TODO: We could note which functions require code locations, as an
// optimization.
Expand Down Expand Up @@ -2271,6 +2279,11 @@ void WasmBinaryReader::readCustomSection(size_t payloadLen) {
} else if (sectionName == Annotations::IdempotentHint) {
deferredAnnotationSections.push_back(AnnotationSectionInfo{
pos, [this, payloadLen]() { this->readIdempotentHints(payloadLen); }});
} else if (sectionName == Annotations::ToolchainInlineHint) {
deferredAnnotationSections.push_back(
AnnotationSectionInfo{pos, [this, payloadLen]() {
this->readToolchainInlineHints(payloadLen);
}});
} else {
// an unfamiliar custom section
if (sectionName.equals(BinaryConsts::CustomSections::Linking)) {
Expand Down Expand Up @@ -5617,21 +5630,22 @@ void WasmBinaryReader::readBranchHints(size_t payloadLen) {
});
}

void WasmBinaryReader::readInlineHints(size_t payloadLen) {
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");
}
// Reads a simple i7 hint, in the range [0..127].
#define READ_I7_HINT(code, field) \
readExpressionHints(code, payloadLen, [&](CodeAnnotation& annotation) { \
auto size = getU32LEB(); \
if (size != 1) { \
throwError("bad InlineHint size"); \
} \
uint8_t field = getInt8(); \
if (field > 127) { \
throwError("bad InlineHint value"); \
} \
annotation.field = field; \
});

annotation.inline_ = inline_;
});
void WasmBinaryReader::readInlineHints(size_t payloadLen) {
READ_I7_HINT(Annotations::InlineHint, inline_);
}

// Reads a simple boolean hint of size 0. Receives the code and the field name
Expand All @@ -5657,6 +5671,10 @@ void WasmBinaryReader::readIdempotentHints(size_t payloadLen) {
READ_BOOLEAN_HINT(Annotations::IdempotentHint, idempotent);
}

void WasmBinaryReader::readToolchainInlineHints(size_t payloadLen) {
READ_I7_HINT(Annotations::ToolchainInlineHint, toolchainInline);
}

std::tuple<Address, Address, Index, MemoryOrder, BackingType>
WasmBinaryReader::readMemoryAccess(bool isAtomic, bool isRMW) {
auto rawAlignment = getU32LEB();
Expand Down
5 changes: 5 additions & 0 deletions src/wasm/wasm-ir-builder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2863,6 +2863,11 @@ void IRBuilder::applyAnnotations(Expression* expr,
assert(func);
func->codeAnnotations[expr].idempotent = true;
}

if (annotation.toolchainInline) {
assert(func);
func->codeAnnotations[expr].toolchainInline = annotation.toolchainInline;
}
}

} // namespace wasm
1 change: 1 addition & 0 deletions src/wasm/wasm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ const Name InlineHint = "metadata.code.inline";
const Name RemovableIfUnusedHint = "binaryen.removable.if.unused";
const Name JSCalledHint = "binaryen.js.called";
const Name IdempotentHint = "binaryen.idempotent";
const Name ToolchainInlineHint = "binaryen.inline";

} // namespace Annotations

Expand Down
9 changes: 7 additions & 2 deletions test/lit/passes/strip-toolchain-annotations-func.wast
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@
(func $idempotent
;; This hint should be removed too.
)
)


;; CHECK: (func $test-binaryen-inline (type $0)
;; CHECK-NEXT: )
(@binaryen.inline "\00")
(func $test-binaryen-inline
;; This hint should be removed too.
)
)
7 changes: 7 additions & 0 deletions test/lit/passes/strip-toolchain-annotations.wast
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
;; CHECK-NEXT: (call $test
;; CHECK-NEXT: (i32.const 4)
;; CHECK-NEXT: )
;; CHECK-NEXT: (call $test
;; CHECK-NEXT: (i32.const 5)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test (param i32)
;; Inlining hints are not removed, as they are for the VM too.
Expand All @@ -44,5 +47,9 @@
;; This should be removed too.
(@binaryen.idempotent)
(call $test (i32.const 4))

;; And this.
(@binaryen.inline "\00")
(call $test (i32.const 5))
)
)
Loading
Loading