Permalink
Browse files

Implement asm.js style exception handling for Wasm (#664)

* Implement asm.js style exception handling for Wasm

Converts invoke wrapper names generated by LLVM backend to real invoke wrapper
names that are expected by JavaScript glue code.
This is required to support wasm exception handling (asm.js style).

LLVM backend lowers
  invoke @func(arg1, arg2) to label %invoke.cont unwind label %lpad
into
... (some code)
  call @invoke_SIG(func, arg1, arg2)
... (some code)
SIG is a mangled string generated based on the LLVM IR-level function
signature. In LLVM IR, types are not lowered yet, so this mangling scheme
simply takes LLVM's string representtion of parameter types and concatenate
them with '_'. For example, the name of an invoke wrapper for function void
foo(struct mystruct*, int) will be "__invoke_void_%struct.mystruct*_int".  This
function converts the names of invoke wrappers based on their lowered argument
types and a return type. In the example above, the resulting new wrapper name
becomes "invoke_vii".

* Address comments

Change variable names to camelcase
Add a small (semi-)handwritten test case

* Export malloc and free from wasm when available

* Add a test case for exporting malloc/free feature

+ cosmetic 'file' name change in text_before_type.s

* fixInvokeWrapper -> fixEmExceptionInvoke

* Add a TODO
  • Loading branch information...
aheejin authored and kripken committed Aug 12, 2016
1 parent 8e8c311 commit 1448c6bb26a60b26e70f7e913e56e0d0bf03b5da
View
@@ -554,17 +554,19 @@ class S2WasmBuilder {
void parseFuncType() {
auto decl = make_unique<FunctionType>();
Name name = getCommaSeparated();
Name rawName = getCommaSeparated();
skipComma();
if(match("void")) {
decl->result = none;
} else {
decl->result = getType();
}
while (*s && skipComma()) decl->params.push_back(getType());
decl->name = "FUNCSIG$" + getSig(decl.get());
std::string sig = getSig(decl.get());
decl->name = "FUNCSIG$" + sig;
FunctionType *ty = wasm->checkFunctionType(decl->name);
Name name = fixEmExceptionInvoke(rawName, sig);
if (!ty) {
// The wasm module takes ownership of the FunctionType if we insert it.
// Otherwise it's already in the module and ours is freed.
@@ -876,14 +878,9 @@ class S2WasmBuilder {
} else {
// non-indirect call
Name assign = getAssign();
Name target = linkerObj->resolveAlias(cleanFunction(getCommaSeparated()), LinkerObject::Relocation::kFunction);
Name rawTarget = cleanFunction(getCommaSeparated());
Call* curr = allocator->alloc<Call>();
curr->target = target;
curr->type = type;
if (!linkerObj->isFunctionImplemented(target)) {
linkerObj->addUndefinedFunctionCall(curr);
}
skipWhitespace();
if (*s == ',') {
skipComma();
@@ -893,6 +890,13 @@ class S2WasmBuilder {
curr->operands.push_back(inputs[i]);
}
}
Name target = linkerObj->resolveAlias(
fixEmExceptionInvoke(rawTarget, curr->type, curr->operands),
LinkerObject::Relocation::kFunction);
curr->target = target;
if (!linkerObj->isFunctionImplemented(target)) {
linkerObj->addUndefinedFunctionCall(curr);
}
setOutput(curr, assign);
}
};
@@ -1342,6 +1346,42 @@ class S2WasmBuilder {
}
}
// Converts invoke wrapper names generated by LLVM backend to real invoke
// wrapper names that are expected by JavaScript glue code.
// This is required to support wasm exception handling (asm.js style).
//
// LLVM backend lowers
// invoke @func(arg1, arg2) to label %invoke.cont unwind label %lpad
// into
// ... (some code)
// call @invoke_SIG(func, arg1, arg2)
// ... (some code)
// SIG is a mangled string generated based on the LLVM IR-level function
// signature. In LLVM IR, types are not lowered yet, so this mangling scheme
// simply takes LLVM's string representtion of parameter types and concatenate
// them with '_'. For example, the name of an invoke wrapper for function
// void foo(struct mystruct*, int) will be
// "__invoke_void_%struct.mystruct*_int".
// This function converts the names of invoke wrappers based on their lowered
// argument types and a return type. In the example above, the resulting new
// wrapper name becomes "invoke_vii".
template<typename ListType>
Name fixEmExceptionInvoke(const Name &name, WasmType result,
const ListType &operands) {
return fixEmExceptionInvoke(name, getSig(result, operands));
}
Name fixEmExceptionInvoke(const Name &name, const std::string &sig) {
std::string nameStr = name.c_str();
if (nameStr.front() == '"' && nameStr.back() == '"') {
nameStr = nameStr.substr(1, nameStr.size() - 2);
}
if (nameStr.find("__invoke_") != 0) {
return name;
}
std::string sigWoOrigFunc = sig.front() + sig.substr(2, sig.size() - 2);
return Name("invoke_" + sigWoOrigFunc);
}
};
View
@@ -210,6 +210,19 @@ void Linker::layout() {
auto* func = out.wasm.getFunction(name);
func->type = ensureFunctionType(getSig(func), &out.wasm)->name;
}
// Export malloc and free whenever availble. JavsScript version of malloc has
// some issues and it cannot be called once _sbrk() is called.
// TODO This should get the list of exported functions from emcc.py - it has
// EXPORTED_FUNCTION metadata to keep track of this. Get the list of exported
// functions using a command-line argument from emcc.py and export all of
// them.
if (out.symbolInfo.implementedFunctions.count("malloc")) {
exportFunction("malloc", true);
}
if (out.symbolInfo.implementedFunctions.count("free")) {
exportFunction("free", true);
}
}
bool Linker::linkObject(S2WasmBuilder& builder) {
@@ -0,0 +1,29 @@
.text
.file "export_malloc_free.bc"
.hidden main
.globl main
.type main,@function
main:
.result i32
i32.const $push0=, 0
.endfunc
.Lfunc_end1:
.size main, .Lfunc_end1-main
.weak malloc
.type malloc,@function
malloc:
.param i32
.result i32
i32.const $push0=, 0
.endfunc
.Lfunc_end20:
.size malloc, .Lfunc_end20-malloc
.weak free
.type free,@function
free:
.param i32
.endfunc
.Lfunc_end21:
.size free, .Lfunc_end21-free
@@ -0,0 +1,16 @@
(module
(memory 1)
(export "memory" memory)
(export "main" $main)
(export "malloc" $malloc)
(export "free" $free)
(func $main (result i32)
(i32.const 0)
)
(func $malloc (param $0 i32) (result i32)
(i32.const 0)
)
(func $free (param $0 i32)
)
)
;; METADATA: { "asmConsts": {},"staticBump": 12, "initializers": [] }
@@ -0,0 +1,67 @@
.text
.file "invoke_wrapper.bc"
.type _Z5func1v,@function
_Z5func1v:
.endfunc
.Lfunc_end0:
.size _Z5func1v, .Lfunc_end0-_Z5func1v
.type _Z5func2iii,@function
_Z5func2iii:
.param i32, i32, i32
.result i32
i32.const $push0=, 3
.endfunc
.Lfunc_end1:
.size _Z5func2iii, .Lfunc_end1-_Z5func2iii
.type _Z5func3fd,@function
_Z5func3fd:
.param f32, f64
.result f32
f32.const $push0=, 0x1p0
.endfunc
.Lfunc_end2:
.size _Z5func3fd, .Lfunc_end2-_Z5func3fd
.type _Z5func4P8mystructS_,@function
_Z5func4P8mystructS_:
.param i32, i32
.result i32
i32.const $push0=, 0
.endfunc
.Lfunc_end3:
.size _Z5func4P8mystructS_, .Lfunc_end3-_Z5func4P8mystructS_
.hidden main
.globl main
.type main,@function
main:
.result i32
.local i32, i32, i32, i32
i32.const $push1=, _Z5func1v@FUNCTION
call __invoke_void@FUNCTION, $pop1
i32.const $push5=, _Z5func2iii@FUNCTION
i32.const $push4=, 1
i32.const $push3=, 2
i32.const $push2=, 3
i32.call $drop=, __invoke_i32_i32_i32_i32@FUNCTION, $pop5, $pop4, $pop3, $pop2
i32.const $push9=, _Z5func3fd@FUNCTION
f32.const $push8=, 0x1.8p0
f64.const $push7=, 0x1.b333333333333p1
f32.call $drop=, __invoke_float_float_double@FUNCTION, $pop9, $pop8, $pop7
i32.const $push21=, _Z5func4P8mystructS_@FUNCTION
i32.const $push37=, 32
i32.add $push38=, $1, $pop37
i32.const $push39=, 4
i32.add $push40=, $1, $pop39
i32.call $drop=, "__invoke_%struct.mystruct*_%struct.mystruct*_%struct.mystruct*"@FUNCTION, $pop21, $pop38, $pop40
i32.const $push23=, 0
.endfunc
.Lfunc_end4:
.size main, .Lfunc_end4-main
.functype __invoke_void, void, i32
.functype __invoke_i32_i32_i32_i32, i32, i32, i32, i32, i32
.functype __invoke_float_float_double, f32, i32, f32, f64
.functype __invoke_%struct.mystruct*_%struct.mystruct*_%struct.mystruct*, i32, i32, i32, i32
@@ -0,0 +1,95 @@
(module
(memory 1)
(export "memory" memory)
(type $FUNCSIG$vi (func (param i32)))
(type $FUNCSIG$iiiii (func (param i32 i32 i32 i32) (result i32)))
(type $FUNCSIG$fifd (func (param i32 f32 f64) (result f32)))
(type $FUNCSIG$iiii (func (param i32 i32 i32) (result i32)))
(type $FUNCSIG$v (func))
(type $FUNCSIG$ffd (func (param f32 f64) (result f32)))
(type $FUNCSIG$iii (func (param i32 i32) (result i32)))
(import $invoke_ffd "env" "invoke_ffd" (param i32 f32 f64) (result f32))
(import $invoke_iii "env" "invoke_iii" (param i32 i32 i32) (result i32))
(import $invoke_iiii "env" "invoke_iiii" (param i32 i32 i32 i32) (result i32))
(import $invoke_v "env" "invoke_v" (param i32))
(export "main" $main)
(export "dynCall_v" $dynCall_v)
(export "dynCall_iiii" $dynCall_iiii)
(export "dynCall_ffd" $dynCall_ffd)
(export "dynCall_iii" $dynCall_iii)
(table $__wasm_nullptr $_Z5func1v $_Z5func2iii $_Z5func3fd $_Z5func4P8mystructS_)
(func $_Z5func1v (type $FUNCSIG$v)
)
(func $_Z5func2iii (type $FUNCSIG$iiii) (param $0 i32) (param $1 i32) (param $2 i32) (result i32)
(i32.const 3)
)
(func $_Z5func3fd (type $FUNCSIG$ffd) (param $0 f32) (param $1 f64) (result f32)
(f32.const 1)
)
(func $_Z5func4P8mystructS_ (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32)
(i32.const 0)
)
(func $main (result i32)
(local $0 i32)
(local $1 i32)
(local $2 i32)
(local $3 i32)
(call_import $invoke_v
(i32.const 1)
)
(call_import $invoke_iiii
(i32.const 2)
(i32.const 1)
(i32.const 2)
(i32.const 3)
)
(call_import $invoke_ffd
(i32.const 3)
(f32.const 1.5)
(f64.const 3.4)
)
(call_import $invoke_iii
(i32.const 4)
(i32.add
(get_local $1)
(i32.const 32)
)
(i32.add
(get_local $1)
(i32.const 4)
)
)
(i32.const 0)
)
(func $__wasm_nullptr (type $FUNCSIG$v)
(unreachable)
)
(func $dynCall_v (param $fptr i32)
(call_indirect $FUNCSIG$v
(get_local $fptr)
)
)
(func $dynCall_iiii (param $fptr i32) (param $0 i32) (param $1 i32) (param $2 i32) (result i32)
(call_indirect $FUNCSIG$iiii
(get_local $fptr)
(get_local $0)
(get_local $1)
(get_local $2)
)
)
(func $dynCall_ffd (param $fptr i32) (param $0 f32) (param $1 f64) (result f32)
(call_indirect $FUNCSIG$ffd
(get_local $fptr)
(get_local $0)
(get_local $1)
)
)
(func $dynCall_iii (param $fptr i32) (param $0 i32) (param $1 i32) (result i32)
(call_indirect $FUNCSIG$iii
(get_local $fptr)
(get_local $0)
(get_local $1)
)
)
)
;; METADATA: { "asmConsts": {},"staticBump": 12, "initializers": [] }
@@ -1,5 +1,5 @@
.text
.file "/tmp/tmpGckQku/foo.bc"
.file "text_before_type.bc"
.hidden main
.globl main
.type main,@function

0 comments on commit 1448c6b

Please sign in to comment.