[dart2wasm] Take advantage of fast js-string builtins
Some wasm engines have started to optimize the `js-string` builtin
proposal (e.g. V8) and those that haven't yet are probaly going to
do soon.
So we can start taking advantage of it in dart2wasm.
=> We make use of them in the JS<->Dart string copy code.
=> This will also provide a better baseline when evaluating whether
switching to JS stringes entirely makes sense.
A somewhat unrelated (but necessary for this CL) change is to tighten
the types we use in `@pragma('wasm:import')` and
`@pragma('wasm:export')` in some cases:
We should only use 'pure' wasm types (i.e. not wasm struct / function
types we define for dart classes & functions) and mostly non-composed
types in import/exports as the `--closed-world` optimizations from
binaryen rely on that (and error otherwise).
Overall this leads to significant improvements in Dart<->JS
string copies.
The `WasmDataTransfer.*{From,To}BrowserString` benchmarks
improve something between 50-100%.
Change-Id: I2048113c462ecb2047402c0616d2b3b1f45773f5
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/400641
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Slava Egorov <vegorov@google.com>
This commit is contained in:
committed by
Commit Queue
parent
e8ebee8845
commit
9b11bf4da9
@@ -88,7 +88,7 @@ class InlineExpander {
|
||||
String parameterString = 'x$j';
|
||||
DartType type = originalArgument.getStaticType(_staticTypeContext);
|
||||
dartPositionalParameters.add(VariableDeclaration(parameterString,
|
||||
type: type, isSynthesized: true));
|
||||
type: _toExternalType(type), isSynthesized: true));
|
||||
if (originalArgument is! VariableGet) {
|
||||
allArgumentsAreGet = false;
|
||||
}
|
||||
@@ -121,4 +121,18 @@ class InlineExpander {
|
||||
_methodCollector.addMethod(dartProcedure, jsMethodName, codeTemplate);
|
||||
return result;
|
||||
}
|
||||
|
||||
// The `JS<foo>("...some js code ...", arg0, arg1)` expressions will produce
|
||||
// wasm imports. We want to only use types in those wasm imports that binaryen
|
||||
// allows under closed world assumptions (and not blindly use `arg<N>`s static
|
||||
// type).
|
||||
//
|
||||
// For now we special case `WasmArray<>` which we turn into a generic `array`
|
||||
// type (super type of all wasm arrays).
|
||||
DartType _toExternalType(DartType type) {
|
||||
if (type is InterfaceType && type.classNode == _util.wasmArrayClass) {
|
||||
return InterfaceType(_util.wasmArrayRefClass, type.declaredNullability);
|
||||
}
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,6 +129,26 @@ const jsRuntimeBlobPart3 = r'''
|
||||
"fromCharCode": (i) => String.fromCharCode(i),
|
||||
"length": (s) => s.length,
|
||||
"substring": (s, a, b) => s.substring(a, b),
|
||||
"fromCharCodeArray": (a, start, end) => {
|
||||
if (end <= start) return '';
|
||||
|
||||
const read = dartInstance.exports.$wasmI16ArrayGet;
|
||||
let result = '';
|
||||
let index = start;
|
||||
const chunkLength = Math.min(end - index, 500);
|
||||
let array = new Array(chunkLength);
|
||||
while (index < end) {
|
||||
const newChunkLength = Math.min(end - index, 500);
|
||||
for (let i = 0; i < newChunkLength; i++) {
|
||||
array[i] = read(a, index++);
|
||||
}
|
||||
if (newChunkLength < chunkLength) {
|
||||
array = array.slice(0, newChunkLength);
|
||||
}
|
||||
result += String.fromCharCode(...array);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
};
|
||||
|
||||
const deferredLibraryHelper = {
|
||||
|
||||
@@ -27,6 +27,8 @@ class CoreTypesUtil {
|
||||
final Procedure jsValueUnboxTarget;
|
||||
final Procedure numToIntTarget;
|
||||
final Class wasmExternRefClass;
|
||||
final Class wasmArrayClass;
|
||||
final Class wasmArrayRefClass;
|
||||
final Procedure wrapDartFunctionTarget;
|
||||
|
||||
CoreTypesUtil(this.coreTypes, this._extensionIndex)
|
||||
@@ -64,6 +66,9 @@ class CoreTypesUtil {
|
||||
.firstWhere((p) => p.name.text == 'unbox'),
|
||||
wasmExternRefClass =
|
||||
coreTypes.index.getClass('dart:_wasm', 'WasmExternRef'),
|
||||
wasmArrayClass = coreTypes.index.getClass('dart:_wasm', 'WasmArray'),
|
||||
wasmArrayRefClass =
|
||||
coreTypes.index.getClass('dart:_wasm', 'WasmArrayRef'),
|
||||
wrapDartFunctionTarget = coreTypes.index
|
||||
.getTopLevelProcedure('dart:_js_helper', '_wrapDartFunction');
|
||||
|
||||
|
||||
@@ -690,7 +690,7 @@ class Translator with KernelNodes {
|
||||
/// (`anyref`, `funcref` or `externref`).
|
||||
/// This function can be called before the class info is built.
|
||||
w.ValueType translateExternalType(DartType type) {
|
||||
bool isPotentiallyNullable = type.isPotentiallyNullable;
|
||||
final bool isPotentiallyNullable = type.isPotentiallyNullable;
|
||||
if (type is InterfaceType) {
|
||||
Class cls = type.classNode;
|
||||
if (cls == wasmFuncRefClass || cls == wasmFunctionClass) {
|
||||
@@ -699,6 +699,16 @@ class Translator with KernelNodes {
|
||||
if (cls == wasmExternRefClass) {
|
||||
return w.RefType.extern(nullable: isPotentiallyNullable);
|
||||
}
|
||||
if (cls == wasmArrayRefClass) {
|
||||
return w.RefType.array(nullable: isPotentiallyNullable);
|
||||
}
|
||||
if (cls == wasmArrayClass) {
|
||||
final elementType =
|
||||
translateExternalStorageType(type.typeArguments.single);
|
||||
return w.RefType.def(
|
||||
wasmArrayType(elementType, '$elementType', mutable: true),
|
||||
nullable: isPotentiallyNullable);
|
||||
}
|
||||
if (!isPotentiallyNullable) {
|
||||
w.StorageType? builtin = builtinTypes[cls];
|
||||
if (builtin != null && builtin.isPrimitive) {
|
||||
@@ -711,6 +721,22 @@ class Translator with KernelNodes {
|
||||
return w.RefType.any(nullable: true);
|
||||
}
|
||||
|
||||
w.StorageType translateExternalStorageType(DartType type) {
|
||||
if (type is InterfaceType) {
|
||||
final cls = type.classNode;
|
||||
if (isWasmType(cls)) {
|
||||
final isNullable = type.isPotentiallyNullable;
|
||||
final w.StorageType? builtin = builtinTypes[cls];
|
||||
if (builtin != null) {
|
||||
if (!isNullable) return builtin;
|
||||
if (builtin.isPrimitive) throw "Wasm numeric types can't be nullable";
|
||||
return (builtin as w.RefType).withNullability(isNullable);
|
||||
}
|
||||
}
|
||||
}
|
||||
return translateExternalType(type) as w.RefType;
|
||||
}
|
||||
|
||||
/// Creates a global reference to [f] in [module]. [f] must also be located
|
||||
/// in [module].
|
||||
w.Global makeFunctionRef(w.ModuleBuilder module, w.BaseFunction f) {
|
||||
|
||||
@@ -15,63 +15,22 @@ import 'dart:typed_data';
|
||||
@pragma('wasm:prefer-inline')
|
||||
JSStringImpl jsStringFromDartString(String s) {
|
||||
if (s is OneByteString) {
|
||||
final fromArray = s.array;
|
||||
final toArray = WasmArray<WasmI16>(fromArray.length);
|
||||
for (int i = 0; i < fromArray.length; ++i) {
|
||||
toArray.write(i, fromArray.readUnsigned(i));
|
||||
}
|
||||
return JSStringImpl(
|
||||
JS<WasmExternRef>(
|
||||
r'''
|
||||
(s, length) => {
|
||||
if (length == 0) return '';
|
||||
|
||||
const read = dartInstance.exports.$stringRead1;
|
||||
let result = '';
|
||||
let index = 0;
|
||||
const chunkLength = Math.min(length - index, 500);
|
||||
let array = new Array(chunkLength);
|
||||
while (index < length) {
|
||||
const newChunkLength = Math.min(length - index, 500);
|
||||
for (let i = 0; i < newChunkLength; i++) {
|
||||
array[i] = read(s, index++);
|
||||
}
|
||||
if (newChunkLength < chunkLength) {
|
||||
array = array.slice(0, newChunkLength);
|
||||
}
|
||||
result += String.fromCharCode(...array);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
''',
|
||||
jsObjectFromDartObject(s),
|
||||
s.length.toWasmI32(),
|
||||
_jsStringFromCharCodeArray(
|
||||
toArray,
|
||||
0.toWasmI32(),
|
||||
toArray.length.toWasmI32(),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (s is TwoByteString) {
|
||||
return JSStringImpl(
|
||||
JS<WasmExternRef>(
|
||||
r'''
|
||||
(s, length) => {
|
||||
if (length == 0) return '';
|
||||
|
||||
const read = dartInstance.exports.$stringRead2;
|
||||
let result = '';
|
||||
let index = 0;
|
||||
const chunkLength = Math.min(length - index, 500);
|
||||
let array = new Array(chunkLength);
|
||||
while (index < length) {
|
||||
const newChunkLength = Math.min(length - index, 500);
|
||||
for (let i = 0; i < newChunkLength; i++) {
|
||||
array[i] = read(s, index++);
|
||||
}
|
||||
if (newChunkLength < chunkLength) {
|
||||
array = array.slice(0, newChunkLength);
|
||||
}
|
||||
result += String.fromCharCode(...array);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
''',
|
||||
jsObjectFromDartObject(s),
|
||||
s.length.toWasmI32(),
|
||||
),
|
||||
_jsStringFromCharCodeArray(s.array, 0.toWasmI32(), s.length.toWasmI32()),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -79,70 +38,34 @@ JSStringImpl jsStringFromDartString(String s) {
|
||||
}
|
||||
|
||||
@patch
|
||||
@pragma('wasm:prefer-inline')
|
||||
String jsStringToDartString(JSStringImpl s) {
|
||||
final length = s.length;
|
||||
if (length == 0) return '';
|
||||
|
||||
return JS<String>(r'''
|
||||
(s) => {
|
||||
let length = s.length;
|
||||
let range = 0;
|
||||
for (let i = 0; i < length; i++) {
|
||||
range |= s.codePointAt(i);
|
||||
}
|
||||
const exports = dartInstance.exports;
|
||||
if (range < 256) {
|
||||
if (length <= 10) {
|
||||
if (length == 1) {
|
||||
return exports.$stringAllocate1_1(s.codePointAt(0));
|
||||
}
|
||||
if (length == 2) {
|
||||
return exports.$stringAllocate1_2(s.codePointAt(0), s.codePointAt(1));
|
||||
}
|
||||
if (length == 3) {
|
||||
return exports.$stringAllocate1_3(s.codePointAt(0), s.codePointAt(1), s.codePointAt(2));
|
||||
}
|
||||
if (length == 4) {
|
||||
return exports.$stringAllocate1_4(s.codePointAt(0), s.codePointAt(1), s.codePointAt(2), s.codePointAt(3));
|
||||
}
|
||||
if (length == 5) {
|
||||
return exports.$stringAllocate1_5(s.codePointAt(0), s.codePointAt(1), s.codePointAt(2), s.codePointAt(3), s.codePointAt(4));
|
||||
}
|
||||
if (length == 6) {
|
||||
return exports.$stringAllocate1_6(s.codePointAt(0), s.codePointAt(1), s.codePointAt(2), s.codePointAt(3), s.codePointAt(4), s.codePointAt(5));
|
||||
}
|
||||
if (length == 7) {
|
||||
return exports.$stringAllocate1_7(s.codePointAt(0), s.codePointAt(1), s.codePointAt(2), s.codePointAt(3), s.codePointAt(4), s.codePointAt(5), s.codePointAt(6));
|
||||
}
|
||||
if (length == 8) {
|
||||
return exports.$stringAllocate1_8(s.codePointAt(0), s.codePointAt(1), s.codePointAt(2), s.codePointAt(3), s.codePointAt(4), s.codePointAt(5), s.codePointAt(6), s.codePointAt(7));
|
||||
}
|
||||
if (length == 9) {
|
||||
return exports.$stringAllocate1_9(s.codePointAt(0), s.codePointAt(1), s.codePointAt(2), s.codePointAt(3), s.codePointAt(4), s.codePointAt(5), s.codePointAt(6), s.codePointAt(7), s.codePointAt(8));
|
||||
}
|
||||
if (length == 10) {
|
||||
return exports.$stringAllocate1_10(s.codePointAt(0), s.codePointAt(1), s.codePointAt(2), s.codePointAt(3), s.codePointAt(4), s.codePointAt(5), s.codePointAt(6), s.codePointAt(7), s.codePointAt(8), s.codePointAt(9));
|
||||
}
|
||||
}
|
||||
const dartString = exports.$stringAllocate1(length);
|
||||
const write = exports.$stringWrite1;
|
||||
for (let i = 0; i < length; i++) {
|
||||
write(dartString, i, s.codePointAt(i));
|
||||
}
|
||||
return dartString;
|
||||
} else {
|
||||
const dartString = exports.$stringAllocate2(length);
|
||||
const write = exports.$stringWrite2;
|
||||
for (let i = 0; i < length; i++) {
|
||||
write(dartString, i, s.charCodeAt(i));
|
||||
}
|
||||
return dartString;
|
||||
}
|
||||
String jsStringToDartString(JSStringImpl jsString) {
|
||||
final length = jsString.length;
|
||||
two_byte:
|
||||
{
|
||||
final oneByteString = OneByteString.withLength(length);
|
||||
final array = oneByteString.array;
|
||||
for (int i = 0; i < length; ++i) {
|
||||
final int codeUnit = jsString.codeUnitAtUnchecked(i);
|
||||
if (codeUnit > 255) break two_byte;
|
||||
array.write(i, codeUnit);
|
||||
}
|
||||
''', s.toExternRef);
|
||||
return oneByteString;
|
||||
}
|
||||
final twoByteString = TwoByteString.withLength(length);
|
||||
final array = twoByteString.array;
|
||||
for (int i = 0; i < length; ++i) {
|
||||
array.write(i, jsString.codeUnitAtUnchecked(i));
|
||||
}
|
||||
return twoByteString;
|
||||
}
|
||||
|
||||
@pragma("wasm:import", "wasm:js-string.fromCharCodeArray")
|
||||
external WasmExternRef _jsStringFromCharCodeArray(
|
||||
WasmArray<WasmI16> array,
|
||||
WasmI32 start,
|
||||
WasmI32 end,
|
||||
);
|
||||
|
||||
@pragma('wasm:prefer-inline')
|
||||
void _copyFromWasmI8Array(
|
||||
WasmExternRef jsArray,
|
||||
@@ -541,219 +464,6 @@ WasmExternRef jsDataViewFromDartByteData(ByteData l, int length) {
|
||||
);
|
||||
}
|
||||
|
||||
@pragma("wasm:export", "\$stringAllocate1")
|
||||
OneByteString _stringAllocate1(WasmI32 length) {
|
||||
return OneByteString.withLength(length.toIntSigned());
|
||||
}
|
||||
|
||||
@pragma("wasm:export", "\$stringAllocate1_1")
|
||||
OneByteString _stringAllocate1_1(WasmI32 a0) {
|
||||
final result = OneByteString.withLength(1);
|
||||
result.setUnchecked(0, a0.toIntSigned());
|
||||
return result;
|
||||
}
|
||||
|
||||
@pragma("wasm:export", "\$stringAllocate1_2")
|
||||
OneByteString _stringAllocate1_2(WasmI32 a0, WasmI32 a1) {
|
||||
final result = OneByteString.withLength(2);
|
||||
result.setUnchecked(1, a1.toIntSigned());
|
||||
result.setUnchecked(0, a0.toIntSigned());
|
||||
return result;
|
||||
}
|
||||
|
||||
@pragma("wasm:export", "\$stringAllocate1_3")
|
||||
OneByteString _stringAllocate1_3(WasmI32 a0, WasmI32 a1, WasmI32 a2) {
|
||||
final result = OneByteString.withLength(3);
|
||||
result.setUnchecked(2, a2.toIntSigned());
|
||||
result.setUnchecked(1, a1.toIntSigned());
|
||||
result.setUnchecked(0, a0.toIntSigned());
|
||||
return result;
|
||||
}
|
||||
|
||||
@pragma("wasm:export", "\$stringAllocate1_4")
|
||||
OneByteString _stringAllocate1_4(
|
||||
WasmI32 a0,
|
||||
WasmI32 a1,
|
||||
WasmI32 a2,
|
||||
WasmI32 a3,
|
||||
) {
|
||||
final result = OneByteString.withLength(4);
|
||||
result.setUnchecked(3, a3.toIntSigned());
|
||||
result.setUnchecked(2, a2.toIntSigned());
|
||||
result.setUnchecked(1, a1.toIntSigned());
|
||||
result.setUnchecked(0, a0.toIntSigned());
|
||||
return result;
|
||||
}
|
||||
|
||||
@pragma("wasm:export", "\$stringAllocate1_5")
|
||||
OneByteString _stringAllocate1_5(
|
||||
WasmI32 a0,
|
||||
WasmI32 a1,
|
||||
WasmI32 a2,
|
||||
WasmI32 a3,
|
||||
WasmI32 a4,
|
||||
) {
|
||||
final result = OneByteString.withLength(5);
|
||||
result.setUnchecked(4, a4.toIntSigned());
|
||||
result.setUnchecked(3, a3.toIntSigned());
|
||||
result.setUnchecked(2, a2.toIntSigned());
|
||||
result.setUnchecked(1, a1.toIntSigned());
|
||||
result.setUnchecked(0, a0.toIntSigned());
|
||||
return result;
|
||||
}
|
||||
|
||||
@pragma("wasm:export", "\$stringAllocate1_6")
|
||||
OneByteString _stringAllocate1_6(
|
||||
WasmI32 a0,
|
||||
WasmI32 a1,
|
||||
WasmI32 a2,
|
||||
WasmI32 a3,
|
||||
WasmI32 a4,
|
||||
WasmI32 a5,
|
||||
) {
|
||||
final result = OneByteString.withLength(6);
|
||||
result.setUnchecked(5, a5.toIntSigned());
|
||||
result.setUnchecked(4, a4.toIntSigned());
|
||||
result.setUnchecked(3, a3.toIntSigned());
|
||||
result.setUnchecked(2, a2.toIntSigned());
|
||||
result.setUnchecked(1, a1.toIntSigned());
|
||||
result.setUnchecked(0, a0.toIntSigned());
|
||||
return result;
|
||||
}
|
||||
|
||||
@pragma("wasm:export", "\$stringAllocate1_7")
|
||||
OneByteString _stringAllocate1_7(
|
||||
WasmI32 a0,
|
||||
WasmI32 a1,
|
||||
WasmI32 a2,
|
||||
WasmI32 a3,
|
||||
WasmI32 a4,
|
||||
WasmI32 a5,
|
||||
WasmI32 a6,
|
||||
) {
|
||||
final result = OneByteString.withLength(7);
|
||||
result.setUnchecked(6, a6.toIntSigned());
|
||||
result.setUnchecked(5, a5.toIntSigned());
|
||||
result.setUnchecked(4, a4.toIntSigned());
|
||||
result.setUnchecked(3, a3.toIntSigned());
|
||||
result.setUnchecked(2, a2.toIntSigned());
|
||||
result.setUnchecked(1, a1.toIntSigned());
|
||||
result.setUnchecked(0, a0.toIntSigned());
|
||||
return result;
|
||||
}
|
||||
|
||||
@pragma("wasm:export", "\$stringAllocate1_8")
|
||||
OneByteString _stringAllocate1_8(
|
||||
WasmI32 a0,
|
||||
WasmI32 a1,
|
||||
WasmI32 a2,
|
||||
WasmI32 a3,
|
||||
WasmI32 a4,
|
||||
WasmI32 a5,
|
||||
WasmI32 a6,
|
||||
WasmI32 a7,
|
||||
) {
|
||||
final result = OneByteString.withLength(8);
|
||||
result.setUnchecked(7, a7.toIntSigned());
|
||||
result.setUnchecked(6, a6.toIntSigned());
|
||||
result.setUnchecked(5, a5.toIntSigned());
|
||||
result.setUnchecked(4, a4.toIntSigned());
|
||||
result.setUnchecked(3, a3.toIntSigned());
|
||||
result.setUnchecked(2, a2.toIntSigned());
|
||||
result.setUnchecked(1, a1.toIntSigned());
|
||||
result.setUnchecked(0, a0.toIntSigned());
|
||||
return result;
|
||||
}
|
||||
|
||||
@pragma("wasm:export", "\$stringAllocate1_9")
|
||||
OneByteString _stringAllocate1_9(
|
||||
WasmI32 a0,
|
||||
WasmI32 a1,
|
||||
WasmI32 a2,
|
||||
WasmI32 a3,
|
||||
WasmI32 a4,
|
||||
WasmI32 a5,
|
||||
WasmI32 a6,
|
||||
WasmI32 a7,
|
||||
WasmI32 a8,
|
||||
) {
|
||||
final result = OneByteString.withLength(9);
|
||||
result.setUnchecked(8, a8.toIntSigned());
|
||||
result.setUnchecked(7, a7.toIntSigned());
|
||||
result.setUnchecked(6, a6.toIntSigned());
|
||||
result.setUnchecked(5, a5.toIntSigned());
|
||||
result.setUnchecked(4, a4.toIntSigned());
|
||||
result.setUnchecked(3, a3.toIntSigned());
|
||||
result.setUnchecked(2, a2.toIntSigned());
|
||||
result.setUnchecked(1, a1.toIntSigned());
|
||||
result.setUnchecked(0, a0.toIntSigned());
|
||||
return result;
|
||||
}
|
||||
|
||||
@pragma("wasm:export", "\$stringAllocate1_10")
|
||||
OneByteString _stringAllocate1_10(
|
||||
WasmI32 a0,
|
||||
WasmI32 a1,
|
||||
WasmI32 a2,
|
||||
WasmI32 a3,
|
||||
WasmI32 a4,
|
||||
WasmI32 a5,
|
||||
WasmI32 a6,
|
||||
WasmI32 a7,
|
||||
WasmI32 a8,
|
||||
WasmI32 a9,
|
||||
) {
|
||||
final result = OneByteString.withLength(10);
|
||||
result.setUnchecked(9, a9.toIntSigned());
|
||||
result.setUnchecked(8, a8.toIntSigned());
|
||||
result.setUnchecked(7, a7.toIntSigned());
|
||||
result.setUnchecked(6, a6.toIntSigned());
|
||||
result.setUnchecked(5, a5.toIntSigned());
|
||||
result.setUnchecked(4, a4.toIntSigned());
|
||||
result.setUnchecked(3, a3.toIntSigned());
|
||||
result.setUnchecked(2, a2.toIntSigned());
|
||||
result.setUnchecked(1, a1.toIntSigned());
|
||||
result.setUnchecked(0, a0.toIntSigned());
|
||||
return result;
|
||||
}
|
||||
|
||||
@pragma("wasm:export", "\$stringRead1")
|
||||
WasmI32 _stringRead1(WasmExternRef? ref, WasmI32 index) {
|
||||
final string = unsafeCastOpaque<OneByteString>(
|
||||
unsafeCast<WasmExternRef>(ref).internalize(),
|
||||
);
|
||||
return string.codeUnitAtUnchecked(index.toIntSigned()).toWasmI32();
|
||||
}
|
||||
|
||||
@pragma("wasm:export", "\$stringWrite1")
|
||||
void _stringWrite1(WasmExternRef? ref, WasmI32 index, WasmI32 codePoint) {
|
||||
final string = unsafeCastOpaque<OneByteString>(
|
||||
unsafeCast<WasmExternRef>(ref).internalize(),
|
||||
);
|
||||
string.setUnchecked(index.toIntSigned(), codePoint.toIntSigned());
|
||||
}
|
||||
|
||||
@pragma("wasm:export", "\$stringAllocate2")
|
||||
TwoByteString _stringAllocate2(WasmI32 length) {
|
||||
return TwoByteString.withLength(length.toIntSigned());
|
||||
}
|
||||
|
||||
@pragma("wasm:export", "\$stringRead2")
|
||||
WasmI32 _stringRead2(WasmExternRef? ref, WasmI32 index) {
|
||||
final string = unsafeCastOpaque<TwoByteString>(
|
||||
unsafeCast<WasmExternRef>(ref).internalize(),
|
||||
);
|
||||
return string.codeUnitAtUnchecked(index.toIntSigned()).toWasmI32();
|
||||
}
|
||||
|
||||
@pragma("wasm:export", "\$stringWrite2")
|
||||
void _stringWrite2(WasmExternRef? ref, WasmI32 index, WasmI32 codePoint) {
|
||||
final string = unsafeCastOpaque<TwoByteString>(
|
||||
unsafeCast<WasmExternRef>(ref).internalize(),
|
||||
);
|
||||
string.setUnchecked(index.toIntSigned(), codePoint.toIntSigned());
|
||||
}
|
||||
|
||||
@pragma("wasm:export", "\$wasmI8ArrayGet")
|
||||
WasmI32 _wasmI8ArrayGet(WasmExternRef? ref, WasmI32 index) {
|
||||
final array = unsafeCastOpaque<WasmArray<WasmI8>>(
|
||||
|
||||
@@ -60,7 +60,6 @@ final class JSStringImpl implements String, StringUncheckedOperationsBase {
|
||||
final s = o.toString();
|
||||
final jsString =
|
||||
s is JSStringImpl ? js.JSValue.boxT<JSAny?>(s.toExternRef) : s.toJS;
|
||||
// array._setUnchecked(i, jsString);
|
||||
array[i] = jsString;
|
||||
}
|
||||
return JSStringImpl(
|
||||
|
||||
@@ -31,6 +31,9 @@ extension OneByteStringUncheckedOperations on OneByteString {
|
||||
|
||||
@pragma('wasm:prefer-inline')
|
||||
void setUnchecked(int index, int codePoint) => _setAt(index, codePoint);
|
||||
|
||||
@pragma('wasm:prefer-inline')
|
||||
WasmArray<WasmI8> get array => _array;
|
||||
}
|
||||
|
||||
extension TwoByteStringUncheckedOperations on TwoByteString {
|
||||
@@ -43,6 +46,9 @@ extension TwoByteStringUncheckedOperations on TwoByteString {
|
||||
|
||||
@pragma('wasm:prefer-inline')
|
||||
void setUnchecked(int index, int codePoint) => _setAt(index, codePoint);
|
||||
|
||||
@pragma('wasm:prefer-inline')
|
||||
WasmArray<WasmI16> get array => _array;
|
||||
}
|
||||
|
||||
/// Static function for `OneByteString._array` to avoid making `_array` public.
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
// 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:js_interop';
|
||||
|
||||
import 'package:expect/expect.dart';
|
||||
|
||||
main() async {
|
||||
final String oneByteString = makeLongString('012345789');
|
||||
final String twoByteString = makeLongString('01234中6789');
|
||||
|
||||
Expect.equals(oneByteString, roundTrip(oneByteString));
|
||||
Expect.equals(twoByteString, roundTrip(twoByteString));
|
||||
}
|
||||
|
||||
/// Ensure we make a very long string, ensuring that we'll also hit slow paths
|
||||
/// in the string copy implementation.
|
||||
String makeLongString(String string) {
|
||||
while (string.length < 1024 * 1024) {
|
||||
string = string + string;
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
/// Copies the string to JS and back again to a dart internal String.
|
||||
String roundTrip(String dartString) {
|
||||
final JSString jsString = dartString.toJS;
|
||||
|
||||
// Using string interpolation will force conversion to internal string (vs
|
||||
// `JSStringImpl`)
|
||||
final string = 'A${jsString.toDart}Z';
|
||||
return string.substring(1, string.length - 1);
|
||||
}
|
||||
Reference in New Issue
Block a user