[dart2wasm] Remove runtime blob to/from js string helpers, use JS<>() instead

This removes the special runtime blob for string conversions, instead we
make 2 functions for conversions use our built-in `JS<>()` support.

This will allow specializing the implementation in a future CL.

Change-Id: I06df25ed805042c0a3e2efb966eaebd1ce67dc0c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/372660
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Ömer Ağacan <omersa@google.com>
This commit is contained in:
Martin Kustermann 2024-06-21 15:36:29 +00:00 committed by Commit Queue
parent c475a7ebcd
commit 9761ccff6f
19 changed files with 170 additions and 130 deletions

View file

@ -16,56 +16,6 @@ export const instantiate = async (modulePromise, importObjectPromise) => {
let dartInstance;
''';
// Break to support system dependent conversion routines.
const jsRuntimeBlobPart2Regular = r'''
function stringFromDartString(string) {
const totalLength = dartInstance.exports.$stringLength(string);
let result = '';
let index = 0;
while (index < totalLength) {
let chunkLength = Math.min(totalLength - index, 0xFFFF);
const array = new Array(chunkLength);
for (let i = 0; i < chunkLength; i++) {
array[i] = dartInstance.exports.$stringRead(string, index++);
}
result += String.fromCharCode(...array);
}
return result;
}
function stringToDartString(string) {
const length = string.length;
let range = 0;
for (let i = 0; i < length; i++) {
range |= string.codePointAt(i);
}
if (range < 256) {
const dartString = dartInstance.exports.$stringAllocate1(length);
for (let i = 0; i < length; i++) {
dartInstance.exports.$stringWrite1(dartString, i, string.codePointAt(i));
}
return dartString;
} else {
const dartString = dartInstance.exports.$stringAllocate2(length);
for (let i = 0; i < length; i++) {
dartInstance.exports.$stringWrite2(dartString, i, string.charCodeAt(i));
}
return dartString;
}
}
''';
// Conversion functions for JSCM.
const jsRuntimeBlobPart2JSCM = r'''
function stringFromDartString(string) {
return dartInstance.exports.$jsStringFromJSStringImpl(string);
}
function stringToDartString(string) {
return dartInstance.exports.$jsStringToJSStringImpl(string);
}
''';
const jsRuntimeBlobPart3 = r'''
// Prints to the console
function printToConsole(value) {

View file

@ -77,7 +77,6 @@ s: [
}
return '''
$jsRuntimeBlobPart1
${mode == wasm_target.Mode.jsCompatibility ? jsRuntimeBlobPart2JSCM : jsRuntimeBlobPart2Regular}
$jsRuntimeBlobPart3
${usedJSMethods.join(',\n')}
$jsRuntimeBlobPart4

View file

@ -3,7 +3,9 @@
// BSD-style license that can be found in the LICENSE file.
import 'dart:_internal' show doubleToIntBits, intBitsToDouble;
import 'dart:_js_helper' show JS;
import 'dart:_js_helper' show JS, jsStringToDartString;
import 'dart:_string';
import 'dart:_wasm';
@pragma("wasm:entry-point")
final class _BoxedDouble extends double {
@ -330,7 +332,8 @@ final class _BoxedDouble extends double {
return "0.0";
}
}
String result = JS<String>("v => stringToDartString(v.toString())", value);
String result = jsStringToDartString(
JSStringImpl(JS<WasmExternRef>("v => v.toString()", value)));
if (this % 1.0 == 0.0 && result.indexOf('e') == -1) {
result = '$result.0';
}
@ -369,10 +372,11 @@ final class _BoxedDouble extends double {
return result;
}
String _toStringAsFixed(int fractionDigits) => JS<String>(
"(d, digits) => stringToDartString(d.toFixed(digits))",
value,
fractionDigits.toDouble());
String _toStringAsFixed(int fractionDigits) =>
jsStringToDartString(JSStringImpl(JS<WasmExternRef>(
"(d, digits) => d.toFixed(digits)",
value,
fractionDigits.toDouble())));
String toStringAsExponential([int? fractionDigits]) {
// See ECMAScript-262, 15.7.4.6 for details.
@ -398,12 +402,11 @@ final class _BoxedDouble extends double {
}
String _toStringAsExponential(int? fractionDigits) {
if (fractionDigits == null) {
return JS<String>("d => stringToDartString(d.toExponential())", value);
} else {
return JS<String>("(d, f) => stringToDartString(d.toExponential(f))",
value, fractionDigits.toDouble());
}
final jsString = JSStringImpl(fractionDigits == null
? JS<WasmExternRef>("d => d.toExponential()", value)
: JS<WasmExternRef>(
"(d, f) => d.toExponential(f)", value, fractionDigits.toDouble()));
return jsStringToDartString(jsString);
}
String toStringAsPrecision(int precision) {
@ -427,10 +430,11 @@ final class _BoxedDouble extends double {
return result;
}
String _toStringAsPrecision(int fractionDigits) => JS<String>(
"(d, precision) => stringToDartString(d.toPrecision(precision))",
value,
fractionDigits.toDouble());
String _toStringAsPrecision(int fractionDigits) =>
jsStringToDartString(JSStringImpl(JS<WasmExternRef>(
"(d, precision) => d.toPrecision(precision)",
value,
fractionDigits.toDouble())));
// Order is: NaN > Infinity > ... > 0.0 > -0.0 > ... > -Infinity.
int compareTo(num other) {

View file

@ -29,11 +29,17 @@ import "dart:_internal"
import "dart:_internal" as _internal;
import 'dart:_js_helper' show JS, JSSyntaxRegExp, quoteStringForRegExp;
import 'dart:_js_helper'
show
JS,
JSSyntaxRegExp,
quoteStringForRegExp,
jsStringFromDartString,
jsStringToDartString;
import 'dart:_list';
import 'dart:_string' show JSStringImpl;
import 'dart:_string' show JSStringImpl, JSStringImplExt;
import "dart:collection"
show

View file

@ -4,7 +4,9 @@
import "dart:_internal" show patch;
import 'dart:_js_helper' show JS;
import 'dart:_js_helper' show JS, jsStringToDartString;
import 'dart:_string';
import 'dart:_wasm';
@patch
class DateTime {
@ -14,15 +16,16 @@ class DateTime {
@patch
static String _timeZoneNameForClampedSeconds(int secondsSinceEpoch) =>
JS<String>(r"""secondsSinceEpoch => {
jsStringToDartString(
JSStringImpl(JS<WasmExternRef>(r"""secondsSinceEpoch => {
const date = new Date(secondsSinceEpoch * 1000);
const match = /\((.*)\)/.exec(date.toString());
if (match == null) {
// This should never happen on any recent browser.
return '';
}
return stringToDartString(match[1]);
}""", secondsSinceEpoch.toDouble());
return match[1];
}""", secondsSinceEpoch.toDouble())));
// In Dart, the offset is the difference between local time and UTC,
// while in JS, the offset is the difference between UTC and local time.

View file

@ -29,12 +29,11 @@ class double {
// - a Dart double literal
// We do allow leading or trailing whitespace.
double result = JS<double>(r"""s => {
const jsSource = stringFromDartString(s);
if (!/^\s*[+-]?(?:Infinity|NaN|(?:\.\d+|\d+(?:\.\d*)?)(?:[eE][+-]?\d+)?)\s*$/.test(jsSource)) {
if (!/^\s*[+-]?(?:Infinity|NaN|(?:\.\d+|\d+(?:\.\d*)?)(?:[eE][+-]?\d+)?)\s*$/.test(s)) {
return NaN;
}
return parseFloat(jsSource);
}""", source);
return parseFloat(s);
}""", jsStringFromDartString(source).toExternRef);
if (result.isNaN) {
String trimmed = source.trim();
if (!(trimmed == 'NaN' || trimmed == '+NaN' || trimmed == '-NaN')) {

View file

@ -2,7 +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:_js_helper" show JS;
import "dart:_js_helper" show JS, jsStringFromDartString, jsStringToDartString;
import "dart:_js_types" show JSStringImpl;
import 'dart:_string';
import 'dart:_wasm';
part "class_id.dart";
@ -144,8 +146,9 @@ List<String> _makeStringList() => <String>[];
@pragma("wasm:export", "\$listAdd")
void _listAdd(List<dynamic> list, dynamic item) => list.add(item);
String jsonEncode(String object) => JS<String>(
"s => stringToDartString(JSON.stringify(stringFromDartString(s)))", object);
String jsonEncode(String object) =>
jsStringToDartString(JSStringImpl(JS<WasmExternRef>(
"s => JSON.stringify(s)", jsStringFromDartString(object).toExternRef)));
/// Whether to check bounds in [indexCheck] and [indexCheckWithName], which are
/// used in list and typed data implementations.

View file

@ -76,8 +76,9 @@ extension DoubleToExternRef on double? {
}
extension StringToExternRef on String? {
WasmExternRef? get toExternRef =>
this == null ? WasmExternRef.nullRef : jsStringFromDartString(this!);
WasmExternRef? get toExternRef => this == null
? WasmExternRef.nullRef
: jsStringFromDartString(this!).toExternRef;
}
extension ListOfObjectToExternRef on List<Object?>? {
@ -285,11 +286,8 @@ WasmExternRef? jsDataViewFromDartByteData(ByteData data, int length) =>
WasmExternRef? jsArrayFromDartList(List<Object?> l) =>
JS<WasmExternRef?>('l => arrayFromDartList(Array, l)', l);
WasmExternRef? jsStringFromDartString(String s) =>
JS<WasmExternRef?>('stringFromDartString', s);
String jsStringToDartString(WasmExternRef? s) =>
JS<String>('stringToDartString', s);
external JSStringImpl jsStringFromDartString(String s);
external String jsStringToDartString(JSStringImpl s);
WasmExternRef? newObjectRaw() => JS<WasmExternRef?>('() => ({})');
@ -364,7 +362,7 @@ WasmExternRef? jsifyRaw(Object? object) {
} else if (object is JSValue) {
return object.toExternRef;
} else if (object is String) {
return jsStringFromDartString(object);
return jsStringFromDartString(object).toExternRef;
} else if (object is js_types.JSInt8ArrayImpl) {
return object.toJSArrayExternRef();
} else if (object is js_types.JSUint8ArrayImpl) {

View file

@ -0,0 +1,54 @@
// Copyright (c) 2024, 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:_js_helper' show JS;
import 'dart:_string';
import 'dart:_wasm';
@patch
@pragma('wasm:prefer-inline')
JSStringImpl jsStringFromDartString(String s) {
if (s is JSStringImpl) return s;
return JSStringImpl(JS<WasmExternRef>(r'''
(s, length) => {
let result = '';
let index = 0;
while (index < length) {
let chunkLength = Math.min(length - index, 0xFFFF);
const array = new Array(chunkLength);
for (let i = 0; i < chunkLength; i++) {
array[i] = dartInstance.exports.$stringRead(s, index++);
}
result += String.fromCharCode(...array);
}
return result;
}
''', jsObjectFromDartObject(s), s.length.toWasmI32()));
}
@patch
@pragma('wasm:prefer-inline')
String jsStringToDartString(JSStringImpl s) => JS<String>(r'''
(s, length) => {
let range = 0;
for (let i = 0; i < length; i++) {
range |= s.codePointAt(i);
}
if (range < 256) {
const dartString = dartInstance.exports.$stringAllocate1(length);
for (let i = 0; i < length; i++) {
dartInstance.exports.$stringWrite1(dartString, i, s.codePointAt(i));
}
return dartString;
} else {
const dartString = dartInstance.exports.$stringAllocate2(length);
for (let i = 0; i < length; i++) {
dartInstance.exports.$stringWrite2(dartString, i, s.charCodeAt(i));
}
return dartString;
}
}
''', s.toExternRef, s.length.toWasmI32());

View file

@ -459,8 +459,8 @@ extension StringToJSString on String {
@patch
JSString get toJS {
final t = this;
return JSString._(
JSValue(t is JSStringImpl ? t.toExternRef : jsStringFromDartString(t)));
return JSString._(JSValue(
(t is JSStringImpl ? t : jsStringFromDartString(t)).toExternRef));
}
}

View file

@ -37,9 +37,6 @@ final class JSStringImpl implements String, StringUncheckedOperationsBase {
static String? box(WasmExternRef? ref) =>
js.isDartNull(ref) ? null : JSStringImpl(ref);
@pragma("wasm:prefer-inline")
WasmExternRef? get toExternRef => _ref;
@override
@pragma("wasm:prefer-inline")
int get length => _jsLength(toExternRef);
@ -113,7 +110,7 @@ final class JSStringImpl implements String, StringUncheckedOperationsBase {
// TODO(joshualitt): Refactor `string_patch.dart` so we can directly
// allocate a string of the right size.
return js.jsStringToDartString(toExternRef) + other;
return js.jsStringToDartString(this) + other;
}
@override
@ -758,6 +755,11 @@ final class JSStringImpl implements String, StringUncheckedOperationsBase {
}
}
extension JSStringImplExt on JSStringImpl {
@pragma("wasm:prefer-inline")
WasmExternRef? get toExternRef => _ref;
}
String _matchString(Match match) => match[0]!;
String _stringIdentity(String string) => string;
@ -770,14 +772,6 @@ String _escapeReplacement(String replacement) {
r'(s) => s.replace(/\$/g, "$$$$")', replacement.toJS.toExternRef));
}
@pragma("wasm:export", "\$jsStringToJSStringImpl")
JSStringImpl _jsStringToJSStringImpl(WasmExternRef? string) =>
JSStringImpl(string);
@pragma("wasm:export", "\$jsStringFromJSStringImpl")
WasmExternRef? _jsStringFromJSStringImpl(JSStringImpl string) =>
string.toExternRef;
bool _jsIdentical(WasmExternRef? ref1, WasmExternRef? ref2) =>
js.JS<bool>('Object.is', ref1, ref2);

View file

@ -5,5 +5,5 @@
part of "internal_patch.dart";
@patch
void printToConsole(String line) =>
JS<void>('s => printToConsole(stringFromDartString(s))');
void printToConsole(String line) => JS<void>(
's => printToConsole(s)', jsStringFromDartString(line).toExternRef);

View file

@ -13,13 +13,12 @@ String quoteStringForRegExp(String string) =>
// This method is optimized to test before replacement, which should be
// much faster. This might be worth measuring in real world use cases
// though.
JS<String>(r"""s => {
let jsString = stringFromDartString(s);
if (/[[\]{}()*+?.\\^$|]/.test(jsString)) {
jsString = jsString.replace(/[[\]{}()*+?.\\^$|]/g, '\\$&');
jsStringToDartString(JSStringImpl(JS<WasmExternRef>(r"""s => {
if (/[[\]{}()*+?.\\^$|]/.test(s)) {
s = s.replace(/[[\]{}()*+?.\\^$|]/g, '\\$&');
}
return stringToDartString(jsString);
}""", string);
return s;
}""", jsStringFromDartString(string).toExternRef)));
// TODO(srujzs): Add this to `JSObject`.
@js.JS('Object.keys')
@ -107,7 +106,7 @@ class JSSyntaxRegExp implements RegExp {
String modifiers = '$m$i$u$s$g';
// The call to create the regexp is wrapped in a try catch so we can
// reformat the exception if need be.
WasmExternRef? result = JS<WasmExternRef?>("""(s, m) => {
final result = JS<WasmExternRef?>("""(s, m) => {
try {
return new RegExp(s, m);
} catch (e) {
@ -117,7 +116,7 @@ class JSSyntaxRegExp implements RegExp {
if (isJSRegExp(result)) return JSValue(result!) as JSNativeRegExp;
// The returned value is the stringified JavaScript exception. Turn it into
// a Dart exception.
String errorMessage = jsStringToDartString(result);
String errorMessage = jsStringToDartString(JSStringImpl(result!));
throw new FormatException('Illegal RegExp pattern ($errorMessage)', source);
}

View file

@ -11,8 +11,8 @@ import "dart:_internal"
unsafeCast,
WasmStringBase;
import 'dart:_js_helper' show JS, jsStringToDartString;
import 'dart:_js_types' show JSStringImpl;
import 'dart:_js_helper' show JS, jsStringFromDartString, jsStringToDartString;
import 'dart:_string';
import 'dart:_object_helper';
import 'dart:_string_helper';
import 'dart:_typed_data';
@ -107,11 +107,13 @@ extension OneByteStringUnsafeExtensions on String {
const int _maxLatin1 = 0xff;
const int _maxUtf16 = 0xffff;
String _toUpperCase(String string) => JS<String>(
"s => stringToDartString(stringFromDartString(s).toUpperCase())", string);
String _toUpperCase(String string) =>
jsStringToDartString(JSStringImpl(JS<WasmExternRef>(
"s => s.toUpperCase()", jsStringFromDartString(string).toExternRef)));
String _toLowerCase(String string) => JS<String>(
"s => stringToDartString(stringFromDartString(s).toLowerCase())", string);
String _toLowerCase(String string) =>
jsStringToDartString(JSStringImpl(JS<WasmExternRef>(
"s => s.toLowerCase()", jsStringFromDartString(string).toExternRef)));
/**
* [StringBase] contains common methods used by concrete String
@ -938,7 +940,7 @@ abstract final class StringBase extends WasmStringBase
final value = values[i];
var stringValue = value is String ? value : value.toString();
if (stringValue is JSStringImpl) {
stringValue = jsStringToDartString(stringValue.toExternRef);
stringValue = jsStringToDartString(stringValue);
}
values[i] = stringValue;
isOneByteString = isOneByteString && stringValue is OneByteString;
@ -1099,7 +1101,7 @@ abstract final class StringBase extends WasmStringBase
for (int i = start; i < end; i++) {
String stringValue = strings[i];
if (stringValue is JSStringImpl) {
stringValue = jsStringToDartString(stringValue.toExternRef);
stringValue = jsStringToDartString(stringValue);
strings[i] = stringValue;
}
isOneByteString = isOneByteString && stringValue is OneByteString;

View file

@ -4,7 +4,6 @@
import "dart:_internal" show patch;
import "dart:_string";
import "dart:_js_types" show JSStringImpl;
@patch
class String {

View file

@ -8,15 +8,15 @@ part of "core_patch.dart";
class Uri {
@patch
static Uri get base {
String? currentUri = JS<String?>("""() => {
final currentUri = JSStringImpl(JS<WasmExternRef?>("""() => {
// On browsers return `globalThis.location.href`
if (globalThis.location != null) {
return stringToDartString(globalThis.location.href);
return globalThis.location.href;
}
return null;
}""");
}"""));
if (currentUri != null) {
return Uri.parse(currentUri);
return Uri.parse(jsStringToDartString(currentUri));
}
throw UnsupportedError("'Uri.base' is not supported");
}

View file

@ -0,0 +1,15 @@
// Copyright (c) 2024, 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, unsafeCast;
import 'dart:_string' show JSStringImpl;
import 'dart:_wasm';
@patch
@pragma('wasm:prefer-inline')
JSStringImpl jsStringFromDartString(String s) => unsafeCast<JSStringImpl>(s);
@patch
@pragma('wasm:prefer-inline')
String jsStringToDartString(JSStringImpl s) => s;

View file

@ -164,6 +164,12 @@
},
"_typed_data": {
"uri": "_internal/wasm/lib/typed_data.dart"
},
"_js_helper": {
"uri": "_internal/wasm/lib/js_helper.dart",
"patches": [
"_internal/wasm/lib/js_helper_patch.dart"
]
}
}
},
@ -212,6 +218,12 @@
},
"_string": {
"uri": "_internal/wasm/lib/js_string.dart"
},
"_js_helper": {
"uri": "_internal/wasm/lib/js_helper.dart",
"patches": [
"_internal/wasm_js_compatibility/lib/js_helper_patch.dart"
]
}
}
},
@ -240,9 +252,6 @@
"_js_annotations": {
"uri": "js/_js_annotations.dart"
},
"_js_helper": {
"uri": "_internal/wasm/lib/js_helper.dart"
},
"_js_string_convert": {
"uri": "_internal/wasm/lib/js_string_convert.dart"
},

View file

@ -149,6 +149,10 @@ wasm:
_internal/wasm/lib/string.dart
_typed_data:
uri: _internal/wasm/lib/typed_data.dart
_js_helper:
uri: _internal/wasm/lib/js_helper.dart
patches:
- _internal/wasm/lib/js_helper_patch.dart
wasm_js_compatibility:
include:
@ -186,6 +190,10 @@ wasm_js_compatibility:
- _internal/wasm_js_compatibility/lib/typed_data_patch.dart
_string:
uri: _internal/wasm/lib/js_string.dart
_js_helper:
uri: _internal/wasm/lib/js_helper.dart
patches:
- _internal/wasm_js_compatibility/lib/js_helper_patch.dart
wasm_common:
libraries:
@ -205,8 +213,6 @@ wasm_common:
- _internal/wasm/lib/internal_patch.dart
_js_annotations:
uri: js/_js_annotations.dart
_js_helper:
uri: _internal/wasm/lib/js_helper.dart
_js_string_convert:
uri: _internal/wasm/lib/js_string_convert.dart
_js_types: