diff --git a/src/pass.h b/src/pass.h index bfbcb26d8d0..abfb836501a 100644 --- a/src/pass.h +++ b/src/pass.h @@ -167,14 +167,26 @@ class NameManager : public WalkerPass { std::ostream& o; unsigned indent = 0; + bool debugInfoComments = false; + size_t lastDebugLocationId = 0; + std::vector* functionDebugLocations = nullptr; + size_t lastLabelId = 0; + std::vector* functionLabels = nullptr; + bool minify; const char *maybeSpace; const char *maybeNewLine; @@ -49,6 +55,8 @@ struct PrintSExpression : public Visitor { void setFullAST(bool fullAST_) { fullAST = fullAST_; } + void setDebugInfoComments(bool debugInfoComments_) { debugInfoComments = debugInfoComments_; } + void incIndent() { if (minify) return; o << '\n'; @@ -78,6 +86,32 @@ struct PrintSExpression : public Visitor { return name; } + void visit(Expression *curr) { + if (debugInfoComments && curr->debugInfo.labelIndex != lastLabelId) { + lastLabelId = curr->debugInfo.labelIndex; + if (functionLabels) { // no labels -- skipping + const Name& labelName = (*functionLabels)[lastLabelId]; + o << "(;!bookmark " << labelName.str << ";)"; + o << maybeNewLine; + !minify && doIndent(o, indent); + } + } + if (debugInfoComments && curr->debugInfo.locationIndex != lastDebugLocationId) { + lastDebugLocationId = curr->debugInfo.locationIndex; + // skipping location id 0 or debug information is absent + if (functionDebugLocations && lastDebugLocationId) { + const DebugLocation& debugLocation = (*functionDebugLocations)[lastDebugLocationId]; + o << "(;!loc " << debugLocation.fileId << ' ' << debugLocation.row << ' ' << debugLocation.column << ";)"; + o << maybeNewLine; + !minify && doIndent(o, indent); + } + } + Visitor::visit(curr); + // keeping previously set location for next siblings + lastDebugLocationId = curr->debugInfo.locationIndex; + lastLabelId = curr->debugInfo.labelIndex; + } + void visitBlock(Block *curr) { // special-case Block, because Block nesting (in their first element) can be incredibly deep std::vector stack; @@ -506,6 +540,11 @@ struct PrintSExpression : public Visitor { void visitFunction(Function *curr) { currFunction = curr; printOpening(o, "func ", true) << curr->name; + functionDebugLocations = &(curr->debugLocations); + lastDebugLocationId = 0; + functionLabels = &(curr->labels); + lastLabelId = 0; + if (curr->type.is()) { o << maybeSpace << "(type " << curr->type << ')'; } @@ -525,6 +564,12 @@ struct PrintSExpression : public Visitor { printMinorOpening(o, "local ") << printableLocal(i) << ' ' << printWasmType(curr->getLocalType(i)) << ")"; o << maybeNewLine; } + if (debugInfoComments && functionLabels) { // no labels -- skipping + const Name& labelName = (*functionLabels)[0]; // first label is normally a function name + doIndent(o, indent); + o << "(;!bookmark " << labelName.str << ";)"; + o << maybeNewLine; + } // It is ok to emit a block here, as a function can directly contain a list, even if our // ast avoids that for simplicity. We can just do that optimization here.. if (!fullAST && curr->body->is() && curr->body->cast()->name.isNull()) { @@ -544,6 +589,35 @@ struct PrintSExpression : public Visitor { } o << ')'; } + void printDebugSections(Module *curr) { + if (!debugInfoComments) + return; + + bool hasSequences = false; + for (auto& child : curr->functions) { + if (child->labels.size() > 0) { + hasSequences = true; + } + } + if (hasSequences) { + doIndent(o, indent); + o << "(;!dbg_section .label_sequences\n"; + for (auto& child : curr->functions) { + o << "\t.function"; + for (auto item : child->labels) { + o << " " << item.str; + } + o << '\n'; + } + o << ";)"; + o << maybeNewLine; + } + for (auto& child : curr->debugSections) { + doIndent(o, indent); + o << "(;!dbg_section " << child.c_str() << ";)"; + o << maybeNewLine; + } + } void visitModule(Module *curr) { printOpening(o, "module", true); incIndent(); @@ -608,18 +682,31 @@ struct PrintSExpression : public Visitor { visitTable(&curr->table); o << maybeNewLine; } + if (debugInfoComments) { + for(auto const &item : curr->debugFileMap) { + doIndent(o, indent); + o << "(;!file " << item.first << " "; + printText(o, item.second.str); + o << ";)"; + o << maybeNewLine; + } + } for (auto& child : curr->functions) { doIndent(o, indent); visitFunction(child.get()); o << maybeNewLine; } + printDebugSections(curr); decIndent(); o << maybeNewLine; } }; void Printer::run(PassRunner* runner, Module* module) { - PrintSExpression print(o); + PrintSExpression print(args.o); + print.setMinify(args.minify); + print.setFullAST(args.fullAST); + print.setDebugInfoComments(args.debugInfo); print.visitModule(module); } @@ -629,14 +716,8 @@ static RegisterPass registerPass("print", "print in s-expression format class MinifiedPrinter : public Printer { public: - MinifiedPrinter() : Printer() {} - MinifiedPrinter(std::ostream& o) : Printer(o) {} - - void run(PassRunner* runner, Module* module) override { - PrintSExpression print(o); - print.setMinify(true); - print.visitModule(module); - } + MinifiedPrinter() : Printer(PrinterArgs(std::cout, true, false, false)) {} + MinifiedPrinter(std::ostream& o) : Printer(PrinterArgs(o, true, false, false)) {} }; static RegisterPass registerMinifyPass("print-minified", "print in minified s-expression format"); @@ -645,18 +726,21 @@ static RegisterPass registerMinifyPass("print-minified", "print class FullPrinter : public Printer { public: - FullPrinter() : Printer() {} - FullPrinter(std::ostream& o) : Printer(o) {} - - void run(PassRunner* runner, Module* module) override { - PrintSExpression print(o); - print.setFullAST(true); - print.visitModule(module); - } + FullPrinter() : Printer(PrinterArgs(std::cout, false, true, false)) {} + FullPrinter(std::ostream& o) : Printer(PrinterArgs(o, false, true, false)) {} }; static RegisterPass registerFullASTPass("print-full", "print in full s-expression format"); +// Prints out full ast module with additional debug info comments +class DebugInfoCommentsPrinter : public Printer { + public: + DebugInfoCommentsPrinter() : Printer(PrinterArgs(std::cout, false, true, true)) {} + DebugInfoCommentsPrinter(std::ostream& o) : Printer(PrinterArgs(o, false, true, true)) {} +}; + +static RegisterPass registerDebugInfoCommentsPass("print-debug-info", "print in full s-expression format with debug info comments"); + // Print individual expressions std::ostream& WasmPrinter::printExpression(Expression* expression, std::ostream& o, bool minify) { diff --git a/src/s2wasm.h b/src/s2wasm.h index 2228ae90006..a30c8dc3599 100644 --- a/src/s2wasm.h +++ b/src/s2wasm.h @@ -399,6 +399,7 @@ class S2WasmBuilder { else if (match("data")) {} else if (match("ident")) {} else if (match("section")) parseToplevelSection(); + else if (match("file")) parseFile(); else if (match("align") || match("p2align")) s = strchr(s, '\n'); else if (match("globl")) parseGlobl(); else abort_on("process"); @@ -408,9 +409,13 @@ class S2WasmBuilder { void parseToplevelSection() { auto section = getCommaSeparated(); // Skipping .debug_ sections - if (!strncmp(section.c_str(), ".debug_", strlen(".debug_"))) { + if (!strncmp(section.c_str(), ".debug_", strlen(".debug_")) || + !strncmp(section.c_str(), ".apple_", strlen(".apple_"))) { + s -= strlen(section.c_str()); // we want name as well const char *next = strstr(s, ".section"); - s = !next ? s + strlen(s) : next; + if (!next) next = s + strlen(s); + wasm->debugSections.emplace_back(s, next); + s = next; return; } // Initializers are anything in a section whose name begins with .init_array @@ -460,8 +465,8 @@ class S2WasmBuilder { size_t fileId = getInt(); skipWhitespace(); auto quoted = getQuoted(); - WASM_UNUSED(fileId); WASM_UNUSED(quoted); // TODO: use the fileId and quoted - s = strchr(s, '\n'); + Name filename(std::string(quoted.begin(), quoted.end())); + wasm->addDebugFile(fileId, filename); return; } // '.file' without first index argument points to bc-file @@ -500,13 +505,20 @@ class S2WasmBuilder { mustMatch(":"); + std::vector debugLocations; + debugLocations.emplace_back(); // reserve 0 slot + size_t currentDebugLocationIndex = 0; + std::vector labels; + labels.emplace_back(name); // reserve 0 slot for function name (which is a label too) + size_t currentLabelIndex = 0; + 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 - s = strchr(s, '\n'); + Name filename(std::string(quoted.begin(), quoted.end())); + wasm->addDebugFile(fileId, filename); }; auto recordLoc = [&]() { if (debug) dump("loc"); @@ -515,14 +527,16 @@ class S2WasmBuilder { size_t row = getInt(); skipWhitespace(); size_t column = getInt(); - WASM_UNUSED(fileId); WASM_UNUSED(row); WASM_UNUSED(column); // TODO: use the fileId, row and column + debugLocations.emplace_back(fileId, row, column); + currentDebugLocationIndex++; s = strchr(s, '\n'); }; auto recordLabel = [&]() { if (debug) dump("label"); Name label = getStrToSep(); + labels.emplace_back(label); + currentLabelIndex++; // TODO: track and create map of labels and their ranges for our AST - WASM_UNUSED(label); s = strchr(s, '\n'); }; @@ -620,6 +634,7 @@ class S2WasmBuilder { auto curr = allocator->alloc(); curr->index = func->getLocalIndex(getStrToSep()); curr->type = func->getLocalType(curr->index); + curr->setDebugInfo(currentDebugLocationIndex, currentLabelIndex); inputs[i] = curr; } else { abort_on("bad input register"); @@ -649,6 +664,7 @@ class S2WasmBuilder { set->index = func->getLocalIndex(assign); set->value = curr; set->type = curr->type; + set->setDebugInfo(currentDebugLocationIndex, currentLabelIndex); addToBlock(set); } }; @@ -678,6 +694,7 @@ class S2WasmBuilder { auto inputs = getInputs(2); curr->left = inputs[0]; curr->right = inputs[1]; + curr->setDebugInfo(currentDebugLocationIndex, currentLabelIndex); curr->finalize(); assert(curr->type == type); setOutput(curr, assign); @@ -689,6 +706,7 @@ class S2WasmBuilder { curr->op = op; curr->value = getInput(); curr->type = type; + curr->setDebugInfo(currentDebugLocationIndex, currentLabelIndex); curr->finalize(); setOutput(curr, assign); }; @@ -696,6 +714,7 @@ class S2WasmBuilder { Name assign = getAssign(); auto curr = allocator->alloc(); curr->op = op; + curr->setDebugInfo(currentDebugLocationIndex, currentLabelIndex); setOutput(curr, assign); }; auto makeHost1 = [&](HostOp op) { @@ -703,12 +722,14 @@ class S2WasmBuilder { auto curr = allocator->alloc(); curr->op = op; curr->operands.push_back(getInput()); + curr->setDebugInfo(currentDebugLocationIndex, currentLabelIndex); setOutput(curr, assign); }; auto makeLoad = [&](WasmType type) { skipComma(); auto curr = allocator->alloc(); curr->type = type; + curr->setDebugInfo(currentDebugLocationIndex, currentLabelIndex); int32_t bytes = getInt() / CHAR_BIT; curr->bytes = bytes > 0 ? bytes : getWasmTypeSize(type); curr->signed_ = match("_s"); @@ -729,6 +750,7 @@ class S2WasmBuilder { skipComma(); auto curr = allocator->alloc(); curr->type = type; + curr->setDebugInfo(currentDebugLocationIndex, currentLabelIndex); int32_t bytes = getInt() / CHAR_BIT; curr->bytes = bytes > 0 ? bytes : getWasmTypeSize(type); Name assign = getAssign(); @@ -755,6 +777,7 @@ class S2WasmBuilder { curr->condition = inputs[2]; assert(curr->condition->type == i32); curr->type = type; + curr->setDebugInfo(currentDebugLocationIndex, currentLabelIndex); setOutput(curr, assign); }; auto makeCall = [&](WasmType type) { @@ -769,6 +792,7 @@ class S2WasmBuilder { auto* funcType = ensureFunctionType(getSig(type, operands), wasm); assert(type == funcType->result); auto* indirect = builder.makeCallIndirect(funcType, target, std::move(operands)); + indirect->setDebugInfo(currentDebugLocationIndex, currentLabelIndex); setOutput(indirect, assign); } else { @@ -779,6 +803,7 @@ class S2WasmBuilder { Call* curr = allocator->alloc(); curr->target = target; curr->type = type; + curr->setDebugInfo(currentDebugLocationIndex, currentLabelIndex); if (!linkerObj->isFunctionImplemented(target)) { linkerObj->addUndefinedFunctionCall(curr); } @@ -813,6 +838,7 @@ class S2WasmBuilder { // may be a relocation auto curr = allocator->alloc(); curr->type = curr->value.type = i32; + curr->setDebugInfo(currentDebugLocationIndex, currentLabelIndex); getRelocatableConst((uint32_t*)curr->value.geti32Ptr()); setOutput(curr, assign); } else { @@ -975,6 +1001,7 @@ class S2WasmBuilder { } else if (match("block")) { auto curr = allocator->alloc(); curr->name = getNextLabel(); + curr->setDebugInfo(currentDebugLocationIndex, currentLabelIndex); addToBlock(curr); bstack.push_back(curr); } else if (match("end_block")) { @@ -992,9 +1019,11 @@ class S2WasmBuilder { addToBlock(curr); curr->in = getNextLabel(); curr->out = getNextLabel(); + curr->setDebugInfo(currentDebugLocationIndex, currentLabelIndex); auto block = allocator->alloc(); block->name = curr->out; // temporary, fake - this way, on bstack we have the right label at the right offset for a br curr->body = block; + curr->setDebugInfo(currentDebugLocationIndex, currentLabelIndex); loopBlocks.push_back(block); bstack.push_back(block); bstack.push_back(curr); @@ -1010,6 +1039,7 @@ class S2WasmBuilder { assert(curr->targets.size() > 0); curr->default_ = curr->targets.back(); curr->targets.pop_back(); + curr->setDebugInfo(currentDebugLocationIndex, currentLabelIndex); addToBlock(curr); } else if (match("br")) { auto curr = allocator->alloc(); @@ -1019,6 +1049,7 @@ class S2WasmBuilder { hasCondition = true; } curr->name = getBranchLabel(getInt()); + curr->setDebugInfo(currentDebugLocationIndex, currentLabelIndex); if (hasCondition) { skipComma(); curr->condition = getInput(); @@ -1038,11 +1069,16 @@ class S2WasmBuilder { skipComma(); curr->value = getInput(); curr->type = curr->value->type; + curr->setDebugInfo(currentDebugLocationIndex, currentLabelIndex); setOutput(curr, assign); } else if (match("return")) { - addToBlock(builder.makeReturn(*s == '$' ? getInput() : nullptr)); + auto curr = builder.makeReturn(*s == '$' ? getInput() : nullptr); + curr->setDebugInfo(currentDebugLocationIndex, currentLabelIndex); + addToBlock(curr); } else if (match("unreachable")) { - addToBlock(allocator->alloc()); + auto curr = allocator->alloc(); + curr->setDebugInfo(currentDebugLocationIndex, currentLabelIndex); + addToBlock(curr); } else if (match("current_memory")) { makeHost(CurrentMemory); } else if (match("grow_memory")) { @@ -1088,6 +1124,8 @@ class S2WasmBuilder { block->name = Name(); } func->body->dynCast()->finalize(); + func->debugLocations.swap(debugLocations); + func->labels.swap(labels); wasm->addFunction(func); } diff --git a/src/tools/s2wasm.cpp b/src/tools/s2wasm.cpp index a6bbdea181b..ae24aa08569 100644 --- a/src/tools/s2wasm.cpp +++ b/src/tools/s2wasm.cpp @@ -31,6 +31,7 @@ using namespace wasm; int main(int argc, const char *argv[]) { bool ignoreUnknownSymbols = false; bool generateEmscriptenGlue = false; + bool addDebugInfoComments = false; std::string startFunction; std::vector archiveLibraries; Options options("s2wasm", "Link .s file into .wast"); @@ -81,6 +82,11 @@ int main(int argc, const char *argv[]) { [&archiveLibraries](Options *o, const std::string &argument) { archiveLibraries.push_back(argument); }) + .add("--debug-info", "", "Add debug info comments to the AST", + Options::Arguments::Zero, + [&addDebugInfoComments](Options *, const std::string &) { + addDebugInfoComments = true; + }) .add_positional("INFILE", Options::Arguments::One, [](Options *o, const std::string &argument) { o->extra["infile"] = argument; @@ -133,7 +139,8 @@ int main(int argc, const char *argv[]) { if (options.debug) std::cerr << "Printing..." << std::endl; Output output(options.extra["output"], Flags::Text, options.debug ? Flags::Debug : Flags::Release); - WasmPrinter::printModule(&linker.getOutput().wasm, output.getStream()); + WasmPrinter::printModule(&linker.getOutput().wasm, PrinterArgs(output.getStream(), false, false, addDebugInfoComments)); + output << meta.str(); if (options.debug) std::cerr << "Done." << std::endl; diff --git a/src/wasm-printing.h b/src/wasm-printing.h index d1f1c42a5c2..805a1394780 100644 --- a/src/wasm-printing.h +++ b/src/wasm-printing.h @@ -32,6 +32,12 @@ struct WasmPrinter { return o; } + static void printModule(Module* module, const PrinterArgs& args) { + PassRunner passRunner(module); + passRunner.add(args); + passRunner.run(); + } + static std::ostream& printModule(Module* module) { return printModule(module, std::cout); } diff --git a/src/wasm.h b/src/wasm.h index 839dcf3c2c1..694dde51e4d 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -840,6 +840,14 @@ enum HostOp { // have internal allocation will need an allocator provided to them in order // to be constructed. +struct DebugInfoRef { + size_t locationIndex; // the reference to debug location in the function, or 0 + size_t labelIndex; // the reference to function label in the function, or 0 + + DebugInfoRef() : locationIndex(0), labelIndex(0) {} + DebugInfoRef(size_t locationIndex, size_t labelIndex) : locationIndex(locationIndex), labelIndex(labelIndex) {} +}; + class Expression { public: enum Id { @@ -869,8 +877,9 @@ class Expression { Id _id; WasmType type; // the type of the expression: its *output*, not necessarily its input(s) + DebugInfoRef debugInfo; // the reference to debug info: location and label - Expression(Id id) : _id(id), type(none) {} + Expression(Id id) : _id(id), type(none), debugInfo() {} void finalize() {} @@ -889,6 +898,11 @@ class Expression { assert(int(_id) == int(T::SpecificId)); return (T*)this; } + + void setDebugInfo(size_t locationIndex, size_t labelIndex) { + debugInfo.locationIndex = locationIndex; + debugInfo.labelIndex = labelIndex; + } }; inline const char *getExpressionName(Expression *curr) { @@ -1304,6 +1318,16 @@ class Unreachable : public SpecificExpression { // Globals +class DebugLocation { +public: + size_t fileId; + size_t row; + size_t column; + + DebugLocation(): fileId(0), row(0), column(0) {} + DebugLocation(size_t fileId, size_t row, size_t column) : fileId(fileId), row(row), column(column) {} +}; + class Function { public: Name name; @@ -1312,6 +1336,8 @@ class Function { std::vector vars; // params plus vars Name type; // if null, it is implicit in params and result Expression *body; + std::vector debugLocations; + std::vector labels; // local names. these are optional. std::vector localNames; @@ -1417,6 +1443,9 @@ class Module { std::vector> exports; std::vector> functions; + std::map debugFileMap; + std::vector debugSections; + Table table; Memory memory; Name start; @@ -1491,6 +1520,9 @@ class Module { void addStart(const Name &s) { start = s; } + void addDebugFile(size_t id, Name name) { + debugFileMap[id] = name; + } void removeImport(Name name) { for (size_t i = 0; i < imports.size(); i++) {