diff --git a/pkg/dart2wasm/lib/js/util.dart b/pkg/dart2wasm/lib/js/util.dart index 13590be258e..f643a28f4f8 100644 --- a/pkg/dart2wasm/lib/js/util.dart +++ b/pkg/dart2wasm/lib/js/util.dart @@ -129,44 +129,94 @@ class CoreTypesUtil { coreTypes.functionClass: jsifyFunction, }; - late final Map _dartifyMap = { + /// Conversion functions from `WasmExternRef?`. These functions should check + /// for `null` and `undefined` values, and types to prevent nulls from flowing + /// into non-nullable Dart values or wrapping references with incorrect types. + /// (e.g. a `String` as `Uint8list`) + /// + /// Return values should be non-nullable. + late final Map _dartifyNonNullableMap = { coreTypes.boolClass: coreTypes.index.getTopLevelProcedure('dart:_js_helper', 'toDartBool'), coreTypes.intClass: - coreTypes.index.getTopLevelProcedure('dart:_js_helper', 'dartifyInt'), + coreTypes.index.getTopLevelProcedure('dart:_js_helper', 'toDartInt'), coreTypes.doubleClass: - coreTypes.index.getTopLevelProcedure('dart:_js_helper', 'toDartNumber'), + coreTypes.index.getTopLevelProcedure('dart:_js_helper', 'toDartDouble'), coreTypes.numClass: - coreTypes.index.getTopLevelProcedure('dart:_js_helper', 'toDartNumber'), + coreTypes.index.getTopLevelProcedure('dart:_js_helper', 'toDartDouble'), coreTypes.stringClass: coreTypes.index.getProcedure('dart:_string', 'JSStringImpl', 'fromRef'), coreTypes.listClass: coreTypes.index.getTopLevelProcedure('dart:_js_helper', 'toDartList'), coreTypes.index.getClass('dart:typed_data', 'Int8List'): coreTypes.index - .getProcedure('dart:_js_types', 'JSInt8ArrayImpl', 'fromArrayRef'), + .getProcedure('dart:_js_types', 'JSInt8ArrayImpl', 'fromRef'), coreTypes.index.getClass('dart:typed_data', 'Uint8List'): coreTypes.index - .getProcedure('dart:_js_types', 'JSUint8ArrayImpl', 'fromArrayRef'), - coreTypes.index.getClass('dart:typed_data', 'Uint8ClampedList'): - coreTypes.index.getProcedure( - 'dart:_js_types', 'JSUint8ClampedArrayImpl', 'fromArrayRef'), + .getProcedure('dart:_js_types', 'JSUint8ArrayImpl', 'fromRef'), + coreTypes.index.getClass('dart:typed_data', 'Uint8ClampedList'): coreTypes + .index + .getProcedure('dart:_js_types', 'JSUint8ClampedArrayImpl', 'fromRef'), coreTypes.index.getClass('dart:typed_data', 'Int16List'): coreTypes.index - .getProcedure('dart:_js_types', 'JSInt16ArrayImpl', 'fromArrayRef'), + .getProcedure('dart:_js_types', 'JSInt16ArrayImpl', 'fromRef'), coreTypes.index.getClass('dart:typed_data', 'Uint16List'): coreTypes.index - .getProcedure('dart:_js_types', 'JSUint16ArrayImpl', 'fromArrayRef'), + .getProcedure('dart:_js_types', 'JSUint16ArrayImpl', 'fromRef'), coreTypes.index.getClass('dart:typed_data', 'Int32List'): coreTypes.index - .getProcedure('dart:_js_types', 'JSInt32ArrayImpl', 'fromArrayRef'), + .getProcedure('dart:_js_types', 'JSInt32ArrayImpl', 'fromRef'), coreTypes.index.getClass('dart:typed_data', 'Uint32List'): coreTypes.index - .getProcedure('dart:_js_types', 'JSUint32ArrayImpl', 'fromArrayRef'), + .getProcedure('dart:_js_types', 'JSUint32ArrayImpl', 'fromRef'), coreTypes.index.getClass('dart:typed_data', 'Float32List'): coreTypes.index - .getProcedure('dart:_js_types', 'JSFloat32ArrayImpl', 'fromArrayRef'), + .getProcedure('dart:_js_types', 'JSFloat32ArrayImpl', 'fromRef'), coreTypes.index.getClass('dart:typed_data', 'Float64List'): coreTypes.index - .getProcedure('dart:_js_types', 'JSFloat64ArrayImpl', 'fromArrayRef'), + .getProcedure('dart:_js_types', 'JSFloat64ArrayImpl', 'fromRef'), coreTypes.index.getClass('dart:typed_data', 'ByteBuffer'): coreTypes.index .getProcedure('dart:_js_types', 'JSArrayBufferImpl', 'fromRef'), coreTypes.index.getClass('dart:typed_data', 'ByteData'): coreTypes.index .getProcedure('dart:_js_types', 'JSDataViewImpl', 'fromRef'), }; + /// Similar to [_dartifyNonNullableMap], but return values should be nullable. + /// + /// These shouldn't throw on `null` or `undefined` arguments and instead + /// return Dart `null`. + late final Map _dartifyNullableMap = { + coreTypes.boolClass: coreTypes.index + .getTopLevelProcedure('dart:_js_helper', 'toDartNullableBool'), + coreTypes.intClass: coreTypes.index + .getTopLevelProcedure('dart:_js_helper', 'toDartNullableInt'), + coreTypes.doubleClass: coreTypes.index + .getTopLevelProcedure('dart:_js_helper', 'toDartNullableDouble'), + coreTypes.numClass: coreTypes.index + .getTopLevelProcedure('dart:_js_helper', 'toDartNullableDouble'), + coreTypes.stringClass: coreTypes.index + .getProcedure('dart:_string', 'JSStringImpl', 'fromRefNullable'), + coreTypes.listClass: coreTypes.index + .getTopLevelProcedure('dart:_js_helper', 'toDartNullableList'), + coreTypes.index.getClass('dart:typed_data', 'Int8List'): coreTypes.index + .getProcedure('dart:_js_types', 'JSInt8ArrayImpl', 'fromRefNullable'), + coreTypes.index.getClass('dart:typed_data', 'Uint8List'): coreTypes.index + .getProcedure('dart:_js_types', 'JSUint8ArrayImpl', 'fromRefNullable'), + coreTypes.index.getClass('dart:typed_data', 'Uint8ClampedList'): + coreTypes.index.getProcedure( + 'dart:_js_types', 'JSUint8ClampedArrayImpl', 'fromRefNullable'), + coreTypes.index.getClass('dart:typed_data', 'Int16List'): coreTypes.index + .getProcedure('dart:_js_types', 'JSInt16ArrayImpl', 'fromRefNullable'), + coreTypes.index.getClass('dart:typed_data', 'Uint16List'): coreTypes.index + .getProcedure('dart:_js_types', 'JSUint16ArrayImpl', 'fromRefNullable'), + coreTypes.index.getClass('dart:typed_data', 'Int32List'): coreTypes.index + .getProcedure('dart:_js_types', 'JSInt32ArrayImpl', 'fromRefNullable'), + coreTypes.index.getClass('dart:typed_data', 'Uint32List'): coreTypes.index + .getProcedure('dart:_js_types', 'JSUint32ArrayImpl', 'fromRefNullable'), + coreTypes.index.getClass('dart:typed_data', 'Float32List'): coreTypes.index + .getProcedure( + 'dart:_js_types', 'JSFloat32ArrayImpl', 'fromRefNullable'), + coreTypes.index.getClass('dart:typed_data', 'Float64List'): coreTypes.index + .getProcedure( + 'dart:_js_types', 'JSFloat64ArrayImpl', 'fromRefNullable'), + coreTypes.index.getClass('dart:typed_data', 'ByteBuffer'): coreTypes.index + .getProcedure('dart:_js_types', 'JSArrayBufferImpl', 'fromRefNullable'), + coreTypes.index.getClass('dart:typed_data', 'ByteData'): coreTypes.index + .getProcedure('dart:_js_types', 'JSDataViewImpl', 'fromRefNullable'), + }; + CoreTypesUtil(this.coreTypes, this.extensionIndex) : dartifyRawTarget = coreTypes.index .getTopLevelProcedure('dart:_js_helper', 'dartifyRaw'), @@ -367,6 +417,9 @@ class CoreTypesUtil { ); /// Cast the [invocation] if needed to conform to the expected [expectedType]. + /// + /// [expectedType] is the return type of the interop function, as written by + /// the user. Expression castInvocationForReturn( Expression invocation, DartType expectedType) { Expression expression; @@ -381,9 +434,6 @@ class CoreTypesUtil { } if (isJSValueType(expectedType)) { - // TODO(joshualitt): Expose boxed `JSNull` and `JSUndefined` to Dart - // code after migrating existing users of js interop on Dart2Wasm. - // expression = _createJSValue(invocation); // Casts are expensive, so we stick to a null-assertion if needed. If // the nullability can't be determined, cast. expression = invokeOneArg(jsValueBoxTarget, invocation); @@ -396,27 +446,16 @@ class CoreTypesUtil { } else { final expectedTypeExtensionTypeErasure = expectedType.extensionTypeErasure; - final expectNullable = - expectedTypeExtensionTypeErasure.isPotentiallyNullable; final conversionProcedure = _dartConversionProcedure(expectedTypeExtensionTypeErasure); final invocationValueVar = VariableDeclaration('#jsInvocation', initializer: invocation, type: nullableWasmExternRefType, isSynthesized: true); - expression = Let( - invocationValueVar, - ConditionalExpression( - StaticInvocation( - isDartNullTarget, Arguments([VariableGet(invocationValueVar)])), - expectNullable - ? NullLiteral() - : StaticInvocation(throwArgumentNullErrorTarget, Arguments([])), - invokeOneArg(conversionProcedure, VariableGet(invocationValueVar)), - expectedType, - ), - ); + expression = Let(invocationValueVar, + invokeOneArg(conversionProcedure, VariableGet(invocationValueVar))); } + return expression; } @@ -505,12 +544,13 @@ class CoreTypesUtil { /// type. /// /// The value passed to the returned conversion function should be an - /// `externref` and should be tested for `null` and `undefined`. The returned - /// procedures do not handle `null`s and `undefined`s. + /// `externref`. Procedure _dartConversionProcedure(DartType expectedType) { Procedure? conversionProcedure; if (expectedType is InterfaceType) { - conversionProcedure = _dartifyMap[expectedType.classNode]; + conversionProcedure = expectedType.isPotentiallyNullable + ? _dartifyNullableMap[expectedType.classNode] + : _dartifyNonNullableMap[expectedType.classNode]; } return conversionProcedure ?? dartifyRawTarget; } diff --git a/pkg/dart2wasm/test/ir_tests/dyn_closure.wat b/pkg/dart2wasm/test/ir_tests/dyn_closure.wat index 0b339c03dd6..ee9aee94eb3 100644 --- a/pkg/dart2wasm/test/ir_tests/dyn_closure.wat +++ b/pkg/dart2wasm/test/ir_tests/dyn_closure.wat @@ -27,7 +27,7 @@ (i32.const 0) (global.get $global0) (ref.func $"foo tear-off trampoline") - (ref.func $"foo tear-off trampoline_316") + (ref.func $"foo tear-off trampoline_319") (struct.new $#Vtable-0-2) (i32.const 12) (i32.const 0) @@ -52,7 +52,7 @@ (i32.const 0) (global.get $global0) (ref.func $"bar tear-off trampoline") - (ref.func $"bar tear-off trampoline_319") + (ref.func $"bar tear-off trampoline_322") (struct.new $#Vtable-0-2) (i32.const 12) (i32.const 0) @@ -74,7 +74,7 @@ (struct.new $#Closure-0-2)) (global $global0 (ref $#DummyStruct) <...>) (func $bar tear-off trampoline (param $var0 (ref struct)) (param $var1 (ref null $#Top)) (result (ref null $#Top)) <...>) - (func $bar tear-off trampoline_319 (param $var0 (ref struct)) (param $var1 (ref null $#Top)) (param $var2 (ref null $#Top)) (result (ref null $#Top)) <...>) + (func $bar tear-off trampoline_322 (param $var0 (ref struct)) (param $var1 (ref null $#Top)) (param $var2 (ref null $#Top)) (result (ref null $#Top)) <...>) (func $foo tear-off trampoline (param $var0 (ref struct)) (param $var1 (ref null $#Top)) (result (ref null $#Top)) <...>) - (func $foo tear-off trampoline_316 (param $var0 (ref struct)) (param $var1 (ref null $#Top)) (param $var2 (ref null $#Top)) (result (ref null $#Top)) <...>) + (func $foo tear-off trampoline_319 (param $var0 (ref struct)) (param $var1 (ref null $#Top)) (param $var2 (ref null $#Top)) (result (ref null $#Top)) <...>) ) \ No newline at end of file diff --git a/pkg/dart2wasm/test/ir_tests/dyn_closure_function_apply.wat b/pkg/dart2wasm/test/ir_tests/dyn_closure_function_apply.wat index 6592f029c26..f37255502e8 100644 --- a/pkg/dart2wasm/test/ir_tests/dyn_closure_function_apply.wat +++ b/pkg/dart2wasm/test/ir_tests/dyn_closure_function_apply.wat @@ -27,7 +27,7 @@ (i32.const 0) (global.get $global0) (ref.func $"foo tear-off trampoline") - (ref.func $"foo tear-off trampoline_316") + (ref.func $"foo tear-off trampoline_319") (struct.new $#Vtable-0-2) (i32.const 12) (i32.const 0) @@ -52,7 +52,7 @@ (i32.const 0) (global.get $global0) (ref.func $"bar tear-off trampoline") - (ref.func $"bar tear-off trampoline_321") + (ref.func $"bar tear-off trampoline_324") (struct.new $#Vtable-0-2) (i32.const 12) (i32.const 0) @@ -74,7 +74,7 @@ (struct.new $#Closure-0-2)) (global $global0 (ref $#DummyStruct) <...>) (func $bar tear-off trampoline (param $var0 (ref struct)) (param $var1 (ref null $#Top)) (result (ref null $#Top)) <...>) - (func $bar tear-off trampoline_321 (param $var0 (ref struct)) (param $var1 (ref null $#Top)) (param $var2 (ref null $#Top)) (result (ref null $#Top)) <...>) + (func $bar tear-off trampoline_324 (param $var0 (ref struct)) (param $var1 (ref null $#Top)) (param $var2 (ref null $#Top)) (result (ref null $#Top)) <...>) (func $foo tear-off trampoline (param $var0 (ref struct)) (param $var1 (ref null $#Top)) (result (ref null $#Top)) <...>) - (func $foo tear-off trampoline_316 (param $var0 (ref struct)) (param $var1 (ref null $#Top)) (param $var2 (ref null $#Top)) (result (ref null $#Top)) <...>) + (func $foo tear-off trampoline_319 (param $var0 (ref struct)) (param $var1 (ref null $#Top)) (param $var2 (ref null $#Top)) (result (ref null $#Top)) <...>) ) \ No newline at end of file diff --git a/pkg/dart2wasm/test/ir_tests/dyn_closure_function_apply_named.wat b/pkg/dart2wasm/test/ir_tests/dyn_closure_function_apply_named.wat index 2123b909597..2d5601d420a 100644 --- a/pkg/dart2wasm/test/ir_tests/dyn_closure_function_apply_named.wat +++ b/pkg/dart2wasm/test/ir_tests/dyn_closure_function_apply_named.wat @@ -31,7 +31,7 @@ (i32.const 0) (global.get $global0) (ref.func $"foo tear-off dynamic call entry") - (ref.func $"foo tear-off trampoline_324") + (ref.func $"foo tear-off trampoline_327") (struct.new $#Vtable-0-2) (i32.const 12) (i32.const 0) @@ -57,7 +57,7 @@ (i32.const 0) (global.get $global0) (ref.func $"bar tear-off dynamic call entry") - (ref.func $"bar tear-off trampoline_330") + (ref.func $"bar tear-off trampoline_333") (struct.new $#Vtable-0-2) (i32.const 12) (i32.const 0) @@ -74,7 +74,7 @@ (struct.new $#Closure-0-2)) (global $global0 (ref $#DummyStruct) <...>) (func $bar tear-off dynamic call entry (param $var0 (ref $#Closure-0-0)) (param $var1 (ref $Array<_Type>)) (param $var2 (ref $Array)) (param $var3 (ref $Array)) (result (ref null $#Top)) <...>) - (func $bar tear-off trampoline_330 (param $var0 (ref struct)) (param $var1 (ref null $#Top)) (param $var2 (ref null $#Top)) (result (ref null $#Top)) <...>) + (func $bar tear-off trampoline_333 (param $var0 (ref struct)) (param $var1 (ref null $#Top)) (param $var2 (ref null $#Top)) (result (ref null $#Top)) <...>) (func $foo tear-off dynamic call entry (param $var0 (ref $#Closure-0-0)) (param $var1 (ref $Array<_Type>)) (param $var2 (ref $Array)) (param $var3 (ref $Array)) (result (ref null $#Top)) <...>) - (func $foo tear-off trampoline_324 (param $var0 (ref struct)) (param $var1 (ref null $#Top)) (param $var2 (ref null $#Top)) (result (ref null $#Top)) <...>) + (func $foo tear-off trampoline_327 (param $var0 (ref struct)) (param $var1 (ref null $#Top)) (param $var2 (ref null $#Top)) (result (ref null $#Top)) <...>) ) \ No newline at end of file diff --git a/pkg/dart2wasm/test/ir_tests/interop.bool.wat b/pkg/dart2wasm/test/ir_tests/interop.bool.wat index 7a60cc298f0..ceb68af3f4c 100644 --- a/pkg/dart2wasm/test/ir_tests/interop.bool.wat +++ b/pkg/dart2wasm/test/ir_tests/interop.bool.wat @@ -1,71 +1,39 @@ (module $module0 (type $#Top (struct (field $field0 i32))) - (func $"dart2wasm._170 (import)" (import "dart2wasm" "_170") (param externref) (result i32)) - (func $"dart2wasm._171 (import)" (import "dart2wasm" "_171") (param i32) (result externref)) - (func $"dart2wasm._295 (import)" (import "dart2wasm" "_295") (param externref) (result externref)) - (func $"dart2wasm._296 (import)" (import "dart2wasm" "_296") (param externref) (result externref)) + (func $"dart2wasm._174 (import)" (import "dart2wasm" "_174") (param i32) (result externref)) + (func $"dart2wasm._298 (import)" (import "dart2wasm" "_298") (param externref) (result externref)) + (func $"dart2wasm._299 (import)" (import "dart2wasm" "_299") (param externref) (result externref)) (global $"C2 false" (ref $#Top) <...>) (global $"C40 true" (ref $#Top) <...>) (global $"boolValueNullable initialized" (mut i32) <...>) (global $boolValueNullable (mut (ref null $#Top)) <...>) - (func $_throwArgumentNullError <...>) (func $boolValue implicit getter (result i32) <...>) (func $ktrue implicit getter (result i32) <...>) (func $sinkBool (param $var0 i32) <...>) (func $sinkBoolNullable (param $var0 (ref null $#Top)) <...>) (func $"testBoolConstant " - (local $var0 externref) i32.const 1 - call $"dart2wasm._171 (import)" - call $"dart2wasm._295 (import)" - local.tee $var0 - call $isDartNull - if (result i32) - call $"_throwArgumentNullError " - unreachable - else - local.get $var0 - call $"dart2wasm._170 (import)" - end + call $"dart2wasm._174 (import)" + call $"dart2wasm._298 (import)" + call $toDartBool call $"sinkBool " ) (func $"testBoolConstantNullable " - (local $var0 externref) ref.null noextern - call $"dart2wasm._296 (import)" - local.tee $var0 - call $isDartNull - if (result (ref null $#Top)) - ref.null none - else - global.get $"C40 true" - global.get $"C2 false" - local.get $var0 - call $"dart2wasm._170 (import)" - select (ref $#Top) - end + call $"dart2wasm._299 (import)" + call $toDartNullableBool call $"sinkBoolNullable " ) (func $"testBoolValue " - (local $var0 externref) call $"boolValue implicit getter" - call $"dart2wasm._171 (import)" - call $"dart2wasm._295 (import)" - local.tee $var0 - call $isDartNull - if (result i32) - call $"_throwArgumentNullError " - unreachable - else - local.get $var0 - call $"dart2wasm._170 (import)" - end + call $"dart2wasm._174 (import)" + call $"dart2wasm._298 (import)" + call $toDartBool call $"sinkBool " ) (func $"testBoolValueNullable " (local $var0 (ref null $#Top)) - (local $var1 externref) global.get $"boolValueNullable initialized" i32.eqz if @@ -91,20 +59,11 @@ local.get $var0 call $jsifyRaw end - call $"dart2wasm._296 (import)" - local.tee $var1 - call $isDartNull - if (result (ref null $#Top)) - ref.null none - else - global.get $"C40 true" - global.get $"C2 false" - local.get $var1 - call $"dart2wasm._170 (import)" - select (ref $#Top) - end + call $"dart2wasm._299 (import)" + call $toDartNullableBool call $"sinkBoolNullable " ) - (func $isDartNull (param $var0 externref) (result i32) <...>) (func $jsifyRaw (param $var0 (ref null $#Top)) (result externref) <...>) + (func $toDartBool (param $var0 externref) (result i32) <...>) + (func $toDartNullableBool (param $var0 externref) (result (ref null $#Top)) <...>) ) \ No newline at end of file diff --git a/pkg/dart2wasm/test/ir_tests/interop.double.wat b/pkg/dart2wasm/test/ir_tests/interop.double.wat index 4a35b25ff79..b9039284752 100644 --- a/pkg/dart2wasm/test/ir_tests/interop.double.wat +++ b/pkg/dart2wasm/test/ir_tests/interop.double.wat @@ -4,65 +4,34 @@ (type $BoxedDouble (sub final $#Top (struct (field $field0 i32) (field $value f64)))) - (func $"dart2wasm._168 (import)" (import "dart2wasm" "_168") (param externref) (result f64)) - (func $"dart2wasm._295 (import)" (import "dart2wasm" "_295") (param f64) (result externref)) - (func $"dart2wasm._296 (import)" (import "dart2wasm" "_296") (param externref) (result externref)) + (func $"dart2wasm._298 (import)" (import "dart2wasm" "_298") (param f64) (result externref)) + (func $"dart2wasm._299 (import)" (import "dart2wasm" "_299") (param externref) (result externref)) (global $"doubleValueNullable initialized" (mut i32) <...>) (global $doubleValueNullable (mut (ref null $BoxedDouble)) <...>) - (func $_throwArgumentNullError <...>) (func $doubleValue implicit getter (result f64) <...>) (func $ktrue implicit getter (result i32) <...>) (func $sinkDouble (param $var0 f64) <...>) (func $sinkDoubleNullable (param $var0 (ref null $BoxedDouble)) <...>) (func $"testDoubleConstant " - (local $var0 externref) f64.const 1.1 - call $"dart2wasm._295 (import)" - local.tee $var0 - call $isDartNull - if (result f64) - call $"_throwArgumentNullError " - unreachable - else - local.get $var0 - call $"dart2wasm._168 (import)" - end + call $"dart2wasm._298 (import)" + call $toDartDouble call $"sinkDouble " ) (func $"testDoubleConstantNullable " - (local $var0 externref) ref.null noextern - call $"dart2wasm._296 (import)" - local.tee $var0 - call $isDartNull - if (result (ref null $BoxedDouble)) - ref.null none - else - i32.const 87 - local.get $var0 - call $"dart2wasm._168 (import)" - struct.new $BoxedDouble - end + call $"dart2wasm._299 (import)" + call $toDartNullableDouble call $"sinkDoubleNullable " ) (func $"testDoubleValue " - (local $var0 externref) call $"doubleValue implicit getter" - call $"dart2wasm._295 (import)" - local.tee $var0 - call $isDartNull - if (result f64) - call $"_throwArgumentNullError " - unreachable - else - local.get $var0 - call $"dart2wasm._168 (import)" - end + call $"dart2wasm._298 (import)" + call $toDartDouble call $"sinkDouble " ) (func $"testDoubleValueNullable " (local $var0 (ref null $BoxedDouble)) - (local $var1 externref) global.get $"doubleValueNullable initialized" i32.eqz if @@ -87,19 +56,11 @@ local.get $var0 call $jsifyRaw end - call $"dart2wasm._296 (import)" - local.tee $var1 - call $isDartNull - if (result (ref null $BoxedDouble)) - ref.null none - else - i32.const 87 - local.get $var1 - call $"dart2wasm._168 (import)" - struct.new $BoxedDouble - end + call $"dart2wasm._299 (import)" + call $toDartNullableDouble call $"sinkDoubleNullable " ) - (func $isDartNull (param $var0 externref) (result i32) <...>) (func $jsifyRaw (param $var0 (ref null $#Top)) (result externref) <...>) + (func $toDartDouble (param $var0 externref) (result f64) <...>) + (func $toDartNullableDouble (param $var0 externref) (result (ref null $BoxedDouble)) <...>) ) \ No newline at end of file diff --git a/pkg/dart2wasm/test/ir_tests/interop.int.wat b/pkg/dart2wasm/test/ir_tests/interop.int.wat index d00b5b435f1..b262fddda41 100644 --- a/pkg/dart2wasm/test/ir_tests/interop.int.wat +++ b/pkg/dart2wasm/test/ir_tests/interop.int.wat @@ -4,66 +4,36 @@ (type $BoxedInt (sub $#Top (struct (field $field0 i32) (field $value i64)))) - (func $"dart2wasm._295 (import)" (import "dart2wasm" "_295") (param externref) (result externref)) - (func $"dart2wasm._296 (import)" (import "dart2wasm" "_296") (param externref) (result externref)) + (func $"dart2wasm._298 (import)" (import "dart2wasm" "_298") (param externref) (result externref)) + (func $"dart2wasm._299 (import)" (import "dart2wasm" "_299") (param externref) (result externref)) (global $"intValueNullable initialized" (mut i32) <...>) (global $intValueNullable (mut (ref null $BoxedInt)) <...>) - (func $_throwArgumentNullError <...>) (func $intValue implicit getter (result i64) <...>) (func $ktrue implicit getter (result i32) <...>) (func $sinkInt (param $var0 i64) <...>) (func $sinkIntNullable (param $var0 (ref null $BoxedInt)) <...>) (func $"testIntConstant " - (local $var0 externref) i64.const 1 call $jsifyInt - call $"dart2wasm._295 (import)" - local.tee $var0 - call $isDartNull - if (result i64) - call $"_throwArgumentNullError " - unreachable - else - local.get $var0 - call $dartifyInt - end + call $"dart2wasm._298 (import)" + call $toDartInt call $"sinkInt " ) (func $"testIntConstantNullable " - (local $var0 externref) ref.null noextern - call $"dart2wasm._296 (import)" - local.tee $var0 - call $isDartNull - if (result (ref null $BoxedInt)) - ref.null none - else - i32.const 69 - local.get $var0 - call $dartifyInt - struct.new $BoxedInt - end + call $"dart2wasm._299 (import)" + call $toDartNullableInt call $"sinkIntNullable " ) (func $"testIntValue " - (local $var0 externref) call $"intValue implicit getter" call $jsifyInt - call $"dart2wasm._295 (import)" - local.tee $var0 - call $isDartNull - if (result i64) - call $"_throwArgumentNullError " - unreachable - else - local.get $var0 - call $dartifyInt - end + call $"dart2wasm._298 (import)" + call $toDartInt call $"sinkInt " ) (func $"testIntValueNullable " (local $var0 (ref null $BoxedInt)) - (local $var1 externref) global.get $"intValueNullable initialized" i32.eqz if @@ -88,21 +58,12 @@ local.get $var0 call $jsifyRaw end - call $"dart2wasm._296 (import)" - local.tee $var1 - call $isDartNull - if (result (ref null $BoxedInt)) - ref.null none - else - i32.const 69 - local.get $var1 - call $dartifyInt - struct.new $BoxedInt - end + call $"dart2wasm._299 (import)" + call $toDartNullableInt call $"sinkIntNullable " ) - (func $dartifyInt (param $var0 externref) (result i64) <...>) - (func $isDartNull (param $var0 externref) (result i32) <...>) (func $jsifyInt (param $var0 i64) (result externref) <...>) (func $jsifyRaw (param $var0 (ref null $#Top)) (result externref) <...>) + (func $toDartInt (param $var0 externref) (result i64) <...>) + (func $toDartNullableInt (param $var0 externref) (result (ref null $BoxedInt)) <...>) ) \ No newline at end of file diff --git a/pkg/dart2wasm/test/ir_tests/interop.num.wat b/pkg/dart2wasm/test/ir_tests/interop.num.wat index 9b41ff7d4b1..d6b712e6964 100644 --- a/pkg/dart2wasm/test/ir_tests/interop.num.wat +++ b/pkg/dart2wasm/test/ir_tests/interop.num.wat @@ -4,84 +4,44 @@ (type $BoxedDouble (sub final $#Top (struct (field $field0 i32) (field $value f64)))) - (func $"dart2wasm._168 (import)" (import "dart2wasm" "_168") (param externref) (result f64)) - (func $"dart2wasm._169 (import)" (import "dart2wasm" "_169") (param f64) (result externref)) - (func $"dart2wasm._295 (import)" (import "dart2wasm" "_295") (param externref) (result externref)) - (func $"dart2wasm._296 (import)" (import "dart2wasm" "_296") (param externref) (result externref)) + (func $"dart2wasm._171 (import)" (import "dart2wasm" "_171") (param f64) (result externref)) + (func $"dart2wasm._298 (import)" (import "dart2wasm" "_298") (param externref) (result externref)) + (func $"dart2wasm._299 (import)" (import "dart2wasm" "_299") (param externref) (result externref)) (global $"numValueNullable initialized" (mut i32) <...>) (global $numValueNullable (mut (ref null $#Top)) <...>) - (func $_throwArgumentNullError <...>) (func $ktrue implicit getter (result i32) <...>) (func $numValue implicit getter (result (ref $#Top)) <...>) (func $sinkNum (param $var0 f64) <...>) (func $sinkNumNullable (param $var0 (ref null $BoxedDouble)) <...>) (func $"testNumConstant " - (local $var0 externref) i64.const 1 call $jsifyInt - call $"dart2wasm._295 (import)" - local.tee $var0 - call $isDartNull - if (result f64) - call $"_throwArgumentNullError " - unreachable - else - local.get $var0 - call $"dart2wasm._168 (import)" - end + call $"dart2wasm._298 (import)" + call $toDartDouble call $"sinkNum " ) (func $"testNumConstantDouble " - (local $var0 externref) f64.const 1.1 - call $"dart2wasm._169 (import)" - call $"dart2wasm._295 (import)" - local.tee $var0 - call $isDartNull - if (result f64) - call $"_throwArgumentNullError " - unreachable - else - local.get $var0 - call $"dart2wasm._168 (import)" - end + call $"dart2wasm._171 (import)" + call $"dart2wasm._298 (import)" + call $toDartDouble call $"sinkNum " ) (func $"testNumConstantNullable " - (local $var0 externref) ref.null noextern - call $"dart2wasm._296 (import)" - local.tee $var0 - call $isDartNull - if (result (ref null $BoxedDouble)) - ref.null none - else - i32.const 87 - local.get $var0 - call $"dart2wasm._168 (import)" - struct.new $BoxedDouble - end + call $"dart2wasm._299 (import)" + call $toDartNullableDouble call $"sinkNumNullable " ) (func $"testNumValue " - (local $var0 externref) call $"numValue implicit getter" call $jsifyNum - call $"dart2wasm._295 (import)" - local.tee $var0 - call $isDartNull - if (result f64) - call $"_throwArgumentNullError " - unreachable - else - local.get $var0 - call $"dart2wasm._168 (import)" - end + call $"dart2wasm._298 (import)" + call $toDartDouble call $"sinkNum " ) (func $"testNumValueNullable " (local $var0 (ref null $#Top)) - (local $var1 externref) global.get $"numValueNullable initialized" i32.eqz if @@ -104,21 +64,13 @@ local.get $var0 call $jsifyRaw end - call $"dart2wasm._296 (import)" - local.tee $var1 - call $isDartNull - if (result (ref null $BoxedDouble)) - ref.null none - else - i32.const 87 - local.get $var1 - call $"dart2wasm._168 (import)" - struct.new $BoxedDouble - end + call $"dart2wasm._299 (import)" + call $toDartNullableDouble call $"sinkNumNullable " ) - (func $isDartNull (param $var0 externref) (result i32) <...>) (func $jsifyInt (param $var0 i64) (result externref) <...>) (func $jsifyNum (param $var0 (ref $#Top)) (result externref) <...>) (func $jsifyRaw (param $var0 (ref null $#Top)) (result externref) <...>) + (func $toDartDouble (param $var0 externref) (result f64) <...>) + (func $toDartNullableDouble (param $var0 externref) (result (ref null $BoxedDouble)) <...>) ) \ No newline at end of file diff --git a/pkg/dart2wasm/test/ir_tests/interop.string.wat b/pkg/dart2wasm/test/ir_tests/interop.string.wat index a5171ac7abf..937c0257823 100644 --- a/pkg/dart2wasm/test/ir_tests/interop.string.wat +++ b/pkg/dart2wasm/test/ir_tests/interop.string.wat @@ -4,65 +4,37 @@ (type $JSStringImpl (sub final $#Top (struct (field $field0 i32) (field $_ref externref)))) - (func $"dart2wasm._295 (import)" (import "dart2wasm" "_295") (param externref) (result externref)) - (func $"dart2wasm._296 (import)" (import "dart2wasm" "_296") (param externref) (result externref)) + (func $"dart2wasm._298 (import)" (import "dart2wasm" "_298") (param externref) (result externref)) + (func $"dart2wasm._299 (import)" (import "dart2wasm" "_299") (param externref) (result externref)) (global $.a (import "" "a") (ref extern)) (global $"stringValueNullable initialized" (mut i32) <...>) (global $stringValueNullable (mut (ref null $JSStringImpl)) <...>) - (func $_throwArgumentNullError <...>) (func $ktrue implicit getter (result i32) <...>) (func $new JSStringImpl.fromRef (param $var0 externref) (result (ref $JSStringImpl)) <...>) (func $sinkString (param $var0 (ref $JSStringImpl)) <...>) (func $sinkStringNullable (param $var0 (ref null $JSStringImpl)) <...>) (func $stringValue implicit getter (result (ref $JSStringImpl)) <...>) (func $"testStringConstant " - (local $var0 externref) global.get $.a - call $"dart2wasm._295 (import)" - local.tee $var0 - call $isDartNull - if (result (ref $JSStringImpl)) - call $"_throwArgumentNullError " - unreachable - else - local.get $var0 - call $"new JSStringImpl.fromRef" - end + call $"dart2wasm._298 (import)" + call $"new JSStringImpl.fromRef" call $"sinkString " ) (func $"testStringConstantNullable " - (local $var0 externref) ref.null noextern - call $"dart2wasm._296 (import)" - local.tee $var0 - call $isDartNull - if (result (ref null $JSStringImpl)) - ref.null none - else - local.get $var0 - call $"new JSStringImpl.fromRef" - end + call $"dart2wasm._299 (import)" + call $JSStringImpl.fromRefNullable call $"sinkStringNullable " ) (func $"testStringValue " - (local $var0 externref) call $"stringValue implicit getter" struct.get $JSStringImpl $_ref - call $"dart2wasm._295 (import)" - local.tee $var0 - call $isDartNull - if (result (ref $JSStringImpl)) - call $"_throwArgumentNullError " - unreachable - else - local.get $var0 - call $"new JSStringImpl.fromRef" - end + call $"dart2wasm._298 (import)" + call $"new JSStringImpl.fromRef" call $"sinkString " ) (func $"testStringValueNullable " (local $var0 (ref null $JSStringImpl)) - (local $var1 externref) global.get $"stringValueNullable initialized" i32.eqz if @@ -85,17 +57,10 @@ local.get $var0 call $jsifyRaw end - call $"dart2wasm._296 (import)" - local.tee $var1 - call $isDartNull - if (result (ref null $JSStringImpl)) - ref.null none - else - local.get $var1 - call $"new JSStringImpl.fromRef" - end + call $"dart2wasm._299 (import)" + call $JSStringImpl.fromRefNullable call $"sinkStringNullable " ) - (func $isDartNull (param $var0 externref) (result i32) <...>) + (func $JSStringImpl.fromRefNullable (param $var0 externref) (result (ref null $JSStringImpl)) <...>) (func $jsifyRaw (param $var0 (ref null $#Top)) (result externref) <...>) ) \ No newline at end of file diff --git a/sdk/lib/_internal/wasm/lib/js_helper.dart b/sdk/lib/_internal/wasm/lib/js_helper.dart index 454566aec5d..e33ec5209a2 100644 --- a/sdk/lib/_internal/wasm/lib/js_helper.dart +++ b/sdk/lib/_internal/wasm/lib/js_helper.dart @@ -158,16 +158,88 @@ bool isJSRegExp(WasmExternRef? o) => JS("o => o instanceof RegExp", o); bool areEqualInJS(WasmExternRef? l, WasmExternRef? r) => JS("(l, r) => l === r", l, r); -// The JS runtime will run helpful conversion routines between refs and bool / -// double. In the longer term hopefully we can find a way to avoid the round -// trip. -double toDartNumber(WasmExternRef? o) => JS("o => o", o); +@pragma('wasm:entry-point') +double toDartDouble(WasmExternRef? ref) { + final numberType = _checkNumberType(ref); + if (numberType != 1) { + throw ArgumentError('JS value is not a number'); + } + return _toDartDoubleUnchecked(ref); +} @pragma('wasm:entry-point') -WasmExternRef? toJSNumber(double o) => JS("o => o", o); +double? toDartNullableDouble(WasmExternRef? ref) { + final refType = _checkNumberType(ref); + if (refType == 0) return null; + if (refType == 1) return _toDartDoubleUnchecked(ref); + throw ArgumentError('JS value is not a number'); +} + +double _toDartDoubleUnchecked(WasmExternRef? ref) => JS("o => o", ref); + +int _checkNumberType(WasmExternRef? ref) { + return JS("""o => { + if (o === undefined || o === null) return 0; + if (typeof o === 'number') return 1; + return 2; + }""", ref).toIntUnsigned(); +} + +int _jsNonNullToInt(WasmExternRef? ref, bool typeIsRight) { + if (typeIsRight) { + final dartDouble = _toDartDoubleUnchecked(ref); + if (dartDouble.isFinite) { + final dartInt = dartDouble.toInt(); + if (dartInt.toDouble() == dartDouble) { + return dartInt; + } + } + } + throw ArgumentError('JS value is not integer'); +} @pragma('wasm:entry-point') -bool toDartBool(WasmExternRef? o) => JS("o => o", o); +int toDartInt(WasmExternRef? ref) { + final numberType = _checkNumberType(ref); + return _jsNonNullToInt(ref, numberType == 1); +} + +@pragma('wasm:entry-point') +int? toDartNullableInt(WasmExternRef? ref) { + final numberType = _checkNumberType(ref); + if (numberType == 0) return null; + return _jsNonNullToInt(ref, numberType == 1); +} + +@pragma('wasm:entry-point') +WasmExternRef? toJSNumber(double ref) => JS("o => o", ref); + +int _checkBoolType(WasmExternRef? ref) { + return JS("""o => { + if (o === undefined || o === null) return 0; + if (typeof o === 'boolean') return 1; + return 2; + }""", ref).toIntUnsigned(); +} + +@pragma('wasm:entry-point') +bool toDartBool(WasmExternRef? ref) { + final refType = _checkBoolType(ref); + if (refType != 1) { + throw ArgumentError('JS value is not a boolean'); + } + return _toDartBoolUnchecked(ref); +} + +@pragma('wasm:entry-point') +bool? toDartNullableBool(WasmExternRef? ref) { + final refType = _checkBoolType(ref); + if (refType == 0) return null; + if (refType == 1) return _toDartBoolUnchecked(ref); + throw ArgumentError('JS value is not a boolean'); +} + +bool _toDartBoolUnchecked(WasmExternRef? ref) => JS("o => o", ref); WasmExternRef? toJSBoolean(bool b) => JS("b => !!b", b); @@ -537,32 +609,28 @@ Object? dartifyRaw(WasmExternRef? ref, [int? refType]) { refType ??= externRefType(ref); return switch (refType) { ExternRefType.null_ || ExternRefType.undefined => null, - ExternRefType.boolean => toDartBool(ref), - ExternRefType.number => toDartNumber(ref), + ExternRefType.boolean => _toDartBoolUnchecked(ref), + ExternRefType.number => _toDartDoubleUnchecked(ref), ExternRefType.string => JSStringImpl.fromRefUnchecked(ref), ExternRefType.array => toDartList(ref), - ExternRefType.int8Array => js_types.JSInt8ArrayImpl.fromArrayRefUnchecked( - ref, - ), - ExternRefType.uint8Array => js_types.JSUint8ArrayImpl.fromArrayRefUnchecked( - ref, - ), + ExternRefType.int8Array => js_types.JSInt8ArrayImpl.fromRefUnchecked(ref), + ExternRefType.uint8Array => js_types.JSUint8ArrayImpl.fromRefUnchecked(ref), ExternRefType.uint8ClampedArray => - js_types.JSUint8ClampedArrayImpl.fromArrayRefUnchecked(ref), - ExternRefType.int16Array => js_types.JSInt16ArrayImpl.fromArrayRefUnchecked( + js_types.JSUint8ClampedArrayImpl.fromRefUnchecked(ref), + ExternRefType.int16Array => js_types.JSInt16ArrayImpl.fromRefUnchecked(ref), + ExternRefType.uint16Array => js_types.JSUint16ArrayImpl.fromRefUnchecked( ref, ), - ExternRefType.uint16Array => - js_types.JSUint16ArrayImpl.fromArrayRefUnchecked(ref), - ExternRefType.int32Array => js_types.JSInt32ArrayImpl.fromArrayRefUnchecked( + ExternRefType.int32Array => js_types.JSInt32ArrayImpl.fromRefUnchecked(ref), + ExternRefType.uint32Array => js_types.JSUint32ArrayImpl.fromRefUnchecked( + ref, + ), + ExternRefType.float32Array => js_types.JSFloat32ArrayImpl.fromRefUnchecked( + ref, + ), + ExternRefType.float64Array => js_types.JSFloat64ArrayImpl.fromRefUnchecked( ref, ), - ExternRefType.uint32Array => - js_types.JSUint32ArrayImpl.fromArrayRefUnchecked(ref), - ExternRefType.float32Array => - js_types.JSFloat32ArrayImpl.fromArrayRefUnchecked(ref), - ExternRefType.float64Array => - js_types.JSFloat64ArrayImpl.fromArrayRefUnchecked(ref), ExternRefType.arrayBuffer || ExternRefType.sharedArrayBuffer => js_types.JSArrayBufferImpl.fromRefUnchecked(ref), ExternRefType.dataView => js_types.JSDataViewImpl.fromRefUnchecked(ref), @@ -581,18 +649,6 @@ Object? dartifyRaw(WasmExternRef? ref, [int? refType]) { }; } -@pragma('wasm:entry-point') -int dartifyInt(WasmExternRef? ref) { - final dartDouble = toDartNumber(ref); - if (dartDouble.isFinite) { - final dartInt = dartDouble.toInt(); - if (dartInt.toDouble() == dartDouble) { - return dartInt; - } - } - throw ArgumentError('JS value is not integer'); -} - List jsFloatTypedArrayToDartFloatTypedData( WasmExternRef? ref, List makeTypedData(int size), @@ -600,7 +656,7 @@ List jsFloatTypedArrayToDartFloatTypedData( int length = objectLength(ref); List list = makeTypedData(length); for (int i = 0; i < length; i++) { - list[i] = toDartNumber(objectReadIndex(ref, i)); + list[i] = toDartDouble(objectReadIndex(ref, i)); } return list; } @@ -612,7 +668,7 @@ List jsIntTypedArrayToDartIntTypedData( int length = objectLength(ref); List list = makeTypedData(length); for (int i = 0; i < length; i++) { - list[i] = toDartNumber(objectReadIndex(ref, i)).toInt(); + list[i] = toDartDouble(objectReadIndex(ref, i)).toInt(); } return list; } @@ -673,6 +729,12 @@ List toDartList(WasmExternRef? ref) => List.generate( (int n) => dartifyRaw(objectReadIndex(ref, n)), ); +@pragma('wasm:entry-point') +List? toDartNullableList(WasmExternRef? ref) { + if (ref.isNull || isJSUndefined(ref)) return null; + return toDartList(ref); +} + // These two trivial helpers are needed to work around an issue with tearing off // functions that take / return [WasmExternRef]. bool _isDartFunctionWrapped(F f) => diff --git a/sdk/lib/_internal/wasm/lib/js_interop_patch.dart b/sdk/lib/_internal/wasm/lib/js_interop_patch.dart index 0b5d97c586d..6cfec6fbf73 100644 --- a/sdk/lib/_internal/wasm/lib/js_interop_patch.dart +++ b/sdk/lib/_internal/wasm/lib/js_interop_patch.dart @@ -374,7 +374,7 @@ extension ByteDataToJSDataView on ByteData { @patch extension JSInt8ArrayToInt8List on JSInt8Array { @patch - Int8List get toDart => js_types.JSInt8ArrayImpl.fromArrayRef(toExternRef); + Int8List get toDart => js_types.JSInt8ArrayImpl.fromRef(toExternRef); } @patch @@ -397,7 +397,7 @@ extension Int8ListToJSInt8Array on Int8List { @patch extension JSUint8ArrayToUint8List on JSUint8Array { @patch - Uint8List get toDart => js_types.JSUint8ArrayImpl.fromArrayRef(toExternRef); + Uint8List get toDart => js_types.JSUint8ArrayImpl.fromRef(toExternRef); } @patch @@ -421,7 +421,7 @@ extension Uint8ListToJSUint8Array on Uint8List { extension JSUint8ClampedArrayToUint8ClampedList on JSUint8ClampedArray { @patch Uint8ClampedList get toDart => - js_types.JSUint8ClampedArrayImpl.fromArrayRef(toExternRef); + js_types.JSUint8ClampedArrayImpl.fromRef(toExternRef); } @patch @@ -444,7 +444,7 @@ extension Uint8ClampedListToJSUint8ClampedArray on Uint8ClampedList { @patch extension JSInt16ArrayToInt16List on JSInt16Array { @patch - Int16List get toDart => js_types.JSInt16ArrayImpl.fromArrayRef(toExternRef); + Int16List get toDart => js_types.JSInt16ArrayImpl.fromRef(toExternRef); } @patch @@ -467,7 +467,7 @@ extension Int16ListToJSInt16Array on Int16List { @patch extension JSUint16ArrayToUint16List on JSUint16Array { @patch - Uint16List get toDart => js_types.JSUint16ArrayImpl.fromArrayRef(toExternRef); + Uint16List get toDart => js_types.JSUint16ArrayImpl.fromRef(toExternRef); } @patch @@ -490,7 +490,7 @@ extension Uint16ListToJSUint16Array on Uint16List { @patch extension JSInt32ArrayToInt32List on JSInt32Array { @patch - Int32List get toDart => js_types.JSInt32ArrayImpl.fromArrayRef(toExternRef); + Int32List get toDart => js_types.JSInt32ArrayImpl.fromRef(toExternRef); } @patch @@ -513,7 +513,7 @@ extension Int32ListToJSInt32Array on Int32List { @patch extension JSUint32ArrayToUint32List on JSUint32Array { @patch - Uint32List get toDart => js_types.JSUint32ArrayImpl.fromArrayRef(toExternRef); + Uint32List get toDart => js_types.JSUint32ArrayImpl.fromRef(toExternRef); } @patch @@ -536,8 +536,7 @@ extension Uint32ListToJSUint32Array on Uint32List { @patch extension JSFloat32ArrayToFloat32List on JSFloat32Array { @patch - Float32List get toDart => - js_types.JSFloat32ArrayImpl.fromArrayRef(toExternRef); + Float32List get toDart => js_types.JSFloat32ArrayImpl.fromRef(toExternRef); } @patch @@ -560,8 +559,7 @@ extension Float32ListToJSFloat32Array on Float32List { @patch extension JSFloat64ArrayToFloat64List on JSFloat64Array { @patch - Float64List get toDart => - js_types.JSFloat64ArrayImpl.fromArrayRef(toExternRef); + Float64List get toDart => js_types.JSFloat64ArrayImpl.fromRef(toExternRef); } @patch @@ -610,18 +608,10 @@ extension ListToJSArray on List { @patch extension JSNumberToNumber on JSNumber { @patch - double get toDartDouble => toDartNumber(toExternRef); + double get toDartDouble => js_helper.toDartDouble(toExternRef); @patch - int get toDartInt { - final number = toDartNumber(toExternRef); - final intVal = number.toInt(); - if (number == intVal) { - return intVal; - } else { - throw 'Expected integer value, but was not integer.'; - } - } + int get toDartInt => js_helper.toDartInt(toExternRef); } @patch diff --git a/sdk/lib/_internal/wasm/lib/js_string.dart b/sdk/lib/_internal/wasm/lib/js_string.dart index 8809e8ff667..c482f854ac8 100644 --- a/sdk/lib/_internal/wasm/lib/js_string.dart +++ b/sdk/lib/_internal/wasm/lib/js_string.dart @@ -40,6 +40,7 @@ final class JSStringImpl implements String, StringUncheckedOperationsBase { assert(_checkRefType(_ref)); } + @pragma('wasm:entry-point') factory JSStringImpl.fromRef(WasmExternRef? ref) { if (!_checkRefType(ref)) { throw minify @@ -49,6 +50,20 @@ final class JSStringImpl implements String, StringUncheckedOperationsBase { return JSStringImpl.fromRefUnchecked(ref); } + @pragma('wasm:entry-point') + static JSStringImpl? fromRefNullable(WasmExternRef? ref) { + final int refType = js.JS('''o => { + if (o === null || o === undefined) return 0; + if (typeof(o) === 'string') return 1; + return 2; + }''', ref).toIntUnsigned(); + if (refType == 0) return null; + if (refType == 1) return JSStringImpl.fromRefUnchecked(ref); + throw minify + ? ArgumentError() + : ArgumentError("JS reference is not a string"); + } + @pragma("wasm:prefer-inline") static String? box(WasmExternRef? ref) => js.isDartNull(ref) ? null : JSStringImpl.fromRefUnchecked(ref); diff --git a/sdk/lib/_internal/wasm/lib/js_typed_array.dart b/sdk/lib/_internal/wasm/lib/js_typed_array.dart index 3d8cc457e58..6c46cdd05d8 100644 --- a/sdk/lib/_internal/wasm/lib/js_typed_array.dart +++ b/sdk/lib/_internal/wasm/lib/js_typed_array.dart @@ -9,9 +9,10 @@ part of "dart:_js_types"; /// /// Constants are preferred over enums for performance. abstract final class _ArrayBufferType { - static const int arrayBuffer = 0; - static const int sharedArrayBuffer = 1; - static const int unknown = 2; + static const int nullOrUndefined = 0; + static const int arrayBuffer = 1; + static const int sharedArrayBuffer = 2; + static const int unknown = 3; } /// A JS `ArrayBuffer` or `SharedArrayBuffer`. @@ -23,16 +24,17 @@ final class JSArrayBufferImpl implements ByteBuffer { late int _refType = _getRefType(_ref); - static int _getRefType(WasmExternRef? _ref) => + static int _getRefType(WasmExternRef? ref) => // Feature check for `SharedArrayBuffer` before doing a type-check. js.JS('''o => { - if (o instanceof ArrayBuffer) return 0; + if (o === null || o === undefined) return 0; + if (o instanceof ArrayBuffer) return 1; if (globalThis.SharedArrayBuffer !== undefined && o instanceof SharedArrayBuffer) { - return 1; + return 2; } - return 2; - }''', _ref).toIntUnsigned(); + return 3; + }''', ref).toIntUnsigned(); bool get isArrayBuffer => _refType == _ArrayBufferType.arrayBuffer; @@ -53,6 +55,14 @@ final class JSArrayBufferImpl implements ByteBuffer { return JSArrayBufferImpl.fromRefUnchecked(ref); } + static JSArrayBufferImpl? fromRefNullable(WasmExternRef? ref) { + final refType = _getRefType(ref); + if (refType == _ArrayBufferType.nullOrUndefined) return null; + if (refType == _ArrayBufferType.unknown) + return _throwConversionFailureError("ByteBuffer"); + return JSArrayBufferImpl.fromRefUnchecked(ref); + } + @pragma("wasm:prefer-inline") WasmExternRef? get toExternRef => _ref; @@ -286,11 +296,14 @@ final class JSDataViewImpl implements ByteData { final bool _immutable; - static bool _checkRefType(WasmExternRef? ref) => - js.JS('o => o instanceof DataView', ref); + static int _getRefType(WasmExternRef? ref) => js.JS('''o => { + if (o === null || o === undefined) return 0; + if (o instanceof DataView) return 1; + return 2; + }''', ref).toIntUnsigned(); JSDataViewImpl._(this._ref, this.lengthInBytes, this._immutable) { - assert(_checkRefType(_ref)); + assert(_getRefType(_ref) == 1); } JSDataViewImpl(this.lengthInBytes) @@ -302,12 +315,18 @@ final class JSDataViewImpl implements ByteData { _immutable = false; factory JSDataViewImpl.fromRef(WasmExternRef? ref) { - if (!_checkRefType(ref)) { - return _throwConversionFailureError("ByteData"); - } + final refType = _getRefType(ref); + if (refType != 1) return _throwConversionFailureError("ByteData"); return JSDataViewImpl.fromRefUnchecked(ref); } + static JSDataViewImpl? fromRefNullable(WasmExternRef? ref) { + final refType = _getRefType(ref); + if (refType == 0) return null; + if (refType == 2) return JSDataViewImpl.fromRefUnchecked(ref); + return _throwConversionFailureError("ByteData"); + } + JSDataViewImpl.immutable(this._ref, this.lengthInBytes) : _immutable = true; factory JSDataViewImpl.view( @@ -825,19 +844,29 @@ final class JSUint8ArrayImpl extends JSIntegerArrayBase factory JSUint8ArrayImpl(int length) => JSUint8ArrayImpl._(_newDataView(length)); - static bool _checkArrayRefType(WasmExternRef? ref) => - js.JS('o => o instanceof Uint8Array', ref); + static int _getRefType(WasmExternRef? ref) => js.JS('''o => { + if (o === null || o === undefined) return 0; + if (o instanceof Uint8Array) return 1; + return 2; + }''', ref).toIntUnsigned(); - factory JSUint8ArrayImpl.fromArrayRefUnchecked(WasmExternRef? ref) { - _checkArrayRefType(ref); + factory JSUint8ArrayImpl.fromRefUnchecked(WasmExternRef? ref) { + assert(_getRefType(ref) == 1); return JSUint8ArrayImpl._(_dataViewFromJSArray(ref), ref); } - factory JSUint8ArrayImpl.fromArrayRef(WasmExternRef? ref) { - if (!_checkArrayRefType(ref)) { + factory JSUint8ArrayImpl.fromRef(WasmExternRef? ref) { + if (_getRefType(ref) != 1) { return _throwConversionFailureError("Uint8List"); } - return JSUint8ArrayImpl.fromArrayRefUnchecked(ref); + return JSUint8ArrayImpl.fromRefUnchecked(ref); + } + + static JSUint8ArrayImpl? fromRefNullable(WasmExternRef? ref) { + final refType = _getRefType(ref); + if (refType == 0) return null; + if (refType == 1) return JSUint8ArrayImpl.fromRefUnchecked(ref); + return _throwConversionFailureError("Uint8List"); } factory JSUint8ArrayImpl.view( @@ -931,19 +960,29 @@ final class JSInt8ArrayImpl extends JSIntegerArrayBase factory JSInt8ArrayImpl(int length) => JSInt8ArrayImpl._(_newDataView(length)); - static bool _checkArrayRefType(WasmExternRef? ref) => - js.JS('o => o instanceof Int8Array', ref); + static int _getRefType(WasmExternRef? ref) => js.JS('''o => { + if (o === null || o === undefined) return 0; + if (o instanceof Int8Array) return 1; + return 2; + }''', ref).toIntUnsigned(); - factory JSInt8ArrayImpl.fromArrayRefUnchecked(WasmExternRef? ref) { - assert(_checkArrayRefType(ref)); + factory JSInt8ArrayImpl.fromRefUnchecked(WasmExternRef? ref) { + assert(_getRefType(ref) == 1); return JSInt8ArrayImpl._(_dataViewFromJSArray(ref), ref); } - factory JSInt8ArrayImpl.fromArrayRef(WasmExternRef? ref) { - if (!_checkArrayRefType(ref)) { + factory JSInt8ArrayImpl.fromRef(WasmExternRef? ref) { + if (_getRefType(ref) != 1) { return _throwConversionFailureError("Int8List"); } - return JSInt8ArrayImpl.fromArrayRefUnchecked(ref); + return JSInt8ArrayImpl.fromRefUnchecked(ref); + } + + static JSInt8ArrayImpl? fromRefNullable(WasmExternRef? ref) { + final refType = _getRefType(ref); + if (refType == 0) return null; + if (refType == 1) return JSInt8ArrayImpl.fromRefUnchecked(ref); + return _throwConversionFailureError("Int8List"); } factory JSInt8ArrayImpl.view( @@ -1042,16 +1081,26 @@ final class JSUint8ClampedArrayImpl extends JSIntegerArrayBase static bool _checkArrayRefType(WasmExternRef? ref) => js.JS('o => o instanceof Uint8ClampedArray', ref); - factory JSUint8ClampedArrayImpl.fromArrayRefUnchecked(WasmExternRef? ref) { + factory JSUint8ClampedArrayImpl.fromRefUnchecked(WasmExternRef? ref) { assert(_checkArrayRefType(ref)); return JSUint8ClampedArrayImpl._(_dataViewFromJSArray(ref), ref); } - factory JSUint8ClampedArrayImpl.fromArrayRef(WasmExternRef? ref) { + factory JSUint8ClampedArrayImpl.fromRef(WasmExternRef? ref) { if (!_checkArrayRefType(ref)) { return _throwConversionFailureError("Uint8ClampedList"); } - return JSUint8ClampedArrayImpl.fromArrayRefUnchecked(ref); + return JSUint8ClampedArrayImpl.fromRefUnchecked(ref); + } + + static JSUint8ClampedArrayImpl? fromRefNullable(WasmExternRef? ref) { + if (!_checkArrayRefType(ref)) { + if (ref.isNull) { + return null; + } + return _throwConversionFailureError("Uint8ClampedList"); + } + return JSUint8ClampedArrayImpl.fromRefUnchecked(ref); } factory JSUint8ClampedArrayImpl.view( @@ -1134,16 +1183,26 @@ final class JSUint16ArrayImpl extends JSIntegerArrayBase static bool _checkArrayRefType(WasmExternRef? ref) => js.JS('o => o instanceof Uint16Array', ref); - factory JSUint16ArrayImpl.fromArrayRefUnchecked(WasmExternRef? ref) { + factory JSUint16ArrayImpl.fromRefUnchecked(WasmExternRef? ref) { assert(_checkArrayRefType(ref)); return JSUint16ArrayImpl._(_dataViewFromJSArray(ref), ref); } - factory JSUint16ArrayImpl.fromArrayRef(WasmExternRef? ref) { + factory JSUint16ArrayImpl.fromRef(WasmExternRef? ref) { if (!_checkArrayRefType(ref)) { return _throwConversionFailureError("Uint16List"); } - return JSUint16ArrayImpl.fromArrayRefUnchecked(ref); + return JSUint16ArrayImpl.fromRefUnchecked(ref); + } + + static JSUint16ArrayImpl? fromRefNullable(WasmExternRef? ref) { + if (!_checkArrayRefType(ref)) { + if (ref.isNull) { + return null; + } + return _throwConversionFailureError("Uint16List"); + } + return JSUint16ArrayImpl.fromRefUnchecked(ref); } factory JSUint16ArrayImpl.view( @@ -1247,16 +1306,26 @@ final class JSInt16ArrayImpl extends JSIntegerArrayBase static bool _checkArrayRefType(WasmExternRef? ref) => js.JS('o => o instanceof Int16Array', ref); - factory JSInt16ArrayImpl.fromArrayRefUnchecked(WasmExternRef? ref) { + factory JSInt16ArrayImpl.fromRefUnchecked(WasmExternRef? ref) { assert(_checkArrayRefType(ref)); return JSInt16ArrayImpl._(_dataViewFromJSArray(ref), ref); } - factory JSInt16ArrayImpl.fromArrayRef(WasmExternRef? ref) { + factory JSInt16ArrayImpl.fromRef(WasmExternRef? ref) { if (!_checkArrayRefType(ref)) { return _throwConversionFailureError("Int16List"); } - return JSInt16ArrayImpl.fromArrayRefUnchecked(ref); + return JSInt16ArrayImpl.fromRefUnchecked(ref); + } + + static JSInt16ArrayImpl? fromRefNullable(WasmExternRef? ref) { + if (!_checkArrayRefType(ref)) { + if (ref.isNull) { + return null; + } + return _throwConversionFailureError("Int16List"); + } + return JSInt16ArrayImpl.fromRefUnchecked(ref); } factory JSInt16ArrayImpl.view( @@ -1357,19 +1426,29 @@ final class JSUint32ArrayImpl extends JSIntegerArrayBase factory JSUint32ArrayImpl(int length) => JSUint32ArrayImpl._(_newDataView(length * 4)); - static bool _checkArrayRefType(WasmExternRef? ref) => - js.JS('o => o instanceof Uint32Array', ref); + static int _getRefType(WasmExternRef? ref) => js.JS('''o => { + if (o === null || o === undefined) return 0; + if (o instanceof Uint32Array) return 1; + return 2; + }''', ref).toIntUnsigned(); - factory JSUint32ArrayImpl.fromArrayRefUnchecked(WasmExternRef? ref) { - assert(_checkArrayRefType(ref)); + factory JSUint32ArrayImpl.fromRefUnchecked(WasmExternRef? ref) { + assert(_getRefType(ref) == 1); return JSUint32ArrayImpl._(_dataViewFromJSArray(ref), ref); } - factory JSUint32ArrayImpl.fromArrayRef(WasmExternRef? ref) { - if (!_checkArrayRefType(ref)) { + factory JSUint32ArrayImpl.fromRef(WasmExternRef? ref) { + if (_getRefType(ref) != 1) { return _throwConversionFailureError("Uint32List"); } - return JSUint32ArrayImpl.fromArrayRefUnchecked(ref); + return JSUint32ArrayImpl.fromRefUnchecked(ref); + } + + static JSUint32ArrayImpl? fromRefNullable(WasmExternRef? ref) { + final refType = _getRefType(ref); + if (refType == 0) return null; + if (refType == 1) return JSUint32ArrayImpl.fromRefUnchecked(ref); + return _throwConversionFailureError("Uint32List"); } factory JSUint32ArrayImpl.view( @@ -1470,19 +1549,29 @@ final class JSInt32ArrayImpl extends JSIntegerArrayBase factory JSInt32ArrayImpl(int length) => JSInt32ArrayImpl._(_newDataView(length * 4)); - static bool _checkArrayRefType(WasmExternRef? ref) => - js.JS('o => o instanceof Int32Array', ref); + static int _getRefType(WasmExternRef? ref) => js.JS('''o => { + if (o === null || o === undefined) return 0; + if (o instanceof Int32Array) return 1; + return 2; + }''', ref).toIntUnsigned(); - factory JSInt32ArrayImpl.fromArrayRefUnchecked(WasmExternRef? ref) { - assert(_checkArrayRefType(ref)); + factory JSInt32ArrayImpl.fromRefUnchecked(WasmExternRef? ref) { + assert(_getRefType(ref) == 1); return JSInt32ArrayImpl._(_dataViewFromJSArray(ref), ref); } - factory JSInt32ArrayImpl.fromArrayRef(WasmExternRef? ref) { - if (!_checkArrayRefType(ref)) { + factory JSInt32ArrayImpl.fromRef(WasmExternRef? ref) { + if (_getRefType(ref) != 1) { return _throwConversionFailureError("Int32List"); } - return JSInt32ArrayImpl.fromArrayRefUnchecked(ref); + return JSInt32ArrayImpl.fromRefUnchecked(ref); + } + + static JSInt32ArrayImpl? fromRefNullable(WasmExternRef? ref) { + final refType = _getRefType(ref); + if (refType == 0) return null; + if (refType == 1) return JSInt32ArrayImpl.fromRefUnchecked(ref); + return _throwConversionFailureError("Int32List"); } factory JSInt32ArrayImpl.view( @@ -2222,19 +2311,29 @@ final class JSFloat32ArrayImpl extends JSFloatArrayBase factory JSFloat32ArrayImpl(int length) => JSFloat32ArrayImpl._(_newDataView(length * 4)); - static bool _checkArrayRefType(WasmExternRef? ref) => - js.JS('o => o instanceof Float32Array', ref); + static int _getRefType(WasmExternRef? ref) => js.JS('''o => { + if (o === null || o === undefined) return 0; + if (o instanceof Float32Array) return 1; + return 2; + }''', ref).toIntUnsigned(); - factory JSFloat32ArrayImpl.fromArrayRefUnchecked(WasmExternRef? ref) { - assert(_checkArrayRefType(ref)); + factory JSFloat32ArrayImpl.fromRefUnchecked(WasmExternRef? ref) { + assert(_getRefType(ref) == 1); return JSFloat32ArrayImpl._(_dataViewFromJSArray(ref), ref); } - factory JSFloat32ArrayImpl.fromArrayRef(WasmExternRef? ref) { - if (!_checkArrayRefType(ref)) { + factory JSFloat32ArrayImpl.fromRef(WasmExternRef? ref) { + if (_getRefType(ref) != 1) { return _throwConversionFailureError("Float32List"); } - return JSFloat32ArrayImpl.fromArrayRefUnchecked(ref); + return JSFloat32ArrayImpl.fromRefUnchecked(ref); + } + + static JSFloat32ArrayImpl? fromRefNullable(WasmExternRef? ref) { + final refType = _getRefType(ref); + if (refType == 0) return null; + if (refType == 1) return JSFloat32ArrayImpl.fromRefUnchecked(ref); + return _throwConversionFailureError("Float32List"); } factory JSFloat32ArrayImpl.view( @@ -2336,19 +2435,29 @@ final class JSFloat64ArrayImpl extends JSFloatArrayBase factory JSFloat64ArrayImpl(int length) => JSFloat64ArrayImpl._(_newDataView(length * 8)); - static bool _checkArrayRefType(WasmExternRef? ref) => - js.JS('o => o instanceof Float64Array', ref); + static int _getRefType(WasmExternRef? ref) => js.JS('''o => { + if (o === null || o === undefined) return 0; + if (o instanceof Float64Array) return 1; + return 2; + }''', ref).toIntUnsigned(); - factory JSFloat64ArrayImpl.fromArrayRefUnchecked(WasmExternRef? ref) { - assert(_checkArrayRefType(ref)); + factory JSFloat64ArrayImpl.fromRefUnchecked(WasmExternRef? ref) { + assert(_getRefType(ref) == 1); return JSFloat64ArrayImpl._(_dataViewFromJSArray(ref), ref); } - factory JSFloat64ArrayImpl.fromArrayRef(WasmExternRef? ref) { - if (!_checkArrayRefType(ref)) { + factory JSFloat64ArrayImpl.fromRef(WasmExternRef? ref) { + if (_getRefType(ref) != 1) { return _throwConversionFailureError("Float64List"); } - return JSFloat64ArrayImpl.fromArrayRefUnchecked(ref); + return JSFloat64ArrayImpl.fromRefUnchecked(ref); + } + + static JSFloat64ArrayImpl? fromRefNullable(WasmExternRef? ref) { + final refType = _getRefType(ref); + if (refType == 0) return null; + if (refType == 1) return JSFloat64ArrayImpl.fromRefUnchecked(ref); + return _throwConversionFailureError("Float64List"); } factory JSFloat64ArrayImpl.view( diff --git a/tests/web/wasm/js_interop_type_tests_test.dart b/tests/web/wasm/js_interop_type_tests_test.dart index 0c6af8bd946..2d4cf25fb42 100644 --- a/tests/web/wasm/js_interop_type_tests_test.dart +++ b/tests/web/wasm/js_interop_type_tests_test.dart @@ -15,47 +15,45 @@ import 'package:expect/expect.dart'; void main() { Expect.throws( - () => JSInt8ArrayImpl.fromArrayRef( + () => + JSInt8ArrayImpl.fromRef(JS('() => new Uint8Array(10)')), + ); + Expect.throws( + () => + JSUint8ArrayImpl.fromRef(JS('() => new Int8Array(10)')), + ); + Expect.throws( + () => JSUint8ClampedArrayImpl.fromRef( JS('() => new Uint8Array(10)'), ), ); Expect.throws( - () => JSUint8ArrayImpl.fromArrayRef( - JS('() => new Int8Array(10)'), - ), - ); - Expect.throws( - () => JSUint8ClampedArrayImpl.fromArrayRef( - JS('() => new Uint8Array(10)'), - ), - ); - Expect.throws( - () => JSInt16ArrayImpl.fromArrayRef( + () => JSInt16ArrayImpl.fromRef( JS('() => new Uint16Array(10)'), ), ); Expect.throws( - () => JSUint16ArrayImpl.fromArrayRef( + () => JSUint16ArrayImpl.fromRef( JS('() => new Int16Array(10)'), ), ); Expect.throws( - () => JSInt32ArrayImpl.fromArrayRef( + () => JSInt32ArrayImpl.fromRef( JS('() => new Uint32Array(10)'), ), ); Expect.throws( - () => JSUint32ArrayImpl.fromArrayRef( + () => JSUint32ArrayImpl.fromRef( JS('() => new Int32Array(10)'), ), ); Expect.throws( - () => JSFloat32ArrayImpl.fromArrayRef( + () => JSFloat32ArrayImpl.fromRef( JS('() => new Float64Array(10)'), ), ); Expect.throws( - () => JSFloat64ArrayImpl.fromArrayRef( + () => JSFloat64ArrayImpl.fromRef( JS('() => new Float32Array(10)'), ), );