Skip to content

Commit db10f1a

Browse files
committed
Output JS source mapping file (instead of .txtmap).
1 parent 61f251f commit db10f1a

20 files changed

+447
-122
lines changed

check.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -177,19 +177,19 @@ def do_asm2wasm_test():
177177

178178
# verify debug info
179179
if 'debugInfo' in asm:
180-
txtmap = 'a.wasm.txtmap'
181-
cmd += ['--binarymap-file', txtmap,
182-
'--binarymap-url', txtmap + '.map',
180+
jsmap = 'a.wasm.map'
181+
cmd += ['--binarymap-file', jsmap,
182+
'--binarymap-url', 'http://example.org/' + jsmap,
183183
'-o', 'a.wasm']
184184
run_command(cmd)
185-
if not os.path.isfile(txtmap):
186-
fail_with_error('Debug info map not created: %s' % txtmap)
187-
with open(wasm + '.txtmap', 'rb') as expected:
188-
with open(txtmap, 'rb') as actual:
185+
if not os.path.isfile(jsmap):
186+
fail_with_error('Debug info map not created: %s' % jsmap)
187+
with open(wasm + '.map', 'rb') as expected:
188+
with open(jsmap, 'rb') as actual:
189189
fail_if_not_identical(actual.read(), expected.read())
190190
with open('a.wasm', 'rb') as binary:
191191
url_section_name = bytearray([16]) + bytearray('sourceMappingURL')
192-
payload = txtmap + '.map'
192+
payload = 'http://example.org/' + jsmap
193193
assert len(payload) < 256, 'name too long'
194194
url_section_contents = bytearray([len(payload)]) + bytearray(payload)
195195
print url_section_name
@@ -252,6 +252,8 @@ def do_asm2wasm_test():
252252
print '..', t
253253
t = os.path.join(options.binaryen_test, t)
254254
cmd = WASM_DIS + [t]
255+
if os.path.isfile(t + '.map'): cmd += ['-bm', t + '.map']
256+
255257
actual = run_command(cmd)
256258

257259
with open(t + '.fromBinary') as f:

src/parsing.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,25 @@ struct ParseException {
193193
}
194194
};
195195

196+
struct MapParseException {
197+
std::string text;
198+
199+
MapParseException() : text("unknown parse error") {}
200+
MapParseException(std::string text) : text(text) {}
201+
202+
void dump(std::ostream& o) {
203+
Colors::magenta(o);
204+
o << "[";
205+
Colors::red(o);
206+
o << "map parse exception: ";
207+
Colors::green(o);
208+
o << text;
209+
Colors::magenta(o);
210+
o << "]";
211+
Colors::normal(o);
212+
}
213+
};
214+
196215
// Helper for parsers that may not have unique label names. This transforms
197216
// the names into unique ones, as required by Binaryen IR.
198217
struct UniqueNameMapper {

src/tools/wasm-dis.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ int main(int argc, const char *argv[]) {
6464
} catch (ParseException& p) {
6565
p.dump(std::cerr);
6666
Fatal() << "error in parsing wasm binary";
67+
} catch (MapParseException& p) {
68+
p.dump(std::cerr);
69+
Fatal() << "error in parsing wasm source mapping";
6770
}
6871

6972
if (options.debug) std::cerr << "Printing..." << std::endl;

src/wasm-binary.h

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,10 @@ class WasmBinaryWriter : public Visitor<WasmBinaryWriter, void> {
593593
void writeSourceMapUrl();
594594
void writeSymbolMap();
595595

596+
void writeBinaryMapProlog();
597+
void writeBinaryMapEpilog();
598+
void writeDebugLocation(size_t offset, const Function::DebugLocation& loc);
599+
596600
// helpers
597601
void writeInlineString(const char* name);
598602
void writeInlineBuffer(const char* data, size_t size);
@@ -616,16 +620,15 @@ class WasmBinaryWriter : public Visitor<WasmBinaryWriter, void> {
616620
void recurse(Expression*& curr);
617621
std::vector<Name> breakStack;
618622
Function::DebugLocation lastDebugLocation;
623+
size_t lastBytecodeOffset;
619624

620625
void visit(Expression* curr) {
621626
if (binaryMap && currFunction) {
622627
// Dump the binaryMap debug info
623628
auto& debugLocations = currFunction->debugLocations;
624629
auto iter = debugLocations.find(curr);
625630
if (iter != debugLocations.end() && iter->second != lastDebugLocation) {
626-
lastDebugLocation = iter->second;
627-
auto fileName = wasm->debugInfoFileNames[iter->second.fileIndex];
628-
*binaryMap << o.size() << ":" << fileName << ":" << iter->second.lineNumber << ":" << iter->second.columnNumber << '\n';
631+
writeDebugLocation(o.size(), iter->second);
629632
}
630633
}
631634
Visitor<WasmBinaryWriter>::visit(curr);
@@ -767,11 +770,11 @@ class WasmBinaryBuilder {
767770
// Debug information reading helpers
768771
void setDebugLocations(std::istream* binaryMap_) {
769772
binaryMap = binaryMap_;
770-
readNextDebugLocation();
771773
}
772774
Function::DebugLocation debugLocation;
773775
std::unordered_map<std::string, Index> debugInfoFileIndices;
774776
void readNextDebugLocation();
777+
void readBinaryMapHeader();
775778

776779
// AST reading
777780
int depth = 0; // only for debugging

src/wasm/wasm-binary.cpp

Lines changed: 173 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ void WasmBinaryWriter::prepare() {
3333

3434
void WasmBinaryWriter::write() {
3535
writeHeader();
36+
if (binaryMap) {
37+
writeBinaryMapProlog();
38+
}
3639

3740
writeTypes();
3841
writeImports();
@@ -49,6 +52,9 @@ void WasmBinaryWriter::write() {
4952
if (binaryMap) writeSourceMapUrl();
5053
if (symbolMap.size() > 0) writeSymbolMap();
5154

55+
if (binaryMap) {
56+
writeBinaryMapEpilog();
57+
}
5258
finishUp();
5359
}
5460

@@ -238,7 +244,6 @@ void WasmBinaryWriter::writeFunctions() {
238244
size_t start = o.size();
239245
Function* function = wasm->functions[i].get();
240246
currFunction = function;
241-
lastDebugLocation = {0, 0, 0};
242247
mappedLocals.clear();
243248
numLocalsByType.clear();
244249
if (debug) std::cerr << "writing" << function->name << std::endl;
@@ -445,6 +450,50 @@ void WasmBinaryWriter::writeSymbolMap() {
445450
file.close();
446451
}
447452

453+
void WasmBinaryWriter::writeBinaryMapProlog() {
454+
lastDebugLocation = {0, /* lineNumber = */ 1, 0};
455+
lastBytecodeOffset = 0;
456+
*binaryMap << "{\"version\":3,\"sources\":[";
457+
for (size_t i = 0; i < wasm->debugInfoFileNames.size(); i++) {
458+
if (i > 0) *binaryMap << ",";
459+
// TODO respect JSON string encoding, e.g. quotes and control chars.
460+
*binaryMap << "\"" << wasm->debugInfoFileNames[i] << "\"";
461+
}
462+
*binaryMap << "],\"names\":[],\"mappings\":\"";
463+
}
464+
465+
void WasmBinaryWriter::writeBinaryMapEpilog() {
466+
*binaryMap << "\"}";
467+
}
468+
469+
static void writeBase64VLQ(std::ostream& out, int32_t n) {
470+
uint32_t value = n >= 0 ? n << 1 : ((-n) << 1) | 1;
471+
while (1) {
472+
uint32_t digit = value & 0x1F;
473+
value >>= 5;
474+
if (!value) {
475+
// last VLQ digit -- base64 codes 'A'..'Z', 'a'..'f'
476+
out << char(digit < 26 ? 'A' + digit : 'a' + digit - 26);
477+
break;
478+
}
479+
// more VLG digit will follow -- add continuation bit (0x20),
480+
// base64 codes 'g'..'z', '0'..'9', '+', '/'
481+
out << char(digit < 20 ? 'g' + digit : digit < 30 ? '0' + digit - 20 : digit == 30 ? '+' : '/');
482+
}
483+
}
484+
485+
void WasmBinaryWriter::writeDebugLocation(size_t offset, const Function::DebugLocation& loc) {
486+
if (lastBytecodeOffset > 0) {
487+
*binaryMap << ",";
488+
}
489+
writeBase64VLQ(*binaryMap, int32_t(offset - lastBytecodeOffset));
490+
writeBase64VLQ(*binaryMap, int32_t(loc.fileIndex - lastDebugLocation.fileIndex));
491+
writeBase64VLQ(*binaryMap, int32_t(loc.lineNumber - lastDebugLocation.lineNumber));
492+
writeBase64VLQ(*binaryMap, int32_t(loc.columnNumber - lastDebugLocation.columnNumber));
493+
lastDebugLocation = loc;
494+
lastBytecodeOffset = offset;
495+
}
496+
448497
void WasmBinaryWriter::writeInlineString(const char* name) {
449498
int32_t size = strlen(name);
450499
o << U32LEB(size);
@@ -949,6 +998,7 @@ static Name RETURN_BREAK("binaryen|break-to-return");
949998
void WasmBinaryBuilder::read() {
950999

9511000
readHeader();
1001+
readBinaryMapHeader();
9521002

9531003
// read sections until the end
9541004
while (more()) {
@@ -1402,43 +1452,134 @@ void WasmBinaryBuilder::readExports() {
14021452
}
14031453
}
14041454

1405-
void WasmBinaryBuilder::readNextDebugLocation() {
1406-
if (binaryMap) {
1407-
std::string line;
1408-
while (std::getline(*binaryMap, line)) {
1409-
auto pos = line.begin();
1410-
while (pos < line.end() && pos[0] != ':') pos++;
1411-
if (pos == line.end()) continue;
1412-
uint32_t position = atoi(std::string(line.begin(), pos).c_str());
1413-
auto filenameStart = ++pos;
1414-
while (pos < line.end() && pos[0] != ':') pos++;
1415-
if (pos == line.end()) continue;
1416-
std::string file(filenameStart, pos);
1417-
auto iter = debugInfoFileIndices.find(file);
1418-
if (iter == debugInfoFileIndices.end()) {
1419-
Index index = wasm.debugInfoFileNames.size();
1420-
wasm.debugInfoFileNames.push_back(file);
1421-
debugInfoFileIndices[file] = index;
1455+
static int32_t readBase64VLQ(std::istream& in) {
1456+
uint32_t value = 0;
1457+
uint32_t shift = 0;
1458+
while (1) {
1459+
char ch = in.get();
1460+
if (ch == EOF)
1461+
throw MapParseException("unexpected EOF in the middle of VLQ");
1462+
if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch < 'g')) {
1463+
// last number digit
1464+
uint32_t digit = ch < 'a' ? ch - 'A' : ch - 'a' + 26;
1465+
value |= digit << shift;
1466+
break;
1467+
}
1468+
if (!(ch >= 'g' && ch <= 'z') && !(ch >= '0' && ch <= '9') &&
1469+
ch != '+' && ch != '/') {
1470+
throw MapParseException("invalid VLQ digit");
1471+
}
1472+
uint32_t digit = ch > '9' ? ch - 'g' : (ch >= '0' ? ch - '0' + 20 : (ch == '+' ? 30 : 31));
1473+
value |= digit << shift;
1474+
shift += 5;
1475+
}
1476+
return value & 1 ? -int32_t(value >> 1) : int32_t(value >> 1);
1477+
}
1478+
1479+
void WasmBinaryBuilder::readBinaryMapHeader() {
1480+
if (!binaryMap) {
1481+
return;
1482+
}
1483+
auto maybeReadChar = [&](char expected) {
1484+
if (binaryMap->peek() != expected)
1485+
return false;
1486+
binaryMap->get();
1487+
return true;
1488+
};
1489+
auto mustReadChar = [&](char expected) {
1490+
if (binaryMap->get() != expected)
1491+
throw MapParseException("Unexpected char");
1492+
};
1493+
auto findField = [&](const char* name, size_t len) {
1494+
bool matching = false;
1495+
size_t pos;
1496+
while (1) {
1497+
int ch = binaryMap->get();
1498+
if (ch == EOF) return false;
1499+
if (ch == '\"') {
1500+
matching = true;
1501+
pos = 0;
1502+
} else if (matching && name[pos] == ch) {
1503+
++pos;
1504+
if (pos == len) {
1505+
if (maybeReadChar('\"')) break; // found field
1506+
}
1507+
} else {
1508+
matching = false;
14221509
}
1423-
uint32_t fileIndex = debugInfoFileIndices[file];
1424-
auto lineNumberStart = ++pos;
1425-
while (pos < line.end() && pos[0] != ':') pos++;
1426-
if (pos == line.end()) {
1427-
// old format
1428-
uint32_t lineNumber = atoi(std::string(lineNumberStart, line.end()).c_str());
1429-
nextDebugLocation = {position, {fileIndex, lineNumber, 0}};
1430-
return;
1510+
}
1511+
mustReadChar(':');
1512+
return true;
1513+
};
1514+
auto readString = [&](std::string& str) {
1515+
std::vector<char> vec;
1516+
mustReadChar('\"');
1517+
if (!maybeReadChar('\"')) {
1518+
while (1) {
1519+
int ch = binaryMap->get();
1520+
if (ch == EOF)
1521+
throw MapParseException("unexpected EOF in the middle of string");
1522+
if (ch == '\"') break;
1523+
vec.push_back(ch);
14311524
}
1432-
uint32_t lineNumber = atoi(std::string(lineNumberStart, pos).c_str());
1433-
auto columnNumberStart = ++pos;
1434-
uint32_t columnNumber = atoi(std::string(columnNumberStart, line.end()).c_str());
1435-
1436-
nextDebugLocation = {position, {fileIndex, lineNumber, columnNumber}};
1437-
return;
14381525
}
1526+
str = std::string(vec.begin(), vec.end());
1527+
};
1528+
1529+
if (!findField("sources", strlen("sources")))
1530+
throw MapParseException("cannot find the sources field in map");
1531+
mustReadChar('[');
1532+
if (!maybeReadChar(']')) {
1533+
do {
1534+
std::string file;
1535+
readString(file);
1536+
Index index = wasm.debugInfoFileNames.size();
1537+
wasm.debugInfoFileNames.push_back(file);
1538+
debugInfoFileIndices[file] = index;
1539+
} while (maybeReadChar(','));
1540+
mustReadChar(']');
1541+
}
1542+
1543+
if (!findField("mappings", strlen("mappings")))
1544+
throw MapParseException("cannot find the mappings field in map");
1545+
mustReadChar('\"');
1546+
if (maybeReadChar('\"')) { // empty mappings
1547+
nextDebugLocation.first = 0;
1548+
return;
1549+
}
1550+
// read first debug location
1551+
uint32_t position = readBase64VLQ(*binaryMap);
1552+
uint32_t fileIndex = readBase64VLQ(*binaryMap);
1553+
uint32_t lineNumber = readBase64VLQ(*binaryMap) + 1; // adjust zero-based line number
1554+
uint32_t columnNumber = readBase64VLQ(*binaryMap);
1555+
nextDebugLocation = {position, {fileIndex, lineNumber, columnNumber}};
1556+
}
1557+
1558+
void WasmBinaryBuilder::readNextDebugLocation() {
1559+
if (!binaryMap) {
1560+
return;
1561+
}
1562+
char ch;
1563+
*binaryMap >> ch;
1564+
if (ch == '\"') { // end of records
14391565
nextDebugLocation.first = 0;
1566+
return;
14401567
}
1568+
if (ch != ',')
1569+
throw MapParseException("Unexpected delimiter");
1570+
1571+
int32_t positionDelta = readBase64VLQ(*binaryMap);
1572+
uint32_t position = nextDebugLocation.first + positionDelta;
1573+
int32_t fileIndexDelta = readBase64VLQ(*binaryMap);
1574+
uint32_t fileIndex = nextDebugLocation.second.fileIndex + fileIndexDelta;
1575+
int32_t lineNumberDelta = readBase64VLQ(*binaryMap);
1576+
uint32_t lineNumber = nextDebugLocation.second.lineNumber + lineNumberDelta;
1577+
int32_t columnNumberDelta = readBase64VLQ(*binaryMap);
1578+
uint32_t columnNumber = nextDebugLocation.second.columnNumber + columnNumberDelta;
1579+
1580+
nextDebugLocation = {position, {fileIndex, lineNumber, columnNumber}};
14411581
}
1582+
14421583
Expression* WasmBinaryBuilder::readExpression() {
14431584
assert(depth == 0);
14441585
processExpressions();

test/debugInfo.fromasm.clamp.map

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/debugInfo.fromasm.clamp.no-opts.map

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/debugInfo.fromasm.clamp.no-opts.txtmap

Lines changed: 0 additions & 14 deletions
This file was deleted.

0 commit comments

Comments
 (0)