Skip to content

Commit 0dc07ea

Browse files
yurydelendikkripken
authored andcommitted
Exporting/importing debug location information from .wast/.asm.js/.s formats (#1017)
* Extends wasm-as, wasm-dis and s2wasm to consume debug locations. * Exports source map from asm2wasm
1 parent fcbe14a commit 0dc07ea

33 files changed

+786
-111
lines changed

auto_update_tests.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@
4141
print ' '.join(cmd)
4242
actual = run_command(cmd)
4343
with open(os.path.join('test', wasm), 'w') as o: o.write(actual)
44+
if 'debugInfo' in asm:
45+
cmd += ['--source-map', os.path.join('test', wasm + '.map'), '-o', 'a.wasm']
46+
run_command(cmd)
47+
4448

4549
for dot_s_dir in ['dot_s', 'llvm_autogenerated']:
4650
for s in sorted(os.listdir(os.path.join('test', dot_s_dir))):

check.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,31 @@ def do_asm2wasm_test():
189189
fail_with_error('wasm interpreter error: ' + err) # failed to pretty-print
190190
fail_with_error('wasm interpreter error')
191191

192+
# verify debug info
193+
if 'debugInfo' in asm:
194+
jsmap = 'a.wasm.map'
195+
cmd += ['--source-map', jsmap,
196+
'--source-map-url', 'http://example.org/' + jsmap,
197+
'-o', 'a.wasm']
198+
run_command(cmd)
199+
if not os.path.isfile(jsmap):
200+
fail_with_error('Debug info map not created: %s' % jsmap)
201+
with open(wasm + '.map', 'rb') as expected:
202+
with open(jsmap, 'rb') as actual:
203+
fail_if_not_identical(actual.read(), expected.read())
204+
with open('a.wasm', 'rb') as binary:
205+
url_section_name = bytearray([16]) + bytearray('sourceMappingURL')
206+
payload = 'http://example.org/' + jsmap
207+
assert len(payload) < 256, 'name too long'
208+
url_section_contents = bytearray([len(payload)]) + bytearray(payload)
209+
print url_section_name
210+
binary_contents = bytearray(binary.read())
211+
if url_section_name not in binary_contents:
212+
fail_with_error('source map url section not found in binary')
213+
if url_section_contents not in binary_contents[binary_contents.index(url_section_name):]:
214+
fail_with_error('source map url not found in url section')
215+
216+
192217
print '\n[ checking asm2wasm binary reading/writing... ]\n'
193218

194219
asmjs = os.path.join(options.binaryen_test, 'hello_world.asm.js')
@@ -241,6 +266,8 @@ def do_asm2wasm_test():
241266
print '..', t
242267
t = os.path.join(options.binaryen_test, t)
243268
cmd = WASM_DIS + [t]
269+
if os.path.isfile(t + '.map'): cmd += ['--source-map', t + '.map']
270+
244271
actual = run_command(cmd)
245272

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

src/asm2wasm.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1382,13 +1382,13 @@ void Asm2WasmBuilder::processAsm(Ref ast) {
13821382
while (i < expressionStack.size()) {
13831383
exp = expressionStack[i];
13841384
if (debugLocations.count(exp) == 0) {
1385-
debugLocations[exp] = { fileIndex, lineNumber };
1385+
debugLocations[exp] = { fileIndex, lineNumber, 0 };
13861386
break;
13871387
}
13881388
i++;
13891389
}
13901390
} else {
1391-
debugLocations[exp] = { fileIndex, lineNumber };
1391+
debugLocations[exp] = { fileIndex, lineNumber, 0 };
13921392
}
13931393
break;
13941394
}

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/passes/Print.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ struct PrintSExpression : public Visitor<PrintSExpression> {
4545

4646
Module* currModule = nullptr;
4747
Function* currFunction = nullptr;
48+
Function::DebugLocation lastPrintedLocation;
4849

4950
PrintSExpression(std::ostream& o) : o(o) {
5051
setMinify(false);
@@ -58,8 +59,11 @@ struct PrintSExpression : public Visitor<PrintSExpression> {
5859
auto iter = debugLocations.find(curr);
5960
if (iter != debugLocations.end()) {
6061
auto fileName = currModule->debugInfoFileNames[iter->second.fileIndex];
61-
o << ";; " << fileName << ":" << iter->second.lineNumber << '\n';
62-
doIndent(o, indent);
62+
if (lastPrintedLocation != iter->second) {
63+
lastPrintedLocation = iter->second;
64+
o << ";;@ " << fileName << ":" << iter->second.lineNumber << ":" << iter->second.columnNumber << '\n';
65+
doIndent(o, indent);
66+
}
6367
}
6468
}
6569
Visitor<PrintSExpression>::visit(curr);
@@ -599,6 +603,7 @@ struct PrintSExpression : public Visitor<PrintSExpression> {
599603
}
600604
void visitFunction(Function *curr) {
601605
currFunction = curr;
606+
lastPrintedLocation = { 0, 0, 0 };
602607
printOpening(o, "func ", true);
603608
printName(curr->name);
604609
if (curr->type.is()) {

src/s2wasm.h

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class S2WasmBuilder {
4444
MixedArena* allocator;
4545
LinkerObject* linkerObj;
4646
std::unique_ptr<LinkerObject::SymbolInfo> symbolInfo;
47+
std::unordered_map<uint32_t, uint32_t> fileIndexMap;
4748

4849
public:
4950
S2WasmBuilder(const char* input, bool debug)
@@ -601,7 +602,9 @@ class S2WasmBuilder {
601602
size_t fileId = getInt();
602603
skipWhitespace();
603604
auto quoted = getQuoted();
604-
WASM_UNUSED(fileId); WASM_UNUSED(quoted); // TODO: use the fileId and quoted
605+
uint32_t index = wasm->debugInfoFileNames.size();
606+
fileIndexMap[fileId] = index;
607+
wasm->debugInfoFileNames.push_back(std::string(quoted.begin(), quoted.end()));
605608
s = strchr(s, '\n');
606609
return;
607610
}
@@ -665,22 +668,31 @@ class S2WasmBuilder {
665668

666669
mustMatch(":");
667670

671+
Function::DebugLocation debugLocation = { 0, 0, 0 };
672+
bool useDebugLocation = false;
668673
auto recordFile = [&]() {
669674
if (debug) dump("file");
670675
size_t fileId = getInt();
671676
skipWhitespace();
672677
auto quoted = getQuoted();
673-
WASM_UNUSED(fileId); WASM_UNUSED(quoted); // TODO: use the fileId and quoted
678+
uint32_t index = wasm->debugInfoFileNames.size();
679+
fileIndexMap[fileId] = index;
680+
wasm->debugInfoFileNames.push_back(std::string(quoted.begin(), quoted.end()));
674681
s = strchr(s, '\n');
675682
};
676683
auto recordLoc = [&]() {
677684
if (debug) dump("loc");
678685
size_t fileId = getInt();
679686
skipWhitespace();
680-
size_t row = getInt();
687+
uint32_t row = getInt();
681688
skipWhitespace();
682-
size_t column = getInt();
683-
WASM_UNUSED(fileId); WASM_UNUSED(row); WASM_UNUSED(column); // TODO: use the fileId, row and column
689+
uint32_t column = getInt();
690+
auto iter = fileIndexMap.find(fileId);
691+
if (iter == fileIndexMap.end()) {
692+
abort_on("idx");
693+
}
694+
useDebugLocation = true;
695+
debugLocation = { iter->second, row, column };
684696
s = strchr(s, '\n');
685697
};
686698
auto recordLabel = [&]() {
@@ -746,7 +758,10 @@ class S2WasmBuilder {
746758
// parse body
747759
func->body = allocator->alloc<Block>();
748760
std::vector<Expression*> bstack;
749-
auto addToBlock = [&bstack](Expression* curr) {
761+
auto addToBlock = [&](Expression* curr) {
762+
if (useDebugLocation) {
763+
func->debugLocations[curr] = debugLocation;
764+
}
750765
Expression* last = bstack.back();
751766
if (last->is<Loop>()) {
752767
last = last->cast<Loop>()->body;

src/tools/asm2wasm.cpp

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ int main(int argc, const char *argv[]) {
3636
bool legalizeJavaScriptFFI = true;
3737
Asm2WasmBuilder::TrapMode trapMode = Asm2WasmBuilder::TrapMode::JS;
3838
bool wasmOnly = false;
39+
std::string sourceMapFilename;
40+
std::string sourceMapUrl;
3941
std::string symbolMap;
4042
bool emitBinary = true;
4143

@@ -99,9 +101,15 @@ int main(int argc, const char *argv[]) {
99101
[&legalizeJavaScriptFFI](Options *o, const std::string &) {
100102
legalizeJavaScriptFFI = false;
101103
})
102-
.add("--debuginfo", "-g", "Emit names section and debug info (for debug info you must emit text, -S, for this to work)",
104+
.add("--debuginfo", "-g", "Emit names section in wasm binary (or full debuginfo in wast)",
103105
Options::Arguments::Zero,
104106
[&](Options *o, const std::string &arguments) { options.passOptions.debugInfo = true; })
107+
.add("--source-map", "-sm", "Emit source map (if using binary output) to the specified file",
108+
Options::Arguments::One,
109+
[&sourceMapFilename](Options *o, const std::string &argument) { sourceMapFilename = argument; })
110+
.add("--source-map-url", "-su", "Use specified string as source map URL",
111+
Options::Arguments::One,
112+
[&sourceMapUrl](Options *o, const std::string &argument) { sourceMapUrl = argument; })
105113
.add("--symbolmap", "-s", "Emit a symbol map (indexes => names)",
106114
Options::Arguments::One,
107115
[&](Options *o, const std::string &argument) { symbolMap = argument; })
@@ -136,8 +144,9 @@ int main(int argc, const char *argv[]) {
136144
}
137145

138146
Asm2WasmPreProcessor pre;
139-
// wasm binaries can contain a names section, but not full debug info
140-
pre.debugInfo = options.passOptions.debugInfo && !emitBinary;
147+
// wasm binaries can contain a names section, but not full debug info --
148+
// debug info is disabled if a map file is not specified with wasm binary
149+
pre.debugInfo = options.passOptions.debugInfo && (!emitBinary || sourceMapFilename.size());
141150
auto input(
142151
read_file<std::vector<char>>(options.extra["infile"], Flags::Text, options.debug ? Flags::Debug : Flags::Release));
143152
char *start = pre.process(input.data());
@@ -204,6 +213,10 @@ int main(int argc, const char *argv[]) {
204213
writer.setDebugInfo(options.passOptions.debugInfo);
205214
writer.setSymbolMap(symbolMap);
206215
writer.setBinary(emitBinary);
216+
if (emitBinary) {
217+
writer.setSourceMapFilename(sourceMapFilename);
218+
writer.setSourceMapUrl(sourceMapUrl);
219+
}
207220
writer.write(wasm, options.extra["output"]);
208221

209222
if (options.debug) std::cerr << "done." << std::endl;

src/tools/wasm-as.cpp

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ using namespace wasm;
3030
int main(int argc, const char *argv[]) {
3131
bool debugInfo = false;
3232
std::string symbolMap;
33+
std::string sourceMapFilename;
34+
std::string sourceMapUrl;
3335
Options options("wasm-as", "Assemble a .wast (WebAssembly text format) into a .wasm (WebAssembly binary format)");
3436
options.extra["validate"] = "wasm";
3537
options
@@ -51,6 +53,12 @@ int main(int argc, const char *argv[]) {
5153
.add("--debuginfo", "-g", "Emit names section and debug info",
5254
Options::Arguments::Zero,
5355
[&](Options *o, const std::string &arguments) { debugInfo = true; })
56+
.add("--source-map", "-sm", "Emit source map to the specified file",
57+
Options::Arguments::One,
58+
[&sourceMapFilename](Options *o, const std::string &argument) { sourceMapFilename = argument; })
59+
.add("--source-map-url", "-su", "Use specified string as source map URL",
60+
Options::Arguments::One,
61+
[&sourceMapUrl](Options *o, const std::string &argument) { sourceMapUrl = argument; })
5462
.add("--symbolmap", "-s", "Emit a symbol map (indexes => names)",
5563
Options::Arguments::One,
5664
[&](Options *o, const std::string &argument) { symbolMap = argument; })
@@ -86,13 +94,23 @@ int main(int argc, const char *argv[]) {
8694
if (options.debug) std::cerr << "binarification..." << std::endl;
8795
BufferWithRandomAccess buffer(options.debug);
8896
WasmBinaryWriter writer(&wasm, buffer, options.debug);
89-
writer.setDebugInfo(debugInfo);
97+
// if debug info is used, then we want to emit the names section
98+
writer.setNamesSection(debugInfo);
99+
std::unique_ptr<std::ofstream> sourceMapStream = nullptr;
100+
if (sourceMapFilename.size()) {
101+
sourceMapStream = make_unique<std::ofstream>();
102+
sourceMapStream->open(sourceMapFilename);
103+
writer.setSourceMap(sourceMapStream.get(), sourceMapUrl);
104+
}
90105
if (symbolMap.size() > 0) writer.setSymbolMap(symbolMap);
91106
writer.write();
92107

93108
if (options.debug) std::cerr << "writing to output..." << std::endl;
94109
Output output(options.extra["output"], Flags::Binary, options.debug ? Flags::Debug : Flags::Release);
95110
buffer.writeTo(output);
111+
if (sourceMapStream) {
112+
sourceMapStream->close();
113+
}
96114

97115
if (options.debug) std::cerr << "Done." << std::endl;
98116
}

src/tools/wasm-dis.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,17 @@ using namespace cashew;
2828
using namespace wasm;
2929

3030
int main(int argc, const char *argv[]) {
31+
std::string sourceMapFilename;
3132
Options options("wasm-dis", "Un-assemble a .wasm (WebAssembly binary format) into a .wast (WebAssembly text format)");
3233
options.add("--output", "-o", "Output file (stdout if not specified)",
3334
Options::Arguments::One,
3435
[](Options *o, const std::string &argument) {
3536
o->extra["output"] = argument;
3637
Colors::disable();
3738
})
39+
.add("--source-map", "-sm", "Consume source map from the specified file to add location information",
40+
Options::Arguments::One,
41+
[&sourceMapFilename](Options *o, const std::string &argument) { sourceMapFilename = argument; })
3842
.add_positional("INFILE", Options::Arguments::One,
3943
[](Options *o, const std::string &argument) {
4044
o->extra["infile"] = argument;
@@ -46,11 +50,23 @@ int main(int argc, const char *argv[]) {
4650
if (options.debug) std::cerr << "parsing binary..." << std::endl;
4751
Module wasm;
4852
try {
53+
std::unique_ptr<std::ifstream> sourceMapStream;
4954
WasmBinaryBuilder parser(wasm, input, options.debug);
55+
if (sourceMapFilename.size()) {
56+
sourceMapStream = make_unique<std::ifstream>();
57+
sourceMapStream->open(sourceMapFilename);
58+
parser.setDebugLocations(sourceMapStream.get());
59+
}
5060
parser.read();
61+
if (sourceMapStream) {
62+
sourceMapStream->close();
63+
}
5164
} catch (ParseException& p) {
5265
p.dump(std::cerr);
5366
Fatal() << "error in parsing wasm binary";
67+
} catch (MapParseException& p) {
68+
p.dump(std::cerr);
69+
Fatal() << "error in parsing wasm source mapping";
5470
}
5571

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

0 commit comments

Comments
 (0)