diff options
Diffstat (limited to 'src/asm2wasm.h')
-rw-r--r-- | src/asm2wasm.h | 143 |
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()); |