summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/asm2wasm.h4
-rw-r--r--src/parsing.h19
-rw-r--r--src/passes/Print.cpp9
-rw-r--r--src/s2wasm.h27
-rw-r--r--src/tools/asm2wasm.cpp19
-rw-r--r--src/tools/wasm-as.cpp20
-rw-r--r--src/tools/wasm-dis.cpp16
-rw-r--r--src/wasm-binary.h43
-rw-r--r--src/wasm-io.h4
-rw-r--r--src/wasm-s-parser.h20
-rw-r--r--src/wasm.h4
-rw-r--r--src/wasm/wasm-binary.cpp206
-rw-r--r--src/wasm/wasm-io.cpp13
-rw-r--r--src/wasm/wasm-s-parser.cpp60
-rw-r--r--src/wasm/wasm.cpp1
15 files changed, 438 insertions, 27 deletions
diff --git a/src/asm2wasm.h b/src/asm2wasm.h
index 16e7c95e1..b8439bdbf 100644
--- a/src/asm2wasm.h
+++ b/src/asm2wasm.h
@@ -1382,13 +1382,13 @@ void Asm2WasmBuilder::processAsm(Ref ast) {
while (i < expressionStack.size()) {
exp = expressionStack[i];
if (debugLocations.count(exp) == 0) {
- debugLocations[exp] = { fileIndex, lineNumber };
+ debugLocations[exp] = { fileIndex, lineNumber, 0 };
break;
}
i++;
}
} else {
- debugLocations[exp] = { fileIndex, lineNumber };
+ debugLocations[exp] = { fileIndex, lineNumber, 0 };
}
break;
}
diff --git a/src/parsing.h b/src/parsing.h
index d4df3c1c2..be3c112f3 100644
--- a/src/parsing.h
+++ b/src/parsing.h
@@ -193,6 +193,25 @@ struct ParseException {
}
};
+struct MapParseException {
+ std::string text;
+
+ MapParseException() : text("unknown parse error") {}
+ MapParseException(std::string text) : text(text) {}
+
+ void dump(std::ostream& o) {
+ Colors::magenta(o);
+ o << "[";
+ Colors::red(o);
+ o << "map parse exception: ";
+ Colors::green(o);
+ o << text;
+ Colors::magenta(o);
+ o << "]";
+ Colors::normal(o);
+ }
+};
+
// Helper for parsers that may not have unique label names. This transforms
// the names into unique ones, as required by Binaryen IR.
struct UniqueNameMapper {
diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp
index 6aebd612b..95f60e5e2 100644
--- a/src/passes/Print.cpp
+++ b/src/passes/Print.cpp
@@ -45,6 +45,7 @@ struct PrintSExpression : public Visitor<PrintSExpression> {
Module* currModule = nullptr;
Function* currFunction = nullptr;
+ Function::DebugLocation lastPrintedLocation;
PrintSExpression(std::ostream& o) : o(o) {
setMinify(false);
@@ -58,8 +59,11 @@ struct PrintSExpression : public Visitor<PrintSExpression> {
auto iter = debugLocations.find(curr);
if (iter != debugLocations.end()) {
auto fileName = currModule->debugInfoFileNames[iter->second.fileIndex];
- o << ";; " << fileName << ":" << iter->second.lineNumber << '\n';
- doIndent(o, indent);
+ if (lastPrintedLocation != iter->second) {
+ lastPrintedLocation = iter->second;
+ o << ";;@ " << fileName << ":" << iter->second.lineNumber << ":" << iter->second.columnNumber << '\n';
+ doIndent(o, indent);
+ }
}
}
Visitor<PrintSExpression>::visit(curr);
@@ -599,6 +603,7 @@ struct PrintSExpression : public Visitor<PrintSExpression> {
}
void visitFunction(Function *curr) {
currFunction = curr;
+ lastPrintedLocation = { 0, 0, 0 };
printOpening(o, "func ", true);
printName(curr->name);
if (curr->type.is()) {
diff --git a/src/s2wasm.h b/src/s2wasm.h
index 6ba4750ec..efa4ad601 100644
--- a/src/s2wasm.h
+++ b/src/s2wasm.h
@@ -44,6 +44,7 @@ class S2WasmBuilder {
MixedArena* allocator;
LinkerObject* linkerObj;
std::unique_ptr<LinkerObject::SymbolInfo> symbolInfo;
+ std::unordered_map<uint32_t, uint32_t> fileIndexMap;
public:
S2WasmBuilder(const char* input, bool debug)
@@ -601,7 +602,9 @@ class S2WasmBuilder {
size_t fileId = getInt();
skipWhitespace();
auto quoted = getQuoted();
- WASM_UNUSED(fileId); WASM_UNUSED(quoted); // TODO: use the fileId and quoted
+ uint32_t index = wasm->debugInfoFileNames.size();
+ fileIndexMap[fileId] = index;
+ wasm->debugInfoFileNames.push_back(std::string(quoted.begin(), quoted.end()));
s = strchr(s, '\n');
return;
}
@@ -665,22 +668,31 @@ class S2WasmBuilder {
mustMatch(":");
+ Function::DebugLocation debugLocation = { 0, 0, 0 };
+ bool useDebugLocation = false;
auto recordFile = [&]() {
if (debug) dump("file");
size_t fileId = getInt();
skipWhitespace();
auto quoted = getQuoted();
- WASM_UNUSED(fileId); WASM_UNUSED(quoted); // TODO: use the fileId and quoted
+ uint32_t index = wasm->debugInfoFileNames.size();
+ fileIndexMap[fileId] = index;
+ wasm->debugInfoFileNames.push_back(std::string(quoted.begin(), quoted.end()));
s = strchr(s, '\n');
};
auto recordLoc = [&]() {
if (debug) dump("loc");
size_t fileId = getInt();
skipWhitespace();
- size_t row = getInt();
+ uint32_t row = getInt();
skipWhitespace();
- size_t column = getInt();
- WASM_UNUSED(fileId); WASM_UNUSED(row); WASM_UNUSED(column); // TODO: use the fileId, row and column
+ uint32_t column = getInt();
+ auto iter = fileIndexMap.find(fileId);
+ if (iter == fileIndexMap.end()) {
+ abort_on("idx");
+ }
+ useDebugLocation = true;
+ debugLocation = { iter->second, row, column };
s = strchr(s, '\n');
};
auto recordLabel = [&]() {
@@ -746,7 +758,10 @@ class S2WasmBuilder {
// parse body
func->body = allocator->alloc<Block>();
std::vector<Expression*> bstack;
- auto addToBlock = [&bstack](Expression* curr) {
+ auto addToBlock = [&](Expression* curr) {
+ if (useDebugLocation) {
+ func->debugLocations[curr] = debugLocation;
+ }
Expression* last = bstack.back();
if (last->is<Loop>()) {
last = last->cast<Loop>()->body;
diff --git a/src/tools/asm2wasm.cpp b/src/tools/asm2wasm.cpp
index cf0715198..3fa97d981 100644
--- a/src/tools/asm2wasm.cpp
+++ b/src/tools/asm2wasm.cpp
@@ -36,6 +36,8 @@ int main(int argc, const char *argv[]) {
bool legalizeJavaScriptFFI = true;
Asm2WasmBuilder::TrapMode trapMode = Asm2WasmBuilder::TrapMode::JS;
bool wasmOnly = false;
+ std::string sourceMapFilename;
+ std::string sourceMapUrl;
std::string symbolMap;
bool emitBinary = true;
@@ -99,9 +101,15 @@ int main(int argc, const char *argv[]) {
[&legalizeJavaScriptFFI](Options *o, const std::string &) {
legalizeJavaScriptFFI = false;
})
- .add("--debuginfo", "-g", "Emit names section and debug info (for debug info you must emit text, -S, for this to work)",
+ .add("--debuginfo", "-g", "Emit names section in wasm binary (or full debuginfo in wast)",
Options::Arguments::Zero,
[&](Options *o, const std::string &arguments) { options.passOptions.debugInfo = true; })
+ .add("--source-map", "-sm", "Emit source map (if using binary output) to the specified file",
+ Options::Arguments::One,
+ [&sourceMapFilename](Options *o, const std::string &argument) { sourceMapFilename = argument; })
+ .add("--source-map-url", "-su", "Use specified string as source map URL",
+ Options::Arguments::One,
+ [&sourceMapUrl](Options *o, const std::string &argument) { sourceMapUrl = argument; })
.add("--symbolmap", "-s", "Emit a symbol map (indexes => names)",
Options::Arguments::One,
[&](Options *o, const std::string &argument) { symbolMap = argument; })
@@ -136,8 +144,9 @@ int main(int argc, const char *argv[]) {
}
Asm2WasmPreProcessor pre;
- // wasm binaries can contain a names section, but not full debug info
- pre.debugInfo = options.passOptions.debugInfo && !emitBinary;
+ // wasm binaries can contain a names section, but not full debug info --
+ // debug info is disabled if a map file is not specified with wasm binary
+ pre.debugInfo = options.passOptions.debugInfo && (!emitBinary || sourceMapFilename.size());
auto input(
read_file<std::vector<char>>(options.extra["infile"], Flags::Text, options.debug ? Flags::Debug : Flags::Release));
char *start = pre.process(input.data());
@@ -204,6 +213,10 @@ int main(int argc, const char *argv[]) {
writer.setDebugInfo(options.passOptions.debugInfo);
writer.setSymbolMap(symbolMap);
writer.setBinary(emitBinary);
+ if (emitBinary) {
+ writer.setSourceMapFilename(sourceMapFilename);
+ writer.setSourceMapUrl(sourceMapUrl);
+ }
writer.write(wasm, options.extra["output"]);
if (options.debug) std::cerr << "done." << std::endl;
diff --git a/src/tools/wasm-as.cpp b/src/tools/wasm-as.cpp
index 067c0f28d..e8003a5ca 100644
--- a/src/tools/wasm-as.cpp
+++ b/src/tools/wasm-as.cpp
@@ -30,6 +30,8 @@ using namespace wasm;
int main(int argc, const char *argv[]) {
bool debugInfo = false;
std::string symbolMap;
+ std::string sourceMapFilename;
+ std::string sourceMapUrl;
Options options("wasm-as", "Assemble a .wast (WebAssembly text format) into a .wasm (WebAssembly binary format)");
options.extra["validate"] = "wasm";
options
@@ -51,6 +53,12 @@ int main(int argc, const char *argv[]) {
.add("--debuginfo", "-g", "Emit names section and debug info",
Options::Arguments::Zero,
[&](Options *o, const std::string &arguments) { debugInfo = true; })
+ .add("--source-map", "-sm", "Emit source map to the specified file",
+ Options::Arguments::One,
+ [&sourceMapFilename](Options *o, const std::string &argument) { sourceMapFilename = argument; })
+ .add("--source-map-url", "-su", "Use specified string as source map URL",
+ Options::Arguments::One,
+ [&sourceMapUrl](Options *o, const std::string &argument) { sourceMapUrl = argument; })
.add("--symbolmap", "-s", "Emit a symbol map (indexes => names)",
Options::Arguments::One,
[&](Options *o, const std::string &argument) { symbolMap = argument; })
@@ -86,13 +94,23 @@ int main(int argc, const char *argv[]) {
if (options.debug) std::cerr << "binarification..." << std::endl;
BufferWithRandomAccess buffer(options.debug);
WasmBinaryWriter writer(&wasm, buffer, options.debug);
- writer.setDebugInfo(debugInfo);
+ // if debug info is used, then we want to emit the names section
+ writer.setNamesSection(debugInfo);
+ std::unique_ptr<std::ofstream> sourceMapStream = nullptr;
+ if (sourceMapFilename.size()) {
+ sourceMapStream = make_unique<std::ofstream>();
+ sourceMapStream->open(sourceMapFilename);
+ writer.setSourceMap(sourceMapStream.get(), sourceMapUrl);
+ }
if (symbolMap.size() > 0) writer.setSymbolMap(symbolMap);
writer.write();
if (options.debug) std::cerr << "writing to output..." << std::endl;
Output output(options.extra["output"], Flags::Binary, options.debug ? Flags::Debug : Flags::Release);
buffer.writeTo(output);
+ if (sourceMapStream) {
+ sourceMapStream->close();
+ }
if (options.debug) std::cerr << "Done." << std::endl;
}
diff --git a/src/tools/wasm-dis.cpp b/src/tools/wasm-dis.cpp
index 93c286913..e6fd7badd 100644
--- a/src/tools/wasm-dis.cpp
+++ b/src/tools/wasm-dis.cpp
@@ -28,6 +28,7 @@ using namespace cashew;
using namespace wasm;
int main(int argc, const char *argv[]) {
+ std::string sourceMapFilename;
Options options("wasm-dis", "Un-assemble a .wasm (WebAssembly binary format) into a .wast (WebAssembly text format)");
options.add("--output", "-o", "Output file (stdout if not specified)",
Options::Arguments::One,
@@ -35,6 +36,9 @@ int main(int argc, const char *argv[]) {
o->extra["output"] = argument;
Colors::disable();
})
+ .add("--source-map", "-sm", "Consume source map from the specified file to add location information",
+ Options::Arguments::One,
+ [&sourceMapFilename](Options *o, const std::string &argument) { sourceMapFilename = argument; })
.add_positional("INFILE", Options::Arguments::One,
[](Options *o, const std::string &argument) {
o->extra["infile"] = argument;
@@ -46,11 +50,23 @@ int main(int argc, const char *argv[]) {
if (options.debug) std::cerr << "parsing binary..." << std::endl;
Module wasm;
try {
+ std::unique_ptr<std::ifstream> sourceMapStream;
WasmBinaryBuilder parser(wasm, input, options.debug);
+ if (sourceMapFilename.size()) {
+ sourceMapStream = make_unique<std::ifstream>();
+ sourceMapStream->open(sourceMapFilename);
+ parser.setDebugLocations(sourceMapStream.get());
+ }
parser.read();
+ if (sourceMapStream) {
+ sourceMapStream->close();
+ }
} catch (ParseException& p) {
p.dump(std::cerr);
Fatal() << "error in parsing wasm binary";
+ } catch (MapParseException& p) {
+ p.dump(std::cerr);
+ Fatal() << "error in parsing wasm source mapping";
}
if (options.debug) std::cerr << "Printing..." << std::endl;
diff --git a/src/wasm-binary.h b/src/wasm-binary.h
index 921f90ac8..889a3aa6c 100644
--- a/src/wasm-binary.h
+++ b/src/wasm-binary.h
@@ -308,6 +308,7 @@ enum EncodedType {
namespace UserSections {
extern const char* Name;
+extern const char* SourceMapUrl;
enum Subsection {
NameFunction = 1,
@@ -534,8 +535,11 @@ inline S32LEB binaryWasmType(WasmType type) {
class WasmBinaryWriter : public Visitor<WasmBinaryWriter, void> {
Module* wasm;
BufferWithRandomAccess& o;
+ Function* currFunction = nullptr;
bool debug;
bool debugInfo = true;
+ std::ostream* sourceMap = nullptr;
+ std::string sourceMapUrl;
std::string symbolMap;
MixedArena allocator;
@@ -546,7 +550,11 @@ public:
prepare();
}
- void setDebugInfo(bool set) { debugInfo = set; }
+ void setNamesSection(bool set) { debugInfo = set; }
+ void setSourceMap(std::ostream* set, std::string url) {
+ sourceMap = set;
+ sourceMapUrl = url;
+ }
void setSymbolMap(std::string set) { symbolMap = set; }
void write();
@@ -582,8 +590,13 @@ public:
void writeFunctionTableDeclaration();
void writeTableElements();
void writeNames();
+ void writeSourceMapUrl();
void writeSymbolMap();
+ void writeSourceMapProlog();
+ void writeSourceMapEpilog();
+ void writeDebugLocation(size_t offset, const Function::DebugLocation& loc);
+
// helpers
void writeInlineString(const char* name);
void writeInlineBuffer(const char* data, size_t size);
@@ -606,6 +619,20 @@ public:
void recurse(Expression*& curr);
std::vector<Name> breakStack;
+ Function::DebugLocation lastDebugLocation;
+ size_t lastBytecodeOffset;
+
+ void visit(Expression* curr) {
+ if (sourceMap && currFunction) {
+ // Dump the sourceMap debug info
+ auto& debugLocations = currFunction->debugLocations;
+ auto iter = debugLocations.find(curr);
+ if (iter != debugLocations.end() && iter->second != lastDebugLocation) {
+ writeDebugLocation(o.size(), iter->second);
+ }
+ }
+ Visitor<WasmBinaryWriter>::visit(curr);
+ }
void visitBlock(Block *curr);
// emits a node, but if it is a block with no name, emit a list of its contents
@@ -641,14 +668,17 @@ class WasmBinaryBuilder {
MixedArena& allocator;
std::vector<char>& input;
bool debug;
+ std::istream* sourceMap;
+ std::pair<uint32_t, Function::DebugLocation> nextDebugLocation;
size_t pos = 0;
Index startIndex = -1;
+ bool useDebugLocation;
std::set<BinaryConsts::Section> seenSections;
public:
- WasmBinaryBuilder(Module& wasm, std::vector<char>& input, bool debug) : wasm(wasm), allocator(wasm.allocator), input(input), debug(debug) {}
+ WasmBinaryBuilder(Module& wasm, std::vector<char>& input, bool debug) : wasm(wasm), allocator(wasm.allocator), input(input), debug(debug), sourceMap(nullptr), nextDebugLocation(0, { 0, 0, 0 }), useDebugLocation(false) {}
void read();
void readUserSection(size_t payloadLen);
@@ -737,6 +767,15 @@ public:
void readTableElements();
void readNames(size_t);
+ // Debug information reading helpers
+ void setDebugLocations(std::istream* sourceMap_) {
+ sourceMap = sourceMap_;
+ }
+ Function::DebugLocation debugLocation;
+ std::unordered_map<std::string, Index> debugInfoFileIndices;
+ void readNextDebugLocation();
+ void readSourceMapHeader();
+
// AST reading
int depth = 0; // only for debugging
diff --git a/src/wasm-io.h b/src/wasm-io.h
index 803cbaf90..afdc4503c 100644
--- a/src/wasm-io.h
+++ b/src/wasm-io.h
@@ -48,11 +48,15 @@ class ModuleWriter : public ModuleIO {
bool binary = true;
bool debugInfo = false;
std::string symbolMap;
+ std::string sourceMapFilename;
+ std::string sourceMapUrl;
public:
void setBinary(bool binary_) { binary = binary_; }
void setDebugInfo(bool debugInfo_) { debugInfo = debugInfo_; }
void setSymbolMap(std::string symbolMap_) { symbolMap = symbolMap_; }
+ void setSourceMapFilename(std::string sourceMapFilename_) { sourceMapFilename = sourceMapFilename_; }
+ void setSourceMapUrl(std::string sourceMapUrl_) { sourceMapUrl = sourceMapUrl_; }
// write text
void writeText(Module& wasm, std::string filename);
diff --git a/src/wasm-s-parser.h b/src/wasm-s-parser.h
index be1f6b699..b02b6523b 100644
--- a/src/wasm-s-parser.h
+++ b/src/wasm-s-parser.h
@@ -28,6 +28,16 @@
namespace wasm {
+class SourceLocation
+{
+public:
+ cashew::IString filename;
+ uint32_t line;
+ uint32_t column;
+ SourceLocation(cashew::IString filename_, uint32_t line_, uint32_t column_ = 0)
+ : filename(filename_), line(line_), column(column_) {}
+};
+
//
// An element in an S-Expression: a list or a string
//
@@ -41,7 +51,7 @@ class Element {
bool quoted_;
public:
- Element(MixedArena& allocator) : isList_(true), list_(allocator), line(-1), col(-1) {}
+ Element(MixedArena& allocator) : isList_(true), list_(allocator), line(-1), col(-1), loc(nullptr) {}
bool isList() { return isList_; }
bool isStr() { return !isList_; }
@@ -49,6 +59,7 @@ public:
bool quoted() { return isStr() && quoted_; }
size_t line, col;
+ SourceLocation* loc;
// list methods
List& list();
@@ -61,14 +72,13 @@ public:
cashew::IString str();
const char* c_str();
Element* setString(cashew::IString str__, bool dollared__, bool quoted__);
- Element* setMetadata(size_t line_, size_t col_);
+ Element* setMetadata(size_t line_, size_t col_, SourceLocation* loc_);
// printing
friend std::ostream& operator<<(std::ostream& o, Element& e);
void dump();
};
-
//
// Generic S-Expression parsing into lists
//
@@ -76,6 +86,7 @@ class SExpressionParser {
char* input;
size_t line;
char* lineStart;
+ SourceLocation* loc;
MixedArena allocator;
@@ -87,6 +98,7 @@ public:
private:
Element* parse();
void skipWhitespace();
+ void parseDebugLocation();
Element* parseString();
};
@@ -102,6 +114,7 @@ class SExpressionWasmBuilder {
int functionCounter;
int globalCounter;
std::map<Name, WasmType> functionTypes; // we need to know function return types before we parse their contents
+ std::unordered_map<cashew::IString, Index> debugInfoFileIndices;
public:
// Assumes control of and modifies the input.
@@ -147,6 +160,7 @@ public:
Expression* parseExpression(Element& s);
private:
+ Expression* makeExpression(Element& s);
Expression* makeBinary(Element& s, BinaryOp op, WasmType type);
Expression* makeUnary(Element& s, UnaryOp op, WasmType type);
Expression* makeSelect(Element& s);
diff --git a/src/wasm.h b/src/wasm.h
index 3caab9dbc..dba08d92e 100644
--- a/src/wasm.h
+++ b/src/wasm.h
@@ -527,7 +527,9 @@ public:
std::map<Name, Index> localIndices;
struct DebugLocation {
- uint32_t fileIndex, lineNumber;
+ uint32_t fileIndex, lineNumber, columnNumber;
+ bool operator==(const DebugLocation& other) const { return fileIndex == other.fileIndex && lineNumber == other.lineNumber && columnNumber == other.columnNumber; }
+ bool operator!=(const DebugLocation& other) const { return !(*this == other); }
};
std::unordered_map<Expression*, DebugLocation> debugLocations;
diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp
index 65c6f2a21..c4bc66f76 100644
--- a/src/wasm/wasm-binary.cpp
+++ b/src/wasm/wasm-binary.cpp
@@ -33,6 +33,9 @@ void WasmBinaryWriter::prepare() {
void WasmBinaryWriter::write() {
writeHeader();
+ if (sourceMap) {
+ writeSourceMapProlog();
+ }
writeTypes();
writeImports();
@@ -46,8 +49,12 @@ void WasmBinaryWriter::write() {
writeFunctions();
writeDataSegments();
if (debugInfo) writeNames();
+ if (sourceMap) writeSourceMapUrl();
if (symbolMap.size() > 0) writeSymbolMap();
+ if (sourceMap) {
+ writeSourceMapEpilog();
+ }
finishUp();
}
@@ -236,6 +243,7 @@ void WasmBinaryWriter::writeFunctions() {
size_t sizePos = writeU32LEBPlaceholder();
size_t start = o.size();
Function* function = wasm->functions[i].get();
+ currFunction = function;
mappedLocals.clear();
numLocalsByType.clear();
if (debug) std::cerr << "writing" << function->name << std::endl;
@@ -258,6 +266,7 @@ void WasmBinaryWriter::writeFunctions() {
if (debug) std::cerr << "body size: " << size << ", writing at " << sizePos << ", next starts at " << o.size() << std::endl;
o.writeAt(sizePos, U32LEB(size));
}
+ currFunction = nullptr;
finishSection(start);
}
@@ -420,6 +429,14 @@ void WasmBinaryWriter::writeNames() {
finishSection(start);
}
+void WasmBinaryWriter::writeSourceMapUrl() {
+ if (debug) std::cerr << "== writeSourceMapUrl" << std::endl;
+ auto start = startSection(BinaryConsts::Section::User);
+ writeInlineString(BinaryConsts::UserSections::SourceMapUrl);
+ writeInlineString(sourceMapUrl.c_str());
+ finishSection(start);
+}
+
void WasmBinaryWriter::writeSymbolMap() {
std::ofstream file(symbolMap);
for (auto& import : wasm->imports) {
@@ -433,6 +450,50 @@ void WasmBinaryWriter::writeSymbolMap() {
file.close();
}
+void WasmBinaryWriter::writeSourceMapProlog() {
+ lastDebugLocation = { 0, /* lineNumber = */ 1, 0 };
+ lastBytecodeOffset = 0;
+ *sourceMap << "{\"version\":3,\"sources\":[";
+ for (size_t i = 0; i < wasm->debugInfoFileNames.size(); i++) {
+ if (i > 0) *sourceMap << ",";
+ // TODO respect JSON string encoding, e.g. quotes and control chars.
+ *sourceMap << "\"" << wasm->debugInfoFileNames[i] << "\"";
+ }
+ *sourceMap << "],\"names\":[],\"mappings\":\"";
+}
+
+void WasmBinaryWriter::writeSourceMapEpilog() {
+ *sourceMap << "\"}";
+}
+
+static void writeBase64VLQ(std::ostream& out, int32_t n) {
+ uint32_t value = n >= 0 ? n << 1 : ((-n) << 1) | 1;
+ while (1) {
+ uint32_t digit = value & 0x1F;
+ value >>= 5;
+ if (!value) {
+ // last VLQ digit -- base64 codes 'A'..'Z', 'a'..'f'
+ out << char(digit < 26 ? 'A' + digit : 'a' + digit - 26);
+ break;
+ }
+ // more VLG digit will follow -- add continuation bit (0x20),
+ // base64 codes 'g'..'z', '0'..'9', '+', '/'
+ out << char(digit < 20 ? 'g' + digit : digit < 30 ? '0' + digit - 20 : digit == 30 ? '+' : '/');
+ }
+}
+
+void WasmBinaryWriter::writeDebugLocation(size_t offset, const Function::DebugLocation& loc) {
+ if (lastBytecodeOffset > 0) {
+ *sourceMap << ",";
+ }
+ writeBase64VLQ(*sourceMap, int32_t(offset - lastBytecodeOffset));
+ writeBase64VLQ(*sourceMap, int32_t(loc.fileIndex - lastDebugLocation.fileIndex));
+ writeBase64VLQ(*sourceMap, int32_t(loc.lineNumber - lastDebugLocation.lineNumber));
+ writeBase64VLQ(*sourceMap, int32_t(loc.columnNumber - lastDebugLocation.columnNumber));
+ lastDebugLocation = loc;
+ lastBytecodeOffset = offset;
+}
+
void WasmBinaryWriter::writeInlineString(const char* name) {
int32_t size = strlen(name);
o << U32LEB(size);
@@ -937,6 +998,7 @@ static Name RETURN_BREAK("binaryen|break-to-return");
void WasmBinaryBuilder::read() {
readHeader();
+ readSourceMapHeader();
// read sections until the end
while (more()) {
@@ -1342,6 +1404,7 @@ void WasmBinaryBuilder::readFunctions() {
// process the function body
if (debug) std::cerr << "processing function: " << i << std::endl;
nextLabel = 0;
+ useDebugLocation = false;
breaksToReturn = false;
// process body
assert(breakStack.empty());
@@ -1389,6 +1452,136 @@ void WasmBinaryBuilder::readExports() {
}
}
+static int32_t readBase64VLQ(std::istream& in) {
+ uint32_t value = 0;
+ uint32_t shift = 0;
+ while (1) {
+ char ch = in.get();
+ if (ch == EOF)
+ throw MapParseException("unexpected EOF in the middle of VLQ");
+ if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch < 'g')) {
+ // last number digit
+ uint32_t digit = ch < 'a' ? ch - 'A' : ch - 'a' + 26;
+ value |= digit << shift;
+ break;
+ }
+ if (!(ch >= 'g' && ch <= 'z') && !(ch >= '0' && ch <= '9') &&
+ ch != '+' && ch != '/') {
+ throw MapParseException("invalid VLQ digit");
+ }
+ uint32_t digit = ch > '9' ? ch - 'g' : (ch >= '0' ? ch - '0' + 20 : (ch == '+' ? 30 : 31));
+ value |= digit << shift;
+ shift += 5;
+ }
+ return value & 1 ? -int32_t(value >> 1) : int32_t(value >> 1);
+}
+
+void WasmBinaryBuilder::readSourceMapHeader() {
+ if (!sourceMap) return;
+
+ auto maybeReadChar = [&](char expected) {
+ if (sourceMap->peek() != expected) return false;
+ sourceMap->get();
+ return true;
+ };
+ auto mustReadChar = [&](char expected) {
+ if (sourceMap->get() != expected) {
+ throw MapParseException("Unexpected char");
+ }
+ };
+ auto findField = [&](const char* name, size_t len) {
+ bool matching = false;
+ size_t pos;
+ while (1) {
+ int ch = sourceMap->get();
+ if (ch == EOF) return false;
+ if (ch == '\"') {
+ matching = true;
+ pos = 0;
+ } else if (matching && name[pos] == ch) {
+ ++pos;
+ if (pos == len) {
+ if (maybeReadChar('\"')) break; // found field
+ }
+ } else {
+ matching = false;
+ }
+ }
+ mustReadChar(':');
+ return true;
+ };
+ auto readString = [&](std::string& str) {
+ std::vector<char> vec;
+ mustReadChar('\"');
+ if (!maybeReadChar('\"')) {
+ while (1) {
+ int ch = sourceMap->get();
+ if (ch == EOF) {
+ throw MapParseException("unexpected EOF in the middle of string");
+ }
+ if (ch == '\"') break;
+ vec.push_back(ch);
+ }
+ }
+ str = std::string(vec.begin(), vec.end());
+ };
+
+ if (!findField("sources", strlen("sources"))) {
+ throw MapParseException("cannot find the sources field in map");
+ }
+ mustReadChar('[');
+ if (!maybeReadChar(']')) {
+ do {
+ std::string file;
+ readString(file);
+ Index index = wasm.debugInfoFileNames.size();
+ wasm.debugInfoFileNames.push_back(file);
+ debugInfoFileIndices[file] = index;
+ } while (maybeReadChar(','));
+ mustReadChar(']');
+ }
+
+ if (!findField("mappings", strlen("mappings"))) {
+ throw MapParseException("cannot find the mappings field in map");
+ }
+ mustReadChar('\"');
+ if (maybeReadChar('\"')) { // empty mappings
+ nextDebugLocation.first = 0;
+ return;
+ }
+ // read first debug location
+ uint32_t position = readBase64VLQ(*sourceMap);
+ uint32_t fileIndex = readBase64VLQ(*sourceMap);
+ uint32_t lineNumber = readBase64VLQ(*sourceMap) + 1; // adjust zero-based line number
+ uint32_t columnNumber = readBase64VLQ(*sourceMap);
+ nextDebugLocation = { position, { fileIndex, lineNumber, columnNumber } };
+}
+
+void WasmBinaryBuilder::readNextDebugLocation() {
+ if (!sourceMap) return;
+
+ char ch;
+ *sourceMap >> ch;
+ if (ch == '\"') { // end of records
+ nextDebugLocation.first = 0;
+ return;
+ }
+ if (ch != ',') {
+ throw MapParseException("Unexpected delimiter");
+ }
+
+ int32_t positionDelta = readBase64VLQ(*sourceMap);
+ uint32_t position = nextDebugLocation.first + positionDelta;
+ int32_t fileIndexDelta = readBase64VLQ(*sourceMap);
+ uint32_t fileIndex = nextDebugLocation.second.fileIndex + fileIndexDelta;
+ int32_t lineNumberDelta = readBase64VLQ(*sourceMap);
+ uint32_t lineNumber = nextDebugLocation.second.lineNumber + lineNumberDelta;
+ int32_t columnNumberDelta = readBase64VLQ(*sourceMap);
+ uint32_t columnNumber = nextDebugLocation.second.columnNumber + columnNumberDelta;
+
+ nextDebugLocation = { position, { fileIndex, lineNumber, columnNumber } };
+}
+
Expression* WasmBinaryBuilder::readExpression() {
assert(depth == 0);
processExpressions();
@@ -1627,6 +1820,16 @@ BinaryConsts::ASTNodes WasmBinaryBuilder::readExpression(Expression*& curr) {
throw ParseException("Reached function end without seeing End opcode");
}
if (debug) std::cerr << "zz recurse into " << ++depth << " at " << pos << std::endl;
+ if (nextDebugLocation.first) {
+ while (nextDebugLocation.first && nextDebugLocation.first <= pos) {
+ if (nextDebugLocation.first < pos) {
+ std::cerr << "skipping debug location info for " << nextDebugLocation.first << std::endl;
+ }
+ debugLocation = nextDebugLocation.second;
+ useDebugLocation = currFunction; // using only for function expressions
+ readNextDebugLocation();
+ }
+ }
uint8_t code = getInt8();
if (debug) std::cerr << "readExpression seeing " << (int)code << std::endl;
switch (code) {
@@ -1661,6 +1864,9 @@ BinaryConsts::ASTNodes WasmBinaryBuilder::readExpression(Expression*& curr) {
throw ParseException("bad node code " + std::to_string(code));
}
}
+ if (useDebugLocation && curr) {
+ currFunction->debugLocations[curr] = debugLocation;
+ }
if (debug) std::cerr << "zz recurse from " << depth-- << " at " << pos << std::endl;
return BinaryConsts::ASTNodes(code);
}
diff --git a/src/wasm/wasm-io.cpp b/src/wasm/wasm-io.cpp
index c3192efbf..ebc9af8de 100644
--- a/src/wasm/wasm-io.cpp
+++ b/src/wasm/wasm-io.cpp
@@ -72,11 +72,21 @@ void ModuleWriter::writeBinary(Module& wasm, std::string filename) {
if (debug) std::cerr << "writing binary to " << filename << "\n";
BufferWithRandomAccess buffer(debug);
WasmBinaryWriter writer(&wasm, buffer, debug);
- writer.setDebugInfo(debugInfo);
+ // if debug info is used, then we want to emit the names section
+ writer.setNamesSection(debugInfo);
+ std::unique_ptr<std::ofstream> sourceMapStream;
+ if (sourceMapFilename.size()) {
+ sourceMapStream = make_unique<std::ofstream>();
+ sourceMapStream->open(sourceMapFilename);
+ writer.setSourceMap(sourceMapStream.get(), sourceMapUrl);
+ }
if (symbolMap.size() > 0) writer.setSymbolMap(symbolMap);
writer.write();
Output output(filename, Flags::Binary, debug ? Flags::Debug : Flags::Release);
buffer.writeTo(output);
+ if (sourceMapStream) {
+ sourceMapStream->close();
+ }
}
void ModuleWriter::write(Module& wasm, std::string filename) {
@@ -88,4 +98,3 @@ void ModuleWriter::write(Module& wasm, std::string filename) {
}
}
-
diff --git a/src/wasm/wasm-s-parser.cpp b/src/wasm/wasm-s-parser.cpp
index 9b053d5b8..1842237a5 100644
--- a/src/wasm/wasm-s-parser.cpp
+++ b/src/wasm/wasm-s-parser.cpp
@@ -71,9 +71,10 @@ Element* Element::setString(IString str__, bool dollared__, bool quoted__) {
return this;
}
-Element* Element::setMetadata(size_t line_, size_t col_) {
+Element* Element::setMetadata(size_t line_, size_t col_, SourceLocation* loc_) {
line = line_;
col = col_;
+ loc = loc_;
return this;
}
@@ -93,7 +94,7 @@ void Element::dump() {
}
-SExpressionParser::SExpressionParser(char* input) : input(input) {
+SExpressionParser::SExpressionParser(char* input) : input(input), loc(nullptr) {
root = nullptr;
line = 1;
lineStart = input;
@@ -104,6 +105,7 @@ SExpressionParser::SExpressionParser(char* input) : input(input) {
Element* SExpressionParser::parse() {
std::vector<Element *> stack;
+ std::vector<SourceLocation*> stackLocs;
Element *curr = allocator.alloc<Element>();
while (1) {
skipWhitespace();
@@ -111,7 +113,9 @@ Element* SExpressionParser::parse() {
if (input[0] == '(') {
input++;
stack.push_back(curr);
- curr = allocator.alloc<Element>()->setMetadata(line, input - lineStart - 1);
+ curr = allocator.alloc<Element>()->setMetadata(line, input - lineStart - 1, loc);
+ stackLocs.push_back(loc);
+ assert(stack.size() == stackLocs.size());
} else if (input[0] == ')') {
input++;
auto last = curr;
@@ -119,7 +123,10 @@ Element* SExpressionParser::parse() {
throw ParseException("s-expr stack empty");
}
curr = stack.back();
+ assert(stack.size() == stackLocs.size());
stack.pop_back();
+ loc = stackLocs.back();
+ stackLocs.pop_back();
curr->list().push_back(last);
} else {
curr->list().push_back(parseString());
@@ -129,6 +136,29 @@ Element* SExpressionParser::parse() {
return curr;
}
+void SExpressionParser::parseDebugLocation() {
+ // Extracting debug location (if valid)
+ char* debugLoc = input + 3; // skipping ";;@"
+ while (debugLoc[0] && debugLoc[0] == ' ') debugLoc++;
+ char* debugLocEnd = debugLoc;
+ while (debugLocEnd[0] && debugLocEnd[0] != '\n') debugLocEnd++;
+ char* pos = debugLoc;
+ while (pos < debugLocEnd && pos[0] != ':') pos++;
+ if (pos >= debugLocEnd) {
+ return; // no line number
+ }
+ std::string name(debugLoc, pos);
+ char* lineStart = ++pos;
+ while (pos < debugLocEnd && pos[0] != ':') pos++;
+ std::string lineStr(lineStart, pos);
+ if (pos >= debugLocEnd) {
+ return; // no column number
+ }
+ std::string colStr(++pos, debugLocEnd);
+ void* buf = allocator.allocSpace(sizeof(SourceLocation));
+ loc = new (buf) SourceLocation(IString(name.c_str(), false), atoi(lineStr.c_str()), atoi(colStr.c_str()));
+}
+
void SExpressionParser::skipWhitespace() {
while (1) {
while (isspace(input[0])) {
@@ -139,6 +169,9 @@ void SExpressionParser::skipWhitespace() {
input++;
}
if (input[0] == ';' && input[1] == ';') {
+ if (input[2] == '@') {
+ parseDebugLocation();
+ }
while (input[0] && input[0] != '\n') input++;
line++;
lineStart = ++input;
@@ -198,13 +231,13 @@ Element* SExpressionParser::parseString() {
input++;
}
input++;
- return allocator.alloc<Element>()->setString(IString(str.c_str(), false), dollared, true)->setMetadata(line, start - lineStart);
+ return allocator.alloc<Element>()->setString(IString(str.c_str(), false), dollared, true)->setMetadata(line, start - lineStart, loc);
}
while (input[0] && !isspace(input[0]) && input[0] != ')' && input[0] != '(' && input[0] != ';') input++;
if (start == input) throw ParseException("expected string", line, input - lineStart);
char temp = input[0];
input[0] = 0;
- auto ret = allocator.alloc<Element>()->setString(IString(start, false), dollared, false)->setMetadata(line, start - lineStart);
+ auto ret = allocator.alloc<Element>()->setString(IString(start, false), dollared, false)->setMetadata(line, start - lineStart, loc);
input[0] = temp;
return ret;
}
@@ -583,6 +616,23 @@ WasmType SExpressionWasmBuilder::stringToWasmType(const char* str, bool allowErr
}
Expression* SExpressionWasmBuilder::parseExpression(Element& s) {
+ Expression* result = makeExpression(s);
+ if (s.loc) {
+ IString file = s.loc->filename;
+ auto& debugInfoFileNames = wasm.debugInfoFileNames;
+ auto iter = debugInfoFileIndices.find(file);
+ if (iter == debugInfoFileIndices.end()) {
+ Index index = debugInfoFileNames.size();
+ debugInfoFileNames.push_back(file.c_str());
+ debugInfoFileIndices[file] = index;
+ }
+ uint32_t fileIndex = debugInfoFileIndices[file];
+ currFunction->debugLocations[result] = {fileIndex, s.loc->line, s.loc->column};
+ }
+ return result;
+}
+
+Expression* SExpressionWasmBuilder::makeExpression(Element& s) {
IString id = s[0]->str();
const char *str = id.str;
const char *dot = strchr(str, '.');
diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp
index 834d9e28f..d2bf4e75c 100644
--- a/src/wasm/wasm.cpp
+++ b/src/wasm/wasm.cpp
@@ -28,6 +28,7 @@ Name WASM("wasm"),
namespace BinaryConsts {
namespace UserSections {
const char* Name = "name";
+const char* SourceMapUrl = "sourceMappingURL";
}
}