Skip to content

Commit cf1c0ca

Browse files
committed
[LLVM][WebAssembly] Implement branch hinting proposal
This commit implements the WebAssembly branch hinting proposal, as detailed at https://webassembly.github.io/branch-hinting/metadata/code/binary.html. This proposal introduces a mechanism to convey branch likelihood information to the WebAssembly engine, allowing for more effective performance optimizations. The proposal specifies a new custom section named `metadata.code.branch_hint`. This section can contain a sequence of hints, where each hint is a single byte that applies to a corresponding `br_if` or `if` instruction. The hint values are: - `0x00` (`unlikely`): The branch is unlikely to be taken. - `0x01` (`likely`): The branch is likely to be taken. This implementation includes the following changes: - Addition of the "branch-hinting" feature (flag) - Collection of edge probabilities in CFGStackify pass - Outputting of `metadata.code.branch_hint` section in WebAssemblyAsmPrinter - Addition of the `WebAssembly::Specifier::S_DEBUG_REF` symbol ref specifier - Custom relaxation of leb128 fragments for storage of uleb128 encoded function indices and instruction offsets - Custom handling of code metadata sections in lld, required since the proposal requires code metadata sections to start with a combined count of function hints, followed by an ordered list of function hints. This change is purely an optimization and does not alter the semantics of WebAssembly programs.
1 parent 1949536 commit cf1c0ca

26 files changed

+777
-8
lines changed

clang/include/clang/Driver/Options.td

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5276,6 +5276,8 @@ def mtail_call : Flag<["-"], "mtail-call">, Group<m_wasm_Features_Group>;
52765276
def mno_tail_call : Flag<["-"], "mno-tail-call">, Group<m_wasm_Features_Group>;
52775277
def mwide_arithmetic : Flag<["-"], "mwide-arithmetic">, Group<m_wasm_Features_Group>;
52785278
def mno_wide_arithmetic : Flag<["-"], "mno-wide-arithmetic">, Group<m_wasm_Features_Group>;
5279+
def mbranch_hinting : Flag<["-"], "mbranch-hinting">, Group<m_wasm_Features_Group>;
5280+
def mno_branch_hinting : Flag<["-"], "mno-branch-hinting">, Group<m_wasm_Features_Group>;
52795281
def mexec_model_EQ : Joined<["-"], "mexec-model=">, Group<m_wasm_Features_Driver_Group>,
52805282
Values<"command,reactor">,
52815283
HelpText<"Execution model (WebAssembly only)">,

clang/lib/Basic/Targets/WebAssembly.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ bool WebAssemblyTargetInfo::hasFeature(StringRef Feature) const {
6969
.Case("simd128", SIMDLevel >= SIMD128)
7070
.Case("tail-call", HasTailCall)
7171
.Case("wide-arithmetic", HasWideArithmetic)
72+
.Case("branch-hinting", HasBranchHinting)
7273
.Default(false);
7374
}
7475

@@ -116,6 +117,8 @@ void WebAssemblyTargetInfo::getTargetDefines(const LangOptions &Opts,
116117
Builder.defineMacro("__wasm_tail_call__");
117118
if (HasWideArithmetic)
118119
Builder.defineMacro("__wasm_wide_arithmetic__");
120+
if (HasBranchHinting)
121+
Builder.defineMacro("__wasm_branch_hinting__");
119122

120123
Builder.defineMacro("__GCC_HAVE_SYNC_COMPARE_AND_SWAP_1");
121124
Builder.defineMacro("__GCC_HAVE_SYNC_COMPARE_AND_SWAP_2");
@@ -194,6 +197,7 @@ bool WebAssemblyTargetInfo::initFeatureMap(
194197
Features["multimemory"] = true;
195198
Features["tail-call"] = true;
196199
Features["wide-arithmetic"] = true;
200+
Features["branch-hinting"] = true;
197201
setSIMDLevel(Features, RelaxedSIMD, true);
198202
};
199203
if (CPU == "generic") {
@@ -347,6 +351,14 @@ bool WebAssemblyTargetInfo::handleTargetFeatures(
347351
HasWideArithmetic = false;
348352
continue;
349353
}
354+
if (Feature == "+branch-hinting") {
355+
HasBranchHinting = true;
356+
continue;
357+
}
358+
if (Feature == "-branch-hinting") {
359+
HasBranchHinting = false;
360+
continue;
361+
}
350362

351363
Diags.Report(diag::err_opt_not_valid_with_opt)
352364
<< Feature << "-target-feature";

clang/lib/Basic/Targets/WebAssembly.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ class LLVM_LIBRARY_VISIBILITY WebAssemblyTargetInfo : public TargetInfo {
7272
bool HasSignExt = false;
7373
bool HasTailCall = false;
7474
bool HasWideArithmetic = false;
75+
bool HasBranchHinting = false;
7576

7677
std::string ABI;
7778

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# RUN: rm -rf %t; split-file %s %t
2+
; RUN: llc -filetype=obj %t/f1.ll -o %t/f1.o -mattr=+branch-hinting
3+
; RUN: llc -filetype=obj %t/f2.ll -o %t/f2.o -mattr=+branch-hinting
4+
; RUN: wasm-ld --export-all -o %t.wasm %t/f2.o %t/f1.o
5+
; RUN: obj2yaml %t.wasm | FileCheck --check-prefixes=CHECK %s
6+
7+
; RUN: llc -filetype=obj %t/f1.ll -o %t/f1.o -mattr=-branch-hinting
8+
; RUN: llc -filetype=obj %t/f2.ll -o %t/f2.o -mattr=-branch-hinting
9+
; RUN: wasm-ld --export-all -o %t.wasm %t/f2.o %t/f1.o
10+
; RUN: obj2yaml %t.wasm | FileCheck --check-prefixes=NCHECK %s
11+
12+
; CHECK: - Type: CUSTOM
13+
; CHECK: Name: metadata.code.branch_hint
14+
; CHECK-NEXT: Entries:
15+
; CHECK-NEXT: - FuncIdx: 1
16+
; CHECK-NEXT: Hints:
17+
; CHECK-NEXT: - Offset: 7
18+
; CHECK-NEXT: Size: 1
19+
; CHECK-NEXT: Data: UNLIKELY
20+
; CHECK-NEXT: - Offset: 14
21+
; CHECK-NEXT: Size: 1
22+
; CHECK-NEXT: Data: LIKELY
23+
; CHECK-NEXT: - FuncIdx: 2
24+
; CHECK-NEXT: Hints:
25+
; CHECK-NEXT: - Offset: 5
26+
; CHECK-NEXT: Size: 1
27+
; CHECK-NEXT: Data: LIKELY
28+
; CHECK-NEXT: - FuncIdx: 3
29+
; CHECK-NEXT: Hints:
30+
; CHECK-NEXT: - Offset: 5
31+
; CHECK-NEXT: Size: 1
32+
; CHECK-NEXT: Data: UNLIKELY
33+
; CHECK-NEXT: - FuncIdx: 4
34+
; CHECK-NEXT: Hints:
35+
; CHECK-NEXT: - Offset: 5
36+
; CHECK-NEXT: Size: 1
37+
; CHECK-NEXT: Data: LIKELY
38+
39+
; CHECK: - Type: CUSTOM
40+
; CHECK-NEXT: Name: name
41+
; CHECK-NEXT: FunctionNames:
42+
; CHECK-NEXT: - Index: 0
43+
; CHECK-NEXT: Name: __wasm_call_ctors
44+
; CHECK-NEXT: - Index: 1
45+
; CHECK-NEXT: Name: test0
46+
; CHECK-NEXT: - Index: 2
47+
; CHECK-NEXT: Name: test1
48+
; CHECK-NEXT: - Index: 3
49+
; CHECK-NEXT: Name: _start
50+
; CHECK-NEXT: - Index: 4
51+
; CHECK-NEXT: Name: test_func1
52+
53+
; CHECK: - Type: CUSTOM
54+
; CHECK: Name: target_features
55+
; CHECK-NEXT: Features:
56+
; CHECK-NEXT: - Prefix: USED
57+
; CHECK-NEXT: Name: branch-hinting
58+
59+
60+
; NCHECK-NOT: Name: metadata.code.branch_hint
61+
; NCHECK-NOT: Name: branch-hinting
62+
63+
#--- f1.ll
64+
define i32 @_start(i32 %a) {
65+
entry:
66+
%cmp = icmp eq i32 %a, 0
67+
br i1 %cmp, label %if.then, label %if.else, !prof !0
68+
if.then:
69+
ret i32 1
70+
if.else:
71+
ret i32 2
72+
}
73+
74+
define i32 @test_func1(i32 %a) {
75+
entry:
76+
%cmp = icmp eq i32 %a, 0
77+
br i1 %cmp, label %if.then, label %if.else, !prof !1
78+
if.then:
79+
ret i32 1
80+
if.else:
81+
ret i32 2
82+
}
83+
84+
!0 = !{!"branch_weights", i32 2000, i32 1}
85+
!1 = !{!"branch_weights", i32 1, i32 2000}
86+
87+
#--- f2.ll
88+
89+
target triple = "wasm32-unknown-unknown"
90+
91+
define i32 @test0(i32 %a) {
92+
entry:
93+
%cmp0 = icmp eq i32 %a, 0
94+
br i1 %cmp0, label %if.then, label %ret1, !prof !0
95+
if.then:
96+
%cmp1 = icmp eq i32 %a, 1
97+
br i1 %cmp1, label %ret1, label %ret2, !prof !1
98+
ret1:
99+
ret i32 2
100+
ret2:
101+
ret i32 1
102+
}
103+
104+
define i32 @test1(i32 %a) {
105+
entry:
106+
%cmp = icmp eq i32 %a, 0
107+
br i1 %cmp, label %if.then, label %if.else, !prof !1
108+
if.then:
109+
ret i32 1
110+
if.else:
111+
ret i32 2
112+
}
113+
114+
; the resulting branch hint is actually reversed, since llvm-br is turned into br_unless, inverting branch probs
115+
!0 = !{!"branch_weights", i32 2000, i32 1}
116+
!1 = !{!"branch_weights", i32 1, i32 2000}

lld/wasm/OutputSections.cpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,62 @@ void CustomSection::writeTo(uint8_t *buf) {
270270
section->writeTo(buf);
271271
}
272272

273+
void CodeMetaDataSection::writeTo(uint8_t *buf) {
274+
log("writing " + toString(*this) + " offset=" + Twine(offset) +
275+
" size=" + Twine(getSize()) + " chunks=" + Twine(inputSections.size()));
276+
277+
assert(offset);
278+
buf += offset;
279+
280+
// Write section header
281+
memcpy(buf, header.data(), header.size());
282+
buf += header.size();
283+
memcpy(buf, nameData.data(), nameData.size());
284+
buf += nameData.size();
285+
286+
uint32_t TotalNumHints = 0;
287+
for (const InputChunk *section :
288+
make_range(inputSections.rbegin(), inputSections.rend())) {
289+
section->writeTo(buf);
290+
unsigned EncodingSize;
291+
uint32_t NumHints =
292+
decodeULEB128(buf + section->outSecOff, &EncodingSize, nullptr);
293+
if (EncodingSize != 5) {
294+
fatal("Unexpected encoding size for function hint vec size in " + name +
295+
": must be exactly 5 bytes.");
296+
}
297+
TotalNumHints += NumHints;
298+
}
299+
encodeULEB128(TotalNumHints, buf, 5);
300+
}
301+
302+
void CodeMetaDataSection::finalizeContents() {
303+
finalizeInputSections();
304+
305+
raw_string_ostream os(nameData);
306+
encodeULEB128(name.size(), os);
307+
os << name;
308+
309+
bool firstSection = true;
310+
for (InputChunk *section : inputSections) {
311+
assert(!section->discarded);
312+
payloadSize = alignTo(payloadSize, section->alignment);
313+
if (firstSection) {
314+
section->outSecOff = payloadSize;
315+
payloadSize += section->getSize();
316+
firstSection = false;
317+
} else {
318+
// adjust output offset so that each section write overwrites exactly the
319+
// subsequent section's function hint vector size (which deduplicates)
320+
section->outSecOff = payloadSize - 5;
321+
// payload size should not include the hint vector size, which is deduped
322+
payloadSize += section->getSize() - 5;
323+
}
324+
}
325+
326+
createHeader(payloadSize + nameData.size());
327+
}
328+
273329
uint32_t CustomSection::getNumRelocations() const {
274330
uint32_t count = 0;
275331
for (const InputChunk *inputSect : inputSections)

lld/wasm/OutputSections.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,15 @@ class CustomSection : public OutputSection {
132132
std::string nameData;
133133
};
134134

135+
class CodeMetaDataSection : public CustomSection {
136+
public:
137+
CodeMetaDataSection(std::string name, ArrayRef<InputChunk *> inputSections)
138+
: CustomSection(name, inputSections) {}
139+
140+
void writeTo(uint8_t *buf) override;
141+
void finalizeContents() override;
142+
};
143+
135144
} // namespace wasm
136145
} // namespace lld
137146

lld/wasm/Writer.cpp

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -165,14 +165,17 @@ void Writer::createCustomSections() {
165165
for (auto &pair : customSectionMapping) {
166166
StringRef name = pair.first;
167167
LLVM_DEBUG(dbgs() << "createCustomSection: " << name << "\n");
168-
169-
OutputSection *sec = make<CustomSection>(std::string(name), pair.second);
168+
OutputSection *Sec;
169+
if (name == "metadata.code.branch_hint")
170+
Sec = make<CodeMetaDataSection>(std::string(name), pair.second);
171+
else
172+
Sec = make<CustomSection>(std::string(name), pair.second);
170173
if (ctx.arg.relocatable || ctx.arg.emitRelocs) {
171-
auto *sym = make<OutputSectionSymbol>(sec);
174+
auto *sym = make<OutputSectionSymbol>(Sec);
172175
out.linkingSec->addToSymtab(sym);
173-
sec->sectionSym = sym;
176+
Sec->sectionSym = sym;
174177
}
175-
addSection(sec);
178+
addSection(Sec);
176179
}
177180
}
178181

llvm/include/llvm/BinaryFormat/Wasm.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,22 @@ struct WasmSignature {
517517
WasmSignature() = default;
518518
};
519519

520+
template <typename T> struct WasmCodeMetadataItemEntry {
521+
uint32_t Offset;
522+
uint32_t Size;
523+
T Data;
524+
};
525+
526+
template <typename T> struct WasmCodeMetadataFuncEntry {
527+
uint32_t FuncIdx;
528+
std::vector<WasmCodeMetadataItemEntry<T>> Hints;
529+
};
530+
531+
enum class WasmCodeMetadataBranchHint : uint8_t {
532+
UNLIKELY = 0x0,
533+
LIKELY = 0x1,
534+
};
535+
520536
// Useful comparison operators
521537
inline bool operator==(const WasmSignature &LHS, const WasmSignature &RHS) {
522538
return LHS.State == RHS.State && LHS.Returns == RHS.Returns &&

llvm/include/llvm/Object/Wasm.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,10 @@ class LLVM_ABI WasmObjectFile : public ObjectFile {
142142
ArrayRef<wasm::WasmFeatureEntry> getTargetFeatures() const {
143143
return TargetFeatures;
144144
}
145+
ArrayRef<wasm::WasmCodeMetadataFuncEntry<wasm::WasmCodeMetadataBranchHint>>
146+
getBranchHints() const {
147+
return BranchHints;
148+
}
145149
ArrayRef<wasm::WasmSignature> types() const { return Signatures; }
146150
ArrayRef<wasm::WasmImport> imports() const { return Imports; }
147151
ArrayRef<wasm::WasmTable> tables() const { return Tables; }
@@ -275,12 +279,16 @@ class LLVM_ABI WasmObjectFile : public ObjectFile {
275279
Error parseProducersSection(ReadContext &Ctx);
276280
Error parseTargetFeaturesSection(ReadContext &Ctx);
277281
Error parseRelocSection(StringRef Name, ReadContext &Ctx);
282+
Error parseCodeMetadataSection(StringRef Name, ReadContext &Ctx);
283+
Error parseBranchHintSection(ReadContext &Ctx);
278284

279285
wasm::WasmObjectHeader Header;
280286
std::vector<WasmSection> Sections;
281287
wasm::WasmDylinkInfo DylinkInfo;
282288
wasm::WasmProducerInfo ProducerInfo;
283289
std::vector<wasm::WasmFeatureEntry> TargetFeatures;
290+
std::vector<wasm::WasmCodeMetadataFuncEntry<wasm::WasmCodeMetadataBranchHint>>
291+
BranchHints;
284292
std::vector<wasm::WasmSignature> Signatures;
285293
std::vector<wasm::WasmTable> Tables;
286294
std::vector<wasm::WasmLimits> Memories;

0 commit comments

Comments
 (0)