diff options
Diffstat (limited to 'src/wasm')
-rw-r--r-- | src/wasm/wasm-binary.cpp | 206 | ||||
-rw-r--r-- | src/wasm/wasm-io.cpp | 13 | ||||
-rw-r--r-- | src/wasm/wasm-s-parser.cpp | 60 | ||||
-rw-r--r-- | src/wasm/wasm.cpp | 1 |
4 files changed, 273 insertions, 7 deletions
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"; } } |