28
28
#include " support/debug.h"
29
29
#include " support/stdckdint.h"
30
30
#include " support/string.h"
31
+ #include " wasm-annotations.h"
31
32
#include " wasm-binary.h"
32
33
#include " wasm-debug.h"
33
34
#include " wasm-limits.h"
@@ -474,6 +475,21 @@ void WasmBinaryWriter::writeFunctions() {
474
475
}
475
476
});
476
477
finishSection (sectionStart);
478
+
479
+ // Code annotations must come before the code section (see comment on
480
+ // writeCodeAnnotations).
481
+ if (auto annotations = writeCodeAnnotations ()) {
482
+ // We need to move the code section and put the annotations before it.
483
+ auto & annotationsBuffer = *annotations;
484
+ auto oldSize = o.size ();
485
+ o.resize (oldSize + annotationsBuffer.size ());
486
+
487
+ // |sectionStart| is the start of the contents of the section. Subtract 1 to
488
+ // include the section code as well, so we move all of it.
489
+ std::move_backward (&o[sectionStart - 1 ], &o[oldSize], o.end ());
490
+ std::copy (
491
+ annotationsBuffer.begin (), annotationsBuffer.end (), &o[sectionStart - 1 ]);
492
+ }
477
493
}
478
494
479
495
void WasmBinaryWriter::writeStrings () {
@@ -1496,14 +1512,17 @@ void WasmBinaryWriter::trackExpressionStart(Expression* curr, Function* func) {
1496
1512
// binary locations tracked, then track it in the output as well. We also
1497
1513
// track locations of instructions that have code annotations, as their binary
1498
1514
// location goes in the custom section.
1499
- if (func && !func->expressionLocations .empty ()) {
1515
+ if (func && (!func->expressionLocations .empty () ||
1516
+ func->codeAnnotations .count (curr))) {
1500
1517
binaryLocations.expressions [curr] =
1501
1518
BinaryLocations::Span{BinaryLocation (o.size ()), 0 };
1502
1519
binaryLocationTrackedExpressionsForFunc.push_back (curr);
1503
1520
}
1504
1521
}
1505
1522
1506
1523
void WasmBinaryWriter::trackExpressionEnd (Expression* curr, Function* func) {
1524
+ // TODO: If we need to track the end of annotated code locations, we need to
1525
+ // enable that here.
1507
1526
if (func && !func->expressionLocations .empty ()) {
1508
1527
auto & span = binaryLocations.expressions .at (curr);
1509
1528
span.end = o.size ();
@@ -1513,11 +1532,123 @@ void WasmBinaryWriter::trackExpressionEnd(Expression* curr, Function* func) {
1513
1532
void WasmBinaryWriter::trackExpressionDelimiter (Expression* curr,
1514
1533
Function* func,
1515
1534
size_t id) {
1535
+ // TODO: If we need to track the delimiters of annotated code locations, we
1536
+ // need to enable that here.
1516
1537
if (func && !func->expressionLocations .empty ()) {
1517
1538
binaryLocations.delimiters [curr][id] = o.size ();
1518
1539
}
1519
1540
}
1520
1541
1542
+ std::optional<BufferWithRandomAccess> WasmBinaryWriter::writeCodeAnnotations () {
1543
+ // Assemble the info for Branch Hinting: for each function, a vector of the
1544
+ // hints.
1545
+ struct ExprHint {
1546
+ Expression* expr;
1547
+ // The offset we will write in the custom section.
1548
+ BinaryLocation offset;
1549
+ Function::CodeAnnotation* hint;
1550
+ };
1551
+
1552
+ struct FuncHints {
1553
+ Name func;
1554
+ std::vector<ExprHint> exprHints;
1555
+ };
1556
+
1557
+ std::vector<FuncHints> funcHintsVec;
1558
+
1559
+ for (auto & func : wasm->functions ) {
1560
+ // Collect the Branch Hints for this function.
1561
+ FuncHints funcHints;
1562
+
1563
+ // We compute the location of the function declaration area (where the
1564
+ // locals are declared) the first time we need it.
1565
+ BinaryLocation funcDeclarationsOffset = 0 ;
1566
+
1567
+ for (auto & [expr, annotation] : func->codeAnnotations ) {
1568
+ if (annotation.branchLikely ) {
1569
+ auto exprIter = binaryLocations.expressions .find (expr);
1570
+ if (exprIter == binaryLocations.expressions .end ()) {
1571
+ // No expression exists for this annotation - perhaps optimizations
1572
+ // removed it.
1573
+ continue ;
1574
+ }
1575
+ auto exprOffset = exprIter->second .start ;
1576
+
1577
+ if (!funcDeclarationsOffset) {
1578
+ auto funcIter = binaryLocations.functions .find (func.get ());
1579
+ assert (funcIter != binaryLocations.functions .end ());
1580
+ funcDeclarationsOffset = funcIter->second .declarations ;
1581
+ }
1582
+
1583
+ // Compute the offset: it should be relative to the start of the
1584
+ // function locals (i.e. the function declarations).
1585
+ auto offset = exprOffset - funcDeclarationsOffset;
1586
+
1587
+ funcHints.exprHints .push_back (ExprHint{expr, offset, &annotation});
1588
+ }
1589
+ }
1590
+
1591
+ if (funcHints.exprHints .empty ()) {
1592
+ continue ;
1593
+ }
1594
+
1595
+ // We found something. Finalize the data.
1596
+ funcHints.func = func->name ;
1597
+
1598
+ // Hints must be sorted by increasing binary offset.
1599
+ std::sort (
1600
+ funcHints.exprHints .begin (),
1601
+ funcHints.exprHints .end (),
1602
+ [](const ExprHint& a, const ExprHint& b) { return a.offset < b.offset ; });
1603
+
1604
+ funcHintsVec.emplace_back (std::move (funcHints));
1605
+ }
1606
+
1607
+ if (funcHintsVec.empty ()) {
1608
+ return {};
1609
+ }
1610
+
1611
+ if (sourceMap) {
1612
+ // TODO: This mode may not matter (when debugging, code annotations are an
1613
+ // optimization that can be skipped), but atm source maps cause
1614
+ // annotations to break.
1615
+ Fatal () << " Annotations are not supported with source maps" ;
1616
+ }
1617
+
1618
+ BufferWithRandomAccess buffer;
1619
+
1620
+ // We found data: emit the section.
1621
+ buffer << uint8_t (BinaryConsts::Custom);
1622
+ auto lebPos = buffer.writeU32LEBPlaceholder ();
1623
+ buffer.writeInlineString (Annotations::BranchHint.str );
1624
+
1625
+ buffer << U32LEB (funcHintsVec.size ());
1626
+ for (auto & funcHints : funcHintsVec) {
1627
+ buffer << U32LEB (getFunctionIndex (funcHints.func ));
1628
+
1629
+ buffer << U32LEB (funcHints.exprHints .size ());
1630
+ for (auto & exprHint : funcHints.exprHints ) {
1631
+ buffer << U32LEB (exprHint.offset );
1632
+
1633
+ // Hint size, always 1 for now.
1634
+ buffer << U32LEB (1 );
1635
+
1636
+ // We must only emit hints that are present.
1637
+ assert (exprHint.hint ->branchLikely );
1638
+
1639
+ // Hint contents: likely or not.
1640
+ buffer << U32LEB (int (*exprHint.hint ->branchLikely ));
1641
+ }
1642
+ }
1643
+
1644
+ // Write the final size. We can ignore the return value, which is the number
1645
+ // of bytes we shrank (if the LEB was smaller than the maximum size), as no
1646
+ // value in this section cares.
1647
+ buffer.emitRetroactiveSectionSizeLEB (lebPos);
1648
+
1649
+ return buffer;
1650
+ }
1651
+
1521
1652
void WasmBinaryWriter::writeData (const char * data, size_t size) {
1522
1653
for (size_t i = 0 ; i < size; i++) {
1523
1654
o << int8_t (data[i]);
@@ -1792,12 +1923,6 @@ WasmBinaryReader::WasmBinaryReader(Module& wasm,
1792
1923
}
1793
1924
1794
1925
void WasmBinaryReader::preScan () {
1795
- // TODO: Once we support code annotations here, we will need to always scan,
1796
- // but for now, DWARF is the only reason.
1797
- if (!DWARF) {
1798
- return ;
1799
- }
1800
-
1801
1926
assert (pos == 0 );
1802
1927
getInt32 (); // magic
1803
1928
getInt32 (); // version
@@ -1813,12 +1938,25 @@ void WasmBinaryReader::preScan() {
1813
1938
auto oldPos = pos;
1814
1939
if (sectionCode == BinaryConsts::Section::Custom) {
1815
1940
auto sectionName = getInlineString ();
1941
+
1942
+ // Code annotations require code locations.
1943
+ // TODO: For Branch Hinting, we could note which functions require
1944
+ // code locations, as an optimization.
1945
+ if (sectionName == Annotations::BranchHint) {
1946
+ needCodeLocations = true ;
1947
+ // Do not break, so we keep looking for DWARF.
1948
+ }
1949
+
1816
1950
// DWARF sections contain code offsets.
1817
1951
if (DWARF && Debug::isDWARFSection (sectionName)) {
1818
1952
needCodeLocations = true ;
1819
1953
foundDWARF = true ;
1820
1954
break ;
1821
1955
}
1956
+
1957
+ // TODO: We could stop early if we see the Code section and DWARF is
1958
+ // disabled, as BranchHint must appear first, but this seems to
1959
+ // make practically no difference in practice.
1822
1960
}
1823
1961
pos = oldPos + payloadLen;
1824
1962
}
@@ -1933,6 +2071,12 @@ void WasmBinaryReader::read() {
1933
2071
}
1934
2072
}
1935
2073
2074
+ // Go back and parse things we deferred.
2075
+ if (branchHintsPos) {
2076
+ pos = branchHintsPos;
2077
+ readBranchHints (branchHintsLen);
2078
+ }
2079
+
1936
2080
validateBinary ();
1937
2081
}
1938
2082
@@ -1953,6 +2097,10 @@ void WasmBinaryReader::readCustomSection(size_t payloadLen) {
1953
2097
readDylink (payloadLen);
1954
2098
} else if (sectionName.equals (BinaryConsts::CustomSections::Dylink0)) {
1955
2099
readDylink0 (payloadLen);
2100
+ } else if (sectionName == Annotations::BranchHint) {
2101
+ // Only note the position and length, we read this later.
2102
+ branchHintsPos = pos;
2103
+ branchHintsLen = payloadLen;
1956
2104
} else {
1957
2105
// an unfamiliar custom section
1958
2106
if (sectionName.equals (BinaryConsts::CustomSections::Linking)) {
@@ -5100,6 +5248,62 @@ void WasmBinaryReader::readDylink0(size_t payloadLen) {
5100
5248
}
5101
5249
}
5102
5250
5251
+ void WasmBinaryReader::readBranchHints (size_t payloadLen) {
5252
+ auto sectionPos = pos;
5253
+
5254
+ auto numFuncs = getU32LEB ();
5255
+ for (Index i = 0 ; i < numFuncs; i++) {
5256
+ auto funcIndex = getU32LEB ();
5257
+ if (funcIndex >= wasm.functions .size ()) {
5258
+ throwError (" bad BranchHint function" );
5259
+ }
5260
+
5261
+ auto & func = wasm.functions [funcIndex];
5262
+
5263
+ // The encoded offsets we read below are relative to the start of the
5264
+ // function's locals (the declarations).
5265
+ auto funcLocalsOffset = func->funcLocation .declarations ;
5266
+
5267
+ // We have a map of expressions to their locations. Invert that to get the
5268
+ // map we will use below, from offsets to expressions.
5269
+ std::unordered_map<BinaryLocation, Expression*> locationsMap;
5270
+
5271
+ for (auto & [expr, span] : func->expressionLocations ) {
5272
+ locationsMap[span.start ] = expr;
5273
+ }
5274
+
5275
+ auto numHints = getU32LEB ();
5276
+ for (Index hint = 0 ; hint < numHints; hint++) {
5277
+ // To get the absolute offset, add the function's offset.
5278
+ auto relativeOffset = getU32LEB ();
5279
+ auto absoluteOffset = funcLocalsOffset + relativeOffset;
5280
+
5281
+ auto iter = locationsMap.find (absoluteOffset);
5282
+ if (iter == locationsMap.end ()) {
5283
+ throwError (" bad BranchHint offset" );
5284
+ }
5285
+ auto * expr = iter->second ;
5286
+
5287
+ auto size = getU32LEB ();
5288
+ if (size != 1 ) {
5289
+ throwError (" bad BranchHint size" );
5290
+ }
5291
+
5292
+ auto likely = getU32LEB ();
5293
+ if (likely != 0 && likely != 1 ) {
5294
+ throwError (" bad BranchHint value" );
5295
+ }
5296
+
5297
+ // Apply the valid hint.
5298
+ func->codeAnnotations [expr].branchLikely = likely;
5299
+ }
5300
+ }
5301
+
5302
+ if (pos != sectionPos + payloadLen) {
5303
+ throwError (" bad BranchHint section size" );
5304
+ }
5305
+ }
5306
+
5103
5307
Index WasmBinaryReader::readMemoryAccess (Address& alignment, Address& offset) {
5104
5308
auto rawAlignment = getU32LEB ();
5105
5309
bool hasMemIdx = false ;
0 commit comments