summaryrefslogtreecommitdiff
path: root/src/asm2wasm.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/asm2wasm.h')
-rw-r--r--src/asm2wasm.h143
1 files changed, 88 insertions, 55 deletions
diff --git a/src/asm2wasm.h b/src/asm2wasm.h
index ed8c75e56..f78b9df55 100644
--- a/src/asm2wasm.h
+++ b/src/asm2wasm.h
@@ -303,6 +303,44 @@ struct Asm2WasmPreProcessor {
}
};
+static CallImport* checkDebugInfo(Expression* curr) {
+ if (auto* call = curr->dynCast<CallImport>()) {
+ if (call->target == EMSCRIPTEN_DEBUGINFO) {
+ return call;
+ }
+ }
+ return nullptr;
+}
+
+// Debug info appears in the ast as calls to the debug intrinsic. These are usually
+// after the relevant node. We adjust them to a position that is not dce-able, so that
+// they are not trivially removed when optimizing.
+struct AdjustDebugInfo : public WalkerPass<PostWalker<AdjustDebugInfo, Visitor<AdjustDebugInfo>>> {
+ bool isFunctionParallel() override { return true; }
+
+ Pass* create() override { return new AdjustDebugInfo(); }
+
+ AdjustDebugInfo() {
+ name = "adjust-debug-info";
+ }
+
+ void visitBlock(Block* curr) {
+ // look for a debug info call that is unreachable
+ if (curr->list.size() == 0) return;
+ auto* back = curr->list.back();
+ for (Index i = 1; i < curr->list.size(); i++) {
+ if (checkDebugInfo(curr->list[i]) && !checkDebugInfo(curr->list[i - 1])) {
+ // swap them
+ std::swap(curr->list[i - 1], curr->list[i]);
+ }
+ }
+ if (curr->list.back() != back) {
+ // we changed the last element, update the type
+ curr->finalize();
+ }
+ }
+};
+
//
// Asm2WasmBuilder - converts an asm.js module into WebAssembly
//
@@ -841,16 +879,6 @@ private:
}
Function* processFunction(Ref ast);
-
-public:
- CallImport* checkDebugInfo(Expression* curr) {
- if (auto* call = curr->dynCast<CallImport>()) {
- if (call->target == EMSCRIPTEN_DEBUGINFO) {
- return call;
- }
- }
- return nullptr;
- }
};
void Asm2WasmBuilder::processAsm(Ref ast) {
@@ -976,6 +1004,10 @@ void Asm2WasmBuilder::processAsm(Ref ast) {
}
// run autodrop first, before optimizations
passRunner.add<AutoDrop>();
+ if (preprocessor.debugInfo) {
+ // fix up debug info to better survive optimization
+ passRunner.add<AdjustDebugInfo>();
+ }
// optimize relooper label variable usage at the wasm level, where it is easy
passRunner.add("relooper-jump-threading");
}, debug, false /* do not validate globally yet */);
@@ -1302,39 +1334,55 @@ void Asm2WasmBuilder::processAsm(Ref ast) {
};
// apply debug info, reducing intrinsic calls into annotations on the ast nodes
- struct ApplyDebugInfo : public WalkerPass<PostWalker<ApplyDebugInfo, UnifiedExpressionVisitor<ApplyDebugInfo>>> {
+ struct ApplyDebugInfo : public WalkerPass<ExpressionStackWalker<ApplyDebugInfo, UnifiedExpressionVisitor<ApplyDebugInfo>>> {
bool isFunctionParallel() override { return true; }
- Pass* create() override { return new ApplyDebugInfo(parent); }
+ Pass* create() override { return new ApplyDebugInfo(); }
- Asm2WasmBuilder* parent;
-
- ApplyDebugInfo(Asm2WasmBuilder* parent) : parent(parent) {
+ ApplyDebugInfo() {
name = "apply-debug-info";
}
- Expression* lastExpression = nullptr;
+ CallImport* lastDebugInfo = nullptr;
void visitExpression(Expression* curr) {
- if (auto* call = parent->checkDebugInfo(curr)) {
- // this is a debuginfo node. turn it into an annotation on the last stack
- auto* last = lastExpression;
- lastExpression = nullptr;
- auto& debugLocations = getFunction()->debugLocations;
- if (last) {
- uint32_t fileIndex = call->operands[0]->cast<Const>()->value.geti32();
+ if (auto* call = checkDebugInfo(curr)) {
+ lastDebugInfo = call;
+ replaceCurrent(getModule()->allocator.alloc<Nop>());
+ } else {
+ if (lastDebugInfo) {
+ auto& debugLocations = getFunction()->debugLocations;
+ uint32_t fileIndex = lastDebugInfo->operands[0]->cast<Const>()->value.geti32();
assert(getModule()->debugInfoFileNames.size() > fileIndex);
- uint32_t lineNumber = call->operands[1]->cast<Const>()->value.geti32();
- debugLocations[last] = {fileIndex, lineNumber};
+ uint32_t lineNumber = lastDebugInfo->operands[1]->cast<Const>()->value.geti32();
+ // look up the stack, apply to the root expression
+ Index i = expressionStack.size() - 1;
+ while (1) {
+ auto* exp = expressionStack[i];
+ bool parentIsStructure = i > 0 && (expressionStack[i - 1]->is<Block>() ||
+ expressionStack[i - 1]->is<Loop>() ||
+ expressionStack[i - 1]->is<If>());
+ if (i == 0 || parentIsStructure || exp->type == none || exp->type == unreachable) {
+ if (debugLocations.count(exp) > 0) {
+ // already present, so look back up
+ i++;
+ while (i < expressionStack.size()) {
+ exp = expressionStack[i];
+ if (debugLocations.count(exp) == 0) {
+ debugLocations[exp] = { fileIndex, lineNumber };
+ break;
+ }
+ i++;
+ }
+ } else {
+ debugLocations[exp] = { fileIndex, lineNumber };
+ }
+ break;
+ }
+ i--;
+ }
+ lastDebugInfo = nullptr;
}
- // eliminate the debug info call
- ExpressionManipulator::nop(curr);
- return;
- }
- // ignore const nodes, as they may be the children of the debug info calls, and they
- // don't really need debug info anyhow
- if (!curr->is<Const>()) {
- lastExpression = curr;
}
}
};
@@ -1354,9 +1402,15 @@ void Asm2WasmBuilder::processAsm(Ref ast) {
passRunner.add("remove-unused-brs");
passRunner.add("optimize-instructions");
passRunner.add("post-emscripten");
+ } else {
+ if (preprocessor.debugInfo) {
+ // we would have run this before if optimizing, do it now otherwise. must
+ // precede ApplyDebugInfo
+ passRunner.add<AdjustDebugInfo>();
+ }
}
if (preprocessor.debugInfo) {
- passRunner.add<ApplyDebugInfo>(this);
+ passRunner.add<ApplyDebugInfo>();
passRunner.add("vacuum"); // FIXME maybe just remove the nops that were debuginfo nodes, if not optimizing?
}
passRunner.run();
@@ -2558,27 +2612,6 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
};
// body
function->body = processStatements(body, start);
- // debug info cleanup: we add debug info calls after each instruction; as
- // a result,
- // return 0; //@line file.cpp
- // will have code after the return. if the function body is a block,
- // it will be forced to the return type of the function, and then
- // the unreachable type of the return makes things work, which we break
- // if we add a none debug intrinsic call afterwards. so we need to fix
- // that up.
- if (preprocessor.debugInfo) {
- if (function->result != none) {
- if (auto* block = function->body->dynCast<Block>()) {
- if (block->list.size() > 0) {
- if (checkDebugInfo(block->list.back())) {
- // add an unreachable. both the debug info and it could be dce'd,
- // but it makes us validate properly.
- block->list.push_back(builder.makeUnreachable());
- }
- }
- }
- }
- }
// cleanups/checks
assert(breakStack.size() == 0 && continueStack.size() == 0);
assert(parentLabel.isNull());