aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAyke van Laethem <[email protected]>2019-05-24 15:30:09 +0200
committerAyke <[email protected]>2019-05-27 13:35:59 +0200
commiteb1d834dd45e274e935064f5ab363556ee91fd5a (patch)
treec6aa8daed001e10c2c6c6e883d637eaa51959118
parent3313decb6898d28bf8a54c4d1a2bd9b37dff7314 (diff)
downloadtinygo-eb1d834dd45e274e935064f5ab363556ee91fd5a.tar.gz
tinygo-eb1d834dd45e274e935064f5ab363556ee91fd5a.zip
wasm: add support for js.FuncOf
-rw-r--r--compiler/goroutine-lowering.go19
-rw-r--r--src/examples/wasm/Makefile5
-rw-r--r--src/examples/wasm/callback/index.html19
-rw-r--r--src/examples/wasm/callback/wasm.go27
-rw-r--r--src/examples/wasm/callback/wasm.js26
-rw-r--r--src/runtime/panic.go4
-rw-r--r--src/runtime/runtime_wasm.go9
-rw-r--r--src/runtime/scheduler.go3
-rw-r--r--targets/wasm_exec.js44
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;
};
}
}