summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--misc/wasm/wasm_exec.js118
-rw-r--r--src/cmd/vet/all/whitelist/wasm.txt22
-rw-r--r--src/syscall/js/js.go106
-rw-r--r--src/syscall/js/js_js.s24
-rw-r--r--src/syscall/js/js_test.go28
5 files changed, 174 insertions, 124 deletions
diff --git a/misc/wasm/wasm_exec.js b/misc/wasm/wasm_exec.js
index 4c29109766..ecb096509f 100644
--- a/misc/wasm/wasm_exec.js
+++ b/misc/wasm/wasm_exec.js
@@ -31,7 +31,7 @@
let outputBuf = "";
global.fs = {
- constants: {},
+ constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1, O_NONBLOCK: -1, O_SYNC: -1 }, // unused
writeSync(fd, buf) {
outputBuf += decoder.decode(buf);
const nl = outputBuf.lastIndexOf("\n");
@@ -81,21 +81,72 @@
}
const loadValue = (addr) => {
+ const f = mem().getFloat64(addr, true);
+ if (!isNaN(f)) {
+ return f;
+ }
+
const id = mem().getUint32(addr, true);
return this._values[id];
}
const storeValue = (addr, v) => {
- if (v === undefined) {
- mem().setUint32(addr, 0, true);
+ if (typeof v === "number") {
+ if (isNaN(v)) {
+ mem().setUint32(addr + 4, 0x7FF80000, true); // NaN
+ mem().setUint32(addr, 0, true);
+ return;
+ }
+ mem().setFloat64(addr, v, true);
return;
}
- if (v === null) {
- mem().setUint32(addr, 1, true);
+
+ mem().setUint32(addr + 4, 0x7FF80000, true); // NaN
+
+ switch (v) {
+ case undefined:
+ mem().setUint32(addr, 1, true);
+ return;
+ case null:
+ mem().setUint32(addr, 2, true);
+ return;
+ case true:
+ mem().setUint32(addr, 3, true);
+ return;
+ case false:
+ mem().setUint32(addr, 4, true);
+ return;
+ }
+
+ if (typeof v === "string") {
+ let ref = this._stringRefs.get(v);
+ if (ref === undefined) {
+ ref = this._values.length;
+ this._values.push(v);
+ this._stringRefs.set(v, ref);
+ }
+ mem().setUint32(addr, ref, true);
return;
}
- this._values.push(v);
- mem().setUint32(addr, this._values.length - 1, true);
+
+ if (typeof v === "symbol") {
+ let ref = this._symbolRefs.get(v);
+ if (ref === undefined) {
+ ref = this._values.length;
+ this._values.push(v);
+ this._symbolRefs.set(v, ref);
+ }
+ mem().setUint32(addr, ref, true);
+ return;
+ }
+
+ let ref = v[this._refProp];
+ if (ref === undefined) {
+ ref = this._values.length;
+ this._values.push(v);
+ v[this._refProp] = ref;
+ }
+ mem().setUint32(addr, ref, true);
}
const loadSlice = (addr) => {
@@ -109,8 +160,7 @@
const len = getInt64(addr + 8);
const a = new Array(len);
for (let i = 0; i < len; i++) {
- const id = mem().getUint32(array + i * 4, true);
- a[i] = this._values[id];
+ a[i] = loadValue(array + i * 8);
}
return a;
}
@@ -173,21 +223,6 @@
crypto.getRandomValues(loadSlice(sp + 8));
},
- // func boolVal(value bool) ref
- "syscall/js.boolVal": (sp) => {
- storeValue(sp + 16, mem().getUint8(sp + 8) !== 0);
- },
-
- // func intVal(value int) ref
- "syscall/js.intVal": (sp) => {
- storeValue(sp + 16, getInt64(sp + 8));
- },
-
- // func floatVal(value float64) ref
- "syscall/js.floatVal": (sp) => {
- storeValue(sp + 16, mem().getFloat64(sp + 8, true));
- },
-
// func stringVal(value string) ref
"syscall/js.stringVal": (sp) => {
storeValue(sp + 24, loadString(sp + 8));
@@ -220,10 +255,10 @@
const m = Reflect.get(v, loadString(sp + 16));
const args = loadSliceOfValues(sp + 32);
storeValue(sp + 56, Reflect.apply(m, v, args));
- mem().setUint8(sp + 60, 1);
+ mem().setUint8(sp + 64, 1);
} catch (err) {
storeValue(sp + 56, err);
- mem().setUint8(sp + 60, 0);
+ mem().setUint8(sp + 64, 0);
}
},
@@ -233,10 +268,10 @@
const v = loadValue(sp + 8);
const args = loadSliceOfValues(sp + 16);
storeValue(sp + 40, Reflect.apply(v, undefined, args));
- mem().setUint8(sp + 44, 1);
+ mem().setUint8(sp + 48, 1);
} catch (err) {
storeValue(sp + 40, err);
- mem().setUint8(sp + 44, 0);
+ mem().setUint8(sp + 48, 0);
}
},
@@ -246,28 +281,13 @@
const v = loadValue(sp + 8);
const args = loadSliceOfValues(sp + 16);
storeValue(sp + 40, Reflect.construct(v, args));
- mem().setUint8(sp + 44, 1);
+ mem().setUint8(sp + 48, 1);
} catch (err) {
storeValue(sp + 40, err);
- mem().setUint8(sp + 44, 0);
+ mem().setUint8(sp + 48, 0);
}
},
- // func valueFloat(v ref) float64
- "syscall/js.valueFloat": (sp) => {
- mem().setFloat64(sp + 16, parseFloat(loadValue(sp + 8)), true);
- },
-
- // func valueInt(v ref) int
- "syscall/js.valueInt": (sp) => {
- setInt64(sp + 16, parseInt(loadValue(sp + 8)));
- },
-
- // func valueBool(v ref) bool
- "syscall/js.valueBool": (sp) => {
- mem().setUint8(sp + 16, !!loadValue(sp + 8));
- },
-
// func valueLength(v ref) int
"syscall/js.valueLength": (sp) => {
setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
@@ -288,7 +308,7 @@
// func valueInstanceOf(v ref, t ref) bool
"syscall/js.valueInstanceOf": (sp) => {
- mem().setUint8(sp + 16, loadValue(sp + 8) instanceof loadValue(sp + 12));
+ mem().setUint8(sp + 24, loadValue(sp + 8) instanceof loadValue(sp + 16));
},
"debug": (value) => {
@@ -301,8 +321,11 @@
async run(instance) {
this._inst = instance;
this._values = [ // TODO: garbage collection
+ NaN,
undefined,
null,
+ true,
+ false,
global,
this._inst.exports.mem,
() => { // resolveCallbackPromise
@@ -312,6 +335,9 @@
setTimeout(this._resolveCallbackPromise, 0); // make sure it is asynchronous
},
];
+ this._stringRefs = new Map();
+ this._symbolRefs = new Map();
+ this._refProp = Symbol();
this.exited = false;
const mem = new DataView(this._inst.exports.mem.buffer)
diff --git a/src/cmd/vet/all/whitelist/wasm.txt b/src/cmd/vet/all/whitelist/wasm.txt
index 2b59e5a700..7a8037f085 100644
--- a/src/cmd/vet/all/whitelist/wasm.txt
+++ b/src/cmd/vet/all/whitelist/wasm.txt
@@ -17,18 +17,12 @@ runtime/asm_wasm.s: [wasm] rt0_go: use of 8(SP) points beyond argument frame
// Calling WebAssembly import. No write from Go assembly.
runtime/sys_wasm.s: [wasm] nanotime: RET without writing to 8-byte ret+0(FP)
runtime/sys_wasm.s: [wasm] scheduleCallback: RET without writing to 4-byte ret+8(FP)
-syscall/js/js_js.s: [wasm] boolVal: RET without writing to 4-byte ret+8(FP)
-syscall/js/js_js.s: [wasm] intVal: RET without writing to 4-byte ret+8(FP)
-syscall/js/js_js.s: [wasm] floatVal: RET without writing to 4-byte ret+8(FP)
-syscall/js/js_js.s: [wasm] stringVal: RET without writing to 4-byte ret+16(FP)
-syscall/js/js_js.s: [wasm] valueGet: RET without writing to 4-byte ret+24(FP)
-syscall/js/js_js.s: [wasm] valueIndex: RET without writing to 4-byte ret+16(FP)
-syscall/js/js_js.s: [wasm] valueCall: RET without writing to 4-byte ret+48(FP)
-syscall/js/js_js.s: [wasm] valueInvoke: RET without writing to 4-byte ret+32(FP)
-syscall/js/js_js.s: [wasm] valueNew: RET without writing to 4-byte ret+32(FP)
-syscall/js/js_js.s: [wasm] valueFloat: RET without writing to 8-byte ret+8(FP)
-syscall/js/js_js.s: [wasm] valueInt: RET without writing to 8-byte ret+8(FP)
-syscall/js/js_js.s: [wasm] valueBool: RET without writing to 1-byte ret+8(FP)
+syscall/js/js_js.s: [wasm] stringVal: RET without writing to 8-byte ret+16(FP)
+syscall/js/js_js.s: [wasm] valueGet: RET without writing to 8-byte ret+24(FP)
+syscall/js/js_js.s: [wasm] valueIndex: RET without writing to 8-byte ret+16(FP)
+syscall/js/js_js.s: [wasm] valueCall: RET without writing to 8-byte ret+48(FP)
+syscall/js/js_js.s: [wasm] valueInvoke: RET without writing to 8-byte ret+32(FP)
+syscall/js/js_js.s: [wasm] valueNew: RET without writing to 8-byte ret+32(FP)
syscall/js/js_js.s: [wasm] valueLength: RET without writing to 8-byte ret+8(FP)
-syscall/js/js_js.s: [wasm] valuePrepareString: RET without writing to 4-byte ret+8(FP)
-syscall/js/js_js.s: [wasm] valueInstanceOf: RET without writing to 1-byte ret+8(FP)
+syscall/js/js_js.s: [wasm] valuePrepareString: RET without writing to 8-byte ret+8(FP)
+syscall/js/js_js.s: [wasm] valueInstanceOf: RET without writing to 1-byte ret+16(FP)
diff --git a/src/syscall/js/js.go b/src/syscall/js/js.go
index 93c3965246..8217c24c5e 100644
--- a/src/syscall/js/js.go
+++ b/src/syscall/js/js.go
@@ -15,8 +15,14 @@ import (
"unsafe"
)
-// ref is used to identify a JavaScript value, since the value itself can not be passed to WebAssembly itself.
-type ref uint32
+// ref is used to identify a JavaScript value, since the value itself can not be passed to WebAssembly.
+// A JavaScript number (64-bit float, except NaN) is represented by its IEEE 754 binary representation.
+// All other values are represented as an IEEE 754 binary representation of NaN with the low 32 bits
+// used as an ID.
+type ref uint64
+
+// nanHead are the upper 32 bits of a ref if the value is not a JavaScript number or NaN itself.
+const nanHead = 0x7FF80000
// Value represents a JavaScript value.
type Value struct {
@@ -27,6 +33,17 @@ func makeValue(v ref) Value {
return Value{ref: v}
}
+func predefValue(id uint32) Value {
+ return Value{ref: nanHead<<32 | ref(id)}
+}
+
+func floatValue(f float64) Value {
+ if f != f {
+ return valueNaN
+ }
+ return Value{ref: *(*ref)(unsafe.Pointer(&f))}
+}
+
// Error wraps a JavaScript error.
type Error struct {
// Value is the underlying JavaScript error value.
@@ -39,11 +56,14 @@ func (e Error) Error() string {
}
var (
- valueUndefined = makeValue(0)
- valueNull = makeValue(1)
- valueGlobal = makeValue(2)
- memory = makeValue(3) // WebAssembly linear memory
- resolveCallbackPromise = makeValue(4) // function that the callback helper uses to resume the execution of Go's WebAssembly code
+ valueNaN = predefValue(0)
+ valueUndefined = predefValue(1)
+ valueNull = predefValue(2)
+ valueTrue = predefValue(3)
+ valueFalse = predefValue(4)
+ valueGlobal = predefValue(5)
+ memory = predefValue(6) // WebAssembly linear memory
+ resolveCallbackPromise = predefValue(7) // function that the callback helper uses to resume the execution of Go's WebAssembly code
)
// Undefined returns the JavaScript value "undefined".
@@ -73,35 +93,39 @@ func ValueOf(x interface{}) Value {
case nil:
return valueNull
case bool:
- return makeValue(boolVal(x))
+ if x {
+ return valueTrue
+ } else {
+ return valueFalse
+ }
case int:
- return makeValue(intVal(x))
+ return floatValue(float64(x))
case int8:
- return makeValue(intVal(int(x)))
+ return floatValue(float64(x))
case int16:
- return makeValue(intVal(int(x)))
+ return floatValue(float64(x))
case int32:
- return makeValue(intVal(int(x)))
+ return floatValue(float64(x))
case int64:
- return makeValue(intVal(int(x)))
+ return floatValue(float64(x))
case uint:
- return makeValue(intVal(int(x)))
+ return floatValue(float64(x))
case uint8:
- return makeValue(intVal(int(x)))
+ return floatValue(float64(x))
case uint16:
- return makeValue(intVal(int(x)))
+ return floatValue(float64(x))
case uint32:
- return makeValue(intVal(int(x)))
+ return floatValue(float64(x))
case uint64:
- return makeValue(intVal(int(x)))
+ return floatValue(float64(x))
case uintptr:
- return makeValue(intVal(int(x)))
+ return floatValue(float64(x))
case unsafe.Pointer:
- return makeValue(intVal(int(uintptr(x))))
+ return floatValue(float64(uintptr(x)))
case float32:
- return makeValue(floatVal(float64(x)))
+ return floatValue(float64(x))
case float64:
- return makeValue(floatVal(x))
+ return floatValue(x)
case string:
return makeValue(stringVal(x))
case []byte:
@@ -114,12 +138,6 @@ func ValueOf(x interface{}) Value {
}
}
-func boolVal(x bool) ref
-
-func intVal(x int) ref
-
-func floatVal(x float64) ref
-
func stringVal(x string) ref
// Get returns the JavaScript property p of value v.
@@ -201,27 +219,35 @@ func (v Value) New(args ...interface{}) Value {
func valueNew(v ref, args []ref) (ref, bool)
-// Float returns the value v converted to float64 according to JavaScript type conversions (parseFloat).
-func (v Value) Float() float64 {
- return valueFloat(v.ref)
+func (v Value) isNumber() bool {
+ return v.ref>>32 != nanHead || v.ref == valueNaN.ref
}
-func valueFloat(v ref) float64
+// Float returns the value v as a float64. It panics if v is not a JavaScript number.
+func (v Value) Float() float64 {
+ if !v.isNumber() {
+ panic("syscall/js: not a number")
+ }
+ return *(*float64)(unsafe.Pointer(&v.ref))
+}
-// Int returns the value v converted to int according to JavaScript type conversions (parseInt).
+// Int returns the value v truncated to an int. It panics if v is not a JavaScript number.
func (v Value) Int() int {
- return valueInt(v.ref)
+ return int(v.Float())
}
-func valueInt(v ref) int
-
-// Bool returns the value v converted to bool according to JavaScript type conversions.
+// Bool returns the value v as a bool. It panics if v is not a JavaScript boolean.
func (v Value) Bool() bool {
- return valueBool(v.ref)
+ switch v.ref {
+ case valueTrue.ref:
+ return true
+ case valueFalse.ref:
+ return false
+ default:
+ panic("syscall/js: not a boolean")
+ }
}
-func valueBool(v ref) bool
-
// String returns the value v converted to string according to JavaScript type conversions.
func (v Value) String() string {
str, length := valuePrepareString(v.ref)
diff --git a/src/syscall/js/js_js.s b/src/syscall/js/js_js.s
index cb90d88a6a..0ec164d5cb 100644
--- a/src/syscall/js/js_js.s
+++ b/src/syscall/js/js_js.s
@@ -4,18 +4,6 @@
#include "textflag.h"
-TEXT ·boolVal(SB), NOSPLIT, $0
- CallImport
- RET
-
-TEXT ·intVal(SB), NOSPLIT, $0
- CallImport
- RET
-
-TEXT ·floatVal(SB), NOSPLIT, $0
- CallImport
- RET
-
TEXT ·stringVal(SB), NOSPLIT, $0
CallImport
RET
@@ -48,18 +36,6 @@ TEXT ·valueNew(SB), NOSPLIT, $0
CallImport
RET
-TEXT ·valueFloat(SB), NOSPLIT, $0
- CallImport
- RET
-
-TEXT ·valueInt(SB), NOSPLIT, $0
- CallImport
- RET
-
-TEXT ·valueBool(SB), NOSPLIT, $0
- CallImport
- RET
-
TEXT ·valueLength(SB), NOSPLIT, $0
CallImport
RET
diff --git a/src/syscall/js/js_test.go b/src/syscall/js/js_test.go
index e5e950f3a3..c96ad82850 100644
--- a/src/syscall/js/js_test.go
+++ b/src/syscall/js/js_test.go
@@ -8,6 +8,7 @@ package js_test
import (
"fmt"
+ "math"
"syscall/js"
"testing"
)
@@ -21,6 +22,7 @@ var dummys = js.Global().Call("eval", `({
add: function(a, b) {
return a + b;
},
+ NaN: NaN,
})`)
func TestBool(t *testing.T) {
@@ -33,6 +35,9 @@ func TestBool(t *testing.T) {
if got := dummys.Get("otherBool").Bool(); got != want {
t.Errorf("got %#v, want %#v", got, want)
}
+ if dummys.Get("someBool") != dummys.Get("someBool") {
+ t.Errorf("same value not equal")
+ }
}
func TestString(t *testing.T) {
@@ -45,6 +50,9 @@ func TestString(t *testing.T) {
if got := dummys.Get("otherString").String(); got != want {
t.Errorf("got %#v, want %#v", got, want)
}
+ if dummys.Get("someString") != dummys.Get("someString") {
+ t.Errorf("same value not equal")
+ }
}
func TestInt(t *testing.T) {
@@ -57,6 +65,9 @@ func TestInt(t *testing.T) {
if got := dummys.Get("otherInt").Int(); got != want {
t.Errorf("got %#v, want %#v", got, want)
}
+ if dummys.Get("someInt") != dummys.Get("someInt") {
+ t.Errorf("same value not equal")
+ }
}
func TestIntConversion(t *testing.T) {
@@ -87,6 +98,23 @@ func TestFloat(t *testing.T) {
if got := dummys.Get("otherFloat").Float(); got != want {
t.Errorf("got %#v, want %#v", got, want)
}
+ if dummys.Get("someFloat") != dummys.Get("someFloat") {
+ t.Errorf("same value not equal")
+ }
+}
+
+func TestObject(t *testing.T) {
+ if dummys.Get("someArray") != dummys.Get("someArray") {
+ t.Errorf("same value not equal")
+ }
+}
+
+func TestNaN(t *testing.T) {
+ want := js.ValueOf(math.NaN())
+ got := dummys.Get("NaN")
+ if got != want {
+ t.Errorf("got %#v, want %#v", got, want)
+ }
}
func TestUndefined(t *testing.T) {