summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJérôme Vouillon <jerome.vouillon@gmail.com>2024-05-14 16:01:01 -0400
committerGitHub <noreply@github.com>2024-05-14 13:01:01 -0700
commit140386ed24ece9f30b2ad7dc55f63716f7a61f5e (patch)
treebebd167535305fbeaad79779e25ecc73b233c502
parent55f33b55daa965672d2b8aca98ce1570e3fb52c0 (diff)
downloadbinaryen-140386ed24ece9f30b2ad7dc55f63716f7a61f5e.tar.gz
binaryen-140386ed24ece9f30b2ad7dc55f63716f7a61f5e.tar.bz2
binaryen-140386ed24ece9f30b2ad7dc55f63716f7a61f5e.zip
Source maps: Allow specifying that an expression has no debug info in text (#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.
-rw-r--r--README.md29
-rw-r--r--src/ir/module-utils.cpp4
-rw-r--r--src/parser/contexts.h8
-rw-r--r--src/passes/Print.cpp32
-rw-r--r--src/wasm-ir-builder.h14
-rw-r--r--src/wasm.h4
-rw-r--r--src/wasm/wasm-binary.cpp4
-rw-r--r--src/wasm/wasm-ir-builder.cpp61
-rw-r--r--src/wasm/wasm-s-parser.cpp4
-rw-r--r--test/example/debug-location-propagation.cpp8
-rw-r--r--test/lit/debug/source-map-stop.wast37
11 files changed, 165 insertions, 40 deletions
diff --git a/README.md b/README.md
index d4271742b..236174ceb 100644
--- a/README.md
+++ b/README.md
@@ -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.
diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp
index e169f0ff1..99652ceff 100644
--- a/src/ir/module-utils.cpp
+++ b/src/ir/module-utils.cpp
@@ -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);
diff --git a/src/parser/contexts.h b/src/parser/contexts.h
index 3e42d0d6b..f66e3d051 100644
--- a/src/parser/contexts.h
+++ b/src/parser/contexts.h
@@ -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;
@@ -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,
diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp
index 02eaea21c..21b5e6b20 100644
--- a/src/passes/Print.cpp
+++ b/src/passes/Print.cpp
@@ -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
@@ -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.
@@ -2475,7 +2481,10 @@ 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) {
@@ -2483,9 +2492,13 @@ void PrintSExpression::printDebugLocation(
}
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);
}
@@ -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) {
@@ -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);
@@ -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());
}
diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h
index 3c4a52bea..650462e12 100644
--- a/src/wasm-ir-builder.h
+++ b/src/wasm-ir-builder.h
@@ -58,7 +58,7 @@ public:
// 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
@@ -238,7 +238,17 @@ private:
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;
diff --git a/src/wasm.h b/src/wasm.h
index 2f3f09ad5..dd7164499 100644
--- a/src/wasm.h
+++ b/src/wasm.h
@@ -2167,7 +2167,9 @@ public:
: 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;
diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp
index c1e506c8e..d72626b90 100644
--- a/src/wasm/wasm-binary.cpp
+++ b/src/wasm/wasm-binary.cpp
@@ -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();
diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp
index 91e990180..f816d4d1e 100644
--- a/src/wasm/wasm-ir-builder.cpp
+++ b/src/wasm/wasm-ir-builder.cpp
@@ -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();
}
}
@@ -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{};
@@ -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"};
@@ -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);
diff --git a/src/wasm/wasm-s-parser.cpp b/src/wasm/wasm-s-parser.cpp
index bca94a768..be6fc5100 100644
--- a/src/wasm/wasm-s-parser.cpp
+++ b/src/wasm/wasm-s-parser.cpp
@@ -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++;
diff --git a/test/example/debug-location-propagation.cpp b/test/example/debug-location-propagation.cpp
index b4685e391..06bf8ab48 100644
--- a/test/example/debug-location-propagation.cpp
+++ b/test/example/debug-location-propagation.cpp
@@ -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);
diff --git a/test/lit/debug/source-map-stop.wast b/test/lit/debug/source-map-stop.wast
index cdc06505e..95545e65a 100644
--- a/test/lit/debug/source-map-stop.wast
+++ b/test/lit/debug/source-map-stop.wast
@@ -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)
+ )
+ )
+ )
)