From 99d503b10514d32f4e1ead80e7bf01e65a88bb4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20A=C4=9Facan?= Date: Tue, 20 Jan 2026 04:32:41 -0800 Subject: [PATCH] [dart2wasm] Avoid redundant null checks of JS interop returns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Split JS-to-Dart conversion functions into two categories: - Those that handle `null` and `undefined` and return Dart `null`. - Those that don't expect `null` or `undefined`. Then based on the return value of an interop function, call the right one. This moves null checks from interop call sites to conversion functions, effectively outlining the duplicated null checks. Boxing functions like `JSInt8ArrayImpl.fromArrayRef` are renamed as `fromRef`, for consistency with other boxing functions, and also because `fromRef` is more accurate. These functions already check the type and so they can be passed any `externref`, not just those that represent JS arrays. Fixes #61906. Issue: https://github.com/dart-lang/sdk/issues/61906 Change-Id: I54e80fe2e541ae6ef384c2c008f2dbc1e7e3bd76 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/473261 Commit-Queue: Ömer Ağacan Reviewed-by: Martin Kustermann --- pkg/dart2wasm/lib/js/util.dart | 110 +++++--- pkg/dart2wasm/test/ir_tests/dyn_closure.wat | 8 +- .../ir_tests/dyn_closure_function_apply.wat | 8 +- .../dyn_closure_function_apply_named.wat | 8 +- pkg/dart2wasm/test/ir_tests/interop.bool.wat | 71 ++---- .../test/ir_tests/interop.double.wat | 63 +---- pkg/dart2wasm/test/ir_tests/interop.int.wat | 63 +---- pkg/dart2wasm/test/ir_tests/interop.num.wat | 80 ++---- .../test/ir_tests/interop.string.wat | 57 +---- sdk/lib/_internal/wasm/lib/js_helper.dart | 140 +++++++--- .../_internal/wasm/lib/js_interop_patch.dart | 32 +-- sdk/lib/_internal/wasm/lib/js_string.dart | 15 ++ .../_internal/wasm/lib/js_typed_array.dart | 239 +++++++++++++----- .../web/wasm/js_interop_type_tests_test.dart | 32 ++- 14 files changed, 469 insertions(+), 457 deletions(-) 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)'), ), );