diff options
author | Ayke van Laethem <[email protected]> | 2019-05-24 15:30:09 +0200 |
---|---|---|
committer | Ayke <[email protected]> | 2019-05-27 13:35:59 +0200 |
commit | eb1d834dd45e274e935064f5ab363556ee91fd5a (patch) | |
tree | c6aa8daed001e10c2c6c6e883d637eaa51959118 | |
parent | 3313decb6898d28bf8a54c4d1a2bd9b37dff7314 (diff) | |
download | tinygo-eb1d834dd45e274e935064f5ab363556ee91fd5a.tar.gz tinygo-eb1d834dd45e274e935064f5ab363556ee91fd5a.zip |
wasm: add support for js.FuncOf
-rw-r--r-- | compiler/goroutine-lowering.go | 19 | ||||
-rw-r--r-- | src/examples/wasm/Makefile | 5 | ||||
-rw-r--r-- | src/examples/wasm/callback/index.html | 19 | ||||
-rw-r--r-- | src/examples/wasm/callback/wasm.go | 27 | ||||
-rw-r--r-- | src/examples/wasm/callback/wasm.js | 26 | ||||
-rw-r--r-- | src/runtime/panic.go | 4 | ||||
-rw-r--r-- | src/runtime/runtime_wasm.go | 9 | ||||
-rw-r--r-- | src/runtime/scheduler.go | 3 | ||||
-rw-r--r-- | targets/wasm_exec.js | 44 |
9 files changed, 129 insertions, 27 deletions
diff --git a/compiler/goroutine-lowering.go b/compiler/goroutine-lowering.go index 2a9276df2..c493bf48c 100644 --- a/compiler/goroutine-lowering.go +++ b/compiler/goroutine-lowering.go @@ -389,7 +389,20 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) { c.builder.SetInsertPointBefore(inst) - parentHandle := f.LastParam() + var parentHandle llvm.Value + if f.Linkage() == llvm.ExternalLinkage { + // Exported function. + // Note that getTaskPromisePtr will panic if it is called with + // a nil pointer, so blocking exported functions that try to + // return anything will not work. + parentHandle = llvm.ConstPointerNull(c.i8ptrType) + } else { + parentHandle = f.LastParam() + if parentHandle.IsNil() || parentHandle.Name() != "parentHandle" { + // sanity check + panic("trying to make exported function async") + } + } // Store return values. switch inst.OperandsCount() { @@ -417,7 +430,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) { // behavior somehow (with the unreachable instruction). continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{ llvm.ConstNull(c.ctx.TokenType()), - llvm.ConstInt(c.ctx.Int1Type(), 1, false), + llvm.ConstInt(c.ctx.Int1Type(), 0, false), }, "ret") sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2) sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), frame.unreachableBlock) @@ -488,7 +501,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) { c.builder.SetInsertPointBefore(deadlockCall) continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{ llvm.ConstNull(c.ctx.TokenType()), - llvm.ConstInt(c.ctx.Int1Type(), 1, false), // final suspend + llvm.ConstInt(c.ctx.Int1Type(), 0, false), }, "") c.splitBasicBlock(deadlockCall, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.wakeup.dead") c.builder.SetInsertPointBefore(deadlockCall) diff --git a/src/examples/wasm/Makefile b/src/examples/wasm/Makefile index a12bfbb3c..78cdaea5b 100644 --- a/src/examples/wasm/Makefile +++ b/src/examples/wasm/Makefile @@ -3,6 +3,11 @@ export: clean wasm_exec cp ./export/wasm.js ./html/ cp ./export/index.html ./html/ +callback: clean wasm_exec + tinygo build -o ./html/wasm.wasm -target wasm ./callback/wasm.go + cp ./callback/wasm.js ./html/ + cp ./callback/index.html ./html/ + main: clean wasm_exec tinygo build -o ./html/wasm.wasm -target wasm -no-debug ./main/main.go cp ./main/index.html ./html/ diff --git a/src/examples/wasm/callback/index.html b/src/examples/wasm/callback/index.html new file mode 100644 index 000000000..4cca19625 --- /dev/null +++ b/src/examples/wasm/callback/index.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> + +<html> + +<head> + <meta charset="utf-8" /> + <title>Go WebAssembly</title> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <script src="wasm_exec.js" defer></script> + <script src="wasm.js" defer></script> +</head> + +<body> + <h1>WebAssembly</h1> + <p>Add two numbers, using WebAssembly:</p> + <input type="number" id="a" value="0" /> + <input type="number" id="b" value="0" /> = <input type="number" id="result" readonly /> +</body> + +</html> diff --git a/src/examples/wasm/callback/wasm.go b/src/examples/wasm/callback/wasm.go new file mode 100644 index 000000000..da86550b4 --- /dev/null +++ b/src/examples/wasm/callback/wasm.go @@ -0,0 +1,27 @@ +package main + +import ( + "strconv" + "syscall/js" +) + +var a, b int + +func main() { + document := js.Global().Get("document") + document.Call("getElementById", "a").Set("oninput", updater(&a)) + document.Call("getElementById", "b").Set("oninput", updater(&b)) + update() +} + +func updater(n *int) js.Func { + return js.FuncOf(func(this js.Value, args []js.Value) interface{} { + *n, _ = strconv.Atoi(this.Get("value").String()) + update() + return nil + }) +} + +func update() { + js.Global().Get("document").Call("getElementById", "result").Set("value", a+b) +} diff --git a/src/examples/wasm/callback/wasm.js b/src/examples/wasm/callback/wasm.js new file mode 100644 index 000000000..378cf726c --- /dev/null +++ b/src/examples/wasm/callback/wasm.js @@ -0,0 +1,26 @@ +'use strict'; + +const WASM_URL = 'wasm.wasm'; + +var wasm; + +function init() { + const go = new Go(); + if ('instantiateStreaming' in WebAssembly) { + WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function (obj) { + wasm = obj.instance; + go.run(wasm); + }) + } else { + fetch(WASM_URL).then(resp => + resp.arrayBuffer() + ).then(bytes => + WebAssembly.instantiate(bytes, go.importObject).then(function (obj) { + wasm = obj.instance; + go.run(wasm); + }) + ) + } +} + +init(); diff --git a/src/runtime/panic.go b/src/runtime/panic.go index a297795e1..e8aafa744 100644 --- a/src/runtime/panic.go +++ b/src/runtime/panic.go @@ -49,3 +49,7 @@ func lookupPanic() { func slicePanic() { runtimePanic("slice out of range") } + +func blockingPanic() { + runtimePanic("trying to do blocking operation in exported function") +} diff --git a/src/runtime/runtime_wasm.go b/src/runtime/runtime_wasm.go index e01845a52..bab786cbd 100644 --- a/src/runtime/runtime_wasm.go +++ b/src/runtime/runtime_wasm.go @@ -37,9 +37,16 @@ func putchar(c byte) { resource_write(stdout, &c, 1) } +var handleEvent func() + //go:linkname setEventHandler syscall/js.setEventHandler func setEventHandler(fn func()) { - // TODO + handleEvent = fn +} + +//go:export resume +func resume() { + handleEvent() } //go:export go_scheduler diff --git a/src/runtime/scheduler.go b/src/runtime/scheduler.go index b4af2360e..9d7bc1def 100644 --- a/src/runtime/scheduler.go +++ b/src/runtime/scheduler.go @@ -120,6 +120,9 @@ func setTaskPromisePtr(task *coroutine, value unsafe.Pointer) { // getTaskPromisePtr is a helper function to get the current .ptr field from a // coroutine promise. func getTaskPromisePtr(task *coroutine) unsafe.Pointer { + if task == nil { + blockingPanic() + } return task.promise().ptr } diff --git a/targets/wasm_exec.js b/targets/wasm_exec.js index 87522fa97..951b085cc 100644 --- a/targets/wasm_exec.js +++ b/targets/wasm_exec.js @@ -240,9 +240,9 @@ }, // func valueIndex(v ref, i int) ref - //"syscall/js.valueIndex": (sp) => { - // storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16))); - //}, + "syscall/js.valueIndex": (ret_addr, v_addr, i) => { + storeValue(ret_addr, Reflect.get(loadValue(v_addr), i)); + }, // valueSetIndex(v ref, i int, x ref) //"syscall/js.valueSetIndex": (sp) => { @@ -291,9 +291,9 @@ }, // func valueLength(v ref) int - //"syscall/js.valueLength": (sp) => { - // setInt64(sp + 16, parseInt(loadValue(sp + 8).length)); - //}, + "syscall/js.valueLength": (v_addr) => { + return loadValue(v_addr).length; + }, // valuePrepareString(v ref) (ref, int) "syscall/js.valuePrepareString": (ret_addr, v_addr) => { @@ -352,25 +352,23 @@ } } - static _makeCallbackHelper(id, pendingCallbacks, go) { - return function () { - pendingCallbacks.push({ id: id, args: arguments }); - go._resolveCallbackPromise(); - }; + _resume() { + if (this.exited) { + throw new Error("Go program has already exited"); + } + this._inst.exports.resume(); + if (this.exited) { + this._resolveExitPromise(); + } } - static _makeEventCallbackHelper(preventDefault, stopPropagation, stopImmediatePropagation, fn) { - return function (event) { - if (preventDefault) { - event.preventDefault(); - } - if (stopPropagation) { - event.stopPropagation(); - } - if (stopImmediatePropagation) { - event.stopImmediatePropagation(); - } - fn(event); + _makeFuncWrapper(id) { + const go = this; + return function () { + const event = { id: id, this: this, args: arguments }; + go._pendingEvent = event; + go._resume(); + return event.result; }; } } |