Browse Source

wasm: add support for js.FuncOf

pull/381/head
Ayke van Laethem 6 years ago
committed by Ayke
parent
commit
eb1d834dd4
  1. 19
      compiler/goroutine-lowering.go
  2. 5
      src/examples/wasm/Makefile
  3. 19
      src/examples/wasm/callback/index.html
  4. 27
      src/examples/wasm/callback/wasm.go
  5. 26
      src/examples/wasm/callback/wasm.js
  6. 4
      src/runtime/panic.go
  7. 9
      src/runtime/runtime_wasm.go
  8. 3
      src/runtime/scheduler.go
  9. 44
      targets/wasm_exec.js

19
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)

5
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/

19
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>

27
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)
}

26
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();

4
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")
}

9
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

3
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
}

44
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;
};
}
}

Loading…
Cancel
Save