Version 3.3.0-82.0.dev
Merge 60d1a40462e0b489a68e9f7a1fe159e9edac19be into dev
diff --git a/pkg/dart2wasm/lib/class_info.dart b/pkg/dart2wasm/lib/class_info.dart
index 85afb84..1667dc0 100644
--- a/pkg/dart2wasm/lib/class_info.dart
+++ b/pkg/dart2wasm/lib/class_info.dart
@@ -54,6 +54,7 @@
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 @@
check(translator.boxedBoolClass, "value", FieldIndex.boxValue);
check(translator.boxedIntClass, "value", FieldIndex.boxValue);
check(translator.boxedDoubleClass, "value", FieldIndex.boxValue);
- check(translator.oneByteStringClass, "_array", FieldIndex.stringArray);
- check(translator.twoByteStringClass, "_array", FieldIndex.stringArray);
+ 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 @@
/// 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 @@
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 @@
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]!;
diff --git a/pkg/dart2wasm/lib/code_generator.dart b/pkg/dart2wasm/lib/code_generator.dart
index 75953b9..e813c68 100644
--- a/pkg/dart2wasm/lib/code_generator.dart
+++ b/pkg/dart2wasm/lib/code_generator.dart
@@ -2837,8 +2837,9 @@
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 @@
.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;
diff --git a/pkg/dart2wasm/lib/compile.dart b/pkg/dart2wasm/lib/compile.dart
index 2f2b968..e642e12 100644
--- a/pkg/dart2wasm/lib/compile.dart
+++ b/pkg/dart2wasm/lib/compile.dart
@@ -151,7 +151,9 @@
}
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);
}
diff --git a/pkg/dart2wasm/lib/constants.dart b/pkg/dart2wasm/lib/constants.dart
index a17d387..a8f60b3 100644
--- a/pkg/dart2wasm/lib/constants.dart
+++ b/pkg/dart2wasm/lib/constants.dart
@@ -322,6 +322,15 @@
@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 @@
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,
diff --git a/pkg/dart2wasm/lib/js/runtime_blob.dart b/pkg/dart2wasm/lib/js/runtime_blob.dart
index fecb1fd..254cf82 100644
--- a/pkg/dart2wasm/lib/js/runtime_blob.dart
+++ b/pkg/dart2wasm/lib/js/runtime_blob.dart
@@ -14,6 +14,10 @@
// 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 @@
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 @@
// 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,
diff --git a/pkg/dart2wasm/lib/js/runtime_generator.dart b/pkg/dart2wasm/lib/js/runtime_generator.dart
index 7792cff..343d3f6 100644
--- a/pkg/dart2wasm/lib/js/runtime_generator.dart
+++ b/pkg/dart2wasm/lib/js/runtime_generator.dart
@@ -8,10 +8,13 @@
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 @@
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 @@
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
''';
}
}
diff --git a/pkg/dart2wasm/lib/kernel_nodes.dart b/pkg/dart2wasm/lib/kernel_nodes.dart
index 5663bff3..d7452f4 100644
--- a/pkg/dart2wasm/lib/kernel_nodes.dart
+++ b/pkg/dart2wasm/lib/kernel_nodes.dart
@@ -12,18 +12,24 @@
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 @@
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");
diff --git a/pkg/dart2wasm/lib/target.dart b/pkg/dart2wasm/lib/target.dart
index 922a962..6c034b6 100644
--- a/pkg/dart2wasm/lib/target.dart
+++ b/pkg/dart2wasm/lib/target.dart
@@ -102,6 +102,7 @@
Class? _wasmImmutableSet;
Class? _oneByteString;
Class? _twoByteString;
+ Class? _jsString;
Class? _closure;
Map<String, Class>? _nativeClasses;
@@ -141,12 +142,11 @@
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 @@
'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 @@
@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) {
diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart
index 58c0f25..c7ec7d4 100644
--- a/pkg/dart2wasm/lib/translator.dart
+++ b/pkg/dart2wasm/lib/translator.dart
@@ -84,6 +84,10 @@
/// [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 @@
boxedIntClass: boxedIntClass,
boxedDoubleClass: boxedDoubleClass,
boxedBoolClass: coreTypes.boolClass,
- oneByteStringClass: stringBaseClass,
- twoByteStringClass: stringBaseClass,
+ if (!options.jsCompatibility) ...{
+ oneByteStringClass: stringBaseClass,
+ twoByteStringClass: stringBaseClass
+ },
+ if (options.jsCompatibility) ...{jsStringClass: jsStringClass},
};
/// Type for vtable entries for dynamic calls. These entries are used in
@@ -1026,6 +1033,19 @@
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 {
diff --git a/pkg/front_end/lib/src/fasta/fasta_codes_cfe_generated.dart b/pkg/front_end/lib/src/fasta/fasta_codes_cfe_generated.dart
index 38b166f..c6e06c3 100644
--- a/pkg/front_end/lib/src/fasta/fasta_codes_cfe_generated.dart
+++ b/pkg/front_end/lib/src/fasta/fasta_codes_cfe_generated.dart
@@ -2799,7 +2799,7 @@
DartType _type3, bool isNonNullableByDefault)>(
"InvalidExtensionTypeSuperExtensionType",
problemMessageTemplate:
- r"""The representation type '#type' of extension type '#name' must be a subtype of the representation type '#type2' of the implemented extension type '#type3'.""",
+ r"""The representation type '#type' of extension type '#name' must be either a subtype of the representation type '#type2' of the implemented extension type '#type3' or a subtype of '#type3' itself.""",
correctionMessageTemplate:
r"""Try changing the representation type to a subtype of '#type2'.""",
withArguments: _withArgumentsInvalidExtensionTypeSuperExtensionType);
@@ -2832,7 +2832,7 @@
String type3 = type3Parts.join();
return new Message(codeInvalidExtensionTypeSuperExtensionType,
problemMessage:
- """The representation type '${type}' of extension type '${name}' must be a subtype of the representation type '${type2}' of the implemented extension type '${type3}'.""" +
+ """The representation type '${type}' of extension type '${name}' must be either a subtype of the representation type '${type2}' of the implemented extension type '${type3}' or a subtype of '${type3}' itself.""" +
labeler.originMessages,
correctionMessage: """Try changing the representation type to a subtype of '${type2}'.""",
arguments: {
diff --git a/pkg/front_end/lib/src/fasta/source/source_extension_type_declaration_builder.dart b/pkg/front_end/lib/src/fasta/source/source_extension_type_declaration_builder.dart
index f4ec217..6134cbf 100644
--- a/pkg/front_end/lib/src/fasta/source/source_extension_type_declaration_builder.dart
+++ b/pkg/front_end/lib/src/fasta/source/source_extension_type_declaration_builder.dart
@@ -458,23 +458,27 @@
typeBuilder.fileUri);
}
} else if (interface is ExtensionType) {
- DartType instantiatedRepresentationType =
- Substitution.fromExtensionType(interface).substituteType(interface
- .extensionTypeDeclaration.declaredRepresentationType);
- if (!hierarchyBuilder.types.isSubtypeOf(
- declaredRepresentationType,
- instantiatedRepresentationType,
- SubtypeCheckMode.withNullabilities)) {
- libraryBuilder.addProblem(
- templateInvalidExtensionTypeSuperExtensionType.withArguments(
- declaredRepresentationType,
- name,
- instantiatedRepresentationType,
- interface,
- true),
- typeBuilder.charOffset!,
- noLength,
- typeBuilder.fileUri);
+ if (!hierarchyBuilder.types.isSubtypeOf(declaredRepresentationType,
+ interface, SubtypeCheckMode.withNullabilities)) {
+ DartType instantiatedImplementedRepresentationType =
+ Substitution.fromExtensionType(interface).substituteType(
+ interface
+ .extensionTypeDeclaration.declaredRepresentationType);
+ if (!hierarchyBuilder.types.isSubtypeOf(
+ declaredRepresentationType,
+ instantiatedImplementedRepresentationType,
+ SubtypeCheckMode.withNullabilities)) {
+ libraryBuilder.addProblem(
+ templateInvalidExtensionTypeSuperExtensionType.withArguments(
+ declaredRepresentationType,
+ name,
+ instantiatedImplementedRepresentationType,
+ interface,
+ true),
+ typeBuilder.charOffset!,
+ noLength,
+ typeBuilder.fileUri);
+ }
}
}
}
diff --git a/pkg/front_end/messages.yaml b/pkg/front_end/messages.yaml
index a0ec14c..1df2721 100644
--- a/pkg/front_end/messages.yaml
+++ b/pkg/front_end/messages.yaml
@@ -7309,7 +7309,7 @@
extension type E(num n) implements int {}
InvalidExtensionTypeSuperExtensionType:
- problemMessage: "The representation type '#type' of extension type '#name' must be a subtype of the representation type '#type2' of the implemented extension type '#type3'."
+ problemMessage: "The representation type '#type' of extension type '#name' must be either a subtype of the representation type '#type2' of the implemented extension type '#type3' or a subtype of '#type3' itself."
correctionMessage: "Try changing the representation type to a subtype of '#type2'."
experiments: inline-class
script: |
diff --git a/pkg/front_end/testcases/extension_types/issue53170.dart.strong.expect b/pkg/front_end/testcases/extension_types/issue53170.dart.strong.expect
index bdfefed..fd28d0a 100644
--- a/pkg/front_end/testcases/extension_types/issue53170.dart.strong.expect
+++ b/pkg/front_end/testcases/extension_types/issue53170.dart.strong.expect
@@ -7,12 +7,12 @@
// extension type S1(num id) implements String /* Error */ {}
// ^
//
-// pkg/front_end/testcases/extension_types/issue53170.dart:11:41: Error: The representation type 'String' of extension type 'V2' must be a subtype of the representation type 'num' of the implemented extension type 'V1'.
+// pkg/front_end/testcases/extension_types/issue53170.dart:11:41: Error: The representation type 'String' of extension type 'V2' must be either a subtype of the representation type 'num' of the implemented extension type 'V1' or a subtype of 'V1' itself.
// Try changing the representation type to a subtype of 'num'.
// extension type V2(String id) implements V1 /* Error */ {}
// ^
//
-// pkg/front_end/testcases/extension_types/issue53170.dart:17:41: Error: The representation type 'String' of extension type 'W2' must be a subtype of the representation type 'num' of the implemented extension type 'W1<num>'.
+// pkg/front_end/testcases/extension_types/issue53170.dart:17:41: Error: The representation type 'String' of extension type 'W2' must be either a subtype of the representation type 'num' of the implemented extension type 'W1<num>' or a subtype of 'W1<num>' itself.
// Try changing the representation type to a subtype of 'num'.
// extension type W2(String id) implements W1<num> /* Error */ {}
// ^
diff --git a/pkg/front_end/testcases/extension_types/issue53170.dart.strong.transformed.expect b/pkg/front_end/testcases/extension_types/issue53170.dart.strong.transformed.expect
index bdfefed..fd28d0a 100644
--- a/pkg/front_end/testcases/extension_types/issue53170.dart.strong.transformed.expect
+++ b/pkg/front_end/testcases/extension_types/issue53170.dart.strong.transformed.expect
@@ -7,12 +7,12 @@
// extension type S1(num id) implements String /* Error */ {}
// ^
//
-// pkg/front_end/testcases/extension_types/issue53170.dart:11:41: Error: The representation type 'String' of extension type 'V2' must be a subtype of the representation type 'num' of the implemented extension type 'V1'.
+// pkg/front_end/testcases/extension_types/issue53170.dart:11:41: Error: The representation type 'String' of extension type 'V2' must be either a subtype of the representation type 'num' of the implemented extension type 'V1' or a subtype of 'V1' itself.
// Try changing the representation type to a subtype of 'num'.
// extension type V2(String id) implements V1 /* Error */ {}
// ^
//
-// pkg/front_end/testcases/extension_types/issue53170.dart:17:41: Error: The representation type 'String' of extension type 'W2' must be a subtype of the representation type 'num' of the implemented extension type 'W1<num>'.
+// pkg/front_end/testcases/extension_types/issue53170.dart:17:41: Error: The representation type 'String' of extension type 'W2' must be either a subtype of the representation type 'num' of the implemented extension type 'W1<num>' or a subtype of 'W1<num>' itself.
// Try changing the representation type to a subtype of 'num'.
// extension type W2(String id) implements W1<num> /* Error */ {}
// ^
diff --git a/pkg/front_end/testcases/extension_types/issue53170.dart.weak.expect b/pkg/front_end/testcases/extension_types/issue53170.dart.weak.expect
index bdfefed..fd28d0a 100644
--- a/pkg/front_end/testcases/extension_types/issue53170.dart.weak.expect
+++ b/pkg/front_end/testcases/extension_types/issue53170.dart.weak.expect
@@ -7,12 +7,12 @@
// extension type S1(num id) implements String /* Error */ {}
// ^
//
-// pkg/front_end/testcases/extension_types/issue53170.dart:11:41: Error: The representation type 'String' of extension type 'V2' must be a subtype of the representation type 'num' of the implemented extension type 'V1'.
+// pkg/front_end/testcases/extension_types/issue53170.dart:11:41: Error: The representation type 'String' of extension type 'V2' must be either a subtype of the representation type 'num' of the implemented extension type 'V1' or a subtype of 'V1' itself.
// Try changing the representation type to a subtype of 'num'.
// extension type V2(String id) implements V1 /* Error */ {}
// ^
//
-// pkg/front_end/testcases/extension_types/issue53170.dart:17:41: Error: The representation type 'String' of extension type 'W2' must be a subtype of the representation type 'num' of the implemented extension type 'W1<num>'.
+// pkg/front_end/testcases/extension_types/issue53170.dart:17:41: Error: The representation type 'String' of extension type 'W2' must be either a subtype of the representation type 'num' of the implemented extension type 'W1<num>' or a subtype of 'W1<num>' itself.
// Try changing the representation type to a subtype of 'num'.
// extension type W2(String id) implements W1<num> /* Error */ {}
// ^
diff --git a/pkg/front_end/testcases/extension_types/issue53170.dart.weak.modular.expect b/pkg/front_end/testcases/extension_types/issue53170.dart.weak.modular.expect
index bdfefed..fd28d0a 100644
--- a/pkg/front_end/testcases/extension_types/issue53170.dart.weak.modular.expect
+++ b/pkg/front_end/testcases/extension_types/issue53170.dart.weak.modular.expect
@@ -7,12 +7,12 @@
// extension type S1(num id) implements String /* Error */ {}
// ^
//
-// pkg/front_end/testcases/extension_types/issue53170.dart:11:41: Error: The representation type 'String' of extension type 'V2' must be a subtype of the representation type 'num' of the implemented extension type 'V1'.
+// pkg/front_end/testcases/extension_types/issue53170.dart:11:41: Error: The representation type 'String' of extension type 'V2' must be either a subtype of the representation type 'num' of the implemented extension type 'V1' or a subtype of 'V1' itself.
// Try changing the representation type to a subtype of 'num'.
// extension type V2(String id) implements V1 /* Error */ {}
// ^
//
-// pkg/front_end/testcases/extension_types/issue53170.dart:17:41: Error: The representation type 'String' of extension type 'W2' must be a subtype of the representation type 'num' of the implemented extension type 'W1<num>'.
+// pkg/front_end/testcases/extension_types/issue53170.dart:17:41: Error: The representation type 'String' of extension type 'W2' must be either a subtype of the representation type 'num' of the implemented extension type 'W1<num>' or a subtype of 'W1<num>' itself.
// Try changing the representation type to a subtype of 'num'.
// extension type W2(String id) implements W1<num> /* Error */ {}
// ^
diff --git a/pkg/front_end/testcases/extension_types/issue53170.dart.weak.outline.expect b/pkg/front_end/testcases/extension_types/issue53170.dart.weak.outline.expect
index 68e1630..abe59f8 100644
--- a/pkg/front_end/testcases/extension_types/issue53170.dart.weak.outline.expect
+++ b/pkg/front_end/testcases/extension_types/issue53170.dart.weak.outline.expect
@@ -7,12 +7,12 @@
// extension type S1(num id) implements String /* Error */ {}
// ^
//
-// pkg/front_end/testcases/extension_types/issue53170.dart:11:41: Error: The representation type 'String' of extension type 'V2' must be a subtype of the representation type 'num' of the implemented extension type 'V1'.
+// pkg/front_end/testcases/extension_types/issue53170.dart:11:41: Error: The representation type 'String' of extension type 'V2' must be either a subtype of the representation type 'num' of the implemented extension type 'V1' or a subtype of 'V1' itself.
// Try changing the representation type to a subtype of 'num'.
// extension type V2(String id) implements V1 /* Error */ {}
// ^
//
-// pkg/front_end/testcases/extension_types/issue53170.dart:17:41: Error: The representation type 'String' of extension type 'W2' must be a subtype of the representation type 'num' of the implemented extension type 'W1<num>'.
+// pkg/front_end/testcases/extension_types/issue53170.dart:17:41: Error: The representation type 'String' of extension type 'W2' must be either a subtype of the representation type 'num' of the implemented extension type 'W1<num>' or a subtype of 'W1<num>' itself.
// Try changing the representation type to a subtype of 'num'.
// extension type W2(String id) implements W1<num> /* Error */ {}
// ^
diff --git a/pkg/front_end/testcases/extension_types/issue53170.dart.weak.transformed.expect b/pkg/front_end/testcases/extension_types/issue53170.dart.weak.transformed.expect
index bdfefed..fd28d0a 100644
--- a/pkg/front_end/testcases/extension_types/issue53170.dart.weak.transformed.expect
+++ b/pkg/front_end/testcases/extension_types/issue53170.dart.weak.transformed.expect
@@ -7,12 +7,12 @@
// extension type S1(num id) implements String /* Error */ {}
// ^
//
-// pkg/front_end/testcases/extension_types/issue53170.dart:11:41: Error: The representation type 'String' of extension type 'V2' must be a subtype of the representation type 'num' of the implemented extension type 'V1'.
+// pkg/front_end/testcases/extension_types/issue53170.dart:11:41: Error: The representation type 'String' of extension type 'V2' must be either a subtype of the representation type 'num' of the implemented extension type 'V1' or a subtype of 'V1' itself.
// Try changing the representation type to a subtype of 'num'.
// extension type V2(String id) implements V1 /* Error */ {}
// ^
//
-// pkg/front_end/testcases/extension_types/issue53170.dart:17:41: Error: The representation type 'String' of extension type 'W2' must be a subtype of the representation type 'num' of the implemented extension type 'W1<num>'.
+// pkg/front_end/testcases/extension_types/issue53170.dart:17:41: Error: The representation type 'String' of extension type 'W2' must be either a subtype of the representation type 'num' of the implemented extension type 'W1<num>' or a subtype of 'W1<num>' itself.
// Try changing the representation type to a subtype of 'num'.
// extension type W2(String id) implements W1<num> /* Error */ {}
// ^
diff --git a/pkg/front_end/testcases/extension_types/issue53867.dart b/pkg/front_end/testcases/extension_types/issue53867.dart
new file mode 100644
index 0000000..daeb4e8
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/issue53867.dart
@@ -0,0 +1,11 @@
+// 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.
+
+extension type E1(num it) {}
+extension type E2(E1 it) implements E1 {} // Ok.
+extension type E3(int it) implements E1 {} // Ok.
+extension type E4(E3 it) implements E1 {} // Ok.
+extension type E5(E3 it) implements E2 {} // Ok.
+extension type E6(E2 it) implements E3 {} // Error.
+extension type E7(String it) implements E1 {} // Error.
diff --git a/pkg/front_end/testcases/extension_types/issue53867.dart.strong.expect b/pkg/front_end/testcases/extension_types/issue53867.dart.strong.expect
new file mode 100644
index 0000000..f9370be
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/issue53867.dart.strong.expect
@@ -0,0 +1,94 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/extension_types/issue53867.dart:10:37: Error: The representation type 'E2' of extension type 'E6' must be either a subtype of the representation type 'int' of the implemented extension type 'E3' or a subtype of 'E3' itself.
+// Try changing the representation type to a subtype of 'int'.
+// extension type E6(E2 it) implements E3 {} // Error.
+// ^
+//
+// pkg/front_end/testcases/extension_types/issue53867.dart:11:41: Error: The representation type 'String' of extension type 'E7' must be either a subtype of the representation type 'num' of the implemented extension type 'E1' or a subtype of 'E1' itself.
+// Try changing the representation type to a subtype of 'num'.
+// extension type E7(String it) implements E1 {} // Error.
+// ^
+//
+import self as self;
+import "dart:core" as core;
+
+extension type E1(core::num it) {
+ abstract inline-class-member representation-field get it() → core::num;
+ constructor • = self::E1|constructor#;
+ constructor tearoff • = self::E1|constructor#_#new#tearOff;
+}
+extension type E2(self::E1 /* = core::num */ it) implements self::E1 /* = core::num */ {
+ abstract inline-class-member representation-field get it() → self::E1 /* = core::num */;
+ constructor • = self::E2|constructor#;
+ constructor tearoff • = self::E2|constructor#_#new#tearOff;
+}
+extension type E3(core::int it) implements self::E1 /* = core::num */ {
+ abstract inline-class-member representation-field get it() → core::int;
+ constructor • = self::E3|constructor#;
+ constructor tearoff • = self::E3|constructor#_#new#tearOff;
+}
+extension type E4(self::E3 /* = core::int */ it) implements self::E1 /* = core::num */ {
+ abstract inline-class-member representation-field get it() → self::E3 /* = core::int */;
+ constructor • = self::E4|constructor#;
+ constructor tearoff • = self::E4|constructor#_#new#tearOff;
+}
+extension type E5(self::E3 /* = core::int */ it) implements self::E2 /* = core::num */ {
+ abstract inline-class-member representation-field get it() → self::E3 /* = core::int */;
+ constructor • = self::E5|constructor#;
+ constructor tearoff • = self::E5|constructor#_#new#tearOff;
+}
+extension type E6(self::E2 /* = core::num */ it) implements self::E3 /* = core::int */ {
+ abstract inline-class-member representation-field get it() → self::E2 /* = core::num */;
+ constructor • = self::E6|constructor#;
+ constructor tearoff • = self::E6|constructor#_#new#tearOff;
+}
+extension type E7(core::String it) implements self::E1 /* = core::num */ {
+ abstract inline-class-member representation-field get it() → core::String;
+ constructor • = self::E7|constructor#;
+ constructor tearoff • = self::E7|constructor#_#new#tearOff;
+}
+static inline-class-member method E1|constructor#(core::num it) → self::E1 /* = core::num */ {
+ lowered final self::E1 /* = core::num */ #this = it;
+ return #this;
+}
+static inline-class-member method E1|constructor#_#new#tearOff(core::num it) → self::E1 /* = core::num */
+ return self::E1|constructor#(it);
+static inline-class-member method E2|constructor#(self::E1 /* = core::num */ it) → self::E2 /* = core::num */ {
+ lowered final self::E2 /* = core::num */ #this = it;
+ return #this;
+}
+static inline-class-member method E2|constructor#_#new#tearOff(self::E1 /* = core::num */ it) → self::E2 /* = core::num */
+ return self::E2|constructor#(it);
+static inline-class-member method E3|constructor#(core::int it) → self::E3 /* = core::int */ {
+ lowered final self::E3 /* = core::int */ #this = it;
+ return #this;
+}
+static inline-class-member method E3|constructor#_#new#tearOff(core::int it) → self::E3 /* = core::int */
+ return self::E3|constructor#(it);
+static inline-class-member method E4|constructor#(self::E3 /* = core::int */ it) → self::E4 /* = core::int */ {
+ lowered final self::E4 /* = core::int */ #this = it;
+ return #this;
+}
+static inline-class-member method E4|constructor#_#new#tearOff(self::E3 /* = core::int */ it) → self::E4 /* = core::int */
+ return self::E4|constructor#(it);
+static inline-class-member method E5|constructor#(self::E3 /* = core::int */ it) → self::E5 /* = core::int */ {
+ lowered final self::E5 /* = core::int */ #this = it;
+ return #this;
+}
+static inline-class-member method E5|constructor#_#new#tearOff(self::E3 /* = core::int */ it) → self::E5 /* = core::int */
+ return self::E5|constructor#(it);
+static inline-class-member method E6|constructor#(self::E2 /* = core::num */ it) → self::E6 /* = core::num */ {
+ lowered final self::E6 /* = core::num */ #this = it;
+ return #this;
+}
+static inline-class-member method E6|constructor#_#new#tearOff(self::E2 /* = core::num */ it) → self::E6 /* = core::num */
+ return self::E6|constructor#(it);
+static inline-class-member method E7|constructor#(core::String it) → self::E7 /* = core::String */ {
+ lowered final self::E7 /* = core::String */ #this = it;
+ return #this;
+}
+static inline-class-member method E7|constructor#_#new#tearOff(core::String it) → self::E7 /* = core::String */
+ return self::E7|constructor#(it);
diff --git a/pkg/front_end/testcases/extension_types/issue53867.dart.strong.transformed.expect b/pkg/front_end/testcases/extension_types/issue53867.dart.strong.transformed.expect
new file mode 100644
index 0000000..f9370be
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/issue53867.dart.strong.transformed.expect
@@ -0,0 +1,94 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/extension_types/issue53867.dart:10:37: Error: The representation type 'E2' of extension type 'E6' must be either a subtype of the representation type 'int' of the implemented extension type 'E3' or a subtype of 'E3' itself.
+// Try changing the representation type to a subtype of 'int'.
+// extension type E6(E2 it) implements E3 {} // Error.
+// ^
+//
+// pkg/front_end/testcases/extension_types/issue53867.dart:11:41: Error: The representation type 'String' of extension type 'E7' must be either a subtype of the representation type 'num' of the implemented extension type 'E1' or a subtype of 'E1' itself.
+// Try changing the representation type to a subtype of 'num'.
+// extension type E7(String it) implements E1 {} // Error.
+// ^
+//
+import self as self;
+import "dart:core" as core;
+
+extension type E1(core::num it) {
+ abstract inline-class-member representation-field get it() → core::num;
+ constructor • = self::E1|constructor#;
+ constructor tearoff • = self::E1|constructor#_#new#tearOff;
+}
+extension type E2(self::E1 /* = core::num */ it) implements self::E1 /* = core::num */ {
+ abstract inline-class-member representation-field get it() → self::E1 /* = core::num */;
+ constructor • = self::E2|constructor#;
+ constructor tearoff • = self::E2|constructor#_#new#tearOff;
+}
+extension type E3(core::int it) implements self::E1 /* = core::num */ {
+ abstract inline-class-member representation-field get it() → core::int;
+ constructor • = self::E3|constructor#;
+ constructor tearoff • = self::E3|constructor#_#new#tearOff;
+}
+extension type E4(self::E3 /* = core::int */ it) implements self::E1 /* = core::num */ {
+ abstract inline-class-member representation-field get it() → self::E3 /* = core::int */;
+ constructor • = self::E4|constructor#;
+ constructor tearoff • = self::E4|constructor#_#new#tearOff;
+}
+extension type E5(self::E3 /* = core::int */ it) implements self::E2 /* = core::num */ {
+ abstract inline-class-member representation-field get it() → self::E3 /* = core::int */;
+ constructor • = self::E5|constructor#;
+ constructor tearoff • = self::E5|constructor#_#new#tearOff;
+}
+extension type E6(self::E2 /* = core::num */ it) implements self::E3 /* = core::int */ {
+ abstract inline-class-member representation-field get it() → self::E2 /* = core::num */;
+ constructor • = self::E6|constructor#;
+ constructor tearoff • = self::E6|constructor#_#new#tearOff;
+}
+extension type E7(core::String it) implements self::E1 /* = core::num */ {
+ abstract inline-class-member representation-field get it() → core::String;
+ constructor • = self::E7|constructor#;
+ constructor tearoff • = self::E7|constructor#_#new#tearOff;
+}
+static inline-class-member method E1|constructor#(core::num it) → self::E1 /* = core::num */ {
+ lowered final self::E1 /* = core::num */ #this = it;
+ return #this;
+}
+static inline-class-member method E1|constructor#_#new#tearOff(core::num it) → self::E1 /* = core::num */
+ return self::E1|constructor#(it);
+static inline-class-member method E2|constructor#(self::E1 /* = core::num */ it) → self::E2 /* = core::num */ {
+ lowered final self::E2 /* = core::num */ #this = it;
+ return #this;
+}
+static inline-class-member method E2|constructor#_#new#tearOff(self::E1 /* = core::num */ it) → self::E2 /* = core::num */
+ return self::E2|constructor#(it);
+static inline-class-member method E3|constructor#(core::int it) → self::E3 /* = core::int */ {
+ lowered final self::E3 /* = core::int */ #this = it;
+ return #this;
+}
+static inline-class-member method E3|constructor#_#new#tearOff(core::int it) → self::E3 /* = core::int */
+ return self::E3|constructor#(it);
+static inline-class-member method E4|constructor#(self::E3 /* = core::int */ it) → self::E4 /* = core::int */ {
+ lowered final self::E4 /* = core::int */ #this = it;
+ return #this;
+}
+static inline-class-member method E4|constructor#_#new#tearOff(self::E3 /* = core::int */ it) → self::E4 /* = core::int */
+ return self::E4|constructor#(it);
+static inline-class-member method E5|constructor#(self::E3 /* = core::int */ it) → self::E5 /* = core::int */ {
+ lowered final self::E5 /* = core::int */ #this = it;
+ return #this;
+}
+static inline-class-member method E5|constructor#_#new#tearOff(self::E3 /* = core::int */ it) → self::E5 /* = core::int */
+ return self::E5|constructor#(it);
+static inline-class-member method E6|constructor#(self::E2 /* = core::num */ it) → self::E6 /* = core::num */ {
+ lowered final self::E6 /* = core::num */ #this = it;
+ return #this;
+}
+static inline-class-member method E6|constructor#_#new#tearOff(self::E2 /* = core::num */ it) → self::E6 /* = core::num */
+ return self::E6|constructor#(it);
+static inline-class-member method E7|constructor#(core::String it) → self::E7 /* = core::String */ {
+ lowered final self::E7 /* = core::String */ #this = it;
+ return #this;
+}
+static inline-class-member method E7|constructor#_#new#tearOff(core::String it) → self::E7 /* = core::String */
+ return self::E7|constructor#(it);
diff --git a/pkg/front_end/testcases/extension_types/issue53867.dart.textual_outline.expect b/pkg/front_end/testcases/extension_types/issue53867.dart.textual_outline.expect
new file mode 100644
index 0000000..d5e591f
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/issue53867.dart.textual_outline.expect
@@ -0,0 +1,7 @@
+extension type E1(num it) {}
+extension type E2(E1 it) implements E1 {}
+extension type E3(int it) implements E1 {}
+extension type E4(E3 it) implements E1 {}
+extension type E5(E3 it) implements E2 {}
+extension type E6(E2 it) implements E3 {}
+extension type E7(String it) implements E1 {}
diff --git a/pkg/front_end/testcases/extension_types/issue53867.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/extension_types/issue53867.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..d5e591f
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/issue53867.dart.textual_outline_modelled.expect
@@ -0,0 +1,7 @@
+extension type E1(num it) {}
+extension type E2(E1 it) implements E1 {}
+extension type E3(int it) implements E1 {}
+extension type E4(E3 it) implements E1 {}
+extension type E5(E3 it) implements E2 {}
+extension type E6(E2 it) implements E3 {}
+extension type E7(String it) implements E1 {}
diff --git a/pkg/front_end/testcases/extension_types/issue53867.dart.weak.expect b/pkg/front_end/testcases/extension_types/issue53867.dart.weak.expect
new file mode 100644
index 0000000..f9370be
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/issue53867.dart.weak.expect
@@ -0,0 +1,94 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/extension_types/issue53867.dart:10:37: Error: The representation type 'E2' of extension type 'E6' must be either a subtype of the representation type 'int' of the implemented extension type 'E3' or a subtype of 'E3' itself.
+// Try changing the representation type to a subtype of 'int'.
+// extension type E6(E2 it) implements E3 {} // Error.
+// ^
+//
+// pkg/front_end/testcases/extension_types/issue53867.dart:11:41: Error: The representation type 'String' of extension type 'E7' must be either a subtype of the representation type 'num' of the implemented extension type 'E1' or a subtype of 'E1' itself.
+// Try changing the representation type to a subtype of 'num'.
+// extension type E7(String it) implements E1 {} // Error.
+// ^
+//
+import self as self;
+import "dart:core" as core;
+
+extension type E1(core::num it) {
+ abstract inline-class-member representation-field get it() → core::num;
+ constructor • = self::E1|constructor#;
+ constructor tearoff • = self::E1|constructor#_#new#tearOff;
+}
+extension type E2(self::E1 /* = core::num */ it) implements self::E1 /* = core::num */ {
+ abstract inline-class-member representation-field get it() → self::E1 /* = core::num */;
+ constructor • = self::E2|constructor#;
+ constructor tearoff • = self::E2|constructor#_#new#tearOff;
+}
+extension type E3(core::int it) implements self::E1 /* = core::num */ {
+ abstract inline-class-member representation-field get it() → core::int;
+ constructor • = self::E3|constructor#;
+ constructor tearoff • = self::E3|constructor#_#new#tearOff;
+}
+extension type E4(self::E3 /* = core::int */ it) implements self::E1 /* = core::num */ {
+ abstract inline-class-member representation-field get it() → self::E3 /* = core::int */;
+ constructor • = self::E4|constructor#;
+ constructor tearoff • = self::E4|constructor#_#new#tearOff;
+}
+extension type E5(self::E3 /* = core::int */ it) implements self::E2 /* = core::num */ {
+ abstract inline-class-member representation-field get it() → self::E3 /* = core::int */;
+ constructor • = self::E5|constructor#;
+ constructor tearoff • = self::E5|constructor#_#new#tearOff;
+}
+extension type E6(self::E2 /* = core::num */ it) implements self::E3 /* = core::int */ {
+ abstract inline-class-member representation-field get it() → self::E2 /* = core::num */;
+ constructor • = self::E6|constructor#;
+ constructor tearoff • = self::E6|constructor#_#new#tearOff;
+}
+extension type E7(core::String it) implements self::E1 /* = core::num */ {
+ abstract inline-class-member representation-field get it() → core::String;
+ constructor • = self::E7|constructor#;
+ constructor tearoff • = self::E7|constructor#_#new#tearOff;
+}
+static inline-class-member method E1|constructor#(core::num it) → self::E1 /* = core::num */ {
+ lowered final self::E1 /* = core::num */ #this = it;
+ return #this;
+}
+static inline-class-member method E1|constructor#_#new#tearOff(core::num it) → self::E1 /* = core::num */
+ return self::E1|constructor#(it);
+static inline-class-member method E2|constructor#(self::E1 /* = core::num */ it) → self::E2 /* = core::num */ {
+ lowered final self::E2 /* = core::num */ #this = it;
+ return #this;
+}
+static inline-class-member method E2|constructor#_#new#tearOff(self::E1 /* = core::num */ it) → self::E2 /* = core::num */
+ return self::E2|constructor#(it);
+static inline-class-member method E3|constructor#(core::int it) → self::E3 /* = core::int */ {
+ lowered final self::E3 /* = core::int */ #this = it;
+ return #this;
+}
+static inline-class-member method E3|constructor#_#new#tearOff(core::int it) → self::E3 /* = core::int */
+ return self::E3|constructor#(it);
+static inline-class-member method E4|constructor#(self::E3 /* = core::int */ it) → self::E4 /* = core::int */ {
+ lowered final self::E4 /* = core::int */ #this = it;
+ return #this;
+}
+static inline-class-member method E4|constructor#_#new#tearOff(self::E3 /* = core::int */ it) → self::E4 /* = core::int */
+ return self::E4|constructor#(it);
+static inline-class-member method E5|constructor#(self::E3 /* = core::int */ it) → self::E5 /* = core::int */ {
+ lowered final self::E5 /* = core::int */ #this = it;
+ return #this;
+}
+static inline-class-member method E5|constructor#_#new#tearOff(self::E3 /* = core::int */ it) → self::E5 /* = core::int */
+ return self::E5|constructor#(it);
+static inline-class-member method E6|constructor#(self::E2 /* = core::num */ it) → self::E6 /* = core::num */ {
+ lowered final self::E6 /* = core::num */ #this = it;
+ return #this;
+}
+static inline-class-member method E6|constructor#_#new#tearOff(self::E2 /* = core::num */ it) → self::E6 /* = core::num */
+ return self::E6|constructor#(it);
+static inline-class-member method E7|constructor#(core::String it) → self::E7 /* = core::String */ {
+ lowered final self::E7 /* = core::String */ #this = it;
+ return #this;
+}
+static inline-class-member method E7|constructor#_#new#tearOff(core::String it) → self::E7 /* = core::String */
+ return self::E7|constructor#(it);
diff --git a/pkg/front_end/testcases/extension_types/issue53867.dart.weak.modular.expect b/pkg/front_end/testcases/extension_types/issue53867.dart.weak.modular.expect
new file mode 100644
index 0000000..f9370be
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/issue53867.dart.weak.modular.expect
@@ -0,0 +1,94 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/extension_types/issue53867.dart:10:37: Error: The representation type 'E2' of extension type 'E6' must be either a subtype of the representation type 'int' of the implemented extension type 'E3' or a subtype of 'E3' itself.
+// Try changing the representation type to a subtype of 'int'.
+// extension type E6(E2 it) implements E3 {} // Error.
+// ^
+//
+// pkg/front_end/testcases/extension_types/issue53867.dart:11:41: Error: The representation type 'String' of extension type 'E7' must be either a subtype of the representation type 'num' of the implemented extension type 'E1' or a subtype of 'E1' itself.
+// Try changing the representation type to a subtype of 'num'.
+// extension type E7(String it) implements E1 {} // Error.
+// ^
+//
+import self as self;
+import "dart:core" as core;
+
+extension type E1(core::num it) {
+ abstract inline-class-member representation-field get it() → core::num;
+ constructor • = self::E1|constructor#;
+ constructor tearoff • = self::E1|constructor#_#new#tearOff;
+}
+extension type E2(self::E1 /* = core::num */ it) implements self::E1 /* = core::num */ {
+ abstract inline-class-member representation-field get it() → self::E1 /* = core::num */;
+ constructor • = self::E2|constructor#;
+ constructor tearoff • = self::E2|constructor#_#new#tearOff;
+}
+extension type E3(core::int it) implements self::E1 /* = core::num */ {
+ abstract inline-class-member representation-field get it() → core::int;
+ constructor • = self::E3|constructor#;
+ constructor tearoff • = self::E3|constructor#_#new#tearOff;
+}
+extension type E4(self::E3 /* = core::int */ it) implements self::E1 /* = core::num */ {
+ abstract inline-class-member representation-field get it() → self::E3 /* = core::int */;
+ constructor • = self::E4|constructor#;
+ constructor tearoff • = self::E4|constructor#_#new#tearOff;
+}
+extension type E5(self::E3 /* = core::int */ it) implements self::E2 /* = core::num */ {
+ abstract inline-class-member representation-field get it() → self::E3 /* = core::int */;
+ constructor • = self::E5|constructor#;
+ constructor tearoff • = self::E5|constructor#_#new#tearOff;
+}
+extension type E6(self::E2 /* = core::num */ it) implements self::E3 /* = core::int */ {
+ abstract inline-class-member representation-field get it() → self::E2 /* = core::num */;
+ constructor • = self::E6|constructor#;
+ constructor tearoff • = self::E6|constructor#_#new#tearOff;
+}
+extension type E7(core::String it) implements self::E1 /* = core::num */ {
+ abstract inline-class-member representation-field get it() → core::String;
+ constructor • = self::E7|constructor#;
+ constructor tearoff • = self::E7|constructor#_#new#tearOff;
+}
+static inline-class-member method E1|constructor#(core::num it) → self::E1 /* = core::num */ {
+ lowered final self::E1 /* = core::num */ #this = it;
+ return #this;
+}
+static inline-class-member method E1|constructor#_#new#tearOff(core::num it) → self::E1 /* = core::num */
+ return self::E1|constructor#(it);
+static inline-class-member method E2|constructor#(self::E1 /* = core::num */ it) → self::E2 /* = core::num */ {
+ lowered final self::E2 /* = core::num */ #this = it;
+ return #this;
+}
+static inline-class-member method E2|constructor#_#new#tearOff(self::E1 /* = core::num */ it) → self::E2 /* = core::num */
+ return self::E2|constructor#(it);
+static inline-class-member method E3|constructor#(core::int it) → self::E3 /* = core::int */ {
+ lowered final self::E3 /* = core::int */ #this = it;
+ return #this;
+}
+static inline-class-member method E3|constructor#_#new#tearOff(core::int it) → self::E3 /* = core::int */
+ return self::E3|constructor#(it);
+static inline-class-member method E4|constructor#(self::E3 /* = core::int */ it) → self::E4 /* = core::int */ {
+ lowered final self::E4 /* = core::int */ #this = it;
+ return #this;
+}
+static inline-class-member method E4|constructor#_#new#tearOff(self::E3 /* = core::int */ it) → self::E4 /* = core::int */
+ return self::E4|constructor#(it);
+static inline-class-member method E5|constructor#(self::E3 /* = core::int */ it) → self::E5 /* = core::int */ {
+ lowered final self::E5 /* = core::int */ #this = it;
+ return #this;
+}
+static inline-class-member method E5|constructor#_#new#tearOff(self::E3 /* = core::int */ it) → self::E5 /* = core::int */
+ return self::E5|constructor#(it);
+static inline-class-member method E6|constructor#(self::E2 /* = core::num */ it) → self::E6 /* = core::num */ {
+ lowered final self::E6 /* = core::num */ #this = it;
+ return #this;
+}
+static inline-class-member method E6|constructor#_#new#tearOff(self::E2 /* = core::num */ it) → self::E6 /* = core::num */
+ return self::E6|constructor#(it);
+static inline-class-member method E7|constructor#(core::String it) → self::E7 /* = core::String */ {
+ lowered final self::E7 /* = core::String */ #this = it;
+ return #this;
+}
+static inline-class-member method E7|constructor#_#new#tearOff(core::String it) → self::E7 /* = core::String */
+ return self::E7|constructor#(it);
diff --git a/pkg/front_end/testcases/extension_types/issue53867.dart.weak.outline.expect b/pkg/front_end/testcases/extension_types/issue53867.dart.weak.outline.expect
new file mode 100644
index 0000000..842e6e2
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/issue53867.dart.weak.outline.expect
@@ -0,0 +1,80 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/extension_types/issue53867.dart:10:37: Error: The representation type 'E2' of extension type 'E6' must be either a subtype of the representation type 'int' of the implemented extension type 'E3' or a subtype of 'E3' itself.
+// Try changing the representation type to a subtype of 'int'.
+// extension type E6(E2 it) implements E3 {} // Error.
+// ^
+//
+// pkg/front_end/testcases/extension_types/issue53867.dart:11:41: Error: The representation type 'String' of extension type 'E7' must be either a subtype of the representation type 'num' of the implemented extension type 'E1' or a subtype of 'E1' itself.
+// Try changing the representation type to a subtype of 'num'.
+// extension type E7(String it) implements E1 {} // Error.
+// ^
+//
+import self as self;
+import "dart:core" as core;
+
+extension type E1(core::num it) {
+ abstract inline-class-member representation-field get it() → core::num;
+ constructor • = self::E1|constructor#;
+ constructor tearoff • = self::E1|constructor#_#new#tearOff;
+}
+extension type E2(self::E1 /* = core::num */ it) implements self::E1 /* = core::num */ {
+ abstract inline-class-member representation-field get it() → self::E1 /* = core::num */;
+ constructor • = self::E2|constructor#;
+ constructor tearoff • = self::E2|constructor#_#new#tearOff;
+}
+extension type E3(core::int it) implements self::E1 /* = core::num */ {
+ abstract inline-class-member representation-field get it() → core::int;
+ constructor • = self::E3|constructor#;
+ constructor tearoff • = self::E3|constructor#_#new#tearOff;
+}
+extension type E4(self::E3 /* = core::int */ it) implements self::E1 /* = core::num */ {
+ abstract inline-class-member representation-field get it() → self::E3 /* = core::int */;
+ constructor • = self::E4|constructor#;
+ constructor tearoff • = self::E4|constructor#_#new#tearOff;
+}
+extension type E5(self::E3 /* = core::int */ it) implements self::E2 /* = core::num */ {
+ abstract inline-class-member representation-field get it() → self::E3 /* = core::int */;
+ constructor • = self::E5|constructor#;
+ constructor tearoff • = self::E5|constructor#_#new#tearOff;
+}
+extension type E6(self::E2 /* = core::num */ it) implements self::E3 /* = core::int */ {
+ abstract inline-class-member representation-field get it() → self::E2 /* = core::num */;
+ constructor • = self::E6|constructor#;
+ constructor tearoff • = self::E6|constructor#_#new#tearOff;
+}
+extension type E7(core::String it) implements self::E1 /* = core::num */ {
+ abstract inline-class-member representation-field get it() → core::String;
+ constructor • = self::E7|constructor#;
+ constructor tearoff • = self::E7|constructor#_#new#tearOff;
+}
+static inline-class-member method E1|constructor#(core::num it) → self::E1 /* = core::num */
+ ;
+static inline-class-member method E1|constructor#_#new#tearOff(core::num it) → self::E1 /* = core::num */
+ return self::E1|constructor#(it);
+static inline-class-member method E2|constructor#(self::E1 /* = core::num */ it) → self::E2 /* = core::num */
+ ;
+static inline-class-member method E2|constructor#_#new#tearOff(self::E1 /* = core::num */ it) → self::E2 /* = core::num */
+ return self::E2|constructor#(it);
+static inline-class-member method E3|constructor#(core::int it) → self::E3 /* = core::int */
+ ;
+static inline-class-member method E3|constructor#_#new#tearOff(core::int it) → self::E3 /* = core::int */
+ return self::E3|constructor#(it);
+static inline-class-member method E4|constructor#(self::E3 /* = core::int */ it) → self::E4 /* = core::int */
+ ;
+static inline-class-member method E4|constructor#_#new#tearOff(self::E3 /* = core::int */ it) → self::E4 /* = core::int */
+ return self::E4|constructor#(it);
+static inline-class-member method E5|constructor#(self::E3 /* = core::int */ it) → self::E5 /* = core::int */
+ ;
+static inline-class-member method E5|constructor#_#new#tearOff(self::E3 /* = core::int */ it) → self::E5 /* = core::int */
+ return self::E5|constructor#(it);
+static inline-class-member method E6|constructor#(self::E2 /* = core::num */ it) → self::E6 /* = core::num */
+ ;
+static inline-class-member method E6|constructor#_#new#tearOff(self::E2 /* = core::num */ it) → self::E6 /* = core::num */
+ return self::E6|constructor#(it);
+static inline-class-member method E7|constructor#(core::String it) → self::E7 /* = core::String */
+ ;
+static inline-class-member method E7|constructor#_#new#tearOff(core::String it) → self::E7 /* = core::String */
+ return self::E7|constructor#(it);
diff --git a/pkg/front_end/testcases/extension_types/issue53867.dart.weak.transformed.expect b/pkg/front_end/testcases/extension_types/issue53867.dart.weak.transformed.expect
new file mode 100644
index 0000000..f9370be
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/issue53867.dart.weak.transformed.expect
@@ -0,0 +1,94 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/extension_types/issue53867.dart:10:37: Error: The representation type 'E2' of extension type 'E6' must be either a subtype of the representation type 'int' of the implemented extension type 'E3' or a subtype of 'E3' itself.
+// Try changing the representation type to a subtype of 'int'.
+// extension type E6(E2 it) implements E3 {} // Error.
+// ^
+//
+// pkg/front_end/testcases/extension_types/issue53867.dart:11:41: Error: The representation type 'String' of extension type 'E7' must be either a subtype of the representation type 'num' of the implemented extension type 'E1' or a subtype of 'E1' itself.
+// Try changing the representation type to a subtype of 'num'.
+// extension type E7(String it) implements E1 {} // Error.
+// ^
+//
+import self as self;
+import "dart:core" as core;
+
+extension type E1(core::num it) {
+ abstract inline-class-member representation-field get it() → core::num;
+ constructor • = self::E1|constructor#;
+ constructor tearoff • = self::E1|constructor#_#new#tearOff;
+}
+extension type E2(self::E1 /* = core::num */ it) implements self::E1 /* = core::num */ {
+ abstract inline-class-member representation-field get it() → self::E1 /* = core::num */;
+ constructor • = self::E2|constructor#;
+ constructor tearoff • = self::E2|constructor#_#new#tearOff;
+}
+extension type E3(core::int it) implements self::E1 /* = core::num */ {
+ abstract inline-class-member representation-field get it() → core::int;
+ constructor • = self::E3|constructor#;
+ constructor tearoff • = self::E3|constructor#_#new#tearOff;
+}
+extension type E4(self::E3 /* = core::int */ it) implements self::E1 /* = core::num */ {
+ abstract inline-class-member representation-field get it() → self::E3 /* = core::int */;
+ constructor • = self::E4|constructor#;
+ constructor tearoff • = self::E4|constructor#_#new#tearOff;
+}
+extension type E5(self::E3 /* = core::int */ it) implements self::E2 /* = core::num */ {
+ abstract inline-class-member representation-field get it() → self::E3 /* = core::int */;
+ constructor • = self::E5|constructor#;
+ constructor tearoff • = self::E5|constructor#_#new#tearOff;
+}
+extension type E6(self::E2 /* = core::num */ it) implements self::E3 /* = core::int */ {
+ abstract inline-class-member representation-field get it() → self::E2 /* = core::num */;
+ constructor • = self::E6|constructor#;
+ constructor tearoff • = self::E6|constructor#_#new#tearOff;
+}
+extension type E7(core::String it) implements self::E1 /* = core::num */ {
+ abstract inline-class-member representation-field get it() → core::String;
+ constructor • = self::E7|constructor#;
+ constructor tearoff • = self::E7|constructor#_#new#tearOff;
+}
+static inline-class-member method E1|constructor#(core::num it) → self::E1 /* = core::num */ {
+ lowered final self::E1 /* = core::num */ #this = it;
+ return #this;
+}
+static inline-class-member method E1|constructor#_#new#tearOff(core::num it) → self::E1 /* = core::num */
+ return self::E1|constructor#(it);
+static inline-class-member method E2|constructor#(self::E1 /* = core::num */ it) → self::E2 /* = core::num */ {
+ lowered final self::E2 /* = core::num */ #this = it;
+ return #this;
+}
+static inline-class-member method E2|constructor#_#new#tearOff(self::E1 /* = core::num */ it) → self::E2 /* = core::num */
+ return self::E2|constructor#(it);
+static inline-class-member method E3|constructor#(core::int it) → self::E3 /* = core::int */ {
+ lowered final self::E3 /* = core::int */ #this = it;
+ return #this;
+}
+static inline-class-member method E3|constructor#_#new#tearOff(core::int it) → self::E3 /* = core::int */
+ return self::E3|constructor#(it);
+static inline-class-member method E4|constructor#(self::E3 /* = core::int */ it) → self::E4 /* = core::int */ {
+ lowered final self::E4 /* = core::int */ #this = it;
+ return #this;
+}
+static inline-class-member method E4|constructor#_#new#tearOff(self::E3 /* = core::int */ it) → self::E4 /* = core::int */
+ return self::E4|constructor#(it);
+static inline-class-member method E5|constructor#(self::E3 /* = core::int */ it) → self::E5 /* = core::int */ {
+ lowered final self::E5 /* = core::int */ #this = it;
+ return #this;
+}
+static inline-class-member method E5|constructor#_#new#tearOff(self::E3 /* = core::int */ it) → self::E5 /* = core::int */
+ return self::E5|constructor#(it);
+static inline-class-member method E6|constructor#(self::E2 /* = core::num */ it) → self::E6 /* = core::num */ {
+ lowered final self::E6 /* = core::num */ #this = it;
+ return #this;
+}
+static inline-class-member method E6|constructor#_#new#tearOff(self::E2 /* = core::num */ it) → self::E6 /* = core::num */
+ return self::E6|constructor#(it);
+static inline-class-member method E7|constructor#(core::String it) → self::E7 /* = core::String */ {
+ lowered final self::E7 /* = core::String */ #this = it;
+ return #this;
+}
+static inline-class-member method E7|constructor#_#new#tearOff(core::String it) → self::E7 /* = core::String */
+ return self::E7|constructor#(it);
diff --git a/runtime/tests/vm/dart/address_local_pointer_il_test.dart b/runtime/tests/vm/dart/address_local_pointer_il_test.dart
index 0842756..eb290f5 100644
--- a/runtime/tests/vm/dart/address_local_pointer_il_test.dart
+++ b/runtime/tests/vm/dart/address_local_pointer_il_test.dart
@@ -17,32 +17,21 @@
int identity(int address) => Pointer<Void>.fromAddress(address).address;
void matchIL$identity(FlowGraph graph) {
- graph.dump();
- if (is32BitConfiguration) {
- // The Dart int address is truncated before being returned.
- graph.match([
- match.block('Graph'),
- match.block('Function', [
- 'address' << match.Parameter(index: 0),
- 'int32' <<
- match.IntConverter('address',
- from: 'int64', to: 'int32', is_truncating: true),
+ final retval = is32BitConfiguration ? 'retval' : 'address';
+ graph.match([
+ match.block('Graph'),
+ match.block('Function', [
+ 'address' << match.Parameter(index: 0),
+ if (is32BitConfiguration) ...[
+ // The Dart int address is truncated before being returned.
'uint32' <<
- match.IntConverter('int32',
- from: 'int32', to: 'uint32', is_truncating: true),
+ match.IntConverter('address',
+ from: 'int64', to: 'uint32', is_truncating: true),
'retval' << match.IntConverter('uint32', from: 'uint32', to: 'int64'),
- match.Return('retval'),
- ]),
- ]);
- } else {
- graph.match([
- match.block('Graph'),
- match.block('Function', [
- 'address' << match.Parameter(index: 0),
- match.Return('address'),
- ]),
- ]);
- }
+ ],
+ match.Return(retval),
+ ]),
+ ]);
}
void main(List<String> args) {
diff --git a/runtime/tests/vm/dart/regress_306327173_il_test.dart b/runtime/tests/vm/dart/regress_306327173_il_test.dart
index a9206d3e..e3c22ff 100644
--- a/runtime/tests/vm/dart/regress_306327173_il_test.dart
+++ b/runtime/tests/vm/dart/regress_306327173_il_test.dart
@@ -30,12 +30,7 @@
// and int64 on 64-bit arches.
if (is32BitConfiguration) ...[
// 'unboxed' needs to be converted to int64 before returning.
- //
- // Note: The first two conversions here should be fixed once all
- // kUnboxedIntPtr uses are appropriately converted to kUnboxedFfiIntPtr.
- 'extra1' << match.IntConverter('unboxed', from: 'uint32', to: 'int32'),
- 'extra2' << match.IntConverter('extra1', from: 'int32', to: 'uint32'),
- 'address' << match.IntConverter('extra2', from: 'uint32', to: 'int64'),
+ 'address' << match.IntConverter('unboxed', from: 'uint32', to: 'int64'),
],
match.Return(retvalName),
]),
diff --git a/runtime/vm/compiler/backend/il.cc b/runtime/vm/compiler/backend/il.cc
index a99cef7..88a876a 100644
--- a/runtime/vm/compiler/backend/il.cc
+++ b/runtime/vm/compiler/backend/il.cc
@@ -1980,41 +1980,25 @@
RangeBoundary::kRangeBoundaryInt32);
}
-bool UnboxInt32Instr::ComputeCanDeoptimize() const {
+bool UnboxIntegerInstr::ComputeCanDeoptimize() const {
if (SpeculativeModeOfInputs() == kNotSpeculative) {
return false;
}
- const intptr_t value_cid = value()->Type()->ToCid();
- if (value_cid == kSmiCid) {
- return (compiler::target::kSmiBits > 32) && !is_truncating() &&
- !RangeUtils::Fits(value()->definition()->range(),
- RangeBoundary::kRangeBoundaryInt32);
- } else if (value_cid == kMintCid) {
- return !is_truncating() &&
- !RangeUtils::Fits(value()->definition()->range(),
- RangeBoundary::kRangeBoundaryInt32);
- } else if (is_truncating() && value()->definition()->IsBoxInteger()) {
- return false;
- } else if ((compiler::target::kSmiBits < 32) && value()->Type()->IsInt()) {
- return !RangeUtils::Fits(value()->definition()->range(),
- RangeBoundary::kRangeBoundaryInt32);
- } else {
+ if (!value()->Type()->IsInt()) {
return true;
}
-}
-
-bool UnboxUint32Instr::ComputeCanDeoptimize() const {
- ASSERT(is_truncating());
- if (SpeculativeModeOfInputs() == kNotSpeculative) {
+ if (representation() == kUnboxedInt64 || is_truncating()) {
return false;
}
- if ((value()->Type()->ToCid() == kSmiCid) ||
- (value()->Type()->ToCid() == kMintCid)) {
+ const intptr_t rep_bitsize =
+ RepresentationUtils::ValueSize(representation()) * kBitsPerByte;
+ if (value()->Type()->ToCid() == kSmiCid &&
+ compiler::target::kSmiBits <= rep_bitsize) {
return false;
}
- // Check input value's range.
- Range* value_range = value()->definition()->range();
- return !RangeUtils::Fits(value_range, RangeBoundary::kRangeBoundaryInt64);
+ return !RangeUtils::IsWithin(value()->definition()->range(),
+ RepresentationUtils::MinValue(representation()),
+ RepresentationUtils::MaxValue(representation()));
}
bool BinaryInt32OpInstr::ComputeCanDeoptimize() const {
@@ -3306,40 +3290,24 @@
set_speculative_mode(kNotSpeculative);
}
- return this;
-}
-
-Definition* UnboxInt32Instr::Canonicalize(FlowGraph* flow_graph) {
- Definition* replacement = UnboxIntegerInstr::Canonicalize(flow_graph);
- if (replacement != this) {
- return replacement;
- }
-
- ConstantInstr* c = value()->definition()->AsConstant();
- if ((c != nullptr) && c->value().IsInteger()) {
- if (!is_truncating()) {
- // Check that constant fits into 32-bit integer.
- const int64_t value = Integer::Cast(c->value()).AsInt64Value();
- if (!Utils::IsInt(32, value)) {
- return this;
+ if (value()->BindsToConstant()) {
+ const auto& obj = value()->BoundConstant();
+ if (obj.IsInteger()) {
+ if (representation() == kUnboxedInt64) {
+ return flow_graph->GetConstant(obj, representation());
+ }
+ const int64_t intval = Integer::Cast(obj).AsInt64Value();
+ if (RepresentationUtils::IsRepresentable(representation(), intval)) {
+ return flow_graph->GetConstant(obj, representation());
+ }
+ if (is_truncating()) {
+ const int64_t result = Evaluator::TruncateTo(intval, representation());
+ return flow_graph->GetConstant(
+ Integer::ZoneHandle(flow_graph->zone(),
+ Integer::NewCanonical(result)),
+ representation());
}
}
-
- return flow_graph->GetConstant(c->value(), kUnboxedInt32);
- }
-
- return this;
-}
-
-Definition* UnboxInt64Instr::Canonicalize(FlowGraph* flow_graph) {
- Definition* replacement = UnboxIntegerInstr::Canonicalize(flow_graph);
- if (replacement != this) {
- return replacement;
- }
-
- ConstantInstr* c = value()->definition()->AsConstant();
- if (c != nullptr && c->value().IsInteger()) {
- return flow_graph->GetConstant(c->value(), kUnboxedInt64);
}
return this;
diff --git a/runtime/vm/compiler/backend/il.h b/runtime/vm/compiler/backend/il.h
index a825904..3906ac3 100644
--- a/runtime/vm/compiler/backend/il.h
+++ b/runtime/vm/compiler/backend/il.h
@@ -8511,6 +8511,8 @@
void mark_truncating() { is_truncating_ = true; }
+ virtual bool ComputeCanDeoptimize() const;
+
virtual CompileType ComputeType() const;
virtual bool AttributesEqual(const Instruction& other) const {
@@ -8521,6 +8523,8 @@
virtual Definition* Canonicalize(FlowGraph* flow_graph);
+ virtual void InferRange(RangeAnalysis* analysis, Range* range);
+
DECLARE_ABSTRACT_INSTRUCTION(UnboxInteger)
PRINT_OPERANDS_TO_SUPPORT
@@ -8570,10 +8574,6 @@
ASSERT(is_truncating());
}
- virtual bool ComputeCanDeoptimize() const;
-
- virtual void InferRange(RangeAnalysis* analysis, Range* range);
-
DECLARE_INSTRUCTION_NO_BACKEND(UnboxUint32)
DECLARE_EMPTY_SERIALIZATION(UnboxUint32Instr, UnboxInteger32Instr)
@@ -8594,12 +8594,6 @@
deopt_id,
speculative_mode) {}
- virtual bool ComputeCanDeoptimize() const;
-
- virtual void InferRange(RangeAnalysis* analysis, Range* range);
-
- virtual Definition* Canonicalize(FlowGraph* flow_graph);
-
DECLARE_INSTRUCTION_NO_BACKEND(UnboxInt32)
DECLARE_EMPTY_SERIALIZATION(UnboxInt32Instr, UnboxInteger32Instr)
@@ -8619,18 +8613,6 @@
deopt_id,
speculative_mode) {}
- virtual void InferRange(RangeAnalysis* analysis, Range* range);
-
- virtual Definition* Canonicalize(FlowGraph* flow_graph);
-
- virtual bool ComputeCanDeoptimize() const {
- if (SpeculativeModeOfInputs() == kNotSpeculative) {
- return false;
- }
-
- return !value()->Type()->IsInt();
- }
-
DECLARE_INSTRUCTION_NO_BACKEND(UnboxInt64)
DECLARE_EMPTY_SERIALIZATION(UnboxInt64Instr, UnboxIntegerInstr)
diff --git a/runtime/vm/compiler/backend/locations.cc b/runtime/vm/compiler/backend/locations.cc
index 77cd5d0..6c298e4 100644
--- a/runtime/vm/compiler/backend/locations.cc
+++ b/runtime/vm/compiler/backend/locations.cc
@@ -121,6 +121,12 @@
}
#undef REP_MAX_VALUE_CLAUSE
+bool RepresentationUtils::IsRepresentable(Representation rep, uint64_t value) {
+ const intptr_t bit_size = ValueSize(rep) * kBitsPerByte;
+ return IsUnsigned(rep) ? Utils::IsUint(bit_size, value)
+ : Utils::IsInt(bit_size, value);
+}
+
const char* Location::RepresentationToCString(Representation repr) {
switch (repr) {
#define REPR_CASE(Name, __, ___) \
diff --git a/runtime/vm/compiler/backend/locations.h b/runtime/vm/compiler/backend/locations.h
index 2489b02..5d631fe 100644
--- a/runtime/vm/compiler/backend/locations.h
+++ b/runtime/vm/compiler/backend/locations.h
@@ -97,12 +97,16 @@
static compiler::OperandSize OperandSize(Representation rep);
// The minimum integral value that can be represented.
- // Assumes that [rep] is a unboxed integer.
+ // Assumes that [rep] is an unboxed integer.
static int64_t MinValue(Representation rep);
// The maximum integral value that can be represented.
- // Assumes that [rep] is a unboxed integer.
+ // Assumes that [rep] is an unboxed integer.
static int64_t MaxValue(Representation rep);
+
+ // Whether the given value is representable in the given representation.
+ // Assumes that [rep] is an unboxed integer.
+ static bool IsRepresentable(Representation rep, uint64_t value);
};
// The representation for word-sized unboxed fields.
diff --git a/runtime/vm/compiler/backend/range_analysis.cc b/runtime/vm/compiler/backend/range_analysis.cc
index d57cc36..90c4bac 100644
--- a/runtime/vm/compiler/backend/range_analysis.cc
+++ b/runtime/vm/compiler/backend/range_analysis.cc
@@ -24,6 +24,35 @@
// Quick access to the locally defined zone() method.
#define Z (zone())
+#if defined(DEBUG)
+static void CheckRangeForRepresentation(const Assert& assert,
+ const Instruction* instr,
+ const Range* range,
+ Representation rep) {
+ const Range other = Range::Full(rep);
+ if (!RangeUtils::IsWithin(range, &other)) {
+ assert.Fail(
+ "During range analysis for:\n %s\n"
+ "expected range containing only %s-representable values, but got %s",
+ instr->ToCString(), RepresentationToCString(rep),
+ Range::ToCString(range));
+ }
+}
+
+#define ASSERT_VALID_RANGE_FOR_REPRESENTATION(instr, range, representation) \
+ do { \
+ CheckRangeForRepresentation(dart::Assert(__FILE__, __LINE__), instr, \
+ range, representation); \
+ } while (false)
+#else
+#define ASSERT_VALID_RANGE_FOR_REPRESENTATION(instr, range, representation) \
+ do { \
+ USE(instr); \
+ USE(range); \
+ USE(representation); \
+ } while (false)
+#endif
+
void RangeAnalysis::Analyze() {
CollectValues();
InsertConstraints();
@@ -2121,6 +2150,19 @@
return OnlyGreaterThanOrEqualTo(min_int) && OnlyLessThanOrEqualTo(max_int);
}
+bool Range::IsWithin(const Range* other) const {
+ auto const lower_bound = other->min().LowerBound();
+ auto const upper_bound = other->max().UpperBound();
+ if (lower_bound.IsNegativeInfinity()) {
+ if (upper_bound.IsPositiveInfinity()) return true;
+ return OnlyLessThanOrEqualTo(other->max().ConstantValue());
+ } else if (upper_bound.IsPositiveInfinity()) {
+ return OnlyGreaterThanOrEqualTo(other->min().ConstantValue());
+ } else {
+ return IsWithin(other->min().ConstantValue(), other->max().ConstantValue());
+ }
+}
+
bool Range::Overlaps(int64_t min_int, int64_t max_int) const {
RangeBoundary lower = min().LowerBound();
RangeBoundary upper = max().UpperBound();
@@ -2601,6 +2643,12 @@
// Only Smi and Mint supported.
FATAL("Unsupported type in: %s", ToCString());
}
+
+ // If the representation also gives us range information, then refine
+ // the range from the type by using the intersection of the two.
+ if (RepresentationUtils::IsUnboxedInteger(representation())) {
+ *range = Range::Full(representation()).Intersect(range);
+ }
}
static bool DependsOnSymbol(const RangeBoundary& a, Definition* symbol) {
@@ -3079,60 +3127,35 @@
void BoxIntegerInstr::InferRange(RangeAnalysis* analysis, Range* range) {
const Range* value_range = value()->definition()->range();
- if (!Range::IsUnknown(value_range)) {
+ if (Range::IsUnknown(value_range)) {
+ *range = Range::Full(from_representation());
+ } else {
+ ASSERT_VALID_RANGE_FOR_REPRESENTATION(value()->definition(), value_range,
+ from_representation());
*range = *value_range;
}
}
-void UnboxInt32Instr::InferRange(RangeAnalysis* analysis, Range* range) {
- if (value()->Type()->ToCid() == kSmiCid) {
- const Range* value_range = analysis->GetSmiRange(value());
- if (!Range::IsUnknown(value_range)) {
- *range = *value_range;
- }
- } else if (RangeAnalysis::IsIntegerDefinition(value()->definition())) {
- const Range* value_range = analysis->GetIntRange(value());
- if (!Range::IsUnknown(value_range)) {
- *range = *value_range;
- }
- } else if (value()->Type()->ToCid() == kSmiCid) {
- *range = Range::Full(RangeBoundary::kRangeBoundarySmi);
- } else {
- *range = Range::Full(RangeBoundary::kRangeBoundaryInt32);
- }
-}
+void UnboxIntegerInstr::InferRange(RangeAnalysis* analysis, Range* range) {
+ auto* const value_range = value()->Type()->ToCid() == kSmiCid
+ ? analysis->GetSmiRange(value())
+ : value()->definition()->range();
+ const Range to_range = Range::Full(representation());
-void UnboxUint32Instr::InferRange(RangeAnalysis* analysis, Range* range) {
- const Range* value_range = nullptr;
-
- if (value()->Type()->ToCid() == kSmiCid) {
- value_range = analysis->GetSmiRange(value());
- } else if (RangeAnalysis::IsIntegerDefinition(value()->definition())) {
- value_range = analysis->GetIntRange(value());
- } else {
- *range = Range(RangeBoundary::FromConstant(0),
- RangeBoundary::FromConstant(kMaxUint32));
- return;
- }
-
- if (!Range::IsUnknown(value_range)) {
- if (value_range->IsPositive()) {
- *range = *value_range;
- } else {
- *range = Range(RangeBoundary::FromConstant(0),
- RangeBoundary::FromConstant(kMaxUint32));
- }
- }
-}
-
-void UnboxInt64Instr::InferRange(RangeAnalysis* analysis, Range* range) {
- const Range* value_range = value()->definition()->range();
- if (value_range != nullptr) {
+ if (Range::IsUnknown(value_range)) {
+ *range = to_range;
+ } else if (value_range->IsWithin(&to_range)) {
*range = *value_range;
- } else if (!value()->definition()->IsInt64Definition() &&
- (value()->definition()->Type()->ToCid() != kSmiCid)) {
- *range = Range::Full(RangeBoundary::kRangeBoundaryInt64);
+ } else if (is_truncating()) {
+ // If truncating, then in most cases any non-representable values means
+ // no assumption can be made about the truncated value.
+ *range = to_range;
+ } else {
+ // When not truncating, then unboxing deoptimizes if the value is outside
+ // the range representation.
+ *range = value_range->Intersect(&to_range);
}
+ ASSERT_VALID_RANGE_FOR_REPRESENTATION(this, range, representation());
}
void IntConverterInstr::InferRange(RangeAnalysis* analysis, Range* range) {
@@ -3140,13 +3163,14 @@
ASSERT(RepresentationUtils::IsUnboxedInteger(to()));
ASSERT(from() == kUntagged || RepresentationUtils::IsUnboxedInteger(from()));
- auto* const value_range = value()->definition()->range();
+ const Range* const value_range = value()->definition()->range();
+ const Range to_range = Range::Full(to());
if (from() == kUntagged) {
ASSERT(value_range == nullptr); // Not an integer-valued definition.
- *range = Range::Full(to());
+ *range = to_range;
} else if (Range::IsUnknown(value_range)) {
- *range = Range::Full(to());
+ *range = to_range;
} else if (RepresentationUtils::ValueSize(to()) >
RepresentationUtils::ValueSize(from()) &&
(!RepresentationUtils::IsUnsigned(to()) ||
@@ -3161,29 +3185,21 @@
// are the same size) or a larger value is being truncated. That means
// we need to determine whether or not the value range lies within the
// range of numbers that have the same representation (modulo truncation).
- const int64_t min_overlap =
- Utils::Maximum(RepresentationUtils::MinValue(from()),
- RepresentationUtils::MinValue(to()));
- const int64_t max_overlap =
- Utils::Minimum(RepresentationUtils::MaxValue(from()),
- RepresentationUtils::MaxValue(to()));
-
- if (value_range->IsWithin(min_overlap, max_overlap)) {
+ const Range common_range = Range::Full(from()).Intersect(&to_range);
+ if (value_range->IsWithin(&common_range)) {
*range = *value_range;
} else {
// In most cases, if there are non-representable values, then no
// assumptions can be made about the converted value.
- *range = Range::Full(to());
+ *range = to_range;
}
} else {
// The conversion deoptimizes if the value is outside the range represented
// by to(), so we can just take the intersection.
- const auto& to_range = Range::Full(to());
*range = value_range->Intersect(&to_range);
}
- ASSERT(RangeUtils::IsWithin(range, RepresentationUtils::MinValue(to()),
- RepresentationUtils::MaxValue(to())));
+ ASSERT_VALID_RANGE_FOR_REPRESENTATION(this, range, to());
}
void AssertAssignableInstr::InferRange(RangeAnalysis* analysis, Range* range) {
diff --git a/runtime/vm/compiler/backend/range_analysis.h b/runtime/vm/compiler/backend/range_analysis.h
index e3260e8..f41997f 100644
--- a/runtime/vm/compiler/backend/range_analysis.h
+++ b/runtime/vm/compiler/backend/range_analysis.h
@@ -429,6 +429,9 @@
bool IsWithin(int64_t min_int, int64_t max_int) const;
// Inclusive.
+ bool IsWithin(const Range* other) const;
+
+ // Inclusive.
bool Overlaps(int64_t min_int, int64_t max_int) const;
bool IsUnsatisfiable() const;
@@ -560,10 +563,14 @@
return !Range::IsUnknown(range) && range->Fits(size);
}
- static bool IsWithin(Range* range, int64_t min, int64_t max) {
+ static bool IsWithin(const Range* range, int64_t min, int64_t max) {
return !Range::IsUnknown(range) && range->IsWithin(min, max);
}
+ static bool IsWithin(const Range* range, const Range* other) {
+ return !Range::IsUnknown(range) && range->IsWithin(other);
+ }
+
static bool IsPositive(Range* range) {
return !Range::IsUnknown(range) && range->IsPositive();
}
diff --git a/runtime/vm/compiler/frontend/kernel_to_il.cc b/runtime/vm/compiler/frontend/kernel_to_il.cc
index 17893dd..16c3edf 100644
--- a/runtime/vm/compiler/frontend/kernel_to_il.cc
+++ b/runtime/vm/compiler/frontend/kernel_to_il.cc
@@ -1384,8 +1384,8 @@
LocalVariable* pointer = MakeTemporary();
body += LoadLocal(pointer);
body += LoadLocal(address);
- body += UnboxTruncate(kUnboxedIntPtr);
- body += ConvertUnboxedToUntagged(kUnboxedIntPtr);
+ body += UnboxTruncate(kUnboxedFfiIntPtr);
+ body += ConvertUnboxedToUntagged(kUnboxedFfiIntPtr);
body += StoreNativeField(Slot::PointerBase_data(),
InnerPointerAccess::kCannotBeInnerPointer,
StoreFieldInstr::Kind::kInitializing);
@@ -1467,8 +1467,8 @@
body += LoadLocal(MakeTemporary()); // Duplicate Pointer.
body += LoadLocal(parsed_function_->RawParameterVariable(0)); // Address.
body += CheckNullOptimized(String::ZoneHandle(Z, function.name()));
- body += UnboxTruncate(kUnboxedIntPtr);
- body += ConvertUnboxedToUntagged(kUnboxedIntPtr);
+ body += UnboxTruncate(kUnboxedFfiIntPtr);
+ body += ConvertUnboxedToUntagged(kUnboxedFfiIntPtr);
body += StoreNativeField(Slot::PointerBase_data(),
InnerPointerAccess::kCannotBeInnerPointer,
StoreFieldInstr::Kind::kInitializing);
@@ -1800,11 +1800,12 @@
body += LoadLocal(typed_data);
body += LoadNativeField(Slot::PointerBase_data(),
InnerPointerAccess::kMayBeInnerPointer);
- body += ConvertUntaggedToUnboxed(kUnboxedIntPtr);
+ body += ConvertUntaggedToUnboxed(kUnboxedFfiIntPtr);
body += LoadLocal(offset_in_bytes);
- body += UnboxTruncate(kUnboxedIntPtr);
- body += BinaryIntegerOp(Token::kADD, kUnboxedIntPtr, /*is_truncating=*/true);
- body += ConvertUnboxedToUntagged(kUnboxedIntPtr);
+ body += UnboxTruncate(kUnboxedFfiIntPtr);
+ body +=
+ BinaryIntegerOp(Token::kADD, kUnboxedFfiIntPtr, /*is_truncating=*/true);
+ body += ConvertUnboxedToUntagged(kUnboxedFfiIntPtr);
body += StoreNativeField(Slot::PointerBase_data(),
InnerPointerAccess::kMayBeInnerPointer,
StoreFieldInstr::Kind::kInitializing);
@@ -1865,27 +1866,27 @@
call_memmove += LoadLocal(arg_to);
call_memmove += LoadNativeField(Slot::PointerBase_data(),
InnerPointerAccess::kMayBeInnerPointer);
- call_memmove += ConvertUntaggedToUnboxed(kUnboxedIntPtr);
+ call_memmove += ConvertUntaggedToUnboxed(kUnboxedFfiIntPtr);
call_memmove += LoadLocal(arg_to_start);
call_memmove += IntConstant(element_size);
call_memmove += SmiBinaryOp(Token::kMUL, /*is_truncating=*/true);
- call_memmove += UnboxTruncate(kUnboxedIntPtr);
+ call_memmove += UnboxTruncate(kUnboxedFfiIntPtr);
call_memmove +=
- BinaryIntegerOp(Token::kADD, kUnboxedIntPtr, /*is_truncating=*/true);
+ BinaryIntegerOp(Token::kADD, kUnboxedFfiIntPtr, /*is_truncating=*/true);
call_memmove += LoadLocal(arg_from);
call_memmove += LoadNativeField(Slot::PointerBase_data(),
InnerPointerAccess::kMayBeInnerPointer);
- call_memmove += ConvertUntaggedToUnboxed(kUnboxedIntPtr);
+ call_memmove += ConvertUntaggedToUnboxed(kUnboxedFfiIntPtr);
call_memmove += LoadLocal(arg_from_start);
call_memmove += IntConstant(element_size);
call_memmove += SmiBinaryOp(Token::kMUL, /*is_truncating=*/true);
- call_memmove += UnboxTruncate(kUnboxedIntPtr);
+ call_memmove += UnboxTruncate(kUnboxedFfiIntPtr);
call_memmove +=
- BinaryIntegerOp(Token::kADD, kUnboxedIntPtr, /*is_truncating=*/true);
+ BinaryIntegerOp(Token::kADD, kUnboxedFfiIntPtr, /*is_truncating=*/true);
call_memmove += LoadLocal(arg_count);
call_memmove += IntConstant(element_size);
call_memmove += SmiBinaryOp(Token::kMUL, /*is_truncating=*/true);
- call_memmove += UnboxTruncate(kUnboxedIntPtr);
+ call_memmove += UnboxTruncate(kUnboxedFfiIntPtr);
call_memmove += LoadThread();
call_memmove += LoadUntagged(
compiler::target::Thread::OffsetFromThread(&kMemoryMoveRuntimeEntry));
@@ -4529,8 +4530,8 @@
LocalVariable* pointer = MakeTemporary();
code += LoadLocal(pointer);
code += LoadLocal(address);
- code += UnboxTruncate(kUnboxedIntPtr);
- code += ConvertUnboxedToUntagged(kUnboxedIntPtr);
+ code += UnboxTruncate(kUnboxedFfiIntPtr);
+ code += ConvertUnboxedToUntagged(kUnboxedFfiIntPtr);
code += StoreNativeField(Slot::PointerBase_data(),
InnerPointerAccess::kCannotBeInnerPointer,
StoreFieldInstr::Kind::kInitializing);
diff --git a/sdk/lib/_internal/wasm/lib/boxed_int.dart b/sdk/lib/_internal/wasm/lib/boxed_int.dart
index 60a0b9f..48448e1 100644
--- a/sdk/lib/_internal/wasm/lib/boxed_int.dart
+++ b/sdk/lib/_internal/wasm/lib/boxed_int.dart
@@ -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 @@
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 @@
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) {
diff --git a/sdk/lib/_internal/wasm/lib/boxed_int_to_string.dart b/sdk/lib/_internal/wasm/lib/boxed_int_to_string.dart
new file mode 100644
index 0000000..c7803ad
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/boxed_int_to_string.dart
@@ -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;
+}
diff --git a/sdk/lib/_internal/wasm/lib/int_common_patch.dart b/sdk/lib/_internal/wasm/lib/int_common_patch.dart
new file mode 100644
index 0000000..506335d
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/int_common_patch.dart
@@ -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);
+}
diff --git a/sdk/lib/_internal/wasm/lib/int_patch.dart b/sdk/lib/_internal/wasm/lib/int_patch.dart
index 3d669b7..e84d8f5 100644
--- a/sdk/lib/_internal/wasm/lib/int_patch.dart
+++ b/sdk/lib/_internal/wasm/lib/int_patch.dart
@@ -9,10 +9,6 @@
@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 @@
-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);
}
diff --git a/sdk/lib/_internal/wasm/lib/internal_patch.dart b/sdk/lib/_internal/wasm/lib/internal_patch.dart
index 00db9f5..3ea31d8 100644
--- a/sdk/lib/_internal/wasm/lib/internal_patch.dart
+++ b/sdk/lib/_internal/wasm/lib/internal_patch.dart
@@ -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 @@
@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 {
diff --git a/sdk/lib/_internal/wasm/lib/js_array.dart b/sdk/lib/_internal/wasm/lib/js_array.dart
index f8865eb..382e59d 100644
--- a/sdk/lib/_internal/wasm/lib/js_array.dart
+++ b/sdk/lib/_internal/wasm/lib/js_array.dart
@@ -11,6 +11,9 @@
JSArrayImpl(this._ref);
+ factory JSArrayImpl.fromLength(int length) =>
+ JSArrayImpl(js.newArrayFromLengthRaw(length));
+
static JSArrayImpl? box(WasmExternRef? ref) =>
js.isDartNull(ref) ? null : JSArrayImpl(ref);
diff --git a/sdk/lib/_internal/wasm/lib/js_helper.dart b/sdk/lib/_internal/wasm/lib/js_helper.dart
index 5cfddbb..116efcd 100644
--- a/sdk/lib/_internal/wasm/lib/js_helper.dart
+++ b/sdk/lib/_internal/wasm/lib/js_helper.dart
@@ -287,6 +287,9 @@
WasmExternRef? newArrayRaw() => JS<WasmExternRef?>('() => []');
+WasmExternRef? newArrayFromLengthRaw(int length) =>
+ JS<WasmExternRef?>('l => new Array(l)', length.toDouble());
+
WasmExternRef? globalThisRaw() => JS<WasmExternRef?>('() => globalThis');
WasmExternRef? callConstructorVarArgsRaw(
diff --git a/sdk/lib/_internal/wasm/lib/js_string.dart b/sdk/lib/_internal/wasm/lib/js_string.dart
index 3d0c4f7..59834a8 100644
--- a/sdk/lib/_internal/wasm/lib/js_string.dart
+++ b/sdk/lib/_internal/wasm/lib/js_string.dart
@@ -21,6 +21,20 @@
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 @@
@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;
diff --git a/sdk/lib/_internal/wasm/lib/string.dart b/sdk/lib/_internal/wasm/lib/string.dart
index b67f7b5..7d3aa1c 100644
--- a/sdk/lib/_internal/wasm/lib/string.dart
+++ b/sdk/lib/_internal/wasm/lib/string.dart
@@ -1381,3 +1381,35 @@
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());
+}
diff --git a/sdk/lib/_internal/wasm_js_compatibility/lib/boxed_int_to_string.dart b/sdk/lib/_internal/wasm_js_compatibility/lib/boxed_int_to_string.dart
new file mode 100644
index 0000000..288ee32
--- /dev/null
+++ b/sdk/lib/_internal/wasm_js_compatibility/lib/boxed_int_to_string.dart
@@ -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));
+}
diff --git a/sdk/lib/_internal/wasm_js_compatibility/lib/convert_patch.dart b/sdk/lib/_internal/wasm_js_compatibility/lib/convert_patch.dart
index 3c62389..cef1d72 100644
--- a/sdk/lib/_internal/wasm_js_compatibility/lib/convert_patch.dart
+++ b/sdk/lib/_internal/wasm_js_compatibility/lib/convert_patch.dart
@@ -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 @@
@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.
}
-}
-class _JsonUtf8Decoder extends Converter<List<int>, Object?> {
- final Object? Function(Object? key, Object? value)? _reviver;
- final bool _allowMalformed;
+ 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;
+ }
- _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 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 _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 @@
}
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 @@
}
}
-@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);
- }
- }
-
- // 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;
+ return convertGeneral(codeUnits, start, maybeEnd, false);
}
}
+
+double _parseDouble(String source, int start, int end) =>
+ double.parse(source.substring(start, end));
diff --git a/sdk/lib/_internal/wasm_js_compatibility/lib/int_patch.dart b/sdk/lib/_internal/wasm_js_compatibility/lib/int_patch.dart
new file mode 100644
index 0000000..ac170fa
--- /dev/null
+++ b/sdk/lib/_internal/wasm_js_compatibility/lib/int_patch.dart
@@ -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();
+ }
+ }
+}
diff --git a/sdk/lib/_internal/wasm_js_compatibility/lib/string_buffer_patch.dart b/sdk/lib/_internal/wasm_js_compatibility/lib/string_buffer_patch.dart
new file mode 100644
index 0000000..941bcb8
--- /dev/null
+++ b/sdk/lib/_internal/wasm_js_compatibility/lib/string_buffer_patch.dart
@@ -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';
+}
diff --git a/sdk/lib/_internal/wasm_js_compatibility/lib/string_patch.dart b/sdk/lib/_internal/wasm_js_compatibility/lib/string_patch.dart
new file mode 100644
index 0000000..23e2052
--- /dev/null
+++ b/sdk/lib/_internal/wasm_js_compatibility/lib/string_patch.dart
@@ -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));
+}
diff --git a/sdk/lib/libraries.json b/sdk/lib/libraries.json
index 1d61015..ccbc9a0 100644
--- a/sdk/lib/libraries.json
+++ b/sdk/lib/libraries.json
@@ -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"
},
diff --git a/sdk/lib/libraries.yaml b/sdk/lib/libraries.yaml
index ea22b5f..7c7f3d5 100644
--- a/sdk/lib/libraries.yaml
+++ b/sdk/lib/libraries.yaml
@@ -119,21 +119,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
wasm_stringref:
include:
@@ -142,21 +144,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
wasm_js_compatibility:
include:
@@ -175,15 +179,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/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 @@
- _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 @@
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 @@
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:
diff --git a/tests/web/wasm/string_switch_test.dart b/tests/web/wasm/string_switch_test.dart
new file mode 100644
index 0000000..faf3460
--- /dev/null
+++ b/tests/web/wasm/string_switch_test.dart
@@ -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");
+ }
+}
diff --git a/tools/VERSION b/tools/VERSION
index 2231d63..354dae7 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 3
MINOR 3
PATCH 0
-PRERELEASE 81
+PRERELEASE 82
PRERELEASE_PATCH 0