Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 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
0f3f313
Merge remote-tracking branch 'origin/main' into callsIfMoved
kripken Jan 28, 2026
937b873
UNDO.ifMoved
kripken Jan 28, 2026
1e499ab
avoid ci warning
kripken Jan 28, 2026
a0e1183
fmrt
kripken Jan 28, 2026
2364fb8
avoid c++20 feature warning
kripken Jan 28, 2026
0212940
try the only remaining possibility and cross fingers
kripken Jan 28, 2026
be7922c
try yet another way to appease the c++ compiler's warnings
kripken Jan 28, 2026
d83c913
simplify to ={}
kripken Jan 29, 2026
35df904
format
kripken Jan 29, 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
2 changes: 1 addition & 1 deletion src/ir/metadata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ bool equal(Function* a, Function* b) {
bList.list[i],
a->codeAnnotations,
b->codeAnnotations,
Function::CodeAnnotation())) {
CodeAnnotation())) {
return false;
}
}
Expand Down
140 changes: 66 additions & 74 deletions src/parser/contexts.h
Original file line number Diff line number Diff line change
Expand Up @@ -1297,39 +1297,63 @@ struct ParseImplicitTypeDefsCtx : TypeParserCtx<ParseImplicitTypeDefsCtx> {
};

struct AnnotationParserCtx {
Copy link
Member

Choose a reason for hiding this comment

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

Not really related to this change, but it looks like parseAnnotations could just be a free function instead of accessed via multiple inheritance from AnnotationParserCtx.

Copy link
Member Author

Choose a reason for hiding this comment

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

Seems reasonable either way to me, I don't feel strongly.

// Return the inline hint for a call instruction, if there is one.
std::optional<std::uint8_t>
getInlineHint(const std::vector<Annotation>& annotations) {
// Find and apply (the last) inline hint.
const Annotation* hint = nullptr;
// Parse annotations into IR.
CodeAnnotation parseAnnotations(const std::vector<Annotation>& 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) {
hint = &a;
if (a.kind == Annotations::BranchHint) {
branchHint = &a;
} else if (a.kind == Annotations::InlineHint) {
inlineHint = &a;
}
}
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;
// 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 {
ret.branchLikely = bool(value);
}
}
}
}

uint8_t value = (*str)[0];
if (value > 127) {
std::cerr << "warning: invalid InlineHint value\n";
return std::nullopt;
// Apply the last inline hint, if valid.
if (inlineHint) {
Lexer lexer(inlineHint->contents);
if (lexer.empty()) {
std::cerr << "warning: empty InlineHint\n";
} 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;
}
}
}
}

return value;
return ret;
}
};

Expand Down Expand Up @@ -2000,10 +2024,10 @@ struct ParseDefsCtx : TypeParserCtx<ParseDefsCtx>, 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));
return withLoc(pos,
irBuilder.makeIf(label ? *label : Name{},
type.getSignature(),
parseAnnotations(annotations)));
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 just pass a CodeAnnotation instead of a const std::vector<Annotation>& into all of these makeXYZ functions?

Copy link
Member Author

Choose a reason for hiding this comment

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

Possibly, good question... Looking around a little, that would change hundreds of lines in the parser, so seems like a large refactoring. Perhaps we can consider it after?

}

Result<> visitElse() { return withLoc(irBuilder.visitElse()); }
Expand Down Expand Up @@ -2422,8 +2446,8 @@ struct ParseDefsCtx : TypeParserCtx<ParseDefsCtx>, AnnotationParserCtx {
const std::vector<Annotation>& annotations,
Name func,
bool isReturn) {
auto inline_ = getInlineHint(annotations);
return withLoc(pos, irBuilder.makeCall(func, isReturn, inline_));
return withLoc(
pos, irBuilder.makeCall(func, isReturn, parseAnnotations(annotations)));
}

Result<> makeCallIndirect(Index pos,
Expand All @@ -2433,52 +2457,18 @@ struct ParseDefsCtx : TypeParserCtx<ParseDefsCtx>, AnnotationParserCtx {
bool isReturn) {
auto t = getTable(pos, table);
CHECK_ERR(t);
auto inline_ = getInlineHint(annotations);
return withLoc(pos,
irBuilder.makeCallIndirect(*t, type, isReturn, inline_));
}

// Return the branch hint for a branching instruction, if there is one.
std::optional<bool>
getBranchHint(const std::vector<Annotation>& 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);
irBuilder.makeCallIndirect(
*t, type, isReturn, parseAnnotations(annotations)));
}

Result<> makeBreak(Index pos,
const std::vector<Annotation>& 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,
Expand Down Expand Up @@ -2617,8 +2607,9 @@ struct ParseDefsCtx : TypeParserCtx<ParseDefsCtx>, AnnotationParserCtx {
const std::vector<Annotation>& 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,
Expand Down Expand Up @@ -2658,8 +2649,9 @@ struct ParseDefsCtx : TypeParserCtx<ParseDefsCtx>, 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,
Expand Down
20 changes: 11 additions & 9 deletions src/wasm-binary.h
Original file line number Diff line number Diff line change
Expand Up @@ -1719,17 +1719,19 @@ 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<void()> read;
};
std::vector<AnnotationSectionInfo> 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);

std::tuple<Address, Address, Index, MemoryOrder>
Expand Down
21 changes: 8 additions & 13 deletions src/wasm-ir-builder.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,20 +129,19 @@ class IRBuilder : public UnifiedExpressionVisitor<IRBuilder, Result<>> {
Result<> makeNop();
Result<> makeBlock(Name label, Signature sig);
Result<>
makeIf(Name label, Signature sig, std::optional<bool> likely = std::nullopt);
makeIf(Name label, Signature sig, const CodeAnnotation& annotations = {});
Result<> makeLoop(Name label, Signature sig);
Result<> makeBreak(Index label,
bool isConditional,
std::optional<bool> likely = std::nullopt);
const CodeAnnotation& annotations = {});
Result<> makeSwitch(const std::vector<Index>& labels, Index defaultLabel);
// Unlike Builder::makeCall, this assumes the function already exists.
Result<> makeCall(Name func,
bool isReturn,
std::optional<std::uint8_t> inline_ = std::nullopt);
Result<>
makeCall(Name func, bool isReturn, const CodeAnnotation& annotations = {});
Result<> makeCallIndirect(Name table,
HeapType type,
bool isReturn,
std::optional<std::uint8_t> inline_ = std::nullopt);
const CodeAnnotation& annotations = {});
Result<> makeLocalGet(Index local);
Result<> makeLocalSet(Index local);
Result<> makeLocalTee(Index local);
Expand Down Expand Up @@ -226,15 +225,15 @@ class IRBuilder : public UnifiedExpressionVisitor<IRBuilder, Result<>> {
Result<> makeI31Get(bool signed_);
Result<> makeCallRef(HeapType type,
bool isReturn,
std::optional<std::uint8_t> inline_ = std::nullopt);
const CodeAnnotation& annotations = {});
Result<> makeRefTest(Type type);
Result<> makeRefCast(Type type, bool isDesc);
Result<> makeRefGetDesc(HeapType type);
Result<> makeBrOn(Index label,
BrOnOp op,
Type in = Type::none,
Type out = Type::none,
std::optional<bool> likely = std::nullopt);
const CodeAnnotation& annotations = {});
Result<> makeStructNew(HeapType type, bool isDesc);
Result<> makeStructNewDefault(HeapType type, bool isDesc);
Result<>
Expand Down Expand Up @@ -735,11 +734,7 @@ class IRBuilder : public UnifiedExpressionVisitor<IRBuilder, Result<>> {
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<bool> likely);

// Add an inlining hint, if |inline_| is present.
void addInlineHint(Expression* expr, std::optional<std::uint8_t> inline_);
void applyAnnotations(Expression* expr, const CodeAnnotation& annotation);

void dump();
};
Expand Down
34 changes: 17 additions & 17 deletions src/wasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -2232,6 +2232,21 @@ 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<bool> branchLikely;

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

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

class Function : public Importable {
public:
// A non-nullable reference to a function type. Exact for defined functions.
Expand Down Expand Up @@ -2285,25 +2300,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<bool> branchLikely;

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

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

// Function-level annotations are implemented with a key of nullptr, matching
// the 0 byte offset in the spec.
std::unordered_map<Expression*, CodeAnnotation> codeAnnotations;

// The effects for this function, if they have been computed. We use a shared
Expand Down
Loading
Loading