Version 2.12.0-120.0.dev
Merge commit '37d8c78237e875355ffe36d6b2b33c29606a43fa' into 'dev'
diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_mini_ast.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_mini_ast.dart
index c62864d..6b7be5d 100644
--- a/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_mini_ast.dart
+++ b/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_mini_ast.dart
@@ -199,7 +199,7 @@
Expression get nonNullAssert => new _NonNullAssert(this);
/// If `this` is an expression `x`, creates the expression `(x)`.
- Expression get parenthesized => new _WrappedExpression(null, this, null);
+ Expression get parenthesized => new _ParenthesizedExpression(this);
/// If `this` is an expression `x`, creates the statement `x;`.
Statement get stmt => new _ExpressionStatement(this);
@@ -1225,6 +1225,28 @@
}
}
+class _ParenthesizedExpression extends Expression {
+ final Expression expr;
+
+ _ParenthesizedExpression(this.expr);
+
+ @override
+ String toString() => '($expr)';
+
+ @override
+ void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
+ expr._preVisit(assignedVariables);
+ }
+
+ @override
+ Type _visit(
+ Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
+ var type = expr._visit(h, flow);
+ flow.parenthesizedExpression(this, expr);
+ return type;
+ }
+}
+
class _PlaceholderExpression extends Expression {
final Type type;
diff --git a/pkg/dds/CHANGELOG.md b/pkg/dds/CHANGELOG.md
index 953ac0b..c88de17 100644
--- a/pkg/dds/CHANGELOG.md
+++ b/pkg/dds/CHANGELOG.md
@@ -1,3 +1,7 @@
+# 1.6.1
+- Fixed unhandled `StateError` that could be thrown if the VM service disconnected
+ while a request was outstanding.
+
# 1.6.0
- Added `errorCode` to `DartDevelopmentServiceException` to communicate the
underlying reason of the failure.
diff --git a/pkg/dds/lib/src/client.dart b/pkg/dds/lib/src/client.dart
index 2c85aae..9637058 100644
--- a/pkg/dds/lib/src/client.dart
+++ b/pkg/dds/lib/src/client.dart
@@ -240,8 +240,14 @@
// Unless otherwise specified, the request is forwarded to the VM service.
// NOTE: This must be the last fallback registered.
- _clientPeer.registerFallback((parameters) async =>
- await _vmServicePeer.sendRequest(parameters.method, parameters.value));
+ _clientPeer.registerFallback((parameters) async {
+ try {
+ return await _vmServicePeer.sendRequest(
+ parameters.method, parameters.value);
+ } on StateError {
+ await dds.shutdown();
+ }
+ });
}
static int _idCounter = 0;
diff --git a/pkg/dds/pubspec.yaml b/pkg/dds/pubspec.yaml
index d4132a1..d0e85c6 100644
--- a/pkg/dds/pubspec.yaml
+++ b/pkg/dds/pubspec.yaml
@@ -3,7 +3,7 @@
A library used to spawn the Dart Developer Service, used to communicate with
a Dart VM Service instance.
-version: 1.6.0
+version: 1.6.1
homepage: https://github.com/dart-lang/sdk/tree/master/pkg/dds
diff --git a/pkg/dds/test/handles_client_disconnect_state_error_test.dart b/pkg/dds/test/handles_client_disconnect_state_error_test.dart
index 2bca092..f500711 100644
--- a/pkg/dds/test/handles_client_disconnect_state_error_test.dart
+++ b/pkg/dds/test/handles_client_disconnect_state_error_test.dart
@@ -24,6 +24,11 @@
// Notify listeners that this client is closed.
doneCompleter.complete();
break;
+ case 'foo':
+ completer.completeError(
+ StateError('The client closed with pending request "foo".'),
+ );
+ break;
default:
completer.complete(await super.sendRequest(method, args));
}
@@ -61,4 +66,28 @@
await client.close();
await dds.done;
});
+
+ test('StateError handled by _DartDevelopmentServiceClient request forwarder',
+ () async {
+ final dds = await DartDevelopmentService.startDartDevelopmentService(
+ Uri(scheme: 'http'));
+ final ws = await WebSocketChannel.connect(dds.uri.replace(scheme: 'ws'));
+
+ // Create a VM service client that connects to DDS.
+ final client = json_rpc.Client(ws.cast<String>());
+ unawaited(client.listen());
+
+ // Make a request that causes the VM service peer to close in the middle of
+ // handling a request. This is meant to mimic a device being disconnected
+ // unexpectedly.
+ try {
+ await client.sendRequest('foo');
+ } on StateError {
+ // This state error is expected. This test is ensuring that DDS exits
+ // gracefully even if the VM service disappears.
+ }
+
+ // DDS should shutdown if the VM service peer disconnects.
+ await dds.done;
+ });
}
diff --git a/pkg/dev_compiler/lib/src/compiler/module_containers.dart b/pkg/dev_compiler/lib/src/compiler/module_containers.dart
new file mode 100644
index 0000000..82f06b4
--- /dev/null
+++ b/pkg/dev_compiler/lib/src/compiler/module_containers.dart
@@ -0,0 +1,259 @@
+// Copyright (c) 2020, 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.
+
+// @dart = 2.9
+
+import '../compiler/js_names.dart' as js_ast;
+import '../js_ast/js_ast.dart' as js_ast;
+import '../js_ast/js_ast.dart' show js;
+
+/// Represents a top-level property hoisted to a top-level object.
+class ModuleItemData {
+ /// The container that holds this module item in the emitted JS.
+ js_ast.Identifier id;
+
+ /// This module item's key in the emitted JS.
+ ///
+ /// A LiteralString if this object is backed by a JS Object/Map.
+ /// A LiteralNumber if this object is backed by a JS Array.
+ js_ast.Literal jsKey;
+
+ /// This module item's value in the emitted JS.
+ js_ast.Expression jsValue;
+
+ ModuleItemData(this.id, this.jsKey, this.jsValue);
+}
+
+/// Holds variables emitted during code gen.
+///
+/// Associates a [K] with a container-unique JS key and arbitrary JS value.
+/// The container is emitted as a single object:
+/// ```
+/// var C = {
+/// jsKey: jsValue,
+/// ...
+/// };
+/// ```
+class ModuleItemContainer<K> {
+ /// Name of the container in the emitted JS.
+ String name;
+
+ /// If null, this container will be automatically renamed based on [name].
+ js_ast.Identifier containerId;
+
+ final Map<K, ModuleItemData> moduleItems = {};
+
+ /// Holds keys that will not be emitted when calling [emit].
+ final Set<K> _noEmit = {};
+
+ ModuleItemContainer._(this.name, this.containerId);
+
+ /// Creates an automatically sharding container backed by JS Objects.
+ factory ModuleItemContainer.asObject(String name,
+ {String Function(K) keyToString}) {
+ return ModuleItemObjectContainer<K>(name, keyToString);
+ }
+
+ /// Creates a container backed by a JS Array.
+ factory ModuleItemContainer.asArray(String name) {
+ return ModuleItemArrayContainer<K>(name);
+ }
+
+ bool get isNotEmpty => moduleItems.isNotEmpty;
+
+ Iterable<K> get keys => moduleItems.keys;
+
+ int get length => moduleItems.keys.length;
+
+ js_ast.Expression operator [](K key) => moduleItems[key]?.jsValue;
+
+ void operator []=(K key, js_ast.Expression value) {
+ if (moduleItems.containsKey(key)) {
+ moduleItems[key].jsValue = value;
+ return;
+ }
+ var fieldString = '$key';
+ // Avoid shadowing common JS properties.
+ if (js_ast.objectProperties.contains(fieldString)) {
+ fieldString += '\$';
+ }
+ moduleItems[key] = ModuleItemData(
+ containerId, js_ast.LiteralString("'$fieldString'"), value);
+ }
+
+ /// Returns the expression that retrieves [key]'s corresponding JS value via
+ /// a property access through its container.
+ js_ast.Expression access(K key) {
+ return js.call('#.#', [containerId, moduleItems[key].jsKey]);
+ }
+
+ /// Emit the container declaration/initializer.
+ ///
+ /// May be multiple statements if the container is automatically sharded.
+ List<js_ast.Statement> emit() {
+ var properties = <js_ast.Property>[];
+ moduleItems.forEach((k, v) {
+ if (!_noEmit.contains(k)) return;
+ properties.add(js_ast.Property(v.jsKey, v.jsValue));
+ });
+ var containerObject =
+ js_ast.ObjectInitializer(properties, multiline: properties.length > 1);
+ return [
+ js.statement('var # = Object.create(#)', [containerId, containerObject])
+ ];
+ }
+
+ bool contains(K key) => moduleItems.containsKey(key);
+
+ bool canEmit(K key) => !_noEmit.contains(key);
+
+ /// Indicates that [K] should be treated as if it weren't hoisted.
+ ///
+ /// Used when we are managing the variable declarations manually (such as
+ /// unhoisting specific symbols for performance reasons).
+ void setNoEmit(K key) {
+ _noEmit.add(key);
+ }
+}
+
+/// Associates a [K] with a container-unique JS key and arbitrary JS value.
+///
+/// Emitted as a series of JS Objects, splitting them into groups of 500 for
+/// JS optimization purposes:
+/// ```
+/// var C = {
+/// jsKey: jsValue,
+/// ...
+/// };
+/// var C$1 = { ... };
+/// ```
+class ModuleItemObjectContainer<K> extends ModuleItemContainer<K> {
+ /// Holds the TemporaryId for the current container shard.
+ js_ast.Identifier _currentContainerId;
+
+ /// Tracks how often JS emitted field names appear.
+ ///
+ /// [keyToString] may resolve multiple unique keys to the same JS string.
+ /// When this occurs, the resolved JS string will automatically be renamed.
+ final Map<String, int> _nameFrequencies = {};
+
+ /// Transforms a [K] into a valid name for a JS object property key.
+ ///
+ /// Non-unique generated strings are automatically renamed.
+ String Function(K) keyToString;
+
+ ModuleItemObjectContainer(String name, this.keyToString)
+ : super._(name, null);
+
+ @override
+ void operator []=(K key, js_ast.Expression value) {
+ if (this.contains(key)) {
+ moduleItems[key].jsValue = value;
+ return;
+ }
+ if (length % 500 == 0) _currentContainerId = js_ast.TemporaryId(name);
+ // Create a unique name for K when emitted as a JS field.
+ var fieldString = keyToString(key);
+ _nameFrequencies.update(fieldString, (v) {
+ fieldString += '\$${v + 1}';
+ return v + 1;
+ }, ifAbsent: () {
+ // Avoid shadowing common JS properties.
+ if (js_ast.objectProperties.contains(fieldString)) {
+ fieldString += '\$';
+ }
+ return 0;
+ });
+ moduleItems[key] = ModuleItemData(
+ _currentContainerId, js_ast.LiteralString("'$fieldString'"), value);
+ }
+
+ @override
+ js_ast.Expression access(K key) {
+ return js.call('#.#', [moduleItems[key].id, moduleItems[key].jsKey]);
+ }
+
+ @override
+ List<js_ast.Statement> emit() {
+ var containersToProperties = <js_ast.Identifier, List<js_ast.Property>>{};
+ moduleItems.forEach((k, v) {
+ if (_noEmit.contains(k)) return;
+ if (!containersToProperties.containsKey(v.id)) {
+ containersToProperties[v.id] = <js_ast.Property>[];
+ }
+ containersToProperties[v.id].add(js_ast.Property(v.jsKey, v.jsValue));
+ });
+
+ var statements = <js_ast.Statement>[];
+ containersToProperties.forEach((containerId, properties) {
+ var containerObject = js_ast.ObjectInitializer(properties,
+ multiline: properties.length > 1);
+ statements.add(js.statement(
+ 'var # = Object.create(#)', [containerId, containerObject]));
+ });
+ return statements;
+ }
+}
+
+/// Associates a unique [K] with an arbitrary JS value.
+///
+/// Emitted as a JS Array:
+/// ```
+/// var C = [
+/// jsValue,
+/// ...
+/// ];
+/// ```
+class ModuleItemArrayContainer<K> extends ModuleItemContainer<K> {
+ ModuleItemArrayContainer(String name)
+ : super._(name, js_ast.TemporaryId(name));
+
+ @override
+ void operator []=(K key, js_ast.Expression value) {
+ if (moduleItems.containsKey(key)) {
+ moduleItems[key].jsValue = value;
+ return;
+ }
+ moduleItems[key] =
+ ModuleItemData(containerId, js_ast.LiteralNumber('$length'), value);
+ }
+
+ @override
+ js_ast.Expression access(K key) {
+ return js.call('#[#]', [containerId, moduleItems[key].jsKey]);
+ }
+
+ @override
+ List<js_ast.Statement> emit() {
+ if (moduleItems.isEmpty) return [];
+ var properties = List<js_ast.Expression>(length);
+
+ // If the entire array holds just one value, generate a short initializer.
+ var valueSet = <js_ast.Expression>{};
+ moduleItems.forEach((k, v) {
+ if (_noEmit.contains(k)) return;
+ valueSet.add(v.jsValue);
+ properties[int.parse((v.jsKey as js_ast.LiteralNumber).value)] =
+ v.jsValue;
+ });
+
+ if (valueSet.length == 1 && moduleItems.length > 1) {
+ return [
+ js.statement('var # = Array(#).fill(#)', [
+ containerId,
+ js_ast.LiteralNumber('${properties.length}'),
+ valueSet.first
+ ])
+ ];
+ }
+ // Array containers are not sharded, as we do not expect to hit V8's
+ // dictionary-mode limit of 99999 elements.
+ return [
+ js.statement('var # = #', [
+ containerId,
+ js_ast.ArrayInitializer(properties, multiline: properties.length > 1)
+ ])
+ ];
+ }
+}
diff --git a/pkg/dev_compiler/lib/src/compiler/shared_compiler.dart b/pkg/dev_compiler/lib/src/compiler/shared_compiler.dart
index e9527d9..2fcad3e 100644
--- a/pkg/dev_compiler/lib/src/compiler/shared_compiler.dart
+++ b/pkg/dev_compiler/lib/src/compiler/shared_compiler.dart
@@ -8,6 +8,7 @@
import 'package:meta/meta.dart';
import '../compiler/js_names.dart' as js_ast;
+import '../compiler/module_containers.dart' show ModuleItemContainer;
import '../js_ast/js_ast.dart' as js_ast;
import '../js_ast/js_ast.dart' show js;
@@ -25,6 +26,10 @@
/// Private member names in this module, organized by their library.
final _privateNames = HashMap<Library, HashMap<String, js_ast.TemporaryId>>();
+ /// Holds all top-level JS symbols (used for caching or indexing fields).
+ final _symbolContainer = ModuleItemContainer<js_ast.Identifier>.asObject('S',
+ keyToString: (js_ast.Identifier i) => '${i.name}');
+
/// Extension member symbols for adding Dart members to JS types.
///
/// These are added to the [extensionSymbolsModule]; see that field for more
@@ -51,11 +56,18 @@
/// Whether we're currently building the SDK, which may require special
/// bootstrapping logic.
///
- /// This is initialized by [startModule], which must be called before
+ /// This is initialized by [emitModule], which must be called before
/// accessing this field.
@protected
bool isBuildingSdk;
+ /// Whether or not to move top level symbols into top-level containers.
+ ///
+ /// This is set in both [emitModule] and [emitLibrary].
+ /// Depends on [isBuildingSdk].
+ @protected
+ bool containerizeSymbols;
+
/// The temporary variable that stores named arguments (these are passed via a
/// JS object literal, to match JS conventions).
@protected
@@ -250,10 +262,16 @@
var idName = name.endsWith('=') ? name.replaceAll('=', '_') : name;
idName = idName.replaceAll(js_ast.invalidCharInIdentifier, '_');
id ??= js_ast.TemporaryId(idName);
- // TODO(vsm): Change back to `const`.
- // See https://github.com/dart-lang/sdk/issues/40380.
- moduleItems.add(js.statement('var # = #.privateName(#, #)',
- [id, runtimeModule, emitLibraryName(library), js.string(name)]));
+ addSymbol(
+ id,
+ js.call('#.privateName(#, #)',
+ [runtimeModule, emitLibraryName(library), js.string(name)]));
+ if (!containerizeSymbols) {
+ // TODO(vsm): Change back to `const`.
+ // See https://github.com/dart-lang/sdk/issues/40380.
+ moduleItems.add(js.statement('var # = #.privateName(#, #)',
+ [id, runtimeModule, emitLibraryName(library), js.string(name)]));
+ }
return id;
}
@@ -338,9 +356,13 @@
var name = js.escapedString(symbolName, "'");
js_ast.Expression result;
if (last.startsWith('_')) {
- var nativeSymbol = emitPrivateNameSymbol(currentLibrary, last);
- result = js.call('new #.new(#, #)',
- [emitConstructorAccess(privateSymbolType), name, nativeSymbol]);
+ var nativeSymbolAccessor =
+ getSymbol(emitPrivateNameSymbol(currentLibrary, last));
+ result = js.call('new #.new(#, #)', [
+ emitConstructorAccess(privateSymbolType),
+ name,
+ nativeSymbolAccessor
+ ]);
} else {
result = js.call(
'new #.new(#)', [emitConstructorAccess(internalSymbolType), name]);
@@ -366,12 +388,11 @@
/// symbols into the list returned by this method. Finally, [finishModule]
/// can be called to complete the module and return the resulting JS AST.
///
- /// This also initializes several fields: [isBuildingSdk], [runtimeModule],
- /// [extensionSymbolsModule], as well as the [_libraries] map needed by
+ /// This also initializes several fields: [runtimeModule],
+ /// [extensionSymbolsModule], and the [_libraries] map needed by
/// [emitLibraryName].
@protected
List<js_ast.ModuleItem> startModule(Iterable<Library> libraries) {
- isBuildingSdk = libraries.any(isSdkInternalRuntime);
if (isBuildingSdk) {
// Don't allow these to be renamed when we're building the SDK.
// There is JS code in dart:* that depends on their names.
@@ -505,9 +526,13 @@
if (isBuildingSdk) {
value = js.call('# = Symbol(#)', [value, js.string('dartx.$name')]);
}
- // TODO(vsm): Change back to `const`.
- // See https://github.com/dart-lang/sdk/issues/40380.
- items.add(js.statement('var # = #;', [id, value]));
+ if (!_symbolContainer.canEmit(id)) {
+ // Extension symbols marked with noEmit are managed manually.
+ // TODO(vsm): Change back to `const`.
+ // See https://github.com/dart-lang/sdk/issues/40380.
+ items.add(js.statement('var # = #;', [id, value]));
+ }
+ _symbolContainer[id] = value;
});
}
@@ -534,6 +559,28 @@
[runtimeModule, js.string(name), module, partMap]));
}
+ /// Returns an accessor for [id] via the symbol container.
+ /// E.g., transforms $sym to S$5.$sym.
+ ///
+ /// A symbol lookup on an id marked no emit omits the symbol accessor.
+ js_ast.Expression getSymbol(js_ast.Identifier id) {
+ return _symbolContainer.canEmit(id) ? _symbolContainer.access(id) : id;
+ }
+
+ /// Returns the raw JS value associated with [id].
+ js_ast.Expression getSymbolValue(js_ast.Identifier id) {
+ return _symbolContainer[id];
+ }
+
+ /// Inserts a symbol into the symbol table.
+ js_ast.Expression addSymbol(js_ast.Identifier id, js_ast.Expression symbol) {
+ _symbolContainer[id] = symbol;
+ if (!containerizeSymbols) {
+ _symbolContainer.setNoEmit(id);
+ }
+ return _symbolContainer[id];
+ }
+
/// Finishes the module created by [startModule], by combining the preable
/// [items] with the [moduleItems] that have been emitted.
///
@@ -552,6 +599,9 @@
// code between `startModule` and `finishModule` is very similar in both.
_emitDebuggerExtensionInfo(moduleName);
+ // Emit all top-level JS symbol containers.
+ items.addAll(_symbolContainer.emit());
+
// Add the module's code (produced by visiting compilation units, above)
_copyAndFlattenBlocks(items, moduleItems);
moduleItems.clear();
@@ -582,10 +632,13 @@
/// handle the many details involved in naming.
@protected
js_ast.TemporaryId getExtensionSymbolInternal(String name) {
- return _extensionSymbols.putIfAbsent(
- name,
- () => js_ast.TemporaryId(
- '\$${js_ast.friendlyNameForDartOperator[name] ?? name}'));
+ if (!_extensionSymbols.containsKey(name)) {
+ var id = js_ast.TemporaryId(
+ '\$${js_ast.friendlyNameForDartOperator[name] ?? name}');
+ _extensionSymbols[name] = id;
+ addSymbol(id, id);
+ }
+ return _extensionSymbols[name];
}
/// Shorthand for identifier-like property names.
diff --git a/pkg/dev_compiler/lib/src/kernel/compiler.dart b/pkg/dev_compiler/lib/src/kernel/compiler.dart
index db85b79..8800d4c 100644
--- a/pkg/dev_compiler/lib/src/kernel/compiler.dart
+++ b/pkg/dev_compiler/lib/src/kernel/compiler.dart
@@ -22,6 +22,7 @@
import '../compiler/js_utils.dart' as js_ast;
import '../compiler/module_builder.dart'
show isSdkInternalRuntimeUri, libraryUriToJsIdentifier, pathToJSIdentifier;
+import '../compiler/module_containers.dart' show ModuleItemContainer;
import '../compiler/shared_command.dart' show SharedCompilerOptions;
import '../compiler/shared_compiler.dart';
import '../js_ast/js_ast.dart' as js_ast;
@@ -69,11 +70,14 @@
/// Let variables collected for the given function.
List<js_ast.TemporaryId> _letVariables;
- final _constTable = _emitTemporaryId('CT');
+ final _constTable = js_ast.TemporaryId('CT');
- // Constant getters used to populate the constant table.
+ /// Constant getters used to populate the constant table.
final _constLazyAccessors = <js_ast.Method>[];
+ /// Container for holding the results of lazily-evaluated constants.
+ final _constTableCache = ModuleItemContainer<String>.asArray('C');
+
/// Tracks the index in [moduleItems] where the const table must be inserted.
/// Required for SDK builds due to internal circular dependencies.
/// E.g., dart.constList depends on JSArray.
@@ -216,7 +220,7 @@
final constAliasCache = HashMap<Constant, js_ast.Expression>();
/// Maps uri strings in asserts and elsewhere to hoisted identifiers.
- final _uriMap = HashMap<String, js_ast.Identifier>();
+ final _uriContainer = ModuleItemContainer<String>.asArray('I');
final Class _jsArrayClass;
final Class _privateSymbolClass;
@@ -323,7 +327,32 @@
var libraries = component.libraries;
- // Initialize our library variables.
+ // Initialize library variables.
+ isBuildingSdk = libraries.any(isSdkInternalRuntime);
+
+ // For runtime performance reasons, we only containerize SDK symbols in web
+ // libraries. Otherwise, we use a 600-member cutoff before a module is
+ // containerized. This is somewhat arbitrary but works promisingly for the
+ // SDK and Flutter Web.
+ if (!isBuildingSdk) {
+ // The number of DDC top-level symbols scales with the number of
+ // non-static class members across an entire module.
+ var uniqueNames = HashSet<String>();
+ libraries.forEach((Library l) {
+ l.classes.forEach((Class c) {
+ c.members.forEach((m) {
+ var isStatic =
+ m is Field ? m.isStatic : (m is Procedure ? m.isStatic : false);
+ if (isStatic) return;
+ var name = js_ast.toJSIdentifier(
+ m.name.text.replaceAll(js_ast.invalidCharInIdentifier, '_'));
+ uniqueNames.add(name);
+ });
+ });
+ });
+ containerizeSymbols = uniqueNames.length > 600;
+ }
+
var items = startModule(libraries);
_nullableInference.allowNotNullDeclarations = isBuildingSdk;
_typeTable = TypeTable(runtimeModule);
@@ -335,10 +364,12 @@
_pendingClasses.addAll(l.classes);
}
- // TODO(markzipan): Don't emit this when compiling the SDK.
- moduleItems
- .add(js.statement('const # = Object.create(null);', [_constTable]));
- _constTableInsertionIndex = moduleItems.length;
+ // Record a safe index after the declaration of type generators and
+ // top-level symbols but before the declaration of any functions.
+ // Various preliminary data structures must be inserted here prior before
+ // referenced by the rest of the module.
+ var safeDeclarationIndex = moduleItems.length;
+ _constTableInsertionIndex = safeDeclarationIndex;
// Add implicit dart:core dependency so it is first.
emitLibraryName(_coreTypes.coreLibrary);
@@ -350,22 +381,25 @@
// This is done by forward declaring items.
libraries.forEach(_emitLibrary);
+ // Emit hoisted assert strings
+ moduleItems.insertAll(safeDeclarationIndex, _uriContainer.emit());
+
+ if (_constTableCache.isNotEmpty) {
+ moduleItems.insertAll(safeDeclarationIndex, _constTableCache.emit());
+ }
+
// This can cause problems if it's ever true during the SDK build, as it's
// emitted before dart.defineLazy.
if (_constLazyAccessors.isNotEmpty) {
+ var constTableDeclaration =
+ js.statement('const # = Object.create(null);', [_constTable]);
var constTableBody = runtimeStatement(
'defineLazy(#, { # }, false)', [_constTable, _constLazyAccessors]);
- moduleItems.insert(_constTableInsertionIndex, constTableBody);
+ moduleItems.insertAll(
+ _constTableInsertionIndex, [constTableDeclaration, constTableBody]);
_constLazyAccessors.clear();
}
- // Add assert locations
- _uriMap.forEach((location, id) {
- var value = location == null ? 'null' : js.escapedString(location);
- moduleItems.insert(
- _constTableInsertionIndex, js.statement('var # = #;', [id, value]));
- });
-
moduleItems.addAll(afterClassDefItems);
afterClassDefItems.clear();
@@ -375,9 +409,8 @@
// Declare imports and extension symbols
emitImportsAndExtensionSymbols(items);
- // Discharge the type table cache variables and
- // hoisted definitions.
- items.addAll(_typeTable.discharge());
+ // Emit the hoisted type table cache variables
+ items.addAll(_typeTable.dischargeBoundTypes());
return finishModule(items, _options.moduleName);
}
@@ -442,6 +475,10 @@
_currentLibrary = library;
_staticTypeContext.enterLibrary(_currentLibrary);
+ if (isBuildingSdk) {
+ containerizeSymbols = _isWebLibrary(library.importUri);
+ }
+
if (isSdkInternalRuntime(library)) {
// `dart:_runtime` uses a different order for bootstrapping.
//
@@ -566,10 +603,29 @@
_classProperties =
ClassPropertyModel.build(_types, _extensionTypes, _virtualFields, c);
+ var body = <js_ast.Statement>[];
+
+ // ClassPropertyModel.build introduces symbols for virtual field accessors.
+ _classProperties.virtualFields.forEach((field, virtualField) {
+ // TODO(vsm): Clean up this logic.
+ //
+ // Typically, [emitClassPrivateNameSymbol] creates a new symbol. If it
+ // is called multiple times, that symbol is cached. If the former,
+ // assign directly to [virtualField]. If the latter, copy the old
+ // variable to [virtualField].
+ var symbol = emitClassPrivateNameSymbol(c.enclosingLibrary,
+ getLocalClassName(c), field.name.text, virtualField);
+ if (symbol != virtualField) {
+ addSymbol(virtualField, getSymbolValue(symbol));
+ if (!containerizeSymbols) {
+ body.add(js.statement('const # = #;', [virtualField, symbol]));
+ }
+ }
+ });
+
var jsCtors = _defineConstructors(c, className);
var jsMethods = _emitClassMethods(c);
- var body = <js_ast.Statement>[];
_emitSuperHelperSymbols(body);
// Deferred supertypes must be evaluated lazily while emitting classes to
// prevent evaluating a JS expression for a deferred type from influencing
@@ -593,7 +649,6 @@
// Attach caches on all canonicalized types.
body.add(runtimeStatement('addTypeCaches(#)', [className]));
- _emitVirtualFieldSymbols(c, body);
_emitClassSignature(c, className, body);
_initExtensionSymbols(c);
if (!c.isMixinDeclaration) {
@@ -644,7 +699,7 @@
var typeConstructor = js.call('(#) => { #; #; return #; }', [
jsFormals,
- _typeTable.discharge(formals),
+ _typeTable.dischargeFreeTypes(formals),
body,
className ?? _emitIdentifier(name)
]);
@@ -1525,7 +1580,13 @@
var body = <js_ast.Statement>[];
void emitFieldInit(Field f, Expression initializer, TreeNode hoverInfo) {
- var access = _classProperties.virtualFields[f] ?? _declareMemberName(f);
+ var virtualField = _classProperties.virtualFields[f];
+
+ // Avoid calling getSymbol on _declareMemberName since _declareMemberName
+ // calls _emitMemberName downstream, which already invokes getSymbol.
+ var access = virtualField == null
+ ? _declareMemberName(f)
+ : getSymbol(virtualField);
var jsInit = _visitInitializer(initializer, f.annotations);
body.add(jsInit
.toAssignExpression(js.call('this.#', [access])
@@ -1912,14 +1973,16 @@
/// wrong behavior if a new field was declared.
List<js_ast.Method> _emitVirtualFieldAccessor(Field field) {
var virtualField = _classProperties.virtualFields[field];
+ var virtualFieldSymbol = getSymbol(virtualField);
var name = _declareMemberName(field);
- var getter = js.fun('function() { return this[#]; }', [virtualField]);
+ var getter = js.fun('function() { return this[#]; }', [virtualFieldSymbol]);
var jsGetter = js_ast.Method(name, getter, isGetter: true)
..sourceInformation = _nodeStart(field);
- var args =
- field.isFinal ? [js_ast.Super(), name] : [js_ast.This(), virtualField];
+ var args = field.isFinal
+ ? [js_ast.Super(), name]
+ : [js_ast.This(), virtualFieldSymbol];
js_ast.Expression value = _emitIdentifier('value');
if (!field.isFinal && isCovariantField(field)) {
@@ -2235,13 +2298,13 @@
var memberLibrary = member?.name?.library ??
memberClass?.enclosingLibrary ??
_currentLibrary;
- return emitPrivateNameSymbol(memberLibrary, name);
+ return getSymbol(emitPrivateNameSymbol(memberLibrary, name));
}
useExtension ??= _isSymbolizedMember(memberClass, name);
name = js_ast.memberNameForDartMember(name, _isExternal(member));
if (useExtension) {
- return getExtensionSymbolInternal(name);
+ return getSymbol(getExtensionSymbolInternal(name));
}
return propertyName(name);
}
@@ -2858,7 +2921,7 @@
js_ast.Expression addTypeFormalsAsParameters(
List<js_ast.Expression> elements) {
- var names = _typeTable.discharge(typeFormals);
+ var names = _typeTable.dischargeFreeTypes(typeFormals);
return names.isEmpty
? js.call('(#) => [#]', [tf, elements])
: js.call('(#) => {#; return [#];}', [tf, names, elements]);
@@ -3006,9 +3069,8 @@
// Issue: https://github.com/dart-lang/sdk/issues/43288
var fun = _emitFunction(functionNode, name);
- var types = _typeTable.discharge();
+ var types = _typeTable?.dischargeFreeTypes();
var constants = _dischargeConstTable();
-
var body = js_ast.Block([...?types, ...?constants, ...fun.body.statements]);
return js_ast.Fun(fun.params, body);
}
@@ -3102,22 +3164,6 @@
return result;
}
- void _emitVirtualFieldSymbols(Class c, List<js_ast.Statement> body) {
- _classProperties.virtualFields.forEach((field, virtualField) {
- // TODO(vsm): Clean up this logic. See comments on the following method.
- //
- // Typically, [emitClassPrivateNameSymbol] creates a new symbol. If it
- // is called multiple times, that symbol is cached. If the former,
- // assign directly to [virtualField]. If the latter, copy the old
- // variable to [virtualField].
- var symbol = emitClassPrivateNameSymbol(c.enclosingLibrary,
- getLocalClassName(c), field.name.text, virtualField);
- if (symbol != virtualField) {
- body.add(js.statement('const # = #;', [virtualField, symbol]));
- }
- });
- }
-
List<js_ast.Identifier> _emitTypeFormals(List<TypeParameter> typeFormals) {
return typeFormals
.map((t) => _emitIdentifier(getTypeParameterName(t)))
@@ -3644,14 +3690,11 @@
// Replace a string `uri` literal with a cached top-level variable containing
// the value to reduce overall code size.
- js_ast.Identifier _cacheUri(String uri) {
- var id = _uriMap[uri];
- if (id == null) {
- var name = 'L${_uriMap.length}';
- id = js_ast.TemporaryId(name);
- _uriMap[uri] = id;
+ js_ast.Expression _cacheUri(String uri) {
+ if (!_uriContainer.contains(uri)) {
+ _uriContainer[uri] = js_ast.LiteralString('"$uri"');
}
- return id;
+ return _uriContainer.access(uri);
}
@override
@@ -5004,7 +5047,7 @@
return _emitType(firstArg.type);
}
if (name == 'extensionSymbol' && firstArg is StringLiteral) {
- return getExtensionSymbolInternal(firstArg.value);
+ return getSymbol(getExtensionSymbolInternal(firstArg.value));
}
if (name == 'compileTimeFlag' && firstArg is StringLiteral) {
@@ -5880,19 +5923,19 @@
}
var constAliasString = 'C${constAliasCache.length}';
var constAliasProperty = propertyName(constAliasString);
- var constAliasId = _emitTemporaryId(constAliasString);
- var constAccessor =
- js.call('# || #.#', [constAliasId, _constTable, constAliasProperty]);
+
+ _constTableCache[constAliasString] = js.call('void 0');
+ var constAliasAccessor = _constTableCache.access(constAliasString);
+
+ var constAccessor = js.call(
+ '# || #.#', [constAliasAccessor, _constTable, constAliasProperty]);
constAliasCache[node] = constAccessor;
var constJs = super.visitConstant(node);
- // TODO(vsm): Change back to `let`.
- // See https://github.com/dart-lang/sdk/issues/40380.
- moduleItems.add(js.statement('var #;', [constAliasId]));
var func = js_ast.Fun(
[],
js_ast.Block([
- js.statement('return # = #;', [constAliasId, constJs])
+ js.statement('return # = #;', [constAliasAccessor, constJs])
]));
var accessor = js_ast.Method(constAliasProperty, func, isGetter: true);
_constLazyAccessors.add(accessor);
@@ -5978,8 +6021,8 @@
// was overridden.
var symbol = cls.isEnum
? _emitMemberName(member.name.text, member: member)
- : emitClassPrivateNameSymbol(
- cls.enclosingLibrary, getLocalClassName(cls), member.name.text);
+ : getSymbol(emitClassPrivateNameSymbol(
+ cls.enclosingLibrary, getLocalClassName(cls), member.name.text));
return js_ast.Property(symbol, constant);
}
diff --git a/pkg/dev_compiler/lib/src/kernel/type_table.dart b/pkg/dev_compiler/lib/src/kernel/type_table.dart
index ddaf27f..b9b73fd 100644
--- a/pkg/dev_compiler/lib/src/kernel/type_table.dart
+++ b/pkg/dev_compiler/lib/src/kernel/type_table.dart
@@ -4,13 +4,17 @@
// @dart = 2.9
+import 'dart:collection';
+
import 'package:kernel/kernel.dart';
import '../compiler/js_names.dart' as js_ast;
+import '../compiler/module_containers.dart' show ModuleItemContainer;
import '../js_ast/js_ast.dart' as js_ast;
import '../js_ast/js_ast.dart' show js;
import 'kernel_helpers.dart';
+/// Returns all non-locally defined type parameters referred to by [t].
Set<TypeParameter> freeTypeParameters(DartType t) {
assert(isKnownDartTypeImplementor(t));
var result = <TypeParameter>{};
@@ -36,160 +40,144 @@
return result;
}
-/// _CacheTable tracks cache variables for variables that
-/// are emitted in place with a hoisted variable for a cache.
-class _CacheTable {
- /// Mapping from types to their canonical names.
- // Use a LinkedHashMap to maintain key insertion order so the generated code
- // is stable under slight perturbation. (If this is not good enough we could
- // sort by name to canonicalize order.)
- final _names = <DartType, js_ast.TemporaryId>{};
- Iterable<DartType> get keys => _names.keys.toList();
-
- js_ast.Statement _dischargeType(DartType type) {
- var name = _names.remove(type);
- if (name != null) {
- return js.statement('let #;', [name]);
- }
- return null;
+/// A name for a type made of JS identifier safe characters.
+///
+/// 'L' and 'N' are prepended to a type name to represent a legacy or nullable
+/// flavor of a type.
+String _typeString(DartType type, {bool flat = false}) {
+ var nullability = type.declaredNullability == Nullability.legacy
+ ? 'L'
+ : type.declaredNullability == Nullability.nullable
+ ? 'N'
+ : '';
+ assert(isKnownDartTypeImplementor(type));
+ if (type is InterfaceType) {
+ var name = '${type.classNode.name}$nullability';
+ var typeArgs = type.typeArguments;
+ if (typeArgs == null) return name;
+ if (typeArgs.every((p) => p == const DynamicType())) return name;
+ return "${name}Of${typeArgs.map(_typeString).join("\$")}";
}
-
- /// Emit a list of statements declaring the cache variables for
- /// types tracked by this table. If [typeFilter] is given,
- /// only emit the types listed in the filter.
- List<js_ast.Statement> discharge([Iterable<DartType> typeFilter]) {
- var decls = <js_ast.Statement>[];
- var types = typeFilter ?? keys;
- for (var t in types) {
- var stmt = _dischargeType(t);
- if (stmt != null) decls.add(stmt);
- }
- return decls;
+ if (type is FutureOrType) {
+ var name = 'FutureOr$nullability';
+ if (type.typeArgument == const DynamicType()) return name;
+ return '${name}Of${_typeString(type.typeArgument)}';
}
-
- bool isNamed(DartType type) => _names.containsKey(type);
-
- /// A name for a type made of JS identifier safe characters.
- ///
- /// 'L' and 'N' are prepended to a type name to represent a legacy or nullable
- /// flavor of a type.
- String _typeString(DartType type, {bool flat = false}) {
- var nullability = type.declaredNullability == Nullability.legacy
- ? 'L'
- : type.declaredNullability == Nullability.nullable
- ? 'N'
- : '';
- assert(isKnownDartTypeImplementor(type));
- if (type is InterfaceType) {
- var name = '${type.classNode.name}$nullability';
- var typeArgs = type.typeArguments;
- if (typeArgs == null) return name;
- if (typeArgs.every((p) => p == const DynamicType())) return name;
- return "${name}Of${typeArgs.map(_typeString).join("\$")}";
- }
- if (type is FutureOrType) {
- var name = 'FutureOr$nullability';
- if (type.typeArgument == const DynamicType()) return name;
- return '${name}Of${_typeString(type.typeArgument)}';
- }
- if (type is TypedefType) {
- var name = '${type.typedefNode.name}$nullability';
- var typeArgs = type.typeArguments;
- if (typeArgs == null) return name;
- if (typeArgs.every((p) => p == const DynamicType())) return name;
- return "${name}Of${typeArgs.map(_typeString).join("\$")}";
- }
- if (type is FunctionType) {
- if (flat) return 'Fn';
- var rType = _typeString(type.returnType, flat: true);
- var params = type.positionalParameters
- .take(3)
- .map((p) => _typeString(p, flat: true));
- var paramList = params.join('And');
- var count = type.positionalParameters.length;
- if (count > 3 || type.namedParameters.isNotEmpty) {
- paramList = '${paramList}__';
- } else if (count == 0) {
- paramList = 'Void';
- }
- return '${paramList}To$nullability$rType';
- }
- if (type is TypeParameterType) return '${type.parameter.name}$nullability';
- if (type is DynamicType) return 'dynamic';
- if (type is VoidType) return 'void';
- if (type is NeverType) return 'Never$nullability';
- if (type is BottomType) return 'bottom';
- if (type is NullType) return 'Null';
- return 'invalid';
+ if (type is TypedefType) {
+ var name = '${type.typedefNode.name}$nullability';
+ var typeArgs = type.typeArguments;
+ if (typeArgs == null) return name;
+ if (typeArgs.every((p) => p == const DynamicType())) return name;
+ return "${name}Of${typeArgs.map(_typeString).join("\$")}";
}
-
- /// Heuristically choose a good name for the cache and generator
- /// variables.
- js_ast.TemporaryId chooseTypeName(DartType type) {
- return js_ast.TemporaryId(escapeIdentifier(_typeString(type)));
- }
-}
-
-/// _GeneratorTable tracks types which have been
-/// named and hoisted.
-class _GeneratorTable extends _CacheTable {
- final _defs = <DartType, js_ast.Expression>{};
-
- final js_ast.Identifier _runtimeModule;
-
- _GeneratorTable(this._runtimeModule);
-
- @override
- js_ast.Statement _dischargeType(DartType t) {
- var name = _names.remove(t);
- if (name != null) {
- var init = _defs.remove(t);
- assert(init != null);
- // TODO(vsm): Change back to `let`.
- // See https://github.com/dart-lang/sdk/issues/40380.
- return js.statement('var # = () => ((# = #.constFn(#))());',
- [name, name, _runtimeModule, init]);
+ if (type is FunctionType) {
+ if (flat) return 'Fn';
+ var rType = _typeString(type.returnType, flat: true);
+ var params = type.positionalParameters
+ .take(3)
+ .map((p) => _typeString(p, flat: true));
+ var paramList = params.join('And');
+ var count = type.positionalParameters.length;
+ if (count > 3 || type.namedParameters.isNotEmpty) {
+ paramList = '${paramList}__';
+ } else if (count == 0) {
+ paramList = 'Void';
}
- return null;
+ return '${paramList}To$nullability$rType';
}
-
- /// If [type] does not already have a generator name chosen for it,
- /// assign it one, using [typeRep] as the initializer for it.
- /// Emit the generator name.
- js_ast.TemporaryId _nameType(DartType type, js_ast.Expression typeRep) {
- var temp = _names[type];
- if (temp == null) {
- _names[type] = temp = chooseTypeName(type);
- _defs[type] = typeRep;
- }
- return temp;
- }
+ if (type is TypeParameterType) return '${type.parameter.name}$nullability';
+ if (type is DynamicType) return 'dynamic';
+ if (type is VoidType) return 'void';
+ if (type is NeverType) return 'Never$nullability';
+ if (type is BottomType) return 'bottom';
+ if (type is NullType) return 'Null';
+ return 'invalid';
}
class TypeTable {
- /// Generator variable names for hoisted types.
- final _GeneratorTable _generators;
-
/// Mapping from type parameters to the types which must have their
/// cache/generator variables discharged at the binding site for the
/// type variable since the type definition depends on the type
/// parameter.
final _scopeDependencies = <TypeParameter, List<DartType>>{};
- TypeTable(js_ast.Identifier runtime) : _generators = _GeneratorTable(runtime);
+ /// Contains types with any free type parameters and maps them to a unique
+ /// JS identifier.
+ ///
+ /// Used to reference types hoisted to the top of a generic class or generic
+ /// function (as opposed to the top of the entire module).
+ final _unboundTypeIds = HashMap<DartType, js_ast.Identifier>();
- /// Emit a list of statements declaring the cache variables and generator
- /// definitions tracked by the table. If [formals] is present, only
- /// emit the definitions which depend on the formals.
- List<js_ast.Statement> discharge([List<TypeParameter> formals]) {
- var filter = formals?.expand((p) => _scopeDependencies[p] ?? <DartType>[]);
- var stmts = _generators.discharge(filter);
- formals?.forEach(_scopeDependencies.remove);
- return stmts;
+ /// Holds JS type generators keyed by their underlying DartType.
+ final typeContainer = ModuleItemContainer<DartType>.asObject('T',
+ keyToString: (DartType t) => escapeIdentifier(_typeString(t)));
+
+ final js_ast.Identifier _runtimeModule;
+
+ TypeTable(this._runtimeModule);
+
+ /// Returns true if [type] is already recorded in the table.
+ bool _isNamed(DartType type) =>
+ typeContainer.contains(type) || _unboundTypeIds.containsKey(type);
+
+ /// Emit the initializer statements for the type container, which contains
+ /// all named types with fully bound type parameters.
+ List<js_ast.Statement> dischargeBoundTypes() {
+ for (var t in typeContainer.keys) {
+ typeContainer[t] = js.call('() => ((# = #.constFn(#))())',
+ [typeContainer.access(t), _runtimeModule, typeContainer[t]]);
+ }
+ return typeContainer.emit();
}
- /// Record the dependencies of the type on its free variables
+ js_ast.Statement _dischargeFreeType(DartType type) {
+ typeContainer.setNoEmit(type);
+ var init = typeContainer[type];
+ var id = _unboundTypeIds[type];
+ // TODO(vsm): Change back to `let`.
+ // See https://github.com/dart-lang/sdk/issues/40380.
+ return js.statement('var # = () => ((# = #.constFn(#))());',
+ [id, id, _runtimeModule, init]);
+ }
+
+ /// Emit a list of statements declaring the cache variables and generator
+ /// definitions tracked by the table so far.
+ ///
+ /// If [formals] is present, only emit the definitions which depend on the
+ /// formals.
+ List<js_ast.Statement> dischargeFreeTypes([Iterable<TypeParameter> formals]) {
+ var decls = <js_ast.Statement>[];
+ var types = formals == null
+ ? typeContainer.keys.where((p) => freeTypeParameters(p).isNotEmpty)
+ : formals.expand((p) => _scopeDependencies[p] ?? <DartType>[]).toSet();
+
+ for (var t in types) {
+ var stmt = _dischargeFreeType(t);
+ if (stmt != null) decls.add(stmt);
+ }
+ return decls;
+ }
+
+ /// Emit a JS expression that evaluates to the generator for [type].
+ ///
+ /// If [type] does not already have a generator name chosen for it,
+ /// assign it one, using [typeRep] as its initializer.
+ js_ast.Expression _nameType(DartType type, js_ast.Expression typeRep) {
+ if (!typeContainer.contains(type)) {
+ typeContainer[type] = typeRep;
+ }
+ return _unboundTypeIds[type] ?? typeContainer.access(type);
+ }
+
+ /// Record the dependencies of the type on its free variables.
+ ///
+ /// Returns true if [type] is a free type parameter (but not a bound) and so
+ /// is not locally hoisted.
bool recordScopeDependencies(DartType type) {
+ if (_isNamed(type)) {
+ return false;
+ }
+
var freeVariables = freeTypeParameters(type);
// TODO(leafp): This is a hack to avoid trying to hoist out of
// generic functions and generic function types. This often degrades
@@ -200,6 +188,17 @@
return true;
}
+ // This is only reached when [type] is itself a bound that depends on a
+ // free type parameter.
+ // TODO(markzipan): Bounds are locally hoisted to their own JS identifiers,
+ // but we don't do this this for other types that depend on free variables,
+ // resulting in some duplicated runtime code. We may get some performance
+ // wins if we just locally hoist everything.
+ if (freeVariables.isNotEmpty) {
+ _unboundTypeIds[type] =
+ js_ast.TemporaryId(escapeIdentifier(_typeString(type)));
+ }
+
for (var free in freeVariables) {
// If `free` is a promoted type parameter, get the original one so we can
// find it in our map.
@@ -212,10 +211,10 @@
/// add the type and its representation to the table, returning an
/// expression which implements the type (but which caches the value).
js_ast.Expression nameType(DartType type, js_ast.Expression typeRep) {
- if (!_generators.isNamed(type) && recordScopeDependencies(type)) {
+ if (recordScopeDependencies(type)) {
return typeRep;
}
- var name = _generators._nameType(type, typeRep);
+ var name = _nameType(type, typeRep);
return js.call('#()', [name]);
}
@@ -228,10 +227,10 @@
js_ast.Expression nameFunctionType(
FunctionType type, js_ast.Expression typeRep,
{bool lazy = false}) {
- if (!_generators.isNamed(type) && recordScopeDependencies(type)) {
+ if (recordScopeDependencies(type)) {
return lazy ? js_ast.ArrowFun([], typeRep) : typeRep;
}
- var name = _generators._nameType(type, typeRep);
+ var name = _nameType(type, typeRep);
return lazy ? name : js.call('#()', [name]);
}
}
diff --git a/tests/lib/lib_dart2js.status b/tests/lib/lib_dart2js.status
index 14a0a1a..d66a2c8 100644
--- a/tests/lib/lib_dart2js.status
+++ b/tests/lib/lib_dart2js.status
@@ -23,6 +23,8 @@
html/xhr_test: Slow, Pass
isolate/*: SkipByDesign # No support for dart:isolate in dart4web (http://dartbug.com/30538)
mirrors/*: SkipByDesign # Mirrors not supported on web in Dart 2.0.
+typed_data/int64_list_load_store_test: SkipByDesign # No support for Int64List
+typed_data/typed_data_hierarchy_int64_test: SkipByDesign # No support for Int64List
wasm/*: SkipByDesign # dart:wasm not currently supported on web.
[ $compiler != dart2js ]
diff --git a/tests/lib_2/typed_data/int32x4_arithmetic_test.dart b/tests/lib_2/typed_data/int32x4_arithmetic_test.dart
index 6f08626..f9a740e 100644
--- a/tests/lib_2/typed_data/int32x4_arithmetic_test.dart
+++ b/tests/lib_2/typed_data/int32x4_arithmetic_test.dart
@@ -91,16 +91,35 @@
Expect.equals(1, o.w);
}
+const int53 = 0x20000000000000; // 2^53.
+final usingJavaScriptNumbers = (int53 + 1) == int53;
+
testTruncation() {
- var n = 0xAABBCCDD00000001;
- var x = new Int32x4(n, 0, 0, 0);
- Expect.equals(x.x, 1);
+ // Check that various bits from bit 32 and up are masked away.
+ var base = usingJavaScriptNumbers ? 0x1BCCDD00000000 : 0xAABBCCDD00000000;
+ var x1 = new Int32x4(base + 1, 0, 0, 0);
+ Expect.equals(1, x1.x);
+
+ // Check that all even bits up to bit 30 are preserved.
+ var x2 = new Int32x4(base + 0x55555555, 0, 0, 0);
+ Expect.equals(0x55555555, x2.x);
+
+ // Check that the odd bits up to bit 31 are preserved, and that
+ // bit 31 is treated as a sign bit.
+ var x3 = new Int32x4(base + 0xAAAAAAAA, 0, 0, 0);
+ const signExtended = -1431655766; // 0xFFFFFFFFAAAAAAAA or 0x3FFFFFAAAAAAAA.
+ Expect.equals(signExtended, x3.x);
+
+ // Check that all bits from bit 32 and up are masked away.
+ var highBase = 0xFFFFFFFF10000000;
+ var x4 = new Int32x4(highBase, 0, 0, 0);
+ Expect.equals(0x10000000, x4.x);
}
main() {
for (int i = 0; i < 20; i++) {
testAdd();
testSub();
- testTruncation(); // //# int64: ok
+ testTruncation();
}
}
diff --git a/tools/VERSION b/tools/VERSION
index 39a0b88..e1df069 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 12
PATCH 0
-PRERELEASE 119
+PRERELEASE 120
PRERELEASE_PATCH 0
\ No newline at end of file