diff options
Diffstat (limited to 'src/asm2wasm.h')
-rw-r--r-- | src/asm2wasm.h | 180 |
1 files changed, 175 insertions, 5 deletions
diff --git a/src/asm2wasm.h b/src/asm2wasm.h index eaf4f8c18..32b6af5b0 100644 --- a/src/asm2wasm.h +++ b/src/asm2wasm.h @@ -34,6 +34,7 @@ #include "ast_utils.h" #include "wasm-builder.h" #include "wasm-emscripten.h" +#include "wasm-printing.h" #include "wasm-validator.h" #include "wasm-module-building.h" @@ -109,7 +110,8 @@ Name I32_CTTZ("i32_cttz"), FTCALL("ftCall_"), MFTCALL("mftCall_"), MAX_("max"), - MIN_("min"); + MIN_("min"), + EMSCRIPTEN_DEBUGINFO("emscripten_debuginfo"); // Utilities @@ -148,6 +150,14 @@ struct AstStackHelper { std::vector<Ref> AstStackHelper::astStack; +static bool startsWith(const char* string, const char *prefix) { + while (1) { + if (*prefix == 0) return true; + if (*string == 0) return false; + if (*string++ != *prefix++) return false; + } +}; + // // Asm2WasmPreProcessor - does some initial parsing/processing // of asm.js code. @@ -155,6 +165,16 @@ std::vector<Ref> AstStackHelper::astStack; struct Asm2WasmPreProcessor { bool memoryGrowth = false; + bool debugInfo = false; + + std::vector<std::string> debugInfoFileNames; + std::unordered_map<std::string, Index> debugInfoFileIndices; + + char* allocatedCopy = nullptr; + + ~Asm2WasmPreProcessor() { + if (allocatedCopy) free(allocatedCopy); + } char* process(char* input) { // emcc --separate-asm modules can look like @@ -206,6 +226,79 @@ struct Asm2WasmPreProcessor { *marker = START_FUNCS[0]; } + // handle debug info, if this build wants that. + if (debugInfo) { + // asm.js debug info comments look like + // ..command..; //@line 4 "tests/hello_world.c" + // we convert those into emscripten_debuginfo(file, line) + // calls, where the params are indices into a mapping. then + // the compiler and optimizer can operate on them. after + // that, we can apply the debug info to the wasm node right + // before it - this is guaranteed to be correct without opts, + // and is usually decently accurate with them. + const auto SCALE_FACTOR = 1.25; // an upper bound on how much more space we need as a multiple of the original + const auto ADD_FACTOR = 100; // an upper bound on how much we write for each debug info element itself + auto size = strlen(input); + auto upperBound = Index(size * SCALE_FACTOR) + ADD_FACTOR; + char* copy = allocatedCopy = (char*)malloc(upperBound); + char* end = copy + upperBound; + char* out = copy; + std::string DEBUGINFO_INTRINSIC = EMSCRIPTEN_DEBUGINFO.str; + auto DEBUGINFO_INTRINSIC_SIZE = DEBUGINFO_INTRINSIC.size(); + bool seenUseAsm = false; + while (input[0]) { + if (out + ADD_FACTOR >= end) { + Fatal() << "error in handling debug info"; + } + if (startsWith(input, "//@line")) { + char* linePos = input + 8; + char* lineEnd = strchr(input + 8, ' '); + char* filePos = strchr(lineEnd, '"') + 1; + char* fileEnd = strchr(filePos, '"'); + input = fileEnd + 1; + *lineEnd = 0; + *fileEnd = 0; + std::string line = linePos, file = filePos; + auto iter = debugInfoFileIndices.find(file); + if (iter == debugInfoFileIndices.end()) { + Index index = debugInfoFileNames.size(); + debugInfoFileNames.push_back(file); + debugInfoFileIndices[file] = index; + } + std::string fileIndex = std::to_string(debugInfoFileIndices[file]); + // write out the intrinsic + strcpy(out, DEBUGINFO_INTRINSIC.c_str()); + out += DEBUGINFO_INTRINSIC_SIZE; + *out++ = '('; + strcpy(out, fileIndex.c_str()); + out += fileIndex.size(); + *out++ = ','; + strcpy(out, line.c_str()); + out += line.size(); + *out++ = ')'; + *out++ = ';'; + } else if (!seenUseAsm && (startsWith(input, "asm'") || startsWith(input, "asm\""))) { + // end of "use asm" or "almost asm" + const auto SKIP = 5; // skip the end of "use asm"; (5 chars, a,s,m," or ',;) + seenUseAsm = true; + memcpy(out, input, SKIP); + out += SKIP; + input += SKIP; + // add a fake import for the intrinsic, so the module validates + std::string import = "\n var emscripten_debuginfo = env.emscripten_debuginfo;"; + strcpy(out, import.c_str()); + out += import.size(); + } else { + *out++ = *input++; + } + } + if (out >= end) { + Fatal() << "error in handling debug info"; + } + *out = 0; + input = copy; + } + return input; } }; @@ -237,7 +330,7 @@ class Asm2WasmBuilder { // function table std::map<IString, int> functionTableStarts; // each asm function table gets a range in the one wasm table, starting at a location - bool memoryGrowth; + Asm2WasmPreProcessor& preprocessor; bool debug; bool imprecise; PassOptions passOptions; @@ -343,11 +436,11 @@ private: } public: - Asm2WasmBuilder(Module& wasm, bool memoryGrowth, bool debug, bool imprecise, PassOptions passOptions, bool runOptimizationPasses, bool wasmOnly) + Asm2WasmBuilder(Module& wasm, Asm2WasmPreProcessor& preprocessor, bool debug, bool imprecise, PassOptions passOptions, bool runOptimizationPasses, bool wasmOnly) : wasm(wasm), allocator(wasm.allocator), builder(wasm), - memoryGrowth(memoryGrowth), + preprocessor(preprocessor), debug(debug), imprecise(imprecise), passOptions(passOptions), @@ -565,6 +658,16 @@ 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) { @@ -1014,6 +1117,43 @@ 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>>> { + bool isFunctionParallel() override { return true; } + + Pass* create() override { return new ApplyDebugInfo(parent); } + + Asm2WasmBuilder* parent; + + ApplyDebugInfo(Asm2WasmBuilder* parent) : parent(parent) { + name = "apply-debug-info"; + } + + Expression* lastExpression = 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& annotations = getFunction()->annotations; + if (last) { + auto fileIndex = call->operands[0]->cast<Const>()->value.geti32(); + auto lineNumber = call->operands[1]->cast<Const>()->value.geti32(); + annotations[last] = parent->preprocessor.debugInfoFileNames[fileIndex] + ":" + std::to_string(lineNumber); + } + // 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; + } + } + }; + PassRunner passRunner(&wasm); if (debug) { passRunner.setDebug(true); @@ -1030,13 +1170,22 @@ void Asm2WasmBuilder::processAsm(Ref ast) { passRunner.add("optimize-instructions"); passRunner.add("post-emscripten"); } + if (preprocessor.debugInfo) { + passRunner.add<ApplyDebugInfo>(this); + passRunner.add("vacuum"); // FIXME maybe just remove the nops that were debuginfo nodes, if not optimizing? + } // make sure to not emit unreachable code at all, even in -O0, as wasm rules for it are complex // and changing. passRunner.add("dce"); passRunner.run(); + // remove the debug info intrinsic + if (preprocessor.debugInfo) { + wasm.removeImport(EMSCRIPTEN_DEBUGINFO); + } + // apply memory growth, if relevant - if (memoryGrowth) { + if (preprocessor.memoryGrowth) { emscripten::generateMemoryGrowthFunction(wasm); wasm.memory.max = Memory::kMaxSize; } @@ -2273,6 +2422,27 @@ 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()); |