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