[dart2wasm] Use only JS strings in JSCM

Refactor libraries so that JSCM will only use `JSStringImpl` class for
strings.

The goal is to disentangle the native string classes and `JSStringImpl`
and start testing `JSStringImpl` in isolation.

Changes:

- `dart:_string` is no longer available in JSCM.

- Make `int.toString` external to allow patching it differently in JSCM
  and normal modes.

  `toString` implementations are in `boxed_int_to_string.dart` patch
  files.

- `int.parse` now uses JS `parseInt`. However `parseInt` is not
  compatible with Dart's `int.parse` so this will cause some more test
  failures in JSCM for now.

- Any dependencies to `dart:_string` from JSCM `dart:convert` are
  removed. The library implementation now uses JS `TextDecoder` for
  UTF-8 decoding.

  Note: `TextDecoder` is not available on d8, so text decoding tests
  will fail on d8.

  JSON encoding and decoding in `dart:convert` will be updated in a
  follow-up CL.

- Compiler (translator, constant generator, code generator etc.) is
  updated to allocate the `JSStringImpl`s in JSCM.

Initially this will make some JSCM test fail as `int` parsing is not
quite right, those will be fixed in follow-up CLs.

Co-authored-by: Joshua Litt <joshualitt@google.com>
Change-Id: I366e06f44cdc369d28fe47b24015234260304399
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/332680
Reviewed-by: Aske Simon Christensen <askesc@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
This commit is contained in:
Ömer Sinan Ağacan 2023-10-31 12:58:26 +00:00 committed by Commit Queue
parent acad65713c
commit 9ed368c2e1
26 changed files with 920 additions and 908 deletions

View file

@ -54,6 +54,7 @@ class FieldIndex {
static const syncStarIteratorCurrent = 3;
static const syncStarIteratorYieldStarIterable = 4;
static const recordFieldBase = 2;
static const jsStringImplRef = 2;
static void validate(Translator translator) {
void check(Class cls, String name, int expectedIndex) {
@ -82,8 +83,10 @@ class FieldIndex {
check(translator.boxedBoolClass, "value", FieldIndex.boxValue);
check(translator.boxedIntClass, "value", FieldIndex.boxValue);
check(translator.boxedDoubleClass, "value", FieldIndex.boxValue);
if (!translator.options.jsCompatibility) {
check(translator.oneByteStringClass, "_array", FieldIndex.stringArray);
check(translator.twoByteStringClass, "_array", FieldIndex.stringArray);
}
check(translator.listBaseClass, "_length", FieldIndex.listLength);
check(translator.listBaseClass, "_data", FieldIndex.listArray);
check(translator.hashFieldBaseClass, "_indexNullable",
@ -259,6 +262,7 @@ class ClassInfoCollector {
/// These types switch from properly reified non-masquerading types in regular
/// Dart2Wasm mode to masquerading types in js compatibility mode.
final Set<String> jsCompatibilityTypes = {
"JSStringImpl",
"JSArrayBufferImpl",
"JSArrayBufferViewImpl",
"JSDataViewImpl",
@ -284,7 +288,6 @@ class ClassInfoCollector {
final jsTypesLibraryIndex =
LibraryIndex(translator.component, ["dart:_js_types"]);
final neverMasquerades = [
"JSStringImpl",
if (!translator.options.jsCompatibility) ...jsCompatibilityTypes,
]
.map((name) => jsTypesLibraryIndex.tryGetClass("dart:_js_types", name))
@ -344,7 +347,9 @@ class ClassInfoCollector {
ClassInfo superInfo = cls == translator.coreTypes.boolClass ||
cls == translator.coreTypes.numClass
? topInfo
: cls == translator.stringBaseClass || cls == translator.typeClass
: (!translator.options.jsCompatibility &&
cls == translator.stringBaseClass) ||
cls == translator.typeClass
? translator.classInfo[cls.implementedTypes.single.classNode]!
: translator.classInfo[superclass]!;

View file

@ -2837,8 +2837,9 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
node.expressions,
InterfaceType(
translator.coreTypes.stringClass, Nullability.nonNullable));
return translator
.outputOrVoid(call(translator.stringInterpolate.reference));
return translator.outputOrVoid(call(translator.options.jsCompatibility
? translator.jsStringInterpolate.reference
: translator.stringInterpolate.reference));
}
@override
@ -3711,7 +3712,9 @@ class SwitchInfo {
.classInfo[translator.coreTypes.stringClass]!.repr.nonNullableType;
nullableType = translator
.classInfo[translator.coreTypes.stringClass]!.repr.nullableType;
compare = () => codeGen.call(translator.stringEquals.reference);
compare = () => codeGen.call(translator.options.jsCompatibility
? translator.jsStringEquals.reference
: translator.stringEquals.reference);
} else {
// Object switch
nonNullableType = translator.topInfo.nonNullableType;

View file

@ -151,7 +151,9 @@ Future<CompilerOutput?> compileToModule(compiler.CompilerOptions options,
}
final wasmModule = translator.translate();
String jsRuntime =
jsRuntimeFinalizer.generate(translator.functions.translatedProcedures);
String jsRuntime = jsRuntimeFinalizer.generate(
translator.functions.translatedProcedures,
translator.internalizedStringsForJSRuntime,
mode);
return CompilerOutput(wasmModule, jsRuntime);
}

View file

@ -322,6 +322,15 @@ class ConstantCreator extends ConstantVisitor<ConstantInfo?>
@override
ConstantInfo? visitStringConstant(StringConstant constant) {
if (translator.options.jsCompatibility) {
ClassInfo info = translator.classInfo[translator.jsStringClass]!;
return createConstant(constant, info.nonNullableType, (function, b) {
b.i32_const(info.classId);
b.i32_const(initialIdentityHash);
b.global_get(translator.getInternalizedStringGlobal(constant.value));
b.struct_new(info.struct);
});
}
bool isOneByte = constant.value.codeUnits.every((c) => c <= 255);
ClassInfo info = translator.classInfo[isOneByte
? translator.oneByteStringClass
@ -885,8 +894,8 @@ class ConstantCreator extends ConstantVisitor<ConstantInfo?>
ConstantInfo? visitSymbolConstant(SymbolConstant constant) {
ClassInfo info = translator.classInfo[translator.symbolClass]!;
translator.functions.allocateClass(info.classId);
w.RefType stringType =
translator.classInfo[translator.coreTypes.stringClass]!.nonNullableType;
w.RefType stringType = translator
.classInfo[translator.coreTypes.stringClass]!.repr.nonNullableType;
StringConstant nameConstant = StringConstant(constant.name);
bool lazy = ensureConstant(nameConstant)?.isLazy ?? false;
return createConstant(constant, info.nonNullableType, lazy: lazy,

View file

@ -14,6 +14,10 @@ let buildArgsList;
// This function returns a promise to the instantiated module.
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 = '';
@ -49,7 +53,20 @@ export const instantiate = async (modulePromise, importObjectPromise) => {
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'''
// Converts a Dart List to a JS array. Any Dart objects will be converted, but
// this will be cheap for JSValues.
function arrayFromDartList(constructor, list) {
@ -84,11 +101,15 @@ export const instantiate = async (modulePromise, importObjectPromise) => {
// We break inside the 'dart2wasm' object to enable injection of methods. We
// could use interpolation, but then we'd have to escape characters.
const jsRuntimeBlobPart2 = r'''
const jsRuntimeBlobPart4 = r'''
};
const baseImports = {
dart2wasm: dart2wasm,
''';
// We break inside of `baseImports` to inject internalized strings.
const jsRuntimeBlobPart5 = r'''
Math: Math,
Date: Date,
Object: Object,

View file

@ -8,10 +8,13 @@ import 'package:_js_interop_checks/src/transformations/static_interop_class_eras
import 'package:dart2wasm/js/interop_transformer.dart';
import 'package:dart2wasm/js/method_collector.dart';
import 'package:dart2wasm/js/runtime_blob.dart';
import 'package:dart2wasm/target.dart' as wasm_target;
import 'package:kernel/ast.dart';
import 'package:kernel/class_hierarchy.dart';
import 'package:kernel/core_types.dart';
import 'dart:convert' show json;
JSMethods _performJSInteropTransformations(
Component component,
CoreTypes coreTypes,
@ -46,7 +49,10 @@ class RuntimeFinalizer {
RuntimeFinalizer(this.allJSMethods);
String generate(Iterable<Procedure> translatedProcedures) {
String generate(Iterable<Procedure> translatedProcedures,
List<String> constantStrings, wasm_target.Mode mode) {
String escape(String s) => json.encode(s);
Set<Procedure> usedProcedures = {};
List<String> usedJSMethods = [];
for (Procedure p in translatedProcedures) {
@ -54,10 +60,22 @@ class RuntimeFinalizer {
usedJSMethods.add(allJSMethods[p]!);
}
}
String internalizedStrings = '';
if (constantStrings.isNotEmpty) {
internalizedStrings = '''
s: [
${constantStrings.map(escape).join(',\n')}
],''';
}
return '''
$jsRuntimeBlobPart1
${mode == wasm_target.Mode.jsCompatibility ? jsRuntimeBlobPart2JSCM : jsRuntimeBlobPart2Regular}
$jsRuntimeBlobPart3
${usedJSMethods.join(',\n')}
$jsRuntimeBlobPart2
$jsRuntimeBlobPart4
$internalizedStrings
$jsRuntimeBlobPart5
''';
}
}

View file

@ -12,18 +12,24 @@ mixin KernelNodes {
late final LibraryIndex index = LibraryIndex(component, [
"dart:_internal",
"dart:_js_helper",
"dart:_js_types",
"dart:_string",
"dart:_wasm",
"dart:async",
"dart:collection",
"dart:core",
"dart:ffi",
"dart:typed_data",
"dart:_string",
"dart:_wasm",
]);
// dart:_internal classes
late final Class symbolClass = index.getClass("dart:_internal", "Symbol");
// dart:_js_types classes
late final Class jsStringClass =
index.getClass("dart:_js_types", "JSStringImpl");
// dart:collection classes
late final Class hashFieldBaseClass =
index.getClass("dart:collection", "_HashFieldBase");
@ -144,6 +150,18 @@ mixin KernelNodes {
late final Procedure checkLibraryIsLoaded =
index.getTopLevelProcedure("dart:_internal", "checkLibraryIsLoaded");
// dart:_js_helper procedures
late final Procedure getInternalizedString =
index.getTopLevelProcedure("dart:_js_helper", "getInternalizedString");
late final Procedure areEqualInJS =
index.getTopLevelProcedure("dart:_js_helper", "areEqualInJS");
// dart:_js_types procedures
late final Procedure jsStringEquals =
index.getProcedure("dart:_js_types", "JSStringImpl", "==");
late final Procedure jsStringInterpolate =
index.getProcedure("dart:_js_types", "JSStringImpl", "interpolate");
// dart:collection procedures
late final Procedure mapFactory =
index.getProcedure("dart:collection", "LinkedHashMap", "_default");

View file

@ -102,6 +102,7 @@ class WasmTarget extends Target {
Class? _wasmImmutableSet;
Class? _oneByteString;
Class? _twoByteString;
Class? _jsString;
Class? _closure;
Map<String, Class>? _nativeClasses;
@ -141,12 +142,11 @@ class WasmTarget extends Target {
TargetFlags get flags => TargetFlags();
@override
List<String> get extraRequiredLibraries => const <String>[
List<String> get extraRequiredLibraries => [
'dart:_http',
'dart:_internal',
'dart:_js_helper',
'dart:_js_types',
'dart:_string',
'dart:_wasm',
'dart:async',
'dart:developer',
@ -158,19 +158,20 @@ class WasmTarget extends Target {
'dart:js_util',
'dart:nativewrappers',
'dart:typed_data',
if (mode != Mode.jsCompatibility) 'dart:_string',
];
@override
List<String> get extraIndexedLibraries => const <String>[
List<String> get extraIndexedLibraries => [
'dart:_js_helper',
'dart:_js_types',
'dart:_string',
'dart:_wasm',
'dart:collection',
'dart:js_interop',
'dart:js_interop_unsafe',
'dart:js_util',
'dart:typed_data',
if (mode != Mode.jsCompatibility) 'dart:_string',
];
@override
@ -462,6 +463,11 @@ class WasmTarget extends Target {
@override
Class concreteStringLiteralClass(CoreTypes coreTypes, String value) {
// In JSCM all strings are JS strings.
if (mode == Mode.jsCompatibility) {
return _jsString ??=
coreTypes.index.getClass("dart:_js_types", "JSStringImpl");
}
const int maxLatin1 = 0xff;
for (int i = 0; i < value.length; ++i) {
if (value.codeUnitAt(i) > maxLatin1) {

View file

@ -84,6 +84,10 @@ class Translator with KernelNodes {
/// [ClassInfoCollector].
final Map<Class, ClassInfo> classInfo = {};
/// Internalized strings to move to the JS runtime
final List<String> internalizedStringsForJSRuntime = [];
final Map<String, w.Global> _internalizedStringGlobals = {};
final Map<w.HeapType, ClassInfo> classForHeapType = {};
final Map<Field, int> fieldIndex = {};
final Map<TypeParameter, int> typeParameterIndex = {};
@ -161,8 +165,11 @@ class Translator with KernelNodes {
boxedIntClass: boxedIntClass,
boxedDoubleClass: boxedDoubleClass,
boxedBoolClass: coreTypes.boolClass,
if (!options.jsCompatibility) ...{
oneByteStringClass: stringBaseClass,
twoByteStringClass: stringBaseClass,
twoByteStringClass: stringBaseClass
},
if (options.jsCompatibility) ...{jsStringClass: jsStringClass},
};
/// Type for vtable entries for dynamic calls. These entries are used in
@ -1026,6 +1033,19 @@ class Translator with KernelNodes {
ClassInfo getRecordClassInfo(RecordType recordType) =>
classInfo[recordClasses[RecordShape.fromType(recordType)]!]!;
w.Global getInternalizedStringGlobal(String s) {
w.Global? internalizedString = _internalizedStringGlobals[s];
if (internalizedString != null) {
return internalizedString;
}
final i = internalizedStringsForJSRuntime.length;
internalizedString = m.globals.import('s', '$i',
w.GlobalType(w.RefType.extern(nullable: true), mutable: false));
_internalizedStringGlobals[s] = internalizedString;
internalizedStringsForJSRuntime.add(s);
return internalizedString;
}
}
abstract class _FunctionGenerator {

View file

@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
import 'dart:_internal';
import 'dart:_string';
@pragma("wasm:entry-point")
final class _BoxedInt extends int {
@ -290,7 +289,7 @@ final class _BoxedInt extends int {
return this.toDouble().toStringAsPrecision(precision);
}
String toRadixString(int radix) => _intToRadixString(value, radix);
external String toRadixString(int radix);
// Returns pow(this, e) % m.
int modPow(int e, int m) {
@ -421,268 +420,7 @@ final class _BoxedInt extends int {
external int get bitLength;
@override
String toString() => _intToString(value);
}
const _digits = "0123456789abcdefghijklmnopqrstuvwxyz";
String _intToRadixString(int value, int radix) {
if (radix < 2 || 36 < radix) {
throw new RangeError.range(radix, 2, 36, "radix");
}
if (radix & (radix - 1) == 0) {
return _toPow2String(value, radix);
}
if (radix == 10) return _intToString(value);
final bool isNegative = value < 0;
value = isNegative ? -value : value;
if (value < 0) {
// With int limited to 64 bits, the value
// MIN_INT64 = -0x8000000000000000 overflows at negation:
// -MIN_INT64 == MIN_INT64, so it requires special handling.
return _minInt64ToRadixString(value, radix);
}
var temp = <int>[];
do {
int digit = value % radix;
value ~/= radix;
temp.add(_digits.codeUnitAt(digit));
} while (value > 0);
if (isNegative) temp.add(0x2d); // '-'.
final string = OneByteString.withLength(temp.length);
for (int i = 0, j = temp.length; j > 0; i++) {
writeIntoOneByteString(string, i, temp[--j]);
}
return string;
}
String _toPow2String(int value, int radix) {
if (value == 0) return "0";
assert(radix & (radix - 1) == 0);
var negative = value < 0;
var bitsPerDigit = radix.bitLength - 1;
var length = 0;
if (negative) {
value = -value;
length = 1;
if (value < 0) {
// With int limited to 64 bits, the value
// MIN_INT64 = -0x8000000000000000 overflows at negation:
// -MIN_INT64 == MIN_INT64, so it requires special handling.
return _minInt64ToRadixString(value, radix);
}
}
// Integer division, rounding up, to find number of _digits.
length += (value.bitLength + bitsPerDigit - 1) ~/ bitsPerDigit;
final string = OneByteString.withLength(length);
writeIntoOneByteString(
string, 0, 0x2d); // '-'. Is overwritten if not negative.
var mask = radix - 1;
do {
writeIntoOneByteString(string, --length, _digits.codeUnitAt(value & mask));
value >>= bitsPerDigit;
} while (value > 0);
return string;
}
/// Converts negative value to radix string.
/// This method is only used to handle corner case of
/// MIN_INT64 = -0x8000000000000000.
String _minInt64ToRadixString(int value, int radix) {
var temp = <int>[];
assert(value < 0);
do {
int digit = -unsafeCast<int>(value.remainder(radix));
value ~/= radix;
temp.add(_digits.codeUnitAt(digit));
} while (value != 0);
temp.add(0x2d); // '-'.
final string = OneByteString.withLength(temp.length);
for (int i = 0, j = temp.length; j > 0; i++) {
writeIntoOneByteString(string, i, temp[--j]);
}
return string;
}
/**
* The digits of '00', '01', ... '99' as a single array.
*
* Get the digits of `n`, with `0 <= n < 100`, as
* `_digitTable[n * 2]` and `_digitTable[n * 2 + 1]`.
*/
const _digitTable = const [
0x30, 0x30, 0x30, 0x31, 0x30, 0x32, 0x30, 0x33, //
0x30, 0x34, 0x30, 0x35, 0x30, 0x36, 0x30, 0x37, //
0x30, 0x38, 0x30, 0x39, 0x31, 0x30, 0x31, 0x31, //
0x31, 0x32, 0x31, 0x33, 0x31, 0x34, 0x31, 0x35, //
0x31, 0x36, 0x31, 0x37, 0x31, 0x38, 0x31, 0x39, //
0x32, 0x30, 0x32, 0x31, 0x32, 0x32, 0x32, 0x33, //
0x32, 0x34, 0x32, 0x35, 0x32, 0x36, 0x32, 0x37, //
0x32, 0x38, 0x32, 0x39, 0x33, 0x30, 0x33, 0x31, //
0x33, 0x32, 0x33, 0x33, 0x33, 0x34, 0x33, 0x35, //
0x33, 0x36, 0x33, 0x37, 0x33, 0x38, 0x33, 0x39, //
0x34, 0x30, 0x34, 0x31, 0x34, 0x32, 0x34, 0x33, //
0x34, 0x34, 0x34, 0x35, 0x34, 0x36, 0x34, 0x37, //
0x34, 0x38, 0x34, 0x39, 0x35, 0x30, 0x35, 0x31, //
0x35, 0x32, 0x35, 0x33, 0x35, 0x34, 0x35, 0x35, //
0x35, 0x36, 0x35, 0x37, 0x35, 0x38, 0x35, 0x39, //
0x36, 0x30, 0x36, 0x31, 0x36, 0x32, 0x36, 0x33, //
0x36, 0x34, 0x36, 0x35, 0x36, 0x36, 0x36, 0x37, //
0x36, 0x38, 0x36, 0x39, 0x37, 0x30, 0x37, 0x31, //
0x37, 0x32, 0x37, 0x33, 0x37, 0x34, 0x37, 0x35, //
0x37, 0x36, 0x37, 0x37, 0x37, 0x38, 0x37, 0x39, //
0x38, 0x30, 0x38, 0x31, 0x38, 0x32, 0x38, 0x33, //
0x38, 0x34, 0x38, 0x35, 0x38, 0x36, 0x38, 0x37, //
0x38, 0x38, 0x38, 0x39, 0x39, 0x30, 0x39, 0x31, //
0x39, 0x32, 0x39, 0x33, 0x39, 0x34, 0x39, 0x35, //
0x39, 0x36, 0x39, 0x37, 0x39, 0x38, 0x39, 0x39, //
];
/**
* Result of int.toString for -99, -98, ..., 98, 99.
*/
const _smallLookupTable = const [
"-99", "-98", "-97", "-96", "-95", "-94", "-93", "-92", "-91", "-90", //
"-89", "-88", "-87", "-86", "-85", "-84", "-83", "-82", "-81", "-80", //
"-79", "-78", "-77", "-76", "-75", "-74", "-73", "-72", "-71", "-70", //
"-69", "-68", "-67", "-66", "-65", "-64", "-63", "-62", "-61", "-60", //
"-59", "-58", "-57", "-56", "-55", "-54", "-53", "-52", "-51", "-50", //
"-49", "-48", "-47", "-46", "-45", "-44", "-43", "-42", "-41", "-40", //
"-39", "-38", "-37", "-36", "-35", "-34", "-33", "-32", "-31", "-30", //
"-29", "-28", "-27", "-26", "-25", "-24", "-23", "-22", "-21", "-20", //
"-19", "-18", "-17", "-16", "-15", "-14", "-13", "-12", "-11", "-10", //
"-9", "-8", "-7", "-6", "-5", "-4", "-3", "-2", "-1", "0", //
"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", //
"11", "12", "13", "14", "15", "16", "17", "18", "19", "20", //
"21", "22", "23", "24", "25", "26", "27", "28", "29", "30", //
"31", "32", "33", "34", "35", "36", "37", "38", "39", "40", //
"41", "42", "43", "44", "45", "46", "47", "48", "49", "50", //
"51", "52", "53", "54", "55", "56", "57", "58", "59", "60", //
"61", "62", "63", "64", "65", "66", "67", "68", "69", "70", //
"71", "72", "73", "74", "75", "76", "77", "78", "79", "80", //
"81", "82", "83", "84", "85", "86", "87", "88", "89", "90", //
"91", "92", "93", "94", "95", "96", "97", "98", "99" //
];
// Powers of 10 above 1000000 are indistinguishable by eye.
const int _POW_10_7 = 10000000;
const int _POW_10_8 = 100000000;
const int _POW_10_9 = 1000000000;
// Find the number of decimal digits in a positive smi.
// Never called with numbers < 100. These are handled before calling.
int _positiveBase10Length(int smi) {
// A positive smi has length <= 19 if 63-bit, <=10 if 31-bit.
// Avoid comparing a 31-bit smi to a non-smi.
if (smi < 1000) return 3;
if (smi < 10000) return 4;
if (smi < _POW_10_7) {
if (smi < 100000) return 5;
if (smi < 1000000) return 6;
return 7;
}
if (smi < _POW_10_8) return 8;
if (smi < _POW_10_9) return 9;
smi = smi ~/ _POW_10_9;
// Handle numbers < 100 before calling recursively.
if (smi < 10) return 10;
if (smi < 100) return 11;
return 9 + _positiveBase10Length(smi);
}
String _intToString(int value) {
if (value < 100 && value > -100) {
// Issue(https://dartbug.com/39639): The analyzer incorrectly reports the
// result type as `num`.
return _smallLookupTable[value + 99];
}
if (value < 0) return _negativeToString(value);
// Inspired by Andrei Alexandrescu: "Three Optimization Tips for C++"
// Avoid expensive remainder operation by doing it on more than
// one digit at a time.
const int DIGIT_ZERO = 0x30;
int length = _positiveBase10Length(value);
final result = OneByteString.withLength(length);
int index = length - 1;
int smi = value;
do {
// Two digits at a time.
final int twoDigits = smi.remainder(100);
smi = smi ~/ 100;
int digitIndex = twoDigits * 2;
writeIntoOneByteString(result, index, _digitTable[digitIndex + 1]);
writeIntoOneByteString(result, index - 1, _digitTable[digitIndex]);
index -= 2;
} while (smi >= 100);
if (smi < 10) {
// Character code for '0'.
// Issue(https://dartbug.com/39639): The analyzer incorrectly reports the
// result type as `num`.
writeIntoOneByteString(result, index, DIGIT_ZERO + smi);
} else {
// No remainder for this case.
// Issue(https://dartbug.com/39639): The analyzer incorrectly reports the
// result type as `num`.
int digitIndex = smi * 2;
writeIntoOneByteString(result, index, _digitTable[digitIndex + 1]);
writeIntoOneByteString(result, index - 1, _digitTable[digitIndex]);
}
return result;
}
// Find the number of decimal digits in a negative smi.
// Never called with numbers > -100. These are handled before calling.
int _negativeBase10Length(int negSmi) {
// A negative smi has length <= 19 if 63-bit, <=10 if 31-bit.
// Avoid comparing a 31-bit smi to a non-smi.
if (negSmi > -1000) return 3;
if (negSmi > -10000) return 4;
if (negSmi > -_POW_10_7) {
if (negSmi > -100000) return 5;
if (negSmi > -1000000) return 6;
return 7;
}
if (negSmi > -_POW_10_8) return 8;
if (negSmi > -_POW_10_9) return 9;
negSmi = negSmi ~/ _POW_10_9;
// Handle numbers > -100 before calling recursively.
if (negSmi > -10) return 10;
if (negSmi > -100) return 11;
return 9 + _negativeBase10Length(negSmi);
}
// Convert a negative smi to a string.
// Doesn't negate the smi to avoid negating the most negative smi, which
// would become a non-smi.
String _negativeToString(int negSmi) {
// Character code for '-'
const int MINUS_SIGN = 0x2d;
// Character code for '0'.
const int DIGIT_ZERO = 0x30;
// Number of digits, not including minus.
int digitCount = _negativeBase10Length(negSmi);
final result = OneByteString.withLength(digitCount + 1);
writeIntoOneByteString(result, 0, MINUS_SIGN); // '-'.
int index = digitCount;
do {
int twoDigits = unsafeCast<int>(negSmi.remainder(100));
negSmi = negSmi ~/ 100;
int digitIndex = -twoDigits * 2;
writeIntoOneByteString(result, index, _digitTable[digitIndex + 1]);
writeIntoOneByteString(result, index - 1, _digitTable[digitIndex]);
index -= 2;
} while (negSmi <= -100);
if (negSmi > -10) {
writeIntoOneByteString(result, index, DIGIT_ZERO - negSmi);
} else {
// No remainder necessary for this case.
int digitIndex = -negSmi * 2;
writeIntoOneByteString(result, index, _digitTable[digitIndex + 1]);
writeIntoOneByteString(result, index - 1, _digitTable[digitIndex]);
}
return result;
external String toString();
}
int _intHashCode(int value) {

View file

@ -0,0 +1,276 @@
// Copyright (c) 2023, 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';
import 'dart:_string';
@patch
class _BoxedInt {
@patch
String toRadixString(int radix) => _intToRadixString(value, radix);
@patch
String toString() => _intToString(value);
}
const _digits = "0123456789abcdefghijklmnopqrstuvwxyz";
String _intToRadixString(int value, int radix) {
if (radix < 2 || 36 < radix) {
throw new RangeError.range(radix, 2, 36, "radix");
}
if (radix & (radix - 1) == 0) {
return _toPow2String(value, radix);
}
if (radix == 10) return _intToString(value);
final bool isNegative = value < 0;
value = isNegative ? -value : value;
if (value < 0) {
// With int limited to 64 bits, the value
// MIN_INT64 = -0x8000000000000000 overflows at negation:
// -MIN_INT64 == MIN_INT64, so it requires special handling.
return _minInt64ToRadixString(value, radix);
}
var temp = <int>[];
do {
int digit = value % radix;
value ~/= radix;
temp.add(_digits.codeUnitAt(digit));
} while (value > 0);
if (isNegative) temp.add(0x2d); // '-'.
final string = OneByteString.withLength(temp.length);
for (int i = 0, j = temp.length; j > 0; i++) {
writeIntoOneByteString(string, i, temp[--j]);
}
return string;
}
String _toPow2String(int value, int radix) {
if (value == 0) return "0";
assert(radix & (radix - 1) == 0);
var negative = value < 0;
var bitsPerDigit = radix.bitLength - 1;
var length = 0;
if (negative) {
value = -value;
length = 1;
if (value < 0) {
// With int limited to 64 bits, the value
// MIN_INT64 = -0x8000000000000000 overflows at negation:
// -MIN_INT64 == MIN_INT64, so it requires special handling.
return _minInt64ToRadixString(value, radix);
}
}
// Integer division, rounding up, to find number of _digits.
length += (value.bitLength + bitsPerDigit - 1) ~/ bitsPerDigit;
final string = OneByteString.withLength(length);
writeIntoOneByteString(
string, 0, 0x2d); // '-'. Is overwritten if not negative.
var mask = radix - 1;
do {
writeIntoOneByteString(string, --length, _digits.codeUnitAt(value & mask));
value >>= bitsPerDigit;
} while (value > 0);
return string;
}
/// Converts negative value to radix string.
/// This method is only used to handle corner case of
/// MIN_INT64 = -0x8000000000000000.
String _minInt64ToRadixString(int value, int radix) {
var temp = <int>[];
assert(value < 0);
do {
int digit = -unsafeCast<int>(value.remainder(radix));
value ~/= radix;
temp.add(_digits.codeUnitAt(digit));
} while (value != 0);
temp.add(0x2d); // '-'.
final string = OneByteString.withLength(temp.length);
for (int i = 0, j = temp.length; j > 0; i++) {
writeIntoOneByteString(string, i, temp[--j]);
}
return string;
}
/**
* The digits of '00', '01', ... '99' as a single array.
*
* Get the digits of `n`, with `0 <= n < 100`, as
* `_digitTable[n * 2]` and `_digitTable[n * 2 + 1]`.
*/
const _digitTable = const [
0x30, 0x30, 0x30, 0x31, 0x30, 0x32, 0x30, 0x33, //
0x30, 0x34, 0x30, 0x35, 0x30, 0x36, 0x30, 0x37, //
0x30, 0x38, 0x30, 0x39, 0x31, 0x30, 0x31, 0x31, //
0x31, 0x32, 0x31, 0x33, 0x31, 0x34, 0x31, 0x35, //
0x31, 0x36, 0x31, 0x37, 0x31, 0x38, 0x31, 0x39, //
0x32, 0x30, 0x32, 0x31, 0x32, 0x32, 0x32, 0x33, //
0x32, 0x34, 0x32, 0x35, 0x32, 0x36, 0x32, 0x37, //
0x32, 0x38, 0x32, 0x39, 0x33, 0x30, 0x33, 0x31, //
0x33, 0x32, 0x33, 0x33, 0x33, 0x34, 0x33, 0x35, //
0x33, 0x36, 0x33, 0x37, 0x33, 0x38, 0x33, 0x39, //
0x34, 0x30, 0x34, 0x31, 0x34, 0x32, 0x34, 0x33, //
0x34, 0x34, 0x34, 0x35, 0x34, 0x36, 0x34, 0x37, //
0x34, 0x38, 0x34, 0x39, 0x35, 0x30, 0x35, 0x31, //
0x35, 0x32, 0x35, 0x33, 0x35, 0x34, 0x35, 0x35, //
0x35, 0x36, 0x35, 0x37, 0x35, 0x38, 0x35, 0x39, //
0x36, 0x30, 0x36, 0x31, 0x36, 0x32, 0x36, 0x33, //
0x36, 0x34, 0x36, 0x35, 0x36, 0x36, 0x36, 0x37, //
0x36, 0x38, 0x36, 0x39, 0x37, 0x30, 0x37, 0x31, //
0x37, 0x32, 0x37, 0x33, 0x37, 0x34, 0x37, 0x35, //
0x37, 0x36, 0x37, 0x37, 0x37, 0x38, 0x37, 0x39, //
0x38, 0x30, 0x38, 0x31, 0x38, 0x32, 0x38, 0x33, //
0x38, 0x34, 0x38, 0x35, 0x38, 0x36, 0x38, 0x37, //
0x38, 0x38, 0x38, 0x39, 0x39, 0x30, 0x39, 0x31, //
0x39, 0x32, 0x39, 0x33, 0x39, 0x34, 0x39, 0x35, //
0x39, 0x36, 0x39, 0x37, 0x39, 0x38, 0x39, 0x39, //
];
/**
* Result of int.toString for -99, -98, ..., 98, 99.
*/
const _smallLookupTable = const [
"-99", "-98", "-97", "-96", "-95", "-94", "-93", "-92", "-91", "-90", //
"-89", "-88", "-87", "-86", "-85", "-84", "-83", "-82", "-81", "-80", //
"-79", "-78", "-77", "-76", "-75", "-74", "-73", "-72", "-71", "-70", //
"-69", "-68", "-67", "-66", "-65", "-64", "-63", "-62", "-61", "-60", //
"-59", "-58", "-57", "-56", "-55", "-54", "-53", "-52", "-51", "-50", //
"-49", "-48", "-47", "-46", "-45", "-44", "-43", "-42", "-41", "-40", //
"-39", "-38", "-37", "-36", "-35", "-34", "-33", "-32", "-31", "-30", //
"-29", "-28", "-27", "-26", "-25", "-24", "-23", "-22", "-21", "-20", //
"-19", "-18", "-17", "-16", "-15", "-14", "-13", "-12", "-11", "-10", //
"-9", "-8", "-7", "-6", "-5", "-4", "-3", "-2", "-1", "0", //
"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", //
"11", "12", "13", "14", "15", "16", "17", "18", "19", "20", //
"21", "22", "23", "24", "25", "26", "27", "28", "29", "30", //
"31", "32", "33", "34", "35", "36", "37", "38", "39", "40", //
"41", "42", "43", "44", "45", "46", "47", "48", "49", "50", //
"51", "52", "53", "54", "55", "56", "57", "58", "59", "60", //
"61", "62", "63", "64", "65", "66", "67", "68", "69", "70", //
"71", "72", "73", "74", "75", "76", "77", "78", "79", "80", //
"81", "82", "83", "84", "85", "86", "87", "88", "89", "90", //
"91", "92", "93", "94", "95", "96", "97", "98", "99" //
];
// Powers of 10 above 1000000 are indistinguishable by eye.
const int _POW_10_7 = 10000000;
const int _POW_10_8 = 100000000;
const int _POW_10_9 = 1000000000;
// Find the number of decimal digits in a positive smi.
// Never called with numbers < 100. These are handled before calling.
int _positiveBase10Length(int smi) {
// A positive smi has length <= 19 if 63-bit, <=10 if 31-bit.
// Avoid comparing a 31-bit smi to a non-smi.
if (smi < 1000) return 3;
if (smi < 10000) return 4;
if (smi < _POW_10_7) {
if (smi < 100000) return 5;
if (smi < 1000000) return 6;
return 7;
}
if (smi < _POW_10_8) return 8;
if (smi < _POW_10_9) return 9;
smi = smi ~/ _POW_10_9;
// Handle numbers < 100 before calling recursively.
if (smi < 10) return 10;
if (smi < 100) return 11;
return 9 + _positiveBase10Length(smi);
}
String _intToString(int value) {
if (value < 100 && value > -100) {
// Issue(https://dartbug.com/39639): The analyzer incorrectly reports the
// result type as `num`.
return _smallLookupTable[value + 99];
}
if (value < 0) return _negativeToString(value);
// Inspired by Andrei Alexandrescu: "Three Optimization Tips for C++"
// Avoid expensive remainder operation by doing it on more than
// one digit at a time.
const int DIGIT_ZERO = 0x30;
int length = _positiveBase10Length(value);
final result = OneByteString.withLength(length);
int index = length - 1;
int smi = value;
do {
// Two digits at a time.
final int twoDigits = smi.remainder(100);
smi = smi ~/ 100;
int digitIndex = twoDigits * 2;
writeIntoOneByteString(result, index, _digitTable[digitIndex + 1]);
writeIntoOneByteString(result, index - 1, _digitTable[digitIndex]);
index -= 2;
} while (smi >= 100);
if (smi < 10) {
// Character code for '0'.
// Issue(https://dartbug.com/39639): The analyzer incorrectly reports the
// result type as `num`.
writeIntoOneByteString(result, index, DIGIT_ZERO + smi);
} else {
// No remainder for this case.
// Issue(https://dartbug.com/39639): The analyzer incorrectly reports the
// result type as `num`.
int digitIndex = smi * 2;
writeIntoOneByteString(result, index, _digitTable[digitIndex + 1]);
writeIntoOneByteString(result, index - 1, _digitTable[digitIndex]);
}
return result;
}
// Find the number of decimal digits in a negative smi.
// Never called with numbers > -100. These are handled before calling.
int _negativeBase10Length(int negSmi) {
// A negative smi has length <= 19 if 63-bit, <=10 if 31-bit.
// Avoid comparing a 31-bit smi to a non-smi.
if (negSmi > -1000) return 3;
if (negSmi > -10000) return 4;
if (negSmi > -_POW_10_7) {
if (negSmi > -100000) return 5;
if (negSmi > -1000000) return 6;
return 7;
}
if (negSmi > -_POW_10_8) return 8;
if (negSmi > -_POW_10_9) return 9;
negSmi = negSmi ~/ _POW_10_9;
// Handle numbers > -100 before calling recursively.
if (negSmi > -10) return 10;
if (negSmi > -100) return 11;
return 9 + _negativeBase10Length(negSmi);
}
// Convert a negative smi to a string.
// Doesn't negate the smi to avoid negating the most negative smi, which
// would become a non-smi.
String _negativeToString(int negSmi) {
// Character code for '-'
const int MINUS_SIGN = 0x2d;
// Character code for '0'.
const int DIGIT_ZERO = 0x30;
// Number of digits, not including minus.
int digitCount = _negativeBase10Length(negSmi);
final result = OneByteString.withLength(digitCount + 1);
writeIntoOneByteString(result, 0, MINUS_SIGN); // '-'.
int index = digitCount;
do {
int twoDigits = unsafeCast<int>(negSmi.remainder(100));
negSmi = negSmi ~/ 100;
int digitIndex = -twoDigits * 2;
writeIntoOneByteString(result, index, _digitTable[digitIndex + 1]);
writeIntoOneByteString(result, index - 1, _digitTable[digitIndex]);
index -= 2;
} while (negSmi <= -100);
if (negSmi > -10) {
writeIntoOneByteString(result, index, DIGIT_ZERO - negSmi);
} else {
// No remainder necessary for this case.
int digitIndex = -negSmi * 2;
writeIntoOneByteString(result, index, _digitTable[digitIndex + 1]);
writeIntoOneByteString(result, index - 1, _digitTable[digitIndex]);
}
return result;
}

View file

@ -0,0 +1,30 @@
// Copyright (c) 2023, 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;
@patch
class int {
@patch
external const factory int.fromEnvironment(String name,
{int defaultValue = 0});
/// Wasm i64.div_s instruction.
external int _div_s(int divisor);
/// Wasm i64.le_u instruction.
external bool _le_u(int other);
/// Wasm i64.lt_u instruction.
external bool _lt_u(int other);
/// Wasm i64.shr_s instruction.
external int _shr_s(int shift);
/// Wasm i64.shr_u instruction.
external int _shr_u(int shift);
/// Wasm i64.shl instruction.
external int _shl(int shift);
}

View file

@ -9,10 +9,6 @@ import "dart:typed_data" show Int64List;
@patch
class int {
@patch
external const factory int.fromEnvironment(String name,
{int defaultValue = 0});
static int? _tryParseSmi(String str, int first, int last) {
assert(first <= last);
var ix = first;
@ -298,22 +294,4 @@ class int {
-unsafeCast<int>(_minInt64.remainder(multiplier));
return _int64OverflowLimits[tableIndex];
}
/// Wasm i64.div_s instruction.
external int _div_s(int divisor);
/// Wasm i64.le_u instruction.
external bool _le_u(int other);
/// Wasm i64.lt_u instruction.
external bool _lt_u(int other);
/// Wasm i64.shr_s instruction.
external int _shr_s(int shift);
/// Wasm i64.shr_u instruction.
external int _shr_u(int shift);
/// Wasm i64.shl instruction.
external int _shl(int shift);
}

View file

@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
import "dart:_js_helper" show JS;
import "dart:_string";
part "class_id.dart";
part "deferred.dart";
@ -14,38 +13,6 @@ part "symbol_patch.dart";
@patch
bool typeAcceptsNull<T>() => null is T;
// String accessors used to perform Dart<->JS string conversion
@pragma("wasm:export", "\$stringLength")
double _stringLength(String string) {
return string.length.toDouble();
}
@pragma("wasm:export", "\$stringRead")
double _stringRead(String string, double index) {
return string.codeUnitAt(index.toInt()).toDouble();
}
@pragma("wasm:export", "\$stringAllocate1")
OneByteString _stringAllocate1(double length) {
return OneByteString.withLength(length.toInt());
}
@pragma("wasm:export", "\$stringWrite1")
void _stringWrite1(OneByteString string, double index, double codePoint) {
writeIntoOneByteString(string, index.toInt(), codePoint.toInt());
}
@pragma("wasm:export", "\$stringAllocate2")
TwoByteString _stringAllocate2(double length) {
return TwoByteString.withLength(length.toInt());
}
@pragma("wasm:export", "\$stringWrite2")
void _stringWrite2(TwoByteString string, double index, double codePoint) {
writeIntoTwoByteString(string, index.toInt(), codePoint.toInt());
}
const bool has63BitSmis = false;
class Lists {

View file

@ -11,6 +11,9 @@ class JSArrayImpl implements List<JSAny?> {
JSArrayImpl(this._ref);
factory JSArrayImpl.fromLength(int length) =>
JSArrayImpl(js.newArrayFromLengthRaw(length));
static JSArrayImpl? box(WasmExternRef? ref) =>
js.isDartNull(ref) ? null : JSArrayImpl(ref);

View file

@ -287,6 +287,9 @@ WasmExternRef? newObjectRaw() => JS<WasmExternRef?>('() => ({})');
WasmExternRef? newArrayRaw() => JS<WasmExternRef?>('() => []');
WasmExternRef? newArrayFromLengthRaw(int length) =>
JS<WasmExternRef?>('l => new Array(l)', length.toDouble());
WasmExternRef? globalThisRaw() => JS<WasmExternRef?>('() => globalThis');
WasmExternRef? callConstructorVarArgsRaw(

View file

@ -21,6 +21,20 @@ final class JSStringImpl implements String {
WasmExternRef? get toExternRef => _ref;
@pragma("wasm:entry-point")
static String interpolate(List<Object?> values) {
final array = JSArrayImpl.fromLength(values.length);
for (int i = 0; i < values.length; i++) {
final o = values[i];
final s = o.toString();
final jsString =
s is JSStringImpl ? js.JSValue.boxT<JSAny?>(s.toExternRef) : s.toJS;
array[i] = jsString;
}
return JSStringImpl(
js.JS<WasmExternRef?>("a => a.join('')", array.toExternRef));
}
@override
int codeUnitAt(int index) {
RangeError.checkValueInInterval(index, 0, length - 1);
@ -635,4 +649,33 @@ final class JSStringImpl implements String {
@override
String toString() => js.stringify(toExternRef);
int firstNonWhitespace() {
final len = this.length;
int first = 0;
for (; first < len; first++) {
if (!_isWhitespace(this.codeUnitAt(first))) {
break;
}
}
return first;
}
int lastNonWhitespace() {
int last = this.length - 1;
for (; last >= 0; last--) {
if (!_isWhitespace(this.codeUnitAt(last))) {
break;
}
}
return last;
}
}
@pragma("wasm:export", "\$jsStringToJSStringImpl")
JSStringImpl _jsStringToJSStringImpl(WasmExternRef? string) =>
JSStringImpl(string);
@pragma("wasm:export", "\$jsStringFromJSStringImpl")
WasmExternRef? _jsStringFromJSStringImpl(JSStringImpl string) =>
string.toExternRef;

View file

@ -1381,3 +1381,35 @@ final class TwoByteString extends StringBase {
return offset + length;
}
}
// String accessors used to perform Dart<->JS string conversion
@pragma("wasm:export", "\$stringLength")
double _stringLength(String string) {
return string.length.toDouble();
}
@pragma("wasm:export", "\$stringRead")
double _stringRead(String string, double index) {
return string.codeUnitAt(index.toInt()).toDouble();
}
@pragma("wasm:export", "\$stringAllocate1")
OneByteString _stringAllocate1(double length) {
return OneByteString.withLength(length.toInt());
}
@pragma("wasm:export", "\$stringWrite1")
void _stringWrite1(OneByteString string, double index, double codePoint) {
writeIntoOneByteString(string, index.toInt(), codePoint.toInt());
}
@pragma("wasm:export", "\$stringAllocate2")
TwoByteString _stringAllocate2(double length) {
return TwoByteString.withLength(length.toInt());
}
@pragma("wasm:export", "\$stringWrite2")
void _stringWrite2(TwoByteString string, double index, double codePoint) {
writeIntoTwoByteString(string, index.toInt(), codePoint.toInt());
}

View file

@ -0,0 +1,26 @@
// Copyright (c) 2023, 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';
import 'dart:_js_helper';
import 'dart:_js_types';
import 'dart:_wasm';
@patch
class _BoxedInt {
@patch
String toRadixString(int radix) {
// We could also catch the `_JavaScriptError` here and convert it to
// `RangeError`, but I'm not sure if that would be faster.
if (radix < 2 || 36 < radix) {
throw RangeError.range(radix, 2, 36, "radix");
}
return JSStringImpl(JS<WasmExternRef?>(
'(n, r) => n.toString(r)', toDouble().toExternRef, radix.toDouble()));
}
@patch
String toString() => JSStringImpl(
JS<WasmExternRef?>('(n) => n.toString()', toDouble().toExternRef));
}

View file

@ -1,20 +1,14 @@
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// Copyright (c) 2022, 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.
/// Note: the VM concatenates all patch files into a single patch file. This
/// file is the first patch in "dart:convert" which contains all the imports
/// used by patches of that library. We plan to change this when we have a
/// shared front end and simply use parts.
import "dart:_internal" show ClassID, patch, POWERS_OF_TEN, unsafeCast;
import "dart:_js_types";
import 'dart:_wasm';
import 'dart:_js_helper' as js;
import "dart:_internal" show patch, POWERS_OF_TEN, unsafeCast;
import "dart:_string";
import "dart:typed_data" show Uint8List, Uint16List;
/// This patch library has no additional parts.
// JSON conversion.
@patch
dynamic _parseJson(
String source, Object? Function(Object? key, Object? value)? reviver) {
@ -29,43 +23,100 @@ dynamic _parseJson(
@patch
class Utf8Decoder {
// Always fall back to the Dart implementation for strings shorter than this
// threshold, as there is a large, constant overhead for using TextDecoder.
// TODO(omersa): This is copied from dart2js runtime, make sure the value is
// right for dart2wasm.
static const int _shortInputThreshold = 15;
@patch
Converter<List<int>, T> fuse<T>(Converter<String, T> next) {
if (next is JsonDecoder) {
return new _JsonUtf8Decoder(
(next as JsonDecoder)._reviver, this._allowMalformed)
as dynamic/*=Converter<List<int>, T>*/;
}
// TODO(lrn): Recognize a fused decoder where the next step is JsonDecoder.
return super.fuse<T>(next);
return super.fuse(next);
}
// Allow intercepting of UTF-8 decoding when built-in lists are passed.
@patch
static String? _convertIntercepted(
bool allowMalformed, List<int> codeUnits, int start, int? end) {
if (codeUnits is JSUint8ArrayImpl) {
final JSUint8ArrayImpl jsCodeUnits = codeUnits;
end ??= jsCodeUnits.length;
if (end - start < _shortInputThreshold) {
return null;
}
return _convertInterceptedUint8List(
allowMalformed, jsCodeUnits, start, end);
}
return null; // This call was not intercepted.
}
static String? _convertInterceptedUint8List(
bool allowMalformed, JSUint8ArrayImpl codeUnits, int start, int end) {
// TODO(omersa): There's a bug somewhere when compiling lazy statics that
// return `WasmExternRef`?
// final WasmExternRef? decoder = allowMalformed ? _decoderNonfatal : _decoder;
// if (decoder == WasmExternRef.nullRef) {
// return null;
// }
final WasmExternRef? decoder;
try {
decoder = allowMalformed
? js.JS<WasmExternRef?>(
'() => new TextDecoder("utf-8", {fatal: false})')
: js.JS<WasmExternRef?>(
'() => new TextDecoder("utf-8", {fatal: true})');
} catch (e) {
return null;
}
class _JsonUtf8Decoder extends Converter<List<int>, Object?> {
final Object? Function(Object? key, Object? value)? _reviver;
final bool _allowMalformed;
_JsonUtf8Decoder(this._reviver, this._allowMalformed);
Object? convert(List<int> input) {
var parser = _JsonUtf8DecoderSink._createParser(_reviver, _allowMalformed);
parser.chunk = input;
parser.chunkEnd = input.length;
parser.parse(0);
parser.close();
return parser.result;
if (0 == start && end == codeUnits.length) {
return _useTextDecoder(decoder, codeUnits.toExternRef);
}
final length = codeUnits.length;
end = RangeError.checkValidRange(start, end, length);
return _useTextDecoder(
decoder,
js.JS<WasmExternRef?>(
'(codeUnits, start, end) => codeUnits.subarray(start, end)',
codeUnits.toExternRef,
start.toDouble,
end.toDouble));
}
ByteConversionSink startChunkedConversion(Sink<Object?> sink) {
return new _JsonUtf8DecoderSink(_reviver, sink, _allowMalformed);
static String? _useTextDecoder(
WasmExternRef? decoder, WasmExternRef? codeUnits) {
// If the input is malformed, catch the exception and return `null` to fall
// back on unintercepted decoder. The fallback will either succeed in
// decoding, or report the problem better than TextDecoder.
try {
return JSStringImpl(js.JS<WasmExternRef?>(
'(decoder, codeUnits) => decoder.decode(codeUnits)',
decoder,
codeUnits));
} catch (e) {}
return null;
}
// TODO(omersa): These values seem to be miscompiled at the use sites, see
// above.
//
// // TextDecoder is not defined on some browsers and on the stand-alone d8 and
// // jsshell engines. Use a lazy initializer to do feature detection once.
// static final WasmExternRef? _decoder = () {
// try {
// return js
// .JS<WasmExternRef?>('() => new TextDecoder("utf-8", {fatal: true})');
// } catch (e) {}
// return null;
// }();
// static final WasmExternRef? _decoderNonfatal = () {
// try {
// return js
// .JS<WasmExternRef?>('() => new TextDecoder("utf-8", {fatal: false})');
// } catch (e) {}
// return null;
// }();
}
//// Implementation ///////////////////////////////////////////////////////////
@ -1425,50 +1476,30 @@ class _JsonStringParser extends _ChunkedJsonParser<String> {
class JsonDecoder {
@patch
StringConversionSink startChunkedConversion(Sink<Object?> sink) {
return new _JsonStringDecoderSink(this._reviver, sink);
// return _JsonStringDecoderSink(this._reviver, sink);
return _JsonDecoderSink(_reviver, sink);
}
}
/**
* Implements the chunked conversion from a JSON string to its corresponding
* object.
*
* The sink only creates one object, but its input can be chunked.
*/
class _JsonStringDecoderSink extends StringConversionSinkBase {
_JsonStringParser _parser;
/// Implements the chunked conversion from a JSON string to its corresponding
/// object.
///
/// The sink only creates one object, but its input can be chunked.
// TODO(floitsch): don't accumulate everything before starting to decode.
class _JsonDecoderSink extends _StringSinkConversionSink<StringBuffer> {
final Object? Function(Object? key, Object? value)? _reviver;
final Sink<Object?> _sink;
_JsonStringDecoderSink(this._reviver, this._sink)
: _parser = _createParser(_reviver);
static _JsonStringParser _createParser(
Object? Function(Object? key, Object? value)? reviver) {
return new _JsonStringParser(new _JsonListener(reviver));
}
void addSlice(String chunk, int start, int end, bool isLast) {
_parser.chunk = chunk;
_parser.chunkEnd = end;
_parser.parse(start);
if (isLast) _parser.close();
}
void add(String chunk) {
addSlice(chunk, 0, chunk.length, false);
}
_JsonDecoderSink(this._reviver, this._sink) : super(StringBuffer(''));
void close() {
_parser.close();
var decoded = _parser.result;
super.close();
String accumulated = _stringSink.toString();
_stringSink.clear();
Object? decoded = _parseJson(accumulated, _reviver);
_sink.add(decoded);
_sink.close();
}
ByteConversionSink asUtf8Sink(bool allowMalformed) {
return new _JsonUtf8DecoderSink(_reviver, _sink, allowMalformed);
}
}
/**
@ -1477,13 +1508,13 @@ class _JsonStringDecoderSink extends StringConversionSinkBase {
class _JsonUtf8Parser extends _ChunkedJsonParser<List<int>> {
static final Uint8List emptyChunk = Uint8List(0);
final _Utf8Decoder decoder;
final bool allowMalformed;
List<int> chunk = emptyChunk;
int chunkEnd = 0;
_JsonUtf8Parser(_JsonListener listener, bool allowMalformed)
: decoder = new _Utf8Decoder(allowMalformed),
super(listener) {
_JsonUtf8Parser(_JsonListener listener, this.allowMalformed)
: super(listener) {
// Starts out checking for an optional BOM (KWD_BOM, count = 0).
partialState =
_ChunkedJsonParser.PARTIAL_KEYWORD | _ChunkedJsonParser.KWD_BOM;
@ -1503,24 +1534,22 @@ class _JsonUtf8Parser extends _ChunkedJsonParser<List<int>> {
}
void beginString() {
decoder.reset();
this.buffer = new StringBuffer();
}
void addSliceToString(int start, int end) {
final StringBuffer buffer = this.buffer;
buffer.write(decoder.convertChunked(chunk, start, end));
buffer
.write(_Utf8Decoder(allowMalformed).convertChunked(chunk, start, end));
}
void addCharToString(int charCode) {
final StringBuffer buffer = this.buffer;
decoder.flush(buffer);
buffer.writeCharCode(charCode);
}
String endString() {
final StringBuffer buffer = this.buffer;
decoder.flush(buffer);
this.buffer = null;
return buffer.toString();
}
@ -1536,476 +1565,21 @@ class _JsonUtf8Parser extends _ChunkedJsonParser<List<int>> {
}
}
@pragma("wasm:prefer-inline")
double _parseDouble(String source, int start, int end) =>
double.parse(source.substring(start, end));
/**
* Implements the chunked conversion from a UTF-8 encoding of JSON
* to its corresponding object.
*/
class _JsonUtf8DecoderSink extends ByteConversionSink {
final _JsonUtf8Parser _parser;
final Sink<Object?> _sink;
_JsonUtf8DecoderSink(reviver, this._sink, bool allowMalformed)
: _parser = _createParser(reviver, allowMalformed);
static _JsonUtf8Parser _createParser(
Object? Function(Object? key, Object? value)? reviver,
bool allowMalformed) {
return new _JsonUtf8Parser(new _JsonListener(reviver), allowMalformed);
}
void addSlice(List<int> chunk, int start, int end, bool isLast) {
_addChunk(chunk, start, end);
if (isLast) close();
}
void add(List<int> chunk) {
_addChunk(chunk, 0, chunk.length);
}
void _addChunk(List<int> chunk, int start, int end) {
_parser.chunk = chunk;
_parser.chunkEnd = end;
_parser.parse(start);
}
void close() {
_parser.close();
var decoded = _parser.result;
_sink.add(decoded);
_sink.close();
}
}
@patch
class _Utf8Decoder {
/// Flags indicating presence of the various kinds of bytes in the input.
int _scanFlags = 0;
/// How many bytes of the BOM have been read so far. Set to -1 when the BOM
/// has been skipped (or was not present).
int _bomIndex = 0;
// Table for the scanning phase, which quickly scans through the input.
//
// Each input byte is looked up in the table, providing a size and some flags.
// The sizes are summed, and the flags are or'ed together.
//
// The resulting size and flags indicate:
// A) How many UTF-16 code units will be emitted by the decoding of this
// input. This can be used to allocate a string of the correct length up
// front.
// B) Which decoder and resulting string representation is appropriate. There
// are three cases:
// 1) Pure ASCII (flags == 0): The input can simply be put into a
// OneByteString without further decoding.
// 2) Latin1 (flags == (flagLatin1 | flagExtension)): The result can be
// represented by a OneByteString, and the decoder can assume that only
// Latin1 characters are present.
// 3) Arbitrary input (otherwise): Needs a full-featured decoder. Output
// can be represented by a TwoByteString.
static const int sizeMask = 0x03;
static const int flagsMask = 0x3C;
static const int flagExtension = 1 << 2;
static const int flagLatin1 = 1 << 3;
static const int flagNonLatin1 = 1 << 4;
static const int flagIllegal = 1 << 5;
// ASCII 'A' = 64 + (1);
// Extension 'D' = 64 + (0 | flagExtension);
// Latin1 'I' = 64 + (1 | flagLatin1);
// BMP 'Q' = 64 + (1 | flagNonLatin1);
// Non-BMP 'R' = 64 + (2 | flagNonLatin1);
// Illegal 'a' = 64 + (1 | flagIllegal);
// Illegal 'b' = 64 + (2 | flagIllegal);
static const String scanTable = ""
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" // 00-1F
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" // 20-3F
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" // 40-5F
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" // 60-7F
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" // 80-9F
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" // A0-BF
"aaIIQQQQQQQQQQQQQQQQQQQQQQQQQQQQ" // C0-DF
"QQQQQQQQQQQQQQQQRRRRRbbbbbbbbbbb" // E0-FF
;
/// Max chunk to scan at a time.
///
/// Avoids staying away from safepoints too long.
/// The Utf8ScanInstr relies on this being small enough to ensure the
/// decoded length stays within Smi range.
static const int scanChunkSize = 65536;
/// Reset the decoder to a state where it is ready to decode a new string but
/// will not skip a leading BOM. Used by the fused UTF-8 / JSON decoder.
void reset() {
_state = initial;
_bomIndex = -1;
}
@pragma("vm:prefer-inline")
int scan(Uint8List bytes, int start, int end) {
// Assumes 0 <= start <= end <= bytes.length
int size = 0;
_scanFlags = 0;
int localStart = start;
while (end - localStart > scanChunkSize) {
int localEnd = localStart + scanChunkSize;
size += _scan(bytes, localStart, localEnd, scanTable);
localStart = localEnd;
}
size += _scan(bytes, localStart, end, scanTable);
return size;
}
@pragma("vm:recognized", "other")
@pragma("vm:prefer-inline")
@pragma("vm:idempotent")
int _scan(Uint8List bytes, int start, int end, String scanTable) {
int size = 0;
int flags = 0;
for (int i = start; i < end; i++) {
int t = scanTable.codeUnitAt(bytes[i]);
size += t & sizeMask;
flags |= t;
}
_scanFlags |= flags & flagsMask;
return size;
}
// The VM decoder handles BOM explicitly instead of via the state machine.
@patch
_Utf8Decoder(this.allowMalformed) : _state = initial;
_Utf8Decoder(this.allowMalformed) : _state = beforeBom;
@patch
String convertSingle(List<int> codeUnits, int start, int? maybeEnd) {
int end = RangeError.checkValidRange(start, maybeEnd, codeUnits.length);
// Have bytes as Uint8List.
Uint8List bytes;
int errorOffset;
if (codeUnits is Uint8List) {
bytes = unsafeCast<Uint8List>(codeUnits);
errorOffset = 0;
} else {
bytes = _makeUint8List(codeUnits, start, end);
errorOffset = start;
end -= start;
start = 0;
}
// Skip initial BOM.
start = skipBomSingle(bytes, start, end);
// Special case empty input.
if (start == end) return "";
// Scan input to determine size and appropriate decoder.
int size = scan(bytes, start, end);
int flags = _scanFlags;
if (flags == 0) {
// Pure ASCII.
assert(size == end - start);
OneByteString result = OneByteString.withLength(size);
copyRangeFromUint8ListToOneByteString(bytes, result, start, 0, size);
return result;
}
String result;
if (flags == (flagLatin1 | flagExtension)) {
// Latin1.
result = decode8(bytes, start, end, size);
} else {
// Arbitrary Unicode.
result = decode16(bytes, start, end, size);
}
if (_state == accept) {
return result;
}
if (!allowMalformed) {
if (!isErrorState(_state)) {
// Unfinished sequence.
_state = errorUnfinished;
_charOrIndex = end;
}
final String message = errorDescription(_state);
throw FormatException(message, codeUnits, errorOffset + _charOrIndex);
}
// Start over on slow path.
_state = initial;
result = decodeGeneral(bytes, start, end, true);
assert(!isErrorState(_state));
return result;
return convertGeneral(codeUnits, start, maybeEnd, true);
}
@patch
String convertChunked(List<int> codeUnits, int start, int? maybeEnd) {
int end = RangeError.checkValidRange(start, maybeEnd, codeUnits.length);
// Have bytes as Uint8List.
Uint8List bytes;
int errorOffset;
if (codeUnits is Uint8List) {
bytes = unsafeCast<Uint8List>(codeUnits);
errorOffset = 0;
} else {
bytes = _makeUint8List(codeUnits, start, end);
errorOffset = start;
end -= start;
start = 0;
}
// Skip initial BOM.
start = skipBomChunked(bytes, start, end);
// Special case empty input.
if (start == end) return "";
// Scan input to determine size and appropriate decoder.
int size = scan(bytes, start, end);
int flags = _scanFlags;
// Adjust scan flags and size based on carry-over state.
switch (_state) {
case IA:
break;
case X1:
flags |= _charOrIndex < (0x100 >> 6) ? flagLatin1 : flagNonLatin1;
if (end - start >= 1) {
size += _charOrIndex < (0x10000 >> 6) ? 1 : 2;
}
break;
case X2:
flags |= flagNonLatin1;
if (end - start >= 2) {
size += _charOrIndex < (0x10000 >> 12) ? 1 : 2;
}
break;
case TO:
case TS:
flags |= flagNonLatin1;
if (end - start >= 2) size += 1;
break;
case X3:
case QO:
case QR:
flags |= flagNonLatin1;
if (end - start >= 3) size += 2;
break;
}
if (flags == 0) {
// Pure ASCII.
assert(_state == accept);
assert(size == end - start);
OneByteString result = OneByteString.withLength(size);
copyRangeFromUint8ListToOneByteString(bytes, result, start, 0, size);
return result;
}
// Do not include any final, incomplete character in size.
int extensionCount = 0;
int i = end - 1;
while (i >= start && (bytes[i] & 0xC0) == 0x80) {
extensionCount++;
i--;
}
if (i >= start && bytes[i] >= ((~0x3F >> extensionCount) & 0xFF)) {
size -= bytes[i] >= 0xF0 ? 2 : 1;
}
final int carryOverState = _state;
final int carryOverChar = _charOrIndex;
String result;
if (flags == (flagLatin1 | flagExtension)) {
// Latin1.
result = decode8(bytes, start, end, size);
} else {
// Arbitrary Unicode.
result = decode16(bytes, start, end, size);
}
if (!isErrorState(_state)) {
return result;
}
assert(_bomIndex == -1);
if (!allowMalformed) {
final String message = errorDescription(_state);
_state = initial; // Ready for more input.
throw FormatException(message, codeUnits, errorOffset + _charOrIndex);
}
// Start over on slow path.
_state = carryOverState;
_charOrIndex = carryOverChar;
result = decodeGeneral(bytes, start, end, false);
assert(!isErrorState(_state));
return result;
}
@pragma("vm:prefer-inline")
int skipBomSingle(Uint8List bytes, int start, int end) {
if (end - start >= 3 &&
bytes[start] == 0xEF &&
bytes[start + 1] == 0xBB &&
bytes[start + 2] == 0xBF) {
return start + 3;
}
return start;
}
@pragma("vm:prefer-inline")
int skipBomChunked(Uint8List bytes, int start, int end) {
assert(start <= end);
int bomIndex = _bomIndex;
// Already skipped?
if (bomIndex == -1) return start;
const bomValues = <int>[0xEF, 0xBB, 0xBF];
int i = start;
while (bomIndex < 3) {
if (i == end) {
// Unfinished BOM.
_bomIndex = bomIndex;
return start;
}
if (bytes[i++] != bomValues[bomIndex++]) {
// No BOM.
_bomIndex = -1;
return start;
}
}
// Complete BOM.
_bomIndex = -1;
_state = initial;
return i;
}
String decode8(Uint8List bytes, int start, int end, int size) {
assert(start < end);
OneByteString result = OneByteString.withLength(size);
int i = start;
int j = 0;
if (_state == X1) {
// Half-way though 2-byte sequence
assert(_charOrIndex == 2 || _charOrIndex == 3);
final int e = bytes[i++] ^ 0x80;
if (e >= 0x40) {
_state = errorMissingExtension;
_charOrIndex = i - 1;
return "";
}
writeIntoOneByteString(result, j++, (_charOrIndex << 6) | e);
_state = accept;
}
assert(_state == accept);
while (i < end) {
int byte = bytes[i++];
if (byte >= 0x80) {
if (byte < 0xC0) {
_state = errorUnexpectedExtension;
_charOrIndex = i - 1;
return "";
}
assert(byte == 0xC2 || byte == 0xC3);
if (i == end) {
_state = X1;
_charOrIndex = byte & 0x1F;
break;
}
final int e = bytes[i++] ^ 0x80;
if (e >= 0x40) {
_state = errorMissingExtension;
_charOrIndex = i - 1;
return "";
}
byte = (byte << 6) | e;
}
writeIntoOneByteString(result, j++, byte);
}
// Output size must match, unless we are doing single conversion and are
// inside an unfinished sequence (which will trigger an error later).
assert(_bomIndex == 0 && _state != accept
? (j == size - 1 || j == size - 2)
: (j == size));
return result;
}
String decode16(Uint8List bytes, int start, int end, int size) {
assert(start < end);
final String typeTable = _Utf8Decoder.typeTable;
final String transitionTable = _Utf8Decoder.transitionTable;
TwoByteString result = TwoByteString.withLength(size);
int i = start;
int j = 0;
int state = _state;
int char;
// First byte
assert(!isErrorState(state));
final int byte = bytes[i++];
final int type = typeTable.codeUnitAt(byte) & typeMask;
if (state == accept) {
char = byte & (shiftedByteMask >> type);
state = transitionTable.codeUnitAt(type);
} else {
char = (byte & 0x3F) | (_charOrIndex << 6);
state = transitionTable.codeUnitAt(state + type);
}
while (i < end) {
final int byte = bytes[i++];
final int type = typeTable.codeUnitAt(byte) & typeMask;
if (state == accept) {
if (char >= 0x10000) {
assert(char < 0x110000);
writeIntoTwoByteString(result, j++, 0xD7C0 + (char >> 10));
writeIntoTwoByteString(result, j++, 0xDC00 + (char & 0x3FF));
} else {
writeIntoTwoByteString(result, j++, char);
}
char = byte & (shiftedByteMask >> type);
state = transitionTable.codeUnitAt(type);
} else if (isErrorState(state)) {
_state = state;
_charOrIndex = i - 2;
return "";
} else {
char = (byte & 0x3F) | (char << 6);
state = transitionTable.codeUnitAt(state + type);
return convertGeneral(codeUnits, start, maybeEnd, false);
}
}
// Final write?
if (state == accept) {
if (char >= 0x10000) {
assert(char < 0x110000);
writeIntoTwoByteString(result, j++, 0xD7C0 + (char >> 10));
writeIntoTwoByteString(result, j++, 0xDC00 + (char & 0x3FF));
} else {
writeIntoTwoByteString(result, j++, char);
}
} else if (isErrorState(state)) {
_state = state;
_charOrIndex = end - 1;
return "";
}
_state = state;
_charOrIndex = char;
// Output size must match, unless we are doing single conversion and are
// inside an unfinished sequence (which will trigger an error later).
assert(_bomIndex == 0 && _state != accept
? (j == size - 1 || j == size - 2)
: (j == size));
return result;
}
}
double _parseDouble(String source, int start, int end) =>
double.parse(source.substring(start, end));

View file

@ -0,0 +1,36 @@
// Copyright (c) 2023, 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:_js_types" show JSStringImpl;
import 'dart:_js_helper' as js;
@patch
class int {
@patch
static int parse(String source,
{int? radix, @deprecated int onError(String source)?}) {
int? value = tryParse(source, radix: radix);
if (value != null) return value;
if (onError != null) return onError(source);
throw new FormatException(source);
}
@patch
static int? tryParse(String source, {int? radix}) {
// TODO(omersa): JS's `parseInt` is not compatible, copy dart2js's
// implementation.
if (radix == null) {
return js
.JS<double>('(s) => parseInt(s)',
unsafeCast<JSStringImpl>(source).toExternRef)
.toInt();
} else {
return js
.JS<double>('(s, r) => parseInt(s, r)',
unsafeCast<JSStringImpl>(source).toExternRef, radix.toDouble())
.toInt();
}
}
}

View file

@ -0,0 +1,62 @@
// Copyright (c) 2023, 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' as js;
@patch
class StringBuffer {
String _contents;
@patch
StringBuffer([Object content = ""]) : _contents = '$content';
@patch
int get length => _contents.length;
@patch
void write(Object? obj) => _writeString('$obj');
@patch
void writeCharCode(int charCode) =>
_writeString(String.fromCharCode(charCode));
@patch
void writeAll(Iterable<dynamic> objects, [String separator = ""]) {
_contents = _writeAll(_contents, objects, separator);
}
@patch
void writeln([Object? obj = ""]) => _writeString('$obj\n');
@patch
void clear() => _contents = "";
@patch
String toString() => _contents;
void _writeString(String str) {
_contents = _contents + str;
}
static String _writeAll(
String string, Iterable<Object?> objects, String separator) {
final iterator = objects.iterator;
if (!iterator.moveNext()) return string;
if (separator.isEmpty) {
do {
string = _writeOne(string, iterator.current);
} while (iterator.moveNext());
} else {
string = _writeOne(string, iterator.current);
while (iterator.moveNext()) {
string = _writeOne(string, separator);
string = _writeOne(string, iterator.current);
}
}
return string;
}
static String _writeOne(String string, Object? obj) => string + '$obj';
}

View file

@ -0,0 +1,102 @@
// Copyright (c) 2023, 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 EfficientLengthIterable, patch, unsafeCast;
import 'dart:_js_helper' as js;
import 'dart:_js_types';
import 'dart:_wasm';
import 'dart:js_interop';
import 'dart:typed_data';
@patch
class String {
@patch
factory String.fromCharCodes(Iterable<int> charCodes,
[int start = 0, int? end]) {
final length = charCodes.length;
RangeError.checkValueInInterval(start, 0, length);
if (end != null) {
RangeError.checkValueInInterval(end, start, length);
}
// Skip until `start`.
final it = charCodes.iterator;
for (int i = 0; i < start; i++) {
it.moveNext();
}
// The part of the iterable converted to string is collected in a JS typed
// array, to be able to effciently get subarrays, to pass to
// `String.fromCharCode.apply`.
final charCodesLength = (end ?? length) - start;
final typedArrayLength = charCodesLength * 2;
final JSUint32ArrayImpl list =
unsafeCast<JSUint32ArrayImpl>(Uint32List(typedArrayLength));
int index = 0; // index in `list`.
end ??= start + charCodesLength;
for (int i = start; i < end; i++) {
if (!it.moveNext()) {
throw RangeError.range(end, start, i);
}
final charCode = it.current;
if (charCode >= 0 && charCode <= 0xffff) {
list[index++] = charCode;
} else if (charCode >= 0 && charCode <= 0x10ffff) {
list[index++] = 0xd800 + ((((charCode - 0x10000) >> 10) & 0x3ff));
list[index++] = 0xdc00 + (charCode & 0x3ff);
} else {
throw RangeError.range(charCode, 0, 0x10ffff);
}
}
// Create JS string from `list`.
const kMaxApply = 500;
if (index <= kMaxApply) {
return _fromCharCodeApplySubarray(list.toExternRef, 0, index.toDouble());
}
String result = '';
for (int i = 0; i < index; i += kMaxApply) {
final chunkEnd = (i + kMaxApply < index) ? i + kMaxApply : index;
result += _fromCharCodeApplySubarray(
list.toExternRef, i.toDouble(), chunkEnd.toDouble());
}
return result;
}
@patch
factory String.fromCharCode(int charCode) => _fromCharCode(charCode);
static String _fromOneByteCharCode(int charCode) => JSStringImpl(js
.JS<WasmExternRef?>('c => String.fromCharCode(c)', charCode.toDouble()));
static String _fromTwoByteCharCode(int low, int high) =>
JSStringImpl(js.JS<WasmExternRef?>('(l, h) => String.fromCharCode(h, l)',
low.toDouble(), high.toDouble()));
static String _fromCharCode(int charCode) {
if (0 <= charCode) {
if (charCode <= 0xffff) {
return _fromOneByteCharCode(charCode);
}
if (charCode <= 0x10ffff) {
var bits = charCode - 0x10000;
var low = 0xDC00 | (bits & 0x3ff);
var high = 0xD800 | (bits >> 10);
return _fromTwoByteCharCode(low, high);
}
}
throw RangeError.range(charCode, 0, 0x10ffff);
}
static String _fromCharCodeApplySubarray(
WasmExternRef? charCodes, double index, double end) =>
JSStringImpl(js.JS<WasmExternRef?>(
'(c, i, e) => String.fromCharCode.apply(null, c.subarray(i, e))',
charCodes,
index,
end));
}

View file

@ -129,21 +129,23 @@
"core": {
"uri": "core/core.dart",
"patches": [
"_internal/wasm/lib/core_patch.dart",
"_internal/vm_shared/lib/array_patch.dart",
"_internal/vm_shared/lib/bigint_patch.dart",
"_internal/vm_shared/lib/bool_patch.dart",
"_internal/vm_shared/lib/date_patch.dart",
"_internal/vm_shared/lib/map_patch.dart",
"_internal/vm_shared/lib/null_patch.dart",
"_internal/wasm/lib/boxed_double.dart",
"_internal/wasm/lib/boxed_int.dart",
"_internal/wasm/lib/boxed_int_to_string.dart",
"_internal/wasm/lib/core_patch.dart",
"_internal/wasm/lib/date_patch_patch.dart",
"_internal/wasm/lib/int_common_patch.dart",
"_internal/wasm/lib/int_patch.dart",
"_internal/wasm/lib/string_buffer_patch.dart",
"_internal/wasm/lib/string_patch.dart",
"_internal/wasm/lib/sync_star_patch.dart",
"_internal/wasm/lib/weak_patch.dart",
"_internal/wasm/lib/boxed_double.dart",
"_internal/wasm/lib/boxed_int.dart"
"_internal/wasm/lib/weak_patch.dart"
]
}
}
@ -158,21 +160,23 @@
"core": {
"uri": "core/core.dart",
"patches": [
"_internal/wasm/lib/core_patch.dart",
"_internal/vm_shared/lib/array_patch.dart",
"_internal/vm_shared/lib/bigint_patch.dart",
"_internal/vm_shared/lib/bool_patch.dart",
"_internal/vm_shared/lib/date_patch.dart",
"_internal/vm_shared/lib/map_patch.dart",
"_internal/vm_shared/lib/null_patch.dart",
"_internal/wasm/lib/boxed_double.dart",
"_internal/wasm/lib/boxed_int.dart",
"_internal/wasm/lib/boxed_int_to_string.dart",
"_internal/wasm/lib/core_patch.dart",
"_internal/wasm/lib/date_patch_patch.dart",
"_internal/wasm/lib/int_common_patch.dart",
"_internal/wasm/lib/int_patch.dart",
"_internal/wasm/lib/string_buffer_patch.dart",
"_internal/wasm/lib/string_stringref_patch.dart",
"_internal/wasm/lib/sync_star_patch.dart",
"_internal/wasm/lib/weak_patch.dart",
"_internal/wasm/lib/boxed_double.dart",
"_internal/wasm/lib/boxed_int.dart"
"_internal/wasm/lib/weak_patch.dart"
]
}
}
@ -199,15 +203,17 @@
"_internal/vm_shared/lib/date_patch.dart",
"_internal/vm_shared/lib/map_patch.dart",
"_internal/vm_shared/lib/null_patch.dart",
"_internal/wasm/lib/boxed_double.dart",
"_internal/wasm/lib/boxed_int.dart",
"_internal/wasm/lib/core_patch.dart",
"_internal/wasm/lib/date_patch_patch.dart",
"_internal/wasm/lib/int_patch.dart",
"_internal/wasm/lib/string_buffer_patch.dart",
"_internal/wasm/lib/string_patch.dart",
"_internal/wasm/lib/int_common_patch.dart",
"_internal/wasm/lib/sync_star_patch.dart",
"_internal/wasm/lib/weak_patch.dart",
"_internal/wasm/lib/boxed_double.dart",
"_internal/wasm/lib/boxed_int.dart"
"_internal/wasm_js_compatibility/lib/boxed_int_to_string.dart",
"_internal/wasm_js_compatibility/lib/int_patch.dart",
"_internal/wasm_js_compatibility/lib/string_buffer_patch.dart",
"_internal/wasm_js_compatibility/lib/string_patch.dart"
]
},
"typed_data": {
@ -246,6 +252,9 @@
"_simd": {
"uri": "_internal/wasm/lib/simd.dart"
},
"_string": {
"uri": "_internal/wasm/lib/string.dart"
},
"_typed_data": {
"uri": "_internal/wasm/lib/typed_data.dart"
}
@ -257,7 +266,8 @@
"uri": "core/core.dart",
"patches": [
"_internal/wasm/lib/boxed_double.dart",
"_internal/wasm/lib/boxed_int.dart"
"_internal/wasm/lib/boxed_int.dart",
"_internal/wasm/lib/int_common_patch.dart"
]
},
"_http": {
@ -281,9 +291,6 @@
"_object_helper": {
"uri": "_internal/wasm/lib/object_helper.dart"
},
"_string": {
"uri": "_internal/wasm/lib/string.dart"
},
"_string_helper": {
"uri": "_internal/wasm/lib/string_helper.dart"
},

View file

@ -119,21 +119,23 @@ wasm:
core:
uri: core/core.dart
patches:
- _internal/wasm/lib/core_patch.dart
- _internal/vm_shared/lib/array_patch.dart
- _internal/vm_shared/lib/bigint_patch.dart
- _internal/vm_shared/lib/bool_patch.dart
- _internal/vm_shared/lib/date_patch.dart
- _internal/vm_shared/lib/map_patch.dart
- _internal/vm_shared/lib/null_patch.dart
- _internal/wasm/lib/boxed_double.dart
- _internal/wasm/lib/boxed_int.dart
- _internal/wasm/lib/boxed_int_to_string.dart
- _internal/wasm/lib/core_patch.dart
- _internal/wasm/lib/date_patch_patch.dart
- _internal/wasm/lib/int_common_patch.dart
- _internal/wasm/lib/int_patch.dart
- _internal/wasm/lib/string_buffer_patch.dart
- _internal/wasm/lib/string_patch.dart
- _internal/wasm/lib/sync_star_patch.dart
- _internal/wasm/lib/weak_patch.dart
- _internal/wasm/lib/boxed_double.dart
- _internal/wasm/lib/boxed_int.dart
wasm_stringref:
include:
@ -142,21 +144,23 @@ wasm_stringref:
core:
uri: core/core.dart
patches:
- _internal/wasm/lib/core_patch.dart
- _internal/vm_shared/lib/array_patch.dart
- _internal/vm_shared/lib/bigint_patch.dart
- _internal/vm_shared/lib/bool_patch.dart
- _internal/vm_shared/lib/date_patch.dart
- _internal/vm_shared/lib/map_patch.dart
- _internal/vm_shared/lib/null_patch.dart
- _internal/wasm/lib/boxed_double.dart
- _internal/wasm/lib/boxed_int.dart
- _internal/wasm/lib/boxed_int_to_string.dart
- _internal/wasm/lib/core_patch.dart
- _internal/wasm/lib/date_patch_patch.dart
- _internal/wasm/lib/int_common_patch.dart
- _internal/wasm/lib/int_patch.dart
- _internal/wasm/lib/string_buffer_patch.dart
- _internal/wasm/lib/string_stringref_patch.dart
- _internal/wasm/lib/sync_star_patch.dart
- _internal/wasm/lib/weak_patch.dart
- _internal/wasm/lib/boxed_double.dart
- _internal/wasm/lib/boxed_int.dart
wasm_js_compatibility:
include:
@ -175,15 +179,17 @@ wasm_js_compatibility:
- _internal/vm_shared/lib/date_patch.dart
- _internal/vm_shared/lib/map_patch.dart
- _internal/vm_shared/lib/null_patch.dart
- _internal/wasm/lib/core_patch.dart
- _internal/wasm/lib/date_patch_patch.dart
- _internal/wasm/lib/int_patch.dart
- _internal/wasm/lib/string_buffer_patch.dart
- _internal/wasm/lib/string_patch.dart
- _internal/wasm/lib/sync_star_patch.dart
- _internal/wasm/lib/weak_patch.dart
- _internal/wasm/lib/boxed_double.dart
- _internal/wasm/lib/boxed_int.dart
- _internal/wasm/lib/core_patch.dart
- _internal/wasm/lib/date_patch_patch.dart
- _internal/wasm/lib/int_common_patch.dart
- _internal/wasm/lib/sync_star_patch.dart
- _internal/wasm/lib/weak_patch.dart
- _internal/wasm_js_compatibility/lib/boxed_int_to_string.dart
- _internal/wasm_js_compatibility/lib/int_patch.dart
- _internal/wasm_js_compatibility/lib/string_buffer_patch.dart
- _internal/wasm_js_compatibility/lib/string_patch.dart
typed_data:
uri: typed_data/typed_data.dart
patches:
@ -208,6 +214,8 @@ wasm_base:
- _internal/wasm/lib/typed_data_patch.dart
_simd:
uri: _internal/wasm/lib/simd.dart
_string:
uri: _internal/wasm/lib/string.dart
_typed_data:
uri: _internal/wasm/lib/typed_data.dart
@ -218,6 +226,7 @@ wasm_common:
patches:
- _internal/wasm/lib/boxed_double.dart
- _internal/wasm/lib/boxed_int.dart
- _internal/wasm/lib/int_common_patch.dart
_http:
uri: _http/http.dart
_internal:
@ -232,8 +241,6 @@ wasm_common:
uri: _internal/wasm/lib/js_types.dart
_object_helper:
uri: _internal/wasm/lib/object_helper.dart
_string:
uri: _internal/wasm/lib/string.dart
_string_helper:
uri: _internal/wasm/lib/string_helper.dart
_wasm:

View file

@ -0,0 +1,26 @@
// Copyright (c) 2023, 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:js_util';
import 'dart:js_interop';
import 'package:expect/expect.dart';
@JS()
external void eval(String code);
void main() {
eval(r'''
globalThis.jsString = "hi";
''');
String jsString = getProperty(globalThis, "jsString");
switch (jsString) {
case "hi":
break;
default:
Expect.fail("Unexpected JS String: $jsString");
}
}