Version 2.6.1
* Cherry-pick cfe29c45fdc4367b1cece295408684bb5e5e8edd to stable
* Cherry-pick e65d95e8c869a6ef26a5fdb6951267ea06c3e6fe to stable
* Cherry-pick 6354b0b97dced6c0cdee8f22009b4351f0095d75 to stable
* Cherry-pick 64b0bfac68138405a6de4aa2c0d1082948137d97 to stable
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 71712b0..48626bd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,9 +1,18 @@
+## 2.6.1 - 2019-11-11
+
+This is a patch release that reduces dart2js memory usage (issue [27883][]),
+improves stability on arm64 (issue [39090][]) and updates the Dart FFI
+documentation.
+
+[27883]: https://github.com/dart-lang/sdk/issues/27883
+[39090]: https://github.com/dart-lang/sdk/issues/39090
+
## 2.6.0 - 2019-11-05
### Language
-* **[IN PREVIEW]** [Static extension members][]: A new language feature allowing
- specially declared static functions to be invoked
+* **[IN PREVIEW]** [Static extension members][]: A new language feature
+ allowing specially declared static functions to be invoked
like instance members on expressions of appropriate static types
is available in preview.
diff --git a/pkg/compiler/lib/src/inferrer/inferrer_engine.dart b/pkg/compiler/lib/src/inferrer/inferrer_engine.dart
index bf214c1..a4accab 100644
--- a/pkg/compiler/lib/src/inferrer/inferrer_engine.dart
+++ b/pkg/compiler/lib/src/inferrer/inferrer_engine.dart
@@ -223,6 +223,17 @@
/// [NativeBehavior].
TypeInformation typeOfNativeBehavior(NativeBehavior nativeBehavior);
+ /// For a given selector, return a shared dynamic call site that will be used
+ /// to combine the results of multiple dynamic calls in the program via
+ /// [IndirectDynamicCallSiteTypeInformation].
+ ///
+ /// This is used only for scalability reasons: if there are too many targets
+ /// and call sites, we may have a quadratic number of edges in the graph, so
+ /// we add a level of indirection to merge the information and keep the graph
+ /// smaller.
+ DynamicCallSiteTypeInformation typeOfSharedDynamicCall(
+ Selector selector, CallStructure structure);
+
bool returnsListElementType(Selector selector, AbstractValue mask);
bool returnsMapValueType(Selector selector, AbstractValue mask);
@@ -397,11 +408,14 @@
void updateSelectorInMember(MemberEntity owner, CallType callType,
ir.Node node, Selector selector, AbstractValue mask) {
KernelGlobalTypeInferenceElementData data = dataOfMember(owner);
- assert(validCallType(callType, node));
+ assert(validCallType(callType, node, selector));
switch (callType) {
case CallType.access:
data.setTypeMask(node, mask);
break;
+ case CallType.indirectAccess:
+ // indirect access is not diretly recorded in the result data.
+ break;
case CallType.forIn:
if (selector == Selectors.iterator) {
data.setIteratorTypeMask(node, mask);
@@ -1142,19 +1156,40 @@
updateSideEffects(sideEffectsBuilder, selector, callee);
});
- CallSiteTypeInformation info = new DynamicCallSiteTypeInformation(
- abstractValueDomain,
- types.currentMember,
- callType,
- node,
- caller,
- selector,
- mask,
- receiverType,
- arguments,
- inLoop,
- isConditional);
+ CallSiteTypeInformation info;
+ // We force using indirection for `==` because it is a very common dynamic
+ // call site on many apps.
+ // TODO(sigmund): it would be even better if we could automatically detect
+ // when dynamic calls are growing too big and add the indirection at that
+ // point.
+ if (selector.name == '==') {
+ info = new IndirectDynamicCallSiteTypeInformation(
+ abstractValueDomain,
+ types.currentMember,
+ node,
+ typeOfSharedDynamicCall(selector, CallStructure.ONE_ARG),
+ caller,
+ selector,
+ mask,
+ receiverType,
+ arguments,
+ inLoop,
+ isConditional);
+ } else {
+ info = new DynamicCallSiteTypeInformation(
+ abstractValueDomain,
+ types.currentMember,
+ callType,
+ node,
+ caller,
+ selector,
+ mask,
+ receiverType,
+ arguments,
+ inLoop,
+ isConditional);
+ }
info.addToGraph(this);
types.allocatedCalls.add(info);
return info;
@@ -1282,6 +1317,48 @@
}
}
+ /// Indirect calls share the same dynamic call site information node. This
+ /// cache holds that shared dynamic call node for a given selector.
+ Map<Selector, DynamicCallSiteTypeInformation> _sharedCalls = {};
+
+ @override
+ DynamicCallSiteTypeInformation typeOfSharedDynamicCall(
+ Selector selector, CallStructure structure) {
+ DynamicCallSiteTypeInformation info = _sharedCalls[selector];
+ if (info != null) return info;
+
+ TypeInformation receiverType =
+ new IndirectParameterTypeInformation(abstractValueDomain, 'receiver');
+ List<TypeInformation> positional = [];
+ for (int i = 0; i < structure.positionalArgumentCount; i++) {
+ positional
+ .add(new IndirectParameterTypeInformation(abstractValueDomain, '$i'));
+ }
+ Map<String, TypeInformation> named = {};
+ if (structure.namedArgumentCount > 0) {
+ for (var name in structure.namedArguments) {
+ named[name] =
+ new IndirectParameterTypeInformation(abstractValueDomain, name);
+ }
+ }
+
+ info = _sharedCalls[selector] = new DynamicCallSiteTypeInformation(
+ abstractValueDomain,
+ null,
+ CallType.indirectAccess,
+ null,
+ null,
+ selector,
+ null,
+ receiverType,
+ ArgumentsTypes(positional, named),
+ false,
+ false);
+ info.addToGraph(this);
+ types.allocatedCalls.add(info);
+ return info;
+ }
+
@override
bool canFieldBeUsedForGlobalOptimizations(FieldEntity element) {
if (closedWorld.backendUsage.isFieldUsedByBackend(element)) {
diff --git a/pkg/compiler/lib/src/inferrer/node_tracer.dart b/pkg/compiler/lib/src/inferrer/node_tracer.dart
index 987cb11..4d4a4bd 100644
--- a/pkg/compiler/lib/src/inferrer/node_tracer.dart
+++ b/pkg/compiler/lib/src/inferrer/node_tracer.dart
@@ -270,6 +270,14 @@
}
}
+ @override
+ visitIndirectDynamicCallSiteTypeInformation(
+ IndirectDynamicCallSiteTypeInformation info) {
+ if (info.dynamicCall == currentUser) {
+ addNewEscapeInformation(info);
+ }
+ }
+
void analyzeStoredIntoList(ListTypeInformation list) {
inferrer.analyzeListAndEnqueue(list);
if (list.bailedOut) {
@@ -547,4 +555,10 @@
}
addNewEscapeInformation(info);
}
+
+ @override
+ void visitIndirectParameterTypeInformation(
+ IndirectParameterTypeInformation info) {
+ addNewEscapeInformation(info);
+ }
}
diff --git a/pkg/compiler/lib/src/inferrer/type_graph_dump.dart b/pkg/compiler/lib/src/inferrer/type_graph_dump.dart
index 70d3fa0..a4ed3e3 100644
--- a/pkg/compiler/lib/src/inferrer/type_graph_dump.dart
+++ b/pkg/compiler/lib/src/inferrer/type_graph_dump.dart
@@ -411,6 +411,12 @@
}
@override
+ void visitIndirectDynamicCallSiteTypeInformation(
+ IndirectDynamicCallSiteTypeInformation info) {
+ handleCall(info, 'IndirectDynamicCallSite', {});
+ }
+
+ @override
void visitDynamicCallSiteTypeInformation(
DynamicCallSiteTypeInformation info) {
handleCall(info, 'DynamicCallSite', {'obj': info.receiver});
@@ -427,6 +433,12 @@
}
@override
+ void visitIndirectParameterTypeInformation(
+ IndirectParameterTypeInformation info) {
+ addNode(info, 'IndirectParameter ${info.debugName}');
+ }
+
+ @override
void visitClosureTypeInformation(ClosureTypeInformation info) {
String text = shorten('${info.debugName}');
addNode(info, 'Closure\n$text');
diff --git a/pkg/compiler/lib/src/inferrer/type_graph_inferrer.dart b/pkg/compiler/lib/src/inferrer/type_graph_inferrer.dart
index 3a41951..a1e7c15 100644
--- a/pkg/compiler/lib/src/inferrer/type_graph_inferrer.dart
+++ b/pkg/compiler/lib/src/inferrer/type_graph_inferrer.dart
@@ -116,8 +116,7 @@
returnType != null &&
abstractValueDomain.isEmpty(returnType).isDefinitelyTrue;
- bool isCalledOnce =
- typeInformation.isCalledOnce(); //isMemberCalledOnce(member);
+ bool isCalledOnce = typeInformation.isCalledOnce();
memberResults[member] = new GlobalTypeInferenceMemberResultImpl(
data, returnType, type,
diff --git a/pkg/compiler/lib/src/inferrer/type_graph_nodes.dart b/pkg/compiler/lib/src/inferrer/type_graph_nodes.dart
index f3a949d..f674734 100644
--- a/pkg/compiler/lib/src/inferrer/type_graph_nodes.dart
+++ b/pkg/compiler/lib/src/inferrer/type_graph_nodes.dart
@@ -377,6 +377,10 @@
// cleanup has been called.
bool _isCalledOnce = null;
+ /// Whether this member is invoked via indirect dynamic calls. In that case
+ /// the exact number of call sites cannot be computed precisely.
+ bool _calledIndirectly = false;
+
/// This map contains the callers of [element]. It stores all unique call
/// sites to enable counting the global number of call sites of [element].
///
@@ -427,6 +431,7 @@
}
bool _computeIsCalledOnce() {
+ if (_calledIndirectly) return false;
if (_callers == null) return false;
int count = 0;
for (var set in _callers.values) {
@@ -872,15 +877,40 @@
}
}
+/// A synthetic parameter used to model the entry points to a
+/// [IndirectDynamicCallSiteTypeInformation].
+class IndirectParameterTypeInformation extends TypeInformation {
+ final String debugName;
+
+ IndirectParameterTypeInformation(
+ AbstractValueDomain abstractValueDomain, this.debugName)
+ : super(abstractValueDomain.emptyType, null);
+
+ @override
+ AbstractValue computeType(InferrerEngine inferrer) =>
+ inferrer.types.computeTypeMask(assignments);
+
+ @override
+ accept(TypeInformationVisitor visitor) {
+ return visitor.visitIndirectParameterTypeInformation(this);
+ }
+
+ @override
+ String toString() => 'IndirectParameter $debugName $type';
+}
+
enum CallType {
access,
+ indirectAccess,
forIn,
}
-bool validCallType(CallType callType, Object call) {
+bool validCallType(CallType callType, Object call, Selector selector) {
switch (callType) {
case CallType.access:
return call is ir.Node;
+ case CallType.indirectAccess:
+ return call == null && selector.name == '==';
case CallType.forIn:
return call is ir.ForInStatement;
}
@@ -914,7 +944,7 @@
this.arguments,
this.inLoop)
: super.noAssignments(abstractValueDomain.emptyType, context) {
- assert(_call is ir.Node);
+ assert(_call is ir.Node || (_call == null && selector.name == '=='));
}
@override
@@ -1011,6 +1041,109 @@
}
}
+/// A call modeled with a level of indirection.
+///
+/// This kind of call is artificial and only exists to address scalability
+/// limitations of the inference algorithm. Any virtual, interface, or dynamic
+/// call is normally modeled via [DynamicCallSiteTypeInformation]. The main
+/// scalability concern of those calls is that we may get a quadratic number of
+/// edges to model all the argument and return values (an edge per dynamic call
+/// and target pair). Adding a level of indirection helps in that all these
+/// calls are funneled through a single [DynamicCallSiteTypeInformation] node,
+/// this in turn reduces the edges to be linear (an edge per indiret call to the
+/// dynamic call node, and an edge from the call to each target).
+class IndirectDynamicCallSiteTypeInformation extends CallSiteTypeInformation {
+ final DynamicCallSiteTypeInformation dynamicCall;
+ final bool isConditional;
+ final TypeInformation receiver;
+
+ IndirectDynamicCallSiteTypeInformation(
+ AbstractValueDomain abstractValueDomain,
+ MemberTypeInformation context,
+ Object call,
+ this.dynamicCall,
+ MemberEntity enclosing,
+ Selector selector,
+ AbstractValue mask,
+ this.receiver,
+ ArgumentsTypes arguments,
+ bool inLoop,
+ this.isConditional)
+ : super(abstractValueDomain, context, call, enclosing, selector, mask,
+ arguments, inLoop);
+
+ @override
+ void addToGraph(InferrerEngine inferrer) {
+ receiver.addUser(this);
+ dynamicCall.receiver.addAssignment(receiver);
+ List<TypeInformation> positional = arguments.positional;
+ for (int i = 0; i < positional.length; i++) {
+ positional[i].addUser(this);
+ dynamicCall.arguments.positional[i].addAssignment(positional[i]);
+ }
+ arguments.named.forEach((name, namedInfo) {
+ dynamicCall.arguments.named[name].addAssignment(namedInfo);
+ });
+ dynamicCall.addUser(this);
+ }
+
+ @override
+ AbstractValue computeType(InferrerEngine inferrer) {
+ AbstractValue typeMask = _computeTypedSelector(inferrer);
+ inferrer.updateSelectorInMember(
+ caller, CallType.access, _call, selector, typeMask);
+
+ AbstractValue result = dynamicCall.computeType(inferrer);
+ AbstractValueDomain abstractValueDomain =
+ inferrer.closedWorld.abstractValueDomain;
+ if (isConditional &&
+ abstractValueDomain.isNull(receiver.type).isPotentiallyTrue) {
+ // Conditional calls like `a?.b` may be null if the receiver is null.
+ result = abstractValueDomain.includeNull(result);
+ }
+ return result;
+ }
+
+ AbstractValue _computeTypedSelector(InferrerEngine inferrer) {
+ AbstractValue receiverType = receiver.type;
+ if (mask == receiverType) return mask;
+ return receiverType == inferrer.abstractValueDomain.dynamicType
+ ? null
+ : receiverType;
+ }
+
+ @override
+ void giveUp(InferrerEngine inferrer, {bool clearAssignments: true}) {
+ if (!abandonInferencing) {
+ inferrer.updateSelectorInMember(
+ caller, CallType.access, _call, selector, mask);
+ }
+ super.giveUp(inferrer, clearAssignments: clearAssignments);
+ }
+
+ @override
+ Iterable<MemberEntity> get callees => dynamicCall.callees;
+
+ @override
+ accept(TypeInformationVisitor visitor) {
+ return visitor.visitIndirectDynamicCallSiteTypeInformation(this);
+ }
+
+ @override
+ bool hasStableType(InferrerEngine inferrer) =>
+ dynamicCall.hasStableType(inferrer);
+
+ @override
+ void removeAndClearReferences(InferrerEngine inferrer) {
+ dynamicCall.removeUser(this);
+ receiver.removeUser(this);
+ if (arguments != null) {
+ arguments.forEach((info) => info.removeUser(this));
+ }
+ super.removeAndClearReferences(inferrer);
+ }
+}
+
class DynamicCallSiteTypeInformation<T> extends CallSiteTypeInformation {
final CallType _callType;
final TypeInformation receiver;
@@ -1034,7 +1167,21 @@
this.isConditional)
: super(abstractValueDomain, context, call, enclosing, selector, mask,
arguments, inLoop) {
- assert(validCallType(_callType, _call));
+ assert(validCallType(_callType, _call, selector));
+ }
+
+ void _addCall(MemberTypeInformation callee) {
+ if (_callType == CallType.indirectAccess) {
+ callee._calledIndirectly = true;
+ } else {
+ callee.addCall(caller, _call);
+ }
+ }
+
+ void _removeCall(MemberTypeInformation callee) {
+ if (_callType != CallType.indirectAccess) {
+ callee.removeCall(caller, _call);
+ }
}
@override
@@ -1051,7 +1198,7 @@
for (MemberEntity element in _concreteTargets) {
MemberTypeInformation callee =
inferrer.types.getInferredTypeOfMember(element);
- callee.addCall(caller, _call);
+ _addCall(callee);
callee.addUser(this);
inferrer.updateParameterAssignments(
this, element, arguments, selector, typeMask,
@@ -1072,7 +1219,6 @@
AbstractValue computeTypedSelector(InferrerEngine inferrer) {
AbstractValue receiverType = receiver.type;
-
if (mask != receiverType) {
return receiverType == inferrer.abstractValueDomain.dynamicType
? null
@@ -1216,7 +1362,7 @@
.forEach((MemberEntity element) {
MemberTypeInformation callee =
inferrer.types.getInferredTypeOfMember(element);
- callee.addCall(caller, _call);
+ _addCall(callee);
callee.addUser(this);
inferrer.updateParameterAssignments(
this, element, arguments, selector, typeMask,
@@ -1229,7 +1375,7 @@
.forEach((MemberEntity element) {
MemberTypeInformation callee =
inferrer.types.getInferredTypeOfMember(element);
- callee.removeCall(caller, _call);
+ _removeCall(callee);
callee.removeUser(this);
inferrer.updateParameterAssignments(
this, element, arguments, selector, typeMask,
@@ -2111,8 +2257,12 @@
T visitClosureCallSiteTypeInformation(ClosureCallSiteTypeInformation info);
T visitStaticCallSiteTypeInformation(StaticCallSiteTypeInformation info);
T visitDynamicCallSiteTypeInformation(DynamicCallSiteTypeInformation info);
+ T visitIndirectDynamicCallSiteTypeInformation(
+ IndirectDynamicCallSiteTypeInformation info);
T visitMemberTypeInformation(MemberTypeInformation info);
T visitParameterTypeInformation(ParameterTypeInformation info);
+ T visitIndirectParameterTypeInformation(
+ IndirectParameterTypeInformation info);
T visitClosureTypeInformation(ClosureTypeInformation info);
T visitAwaitTypeInformation(AwaitTypeInformation info);
T visitYieldTypeInformation(YieldTypeInformation info);
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index ca5c1b5..7bce0d2 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -2306,12 +2306,16 @@
InitializeObject(address, cls_id, size);
RawObject* raw_obj = reinterpret_cast<RawObject*>(address + kHeapObjectTag);
ASSERT(cls_id == RawObject::ClassIdTag::decode(raw_obj->ptr()->tags_));
- if (raw_obj->IsOldObject() && thread->is_marking()) {
+ if (raw_obj->IsOldObject() && UNLIKELY(thread->is_marking())) {
// Black allocation. Prevents a data race between the mutator and concurrent
// marker on ARM and ARM64 (the marker may observe a publishing store of
// this object before the stores that initialize its slots), and helps the
// collection to finish sooner.
raw_obj->SetMarkBitUnsynchronized();
+ // Setting the mark bit must not be ordered after a publishing store of this
+ // object. Adding a barrier here is cheaper than making every store into the
+ // heap a store-release.
+ std::atomic_thread_fence(std::memory_order_release);
heap->old_space()->AllocateBlack(size);
}
return raw_obj;
diff --git a/sdk/lib/ffi/ffi.dart b/sdk/lib/ffi/ffi.dart
index 422788f..2a3eafd 100644
--- a/sdk/lib/ffi/ffi.dart
+++ b/sdk/lib/ffi/ffi.dart
@@ -5,8 +5,7 @@
/**
* Foreign Function Interface for interoperability with the C programming language.
*
- * **NOTE**: Dart:FFI is in technical preview. The overall feature is incomplete,
- * may contain issues, and breaking API changes are still expected.
+ * **NOTE**: Dart:FFI is in beta, and breaking API changes might still happen.
*
* For further details, please see: https://dart.dev/server/c-interop
*
diff --git a/sdk_nnbd/lib/ffi/ffi.dart b/sdk_nnbd/lib/ffi/ffi.dart
index c00226f..402cff9 100644
--- a/sdk_nnbd/lib/ffi/ffi.dart
+++ b/sdk_nnbd/lib/ffi/ffi.dart
@@ -7,8 +7,7 @@
/**
* Foreign Function Interface for interoperability with the C programming language.
*
- * **NOTE**: Dart:FFI is in technical preview. The overall feature is incomplete,
- * may contain issues, and breaking API changes are still expected.
+ * **NOTE**: Dart:FFI is in beta, and breaking API changes might still happen.
*
* For further details, please see: https://dart.dev/server/c-interop
*
diff --git a/tests/compiler/dart2js/inference/data/general.dart b/tests/compiler/dart2js/inference/data/general.dart
index 678911c..beb067d 100644
--- a/tests/compiler/dart2js/inference/data/general.dart
+++ b/tests/compiler/dart2js/inference/data/general.dart
@@ -675,8 +675,7 @@
A.generative();
/*member: A.==:[exact=JSBool]*/
- operator ==(/*Union([exact=JSString], [exact=JSUInt31])*/ other) =>
- 42 as dynamic;
+ operator ==(/*[null|subclass=Object]*/ other) => 42 as dynamic;
/*member: A.myField:[exact=JSUInt31]*/
get myField => 42;
diff --git a/tools/VERSION b/tools/VERSION
index b2fe8b5..756dd00 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -32,7 +32,7 @@
CHANNEL stable
MAJOR 2
MINOR 6
-PATCH 0
+PATCH 1
PRERELEASE 0
PRERELEASE_PATCH 0
ABI_VERSION 19