diff options
author | Richard Musiol <mail@richard-musiol.de> | 2018-06-24 17:23:38 +0200 |
---|---|---|
committer | Paul Jolly <paul@myitcv.org.uk> | 2018-06-26 16:40:09 +0000 |
commit | 8997ec1c4e01b01f9950f0869085ef5be6d45ef0 (patch) | |
tree | b69c255f170e99e9bff891f16765cb500cc9bff4 /src/syscall/js | |
parent | d89efc3c06edd25c686717003b4b82e864d7d0bc (diff) | |
download | go-git-8997ec1c4e01b01f9950f0869085ef5be6d45ef0.tar.gz |
syscall/js: use stable references to JavaScript values
This commit changes how JavaScript values are referenced by Go code.
After this change, a JavaScript value is always represented by the same
ref, even if passed multiple times from JavaScript to Go. This allows
Go's == operator to work as expected on js.Value (strict equality).
Additionally, the performance of some operations of the syscall/js
package got improved by saving additional roundtrips to JavaScript code.
Fixes #25802.
Change-Id: Ide6ffe66c6aa1caf5327a2d3ddbe48fe7c180461
Reviewed-on: https://go-review.googlesource.com/120561
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Diffstat (limited to 'src/syscall/js')
-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 |
3 files changed, 94 insertions, 64 deletions
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) { |