[dart2wasm] Use WasmArray<>s for string interpolation expressions
* Use `WasmArray<>`s in string interoplation expressions
* Use `WasmArray<>`s in string buffer implementation
* Avoid making OneByteString+JSString imply TwoByteString
This also increases performance significantly, e.g.
JsonObjectRoundTrip & StringBuffer by 40% in -O4 and even more
speedup in -O2
Change-Id: I25485a6c532c3afed7d92943b6de4d1452930606
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/352000
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
diff --git a/pkg/dart2wasm/lib/code_generator.dart b/pkg/dart2wasm/lib/code_generator.dart
index 0eb61bb..080ee16 100644
--- a/pkg/dart2wasm/lib/code_generator.dart
+++ b/pkg/dart2wasm/lib/code_generator.dart
@@ -2854,10 +2854,8 @@
return visitStringLiteral(expr, expectedType);
}
- makeListFromExpressions(
- node.expressions,
- InterfaceType(
- translator.coreTypes.stringClass, Nullability.nonNullable));
+ makeArrayFromExpressions(node.expressions,
+ translator.coreTypes.objectRawType(Nullability.nullable));
return translator.outputOrVoid(call(translator.options.jsCompatibility
? translator.jsStringInterpolate.reference
: translator.stringInterpolate.reference));
diff --git a/pkg/dart2wasm/lib/kernel_nodes.dart b/pkg/dart2wasm/lib/kernel_nodes.dart
index 69d1dad..8f8bea1 100644
--- a/pkg/dart2wasm/lib/kernel_nodes.dart
+++ b/pkg/dart2wasm/lib/kernel_nodes.dart
@@ -176,7 +176,7 @@
late final Procedure jsStringEquals =
index.getProcedure("dart:_js_types", "JSStringImpl", "==");
late final Procedure jsStringInterpolate =
- index.getProcedure("dart:_js_types", "JSStringImpl", "interpolate");
+ index.getProcedure("dart:_js_types", "JSStringImpl", "_interpolate");
// dart:collection procedures
late final Procedure mapFactory =
diff --git a/sdk/lib/_internal/wasm/lib/js_string.dart b/sdk/lib/_internal/wasm/lib/js_string.dart
index 1db58a5..af47437 100644
--- a/sdk/lib/_internal/wasm/lib/js_string.dart
+++ b/sdk/lib/_internal/wasm/lib/js_string.dart
@@ -29,7 +29,7 @@
bool get isNotEmpty => !isEmpty;
@pragma("wasm:entry-point")
- static String interpolate(List<Object?> values) {
+ static String _interpolate(WasmArray<Object?> values) {
final valuesLength = values.length;
final array = JSArrayImpl.fromLength(valuesLength);
for (int i = 0; i < valuesLength; i++) {
diff --git a/sdk/lib/_internal/wasm/lib/string.dart b/sdk/lib/_internal/wasm/lib/string.dart
index 3989b37..56ce152 100644
--- a/sdk/lib/_internal/wasm/lib/string.dart
+++ b/sdk/lib/_internal/wasm/lib/string.dart
@@ -13,7 +13,7 @@
unsafeCast,
WasmStringBase;
-import 'dart:_js_helper' show JS;
+import 'dart:_js_helper' show JS, jsStringToDartString;
import 'dart:_js_types' show JSStringImpl;
import 'dart:_object_helper';
import 'dart:_string_helper';
@@ -299,7 +299,9 @@
bool get isNotEmpty => !isEmpty;
- String operator +(String other) => _interpolate([this, other]);
+ String operator +(String other) {
+ return _interpolate(WasmArray<Object?>.literal([this, other]));
+ }
String toString() {
return this;
@@ -891,34 +893,30 @@
return buffer.toString();
}
- /**
- * Convert all objects in [values] to strings and concat them
- * into a result string.
- * Modifies the input list if it contains non-`String` values.
- */
+ // Used in string interpolation expressions where ownership of array is passed
+ // to this function.
+ //
+ // It special cases all [OneByteString]s. We could also special case all
+ // [TwoByteString] & all [JSStringImpl] cases.
@pragma("wasm:entry-point", "call")
- static String _interpolate(final List<Object?> values) {
- final numValues = values.length;
+ static String _interpolate(final WasmArray<Object?> values) {
int totalLength = 0;
- int i = 0;
- while (i < numValues) {
- final e = values[i];
- final s = e.toString();
- values[i] = s;
- if (s is OneByteString) {
- totalLength += s.length;
- i++;
- } else {
- // Handle remaining elements without checking for one-byte-ness.
- while (++i < numValues) {
- final e = values[i];
- values[i] = e.toString();
- }
- return _concatRangeNative(values, 0, numValues);
+ bool isOneByteString = true;
+ final numValues = values.length;
+ for (int i = 0; i < numValues; ++i) {
+ final value = values[i];
+ var stringValue = value is String ? value : value.toString();
+ if (stringValue is JSStringImpl) {
+ stringValue = jsStringToDartString(stringValue.toExternRef);
}
+ values[i] = stringValue;
+ isOneByteString = isOneByteString && stringValue is OneByteString;
+ totalLength += stringValue.length;
}
- // All strings were one-byte strings.
- return OneByteString._concatAll(values, totalLength);
+ if (isOneByteString) {
+ return OneByteString._concatAll(values, totalLength);
+ }
+ return StringBase._concatAllFallback(values, totalLength);
}
static ArgumentError _interpolationError(Object? o, Object? result) {
@@ -992,40 +990,52 @@
String toLowerCase() => _toLowerCase(this);
+ // To be called if not all of the given [StringBase] strings are
+ // [OneByteString]s.
+ static String _concatAllFallback(
+ WasmArray<Object?> strings, int totalLength) {
+ final result = TwoByteString.withLength(totalLength);
+ int offset = 0;
+ for (int i = 0; i < strings.length; i++) {
+ final s = unsafeCast<StringBase>(strings[i]);
+ offset = s._copyIntoTwoByteString(result, offset);
+ }
+ return result;
+ }
+
// Concatenate ['start', 'end'[ elements of 'strings'.
- static String concatRange(List<String> strings, int start, int end) {
+ //
+ // It special cases all [OneByteString]s. We could also special case all
+ // [TwoByteString] & all [JSStringImpl] cases.
+ static String concatRange(WasmArray<String> strings, int start, int end) {
if ((end - start) == 1) {
return strings[start];
}
- return _concatRangeNative(strings, start, end);
+ int totalLength = 0;
+ bool isOneByteString = true;
+ for (int i = start; i < end; i++) {
+ String stringValue = strings[i];
+ if (stringValue is JSStringImpl) {
+ stringValue = jsStringToDartString(stringValue.toExternRef);
+ strings[i] = stringValue;
+ }
+ isOneByteString = isOneByteString && stringValue is OneByteString;
+ totalLength += stringValue.length;
+ }
+ if (isOneByteString) {
+ return OneByteString._concatRange(strings, start, end, totalLength);
+ }
+ return _concatRangeFallback(strings, start, end, totalLength);
}
- // Call this method if all elements of [strings] are known to be strings
- // but not all are known to be OneByteString(s).
- static String _concatRangeNative(List<Object?> strings, int start, int end) {
- int totalLength = 0;
- for (int i = start; i < end; i++) {
- final str = strings[i];
- if (str is JSStringImpl) {
- totalLength += str.length;
- } else {
- totalLength += unsafeCast<StringBase>(str).length;
- }
- }
- TwoByteString result = TwoByteString.withLength(totalLength);
+ // To be called if not all strings are [OneByteString]s.
+ static String _concatRangeFallback(
+ WasmArray<String> strings, int start, int end, int totalLength) {
+ final result = TwoByteString.withLength(totalLength);
int offset = 0;
for (int i = start; i < end; i++) {
- final str = strings[i];
- if (str is JSStringImpl) {
- final length = str.length;
- final to = result._array;
- for (int j = 0; j < length; j++) {
- to.write(offset++, str.codeUnitAt(j));
- }
- } else {
- StringBase s = unsafeCast<StringBase>(strings[i]);
- offset = s._copyIntoTwoByteString(result, offset);
- }
+ final s = unsafeCast<StringBase>(strings[i]);
+ offset = s._copyIntoTwoByteString(result, offset);
}
return result;
}
@@ -1036,7 +1046,7 @@
@pragma("wasm:entry-point")
final class OneByteString extends StringBase {
@pragma("wasm:entry-point")
- WasmArray<WasmI8> _array;
+ final WasmArray<WasmI8> _array;
OneByteString.withLength(int length) : _array = WasmArray<WasmI8>(length);
@@ -1101,16 +1111,28 @@
}
// All element of 'strings' must be OneByteStrings.
- static _concatAll(List strings, int totalLength) {
+ static OneByteString _concatAll(WasmArray<Object?> strings, int totalLength) {
final result = OneByteString.withLength(totalLength);
- final to = result._array;
- final stringsLength = strings.length;
+ final resultBytes = result._array;
int resultOffset = 0;
- for (int s = 0; s < stringsLength; s++) {
- final OneByteString e = unsafeCast<OneByteString>(strings[s]);
- final length = e._array.length;
- to.copy(resultOffset, e._array, 0, length);
- resultOffset += length;
+ for (int i = 0; i < strings.length; i++) {
+ final bytes = unsafeCast<OneByteString>(strings[i])._array;
+ resultBytes.copy(resultOffset, bytes, 0, bytes.length);
+ resultOffset += bytes.length;
+ }
+ return result;
+ }
+
+ // All element of 'strings' must be OneByteStrings.
+ static OneByteString _concatRange(
+ WasmArray<String> strings, int start, int end, int totalLength) {
+ final result = OneByteString.withLength(totalLength);
+ final resultBytes = result._array;
+ int resultOffset = 0;
+ for (int i = start; i < end; i++) {
+ final bytes = unsafeCast<OneByteString>(strings[i])._array;
+ resultBytes.copy(resultOffset, bytes, 0, bytes.length);
+ resultOffset += bytes.length;
}
return result;
}
@@ -1360,7 +1382,7 @@
@pragma("wasm:entry-point")
final class TwoByteString extends StringBase {
@pragma("wasm:entry-point")
- WasmArray<WasmI16> _array;
+ final WasmArray<WasmI16> _array;
TwoByteString.withLength(int length) : _array = WasmArray<WasmI16>(length);
diff --git a/sdk/lib/_internal/wasm/lib/string_buffer_patch.dart b/sdk/lib/_internal/wasm/lib/string_buffer_patch.dart
index a6d0de0..d5d74d8 100644
--- a/sdk/lib/_internal/wasm/lib/string_buffer_patch.dart
+++ b/sdk/lib/_internal/wasm/lib/string_buffer_patch.dart
@@ -4,6 +4,7 @@
import "dart:_internal" show patch;
import "dart:_string" show StringBase, TwoByteString;
+import "dart:_wasm";
import "dart:typed_data" show Uint16List;
@@ -17,7 +18,8 @@
* When strings are written to the string buffer, we add them to a
* list of string parts.
*/
- List<String>? _parts;
+ WasmArray<String>? _parts = null;
+ int _partsWriteIndex = -1;
/**
* Total number of code units in the string parts. Does not include
@@ -120,6 +122,7 @@
@patch
void clear() {
_parts = null;
+ _partsWriteIndex = -1;
_partsCodeUnits = _bufferPosition = _bufferCodeUnitMagnitude = 0;
}
@@ -130,7 +133,7 @@
final localParts = _parts;
return (_partsCodeUnits == 0 || localParts == null)
? ""
- : StringBase.concatRange(localParts, 0, localParts.length);
+ : StringBase.concatRange(localParts, 0, _partsWriteIndex);
}
/** Ensures that the buffer has enough capacity to add n code units. */
@@ -161,23 +164,29 @@
* many code units are contained in the parts.
*/
void _addPart(String str) {
- final localParts = _parts;
+ WasmArray<String>? localParts = _parts;
int length = str.length;
_partsCodeUnits += length;
_partsCodeUnitsSinceCompaction += length;
if (localParts == null) {
- // Empirically this is a good capacity to minimize total bytes allocated.
- // _parts = _GrowableList.withCapacity(10)..add(str);
- // TODO(omersa): Uncomment the line above after moving list
- // implementations to a library.
- _parts = [str];
- } else {
- localParts.add(str);
- int partsSinceCompaction = localParts.length - _partsCompactionIndex;
- if (partsSinceCompaction == _PARTS_TO_COMPACT) {
- _compact();
- }
+ _parts = WasmArray<String>.filled(10, str);
+ _partsWriteIndex = 1;
+ return;
+ }
+
+ // Possibly grow the backing array.
+ if (localParts.length <= _partsWriteIndex) {
+ localParts =
+ WasmArray<String>.filled(2 * localParts.length, localParts[0])
+ ..copy(0, localParts, 0, _partsWriteIndex);
+ _parts = localParts;
+ }
+
+ localParts[_partsWriteIndex++] = str;
+ int partsSinceCompaction = _partsWriteIndex - _partsCompactionIndex;
+ if (partsSinceCompaction == _PARTS_TO_COMPACT) {
+ _compact();
}
}
@@ -193,11 +202,11 @@
_partsCompactionIndex, // Start
_partsCompactionIndex + _PARTS_TO_COMPACT // End
);
- localParts.length = localParts.length - _PARTS_TO_COMPACT;
- localParts.add(compacted);
+ _partsWriteIndex = _partsWriteIndex - _PARTS_TO_COMPACT;
+ localParts[_partsWriteIndex++] = compacted;
}
_partsCodeUnitsSinceCompaction = 0;
- _partsCompactionIndex = localParts.length;
+ _partsCompactionIndex = _partsWriteIndex;
}
/**