Skip to content

Commit

Permalink
convert debug intrinsics into annotations, and print them
Browse files Browse the repository at this point in the history
ignore --debuginfo if not emitting text, as wasm binaries don't support that yet
  • Loading branch information
kripken committed Feb 3, 2017
1 parent e5fe58e commit d46b672
Show file tree
Hide file tree
Showing 13 changed files with 404 additions and 8 deletions.
2 changes: 2 additions & 0 deletions auto_update_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
cmd += ['-O0'] # test that -O0 does nothing
else:
cmd += ['-O']
if 'debugInfo' in asm:
cmd += ['-g']
if precise and opts:
# test mem init importing
open('a.mem', 'wb').write(asm)
Expand Down
2 changes: 2 additions & 0 deletions check.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@
cmd += ['-O0'] # test that -O0 does nothing
else:
cmd += ['-O']
if 'debugInfo' in asm:
cmd += ['-g']
if precise and opts:
# test mem init importing
open('a.mem', 'wb').write(asm)
Expand Down
86 changes: 80 additions & 6 deletions src/asm2wasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -109,7 +110,8 @@ Name I32_CTTZ("i32_cttz"),
FTCALL("ftCall_"),
MFTCALL("mftCall_"),
MAX_("max"),
MIN_("min");
MIN_("min"),
EMSCRIPTEN_DEBUGINFO("emscripten_debuginfo");

// Utilities

Expand Down Expand Up @@ -225,7 +227,7 @@ struct Asm2WasmPreProcessor {
char* copy = (char*)malloc(upperBound);
char* end = copy + upperBound;
char* out = copy;
std::string DEBUGINFO_INTRINSIC = "emscripten_debuginfo";
std::string DEBUGINFO_INTRINSIC = EMSCRIPTEN_DEBUGINFO.str;
auto DEBUGINFO_INTRINSIC_SIZE = DEBUGINFO_INTRINSIC.size();
bool seenUseAsm = false;
while (input[0]) {
Expand Down Expand Up @@ -313,7 +315,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;
Expand Down Expand Up @@ -419,11 +421,11 @@ class Asm2WasmBuilder {
}

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),
Expand Down Expand Up @@ -641,6 +643,16 @@ class Asm2WasmBuilder {
}

Function* processFunction(Ref ast);

public:
CallImport* isDebugInfo(Expression* curr) {
if (auto* call = curr->dynCast<CallImport>()) {
if (call->target == EMSCRIPTEN_DEBUGINFO) {
return call;
}
}
return nullptr;
}
};

void Asm2WasmBuilder::processAsm(Ref ast) {
Expand Down Expand Up @@ -1090,6 +1102,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->isDebugInfo(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);
Expand All @@ -1106,13 +1155,17 @@ 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();

// apply memory growth, if relevant
if (memoryGrowth) {
if (preprocessor.memoryGrowth) {
emscripten::generateMemoryGrowthFunction(wasm);
wasm.memory.max = Memory::kMaxSize;
}
Expand Down Expand Up @@ -2349,6 +2402,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 (isDebugInfo(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());
Expand Down
13 changes: 13 additions & 0 deletions src/passes/Print.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,19 @@ struct PrintSExpression : public Visitor<PrintSExpression> {
}
}

void visit(Expression* curr) {
if (currFunction) {
// show an annotation, if there is one
auto& annotations = currFunction->annotations;
auto iter = annotations.find(curr);
if (iter != annotations.end()) {
o << ";; " << iter->second << '\n';
doIndent(o, indent);
}
}
Visitor<PrintSExpression>::visit(curr);
}

void setMinify(bool minify_) {
minify = minify_;
maybeSpace = minify ? "" : " ";
Expand Down
14 changes: 12 additions & 2 deletions src/tools/asm2wasm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ int main(int argc, const char *argv[]) {
[&wasmOnly](Options *o, const std::string &) {
wasmOnly = true;
})
.add("--debuginfo", "-g", "Emit names section and debug info",
.add("--debuginfo", "-g", "Emit names section and debug info (you must emit text, -S, for this to work)",
Options::Arguments::Zero,
[&](Options *o, const std::string &arguments) { debugInfo = true; })
.add("--symbolmap", "-s", "Emit a symbol map (indexes => names)",
Expand All @@ -99,6 +99,16 @@ int main(int argc, const char *argv[]) {
});
options.parse(argc, argv);

// finalize arguments
if (options.extra["output"].size() == 0) {
// when no output file is specified, we emit text to stdout
emitBinary = false;
}
if (emitBinary) {
// wasm binaries can't contain debug info yet
debugInfo = false;
}

const auto &tm_it = options.extra.find("total memory");
size_t totalMemory =
tm_it == options.extra.end() ? 16 * 1024 * 1024 : atoi(tm_it->second.c_str());
Expand All @@ -121,7 +131,7 @@ int main(int argc, const char *argv[]) {
if (options.debug) std::cerr << "wasming..." << std::endl;
Module wasm;
wasm.memory.initial = wasm.memory.max = totalMemory / Memory::kPageSize;
Asm2WasmBuilder asm2wasm(wasm, pre.memoryGrowth, options.debug, imprecise, passOptions, runOptimizationPasses, wasmOnly);
Asm2WasmBuilder asm2wasm(wasm, pre, options.debug, imprecise, passOptions, runOptimizationPasses, wasmOnly);
asm2wasm.processAsm(asmjs);

// import mem init file, if provided
Expand Down
3 changes: 3 additions & 0 deletions src/wasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -1385,6 +1385,9 @@ class Function {
std::vector<Name> localNames;
std::map<Name, Index> localIndices;

// node annotations, printed alongside the node in the text format
std::unordered_map<Expression*, std::string> annotations;

Function() : result(none) {}

size_t getNumParams() {
Expand Down
26 changes: 26 additions & 0 deletions test/debugInfo.asm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
function () {
"use asm";
function add(x, y) {
x = x | 0;
y = y | 0;
x = x; //@line 5 "tests/hello_world.c"
y = y; //@line 6 "tests/hello_world.c"
x = y; //@line 314159 "tests/other_file.cpp"
return x + y | 0;
}
function ret(x) {
x = x | 0;
x = x << 1; //@line 50 "return.cpp"
return x + 1 | 0; //@line 100 "return.cpp"
}
function opts(x, y) {
x = x | 0;
y = y | 0;
x = (x + y) | 0; //@line 1 "even-opted.cpp"
y = y >> x; //@line 2 "even-opted.cpp"
x = (x | 0) % (y | 0); //@line 3 "even-opted.cpp"
return x + y | 0;
}
return { add: add, ret: ret, opts: opts };
}

54 changes: 54 additions & 0 deletions test/debugInfo.fromasm
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
(module
(type $FUNCSIG$iii (func (param i32 i32) (result i32)))
(type $FUNCSIG$vii (func (param i32 i32)))
(import "env" "emscripten_debuginfo" (func $emscripten_debuginfo (param i32 i32)))
(import "asm2wasm" "i32s-rem" (func $i32s-rem (param i32 i32) (result i32)))
(import "env" "memory" (memory $0 256 256))
(import "env" "table" (table 0 0 anyfunc))
(import "env" "memoryBase" (global $memoryBase i32))
(import "env" "tableBase" (global $tableBase i32))
(data (get_global $memoryBase) "debugInfo.asm.js")
(export "add" (func $add))
(export "ret" (func $ret))
(export "opts" (func $opts))
(func $add (param $0 i32) (param $1 i32) (result i32)
(i32.add
(get_local $1)
(get_local $1)
)
)
(func $ret (param $0 i32) (result i32)
(i32.add
(i32.shl
(get_local $0)
(i32.const 1)
)
(i32.const 1)
)
)
(func $opts (param $0 i32) (param $1 i32) (result i32)
;; even-opted.cpp:2
(set_local $1
(i32.shr_s
(get_local $1)
(tee_local $0
(i32.add
(get_local $0)
(get_local $1)
)
)
)
)
;; even-opted.cpp:3
(set_local $0
(call $i32s-rem
(get_local $0)
(get_local $1)
)
)
(i32.add
(get_local $0)
(get_local $1)
)
)
)
47 changes: 47 additions & 0 deletions test/debugInfo.fromasm.imprecise
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
(module
(type $FUNCSIG$vii (func (param i32 i32)))
(import "env" "emscripten_debuginfo" (func $emscripten_debuginfo (param i32 i32)))
(import "env" "memory" (memory $0 256 256))
(import "env" "table" (table 0 0 anyfunc))
(import "env" "memoryBase" (global $memoryBase i32))
(import "env" "tableBase" (global $tableBase i32))
(export "add" (func $add))
(export "ret" (func $ret))
(export "opts" (func $opts))
(func $add (param $0 i32) (param $1 i32) (result i32)
(i32.add
(get_local $1)
(get_local $1)
)
)
(func $ret (param $0 i32) (result i32)
(i32.add
(i32.shl
(get_local $0)
(i32.const 1)
)
(i32.const 1)
)
)
(func $opts (param $0 i32) (param $1 i32) (result i32)
;; even-opted.cpp:2
(set_local $1
(i32.shr_s
(get_local $1)
(tee_local $0
(i32.add
(get_local $0)
(get_local $1)
)
)
)
)
(i32.add
(i32.rem_s
(get_local $0)
(get_local $1)
)
(get_local $1)
)
)
)
Loading

0 comments on commit d46b672

Please sign in to comment.