Skip to content

Commit 1df8e5c

Browse files
kripkentlively
andauthored
[Code Annotations] Start Branch Hinting support, in the text format, for Breaks (#7567)
This adds the first minimal amount of work for Branch Hinting (and code annotations, more generally): Just the text format, and just a single instruction (Break). This design follows debug info: Assuming most instructions have no branch hints (like most do not have debug info), we do not add a field to the Break class itself, but to metadata on the side. --------- Co-authored-by: Thomas Lively <[email protected]>
1 parent 527140a commit 1df8e5c

File tree

8 files changed

+173
-17
lines changed

8 files changed

+173
-17
lines changed

src/parser/contexts.h

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "support/name.h"
2424
#include "support/result.h"
2525
#include "support/string.h"
26+
#include "wasm-annotations.h"
2627
#include "wasm-builder.h"
2728
#include "wasm-ir-builder.h"
2829
#include "wasm.h"
@@ -2339,11 +2340,47 @@ struct ParseDefsCtx : TypeParserCtx<ParseDefsCtx> {
23392340
return withLoc(pos, irBuilder.makeCallIndirect(*t, type, isReturn));
23402341
}
23412342

2343+
// Return the branch hint for a branching instruction, if there is one.
2344+
std::optional<bool>
2345+
getBranchHint(const std::vector<Annotation>& annotations) {
2346+
// Find and apply (the last) branch hint.
2347+
const Annotation* hint = nullptr;
2348+
for (auto& a : annotations) {
2349+
if (a.kind == Annotations::BranchHint) {
2350+
hint = &a;
2351+
}
2352+
}
2353+
if (!hint) {
2354+
return std::nullopt;
2355+
}
2356+
2357+
Lexer lexer(hint->contents);
2358+
if (lexer.empty()) {
2359+
std::cerr << "warning: empty BranchHint\n";
2360+
return std::nullopt;
2361+
}
2362+
2363+
auto str = lexer.takeString();
2364+
if (!str || str->size() != 1) {
2365+
std::cerr << "warning: invalid BranchHint string\n";
2366+
return std::nullopt;
2367+
}
2368+
2369+
auto value = (*str)[0];
2370+
if (value != 0 && value != 1) {
2371+
std::cerr << "warning: invalid BranchHint value\n";
2372+
return std::nullopt;
2373+
}
2374+
2375+
return bool(value);
2376+
}
2377+
23422378
Result<> makeBreak(Index pos,
23432379
const std::vector<Annotation>& annotations,
23442380
Index label,
23452381
bool isConditional) {
2346-
return withLoc(pos, irBuilder.makeBreak(label, isConditional));
2382+
auto likely = getBranchHint(annotations);
2383+
return withLoc(pos, irBuilder.makeBreak(label, isConditional, likely));
23472384
}
23482385

23492386
Result<> makeSwitch(Index pos,

src/passes/Print.cpp

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include <pass.h>
2828
#include <pretty_printing.h>
2929
#include <support/string.h>
30+
#include <wasm-annotations.h>
3031
#include <wasm-stack.h>
3132
#include <wasm-type-printing.h>
3233
#include <wasm.h>
@@ -267,15 +268,17 @@ struct PrintSExpression : public UnifiedExpressionVisitor<PrintSExpression> {
267268

268269
void
269270
printDebugLocation(const std::optional<Function::DebugLocation>& location);
270-
void printDebugLocation(Expression* curr);
271271

272272
// Prints debug info for a delimiter in an expression.
273273
void printDebugDelimiterLocation(Expression* curr, Index i);
274274

275+
// Prints debug info and code annotations.
276+
void printMetadata(Expression* curr);
277+
275278
void printExpressionContents(Expression* curr);
276279

277280
void visit(Expression* curr) {
278-
printDebugLocation(curr);
281+
printMetadata(curr);
279282
UnifiedExpressionVisitor<PrintSExpression>::visit(curr);
280283
}
281284

@@ -2677,27 +2680,40 @@ void PrintSExpression::printDebugLocation(
26772680
doIndent(o, indent);
26782681
}
26792682

2680-
void PrintSExpression::printDebugLocation(Expression* curr) {
2683+
void PrintSExpression::printMetadata(Expression* curr) {
26812684
if (currFunction) {
2682-
// show an annotation, if there is one
2683-
auto& debugLocations = currFunction->debugLocations;
2684-
auto iter = debugLocations.find(curr);
2685-
if (iter != debugLocations.end()) {
2685+
// Show a debug location, if there is one.
2686+
if (auto iter = currFunction->debugLocations.find(curr);
2687+
iter != currFunction->debugLocations.end()) {
26862688
printDebugLocation(iter->second);
26872689
} else {
26882690
printDebugLocation(std::nullopt);
26892691
}
2690-
// show a binary position, if there is one
2692+
2693+
// Show a binary position, if there is one.
26912694
if (debugInfo) {
2692-
auto iter = currFunction->expressionLocations.find(curr);
2693-
if (iter != currFunction->expressionLocations.end()) {
2695+
if (auto iter = currFunction->expressionLocations.find(curr);
2696+
iter != currFunction->expressionLocations.end()) {
26942697
Colors::grey(o);
26952698
o << ";; code offset: 0x" << std::hex << iter->second.start << std::dec
26962699
<< '\n';
26972700
restoreNormalColor(o);
26982701
doIndent(o, indent);
26992702
}
27002703
}
2704+
2705+
// Show a code annotation, if there is one.
2706+
if (auto iter = currFunction->codeAnnotations.find(curr);
2707+
iter != currFunction->codeAnnotations.end()) {
2708+
auto& annotation = iter->second;
2709+
if (annotation.branchLikely) {
2710+
Colors::grey(o);
2711+
o << "(@" << Annotations::BranchHint << " \"\\0"
2712+
<< (*annotation.branchLikely ? "1" : "0") << "\")\n";
2713+
restoreNormalColor(o);
2714+
doIndent(o, indent);
2715+
}
2716+
}
27012717
}
27022718
}
27032719

@@ -2781,7 +2797,7 @@ void PrintSExpression::visitBlock(Block* curr) {
27812797
while (1) {
27822798
if (stack.size() > 0) {
27832799
doIndent(o, indent);
2784-
printDebugLocation(curr);
2800+
printMetadata(curr);
27852801
}
27862802
stack.push_back(curr);
27872803
o << '(';

src/wasm-annotations.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 2025 WebAssembly Community Group participants
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
//
18+
// Support for code annotations.
19+
//
20+
21+
#ifndef wasm_annotations_h
22+
#define wasm_annotations_h
23+
24+
#include "support/name.h"
25+
26+
namespace wasm::Annotations {
27+
28+
extern const Name BranchHint;
29+
30+
} // namespace wasm::Annotations
31+
32+
#endif // wasm_annotations_h

src/wasm-ir-builder.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,7 @@ class IRBuilder : public UnifiedExpressionVisitor<IRBuilder, Result<>> {
7272
this->codeSectionOffset = codeSectionOffset;
7373
}
7474

75-
// Set the function used to add scratch locals when constructing an isolated
76-
// sequence of IR.
75+
// The current function, used to create scratch locals, add annotations, etc.
7776
void setFunction(Function* func) { this->func = func; }
7877

7978
// Handle the boundaries of control flow structures. Users may choose to use
@@ -119,7 +118,9 @@ class IRBuilder : public UnifiedExpressionVisitor<IRBuilder, Result<>> {
119118
Result<> makeBlock(Name label, Signature sig);
120119
Result<> makeIf(Name label, Signature sig);
121120
Result<> makeLoop(Name label, Signature sig);
122-
Result<> makeBreak(Index label, bool isConditional);
121+
Result<> makeBreak(Index label,
122+
bool isConditional,
123+
std::optional<bool> likely = std::nullopt);
123124
Result<> makeSwitch(const std::vector<Index>& labels, Index defaultLabel);
124125
// Unlike Builder::makeCall, this assumes the function already exists.
125126
Result<> makeCall(Name func, bool isReturn);

src/wasm.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2191,6 +2191,16 @@ class Function : public Importable {
21912191
delimiterLocations;
21922192
BinaryLocations::FunctionLocations funcLocation;
21932193

2194+
// Code annotations. As with debug info, we do not store these on Expressions
2195+
// themselves, as we assume most instances are unannotated, and do not want to
2196+
// add constant memory overhead.
2197+
struct CodeAnnotation {
2198+
// Branch hinting proposal: Whether the branch is likely, or unlikely.
2199+
std::optional<bool> branchLikely;
2200+
};
2201+
2202+
std::unordered_map<Expression*, CodeAnnotation> codeAnnotations;
2203+
21942204
// The effects for this function, if they have been computed. We use a shared
21952205
// ptr here to avoid compilation errors with the forward-declared
21962206
// EffectAnalyzer.

src/wasm/wasm-ir-builder.cpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1393,7 +1393,9 @@ Result<> IRBuilder::makeLoop(Name label, Signature sig) {
13931393
return visitLoopStart(loop, sig.params);
13941394
}
13951395

1396-
Result<> IRBuilder::makeBreak(Index label, bool isConditional) {
1396+
Result<> IRBuilder::makeBreak(Index label,
1397+
bool isConditional,
1398+
std::optional<bool> likely) {
13971399
auto name = getLabelName(label);
13981400
CHECK_ERR(name);
13991401
auto labelType = getLabelType(label);
@@ -1404,7 +1406,15 @@ Result<> IRBuilder::makeBreak(Index label, bool isConditional) {
14041406
// Use a dummy condition value if we need to pop a condition.
14051407
curr.condition = isConditional ? &curr : nullptr;
14061408
CHECK_ERR(ChildPopper{*this}.visitBreak(&curr, *labelType));
1407-
push(builder.makeBreak(curr.name, curr.value, curr.condition));
1409+
auto* br = builder.makeBreak(curr.name, curr.value, curr.condition);
1410+
push(br);
1411+
1412+
if (likely) {
1413+
// Branches are only possible inside functions.
1414+
assert(func);
1415+
func->codeAnnotations[br].branchLikely = likely;
1416+
}
1417+
14081418
return Ok{};
14091419
}
14101420

src/wasm/wasm.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#include "wasm.h"
1818
#include "ir/branch-utils.h"
19+
#include "wasm-annotations.h"
1920
#include "wasm-traversal.h"
2021
#include "wasm-type.h"
2122

@@ -63,6 +64,12 @@ const char* CustomDescriptorsFeature = "custom-descriptors";
6364

6465
} // namespace BinaryConsts::CustomSections
6566

67+
namespace Annotations {
68+
69+
const Name BranchHint = "metadata.code.branch_hint";
70+
71+
} // namespace Annotations
72+
6673
Name STACK_POINTER("__stack_pointer");
6774
Name MODULE("module");
6875
Name START("start");

test/lit/wat-annotations.wast

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
2+
3+
;; RUN: wasm-opt -all %s -S -o - | filecheck %s
4+
5+
(module
6+
;; CHECK: (type $0 (func (param i32)))
7+
8+
;; CHECK: (func $branch-hints-br_if (type $0) (param $x i32)
9+
;; CHECK-NEXT: (block $out
10+
;; CHECK-NEXT: (@metadata.code.branch_hint "\00")
11+
;; CHECK-NEXT: (br_if $out
12+
;; CHECK-NEXT: (local.get $x)
13+
;; CHECK-NEXT: )
14+
;; CHECK-NEXT: (@metadata.code.branch_hint "\01")
15+
;; CHECK-NEXT: (br_if $out
16+
;; CHECK-NEXT: (local.get $x)
17+
;; CHECK-NEXT: )
18+
;; CHECK-NEXT: (@metadata.code.branch_hint "\00")
19+
;; CHECK-NEXT: (br_if $out
20+
;; CHECK-NEXT: (local.get $x)
21+
;; CHECK-NEXT: )
22+
;; CHECK-NEXT: )
23+
;; CHECK-NEXT: )
24+
(func $branch-hints-br_if (param $x i32)
25+
(block $out
26+
;; A branch annotated as unlikely, and one as likely.
27+
(@metadata.code.branch_hint "\00")
28+
(br_if $out
29+
(local.get $x)
30+
)
31+
(@metadata.code.branch_hint "\01")
32+
(br_if $out
33+
(local.get $x)
34+
)
35+
;; The last one wins.
36+
(@metadata.code.branch_hint "\01")
37+
(@metadata.code.branch_hint "\00")
38+
(br_if $out
39+
(local.get $x)
40+
)
41+
)
42+
)
43+
)

0 commit comments

Comments
 (0)