Skip to content

syscall/js.FuncOf support #367

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions compiler/asserts.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Valu

// Fail: this is a nil pointer, exit with a panic.
c.builder.SetInsertPointAtEnd(faultBlock)
c.createRuntimeCall("lookuppanic", nil, "")
c.createRuntimeCall("lookupPanic", nil, "")
c.builder.CreateUnreachable()

// Ok: this is a valid pointer.
Expand Down Expand Up @@ -103,7 +103,7 @@ func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high llvm.V

// Fail: this is a nil pointer, exit with a panic.
c.builder.SetInsertPointAtEnd(faultBlock)
c.createRuntimeCall("slicepanic", nil, "")
c.createRuntimeCall("slicePanic", nil, "")
c.builder.CreateUnreachable()

// Ok: this is a valid pointer.
Expand Down Expand Up @@ -146,7 +146,7 @@ func (c *Compiler) emitNilCheck(frame *Frame, ptr llvm.Value, blockPrefix string

// Fail: this is a nil pointer, exit with a panic.
c.builder.SetInsertPointAtEnd(faultBlock)
c.createRuntimeCall("nilpanic", nil, "")
c.createRuntimeCall("nilPanic", nil, "")
c.builder.CreateUnreachable()

// Ok: this is a valid pointer.
Expand Down
8 changes: 4 additions & 4 deletions compiler/func-lowering.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,17 +154,17 @@ func (c *Compiler) LowerFuncValues() {
// What we'll do is transform the following:
// rawPtr := runtime.getFuncPtr(fn)
// if func.rawPtr == nil {
// runtime.nilpanic()
// runtime.nilPanic()
// }
// result := func.rawPtr(...args, func.context)
// into this:
// if false {
// runtime.nilpanic()
// runtime.nilPanic()
// }
// var result // Phi
// switch fn.id {
// case 0:
// runtime.nilpanic()
// runtime.nilPanic()
// case 1:
// result = call first implementation...
// case 2:
Expand Down Expand Up @@ -222,7 +222,7 @@ func (c *Compiler) LowerFuncValues() {
// The 0 case, which is actually a nil check.
nilBlock := llvm.InsertBasicBlock(nextBlock, "func.nil")
c.builder.SetInsertPointAtEnd(nilBlock)
c.createRuntimeCall("nilpanic", nil, "")
c.createRuntimeCall("nilPanic", nil, "")
c.builder.CreateUnreachable()
sw.AddCase(llvm.ConstInt(c.uintptrType, 0, false), nilBlock)

Expand Down
19 changes: 16 additions & 3 deletions compiler/goroutine-lowering.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
5 changes: 5 additions & 0 deletions src/examples/wasm/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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 ./main/main.go
cp ./main/index.html ./html/
Expand Down
19 changes: 19 additions & 0 deletions src/examples/wasm/callback/index.html
Original file line number Diff line number Diff line change
@@ -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 changes: 27 additions & 0 deletions src/examples/wasm/callback/wasm.go
Original file line number Diff line number Diff line change
@@ -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 changes: 26 additions & 0 deletions src/examples/wasm/callback/wasm.js
Original file line number Diff line number Diff line change
@@ -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();
10 changes: 7 additions & 3 deletions src/runtime/panic.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,20 @@ func isnil(ptr *uint8) bool {
}

// Panic when trying to dereference a nil pointer.
func nilpanic() {
func nilPanic() {
runtimePanic("nil pointer dereference")
}

// Panic when trying to acces an array or slice out of bounds.
func lookuppanic() {
func lookupPanic() {
runtimePanic("index out of range")
}

// Panic when trying to slice a slice out of bounds.
func slicepanic() {
func slicePanic() {
runtimePanic("slice out of range")
}

func blockingPanic() {
runtimePanic("trying to do blocking operation in exported function")
}
9 changes: 8 additions & 1 deletion src/runtime/runtime_wasm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions src/runtime/scheduler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
44 changes: 21 additions & 23 deletions targets/wasm_exec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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;
};
}
}
Expand Down