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) 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. // Store return values.
switch inst.OperandsCount() { switch inst.OperandsCount() {
@ -417,7 +430,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
// behavior somehow (with the unreachable instruction). // behavior somehow (with the unreachable instruction).
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{ continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
llvm.ConstNull(c.ctx.TokenType()), llvm.ConstNull(c.ctx.TokenType()),
llvm.ConstInt(c.ctx.Int1Type(), 1, false), llvm.ConstInt(c.ctx.Int1Type(), 0, false),
}, "ret") }, "ret")
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2) sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), frame.unreachableBlock) 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) c.builder.SetInsertPointBefore(deadlockCall)
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{ continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
llvm.ConstNull(c.ctx.TokenType()), 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.splitBasicBlock(deadlockCall, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.wakeup.dead")
c.builder.SetInsertPointBefore(deadlockCall) c.builder.SetInsertPointBefore(deadlockCall)

5
src/examples/wasm/Makefile

@ -3,6 +3,11 @@ export: clean wasm_exec
cp ./export/wasm.js ./html/ cp ./export/wasm.js ./html/
cp ./export/index.html ./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 main: clean wasm_exec
tinygo build -o ./html/wasm.wasm -target wasm -no-debug ./main/main.go tinygo build -o ./html/wasm.wasm -target wasm -no-debug ./main/main.go
cp ./main/index.html ./html/ 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() { func slicePanic() {
runtimePanic("slice out of range") 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) resource_write(stdout, &c, 1)
} }
var handleEvent func()
//go:linkname setEventHandler syscall/js.setEventHandler //go:linkname setEventHandler syscall/js.setEventHandler
func setEventHandler(fn func()) { func setEventHandler(fn func()) {
// TODO handleEvent = fn
}
//go:export resume
func resume() {
handleEvent()
} }
//go:export go_scheduler //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 // getTaskPromisePtr is a helper function to get the current .ptr field from a
// coroutine promise. // coroutine promise.
func getTaskPromisePtr(task *coroutine) unsafe.Pointer { func getTaskPromisePtr(task *coroutine) unsafe.Pointer {
if task == nil {
blockingPanic()
}
return task.promise().ptr return task.promise().ptr
} }

44
targets/wasm_exec.js

@ -240,9 +240,9 @@
}, },
// func valueIndex(v ref, i int) ref // func valueIndex(v ref, i int) ref
//"syscall/js.valueIndex": (sp) => { "syscall/js.valueIndex": (ret_addr, v_addr, i) => {
// storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16))); storeValue(ret_addr, Reflect.get(loadValue(v_addr), i));
//}, },
// valueSetIndex(v ref, i int, x ref) // valueSetIndex(v ref, i int, x ref)
//"syscall/js.valueSetIndex": (sp) => { //"syscall/js.valueSetIndex": (sp) => {
@ -291,9 +291,9 @@
}, },
// func valueLength(v ref) int // func valueLength(v ref) int
//"syscall/js.valueLength": (sp) => { "syscall/js.valueLength": (v_addr) => {
// setInt64(sp + 16, parseInt(loadValue(sp + 8).length)); return loadValue(v_addr).length;
//}, },
// valuePrepareString(v ref) (ref, int) // valuePrepareString(v ref) (ref, int)
"syscall/js.valuePrepareString": (ret_addr, v_addr) => { "syscall/js.valuePrepareString": (ret_addr, v_addr) => {
@ -352,25 +352,23 @@
} }
} }
static _makeCallbackHelper(id, pendingCallbacks, go) { _resume() {
return function () { if (this.exited) {
pendingCallbacks.push({ id: id, args: arguments }); throw new Error("Go program has already exited");
go._resolveCallbackPromise(); }
}; this._inst.exports.resume();
if (this.exited) {
this._resolveExitPromise();
}
} }
static _makeEventCallbackHelper(preventDefault, stopPropagation, stopImmediatePropagation, fn) { _makeFuncWrapper(id) {
return function (event) { const go = this;
if (preventDefault) { return function () {
event.preventDefault(); const event = { id: id, this: this, args: arguments };
} go._pendingEvent = event;
if (stopPropagation) { go._resume();
event.stopPropagation(); return event.result;
}
if (stopImmediatePropagation) {
event.stopImmediatePropagation();
}
fn(event);
}; };
} }
} }

Loading…
Cancel
Save