diff --git a/pkg/dart2wasm/lib/async.dart b/pkg/dart2wasm/lib/async.dart index 0def513947d..95a4b96dc78 100644 --- a/pkg/dart2wasm/lib/async.dart +++ b/pkg/dart2wasm/lib/async.dart @@ -324,20 +324,22 @@ class AsyncStateMachineCodeGenerator extends StateMachineCodeGenerator { b.local_set(exceptionLocal); callCompleteError(); - // Handle JS exceptions. - b.catch_legacy(translator.getJsExceptionTag(b.moduleBuilder)); + if (!translator.options.standalone) { + // Handle JS exceptions. + b.catch_legacy(translator.getJsExceptionTag(b.moduleBuilder)); - final jsExceptionLocal = addLocal(w.RefType.extern(nullable: true)); - b.local_tee(jsExceptionLocal); + final jsExceptionLocal = addLocal(w.RefType.extern(nullable: true)); + b.local_tee(jsExceptionLocal); - call(translator.boxJsException.reference); - b.local_tee(exceptionLocal); // ref null #Top + call(translator.boxJsException.reference); + b.local_tee(exceptionLocal); // ref null #Top - b.local_get(jsExceptionLocal); - call(translator.jsExceptionStackTrace.reference); - b.local_set(stackTraceLocal); + b.local_get(jsExceptionLocal); + call(translator.jsExceptionStackTrace.reference); + b.local_set(stackTraceLocal); - callCompleteError(); + callCompleteError(); + } b.end(); // try diff --git a/pkg/dart2wasm/lib/code_generator.dart b/pkg/dart2wasm/lib/code_generator.dart index 12c47f63088..581e8406dbe 100644 --- a/pkg/dart2wasm/lib/code_generator.dart +++ b/pkg/dart2wasm/lib/code_generator.dart @@ -1129,9 +1129,11 @@ abstract class AstCodeGenerator b.rethrow_(tryBlock); // Handle JS exceptions. - b.catch_legacy(translator.getJsExceptionTag(b.moduleBuilder)); - translateStatement(node.finalizer); - b.rethrow_(tryBlock); + if (!translator.options.standalone) { + b.catch_legacy(translator.getJsExceptionTag(b.moduleBuilder)); + translateStatement(node.finalizer); + b.rethrow_(tryBlock); + } b.end(); // tryBlock @@ -6201,6 +6203,12 @@ class LambdaCallTarget extends CallTarget { /// Note that the guard type can be nullable, but the value for the exception /// needs to be non-null regardless of the guard type, as per Dart semantics. bool guardCanMatchJSException(Translator translator, DartType guard) { + if (translator.options.standalone) { + // Standalone mode doesn't run in a JavaScript context and doesn't have JS + // exceptions. + return false; + } + return translator.typeEnvironment.isSubtypeOf( InterfaceType(translator.jsValueClass, Nullability.nonNullable), guard.extensionTypeErasure, diff --git a/pkg/dart2wasm/lib/compile.dart b/pkg/dart2wasm/lib/compile.dart index e2158e5e2c1..abeeedc1452 100644 --- a/pkg/dart2wasm/lib/compile.dart +++ b/pkg/dart2wasm/lib/compile.dart @@ -425,11 +425,13 @@ Future _runTfaPhase( ); } - js.performJSInteropTransformations( - component.libraries, - coreTypes, - classHierarchy, - ); + if (!options.translatorOptions.standalone) { + js.performJSInteropTransformations( + component.libraries, + coreTypes, + classHierarchy, + ); + } final librariesToTransform = component.libraries; final constantEvaluator = ConstantEvaluator( @@ -635,25 +637,30 @@ Future _runCodegenPhase( }); await Future.wait(writeFutures); - final jsRuntimeFinalizer = js.RuntimeFinalizer( - coreTypes, - translator.interopMemberNamer, - ); + if (!options.translatorOptions.standalone) { + final jsRuntimeFinalizer = js.RuntimeFinalizer( + coreTypes, + translator.interopMemberNamer, + ); - final jsRuntime = jsRuntimeFinalizer.generate( - moduleOutputData.mainModule.moduleImportName, - translator.functions.translatedProcedures, - translator.internalizedStringsForJSRuntime, - translator.options.requireJsStringBuiltin, - translator.options.enableDeferredLoading || - translator.options.enableMultiModuleStressTestMode, - ); + final jsRuntime = jsRuntimeFinalizer.generate( + moduleOutputData.mainModule.moduleImportName, + translator.functions.translatedProcedures, + translator.internalizedStringsForJSRuntime, + translator.options.requireJsStringBuiltin, + translator.options.enableDeferredLoading || + translator.options.enableMultiModuleStressTestMode, + ); - final supportJs = _generateSupportJs(options.translatorOptions); + final supportJs = _generateSupportJs(options.translatorOptions); - final deferredMapFile = options.deferredMapUri; - if (deferredMapFile != null) { - await writeDeferredMapFile(component, coreTypes, options, loadingMap); + final deferredMapFile = options.deferredMapUri; + if (deferredMapFile != null) { + await writeDeferredMapFile(component, coreTypes, options, loadingMap); + } + + await ioManager.writeJsRuntime(jsRuntime); + await ioManager.writeSupportJs(supportJs); } final wasmOutputFilename = path.basename(options.outputFile); @@ -666,9 +673,6 @@ Future _runCodegenPhase( ) .toSet(); - await ioManager.writeJsRuntime(jsRuntime); - await ioManager.writeSupportJs(supportJs); - if (options.recordedUsesFile != null) { record_use.LoadingUnit loadingUnitForNode(TreeNode node) { while (node is! NamedNode) { diff --git a/pkg/dart2wasm/lib/state_machine.dart b/pkg/dart2wasm/lib/state_machine.dart index 7fa0a85135c..da88f042b70 100644 --- a/pkg/dart2wasm/lib/state_machine.dart +++ b/pkg/dart2wasm/lib/state_machine.dart @@ -386,7 +386,7 @@ class ExceptionHandlerStack { canHandleJSExceptions |= handler.canHandleJSExceptions; } - if (canHandleJSExceptions) { + if (canHandleJSExceptions && !codeGen.translator.options.standalone) { b.catch_legacy(codeGen.translator.getJsExceptionTag(b.moduleBuilder)); final jsExceptionLocal = codeGen.addLocal( diff --git a/pkg/dart2wasm/lib/tags.dart b/pkg/dart2wasm/lib/tags.dart index 303250ab7c6..762bdb8c652 100644 --- a/pkg/dart2wasm/lib/tags.dart +++ b/pkg/dart2wasm/lib/tags.dart @@ -43,6 +43,8 @@ class ExceptionTags { } w.Tag _importJsExceptionTag() { + assert(!translator.options.standalone); + final w.FunctionType tagType = translator.typesBuilder.defineFunction( const [w.RefType.extern(nullable: true)], const [], diff --git a/pkg/dart2wasm/lib/target.dart b/pkg/dart2wasm/lib/target.dart index 65cc3f280b3..edaefb1403f 100644 --- a/pkg/dart2wasm/lib/target.dart +++ b/pkg/dart2wasm/lib/target.dart @@ -159,8 +159,6 @@ class WasmTarget extends Target { 'dart:_compact_hash', 'dart:_http', 'dart:_internal', - 'dart:_js_helper', - 'dart:_js_types', 'dart:_list', 'dart:_string', 'dart:_wasm', @@ -168,11 +166,16 @@ class WasmTarget extends Target { 'dart:developer', 'dart:ffi', 'dart:io', - 'dart:js_interop', - 'dart:js_interop_unsafe', 'dart:nativewrappers', 'dart:typed_data', - if (mode == .standalone) 'dart:_embedder', + if (mode == .standalone) + 'dart:_embedder' + else ...[ + 'dart:_js_helper', + 'dart:_js_types', + 'dart:js_interop', + 'dart:js_interop_unsafe', + ], ]; @override @@ -182,16 +185,19 @@ class WasmTarget extends Target { 'dart:_boxed_int', 'dart:_compact_hash', 'dart:_error_utils', - 'dart:_js_helper', - 'dart:_js_types', 'dart:_list', 'dart:_string', 'dart:_wasm', 'dart:collection', - 'dart:js_interop', - 'dart:js_interop_unsafe', 'dart:typed_data', - if (mode == .standalone) 'dart:_embedder', + if (mode == .standalone) + 'dart:_embedder' + else ...[ + 'dart:_js_helper', + 'dart:_js_types', + 'dart:js_interop', + 'dart:js_interop_unsafe', + ], ]; @override @@ -319,32 +325,34 @@ class WasmTarget extends Target { } } - Set transitiveImportingJSInterop = { - ...jsInteropHelper.calculateTransitiveImportsOfJsInteropIfUsed( - component.libraries, - Uri.parse("dart:js_interop"), - ), - ...jsInteropHelper.calculateTransitiveImportsOfJsInteropIfUsed( - component.libraries, - Uri.parse("dart:convert"), - ), - ...jsInteropHelper.calculateTransitiveImportsOfJsInteropIfUsed( - component.libraries, - Uri.parse("dart:_string"), - ), - }; - if (transitiveImportingJSInterop.isEmpty) { - logger?.call("Skipped JS interop transformations"); - } else { - _performJSInteropTransformations( - component, - coreTypes, - hierarchy, - transitiveImportingJSInterop, - diagnosticReporter, - referenceFromIndex, - ); - logger?.call("Transformed JS interop classes"); + if (mode != .standalone) { + Set transitiveImportingJSInterop = { + ...jsInteropHelper.calculateTransitiveImportsOfJsInteropIfUsed( + component.libraries, + Uri.parse("dart:js_interop"), + ), + ...jsInteropHelper.calculateTransitiveImportsOfJsInteropIfUsed( + component.libraries, + Uri.parse("dart:convert"), + ), + ...jsInteropHelper.calculateTransitiveImportsOfJsInteropIfUsed( + component.libraries, + Uri.parse("dart:_string"), + ), + }; + if (transitiveImportingJSInterop.isEmpty) { + logger?.call("Skipped JS interop transformations"); + } else { + _performJSInteropTransformations( + component, + coreTypes, + hierarchy, + transitiveImportingJSInterop, + diagnosticReporter, + referenceFromIndex, + ); + logger?.call("Transformed JS interop classes"); + } } // If we are compiling with a null environment, skip constant resolution diff --git a/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/fuse_with_and.constraints.txt b/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/fuse_with_and.constraints.txt index 7f33fa5f2db..b98051f92da 100644 --- a/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/fuse_with_and.constraints.txt +++ b/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/fuse_with_and.constraints.txt @@ -6,8 +6,8 @@ Part 0 - pkg/compiler/test/custom_split/data/fuse_with_and/lib4.dart prefix: b4 References - dart:_boxed_int::BoxedInt::@methods::toRadixString - - dart:_string::@methods::dart:_string::_JS_Inline_22 - - dart:_string::@methods::dart:_string::_JS_Inline_30 + - dart:_string::@methods::dart:_string::_JS_Inline_173 + - dart:_string::@methods::dart:_string::_JS_Inline_181 - dart:_string::@methods::dart:_string::_jsIdentical - dart:_string::@methods::dart:_string::_jsStringToUpperCase - dart:_string::@methods::jsStringFromCharCodeArray diff --git a/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/fuse_with_and.default.txt b/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/fuse_with_and.default.txt index 2639838b9f0..d9d272e904b 100644 --- a/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/fuse_with_and.default.txt +++ b/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/fuse_with_and.default.txt @@ -6,8 +6,8 @@ Part 0 - pkg/compiler/test/custom_split/data/fuse_with_and/lib4.dart prefix: b4 References - dart:_boxed_int::BoxedInt::@methods::toRadixString - - dart:_string::@methods::dart:_string::_JS_Inline_22 - - dart:_string::@methods::dart:_string::_JS_Inline_30 + - dart:_string::@methods::dart:_string::_JS_Inline_173 + - dart:_string::@methods::dart:_string::_JS_Inline_181 - dart:_string::@methods::dart:_string::_jsIdentical - dart:_string::@methods::dart:_string::_jsStringToUpperCase - dart:_string::@methods::jsStringFromCharCodeArray diff --git a/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/fuse_with_or.constraints.txt b/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/fuse_with_or.constraints.txt index 0733798aab0..6ed2762aff5 100644 --- a/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/fuse_with_or.constraints.txt +++ b/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/fuse_with_or.constraints.txt @@ -6,8 +6,8 @@ Part 0 - pkg/compiler/test/custom_split/data/fuse_with_or/lib4.dart prefix: b4 References - dart:_boxed_int::BoxedInt::@methods::toRadixString - - dart:_string::@methods::dart:_string::_JS_Inline_22 - - dart:_string::@methods::dart:_string::_JS_Inline_30 + - dart:_string::@methods::dart:_string::_JS_Inline_173 + - dart:_string::@methods::dart:_string::_JS_Inline_181 - dart:_string::@methods::dart:_string::_jsIdentical - dart:_string::@methods::dart:_string::_jsStringToUpperCase - dart:_string::@methods::jsStringFromCharCodeArray diff --git a/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/fuse_with_or.default.txt b/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/fuse_with_or.default.txt index 525f2bb6608..b2f469ca986 100644 --- a/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/fuse_with_or.default.txt +++ b/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/fuse_with_or.default.txt @@ -6,8 +6,8 @@ Part 0 - pkg/compiler/test/custom_split/data/fuse_with_or/lib4.dart prefix: b4 References - dart:_boxed_int::BoxedInt::@methods::toRadixString - - dart:_string::@methods::dart:_string::_JS_Inline_22 - - dart:_string::@methods::dart:_string::_JS_Inline_30 + - dart:_string::@methods::dart:_string::_JS_Inline_173 + - dart:_string::@methods::dart:_string::_JS_Inline_181 - dart:_string::@methods::dart:_string::_jsIdentical - dart:_string::@methods::dart:_string::_jsStringToUpperCase - dart:_string::@methods::jsStringFromCharCodeArray diff --git a/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/just_fuse.constraints.txt b/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/just_fuse.constraints.txt index 941697daef8..d032b38c02f 100644 --- a/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/just_fuse.constraints.txt +++ b/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/just_fuse.constraints.txt @@ -6,8 +6,8 @@ Part 0 - pkg/compiler/test/custom_split/data/just_fuse/lib4.dart prefix: b4 References - dart:_boxed_int::BoxedInt::@methods::toRadixString - - dart:_string::@methods::dart:_string::_JS_Inline_22 - - dart:_string::@methods::dart:_string::_JS_Inline_30 + - dart:_string::@methods::dart:_string::_JS_Inline_173 + - dart:_string::@methods::dart:_string::_JS_Inline_181 - dart:_string::@methods::dart:_string::_jsIdentical - dart:_string::@methods::dart:_string::_jsStringToUpperCase - dart:_string::@methods::jsStringFromCharCodeArray diff --git a/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/just_fuse.default.txt b/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/just_fuse.default.txt index 59574a3e699..7998286f7bc 100644 --- a/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/just_fuse.default.txt +++ b/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/just_fuse.default.txt @@ -6,8 +6,8 @@ Part 0 - pkg/compiler/test/custom_split/data/just_fuse/lib4.dart prefix: b4 References - dart:_boxed_int::BoxedInt::@methods::toRadixString - - dart:_string::@methods::dart:_string::_JS_Inline_22 - - dart:_string::@methods::dart:_string::_JS_Inline_30 + - dart:_string::@methods::dart:_string::_JS_Inline_173 + - dart:_string::@methods::dart:_string::_JS_Inline_181 - dart:_string::@methods::dart:_string::_jsIdentical - dart:_string::@methods::dart:_string::_jsStringToUpperCase - dart:_string::@methods::jsStringFromCharCodeArray diff --git a/pkg/dart2wasm/test/ir_tests/constructor.wat b/pkg/dart2wasm/test/ir_tests/constructor.wat index 56e3be2e0c8..b58a8822de9 100644 --- a/pkg/dart2wasm/test/ir_tests/constructor.wat +++ b/pkg/dart2wasm/test/ir_tests/constructor.wat @@ -116,7 +116,7 @@ local.get $var0 local.get $onlyUsedInSuper local.set $var3 - i32.const 63 + i32.const 85 local.get $var3 struct.new $BoxedInt call $"new _MixinApplication1&Base&SubMixin.named (initializer)" @@ -248,7 +248,7 @@ global.get $"\", \"" local.get $onlyUsedInSubBody local.set $var4 - i32.const 63 + i32.const 85 local.get $var4 struct.new $BoxedInt array.new_fixed $Array 6 @@ -273,7 +273,7 @@ local.get $var0 local.get $onlyUsedInSuper1 local.set $var3 - i32.const 63 + i32.const 85 local.get $var3 struct.new $BoxedInt local.get $onlyUsedInSuper2 @@ -328,7 +328,7 @@ local.get $var0 local.get $onlyUsedInSuper1 local.set $var3 - i32.const 63 + i32.const 85 local.get $var3 struct.new $BoxedInt call $"new _MixinApplication1&Base&SubMixin.sub2 (initializer)" diff --git a/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use_module1.wat b/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use_module1.wat index 6e703de7881..fcb344a4470 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use_module1.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use_module1.wat @@ -18,7 +18,7 @@ (global $MyConstClass (ref $MyConstClass) (i32.const 108) (i32.const 0) - (i32.const 103) + (i32.const 60) (i32.const 0) (global.get $.h1-nonshared-const) (struct.new $JSExternWrapper) diff --git a/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use_module2.wat b/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use_module2.wat index d26152079db..7da3ac8a14b 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use_module2.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use_module2.wat @@ -17,7 +17,7 @@ (global $MyConstClass (ref $MyConstClass) (i32.const 108) (i32.const 0) - (i32.const 103) + (i32.const 60) (i32.const 0) (global.get $.h0-nonshared-const) (struct.new $JSExternWrapper) diff --git a/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use_module3.wat b/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use_module3.wat index 737b66851cc..1c54c08596b 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use_module3.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use_module3.wat @@ -16,7 +16,7 @@ (global $MyConstClass (ref $MyConstClass) (i32.const 108) (i32.const 0) - (i32.const 103) + (i32.const 60) (i32.const 0) (global.get $.shared-const) (struct.new $JSExternWrapper) diff --git a/pkg/dart2wasm/test/ir_tests/deferred.constant.type_use_module1.wat b/pkg/dart2wasm/test/ir_tests/deferred.constant.type_use_module1.wat index 0960a20b38b..87304e7697e 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.constant.type_use_module1.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.constant.type_use_module1.wat @@ -10,7 +10,7 @@ (global $".Foo called " (import "" "Foo called ") (ref extern)) (table $module0.cross-module-funcs-0 (import "module0" "cross-module-funcs-0") 4 funcref) (global $"\"Foo called \"" (ref $JSExternWrapper) - (i32.const 102) + (i32.const 58) (i32.const 0) (global.get $".Foo called ") (struct.new $JSExternWrapper)) diff --git a/pkg/dart2wasm/test/ir_tests/deferred.constant_module1.wat b/pkg/dart2wasm/test/ir_tests/deferred.constant_module1.wat index fa63dae5b82..4f931842ac4 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.constant_module1.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.constant_module1.wat @@ -38,7 +38,7 @@ (ref.null none)) (global $"\">(\"" (ref $JSExternWrapper) <...>) (global $"\"globalH1Bar<\"" (ref $JSExternWrapper) - (i32.const 102) + (i32.const 58) (i32.const 0) (global.get $.globalH1Bar<) (struct.new $JSExternWrapper)) @@ -144,7 +144,7 @@ struct.get $H1 $fun local.tee $var0 struct.get $#Closure-0-1 $context - i32.const 64 + i32.const 86 i64.const 1 struct.new $BoxedInt local.get $var0 diff --git a/pkg/dart2wasm/test/ir_tests/deferred.constant_module2.wat b/pkg/dart2wasm/test/ir_tests/deferred.constant_module2.wat index 5c992dffc41..f8fc0efc04e 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.constant_module2.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.constant_module2.wat @@ -21,7 +21,7 @@ (table $module0.constant-table0 (import "module0" "constant-table0") 1 (ref null $_FunctionType)) (table $module0.cross-module-funcs-0 (import "module0" "cross-module-funcs-0") 11 funcref) (global $"\"globalH0Foo\"" (ref $JSExternWrapper) - (i32.const 102) + (i32.const 58) (i32.const 0) (global.get $.globalH0Foo) (struct.new $JSExternWrapper)) diff --git a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained.devirtualized_module1.wat b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained.devirtualized_module1.wat index b24f3d4deb5..51a13f85e41 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained.devirtualized_module1.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained.devirtualized_module1.wat @@ -11,24 +11,24 @@ (global $1 (import "module0" "global2") (ref $BoxedInt)) (global $2 (import "module0" "global3") (ref $BoxedInt)) (table $module0.cross-module-funcs-0 (import "module0" "cross-module-funcs-0") 18 funcref) - (table $module0.dispatch0 (import "module0" "dispatch0") 649 funcref) + (table $module0.dispatch0 (import "module0" "dispatch0") 654 funcref) (global $"\"Foo0.doitDispatch(\"" (ref $JSExternWrapper) - (i32.const 103) + (i32.const 60) (i32.const 0) (global.get $".Foo0.doitDispatch(") (struct.new $JSExternWrapper)) (global $"\"Foo1.doitDevirt(\"" (ref $JSExternWrapper) - (i32.const 103) + (i32.const 60) (i32.const 0) (global.get $".Foo1.doitDevirt(") (struct.new $JSExternWrapper)) (global $"\"Foo1.doitDispatch(\"" (ref $JSExternWrapper) - (i32.const 103) + (i32.const 60) (i32.const 0) (global.get $".Foo1.doitDispatch(") (struct.new $JSExternWrapper)) (global $"\"FooBase(\"" (ref $JSExternWrapper) - (i32.const 103) + (i32.const 60) (i32.const 0) (global.get $".FooBase(") (struct.new $JSExternWrapper)) @@ -71,7 +71,7 @@ global.get $1 local.get $var0 struct.get $Object $field0 - i32.const 457 + i32.const 365 i32.add call_indirect $module0.dispatch0 (param (ref $Object) (ref null $#Top)) block $label2 (result (ref $Object)) diff --git a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module3.wat b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module3.wat index bbcdb7f9165..bdc638c970d 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module3.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module3.wat @@ -22,44 +22,44 @@ (global $FooConst0 (import "module0" "global7") (ref $Object)) (global $fooGlobal0 (import "module0" "global16") (ref null $#Top)) (table $module0.cross-module-funcs-0 (import "module0" "cross-module-funcs-0") 45 funcref) - (table $module0.dispatch0 (import "module0" "dispatch0") 666 funcref) + (table $module0.dispatch0 (import "module0" "dispatch0") 670 funcref) (global $"\"0\"" (ref $JSExternWrapper) <...>) (global $"\"1\"" (ref $JSExternWrapper) <...>) (global $"\"2\"" (ref $JSExternWrapper) <...>) (global $"\"3\"" (ref $JSExternWrapper) <...>) (global $"\"4\"" (ref $JSExternWrapper) <...>) (global $"\"FooConst0(\"" (ref $JSExternWrapper) - (i32.const 103) + (i32.const 60) (i32.const 0) (global.get $".FooConst0(") (struct.new $JSExternWrapper)) (global $"\"FooConst1(\"" (ref $JSExternWrapper) - (i32.const 103) + (i32.const 60) (i32.const 0) (global.get $".FooConst1(") (struct.new $JSExternWrapper)) (global $"\"FooConst2(\"" (ref $JSExternWrapper) - (i32.const 103) + (i32.const 60) (i32.const 0) (global.get $".FooConst2(") (struct.new $JSExternWrapper)) (global $"\"FooConst3(\"" (ref $JSExternWrapper) - (i32.const 103) + (i32.const 60) (i32.const 0) (global.get $".FooConst3(") (struct.new $JSExternWrapper)) (global $"\"FooConst4(\"" (ref $JSExternWrapper) - (i32.const 103) + (i32.const 60) (i32.const 0) (global.get $".FooConst4(") (struct.new $JSExternWrapper)) (global $"\"FooConst5(\"" (ref $JSExternWrapper) - (i32.const 103) + (i32.const 60) (i32.const 0) (global.get $".FooConst5(") (struct.new $JSExternWrapper)) (global $"\"FooConstBase(\"" (ref $JSExternWrapper) - (i32.const 103) + (i32.const 60) (i32.const 0) (global.get $".FooConstBase(") (struct.new $JSExternWrapper)) diff --git a/pkg/dart2wasm/test/ir_tests/deferred.type_checks_module1.wat b/pkg/dart2wasm/test/ir_tests/deferred.type_checks_module1.wat index 30d270a8eb2..5f59e2c3947 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.type_checks_module1.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.type_checks_module1.wat @@ -9,7 +9,7 @@ (type $_Type <...>) (global $"\")\"_11" (import "$" "2") (ref $JSExternWrapper)) (global $_InterfaceType (import "$" "0") (ref $_InterfaceType)) - (table $$.% (import "$" "%") 742 funcref) + (table $$.% (import "$" "%") 765 funcref) (table $$.' (import "$" "'") 20 funcref) (global $"\">.takeT(\"" (ref $JSExternWrapper) <...>) (global $"\"Foo<\"" (ref $JSExternWrapper) <...>) diff --git a/pkg/dart2wasm/test/ir_tests/dispatch_table_reuse_module1.wat b/pkg/dart2wasm/test/ir_tests/dispatch_table_reuse_module1.wat index 1366de94893..bfa645d89b8 100644 --- a/pkg/dart2wasm/test/ir_tests/dispatch_table_reuse_module1.wat +++ b/pkg/dart2wasm/test/ir_tests/dispatch_table_reuse_module1.wat @@ -7,7 +7,7 @@ (type $WasmListBase <...>) (type $_Type <...>) (table $module0.cross-module-funcs-0 (import "module0" "cross-module-funcs-0") 12 funcref) - (table $module0.dispatch0 (import "module0" "dispatch0") 666 funcref) + (table $module0.dispatch0 (import "module0" "dispatch0") 670 funcref) (elem $module0.cross-module-funcs-0 (set 0 (ref.func $"runTest "))) (func $"runTest " diff --git a/pkg/dart2wasm/test/ir_tests/dynamic_call.wat b/pkg/dart2wasm/test/ir_tests/dynamic_call.wat index 705d67b1b5b..a93faa33d11 100644 --- a/pkg/dart2wasm/test/ir_tests/dynamic_call.wat +++ b/pkg/dart2wasm/test/ir_tests/dynamic_call.wat @@ -46,7 +46,7 @@ (table $dtable0 3 i31ref) (table $dtable2 3 funcref) (global $1 (ref $BoxedInt) - (i32.const 63) + (i32.const 85) (i64.const 1) (struct.new $BoxedInt)) (global $true (ref $BoxedBool) diff --git a/pkg/dart2wasm/test/ir_tests/external_effect.wat b/pkg/dart2wasm/test/ir_tests/external_effect.wat index 603b4c8f424..0b03bdb5c95 100644 --- a/pkg/dart2wasm/test/ir_tests/external_effect.wat +++ b/pkg/dart2wasm/test/ir_tests/external_effect.wat @@ -5,13 +5,13 @@ (field $field0 i32) (field $value i64)))) (global $1 (ref $BoxedInt) - (i32.const 59) + (i32.const 84) (i64.const 1) (struct.new $BoxedInt)) (func $"main " global.get $1 call $print - i32.const 59 + i32.const 84 i64.const 2 struct.new $BoxedInt call $print diff --git a/pkg/dart2wasm/test/ir_tests/hello.wat b/pkg/dart2wasm/test/ir_tests/hello.wat index 9499a69277b..82986f31b1b 100644 --- a/pkg/dart2wasm/test/ir_tests/hello.wat +++ b/pkg/dart2wasm/test/ir_tests/hello.wat @@ -6,7 +6,7 @@ (field $_externRef externref)))) (global $".hello world" (import "" "hello world") (ref extern)) (global $"\"hello world\"" (ref $JSExternWrapper) - (i32.const 95) + (i32.const 56) (global.get $".hello world") (struct.new $JSExternWrapper)) (func $"main " diff --git a/pkg/dart2wasm/test/ir_tests/import_name_module1.wat b/pkg/dart2wasm/test/ir_tests/import_name_module1.wat index ddcf44a79f7..051bb9de685 100644 --- a/pkg/dart2wasm/test/ir_tests/import_name_module1.wat +++ b/pkg/dart2wasm/test/ir_tests/import_name_module1.wat @@ -11,7 +11,7 @@ (global $".hello world" (import "" "hello world") (ref extern)) (table $module0.cross-module-funcs-0 (import "module0" "cross-module-funcs-0") 2 funcref) (global $"\"hello world\"" (ref $JSExternWrapper) - (i32.const 96) + (i32.const 57) (i32.const 0) (global.get $".hello world") (struct.new $JSExternWrapper)) diff --git a/pkg/dart2wasm/test/ir_tests/interop.double.wat b/pkg/dart2wasm/test/ir_tests/interop.double.wat index b5a8e031da9..62fb2588e1d 100644 --- a/pkg/dart2wasm/test/ir_tests/interop.double.wat +++ b/pkg/dart2wasm/test/ir_tests/interop.double.wat @@ -39,7 +39,7 @@ else call $"ktrue implicit getter" if (result (ref null $BoxedDouble)) - i32.const 72 + i32.const 92 call $"doubleValue implicit getter" struct.new $BoxedDouble else diff --git a/pkg/dart2wasm/test/ir_tests/interop.int.wat b/pkg/dart2wasm/test/ir_tests/interop.int.wat index deea6d018d2..e8427398bc0 100644 --- a/pkg/dart2wasm/test/ir_tests/interop.int.wat +++ b/pkg/dart2wasm/test/ir_tests/interop.int.wat @@ -40,7 +40,7 @@ else call $"ktrue implicit getter" if (result (ref null $BoxedInt)) - i32.const 62 + i32.const 86 call $"intValue implicit getter" struct.new $BoxedInt else diff --git a/pkg/dart2wasm/test/ir_tests/memory_use.wat b/pkg/dart2wasm/test/ir_tests/memory_use.wat index 9f664bffd54..e06e6125a37 100644 --- a/pkg/dart2wasm/test/ir_tests/memory_use.wat +++ b/pkg/dart2wasm/test/ir_tests/memory_use.wat @@ -9,30 +9,30 @@ i32.const 1 memory.grow $foo.mem drop - i32.const 67 + i32.const 89 i32.const 0 f32.load align=4 f64.promote_f32 struct.new $BoxedDouble call $print - i32.const 67 + i32.const 89 i32.const 0 f32.load align=4 f64.promote_f32 struct.new $BoxedDouble call $print - i32.const 67 + i32.const 89 i32.const 0 f64.load align=8 struct.new $BoxedDouble call $print - i32.const 67 + i32.const 89 i32.const 1 f32.load align=4 f64.promote_f32 struct.new $BoxedDouble call $print - i32.const 67 + i32.const 89 i32.const 1 f32.load align=4 f64.promote_f32 diff --git a/pkg/dart2wasm/test/ir_tests/pure_function.wat b/pkg/dart2wasm/test/ir_tests/pure_function.wat index 3d174fe7d46..95256532dd5 100644 --- a/pkg/dart2wasm/test/ir_tests/pure_function.wat +++ b/pkg/dart2wasm/test/ir_tests/pure_function.wat @@ -21,7 +21,7 @@ global.get $"\")\"" call $JSStringImpl._interpolate3 drop - i32.const 59 + i32.const 84 local.get $var0 struct.get $JSExternWrapper $_externRef call $"wasm:js-string.length (import)" diff --git a/pkg/dart2wasm/test/ir_tests/standalone.wat b/pkg/dart2wasm/test/ir_tests/standalone.wat index 534736e5530..0336e0fed2a 100644 --- a/pkg/dart2wasm/test/ir_tests/standalone.wat +++ b/pkg/dart2wasm/test/ir_tests/standalone.wat @@ -1,9 +1,9 @@ (module $module0 (type $#Top (struct (field $field0 i32))) - (type $JSExternWrapper (sub $#Top (struct + (type $JSStringImpl (sub final $#Top (struct (field $field0 i32) - (field $_externRef externref)))) - (global $"\"Hello world\"" (mut (ref null $JSExternWrapper)) + (field $_ref (ref extern))))) + (global $"\"Hello world\"" (mut (ref null $JSStringImpl)) (ref.null none)) ) \ No newline at end of file diff --git a/pkg/dart2wasm/test/ir_tests/try_blocks.wat b/pkg/dart2wasm/test/ir_tests/try_blocks.wat index 4dc433fe19e..05c4879af18 100644 --- a/pkg/dart2wasm/test/ir_tests/try_blocks.wat +++ b/pkg/dart2wasm/test/ir_tests/try_blocks.wat @@ -5,9 +5,9 @@ (type $JSExternWrapper <...>) (type $JavaScriptStack <...>) (tag $tag0 (param (ref $#Top) (ref $#Top))) - (global $"WasmArray[748]" (ref $Array) <...>) + (global $"WasmArray[688]" (ref $Array) <...>) (global $"WasmArray[225]" (ref $Array) <...>) - (global $"WasmArray[748]" (ref $Array) <...>) + (global $"WasmArray[688]" (ref $Array) <...>) (global $"\"Caught Error\"" (ref $JSExternWrapper) <...>) (global $"\"Caught JSAny\"" (ref $JSExternWrapper) <...>) (global $"\"Caught Object\"" (ref $JSExternWrapper) <...>) @@ -32,7 +32,7 @@ local.get $var1 struct.get $#Top $field0 local.tee $var0 - i32.const 76 + i32.const 59 i32.eq if (result i32) i32.const 0 @@ -40,22 +40,22 @@ block $label3 (result i32) i32.const -1 global.get $"WasmArray[225]" - i32.const 76 + i32.const 59 array.get $Array local.get $var0 i32.add local.tee $var0 - i32.const 748 + i32.const 688 i32.ge_u br_if $label3 drop - global.get $"WasmArray[748]" + global.get $"WasmArray[688]" local.get $var0 array.get $Array - i32.const 76 + i32.const 59 i32.eq if - global.get $"WasmArray[748]" + global.get $"WasmArray[688]" local.get $var0 array.get_u $Array br $label3 @@ -128,30 +128,52 @@ local.set $var2 local.set $var1 block $label3 (result i32) - i32.const 1 - local.get $var1 - struct.get $#Top $field0 - local.tee $var0 - i32.const 40 - i32.eq - br_if $label3 - drop - i32.const 1 - local.get $var0 - i32.const 43 - i32.sub - i32.const 12 - i32.lt_u - br_if $label3 - drop - i32.const 1 - local.get $var0 - i32.const 99 - i32.sub - i32.const 2 - i32.lt_u - br_if $label3 - drop + block $label4 + local.get $var1 + struct.get $#Top $field0 + local.tee $var0 + i32.const 54 + i32.le_u + if + local.get $var0 + i32.const 40 + i32.le_u + if + i32.const 1 + local.get $var0 + i32.const 40 + i32.eq + br_if $label3 + drop + br $label4 + end + i32.const 1 + local.get $var0 + i32.const 43 + i32.ge_u + br_if $label3 + drop + br $label4 + end + local.get $var0 + i32.const 101 + i32.le_u + if + i32.const 1 + local.get $var0 + i32.const 101 + i32.eq + br_if $label3 + drop + br $label4 + end + i32.const 1 + local.get $var0 + i32.const 105 + i32.eq + br_if $label3 + drop + end $label4 i32.const 0 end $label3 br_if $label1 diff --git a/pkg/test_runner/lib/src/browser.dart b/pkg/test_runner/lib/src/browser.dart index 7f38a98b052..46e22d46f59 100644 --- a/pkg/test_runner/lib/src/browser.dart +++ b/pkg/test_runner/lib/src/browser.dart @@ -343,6 +343,9 @@ and `base64 -w 0`: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (param (ref null $i16array) i32 i32) (result (ref extern))) ) + (import "wasm:js-string" "intoCharCodeArray" + (func $intoCharCodeArray (param (ref null extern) (ref null $i16array) i32) (result i32)) + ) (func (export "stringFromAsciiBytes") (param $arr (ref $i8array)) @@ -357,21 +360,24 @@ and `base64 -w 0`: (local.set $i (local.get $length)) (local.set $expanded (array.new $i16array (i32.const 0) (local.get $length))) - ;; do { i--; expanded[i] = arr[i]; } while (i >= start); + ;; do { i--; expanded[i] = arr[i + start]; } while (i >= 0); (block $break (loop $loop (local.set $i (i32.add (local.get $i) (i32.const -1))) (br_if $break (i32.lt_s (local.get $i) - (local.get $start) + (i32.const 0) ) ) (array.set $i16array (local.get $expanded) (local.get $i) - (array.get_u $i8array (local.get $arr) (local.get $i)) + (array.get_u $i8array + (local.get $arr) + (i32.add (local.get $i) (local.get $start)) + ) ) br $loop ) @@ -400,6 +406,18 @@ and `base64 -w 0`: ) ) + (func (export "stringToCharCodeArray") + (param $str externref) + (param $array (ref $i16array)) + (param $start i32) + + (drop (call $intoCharCodeArray + (local.get $str) + (local.get $array) + (local.get $start) + )) + ) + (func (export "emptyExternRefArray") (result (ref $externarray)) (array.new_default $externarray (i32.const 0)) @@ -407,15 +425,13 @@ and `base64 -w 0`: ) */ const _wasmStandaloneArrayHelper = - 'AGFzbQEAAAABKgdedwFeeAFebwFgA2MAf38BZG9gA2QBf38BZG9gA2QAf38BZG9gAAFkAgIkAQ53YXNtOmpzLXN0cmluZxFmcm9tQ2hhckNvZGVBcnJheQADAwQDBAUGB0gDFHN0cmluZ0Zyb21Bc2NpaUJ5dGVzAAEXc3RyaW5nRnJvbUNoYXJDb2RlQXJyYXkAAhNlbXB0eUV4dGVyblJlZkFycmF5AAMKWwNDAgF/AWQAIAIhA0EAIAL7BgAhBAJAA0AgA0F/aiEDIAMgAUgNASAEIAMgACAD+w0B+w4ADAALAAsgBEEAIAT7DxAACw0AIAAgASABIAJqEAALBwBBAPsHAgs='; + 'AGFzbQEAAAABOQledwFeeAFebwFgA2MAf38BZG9gA29jAH8Bf2ADZAF/fwFkb2ADZAB/fwFkb2ADb2QAfwBgAAFkAgJHAg53YXNtOmpzLXN0cmluZxFmcm9tQ2hhckNvZGVBcnJheQADDndhc206anMtc3RyaW5nEWludG9DaGFyQ29kZUFycmF5AAQDBQQFBgcIB2AEFHN0cmluZ0Zyb21Bc2NpaUJ5dGVzAAIXc3RyaW5nRnJvbUNoYXJDb2RlQXJyYXkAAxVzdHJpbmdUb0NoYXJDb2RlQXJyYXkABBNlbXB0eUV4dGVyblJlZkFycmF5AAUKagRGAgF/AWQAIAIhA0EAIAL7BgAhBAJAA0AgA0F/aiEDIANBAEgNASAEIAMgACADIAFq+w0B+w4ADAALAAsgBEEAIAT7DxAACw0AIAAgASABIAJqEAALCwAgACABIAIQARoLBwBBAPsHAgs='; -String dart2wasmHtml( - String title, - String wasmPath, - String mjsPath, - String supportJsPath, - bool standalone, -) { +/* + +*/ + +String dart2WasmStandaloneHtml(String title, String wasmPath) { const standaloneEmbedder = """ const { instance: helperInstance } = await WebAssembly.instantiate(Uint8Array.fromBase64('$_wasmStandaloneArrayHelper'), {}, { @@ -447,6 +463,27 @@ String dart2wasmHtml( const str = helperInstance.exports.stringFromCharCodeArray(chars, start, length); return str; }, + stringLength: (s) => s.length, + stringEquals: (a, b) => a === b, + stringCompare: (a, b) => a === b ? 0 : (a < b ? -1 : 1), + stringCodeUnitAt: (str, idx) => str.charCodeAt(idx), + stringIndexOfString: (a, b, start) => a.indexOf(b, start), + stringLastIndexOfString: (a, b, start) => a.lastIndexOf(b, start), + stringReplaceAllString: (str, needle, replace) => str.replaceAll(needle, () => replace), + stringReplaceAllRegExp: (str, regex, replace) => str.replaceAll(regex.regular, () => replace), + stringSubstring: (str, start, end) => str.substring(start, end), + stringToLowerCase: (str) => str.toLowerCase(), + stringToUpperCase: (str) => str.toUpperCase(), + stringConcat: (a, b) => a + b, + stringRepeat: (str, amount) => str.repeat(amount), + stringReplaceRange: (str, start, end, replacement) => { + const before = str.substring(0, start); + const after = str.substring(end); + return before + replacement + after; + }, + stringToCodeUnits: (str, array, startIndex) => { + helperInstance.exports.stringToCharCodeArray(str, array, startIndex); + }, monotonicClockFrequency: () => 1_000_000, monotonicClockTicks: () => BigInt(Math.round(performance.now() * 1000)), weakRefCreate: (dartValue) => new WeakRef(dartValue), @@ -627,14 +664,61 @@ String dart2wasmHtml( reportTaskEvent: (taskId, flowId, type, name, jsonArgs) => {}, }; """; - final additionalImports = standalone ? '{ dart: dartEmbedder }' : '{}'; - final mainInvocation = standalone - ? r'appInstance.instantiatedModule.exports.$invokeMain(helperInstance.exports.emptyExternRefArray())' - : 'appInstance.invokeMain();'; return """ + + + + Test $title + + + + +

Running $title

+ + + +"""; +} + +String dart2wasmHtml( + String title, + String wasmPath, + String mjsPath, + String supportJsPath, +) { + return """ + + @@ -674,13 +758,12 @@ String dart2wasmHtml( const response = await fetch(`\${path}/\${relativeToWasmFileUri}`); return response.arrayBuffer(); }; -${standalone ? standaloneEmbedder : ''} - const appInstance = await compiledApp.instantiate($additionalImports, { + const appInstance = await compiledApp.instantiate({}, { loadDeferredModules: (modules, handleWasmBytes) => Promise.all(modules.map((m) => fetch(m).then((b) => handleWasmBytes(m, b)))), }); dartMainRunner(() => { - $mainInvocation + appInstance.invokeMain(); }); } diff --git a/pkg/test_runner/lib/src/test_suite.dart b/pkg/test_runner/lib/src/test_suite.dart index 494915d6b74..df8e0c1c824 100644 --- a/pkg/test_runner/lib/src/test_suite.dart +++ b/pkg/test_runner/lib/src/test_suite.dart @@ -1078,13 +1078,15 @@ class StandardTestSuite extends TestSuite { Path('$outputDir/$nameNoExt.support.js'), ); - content = dart2wasmHtml( - testFile.path.toNativePath(), - wasmPath, - mjsPath, - supportJsPath, - configuration.isDart2wasmStandalone, - ); + final title = testFile.path.toNativePath(); + content = configuration.isDart2wasmStandalone + ? dart2WasmStandaloneHtml(title, wasmPath) + : dart2wasmHtml( + testFile.path.toNativePath(), + wasmPath, + mjsPath, + supportJsPath, + ); } else if (configuration.compiler == Compiler.ddc) { var ddcConfig = configuration.compilerConfiguration as DevCompilerConfiguration; diff --git a/sdk/lib/_internal/wasm/lib/int_patch.dart b/sdk/lib/_internal/wasm/lib/int_patch.dart index 8738c8995de..2efc93ee0ee 100644 --- a/sdk/lib/_internal/wasm/lib/int_patch.dart +++ b/sdk/lib/_internal/wasm/lib/int_patch.dart @@ -2,8 +2,9 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -import "dart:_internal" show patch; +import "dart:_internal" show patch, unsafeCast; import "dart:_string" show StringUncheckedOperations; +import "dart:_string_helper" show skipLeadingWhitespace, skipTrailingWhitespace; import "dart:_wasm"; import "dart:_error_utils"; @@ -53,11 +54,11 @@ class int { int? radix, int? Function(String)? onError, ) { - int end = source.lastNonWhitespace() + 1; + int end = skipTrailingWhitespace(source, source.length); if (end == 0) { return _handleFormatError(onError, source, source.length, radix, null); } - int start = source.firstNonWhitespace(); + int start = skipLeadingWhitespace(source, 0); int first = source.codeUnitAtUnchecked(start); int sign = 1; diff --git a/sdk/lib/_internal/wasm/lib/js_string.dart b/sdk/lib/_internal/wasm/lib/js_string.dart index 401e9240b38..07461db1657 100644 --- a/sdk/lib/_internal/wasm/lib/js_string.dart +++ b/sdk/lib/_internal/wasm/lib/js_string.dart @@ -308,61 +308,7 @@ final class JSStringImpl extends js.JSExternWrapper String Function(Match)? onMatch, String Function(String)? onNonMatch, }) { - if (onMatch == null) onMatch = _matchString; - if (onNonMatch == null) onNonMatch = _stringIdentity; - if (from is String) { - final patternLength = from.length; - if (patternLength == 0) { - // Pattern is the empty string. - StringBuffer buffer = StringBuffer(); - int i = 0; - buffer.write(onNonMatch("")); - final length = this.length; - while (i < length) { - buffer.write(onMatch(StringMatch(i, this, ""))); - // Special case to avoid splitting a surrogate pair. - int code = codeUnitAt(i); - if ((code & ~0x3FF) == 0xD800 && length > i + 1) { - // Leading surrogate; - code = codeUnitAt(i + 1); - if ((code & ~0x3FF) == 0xDC00) { - // Matching trailing surrogate. - buffer.write(onNonMatch(substring(i, i + 2))); - i += 2; - continue; - } - } - buffer.write(onNonMatch(this[i])); - i++; - } - buffer.write(onMatch(StringMatch(i, this, ""))); - buffer.write(onNonMatch("")); - return buffer.toString(); - } - StringBuffer buffer = StringBuffer(); - int startIndex = 0; - final length = this.length; - while (startIndex < length) { - int position = indexOf(from, startIndex); - if (position == -1) { - break; - } - buffer.write(onNonMatch(substring(startIndex, position))); - buffer.write(onMatch(StringMatch(position, this, from))); - startIndex = position + patternLength; - } - buffer.write(onNonMatch(substring(startIndex))); - return buffer.toString(); - } - StringBuffer buffer = StringBuffer(); - int startIndex = 0; - for (Match match in from.allMatches(this)) { - buffer.write(onNonMatch(substring(startIndex, match.start))); - buffer.write(onMatch(match)); - startIndex = match.end; - } - buffer.write(onNonMatch(substring(startIndex))); - return buffer.toString(); + return splitMapJoinImpl(this, from, onMatch, onNonMatch); } String _replaceRange(int start, int end, String replacement) { @@ -435,32 +381,7 @@ final class JSStringImpl extends js.JSExternWrapper (re as js.JSValue).toExternRef, ); } else { - final result = []; - // End of most recent match. That is, start of next part to add to result. - int start = 0; - // Length of most recent match. - // Set >0, so no match on the empty string causes the result to be [""]. - int length = 1; - for (var match in pattern.allMatches(this)) { - int matchStart = match.start; - int matchEnd = match.end; - length = matchEnd - matchStart; - if (length == 0 && start == matchStart) { - // An empty match right after another match is ignored. - // This includes an empty match at the start of the string. - continue; - } - int end = matchStart; - result.add(substring(start, end)); - start = matchEnd; - } - if (start < this.length || length > 0) { - // An empty match at the end of the string does not cause a "" at the - // end. A non-empty match ending at the end of the string does add a - // "". - result.add(substring(start)); - } - return result; + return genericSplitImpl(this, pattern); } } @@ -514,98 +435,6 @@ final class JSStringImpl extends js.JSExternWrapper : JSStringImpl.fromRefUnchecked(upperCaseRef); } - // Characters with Whitespace property (Unicode 6.3). - // 0009..000D ; White_Space # Cc .. - // 0020 ; White_Space # Zs SPACE - // 0085 ; White_Space # Cc - // 00A0 ; White_Space # Zs NO-BREAK SPACE - // 1680 ; White_Space # Zs OGHAM SPACE MARK - // 2000..200A ; White_Space # Zs EN QUAD..HAIR SPACE - // 2028 ; White_Space # Zl LINE SEPARATOR - // 2029 ; White_Space # Zp PARAGRAPH SEPARATOR - // 202F ; White_Space # Zs NARROW NO-BREAK SPACE - // 205F ; White_Space # Zs MEDIUM MATHEMATICAL SPACE - // 3000 ; White_Space # Zs IDEOGRAPHIC SPACE - // - // BOM: 0xFEFF - static bool _isWhitespace(int codeUnit) { - // Most codeUnits should be less than 256. Special case with a smaller - // switch. - if (codeUnit < 256) { - switch (codeUnit) { - case 0x09: - case 0x0A: - case 0x0B: - case 0x0C: - case 0x0D: - case 0x20: - case 0x85: - case 0xA0: - return true; - default: - return false; - } - } - switch (codeUnit) { - case 0x1680: - case 0x2000: - case 0x2001: - case 0x2002: - case 0x2003: - case 0x2004: - case 0x2005: - case 0x2006: - case 0x2007: - case 0x2008: - case 0x2009: - case 0x200A: - case 0x2028: - case 0x2029: - case 0x202F: - case 0x205F: - case 0x3000: - case 0xFEFF: - return true; - default: - return false; - } - } - - static const int spaceCodeUnit = 0x20; - static const int carriageReturnCodeUnit = 0x0D; - static const int nelCodeUnit = 0x85; - - /// Finds the index of the first non-whitespace character, or the - /// end of the string. Start looking at position [index]. - static int _skipLeadingWhitespace(JSStringImpl string, int index) { - final stringLength = string.length; - while (index < stringLength) { - int codeUnit = string._codeUnitAtUnchecked(index); - if (codeUnit != spaceCodeUnit && - codeUnit != carriageReturnCodeUnit && - !_isWhitespace(codeUnit)) { - break; - } - index++; - } - return index; - } - - /// Finds the index after the last non-whitespace character, or 0. - /// Start looking at position [index - 1]. - static int _skipTrailingWhitespace(JSStringImpl string, int index) { - while (index > 0) { - int codeUnit = string._codeUnitAtUnchecked(index - 1); - if (codeUnit != spaceCodeUnit && - codeUnit != carriageReturnCodeUnit && - !_isWhitespace(codeUnit)) { - break; - } - index--; - } - return index; - } - // dart2wasm can't use JavaScript trim directly, because JavaScript does not // trim the NEXT LINE (NEL) character (0x85). @override @@ -625,7 +454,7 @@ final class JSStringImpl extends js.JSExternWrapper final int firstCode = result._codeUnitAtUnchecked(0); int startIndex = 0; if (firstCode == nelCodeUnit) { - startIndex = _skipLeadingWhitespace(result, 1); + startIndex = skipLeadingWhitespace(result, 1); if (startIndex == resultLength) return ""; } @@ -635,7 +464,7 @@ final class JSStringImpl extends js.JSExternWrapper // Therefore we don't need to verify that endIndex > startIndex. final int lastCode = result.codeUnitAt(endIndex - 1); if (lastCode == nelCodeUnit) { - endIndex = _skipTrailingWhitespace(result, endIndex - 1); + endIndex = skipTrailingWhitespace(result, endIndex - 1); } if (startIndex == 0 && endIndex == resultLength) { @@ -664,7 +493,7 @@ final class JSStringImpl extends js.JSExternWrapper // Check NEL. int firstCode = result._codeUnitAtUnchecked(0); if (firstCode == nelCodeUnit) { - startIndex = _skipLeadingWhitespace(result, 1); + startIndex = skipLeadingWhitespace(result, 1); } if (startIndex == 0) { @@ -692,7 +521,7 @@ final class JSStringImpl extends js.JSExternWrapper int endIndex = resultLength; int lastCode = result.codeUnitAt(endIndex - 1); if (lastCode == nelCodeUnit) { - endIndex = _skipTrailingWhitespace(result, endIndex - 1); + endIndex = skipTrailingWhitespace(result, endIndex - 1); } if (endIndex == resultLength) { @@ -850,33 +679,8 @@ final class JSStringImpl extends js.JSExternWrapper @override String toString() => this; - - int firstNonWhitespace() { - final length = this.length; - int first = 0; - for (; first < length; first++) { - if (!_isWhitespace(_codeUnitAtUnchecked(first))) { - break; - } - } - return first; - } - - int lastNonWhitespace() { - int last = length - 1; - for (; last >= 0; last--) { - if (!_isWhitespace(_codeUnitAtUnchecked(last))) { - break; - } - } - return last; - } } -String _matchString(Match match) => match[0]!; - -String _stringIdentity(String string) => string; - // NOTE: The [replacement] does not need special escaping it will be used // as-is (due to passing `() => r` to the JS `replaceAll()` call) JSStringImpl _jsStringReplaceAll( diff --git a/sdk/lib/_internal/wasm/lib/string_helper.dart b/sdk/lib/_internal/wasm/lib/string_helper.dart index d65a1b0b55b..59e4488ab8f 100644 --- a/sdk/lib/_internal/wasm/lib/string_helper.dart +++ b/sdk/lib/_internal/wasm/lib/string_helper.dart @@ -2,10 +2,11 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -library dart._string_match; +library dart._string_helper; import "dart:_error_utils"; -import "dart:_internal" show IterableElementError; +import "dart:_internal" show IterableElementError, unsafeCast; +import 'dart:_string' show StringUncheckedOperations; class StringMatch implements Match { const StringMatch(this.start, this.input, this.pattern); @@ -96,3 +97,193 @@ int stringFinalizeHash(int hash) { hash &= 0x3FFFFFFF; return hash == 0 ? 1 : hash; } + +String splitMapJoinImpl( + String source, + Pattern from, + String Function(Match)? onMatch, + String Function(String)? onNonMatch, +) { + if (onMatch == null) onMatch = _matchString; + if (onNonMatch == null) onNonMatch = _stringIdentity; + if (from is String) { + final patternLength = from.length; + if (patternLength == 0) { + // Pattern is the empty string. + StringBuffer buffer = StringBuffer(); + int i = 0; + buffer.write(onNonMatch("")); + final length = source.length; + while (i < length) { + buffer.write(onMatch(StringMatch(i, source, ""))); + // Special case to avoid splitting a surrogate pair. + int code = source.codeUnitAt(i); + if ((code & ~0x3FF) == 0xD800 && length > i + 1) { + // Leading surrogate; + code = source.codeUnitAt(i + 1); + if ((code & ~0x3FF) == 0xDC00) { + // Matching trailing surrogate. + buffer.write(onNonMatch(source.substring(i, i + 2))); + i += 2; + continue; + } + } + buffer.write(onNonMatch(source[i])); + i++; + } + buffer.write(onMatch(StringMatch(i, source, ""))); + buffer.write(onNonMatch("")); + return buffer.toString(); + } + StringBuffer buffer = StringBuffer(); + int startIndex = 0; + final length = source.length; + while (startIndex < length) { + int position = source.indexOf(from, startIndex); + if (position == -1) { + break; + } + buffer.write(onNonMatch(source.substring(startIndex, position))); + buffer.write(onMatch(StringMatch(position, source, from))); + startIndex = position + patternLength; + } + buffer.write(onNonMatch(source.substring(startIndex))); + return buffer.toString(); + } + StringBuffer buffer = StringBuffer(); + int startIndex = 0; + for (Match match in from.allMatches(source)) { + buffer.write(onNonMatch(source.substring(startIndex, match.start))); + buffer.write(onMatch(match)); + startIndex = match.end; + } + buffer.write(onNonMatch(source.substring(startIndex))); + return buffer.toString(); +} + +/// Implementation of [String.split] for patterns where no specialized JS +/// implementation exists. +List genericSplitImpl(String source, Pattern pattern) { + final result = []; + // End of most recent match. That is, start of next part to add to result. + int start = 0; + // Length of most recent match. + // Set >0, so no match on the empty string causes the result to be [""]. + int length = 1; + for (var match in pattern.allMatches(source)) { + int matchStart = match.start; + int matchEnd = match.end; + length = matchEnd - matchStart; + if (length == 0 && start == matchStart) { + // An empty match right after another match is ignored. + // This includes an empty match at the start of the string. + continue; + } + int end = matchStart; + result.add(source.substring(start, end)); + start = matchEnd; + } + if (start < source.length || length > 0) { + // An empty match at the end of the string does not cause a "" at the + // end. A non-empty match ending at the end of the string does add a + // "". + result.add(source.substring(start)); + } + return result; +} + +// Characters with Whitespace property (Unicode 6.3). +// 0009..000D ; White_Space # Cc .. +// 0020 ; White_Space # Zs SPACE +// 0085 ; White_Space # Cc +// 00A0 ; White_Space # Zs NO-BREAK SPACE +// 1680 ; White_Space # Zs OGHAM SPACE MARK +// 2000..200A ; White_Space # Zs EN QUAD..HAIR SPACE +// 2028 ; White_Space # Zl LINE SEPARATOR +// 2029 ; White_Space # Zp PARAGRAPH SEPARATOR +// 202F ; White_Space # Zs NARROW NO-BREAK SPACE +// 205F ; White_Space # Zs MEDIUM MATHEMATICAL SPACE +// 3000 ; White_Space # Zs IDEOGRAPHIC SPACE +// +// BOM: 0xFEFF +bool isWhitespace(int codeUnit) { + // Most codeUnits should be less than 256. Special case with a smaller + // switch. + if (codeUnit < 256) { + switch (codeUnit) { + case 0x09: + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0D: + case 0x20: + case 0x85: + case 0xA0: + return true; + default: + return false; + } + } + switch (codeUnit) { + case 0x1680: + case 0x2000: + case 0x2001: + case 0x2002: + case 0x2003: + case 0x2004: + case 0x2005: + case 0x2006: + case 0x2007: + case 0x2008: + case 0x2009: + case 0x200A: + case 0x2028: + case 0x2029: + case 0x202F: + case 0x205F: + case 0x3000: + case 0xFEFF: + return true; + default: + return false; + } +} + +const int spaceCodeUnit = 0x20; +const int carriageReturnCodeUnit = 0x0D; +const int nelCodeUnit = 0x85; + +/// Finds the index of the first non-whitespace character, or the +/// end of the string. Start looking at position [index]. +int skipLeadingWhitespace(String string, int index) { + final stringLength = string.length; + while (index < stringLength) { + int codeUnit = string.codeUnitAtUnchecked(index); + if (codeUnit != spaceCodeUnit && + codeUnit != carriageReturnCodeUnit && + !isWhitespace(codeUnit)) { + break; + } + index++; + } + return index; +} + +/// Finds the index after the last non-whitespace character, or 0. +/// Start looking at position [index - 1] to [from]. +int skipTrailingWhitespace(String string, int index, [int from = 0]) { + while (index > from) { + int codeUnit = string.codeUnitAtUnchecked(index - 1); + if (codeUnit != spaceCodeUnit && + codeUnit != carriageReturnCodeUnit && + !isWhitespace(codeUnit)) { + break; + } + index--; + } + return index; +} + +String _matchString(Match match) => match[0]!; + +String _stringIdentity(String string) => string; diff --git a/sdk/lib/_internal/wasm/lib/string_patch.dart b/sdk/lib/_internal/wasm/lib/string_patch.dart index 672821828c1..2b1c741678f 100644 --- a/sdk/lib/_internal/wasm/lib/string_patch.dart +++ b/sdk/lib/_internal/wasm/lib/string_patch.dart @@ -208,10 +208,3 @@ class String { return JSStringImpl.fromCodePoint(charCode); } } - -extension _StringExt on String { - int firstNonWhitespace() => - unsafeCast(this).firstNonWhitespace(); - - int lastNonWhitespace() => unsafeCast(this).lastNonWhitespace(); -} diff --git a/sdk/lib/_internal/wasm_js_compatibility/lib/string_patch.dart b/sdk/lib/_internal/wasm_js_compatibility/lib/string_patch.dart index 74f53c181bb..813d48fc6cf 100644 --- a/sdk/lib/_internal/wasm_js_compatibility/lib/string_patch.dart +++ b/sdk/lib/_internal/wasm_js_compatibility/lib/string_patch.dart @@ -120,10 +120,3 @@ class String { ); } } - -extension _StringExt on String { - int firstNonWhitespace() => - unsafeCast(this).firstNonWhitespace(); - - int lastNonWhitespace() => unsafeCast(this).lastNonWhitespace(); -} diff --git a/sdk/lib/_internal/wasm_standalone/lib/convert_standalone_patch.dart b/sdk/lib/_internal/wasm_standalone/lib/convert_standalone_patch.dart index 26aca3f3887..2853da35d45 100644 --- a/sdk/lib/_internal/wasm_standalone/lib/convert_standalone_patch.dart +++ b/sdk/lib/_internal/wasm_standalone/lib/convert_standalone_patch.dart @@ -4,8 +4,8 @@ import "dart:_embedder" as embedder; import "dart:_internal" show patch, unsafeCast; -import "dart:_js_helper" show jsStringFromDartString, JSExternWrapperExt; import "dart:_string"; +import "dart:_string_helper"; import "dart:_wasm"; @patch @@ -21,12 +21,13 @@ class _Utf8Decoder { class _StringParser { @patch static WasmArray stringToCharCodeArray(String string, int end) { - final externRef = unsafeCast(string).wrappedExternRef; final array = WasmArray(end); if (string.length == end) { - jsStringIntoCharCodeArray(externRef, array, 0.toWasmI32()); + final externRef = unsafeCast(string).wrappedExternRef; + embedder.stringToCodeUnits(externRef, array, 0.toWasmI32()); } else { - for (int i = 0; i < end; ++i) array.write(i, jsCharCodeAt(externRef, i)); + for (int i = 0; i < end; ++i) + array.write(i, string.codeUnitAtUnchecked(i)); } return array; diff --git a/sdk/lib/_internal/wasm_standalone/lib/developer_patch.dart b/sdk/lib/_internal/wasm_standalone/lib/developer_patch.dart index 649867913fe..693e4a9bcb8 100644 --- a/sdk/lib/_internal/wasm_standalone/lib/developer_patch.dart +++ b/sdk/lib/_internal/wasm_standalone/lib/developer_patch.dart @@ -4,7 +4,7 @@ import 'dart:_embedder' as embedder; import 'dart:_internal' show patch; -import 'dart:_js_helper' show jsStringFromDartString, JSExternWrapperExt; +import 'dart:_string' show embedderStringFromDartString; import 'dart:_wasm'; import 'dart:async' show Zone; import 'dart:isolate'; @@ -15,7 +15,7 @@ bool debugger({bool when = true, String? message}) { embedder.debugger( message == null ? WasmExternRef.nullRef - : jsStringFromDartString(message).wrappedExternRef, + : embedderStringFromDartString(message).wrappedExternRef, ); } @@ -193,7 +193,7 @@ void _reportTaskEvent( WasmI32.fromInt(taskId), WasmI32.fromInt(flowId), WasmI32.fromInt(type), - jsStringFromDartString(name).wrappedExternRef, - jsStringFromDartString(argumentsAsJson).wrappedExternRef, + embedderStringFromDartString(name).wrappedExternRef, + embedderStringFromDartString(argumentsAsJson).wrappedExternRef, ); } diff --git a/sdk/lib/_internal/wasm_standalone/lib/double_patch.dart b/sdk/lib/_internal/wasm_standalone/lib/double_patch.dart index e9d8274abc8..0b3fc7bf69b 100644 --- a/sdk/lib/_internal/wasm_standalone/lib/double_patch.dart +++ b/sdk/lib/_internal/wasm_standalone/lib/double_patch.dart @@ -4,7 +4,7 @@ import 'dart:_embedder'; import 'dart:_internal' show patch; -import 'dart:_js_helper'; +import 'dart:_string'; import 'dart:_wasm'; @patch @@ -21,7 +21,7 @@ class double { @patch static double? tryParse(String source) { final parseResult = doubleTryParse( - jsStringFromDartString(source).wrappedExternRef, + embedderStringFromDartString(source).wrappedExternRef, ); if (parseResult.isNull) { return null; diff --git a/sdk/lib/_internal/wasm_standalone/lib/embedder.dart b/sdk/lib/_internal/wasm_standalone/lib/embedder.dart index e9c2a9aa8bb..b914d8a759d 100644 --- a/sdk/lib/_internal/wasm_standalone/lib/embedder.dart +++ b/sdk/lib/_internal/wasm_standalone/lib/embedder.dart @@ -74,6 +74,75 @@ external WasmExternRef stringFromAsciiBytes( WasmI32 length, ); +@pragma("wasm:import", "dart.stringLength") +external WasmI32 stringLength(WasmExternRef? string); +@pragma("wasm:import", "dart.stringEquals") +external WasmI32 stringEquals(WasmExternRef? a, WasmExternRef? b); +@pragma("wasm:import", "dart.stringCompare") +external WasmI32 stringCompare(WasmExternRef? a, WasmExternRef? b); +@pragma("wasm:import", "dart.stringCodeUnitAt") +external WasmI32 stringCodeUnitAt(WasmExternRef? a, WasmI32 index); +@pragma("wasm:import", "dart.stringIndexOfString") +external WasmI32 stringIndexOfString( + WasmExternRef? a, + WasmExternRef? b, + WasmI32 start, +); +@pragma("wasm:import", "dart.stringLastIndexOfString") +external WasmI32 stringLastIndexOfString( + WasmExternRef? a, + WasmExternRef? b, + WasmI32 start, +); + +/// Specialization for [String.replaceAll] where the pattern is an embedder- +/// managed string. +@pragma("wasm:import", "dart.stringReplaceAllString") +external WasmExternRef? stringReplaceAllString( + WasmExternRef? string, + WasmExternRef? needle, + WasmExternRef? replacement, +); + +/// Specialization for [String.replaceAll] where the pattern is an embedder- +/// managed regular expression. +@pragma("wasm:import", "dart.stringReplaceAllRegExp") +external WasmExternRef? stringReplaceAllRegExp( + WasmExternRef? string, + WasmExternRef? needle, + WasmExternRef? replacement, +); +@pragma("wasm:import", "dart.stringSubstring") +external WasmExternRef? stringSubstring( + WasmExternRef? a, + WasmI32 start, + WasmI32 end, +); +@pragma("wasm:import", "dart.stringToLowerCase") +external WasmExternRef? stringToLowerCase(WasmExternRef? string); +@pragma("wasm:import", "dart.stringToUpperCase") +external WasmExternRef? stringToUpperCase(WasmExternRef? string); +@pragma("wasm:import", "dart.stringConcat") +external WasmExternRef? stringConcat(WasmExternRef? a, WasmExternRef? b); +@pragma("wasm:import", "dart.stringRepeat") +external WasmExternRef? stringRepeat(WasmExternRef? string, WasmI32 times); +@pragma("wasm:import", "dart.stringReplaceRange") +external WasmExternRef? stringReplaceRange( + WasmExternRef? string, + WasmI32 start, + WasmI32 end, + WasmExternRef? replacement, +); + +/// Writes code units of [string] into [outArray], starting at array position +/// [startIndex]. +@pragma("wasm:import", "dart.stringToCodeUnits") +external WasmVoid stringToCodeUnits( + WasmExternRef? string, + WasmArray outArray, + WasmI32 startIndex, +); + /// Get the frequency of ticks reported by [monotonicClockTicks] in Hz. /// /// Currently, the only supported values are 1kHz and 1MHz. Attempting to use diff --git a/sdk/lib/_internal/wasm_standalone/lib/embedder_string.dart b/sdk/lib/_internal/wasm_standalone/lib/embedder_string.dart new file mode 100644 index 00000000000..484157b61a6 --- /dev/null +++ b/sdk/lib/_internal/wasm_standalone/lib/embedder_string.dart @@ -0,0 +1,532 @@ +// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:_embedder' as embedder; +import 'dart:_error_utils'; +import 'dart:_internal'; +import 'dart:_object_helper'; +import 'dart:_string_helper'; +import 'dart:_wasm'; + +part 'regexp.dart'; + +abstract class StringUncheckedOperationsBase { + int _codeUnitAtUnchecked(int index); + String _substringUnchecked(int start, int end); +} + +extension StringUncheckedOperations on String { + @pragma('wasm:prefer-inline') + int codeUnitAtUnchecked(int index) => + unsafeCast( + this, + )._codeUnitAtUnchecked(index); + + @pragma('wasm:prefer-inline') + String substringUnchecked(int start, int end) => + unsafeCast( + this, + )._substringUnchecked(start, end); +} + +/// A string managed by the WebAssembly embedder. +/// +/// This is not necessarily a JavaScript string, but the `JSStringImpl` name is +/// referenced a lot in the compiler and since this class and it's counterpart +/// in the JS target have the same structure (wrapping an externref), adopting +/// the same name avoids conditional names in the compiler. +final class JSStringImpl implements String, StringUncheckedOperationsBase { + WasmExternRef? _ref; + + JSStringImpl.fromRefUnchecked(this._ref); + + WasmExternRef? get wrappedExternRef => _ref; + + @override + @pragma("wasm:prefer-inline") + int get length => embedder.stringLength(_ref).toIntUnsigned(); + + @override + @pragma("wasm:prefer-inline") + bool get isEmpty => length == 0; + + @override + @pragma("wasm:prefer-inline") + bool get isNotEmpty => !isEmpty; + + @pragma("wasm:entry-point") + static String _interpolate(WasmArray values) { + final valuesLength = values.length; + final result = StringBuffer(); + for (int i = 0; i < valuesLength; i++) { + result.write(values[i].toString()); + } + return result.toString(); + } + + @pragma("wasm:entry-point", "call") + static String _interpolate1(Object? value) { + return value is String ? value : value.toString(); + } + + @pragma("wasm:entry-point", "call") + static String _interpolate2(Object? value1, Object? value2) { + return (StringBuffer( + value1 is String ? value1 : value1.toString(), + )..write(value2 is String ? value2 : value2.toString())).toString(); + } + + @pragma("wasm:entry-point", "call") + static String _interpolate3(Object? value1, Object? value2, Object? value3) { + return (StringBuffer(value1 is String ? value1 : value1.toString()) + ..write(value2 is String ? value2 : value2.toString()) + ..write(value3 is String ? value3 : value3.toString())) + .toString(); + } + + @pragma("wasm:entry-point", "call") + static String _interpolate4( + Object? value1, + Object? value2, + Object? value3, + Object? value4, + ) { + return (StringBuffer(value1 is String ? value1 : value1.toString()) + ..write(value2 is String ? value2 : value2.toString()) + ..write(value3 is String ? value3 : value3.toString()) + ..write(value4 is String ? value4 : value4.toString())) + .toString(); + } + + static JSStringImpl fromAsciiBytes( + WasmArray source, + int start, + int end, + ) { + final length = WasmI32.fromInt(end - start); + return JSStringImpl.fromRefUnchecked( + embedder.stringFromAsciiBytes(source, WasmI32.fromInt(start), length), + ); + } + + static JSStringImpl fromCharCodeArray( + WasmArray source, + int start, + int end, + ) { + return JSStringImpl.fromRefUnchecked( + embedder.stringFromCharCodeArray( + source, + WasmI32.fromInt(start), + WasmI32.fromInt(end - start), + ), + ); + } + + @pragma("wasm:initialize-at-startup") + static final _stringFromCodePointBuffer = WasmArray(2); + + static JSStringImpl fromCharCode(int charCode) { + final array = _stringFromCodePointBuffer; + array.write(0, charCode); + return JSStringImpl.fromCharCodeArray(array, 0, 1); + } + + static JSStringImpl fromCodePoint(int codePoint) { + final array = _stringFromCodePointBuffer; + if (codePoint <= 0xffff) { + array.write(0, codePoint); + return JSStringImpl.fromCharCodeArray(array, 0, 1); + } + final low = 0xDC00 | (codePoint & 0x3ff); + final high = 0xD7C0 + (codePoint >> 10); + array.write(0, high); + array.write(1, low); + return JSStringImpl.fromCharCodeArray(array, 0, 2); + } + + @override + @pragma("wasm:prefer-inline") + int codeUnitAt(int index) { + final length = this.length; + IndexErrorUtils.checkIndex(index, length); + return _codeUnitAtUnchecked(index); + } + + @override + @pragma("wasm:prefer-inline") + int _codeUnitAtUnchecked(int index) { + return embedder + .stringCodeUnitAt(wrappedExternRef, WasmI32.fromInt(index)) + .toIntUnsigned(); + } + + @override + Iterable allMatches(String string, [int start = 0]) { + final stringLength = string.length; + RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(start, stringLength); + return StringAllMatchesIterable(string, this, start); + } + + @override + Match? matchAsPrefix(String string, [int start = 0]) { + final stringLength = string.length; + RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(start, stringLength); + final length = this.length; + if (start + length > stringLength) return null; + // TODO(lrn): See if this can be optimized. + for (int i = 0; i < length; i++) { + if (string.codeUnitAt(start + i) != codeUnitAt(i)) { + return null; + } + } + return StringMatch(start, string, this); + } + + @override + @pragma('wasm:pure-function') + String operator +(String other) { + return JSStringImpl.fromRefUnchecked( + embedder.stringConcat( + wrappedExternRef, + unsafeCast(other).wrappedExternRef, + ), + ); + } + + @override + bool endsWith(String other) { + final otherLength = other.length; + final length = this.length; + if (otherLength > length) return false; + return other == _substringUnchecked(length - otherLength, length); + } + + @override + String replaceAll(Pattern from, String to) { + if (from is String) { + if (from.isEmpty) { + if (isEmpty) return to; + StringBuffer result = StringBuffer(); + result.write(to); + final length = this.length; + for (int i = 0; i < length; i++) { + result.write(this[i]); + result.write(to); + } + return result.toString(); + } + return JSStringImpl.fromRefUnchecked( + embedder.stringReplaceAllString( + wrappedExternRef, + unsafeCast(from).wrappedExternRef, + unsafeCast(to).wrappedExternRef, + ), + ); + } else if (from is EmbedderRegExp) { + return JSStringImpl.fromRefUnchecked( + embedder.stringReplaceAllRegExp( + wrappedExternRef, + from._regexp, + unsafeCast(to).wrappedExternRef, + ), + ); + } else { + int startIndex = 0; + StringBuffer result = StringBuffer(); + for (Match match in from.allMatches(this)) { + result.write(substring(startIndex, match.start)); + result.write(to); + startIndex = match.end; + } + result.write(substring(startIndex)); + return result.toString(); + } + } + + @override + String replaceAllMapped(Pattern from, String Function(Match) convert) { + return splitMapJoin(from, onMatch: convert); + } + + @override + String splitMapJoin( + Pattern from, { + String Function(Match)? onMatch, + String Function(String)? onNonMatch, + }) { + return splitMapJoinImpl(this, from, onMatch, onNonMatch); + } + + String _replaceRange(int start, int end, String replacement) { + return JSStringImpl.fromRefUnchecked( + embedder.stringReplaceRange( + wrappedExternRef, + WasmI32.fromInt(start), + WasmI32.fromInt(end), + unsafeCast(replacement).wrappedExternRef, + ), + ); + } + + @override + String replaceFirst(Pattern from, String to, [int startIndex = 0]) { + Iterator matches = from.allMatches(this, startIndex).iterator; + if (!matches.moveNext()) return this; + Match match = matches.current; + return replaceRange(match.start, match.end, to); + } + + @override + String replaceFirstMapped( + Pattern from, + String replace(Match match), [ + int startIndex = 0, + ]) { + RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(startIndex, length); + final Iterator matches = from.allMatches(this, startIndex).iterator; + if (!matches.moveNext()) return this; + final Match match = matches.current; + return replaceRange(match.start, match.end, replace(match)); + } + + @override + List split(Pattern pattern) { + return genericSplitImpl(this, pattern); + } + + @override + String replaceRange(int start, int? end, String replacement) { + end ??= length; + RangeErrorUtils.checkValidRange(start, end, length); + return _replaceRange(start, end, replacement); + } + + @override + bool startsWith(Pattern pattern, [int index = 0]) { + RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(index, length); + if (pattern is String) { + final patternLength = pattern.length; + final endIndex = index + patternLength; + if (endIndex > length) return false; + return pattern == substring(index, endIndex); + } + return pattern.matchAsPrefix(this, index) != null; + } + + @override + String substring(int start, [int? end]) { + end ??= length; + RangeErrorUtils.checkValidRange(start, end, length); + if (start == end) return ""; + return _substringUnchecked(start, end); + } + + @override + @pragma('wasm:prefer-inline') + String _substringUnchecked(int start, int end) => + JSStringImpl.fromRefUnchecked( + embedder.stringSubstring( + wrappedExternRef, + WasmI32.fromInt(start), + WasmI32.fromInt(end), + ), + ); + + @override + String toLowerCase() { + final toLower = embedder.stringToLowerCase(wrappedExternRef); + if (embedder.stringEquals(toLower, wrappedExternRef).toBool()) { + return this; + } else { + return JSStringImpl.fromRefUnchecked(toLower); + } + } + + @override + String toUpperCase() { + final toUpper = embedder.stringToUpperCase(wrappedExternRef); + if (embedder.stringEquals(toUpper, wrappedExternRef).toBool()) { + return this; + } else { + return JSStringImpl.fromRefUnchecked(toUpper); + } + } + + String _trim(bool left, bool right) { + if (isEmpty) return this; + + var start = 0, end = length; + if (left) start = skipLeadingWhitespace(this, 0); + if (right) end = skipTrailingWhitespace(this, length, start); + if (start >= end) return ''; + if (start == 0 && end == length) return this; + + return _substringUnchecked(start, end); + } + + @override + String trim() { + return _trim(true, true); + } + + @override + String trimLeft() { + return _trim(true, false); + } + + @override + String trimRight() { + return _trim(false, true); + } + + @override + String operator *(int times) { + if (0 >= times) return ''; + if (times == 1 || length == 0) return this; + if (times & 0x7fffffff != times) { + throw Exception( + 'The implementation cannot handle very large operands (was: $times).', + ); + } + + return JSStringImpl.fromRefUnchecked( + embedder.stringRepeat(wrappedExternRef, WasmI32.fromInt(times)), + ); + } + + @override + String padLeft(int width, [String padding = ' ']) { + int delta = width - length; + if (delta <= 0) return this; + return (padding * delta) + this; + } + + @override + String padRight(int width, [String padding = ' ']) { + int delta = width - length; + if (delta <= 0) return this; + return this + (padding * delta); + } + + @override + List get codeUnits => CodeUnits(this); + + @override + Runes get runes => Runes(this); + + @override + int indexOf(Pattern pattern, [int start = 0]) { + final length = this.length; + RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(start, length); + if (pattern is JSStringImpl) { + return embedder + .stringIndexOfString( + wrappedExternRef, + pattern.wrappedExternRef, + WasmI32.fromInt(start), + ) + .toIntSigned(); + } else if (pattern is EmbedderRegExp) { + final match = pattern._search(this, start, false); + return match?.start ?? -1; + } else { + for (int i = start; i <= length; i++) { + if (pattern.matchAsPrefix(this, i) != null) return i; + } + return -1; + } + } + + @override + int lastIndexOf(Pattern pattern, [int? start]) { + final length = this.length; + if (start == null) { + start = length; + } else { + RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(start, length); + } + if (pattern is JSStringImpl) { + if (start + pattern.length > length) { + start = length - pattern.length; + } + return embedder + .stringLastIndexOfString( + wrappedExternRef, + pattern.wrappedExternRef, + WasmI32.fromInt(start), + ) + .toIntSigned(); + } + + for (int i = start; i >= 0; i--) { + if (pattern.matchAsPrefix(this, i) != null) return i; + } + return -1; + } + + @override + bool contains(Pattern other, [int startIndex = 0]) { + final length = this.length; + RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(startIndex, length); + if (other is String || other is EmbedderRegExp) { + return indexOf(other, startIndex) >= 0; + } else { + return other.allMatches(substring(startIndex)).isNotEmpty; + } + } + + @override + int get hashCode { + int hash = getIdentityHashField(this); + if (hash != 0) return hash; + hash = _computeHashCode(); + setIdentityHashField(this, hash); + return hash; + } + + /// This must be kept in sync with `StringBase.hashCode` in string_patch.dart. + int _computeHashCode() { + int hash = 0; + final length = this.length; + for (int i = 0; i < length; i++) { + hash = stringCombineHashes(hash, _codeUnitAtUnchecked(i)); + } + return stringFinalizeHash(hash); + } + + @override + @pragma("wasm:prefer-inline") + String operator [](int index) { + IndexErrorUtils.checkIndex(index, length); + return JSStringImpl.fromCharCode(_codeUnitAtUnchecked(index)); + } + + @override + @pragma('wasm:prefer-inline') + bool operator ==(Object other) => + other is JSStringImpl && embedder.stringEquals(_ref, other._ref).toBool(); + + @override + @pragma('wasm:prefer-inline') + int compareTo(String other) => embedder + .stringCompare( + wrappedExternRef, + unsafeCast(other).wrappedExternRef, + ) + .toIntSigned(); + + @override + String toString() => this; +} + +String _matchString(Match match) => match[0]!; + +String _stringIdentity(String string) => string; + +@patch +@pragma('wasm:prefer-inline') +JSStringImpl embedderStringFromDartString(String s) { + return unsafeCast(s); +} diff --git a/sdk/lib/_internal/wasm_standalone/lib/internal_json_encode_patch.dart b/sdk/lib/_internal/wasm_standalone/lib/internal_json_encode_patch.dart index 9c9d308d409..4ff8d471728 100644 --- a/sdk/lib/_internal/wasm_standalone/lib/internal_json_encode_patch.dart +++ b/sdk/lib/_internal/wasm_standalone/lib/internal_json_encode_patch.dart @@ -3,10 +3,11 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:_embedder' as embedder; -import "dart:_js_helper" show jsStringFromDartString, JSExternWrapperExt; -import 'dart:_string' show JSStringImpl; +import 'dart:_string' show embedderStringFromDartString, JSStringImpl; import 'dart:_wasm'; -String jsonEncode(String object) => JSStringImpl.fromRef( - embedder.jsonEncodeString(jsStringFromDartString(object).wrappedExternRef), +String jsonEncode(String object) => JSStringImpl.fromRefUnchecked( + embedder.jsonEncodeString( + embedderStringFromDartString(object).wrappedExternRef, + ), ); diff --git a/sdk/lib/_internal/wasm_standalone/lib/js_interop.dart b/sdk/lib/_internal/wasm_standalone/lib/js_interop.dart new file mode 100644 index 00000000000..8a6e35e1277 --- /dev/null +++ b/sdk/lib/_internal/wasm_standalone/lib/js_interop.dart @@ -0,0 +1,9 @@ +// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// dart:js_interop is not available on this target, but it's referenced in +// dart:_wasm. +// TODO(63166): Untangle these libraries. + +typedef JSAny = Never; diff --git a/sdk/lib/_internal/wasm_standalone/lib/print_patch.dart b/sdk/lib/_internal/wasm_standalone/lib/print_patch.dart index 31a5d70c8d0..dcecd1cdf53 100644 --- a/sdk/lib/_internal/wasm_standalone/lib/print_patch.dart +++ b/sdk/lib/_internal/wasm_standalone/lib/print_patch.dart @@ -3,8 +3,8 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:_embedder' as embedder; -import 'dart:_js_helper' show jsStringFromDartString, JSExternWrapperExt; +import 'dart:_string' show embedderStringFromDartString; @patch void printToConsole(String line) => - embedder.print(jsStringFromDartString(line).wrappedExternRef); + embedder.print(embedderStringFromDartString(line).wrappedExternRef); diff --git a/sdk/lib/_internal/wasm_standalone/lib/regexp.dart b/sdk/lib/_internal/wasm_standalone/lib/regexp.dart new file mode 100644 index 00000000000..c7e4a3ee8dc --- /dev/null +++ b/sdk/lib/_internal/wasm_standalone/lib/regexp.dart @@ -0,0 +1,216 @@ +// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of 'dart:_string'; + +final class EmbedderRegExp implements RegExp { + WasmExternRef? _regexp = WasmExternRef.nullRef; + + @override + final String pattern; + @override + final bool isMultiLine; + @override + final bool isCaseSensitive; + @override + final bool isUnicode; + @override + final bool isDotAll; + + EmbedderRegExp( + this.pattern, + this.isMultiLine, + this.isCaseSensitive, + this.isUnicode, + this.isDotAll, + ) { + final compiled = embedder.regexpCreateOrFailWithString( + embedderStringFromDartString(pattern).wrappedExternRef, + WasmI32.fromBool(isMultiLine), + WasmI32.fromBool(isCaseSensitive), + WasmI32.fromBool(isUnicode), + WasmI32.fromBool(isDotAll), + ); + if (!embedder.regexpIsRegexp(compiled).toBool()) { + // The returned value is the stringified JavaScript exception. Turn it + // into a Dart exception. + final errorMessage = JSStringImpl.fromRefUnchecked(compiled); + throw FormatException('Illegal RegExp pattern ($errorMessage)', pattern); + } + + this._regexp = compiled; + } + + @override + String toString() { + final buffer = StringBuffer('RegExp/'); + buffer.write(pattern); + buffer.write('/'); + + if (isMultiLine) buffer.write('m'); + if (!isCaseSensitive) buffer.write('i'); + if (isUnicode) buffer.write('u'); + if (isDotAll) buffer.write('s'); + return buffer.toString(); + } + + @override + Iterable allMatches(String input, [int start = 0]) { + RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(start, input.length); + return Iterable.withIterator( + () => _EmbedderMatchesIterator(this, input, start), + ); + } + + @override + RegExpMatch? firstMatch(String input) { + return _search(input, 0, false); + } + + @override + bool hasMatch(String input) { + return firstMatch(input) != null; + } + + @override + Match? matchAsPrefix(String string, [int start = 0]) { + return _search(string, start, true); + } + + _EmbedderMatch? _search(String string, int start, bool exactStartIndex) { + RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(start, string.length); + final match = embedder.regexpMatch( + _regexp, + embedderStringFromDartString(string).wrappedExternRef, + WasmI32.fromInt(start), + WasmI32.fromBool(exactStartIndex), + ); + if (match.isNull) { + return null; + } + + return _EmbedderMatch(this, string).._match = match; + } + + @override + String? stringMatch(String input) { + var match = firstMatch(input); + if (match != null) return match[0]; + return null; + } +} + +final class _EmbedderMatch implements RegExpMatch { + @override + final EmbedderRegExp pattern; + @override + final String input; + + WasmExternRef? _match = WasmExternRef.nullRef; + + _EmbedderMatch(this.pattern, this.input); + + @override + String? operator [](int group) { + return this.group(group); + } + + @override + int get start => embedder.regexpMatchGetStart(_match).toIntUnsigned(); + + @override + int get end => embedder.regexpMatchGetEnd(_match).toIntUnsigned(); + + @override + int get groupCount => + embedder.regexpMatchGetGroupCount(_match).toIntUnsigned(); + + @override + String? group(int group) { + IndexErrorUtils.checkIndex(group, groupCount + 1); + final contents = embedder.regexpMatchGetGroup( + _match, + WasmI32.fromInt(group), + ); + return contents.isNull ? null : JSStringImpl.fromRefUnchecked(contents); + } + + @override + List groups(List groupIndices) { + return [for (final index in groupIndices) group(index)]; + } + + @override + late final List groupNames = List.generate( + embedder.regexpMatchGetNamedGroups(_match).toIntUnsigned(), + (i) { + return JSStringImpl.fromRefUnchecked( + embedder.regexpMatchGetGroupName(_match, WasmI32.fromInt(i)), + ); + }, + ); + + @override + String? namedGroup(String name) { + final groupIndex = groupNames.indexOf(name); + if (groupIndex < 0) { + throw ArgumentError.value(name, "name", "Not a capture group name"); + } + + final contents = embedder.regexpMatchGetGroupByName( + _match, + WasmI32.fromInt(groupIndex), + ); + return contents.isNull ? null : JSStringImpl.fromRefUnchecked(contents); + } +} + +class _EmbedderMatchesIterator implements Iterator { + final EmbedderRegExp _regExp; + String? _string; + int _nextIndex; + RegExpMatch? _current; + + _EmbedderMatchesIterator(this._regExp, this._string, this._nextIndex); + + RegExpMatch get current => _current as RegExpMatch; + + static bool _isLeadSurrogate(int c) { + return c >= 0xd800 && c <= 0xdbff; + } + + static bool _isTrailSurrogate(int c) { + return c >= 0xdc00 && c <= 0xdfff; + } + + bool moveNext() { + var string = _string; + if (string == null) return false; + + if (_nextIndex <= string.length) { + final match = _regExp._search(_string!, _nextIndex, false); + if (match != null) { + _current = match; + int nextIndex = match.end; + if (match.start == nextIndex) { + // Zero-width match. Advance by one more, unless the regexp + // is in unicode mode and it would put us within a surrogate + // pair. In that case, advance past the code point as a whole. + if (_regExp.isUnicode && + _nextIndex + 1 < string.length && + _isLeadSurrogate(string.codeUnitAt(_nextIndex)) && + _isTrailSurrogate(string.codeUnitAt(_nextIndex + 1))) { + nextIndex++; + } + nextIndex++; + } + _nextIndex = nextIndex; + return true; + } + } + _current = null; + _string = null; // Marks iteration as ended. + return false; + } +} diff --git a/sdk/lib/_internal/wasm_standalone/lib/regexp_patch.dart b/sdk/lib/_internal/wasm_standalone/lib/regexp_patch.dart index f4c66508067..d62a1892ad6 100644 --- a/sdk/lib/_internal/wasm_standalone/lib/regexp_patch.dart +++ b/sdk/lib/_internal/wasm_standalone/lib/regexp_patch.dart @@ -5,7 +5,6 @@ import 'dart:_embedder'; import 'dart:_error_utils'; import 'dart:_internal' show patch; -import 'dart:_js_helper'; import 'dart:_string'; import 'dart:_wasm'; @@ -19,220 +18,13 @@ class RegExp { bool unicode = false, bool dotAll = false, }) { - return _EmbedderRegExp(source, multiLine, caseSensitive, unicode, dotAll); + return EmbedderRegExp(source, multiLine, caseSensitive, unicode, dotAll); } @patch static String escape(String text) { return JSStringImpl.fromRefUnchecked( - regexpEscape(jsStringFromDartString(text).wrappedExternRef), + regexpEscape(embedderStringFromDartString(text).wrappedExternRef), ); } } - -final class _EmbedderRegExp implements RegExp { - WasmExternRef? _regexp = WasmExternRef.nullRef; - - @override - final String pattern; - @override - final bool isMultiLine; - @override - final bool isCaseSensitive; - @override - final bool isUnicode; - @override - final bool isDotAll; - - _EmbedderRegExp( - this.pattern, - this.isMultiLine, - this.isCaseSensitive, - this.isUnicode, - this.isDotAll, - ) { - final compiled = regexpCreateOrFailWithString( - jsStringFromDartString(pattern).wrappedExternRef, - WasmI32.fromBool(isMultiLine), - WasmI32.fromBool(isCaseSensitive), - WasmI32.fromBool(isUnicode), - WasmI32.fromBool(isDotAll), - ); - if (!regexpIsRegexp(compiled).toBool()) { - // The returned value is the stringified JavaScript exception. Turn it - // into a Dart exception. - final errorMessage = JSStringImpl.fromRefUnchecked(compiled); - throw FormatException('Illegal RegExp pattern ($errorMessage)', pattern); - } - - this._regexp = compiled; - } - - @override - String toString() { - final buffer = StringBuffer('RegExp/'); - buffer.write(pattern); - buffer.write('/'); - - if (isMultiLine) buffer.write('m'); - if (!isCaseSensitive) buffer.write('i'); - if (isUnicode) buffer.write('u'); - if (isDotAll) buffer.write('s'); - return buffer.toString(); - } - - @override - Iterable allMatches(String input, [int start = 0]) { - RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(start, input.length); - return Iterable.withIterator( - () => _EmbedderMatchesIterator(this, input, start), - ); - } - - @override - RegExpMatch? firstMatch(String input) { - return _search(input, 0, false); - } - - @override - bool hasMatch(String input) { - return firstMatch(input) != null; - } - - @override - Match? matchAsPrefix(String string, [int start = 0]) { - return _search(string, start, true); - } - - _EmbedderMatch? _search(String string, int start, bool exactStartIndex) { - RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(start, string.length); - final match = regexpMatch( - _regexp, - jsStringFromDartString(string).wrappedExternRef, - WasmI32.fromInt(start), - WasmI32.fromBool(exactStartIndex), - ); - if (match.isNull) { - return null; - } - - return _EmbedderMatch(this, string).._match = match; - } - - @override - String? stringMatch(String input) { - var match = firstMatch(input); - if (match != null) return match[0]; - return null; - } -} - -final class _EmbedderMatch implements RegExpMatch { - @override - final _EmbedderRegExp pattern; - @override - final String input; - - WasmExternRef? _match = WasmExternRef.nullRef; - - _EmbedderMatch(this.pattern, this.input); - - @override - String? operator [](int group) { - return this.group(group); - } - - @override - int get start => regexpMatchGetStart(_match).toIntUnsigned(); - - @override - int get end => regexpMatchGetEnd(_match).toIntUnsigned(); - - @override - int get groupCount => regexpMatchGetGroupCount(_match).toIntUnsigned(); - - @override - String? group(int group) { - IndexErrorUtils.checkIndex(group, groupCount + 1); - final contents = regexpMatchGetGroup(_match, WasmI32.fromInt(group)); - return contents.isNull ? null : JSStringImpl.fromRefUnchecked(contents); - } - - @override - List groups(List groupIndices) { - return [for (final index in groupIndices) group(index)]; - } - - @override - late final List groupNames = List.generate( - regexpMatchGetNamedGroups(_match).toIntUnsigned(), - (i) { - return JSStringImpl.fromRefUnchecked( - regexpMatchGetGroupName(_match, WasmI32.fromInt(i)), - ); - }, - ); - - @override - String? namedGroup(String name) { - final groupIndex = groupNames.indexOf(name); - if (groupIndex < 0) { - throw ArgumentError.value(name, "name", "Not a capture group name"); - } - - final contents = regexpMatchGetGroupByName( - _match, - WasmI32.fromInt(groupIndex), - ); - return contents.isNull ? null : JSStringImpl.fromRefUnchecked(contents); - } -} - -class _EmbedderMatchesIterator implements Iterator { - final _EmbedderRegExp _regExp; - String? _string; - int _nextIndex; - RegExpMatch? _current; - - _EmbedderMatchesIterator(this._regExp, this._string, this._nextIndex); - - RegExpMatch get current => _current as RegExpMatch; - - static bool _isLeadSurrogate(int c) { - return c >= 0xd800 && c <= 0xdbff; - } - - static bool _isTrailSurrogate(int c) { - return c >= 0xdc00 && c <= 0xdfff; - } - - bool moveNext() { - var string = _string; - if (string == null) return false; - - if (_nextIndex <= string.length) { - final match = _regExp._search(_string!, _nextIndex, false); - if (match != null) { - _current = match; - int nextIndex = match.end; - if (match.start == nextIndex) { - // Zero-width match. Advance by one more, unless the regexp - // is in unicode mode and it would put us within a surrogate - // pair. In that case, advance past the code point as a whole. - if (_regExp.isUnicode && - _nextIndex + 1 < string.length && - _isLeadSurrogate(string.codeUnitAt(_nextIndex)) && - _isTrailSurrogate(string.codeUnitAt(_nextIndex + 1))) { - nextIndex++; - } - nextIndex++; - } - _nextIndex = nextIndex; - return true; - } - } - _current = null; - _string = null; // Marks iteration as ended. - return false; - } -} diff --git a/sdk/lib/_internal/wasm_standalone/lib/string_buffer_patch.dart b/sdk/lib/_internal/wasm_standalone/lib/string_buffer_patch.dart index c1d35106c6d..0a6f42a50cd 100644 --- a/sdk/lib/_internal/wasm_standalone/lib/string_buffer_patch.dart +++ b/sdk/lib/_internal/wasm_standalone/lib/string_buffer_patch.dart @@ -3,8 +3,8 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:_embedder'; +import 'dart:_error_utils' show RangeErrorUtils; import 'dart:_internal' show patch; -import 'dart:_js_helper'; import 'dart:_string'; import 'dart:_wasm'; @@ -35,6 +35,7 @@ class StringBuffer { @patch void writeCharCode(int charCode) { + RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(charCode, 0x10ffff); stringBufferWriteCharCode(_hostBuffer, WasmI32.fromInt(charCode)); } @@ -75,7 +76,7 @@ class StringBuffer { void _writeString(String str) { stringBufferWriteString( _hostBuffer, - jsStringFromDartString(str).wrappedExternRef, + embedderStringFromDartString(str).wrappedExternRef, ); } } diff --git a/sdk/lib/_internal/wasm_standalone/lib/string_patch.dart b/sdk/lib/_internal/wasm_standalone/lib/string_patch.dart new file mode 100644 index 00000000000..e613b56d6ae --- /dev/null +++ b/sdk/lib/_internal/wasm_standalone/lib/string_patch.dart @@ -0,0 +1,164 @@ +// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:_boxed_int'; +import 'dart:_embedder' show stringFromCharCodeArray; +import 'dart:_error_utils'; +import 'dart:_internal' show EfficientLengthIterable, patch, unsafeCast; +import 'dart:_typed_data'; +import 'dart:_list'; +import 'dart:_string'; +import 'dart:_wasm'; +import 'dart:typed_data'; + +@pragma('wasm:initialize-at-startup') +const int _stringFromCharCodesSize = 512; +final _stringFromCharCodes = WasmArray(_stringFromCharCodesSize); + +@patch +class String { + @patch + factory String.fromCharCodes( + Iterable charCodes, [ + int start = 0, + int? end, + ]) { + RangeError.checkNotNegative(start, "start"); + if (end != null && end < start) { + throw RangeError.range(end, start, null, "end"); + } + if (charCodes is U8List) { + return _fromU8ListCharCodes(charCodes, start, end); + } + if (charCodes is U16List) { + return _fromU16ListCharCodes(charCodes, start, end); + } + if (charCodes is WasmListBase) { + final result = _fromWasmListBaseCharCodes( + unsafeCast>(charCodes), + start, + end, + ); + if (result != null) return result; + } + return _fromIterableCharCodes(charCodes, start, end); + } + + static String _fromU8ListCharCodes( + U8List charCodes, + int start, + int? optionalEnd, + ) { + final length = charCodes.length; + int end = optionalEnd != null + ? (optionalEnd < length ? optionalEnd : length) + : length; + if (end <= start) return ''; + + return JSStringImpl.fromAsciiBytes( + charCodes.data, + charCodes.offsetInElements + start, + charCodes.offsetInElements + end, + ); + } + + static String _fromU16ListCharCodes( + U16List charCodes, + int start, + int? optionalEnd, + ) { + final length = charCodes.length; + int end = optionalEnd != null + ? (optionalEnd < length ? optionalEnd : length) + : length; + if (end <= start) return ''; + final count = end - start; + + final int offset = charCodes.offsetInElements; + start += offset; + end += offset; + + final data = charCodes.data; + return JSStringImpl.fromCharCodeArray(data, start, end); + } + + static String? _fromWasmListBaseCharCodes( + WasmListBase charCodes, + int start, + int? optionalEnd, + ) { + final length = charCodes.length; + final int end = optionalEnd != null + ? (optionalEnd < length ? optionalEnd : length) + : length; + if (end <= start) return ''; + final count = end - start; + + final src = charCodes.data; + final dst = count < _stringFromCharCodesSize + ? _stringFromCharCodes + : WasmArray(count); + for (int i = 0; i < count; ++i) { + final charCode = unsafeCast(src[start + i]); + if (charCode.gtU(0xffff)) { + return null; // fall back to general case. + } + dst.write(i, charCode); + } + return JSStringImpl.fromCharCodeArray(dst, 0, count); + } + + static String _fromIterableCharCodes( + Iterable charCodes, + int start, + int? end, + ) { + RangeError.checkNotNegative(start, "start"); + if (end != null) { + if (end < start) { + throw RangeError.range(end, start, null, "end"); + } + if (end == start) return ""; + } + + final length = charCodes.length; + + // Skip until `start`. + final it = charCodes.iterator; + for (int i = 0; i < start; i++) { + it.moveNext(); + } + + // Convert to WasmArray for JSStringImpl.fromCharCodeArray. + final charCodesLength = (end ?? length) - start; + if (charCodesLength <= 0) return ""; + final typedArrayLength = charCodesLength * 2; + final WasmArray list = WasmArray(typedArrayLength); + int index = 0; // index in `list`. + end ??= start + charCodesLength; + for (int i = start; i < end; i++) { + if (!it.moveNext()) { + break; + } + final charCode = it.current; + if (charCode.leU(0xffff)) { + list.write(index++, charCode); + } else if (charCode.leU(0x10ffff)) { + list.write(index++, 0xd800 + ((((charCode - 0x10000) >> 10) & 0x3ff))); + list.write(index++, 0xdc00 + (charCode & 0x3ff)); + } else { + throw RangeError.range(charCode, 0, 0x10ffff); + } + } + + return JSStringImpl.fromCharCodeArray(list, 0, index); + } + + @patch + @pragma("wasm:prefer-inline") + factory String.fromCharCode(int charCode) { + RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(charCode, 0x10ffff); + return JSStringImpl.fromCodePoint(charCode); + } +} diff --git a/sdk/lib/_internal/wasm_standalone/lib/wasm_types_patch.dart b/sdk/lib/_internal/wasm_standalone/lib/wasm_types_patch.dart new file mode 100644 index 00000000000..8aed1a797fd --- /dev/null +++ b/sdk/lib/_internal/wasm_standalone/lib/wasm_types_patch.dart @@ -0,0 +1,19 @@ +// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +import "dart:_internal" show patch; +import "dart:_wasm"; +import "dart:js_interop"; + +@patch +extension WasmExternRefToJSAny on WasmExternRef { + @patch + JSAny get toJS => throw UnsupportedError( + 'WasmExternRefToJSAny.toJS is unsupported on the standalone target.', + ); +} + +@patch +WasmExternRef? externRefForJSAny(JSAny object) => throw UnsupportedError( + 'externRefForJSAny is unsupported on the standalone target.', +); diff --git a/sdk/lib/libraries.json b/sdk/lib/libraries.json index 5b247d6aaaa..756966bdf4a 100644 --- a/sdk/lib/libraries.json +++ b/sdk/lib/libraries.json @@ -295,7 +295,7 @@ "_internal/wasm_standalone/lib/regexp_patch.dart", "_internal/wasm_standalone/lib/stack_trace_patch.dart", "_internal/wasm_standalone/lib/string_buffer_patch.dart", - "_internal/wasm/lib/string_patch.dart", + "_internal/wasm_standalone/lib/string_patch.dart", "_internal/wasm_standalone/lib/stopwatch_patch.dart", "_internal/wasm/lib/sync_star_patch.dart", "_internal/wasm_standalone/lib/uri_patch.dart", @@ -334,7 +334,7 @@ ] }, "_string": { - "uri": "_internal/wasm/lib/js_string.dart" + "uri": "_internal/wasm_standalone/lib/embedder_string.dart" }, "_typed_data": { "uri": "_internal/wasm/lib/typed_data.dart", @@ -342,12 +342,6 @@ "_internal/wasm_standalone/lib/typed_data_copy_from_js.dart" ] }, - "_js_helper": { - "uri": "_internal/wasm/lib/js_helper.dart", - "patches": [ - "_internal/wasm/lib/js_helper_patch.dart" - ] - }, "_internal": { "uri": "internal/internal.dart", "patches": [ @@ -361,7 +355,7 @@ }, "_wasm": { "uri": "_wasm/wasm_types.dart", - "patches": "_internal/wasm/lib/wasm_types_patch.dart" + "patches": "_internal/wasm_standalone/lib/wasm_types_patch.dart" }, "math": { "uri": "math/math.dart", @@ -369,6 +363,10 @@ "_internal/wasm/lib/math_patch.dart", "_internal/wasm_standalone/lib/math_externs_patch.dart" ] + }, + "js_interop": { + "uri": "_internal/wasm_standalone/lib/js_interop.dart", + "support_conditional_import": false } } }, @@ -419,6 +417,20 @@ "_internal/wasm/lib/math_patch.dart", "_internal/wasm/lib/math_externs_patch.dart" ] + }, + "_js_string_convert": { + "uri": "_internal/wasm/lib/js_string_convert.dart" + }, + "_js_types": { + "uri": "_internal/wasm/lib/js_types.dart" + }, + "js_interop": { + "uri": "js_interop/js_interop.dart", + "patches": "_internal/wasm/lib/js_interop_patch.dart" + }, + "js_interop_unsafe": { + "uri": "js_interop_unsafe/js_interop_unsafe.dart", + "patches": "_internal/wasm/lib/js_interop_unsafe_patch.dart" } } }, @@ -448,12 +460,6 @@ "_internal/js_shared/lib/http_patch.dart" ] }, - "_js_string_convert": { - "uri": "_internal/wasm/lib/js_string_convert.dart" - }, - "_js_types": { - "uri": "_internal/wasm/lib/js_types.dart" - }, "_list": { "uri": "_internal/wasm/lib/list.dart" }, @@ -497,14 +503,6 @@ "patches": [ "_internal/wasm/lib/isolate_patch.dart" ] - }, - "js_interop": { - "uri": "js_interop/js_interop.dart", - "patches": "_internal/wasm/lib/js_interop_patch.dart" - }, - "js_interop_unsafe": { - "uri": "js_interop_unsafe/js_interop_unsafe.dart", - "patches": "_internal/wasm/lib/js_interop_unsafe_patch.dart" } } }, diff --git a/sdk/lib/libraries.yaml b/sdk/lib/libraries.yaml index e23e2f29269..55e1270744e 100644 --- a/sdk/lib/libraries.yaml +++ b/sdk/lib/libraries.yaml @@ -253,7 +253,7 @@ wasm_standalone: - _internal/wasm_standalone/lib/regexp_patch.dart - _internal/wasm_standalone/lib/stack_trace_patch.dart - _internal/wasm_standalone/lib/string_buffer_patch.dart - - _internal/wasm/lib/string_patch.dart # TODO(53884): Rewrite without JS interop in _internal/wasm_standalone + - _internal/wasm_standalone/lib/string_patch.dart - _internal/wasm_standalone/lib/stopwatch_patch.dart - _internal/wasm/lib/sync_star_patch.dart - _internal/wasm_standalone/lib/uri_patch.dart @@ -282,15 +282,11 @@ wasm_standalone: patches: - _internal/wasm_standalone/lib/boxed_double_patch.dart _string: - uri: _internal/wasm/lib/js_string.dart # TODO(53884): Rewrite without JS interop in _internal/wasm_standalone + uri: _internal/wasm_standalone/lib/embedder_string.dart _typed_data: uri: _internal/wasm/lib/typed_data.dart patches: - _internal/wasm_standalone/lib/typed_data_copy_from_js.dart - _js_helper: # TODO(53884): Remove once other wasm_standalone patches no longer reference this - uri: _internal/wasm/lib/js_helper.dart - patches: - - _internal/wasm/lib/js_helper_patch.dart _internal: uri: internal/internal.dart patches: @@ -302,12 +298,17 @@ wasm_standalone: - _internal/vm_shared/lib/check_valid_weak_target_patch.dart _wasm: uri: _wasm/wasm_types.dart - patches: _internal/wasm/lib/wasm_types_patch.dart # TODO(53884): Remove once other wasm_standalone patches no longer reference this + patches: _internal/wasm_standalone/lib/wasm_types_patch.dart math: uri: math/math.dart patches: - _internal/wasm/lib/math_patch.dart - _internal/wasm_standalone/lib/math_externs_patch.dart + js_interop: + # TODO(63166): Remove js_interop from wasm_standalone. It's fundamentally unsupported, but + # currently added as a stub because APIs in dart:_wasm reference it. + uri: _internal/wasm_standalone/lib/js_interop.dart + support_conditional_import: false wasm_js_common: include: @@ -343,6 +344,16 @@ wasm_js_common: patches: - _internal/wasm/lib/math_patch.dart - _internal/wasm/lib/math_externs_patch.dart + _js_string_convert: + uri: _internal/wasm/lib/js_string_convert.dart + _js_types: + uri: _internal/wasm/lib/js_types.dart + js_interop: + uri: js_interop/js_interop.dart + patches: _internal/wasm/lib/js_interop_patch.dart + js_interop_unsafe: + uri: js_interop_unsafe/js_interop_unsafe.dart + patches: _internal/wasm/lib/js_interop_unsafe_patch.dart wasm_common: libraries: @@ -362,10 +373,6 @@ wasm_common: uri: _http/http.dart patches: - _internal/js_shared/lib/http_patch.dart - _js_string_convert: - uri: _internal/wasm/lib/js_string_convert.dart - _js_types: - uri: _internal/wasm/lib/js_types.dart _list: uri: _internal/wasm/lib/list.dart _object_helper: @@ -398,12 +405,6 @@ wasm_common: uri: isolate/isolate.dart patches: - "_internal/wasm/lib/isolate_patch.dart" - js_interop: - uri: js_interop/js_interop.dart - patches: _internal/wasm/lib/js_interop_patch.dart - js_interop_unsafe: - uri: js_interop_unsafe/js_interop_unsafe.dart - patches: _internal/wasm/lib/js_interop_unsafe_patch.dart dart2js: include: diff --git a/tests/co19/co19-dart2wasm.status b/tests/co19/co19-dart2wasm.status index 2e9244bd33b..3fd78eb171a 100644 --- a/tests/co19/co19-dart2wasm.status +++ b/tests/co19/co19-dart2wasm.status @@ -49,3 +49,8 @@ LibTest/js_interop/importModule_A02_t01: Skip # https://github.com/dart-lang/sdk LibTest/js_interop/importModule_A02_t02: Skip # https://github.com/dart-lang/sdk/issues/61204 LibTest/js_interop/importModule_A02_t03: Skip # https://github.com/dart-lang/sdk/issues/61204 LibTest/js_interop/interop_A08_t05: Skip # https://github.com/dart-lang/sdk/issues/61204 + +[ $compiler == dart2wasm && $dart2wasm_standalone ] +LanguageFeatures/Augmentations/js_interop/*: SkipByDesign # This is a generic WebAssembly target without JavaScript support +LibTest/js_interop/*: SkipByDesign # This is a generic WebAssembly target without JavaScript support +LibTest/js_interop_unsafe/*: SkipByDesign # This is a generic WebAssembly target without JavaScript support diff --git a/tests/language/library/env_test.dart b/tests/language/library/env_test.dart index d1ed48fb80a..dc1cd790b0e 100644 --- a/tests/language/library/env_test.dart +++ b/tests/language/library/env_test.dart @@ -37,9 +37,13 @@ main() { const bool.fromEnvironment("dart.library.web_gl"), ); - // All web backends support `dart:js_interop` + // All web backends support `dart:js_interop`. dart2wasm standalone is tested + // on the web but is not a web backend. + final isDart2WasmStandalone = const String.fromEnvironment( + "test_runner.configuration", + ).contains('standalone'); Expect.equals( - isWebConfiguration, + isWebConfiguration && !isDart2WasmStandalone, const bool.fromEnvironment("dart.library.js_interop"), ); diff --git a/tests/lib/lib.status b/tests/lib/lib.status index 5f752dda575..57e1e048c38 100644 --- a/tests/lib/lib.status +++ b/tests/lib/lib.status @@ -135,6 +135,10 @@ js_interop_unsafe/*: SkipByDesign # Only supported on web backends. [ $compiler != dart2js && $compiler != ddc ] web/*: SkipByDesign +[ $compiler == dart2wasm && $dart2wasm_standalone ] +js/*: SkipByDesign # This is a generic WebAssembly target without JavaScript support +js_interop_unsafe/*: SkipByDesign # This is a generic WebAssembly target without JavaScript support + [ $runtime == chrome && $system == macos ] convert/streamed_conversion_utf8_encode_test: SkipSlow # Times out. Issue 22050 html/canvasrendering/arc_test: Skip # Issue 42048