diff options
-rw-r--r-- | misc/wasm/wasm_exec.js | 118 | ||||
-rw-r--r-- | src/cmd/vet/all/whitelist/wasm.txt | 22 | ||||
-rw-r--r-- | src/syscall/js/js.go | 106 | ||||
-rw-r--r-- | src/syscall/js/js_js.s | 24 | ||||
-rw-r--r-- | src/syscall/js/js_test.go | 28 |
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) { |