Skip to content

Commit

Permalink
Source maps: Allow specifying that an expression has no debug info in…
Browse files Browse the repository at this point in the history
… text (WebAssembly#6520)

;;@

with nothing else (no source:line) can be used to specify that the following
expression does not have any debug info associated to it. This can be used
to stop the automatic propagation of debug info in the text parsers.

The text printer has also been updated to output this comment when needed.
  • Loading branch information
vouillon authored May 14, 2024
1 parent 55f33b5 commit 140386e
Show file tree
Hide file tree
Showing 11 changed files with 165 additions and 40 deletions.
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -924,6 +924,35 @@ environment. That will print this for the above `add`:
(full print mode also adds a `[type]` for each expression, right before the
debug location).

The debug information is also propagated from an expression to its
next sibling:
```wat
;;@ src.cpp:100:33
(local.set $x
(i32.const 0)
)
(local.set $y ;; This receives an annotation of src.cpp:100:33
(i32.const 0)
)
```

You can prevent the propagation of debug info by explicitly mentioning
that an expression has not debug info using the annotation `;;@` with
nothing else:
```wat
;;@ src.cpp:100:33
(local.set $x
;;@
(i32.const 0) ;; This does not receive any annotation
)
;;@
(local.set $y ;; This does not receive any annotation
(i32.const 7)
)
```
This stops the propagatation to children and siblings as well. So,
expression `(i32.const 7)` does not have any debug info either.

There is no shorthand in the binary format. That is, roundtripping (writing and
reading) through a binary + source map should not change which expressions have
debug info on them or the contents of that info.
Expand Down
4 changes: 3 additions & 1 deletion src/ir/module-utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ Function* copyFunction(Function* func,
// Update file indices if needed
if (fileIndexMap) {
for (auto& iter : ret->debugLocations) {
iter.second.fileIndex = (*fileIndexMap)[iter.second.fileIndex];
if (iter.second) {
iter.second->fileIndex = (*fileIndexMap)[iter.second->fileIndex];
}
}
updateLocationSet(ret->prologLocation, *fileIndexMap);
updateLocationSet(ret->epilogLocation, *fileIndexMap);
Expand Down
8 changes: 7 additions & 1 deletion src/parser/contexts.h
Original file line number Diff line number Diff line change
Expand Up @@ -1731,6 +1731,11 @@ struct ParseDefsCtx : TypeParserCtx<ParseDefsCtx> {
return;
}
Lexer lexer(annotation->contents);
if (lexer.empty()) {
irBuilder.setDebugLocation(std::nullopt);
return;
}

auto contents = lexer.takeKeyword();
if (!contents || !lexer.empty()) {
return;
Expand Down Expand Up @@ -1766,7 +1771,8 @@ struct ParseDefsCtx : TypeParserCtx<ParseDefsCtx> {
assert(wasm.debugInfoFileNames.size() == it->second);
wasm.debugInfoFileNames.push_back(std::string(file));
}
irBuilder.setDebugLocation({it->second, *line, *col});
irBuilder.setDebugLocation(
Function::DebugLocation({it->second, *line, *col}));
}

Result<> makeBlock(Index pos,
Expand Down
32 changes: 24 additions & 8 deletions src/passes/Print.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,12 @@ struct PrintSExpression : public UnifiedExpressionVisitor<PrintSExpression> {

Module* currModule = nullptr;
Function* currFunction = nullptr;
Function::DebugLocation lastPrintedLocation;
// Keep track of the last printed debug location to avoid printing
// repeated debug locations for children. nullopt means that we have
// not yet printed any debug location, or that we last printed an
// annotation indicating that the expression had no associated
// debug location.
std::optional<Function::DebugLocation> lastPrintedLocation;
bool debugInfo;

// Used to print delegate's depth argument when it throws to the caller
Expand Down Expand Up @@ -247,7 +252,8 @@ struct PrintSExpression : public UnifiedExpressionVisitor<PrintSExpression> {
return o;
}

void printDebugLocation(const Function::DebugLocation& location);
void
printDebugLocation(const std::optional<Function::DebugLocation>& location);
void printDebugLocation(Expression* curr);

// Prints debug info for a delimiter in an expression.
Expand Down Expand Up @@ -2475,17 +2481,24 @@ std::ostream& PrintSExpression::printPrefixedTypes(const char* prefix,
}

void PrintSExpression::printDebugLocation(
const Function::DebugLocation& location) {
const std::optional<Function::DebugLocation>& location) {
if (minify) {
return;
}
// Do not skip repeated debug info in full mode, for less-confusing debugging:
// full mode prints out everything in the most verbose manner.
if (lastPrintedLocation == location && indent > lastPrintIndent && !full) {
return;
}
lastPrintedLocation = location;
lastPrintIndent = indent;
auto fileName = currModule->debugInfoFileNames[location.fileIndex];
o << ";;@ " << fileName << ":" << location.lineNumber << ":"
<< location.columnNumber << '\n';
if (!location) {
o << ";;@\n";
} else {
auto fileName = currModule->debugInfoFileNames[location->fileIndex];
o << ";;@ " << fileName << ":" << location->lineNumber << ":"
<< location->columnNumber << '\n';
}
doIndent(o, indent);
}

Expand All @@ -2496,6 +2509,8 @@ void PrintSExpression::printDebugLocation(Expression* curr) {
auto iter = debugLocations.find(curr);
if (iter != debugLocations.end()) {
printDebugLocation(iter->second);
} else {
printDebugLocation(std::nullopt);
}
// show a binary position, if there is one
if (debugInfo) {
Expand Down Expand Up @@ -2958,7 +2973,7 @@ void PrintSExpression::visitFunction(Function* curr) {
void PrintSExpression::visitImportedFunction(Function* curr) {
doIndent(o, indent);
currFunction = curr;
lastPrintedLocation = {0, 0, 0};
lastPrintedLocation = std::nullopt;
o << '(';
emitImportHeader(curr);
handleSignature(curr->type, curr->name);
Expand All @@ -2969,7 +2984,8 @@ void PrintSExpression::visitImportedFunction(Function* curr) {
void PrintSExpression::visitDefinedFunction(Function* curr) {
doIndent(o, indent);
currFunction = curr;
lastPrintedLocation = {0, 0, 0};
lastPrintedLocation = std::nullopt;
lastPrintIndent = 0;
if (currFunction->prologLocation.size()) {
printDebugLocation(*currFunction->prologLocation.begin());
}
Expand Down
14 changes: 12 additions & 2 deletions src/wasm-ir-builder.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class IRBuilder : public UnifiedExpressionVisitor<IRBuilder, Result<>> {

// Set the debug location to be attached to the next visited, created, or
// pushed instruction.
void setDebugLocation(const Function::DebugLocation&);
void setDebugLocation(const std::optional<Function::DebugLocation>&);

// Handle the boundaries of control flow structures. Users may choose to use
// the corresponding `makeXYZ` function below instead of `visitXYZStart`, but
Expand Down Expand Up @@ -238,7 +238,17 @@ class IRBuilder : public UnifiedExpressionVisitor<IRBuilder, Result<>> {
Module& wasm;
Function* func;
Builder builder;
std::optional<Function::DebugLocation> debugLoc;

// The location lacks debug info as it was marked as not having it.
struct NoDebug : public std::monostate {};
// The location lacks debug info, but was not marked as not having
// it, and it can receive it from the parent or its previous sibling
// (if it has one).
struct CanReceiveDebug : public std::monostate {};
using DebugVariant =
std::variant<NoDebug, CanReceiveDebug, Function::DebugLocation>;

DebugVariant debugLoc;

struct ChildPopper;

Expand Down
4 changes: 3 additions & 1 deletion src/wasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -2167,7 +2167,9 @@ class Function : public Importable {
: columnNumber < other.columnNumber;
}
};
std::unordered_map<Expression*, DebugLocation> debugLocations;
// One can explicitly set the debug location of an expression to
// nullopt to stop the propagation of debug locations.
std::unordered_map<Expression*, std::optional<DebugLocation>> debugLocations;
std::set<DebugLocation> prologLocation;
std::set<DebugLocation> epilogLocation;

Expand Down
4 changes: 2 additions & 2 deletions src/wasm/wasm-binary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1411,9 +1411,9 @@ void WasmBinaryWriter::writeDebugLocation(Expression* curr, Function* func) {
if (sourceMap) {
auto& debugLocations = func->debugLocations;
auto iter = debugLocations.find(curr);
if (iter != debugLocations.end()) {
if (iter != debugLocations.end() && iter->second) {
// There is debug information here, write it out.
writeDebugLocation(iter->second);
writeDebugLocation(*(iter->second));
} else {
// This expression has no debug location.
writeNoDebugLocation();
Expand Down
61 changes: 40 additions & 21 deletions src/wasm/wasm-ir-builder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -168,21 +168,37 @@ Result<Expression*> IRBuilder::build() {
return expr;
}

void IRBuilder::setDebugLocation(const Function::DebugLocation& loc) {
DBG(std::cerr << "setting debugloc " << loc.fileIndex << ":" << loc.lineNumber
<< ":" << loc.columnNumber << "\n";);
debugLoc = loc;
void IRBuilder::setDebugLocation(
const std::optional<Function::DebugLocation>& loc) {
if (loc) {
DBG(std::cerr << "setting debugloc " << loc->fileIndex << ":"
<< loc->lineNumber << ":" << loc->columnNumber << "\n";);
} else {
DBG(std::cerr << "setting debugloc to none\n";);
}
if (loc) {
debugLoc = *loc;
} else {
debugLoc = NoDebug();
}
}

void IRBuilder::applyDebugLoc(Expression* expr) {
if (debugLoc) {
if (!std::get_if<CanReceiveDebug>(&debugLoc)) {
if (func) {
DBG(std::cerr << "applying debugloc " << debugLoc->fileIndex << ":"
<< debugLoc->lineNumber << ":" << debugLoc->columnNumber
<< " to expression " << ShallowExpression{expr} << "\n");
func->debugLocations[expr] = *debugLoc;
if (auto* loc = std::get_if<Function::DebugLocation>(&debugLoc)) {
DBG(std::cerr << "applying debugloc " << loc->fileIndex << ":"
<< loc->lineNumber << ":" << loc->columnNumber
<< " to expression " << ShallowExpression{expr} << "\n");
func->debugLocations[expr] = *loc;
} else {
assert(std::get_if<NoDebug>(&debugLoc));
DBG(std::cerr << "applying debugloc to expression "
<< ShallowExpression{expr} << "\n");
func->debugLocations[expr] = std::nullopt;
}
}
debugLoc.reset();
debugLoc = CanReceiveDebug();
}
}

Expand Down Expand Up @@ -677,10 +693,10 @@ Result<> IRBuilder::visitFunctionStart(Function* func) {
if (!scopeStack.empty()) {
return Err{"unexpected start of function"};
}
if (debugLoc) {
func->prologLocation.insert(*debugLoc);
debugLoc.reset();
if (auto* loc = std::get_if<Function::DebugLocation>(&debugLoc)) {
func->prologLocation.insert(*loc);
}
debugLoc = CanReceiveDebug();
scopeStack.push_back(ScopeCtx::makeFunc(func));
this->func = func;
return Ok{};
Expand Down Expand Up @@ -718,12 +734,13 @@ Result<> IRBuilder::visitTryTableStart(TryTable* trytable, Name label) {
}

Result<Expression*> IRBuilder::finishScope(Block* block) {
if (debugLoc) {
DBG(std::cerr << "discarding debugloc " << debugLoc->fileIndex << ":"
<< debugLoc->lineNumber << ":" << debugLoc->columnNumber
<< "\n");
#if IR_BUILDER_DEBUG
if (auto* loc = std::get_if<Function::DebugLocation>(&debugLoc)) {
std::cerr << "discarding debugloc " << loc->fileIndex << ":"
<< loc->lineNumber << ":" << loc->columnNumber << "\n";
}
debugLoc.reset();
#endif
debugLoc = CanReceiveDebug();

if (scopeStack.empty() || scopeStack.back().isNone()) {
return Err{"unexpected end of scope"};
Expand Down Expand Up @@ -913,10 +930,12 @@ Result<> IRBuilder::visitEnd() {
if (scope.isNone()) {
return Err{"unexpected end"};
}
if (auto* func = scope.getFunction(); func && debugLoc) {
func->epilogLocation.insert(*debugLoc);
debugLoc.reset();
if (auto* func = scope.getFunction(); func) {
if (auto* loc = std::get_if<Function::DebugLocation>(&debugLoc)) {
func->epilogLocation.insert(*loc);
}
}
debugLoc = CanReceiveDebug();
auto expr = finishScope(scope.getBlock());
CHECK_ERR(expr);

Expand Down
4 changes: 4 additions & 0 deletions src/wasm/wasm-s-parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,10 @@ void SExpressionParser::parseDebugLocation() {
while (debugLocEnd[0] && debugLocEnd[0] != '\n') {
debugLocEnd++;
}
if (debugLocEnd == debugLoc) {
loc = nullptr;
return;
}
char const* pos = debugLoc;
while (pos < debugLocEnd && pos[0] != ':') {
pos++;
Expand Down
8 changes: 4 additions & 4 deletions test/example/debug-location-propagation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ int main() {

auto& debugLocations = module->getFunction("adder")->debugLocations;
assert(debugLocations.size() == 4);
assert(debugLocations[x].columnNumber == 13);
assert(debugLocations[y].columnNumber == 13);
assert(debugLocations[add].columnNumber == 2);
assert(debugLocations[drop].columnNumber == 2);
assert(debugLocations[x]->columnNumber == 13);
assert(debugLocations[y]->columnNumber == 13);
assert(debugLocations[add]->columnNumber == 2);
assert(debugLocations[drop]->columnNumber == 2);

BinaryenSetDebugInfo(false);
BinaryenModuleDispose(module);
Expand Down
37 changes: 37 additions & 0 deletions test/lit/debug/source-map-stop.wast
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,41 @@
;;@ waka:200:2
(i32.const 2)
)

;; CHECK: (func $foo (param $x i32) (param $y i32)
;; CHECK-NEXT: ;;@ src.cpp:90:1
;; CHECK-NEXT: (if
;; CHECK-NEXT: ;;@
;; CHECK-NEXT: (i32.add
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: (local.get $y)
;; CHECK-NEXT: )
;; CHECK-NEXT: (then
;; CHECK-NEXT: ;;@ src.cpp:100:1
;; CHECK-NEXT: (return)
;; CHECK-NEXT: )
;; CHECK-NEXT: (else
;; CHECK-NEXT: ;;@
;; CHECK-NEXT: (return)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $foo (param $x i32) (param $y i32)
;;@ src.cpp:90:1
(if
;;@
(i32.add
(local.get $x)
(local.get $y)
)
(then
;;@ src.cpp:100:1
(return)
)
(else
;;@
(return)
)
)
)
)

0 comments on commit 140386e

Please sign in to comment.