Version 2.12.0-143.0.dev

Merge commit 'e2b88eea9596b10772c959cf5bdcecd45423624b' into 'dev'
diff --git a/DEPS b/DEPS
index 66b4294..5f32e2f 100644
--- a/DEPS
+++ b/DEPS
@@ -68,7 +68,7 @@
   "gperftools_revision": "180bfa10d7cb38e8b3784d60943d50e8fcef0dcb",
 
   # Revisions of /third_party/* dependencies.
-  "args_rev": "139140125126661fac88c9aa5882165936d01c91",
+  "args_rev": "6921e2f63796368bbe65a64ad943df84932dff55",
   "async_rev": "695b3ac280f107c84adf7488743abfdfaaeea68f",
   "bazel_worker_rev": "060c55a933d39798681a4f533b161b81dc48d77e",
   "benchmark_harness_rev": "ec6b646f5443faa871e126ac1ba248c94ca06257",
@@ -82,7 +82,7 @@
   "clock_rev" : "a494269254ba978e7ef8f192c5f7fec3fc05b9d3",
   "collection_rev": "e4bb038ce2d8e66fb15818aa40685c68d53692ab",
   "convert_rev": "6513985a1b1ea8a0b987fbef699250ce2cdc3cca",
-  "crypto_rev": "a5ec902dda5a635a35c6b363ec64458cf84c5872",
+  "crypto_rev": "c89a5be0375875fe7ff71625fa2b79f5a421f06d",
   "csslib_rev": "6f77b3dcee957d3e2d5083f666221a220e9ed1f1",
   "dart2js_info_rev" : "e0acfeb5affdf94c53067e68bd836adf589628fd",
 
diff --git a/pkg/analysis_server/lib/lsp_protocol/protocol_generated.dart b/pkg/analysis_server/lib/lsp_protocol/protocol_generated.dart
index b8c93e5..ff55c14 100644
--- a/pkg/analysis_server/lib/lsp_protocol/protocol_generated.dart
+++ b/pkg/analysis_server/lib/lsp_protocol/protocol_generated.dart
@@ -14592,6 +14592,98 @@
   String toString() => jsonEncoder.convert(toJson());
 }
 
+/// A filter to describe in which file operation requests or notifications the
+/// server is interested in.
+///  @since 3.16.0
+class FileOperationFilter implements ToJsonable {
+  static const jsonHandler = LspJsonHandler(
+      FileOperationFilter.canParse, FileOperationFilter.fromJson);
+
+  FileOperationFilter({this.scheme, @required this.pattern}) {
+    if (pattern == null) {
+      throw 'pattern is required but was not provided';
+    }
+  }
+  static FileOperationFilter fromJson(Map<String, dynamic> json) {
+    final scheme = json['scheme'];
+    final pattern = json['pattern'] != null
+        ? FileOperationPattern.fromJson(json['pattern'])
+        : null;
+    return FileOperationFilter(scheme: scheme, pattern: pattern);
+  }
+
+  /// The actual file operation pattern.
+  final FileOperationPattern pattern;
+
+  /// A Uri like `file` or `untitled`.
+  final String scheme;
+
+  Map<String, dynamic> toJson() {
+    var __result = <String, dynamic>{};
+    if (scheme != null) {
+      __result['scheme'] = scheme;
+    }
+    __result['pattern'] =
+        pattern?.toJson() ?? (throw 'pattern is required but was not set');
+    return __result;
+  }
+
+  static bool canParse(Object obj, LspJsonReporter reporter) {
+    if (obj is Map<String, dynamic>) {
+      reporter.push('scheme');
+      try {
+        if (obj['scheme'] != null && !(obj['scheme'] is String)) {
+          reporter.reportError('must be of type String');
+          return false;
+        }
+      } finally {
+        reporter.pop();
+      }
+      reporter.push('pattern');
+      try {
+        if (!obj.containsKey('pattern')) {
+          reporter.reportError('must not be undefined');
+          return false;
+        }
+        if (obj['pattern'] == null) {
+          reporter.reportError('must not be null');
+          return false;
+        }
+        if (!(FileOperationPattern.canParse(obj['pattern'], reporter))) {
+          reporter.reportError('must be of type FileOperationPattern');
+          return false;
+        }
+      } finally {
+        reporter.pop();
+      }
+      return true;
+    } else {
+      reporter.reportError('must be of type FileOperationFilter');
+      return false;
+    }
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (other is FileOperationFilter &&
+        other.runtimeType == FileOperationFilter) {
+      return scheme == other.scheme && pattern == other.pattern && true;
+    }
+    return false;
+  }
+
+  @override
+  int get hashCode {
+    var hash = 0;
+    hash = JenkinsSmiHash.combine(hash, scheme.hashCode);
+    hash = JenkinsSmiHash.combine(hash, pattern.hashCode);
+    return JenkinsSmiHash.finish(hash);
+  }
+
+  @override
+  String toString() => jsonEncoder.convert(toJson());
+}
+
 /// A pattern to describe in which file operation requests or notifications the
 /// server is interested in.
 ///  @since 3.16.0 - proposed state
@@ -14818,45 +14910,46 @@
       FileOperationRegistrationOptions.canParse,
       FileOperationRegistrationOptions.fromJson);
 
-  FileOperationRegistrationOptions({@required this.patterns}) {
-    if (patterns == null) {
-      throw 'patterns is required but was not provided';
+  FileOperationRegistrationOptions({@required this.filters}) {
+    if (filters == null) {
+      throw 'filters is required but was not provided';
     }
   }
   static FileOperationRegistrationOptions fromJson(Map<String, dynamic> json) {
-    final patterns = json['patterns']
+    final filters = json['filters']
         ?.map(
-            (item) => item != null ? FileOperationPattern.fromJson(item) : null)
-        ?.cast<FileOperationPattern>()
+            (item) => item != null ? FileOperationFilter.fromJson(item) : null)
+        ?.cast<FileOperationFilter>()
         ?.toList();
-    return FileOperationRegistrationOptions(patterns: patterns);
+    return FileOperationRegistrationOptions(filters: filters);
   }
 
-  final List<FileOperationPattern> patterns;
+  /// The actual filters.
+  final List<FileOperationFilter> filters;
 
   Map<String, dynamic> toJson() {
     var __result = <String, dynamic>{};
-    __result['patterns'] =
-        patterns ?? (throw 'patterns is required but was not set');
+    __result['filters'] =
+        filters ?? (throw 'filters is required but was not set');
     return __result;
   }
 
   static bool canParse(Object obj, LspJsonReporter reporter) {
     if (obj is Map<String, dynamic>) {
-      reporter.push('patterns');
+      reporter.push('filters');
       try {
-        if (!obj.containsKey('patterns')) {
+        if (!obj.containsKey('filters')) {
           reporter.reportError('must not be undefined');
           return false;
         }
-        if (obj['patterns'] == null) {
+        if (obj['filters'] == null) {
           reporter.reportError('must not be null');
           return false;
         }
-        if (!((obj['patterns'] is List &&
-            (obj['patterns'].every(
-                (item) => FileOperationPattern.canParse(item, reporter)))))) {
-          reporter.reportError('must be of type List<FileOperationPattern>');
+        if (!((obj['filters'] is List &&
+            (obj['filters'].every(
+                (item) => FileOperationFilter.canParse(item, reporter)))))) {
+          reporter.reportError('must be of type List<FileOperationFilter>');
           return false;
         }
       } finally {
@@ -14873,8 +14966,8 @@
   bool operator ==(Object other) {
     if (other is FileOperationRegistrationOptions &&
         other.runtimeType == FileOperationRegistrationOptions) {
-      return listEqual(patterns, other.patterns,
-              (FileOperationPattern a, FileOperationPattern b) => a == b) &&
+      return listEqual(filters, other.filters,
+              (FileOperationFilter a, FileOperationFilter b) => a == b) &&
           true;
     }
     return false;
@@ -14883,7 +14976,7 @@
   @override
   int get hashCode {
     var hash = 0;
-    hash = JenkinsSmiHash.combine(hash, lspHashCode(patterns));
+    hash = JenkinsSmiHash.combine(hash, lspHashCode(filters));
     return JenkinsSmiHash.finish(hash);
   }
 
diff --git a/pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart b/pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart
index 0dae89c..48fae93 100644
--- a/pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart
+++ b/pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart
@@ -327,9 +327,15 @@
     HintCode.DEPRECATED_MEMBER_USE: [
       DataDriven.newInstance,
     ],
+    HintCode.DEPRECATED_MEMBER_USE_FROM_SAME_PACKAGE: [
+      DataDriven.newInstance,
+    ],
     HintCode.DEPRECATED_MEMBER_USE_WITH_MESSAGE: [
       DataDriven.newInstance,
     ],
+    HintCode.DEPRECATED_MEMBER_USE_FROM_SAME_PACKAGE_WITH_MESSAGE: [
+      DataDriven.newInstance,
+    ],
     HintCode.OVERRIDE_ON_NON_OVERRIDING_METHOD: [
       DataDriven.newInstance,
     ],
diff --git a/pkg/analysis_server/tool/lsp_spec/lsp_specification.md b/pkg/analysis_server/tool/lsp_spec/lsp_specification.md
index cd6473c..8bd0c6b 100644
--- a/pkg/analysis_server/tool/lsp_spec/lsp_specification.md
+++ b/pkg/analysis_server/tool/lsp_spec/lsp_specification.md
@@ -352,6 +352,12 @@
 type DocumentUri = string;
 ```
 
+There is also a tagging interface for normal non document URIs. It maps to a `string` as well.
+
+```typescript
+type URI = string;
+```
+
 #### <a href="#regExp" name="regExp" class="anchor"> Regular Expressions </a>
 
 Regular expression are a powerful tool and there are actual use cases for them in the language server protocol. However the downside with them is that almost every programming language has its own set of regular expression features so the specification can not simply refer to them as a regular expression. So the LSP uses a two step approach to support regular expressions:
@@ -3422,7 +3428,10 @@
  * @since 3.16.0 - proposed state
  */
 interface FileOperationRegistrationOptions {
-	patterns: FileOperationPattern[];
+	/**
+	 * The actual filters.
+	 */
+	filters: FileOperationFilter[];
 }
 
 /**
@@ -3492,6 +3501,25 @@
 	 */
 	options?: FileOperationPatternOptions;
 }
+
+/**
+ * A filter to describe in which file operation requests or notifications
+ * the server is interested in.
+ *
+ * @since 3.16.0
+ */
+export interface FileOperationFilter {
+
+	/**
+	 * A Uri like `file` or `untitled`.
+	 */
+	scheme?: string;
+
+	/**
+	 * The actual file operation pattern.
+	 */
+	pattern: FileOperationPattern;
+}
 ```
 
 The capability indicates that the server is interested in receiving `workspace/willCreateFiles` requests.
diff --git a/pkg/analyzer/lib/src/error/best_practices_verifier.dart b/pkg/analyzer/lib/src/error/best_practices_verifier.dart
index 23bc84d..99ead99 100644
--- a/pkg/analyzer/lib/src/error/best_practices_verifier.dart
+++ b/pkg/analyzer/lib/src/error/best_practices_verifier.dart
@@ -421,8 +421,7 @@
       _inDoNotStoreMember = true;
     }
     try {
-      _checkForMissingReturn(
-          node.returnType, node.functionExpression.body, element, node);
+      _checkForMissingReturn(node.functionExpression.body, node);
 
       // Return types are inferred only on non-recursive local functions.
       if (node.parent is CompilationUnit && !node.isSetter) {
@@ -447,7 +446,7 @@
   @override
   void visitFunctionExpression(FunctionExpression node) {
     if (node.parent is! FunctionDeclaration) {
-      _checkForMissingReturn(null, node.body, node.declaredElement, node);
+      _checkForMissingReturn(node.body, node);
     }
     DartType functionType = InferenceContext.getContext(node);
     if (functionType is! FunctionType) {
@@ -557,7 +556,7 @@
     try {
       // This was determined to not be a good hint, see: dartbug.com/16029
       //checkForOverridingPrivateMember(node);
-      _checkForMissingReturn(node.returnType, node.body, element, node);
+      _checkForMissingReturn(node.body, node);
       _mustCallSuperVerifier.checkMethodDeclaration(node);
       _checkForUnnecessaryNoSuchMethod(node);
 
@@ -1153,8 +1152,7 @@
   /// function has a return type that Future<Null> is not assignable to.
   ///
   /// See [HintCode.MISSING_RETURN].
-  void _checkForMissingReturn(TypeAnnotation returnNode, FunctionBody body,
-      ExecutableElement element, AstNode functionNode) {
+  void _checkForMissingReturn(FunctionBody body, AstNode functionNode) {
     if (_isNonNullableByDefault) {
       return;
     }
@@ -1436,8 +1434,7 @@
     }
   }
 
-  /// In "strict-inference" mode, check that [returnNode]'s return type is
-  /// specified.
+  /// In "strict-inference" mode, check that [returnType] is specified.
   void _checkStrictInferenceReturnType(
       AstNode returnType, AstNode reportNode, String displayName) {
     if (!_strictInference) {
diff --git a/runtime/vm/clustered_snapshot.cc b/runtime/vm/clustered_snapshot.cc
index 895f537..992db17 100644
--- a/runtime/vm/clustered_snapshot.cc
+++ b/runtime/vm/clustered_snapshot.cc
@@ -6593,39 +6593,49 @@
              num_base_objects_, next_ref_index_ - kFirstReference);
     }
 
-    for (intptr_t i = 0; i < num_canonical_clusters_; i++) {
-      canonical_clusters_[i] = ReadCluster();
-      canonical_clusters_[i]->ReadAlloc(this, /*is_canonical*/ true);
+    {
+      TIMELINE_DURATION(thread(), Isolate, "ReadAlloc");
+      for (intptr_t i = 0; i < num_canonical_clusters_; i++) {
+        canonical_clusters_[i] = ReadCluster();
+        TIMELINE_DURATION(thread(), Isolate, canonical_clusters_[i]->name());
+        canonical_clusters_[i]->ReadAlloc(this, /*is_canonical*/ true);
 #if defined(DEBUG)
-      intptr_t serializers_next_ref_index_ = Read<int32_t>();
-      ASSERT_EQUAL(serializers_next_ref_index_, next_ref_index_);
+        intptr_t serializers_next_ref_index_ = Read<int32_t>();
+        ASSERT_EQUAL(serializers_next_ref_index_, next_ref_index_);
 #endif
-    }
-    for (intptr_t i = 0; i < num_clusters_; i++) {
-      clusters_[i] = ReadCluster();
-      clusters_[i]->ReadAlloc(this, /*is_canonical*/ false);
+      }
+      for (intptr_t i = 0; i < num_clusters_; i++) {
+        clusters_[i] = ReadCluster();
+        TIMELINE_DURATION(thread(), Isolate, clusters_[i]->name());
+        clusters_[i]->ReadAlloc(this, /*is_canonical*/ false);
 #if defined(DEBUG)
-      intptr_t serializers_next_ref_index_ = Read<int32_t>();
-      ASSERT_EQUAL(serializers_next_ref_index_, next_ref_index_);
+        intptr_t serializers_next_ref_index_ = Read<int32_t>();
+        ASSERT_EQUAL(serializers_next_ref_index_, next_ref_index_);
 #endif
+      }
     }
 
     // We should have completely filled the ref array.
     ASSERT_EQUAL(next_ref_index_ - kFirstReference, num_objects_);
 
-    for (intptr_t i = 0; i < num_canonical_clusters_; i++) {
-      canonical_clusters_[i]->ReadFill(this, /*is_canonical*/ true);
+    {
+      TIMELINE_DURATION(thread(), Isolate, "ReadFill");
+      for (intptr_t i = 0; i < num_canonical_clusters_; i++) {
+        TIMELINE_DURATION(thread(), Isolate, canonical_clusters_[i]->name());
+        canonical_clusters_[i]->ReadFill(this, /*is_canonical*/ true);
 #if defined(DEBUG)
-      int32_t section_marker = Read<int32_t>();
-      ASSERT(section_marker == kSectionMarker);
+        int32_t section_marker = Read<int32_t>();
+        ASSERT(section_marker == kSectionMarker);
 #endif
-    }
-    for (intptr_t i = 0; i < num_clusters_; i++) {
-      clusters_[i]->ReadFill(this, /*is_canonical*/ false);
+      }
+      for (intptr_t i = 0; i < num_clusters_; i++) {
+        TIMELINE_DURATION(thread(), Isolate, clusters_[i]->name());
+        clusters_[i]->ReadFill(this, /*is_canonical*/ false);
 #if defined(DEBUG)
-      int32_t section_marker = Read<int32_t>();
-      ASSERT(section_marker == kSectionMarker);
+        int32_t section_marker = Read<int32_t>();
+        ASSERT(section_marker == kSectionMarker);
 #endif
+      }
     }
 
     roots->ReadRoots(this);
@@ -6653,12 +6663,16 @@
   // canonicalization first, canonicalize and update the ref array, the load
   // the remaining clusters to avoid a full heap walk to update references to
   // the losers of any canonicalization races.
-  for (intptr_t i = 0; i < num_canonical_clusters_; i++) {
-    canonical_clusters_[i]->PostLoad(this, refs, /*is_canonical*/ true);
-  }
-
-  for (intptr_t i = 0; i < num_clusters_; i++) {
-    clusters_[i]->PostLoad(this, refs, /*is_canonical*/ false);
+  {
+    TIMELINE_DURATION(thread(), Isolate, "PostLoad");
+    for (intptr_t i = 0; i < num_canonical_clusters_; i++) {
+      TIMELINE_DURATION(thread(), Isolate, canonical_clusters_[i]->name());
+      canonical_clusters_[i]->PostLoad(this, refs, /*is_canonical*/ true);
+    }
+    for (intptr_t i = 0; i < num_clusters_; i++) {
+      TIMELINE_DURATION(thread(), Isolate, clusters_[i]->name());
+      clusters_[i]->PostLoad(this, refs, /*is_canonical*/ false);
+    }
   }
 }
 
diff --git a/runtime/vm/code_patcher_arm64.cc b/runtime/vm/code_patcher_arm64.cc
index 824274a..f13fb80 100644
--- a/runtime/vm/code_patcher_arm64.cc
+++ b/runtime/vm/code_patcher_arm64.cc
@@ -16,8 +16,8 @@
  public:
   PoolPointerCall(uword pc, const Code& code)
       : end_(pc), object_pool_(ObjectPool::Handle(code.GetObjectPool())) {
-    // Last instruction: blr ip0.
-    ASSERT(*(reinterpret_cast<uint32_t*>(end_) - 1) == 0xd63f0200);
+    // Last instruction: blr lr.
+    ASSERT(*(reinterpret_cast<uint32_t*>(end_) - 1) == 0xd63f03c0);
     InstructionPattern::DecodeLoadWordFromPool(end_ - 2 * Instr::kInstrSize,
                                                &reg_, &index_);
   }
diff --git a/runtime/vm/code_patcher_arm64_test.cc b/runtime/vm/code_patcher_arm64_test.cc
index 61767d8..d5d33e7 100644
--- a/runtime/vm/code_patcher_arm64_test.cc
+++ b/runtime/vm/code_patcher_arm64_test.cc
@@ -41,8 +41,9 @@
       function, target_name, args_descriptor, 15, 1, ICData::kInstance));
   const Code& stub = StubCode::OneArgCheckInlineCache();
 
-  // Code accessing pp is generated, but not executed. Uninitialized pp is OK.
-  __ set_constant_pool_allowed(true);
+  // Code is generated, but not executed. Just parsed with CodePatcher.
+  __ set_constant_pool_allowed(true);  // Uninitialized pp is OK.
+  SPILLS_LR_TO_FRAME({});              // Clobbered LR is OK.
 
   compiler::ObjectPoolBuilder& op = __ object_pool_builder();
   const intptr_t ic_data_index =
@@ -51,10 +52,9 @@
       op.AddObject(stub, ObjectPool::Patchability::kPatchable);
   ASSERT((ic_data_index + 1) == stub_index);
   __ LoadDoubleWordFromPoolIndex(R5, CODE_REG, ic_data_index);
-  __ ldr(LR, compiler::FieldAddress(
-                 CODE_REG,
-                 Code::entry_point_offset(Code::EntryKind::kMonomorphic)));
-  __ blr(LR);
+  __ Call(compiler::FieldAddress(
+      CODE_REG, Code::entry_point_offset(Code::EntryKind::kMonomorphic)));
+  RESTORES_LR_FROM_FRAME({});  // Clobbered LR is OK.
   __ ret();
 }
 
diff --git a/runtime/vm/code_patcher_arm_test.cc b/runtime/vm/code_patcher_arm_test.cc
index 96c34e6..3d1c56f 100644
--- a/runtime/vm/code_patcher_arm_test.cc
+++ b/runtime/vm/code_patcher_arm_test.cc
@@ -40,11 +40,13 @@
   const ICData& ic_data = ICData::ZoneHandle(ICData::New(
       function, target_name, args_descriptor, 15, 1, ICData::kInstance));
 
-  // Code accessing pp is generated, but not executed. Uninitialized pp is OK.
-  __ set_constant_pool_allowed(true);
+  // Code is generated, but not executed. Just parsed with CodePatcher.
+  __ set_constant_pool_allowed(true);  // Uninitialized pp is OK.
+  SPILLS_LR_TO_FRAME({});              // Clobbered LR is OK.
 
   __ LoadObject(R9, ic_data);
   __ BranchLinkPatchable(StubCode::OneArgCheckInlineCache());
+  RESTORES_LR_FROM_FRAME({});  // Clobbered LR is OK.
   __ Ret();
 }
 
diff --git a/runtime/vm/compiler/asm_intrinsifier_arm.cc b/runtime/vm/compiler/asm_intrinsifier_arm.cc
index fd9c7c7..042d3f0 100644
--- a/runtime/vm/compiler/asm_intrinsifier_arm.cc
+++ b/runtime/vm/compiler/asm_intrinsifier_arm.cc
@@ -34,14 +34,16 @@
   COMPILE_ASSERT(IsAbiPreservedRegister(CALLEE_SAVED_TEMP));
 
   // Save LR by moving it to a callee saved temporary register.
-  assembler->Comment("IntrinsicCallPrologue");
-  assembler->mov(CALLEE_SAVED_TEMP, Operand(LR));
+  __ Comment("IntrinsicCallPrologue");
+  SPILLS_RETURN_ADDRESS_FROM_LR_TO_REGISTER(
+      __ mov(CALLEE_SAVED_TEMP, Operand(LR)));
 }
 
 void AsmIntrinsifier::IntrinsicCallEpilogue(Assembler* assembler) {
   // Restore LR.
-  assembler->Comment("IntrinsicCallEpilogue");
-  assembler->mov(LR, Operand(CALLEE_SAVED_TEMP));
+  __ Comment("IntrinsicCallEpilogue");
+  RESTORES_RETURN_ADDRESS_FROM_REGISTER_TO_LR(
+      __ mov(LR, Operand(CALLEE_SAVED_TEMP)));
 }
 
 // Allocate a GrowableObjectArray:: using the backing array specified.
@@ -93,7 +95,7 @@
                                              Label* normal_ir_body) {
   TestBothArgumentsSmis(assembler, normal_ir_body);  // Checks two smis.
   __ adds(R0, R0, Operand(R1));                      // Adds.
-  __ bx(LR, VC);                                     // Return if no overflow.
+  READS_RETURN_ADDRESS_FROM_LR(__ bx(LR, VC));       // Return if no overflow.
   // Otherwise fall through.
   __ Bind(normal_ir_body);
 }
@@ -106,7 +108,7 @@
                                              Label* normal_ir_body) {
   TestBothArgumentsSmis(assembler, normal_ir_body);
   __ subs(R0, R0, Operand(R1));  // Subtract.
-  __ bx(LR, VC);                 // Return if no overflow.
+  READS_RETURN_ADDRESS_FROM_LR(__ bx(LR, VC));  // Return if no overflow.
   // Otherwise fall through.
   __ Bind(normal_ir_body);
 }
@@ -114,7 +116,7 @@
 void AsmIntrinsifier::Integer_sub(Assembler* assembler, Label* normal_ir_body) {
   TestBothArgumentsSmis(assembler, normal_ir_body);
   __ subs(R0, R1, Operand(R0));  // Subtract.
-  __ bx(LR, VC);                 // Return if no overflow.
+  READS_RETURN_ADDRESS_FROM_LR(__ bx(LR, VC));  // Return if no overflow.
   // Otherwise fall through.
   __ Bind(normal_ir_body);
 }
@@ -125,7 +127,7 @@
   __ SmiUntag(R0);           // Untags R0. We only want result shifted by one.
   __ smull(R0, IP, R0, R1);  // IP:R0 <- R0 * R1.
   __ cmp(IP, Operand(R0, ASR, 31));
-  __ bx(LR, EQ);
+  READS_RETURN_ADDRESS_FROM_LR(__ bx(LR, EQ));
   __ Bind(normal_ir_body);  // Fall through on overflow.
 }
 
@@ -155,10 +157,10 @@
   // Check for quick zero results.
   __ cmp(left, Operand(0));
   __ mov(R0, Operand(0), EQ);
-  __ bx(LR, EQ);  // left is 0? Return 0.
+  READS_RETURN_ADDRESS_FROM_LR(__ bx(LR, EQ));  // left is 0? Return 0.
   __ cmp(left, Operand(right));
   __ mov(R0, Operand(0), EQ);
-  __ bx(LR, EQ);  // left == right? Return 0.
+  READS_RETURN_ADDRESS_FROM_LR(__ bx(LR, EQ));  // left == right? Return 0.
 
   // Check if result should be left.
   __ cmp(left, Operand(0));
@@ -167,8 +169,7 @@
   __ cmp(left, Operand(right));
   // left is less than right, result is left.
   __ mov(R0, Operand(left), LT);
-  __ bx(LR, LT);
-
+  READS_RETURN_ADDRESS_FROM_LR(__ bx(LR, LT));
   __ Bind(&modulo);
   // result <- left - right * (left / right)
   __ SmiUntag(left);
@@ -209,8 +210,7 @@
 
   __ cmp(R1, Operand(0));
   __ mov(R0, Operand(R1, LSL, 1), GE);  // Tag and move result to R0.
-  __ bx(LR, GE);
-
+  READS_RETURN_ADDRESS_FROM_LR(__ bx(LR, GE));
   // Result is negative, adjust it.
   __ cmp(R0, Operand(0));
   __ sub(R0, R1, Operand(R0), LT);
@@ -241,7 +241,7 @@
   // cannot tag the result.
   __ CompareImmediate(R0, 0x40000000);
   __ SmiTag(R0, NE);  // Not equal. Okay to tag and return.
-  __ bx(LR, NE);      // Return.
+  READS_RETURN_ADDRESS_FROM_LR(__ bx(LR, NE));
   __ Bind(normal_ir_body);
 }
 
@@ -251,7 +251,8 @@
   __ tst(R0, Operand(kSmiTagMask));                 // Test for Smi.
   __ b(normal_ir_body, NE);
   __ rsbs(R0, R0, Operand(0));  // R0 is a Smi. R0 <- 0 - R0.
-  __ bx(LR, VC);  // Return if there wasn't overflow, fall through otherwise.
+  READS_RETURN_ADDRESS_FROM_LR(__ bx(
+      LR, VC));  // Return if there wasn't overflow, fall through otherwise.
   // R0 is not a Smi. Fall through.
   __ Bind(normal_ir_body);
 }
@@ -314,8 +315,7 @@
 
   // No overflow, result in R0.
   __ mov(R0, Operand(R1, LSL, R0), EQ);
-  __ bx(LR, EQ);
-
+  READS_RETURN_ADDRESS_FROM_LR(__ bx(LR, EQ));
   // Arguments are Smi but the shift produced an overflow to Mint.
   __ CompareImmediate(R1, 0);
   __ b(normal_ir_body, LT);
@@ -500,8 +500,8 @@
   // Receiver is Mint, return false if right is Smi.
   __ tst(R0, Operand(kSmiTagMask));
   __ LoadObject(R0, CastHandle<Object>(FalseObject()), EQ);
-  __ bx(LR, EQ);
-  // TODO(srdjan): Implement Mint == Mint comparison.
+  READS_RETURN_ADDRESS_FROM_LR(
+      __ bx(LR, EQ));  // TODO(srdjan): Implement Mint == Mint comparison.
 
   __ Bind(normal_ir_body);
 }
@@ -1047,7 +1047,7 @@
     __ vmstat();
     __ LoadObject(R0, CastHandle<Object>(FalseObject()));
     // Return false if D0 or D1 was NaN before checking true condition.
-    __ bx(LR, VS);
+    READS_RETURN_ADDRESS_FROM_LR(__ bx(LR, VS));
     __ LoadObject(R0, CastHandle<Object>(TrueObject()), true_condition);
     __ Ret();
 
@@ -1216,15 +1216,14 @@
     // If the low word isn't 0, then it isn't infinity.
     __ cmp(R1, Operand(0));
     __ LoadObject(R0, CastHandle<Object>(FalseObject()), NE);
-    __ bx(LR, NE);  // Return if NE.
+    READS_RETURN_ADDRESS_FROM_LR(__ bx(LR, NE));  // Return if NE.
 
     // Mask off the sign bit.
     __ AndImmediate(R2, R2, 0x7FFFFFFF);
     // Compare with +infinity.
     __ CompareImmediate(R2, 0x7FF00000);
     __ LoadObject(R0, CastHandle<Object>(FalseObject()), NE);
-    __ bx(LR, NE);
-
+    READS_RETURN_ADDRESS_FROM_LR(__ bx(LR, NE));
     __ LoadObject(R0, CastHandle<Object>(TrueObject()));
     __ Ret();
   }
@@ -1280,7 +1279,7 @@
     // Check for overflow and that it fits into Smi.
     __ CompareImmediate(R0, 0xC0000000);
     __ SmiTag(R0, PL);
-    __ bx(LR, PL);
+    READS_RETURN_ADDRESS_FROM_LR(__ bx(LR, PL));
     __ Bind(normal_ir_body);
   }
 }
@@ -1317,8 +1316,7 @@
   __ vcvtdi(D1, S2);
   __ vcmpd(D0, D1);
   __ vmstat();
-  __ bx(LR, EQ);
-
+  READS_RETURN_ADDRESS_FROM_LR(__ bx(LR, EQ));
   // Convert the double bits to a hash code that fits in a Smi.
   __ Bind(&double_hash);
   __ ldr(R0, FieldAddress(R1, target::Double::value_offset()));
@@ -1579,8 +1577,7 @@
   __ ldr(R0, Address(SP, 0 * target::kWordSize));
   __ ldr(R0, FieldAddress(R0, target::String::hash_offset()));
   __ cmp(R0, Operand(0));
-  __ bx(LR, NE);
-  // Hash not yet computed.
+  READS_RETURN_ADDRESS_FROM_LR(__ bx(LR, NE));  // Hash not yet computed.
   __ Bind(normal_ir_body);
 }
 
@@ -1589,8 +1586,7 @@
   __ ldr(R0, Address(SP, 0 * target::kWordSize));
   __ ldr(R0, FieldAddress(R0, target::Type::hash_offset()));
   __ cmp(R0, Operand(0));
-  __ bx(LR, NE);
-  // Hash not yet computed.
+  READS_RETURN_ADDRESS_FROM_LR(__ bx(LR, NE));  // Hash not yet computed.
   __ Bind(normal_ir_body);
 }
 
@@ -1831,7 +1827,7 @@
   __ ldr(R1, Address(SP, 0 * target::kWordSize));
   __ ldr(R0, FieldAddress(R1, target::String::hash_offset()));
   __ cmp(R0, Operand(0));
-  __ bx(LR, NE);  // Return if already computed.
+  READS_RETURN_ADDRESS_FROM_LR(__ bx(LR, NE));  // Return if already computed.
 
   __ ldr(R2, FieldAddress(R1, target::String::length_offset()));
 
diff --git a/runtime/vm/compiler/asm_intrinsifier_arm64.cc b/runtime/vm/compiler/asm_intrinsifier_arm64.cc
index 1c1271d..ab4430c 100644
--- a/runtime/vm/compiler/asm_intrinsifier_arm64.cc
+++ b/runtime/vm/compiler/asm_intrinsifier_arm64.cc
@@ -38,15 +38,15 @@
   COMPILE_ASSERT(CALLEE_SAVED_TEMP2 != CODE_REG);
   COMPILE_ASSERT(CALLEE_SAVED_TEMP2 != ARGS_DESC_REG);
 
-  assembler->Comment("IntrinsicCallPrologue");
-  assembler->mov(CALLEE_SAVED_TEMP, LR);
-  assembler->mov(CALLEE_SAVED_TEMP2, ARGS_DESC_REG);
+  __ Comment("IntrinsicCallPrologue");
+  SPILLS_RETURN_ADDRESS_FROM_LR_TO_REGISTER(__ mov(CALLEE_SAVED_TEMP, LR));
+  __ mov(CALLEE_SAVED_TEMP2, ARGS_DESC_REG);
 }
 
 void AsmIntrinsifier::IntrinsicCallEpilogue(Assembler* assembler) {
-  assembler->Comment("IntrinsicCallEpilogue");
-  assembler->mov(LR, CALLEE_SAVED_TEMP);
-  assembler->mov(ARGS_DESC_REG, CALLEE_SAVED_TEMP2);
+  __ Comment("IntrinsicCallEpilogue");
+  RESTORES_RETURN_ADDRESS_FROM_REGISTER_TO_LR(__ mov(LR, CALLEE_SAVED_TEMP));
+  __ mov(ARGS_DESC_REG, CALLEE_SAVED_TEMP2);
 }
 
 // Allocate a GrowableObjectArray:: using the backing array specified.
diff --git a/runtime/vm/compiler/assembler/assembler_arm.cc b/runtime/vm/compiler/assembler/assembler_arm.cc
index bcab63b..c28326d 100644
--- a/runtime/vm/compiler/assembler/assembler_arm.cc
+++ b/runtime/vm/compiler/assembler/assembler_arm.cc
@@ -19,6 +19,9 @@
 #error ARM cross-compile only supported on Linux, Android, iOS, and Mac
 #endif
 
+// For use by LR related macros (e.g. CLOBBERS_LR).
+#define __ this->
+
 namespace dart {
 
 DECLARE_FLAG(bool, check_code_pointer);
@@ -34,16 +37,13 @@
       use_far_branches_(use_far_branches),
       constant_pool_allowed_(false) {
   generate_invoke_write_barrier_wrapper_ = [&](Condition cond, Register reg) {
-    ldr(LR,
+    Call(
         Address(THR, target::Thread::write_barrier_wrappers_thread_offset(reg)),
         cond);
-    blx(LR, cond);
   };
   generate_invoke_array_write_barrier_ = [&](Condition cond) {
-    ldr(LR,
-        Address(THR, target::Thread::array_write_barrier_entry_point_offset()),
-        cond);
-    blx(LR, cond);
+    Call(Address(THR, target::Thread::array_write_barrier_entry_point_offset()),
+         cond);
   };
 }
 
@@ -1625,10 +1625,7 @@
       return;
     }
   }
-  if (!CanLoadFromObjectPool(object)) {
-    UNREACHABLE();
-    return;
-  }
+  RELEASE_ASSERT(CanLoadFromObjectPool(object));
   // Make sure that class CallPattern is able to decode this load from the
   // object pool.
   const auto index = is_unique ? object_pool_builder().AddObject(object)
@@ -1726,12 +1723,11 @@
 void Assembler::StoreIntoObject(Register object,
                                 const Address& dest,
                                 Register value,
-                                CanBeSmi can_be_smi,
-                                bool lr_reserved) {
+                                CanBeSmi can_be_smi) {
   // x.slot = x. Barrier should have be removed at the IL level.
   ASSERT(object != value);
-  ASSERT(object != LR);
-  ASSERT(value != LR);
+  ASSERT(object != LINK_REGISTER);
+  ASSERT(value != LINK_REGISTER);
   ASSERT(object != TMP);
   ASSERT(value != TMP);
 
@@ -1748,12 +1744,18 @@
   if (can_be_smi == kValueCanBeSmi) {
     BranchIfSmi(value, &done);
   }
-  if (!lr_reserved) Push(LR);
-  ldrb(TMP, FieldAddress(object, target::Object::tags_offset()));
-  ldrb(LR, FieldAddress(value, target::Object::tags_offset()));
-  and_(TMP, LR, Operand(TMP, LSR, target::ObjectLayout::kBarrierOverlapShift));
-  ldr(LR, Address(THR, target::Thread::write_barrier_mask_offset()));
-  tst(TMP, Operand(LR));
+  const bool preserve_lr = lr_state().LRContainsReturnAddress();
+  if (preserve_lr) {
+    SPILLS_LR_TO_FRAME(Push(LR));
+  }
+  CLOBBERS_LR({
+    ldrb(TMP, FieldAddress(object, target::Object::tags_offset()));
+    ldrb(LR, FieldAddress(value, target::Object::tags_offset()));
+    and_(TMP, LR,
+         Operand(TMP, LSR, target::ObjectLayout::kBarrierOverlapShift));
+    ldr(LR, Address(THR, target::Thread::write_barrier_mask_offset()));
+    tst(TMP, Operand(LR));
+  });
   if (value != kWriteBarrierValueReg) {
     // Unlikely. Only non-graph intrinsics.
     // TODO(rmacnak): Shuffle registers in intrinsics.
@@ -1781,20 +1783,21 @@
   } else {
     generate_invoke_write_barrier_wrapper_(NE, object);
   }
-  if (!lr_reserved) Pop(LR);
+  if (preserve_lr) {
+    RESTORES_LR_FROM_FRAME(Pop(LR));
+  }
   Bind(&done);
 }
 
 void Assembler::StoreIntoArray(Register object,
                                Register slot,
                                Register value,
-                               CanBeSmi can_be_smi,
-                               bool lr_reserved) {
+                               CanBeSmi can_be_smi) {
   // x.slot = x. Barrier should have be removed at the IL level.
   ASSERT(object != value);
-  ASSERT(object != LR);
-  ASSERT(value != LR);
-  ASSERT(slot != LR);
+  ASSERT(object != LINK_REGISTER);
+  ASSERT(value != LINK_REGISTER);
+  ASSERT(slot != LINK_REGISTER);
   ASSERT(object != TMP);
   ASSERT(value != TMP);
   ASSERT(slot != TMP);
@@ -1812,12 +1815,19 @@
   if (can_be_smi == kValueCanBeSmi) {
     BranchIfSmi(value, &done);
   }
-  if (!lr_reserved) Push(LR);
-  ldrb(TMP, FieldAddress(object, target::Object::tags_offset()));
-  ldrb(LR, FieldAddress(value, target::Object::tags_offset()));
-  and_(TMP, LR, Operand(TMP, LSR, target::ObjectLayout::kBarrierOverlapShift));
-  ldr(LR, Address(THR, target::Thread::write_barrier_mask_offset()));
-  tst(TMP, Operand(LR));
+  const bool preserve_lr = lr_state().LRContainsReturnAddress();
+  if (preserve_lr) {
+    SPILLS_LR_TO_FRAME(Push(LR));
+  }
+
+  CLOBBERS_LR({
+    ldrb(TMP, FieldAddress(object, target::Object::tags_offset()));
+    ldrb(LR, FieldAddress(value, target::Object::tags_offset()));
+    and_(TMP, LR,
+         Operand(TMP, LSR, target::ObjectLayout::kBarrierOverlapShift));
+    ldr(LR, Address(THR, target::Thread::write_barrier_mask_offset()));
+    tst(TMP, Operand(LR));
+  });
 
   if ((object != kWriteBarrierObjectReg) || (value != kWriteBarrierValueReg) ||
       (slot != kWriteBarrierSlotReg)) {
@@ -1827,23 +1837,24 @@
     UNIMPLEMENTED();
   }
   generate_invoke_array_write_barrier_(NE);
-  if (!lr_reserved) Pop(LR);
+  if (preserve_lr) {
+    RESTORES_LR_FROM_FRAME(Pop(LR));
+  }
   Bind(&done);
 }
 
 void Assembler::StoreIntoObjectOffset(Register object,
                                       int32_t offset,
                                       Register value,
-                                      CanBeSmi can_value_be_smi,
-                                      bool lr_reserved) {
+                                      CanBeSmi can_value_be_smi) {
   int32_t ignored = 0;
   if (Address::CanHoldStoreOffset(kFourBytes, offset - kHeapObjectTag,
                                   &ignored)) {
     StoreIntoObject(object, FieldAddress(object, offset), value,
-                    can_value_be_smi, lr_reserved);
+                    can_value_be_smi);
   } else {
     AddImmediate(IP, object, offset - kHeapObjectTag);
-    StoreIntoObject(object, Address(IP), value, can_value_be_smi, lr_reserved);
+    StoreIntoObject(object, Address(IP), value, can_value_be_smi);
   }
 }
 
@@ -2122,6 +2133,7 @@
     } else {
       EmitType5(cond, dest, link);
     }
+    label->UpdateLRState(lr_state());
   } else {
     const intptr_t position = buffer_.Size();
     if (use_far_branches()) {
@@ -2131,7 +2143,7 @@
       // Use the offset field of the branch instruction for linking the sites.
       EmitType5(cond, label->position_, link);
     }
-    label->LinkTo(position);
+    label->LinkTo(position, lr_state());
   }
 }
 
@@ -2199,7 +2211,7 @@
       label->position_ = Assembler::DecodeBranchOffset(next);
     }
   }
-  label->BindTo(bound_pc);
+  label->BindTo(bound_pc, lr_state());
 }
 
 void Assembler::Bind(Label* label) {
@@ -2604,8 +2616,7 @@
   const intptr_t index =
       object_pool_builder().FindObject(ToObject(target), patchable);
   LoadWordFromPoolIndex(CODE_REG, index, PP, AL);
-  ldr(LR, FieldAddress(CODE_REG, target::Code::entry_point_offset(entry_kind)));
-  blx(LR);  // Use blx instruction so that the return branch prediction works.
+  Call(FieldAddress(CODE_REG, target::Code::entry_point_offset(entry_kind)));
 }
 
 void Assembler::BranchLinkPatchable(const Code& target,
@@ -2628,13 +2639,14 @@
   const intptr_t index =
       object_pool_builder().FindObject(ToObject(target), equivalence);
   LoadWordFromPoolIndex(CODE_REG, index, PP, AL);
-  ldr(LR, FieldAddress(CODE_REG, target::Code::entry_point_offset(entry_kind)));
-  blx(LR);  // Use blx instruction so that the return branch prediction works.
+  Call(FieldAddress(CODE_REG, target::Code::entry_point_offset(entry_kind)));
 }
 
 void Assembler::BranchLink(const ExternalLabel* label) {
-  LoadImmediate(LR, label->address());  // Target address is never patched.
-  blx(LR);  // Use blx instruction so that the return branch prediction works.
+  CLOBBERS_LR({
+    LoadImmediate(LR, label->address());  // Target address is never patched.
+    blx(LR);  // Use blx instruction so that the return branch prediction works.
+  });
 }
 
 void Assembler::BranchLinkOffset(Register base, int32_t offset) {
@@ -3117,8 +3129,8 @@
   PopList(regs);
 }
 
-void Assembler::Ret() {
-  bx(LR);
+void Assembler::Ret(Condition cond /* = AL */) {
+  READS_RETURN_ADDRESS_FROM_LR(bx(LR, cond));
 }
 
 void Assembler::ReserveAlignedFrameSpace(intptr_t frame_space) {
@@ -3149,7 +3161,8 @@
 void Assembler::EnterCallRuntimeFrame(intptr_t frame_space) {
   Comment("EnterCallRuntimeFrame");
   // Preserve volatile CPU registers and PP.
-  EnterFrame(kDartVolatileCpuRegs | (1 << PP) | (1 << FP) | (1 << LR), 0);
+  SPILLS_LR_TO_FRAME(
+      EnterFrame(kDartVolatileCpuRegs | (1 << PP) | (1 << FP) | (1 << LR), 0));
   COMPILE_ASSERT((kDartVolatileCpuRegs & (1 << PP)) == 0);
 
   // Preserve all volatile FPU registers.
@@ -3199,7 +3212,8 @@
   }
 
   // Restore volatile CPU registers.
-  LeaveFrame(kDartVolatileCpuRegs | (1 << PP) | (1 << FP) | (1 << LR));
+  RESTORES_LR_FROM_FRAME(
+      LeaveFrame(kDartVolatileCpuRegs | (1 << PP) | (1 << FP) | (1 << LR)));
 }
 
 void Assembler::CallRuntime(const RuntimeEntry& entry,
@@ -3213,15 +3227,16 @@
   // Registers are pushed in descending order: R5 | R6 | R7/R11 | R14.
   COMPILE_ASSERT(PP < CODE_REG);
   COMPILE_ASSERT(CODE_REG < FP);
-  COMPILE_ASSERT(FP < LR);
+  COMPILE_ASSERT(FP < LINK_REGISTER.code);
 
   if (!(FLAG_precompiled_mode && FLAG_use_bare_instructions)) {
-    EnterFrame((1 << PP) | (1 << CODE_REG) | (1 << FP) | (1 << LR), 0);
+    SPILLS_LR_TO_FRAME(
+        EnterFrame((1 << PP) | (1 << CODE_REG) | (1 << FP) | (1 << LR), 0));
 
     // Setup pool pointer for this dart function.
     if (load_pool_pointer) LoadPoolPointer();
   } else {
-    EnterFrame((1 << FP) | (1 << LR), 0);
+    SPILLS_LR_TO_FRAME(EnterFrame((1 << FP) | (1 << LR), 0));
   }
   set_constant_pool_allowed(true);
 
@@ -3252,7 +3267,7 @@
 
   // This will implicitly drop saved PP, PC marker due to restoring SP from FP
   // first.
-  LeaveFrame((1 << FP) | (1 << LR));
+  RESTORES_LR_FROM_FRAME(LeaveFrame((1 << FP) | (1 << LR)));
 }
 
 void Assembler::LeaveDartFrameAndReturn() {
diff --git a/runtime/vm/compiler/assembler/assembler_arm.h b/runtime/vm/compiler/assembler/assembler_arm.h
index f853da7..c9d44f8 100644
--- a/runtime/vm/compiler/assembler/assembler_arm.h
+++ b/runtime/vm/compiler/assembler/assembler_arm.h
@@ -752,9 +752,14 @@
   // Branch and link to [base + offset]. Call sequence is never patched.
   void BranchLinkOffset(Register base, int32_t offset);
 
-  void Call(Address target) {
-    ldr(LR, target);
-    blx(LR);
+  void Call(Address target, Condition cond = AL) {
+    // CLOBBERS_LR uses __ to access the assembler.
+#define __ this->
+    CLOBBERS_LR({
+      ldr(LR, target, cond);
+      blx(LR, cond);
+    });
+#undef __
   }
   void Call(const Code& code) { BranchLink(code); }
 
@@ -855,18 +860,15 @@
   void StoreIntoObject(Register object,      // Object we are storing into.
                        const Address& dest,  // Where we are storing into.
                        Register value,       // Value we are storing.
-                       CanBeSmi can_value_be_smi = kValueCanBeSmi,
-                       bool lr_reserved = false);
+                       CanBeSmi can_value_be_smi = kValueCanBeSmi);
   void StoreIntoArray(Register object,
                       Register slot,
                       Register value,
-                      CanBeSmi can_value_be_smi = kValueCanBeSmi,
-                      bool lr_reserved = false);
+                      CanBeSmi can_value_be_smi = kValueCanBeSmi);
   void StoreIntoObjectOffset(Register object,
                              int32_t offset,
                              Register value,
-                             CanBeSmi can_value_be_smi = kValueCanBeSmi,
-                             bool lr_reserved = false);
+                             CanBeSmi can_value_be_smi = kValueCanBeSmi);
 
   void StoreIntoObjectNoBarrier(Register object,
                                 const Address& dest,
@@ -1116,7 +1118,7 @@
   // Function frame setup and tear down.
   void EnterFrame(RegList regs, intptr_t frame_space);
   void LeaveFrame(RegList regs, bool allow_pop_pc = false);
-  void Ret();
+  void Ret(Condition cond = AL);
   void ReserveAlignedFrameSpace(intptr_t frame_space);
 
   // In debug mode, this generates code to check that:
@@ -1282,6 +1284,9 @@
   bool constant_pool_allowed() const { return constant_pool_allowed_; }
   void set_constant_pool_allowed(bool b) { constant_pool_allowed_ = b; }
 
+  compiler::LRState lr_state() const { return lr_state_; }
+  void set_lr_state(compiler::LRState b) { lr_state_ = b; }
+
   // Whether we can branch to a target which is [distance] bytes away from the
   // beginning of the branch instruction.
   //
@@ -1301,6 +1306,10 @@
  private:
   bool use_far_branches_;
 
+  bool constant_pool_allowed_;
+
+  compiler::LRState lr_state_ = compiler::LRState::OnEntry();
+
   // If you are thinking of using one or both of these instructions directly,
   // instead LoadImmediate should probably be used.
   void movw(Register rd, uint16_t imm16, Condition cond = AL);
@@ -1310,8 +1319,6 @@
 
   void BranchLink(const ExternalLabel* label);
 
-  bool constant_pool_allowed_;
-
   void LoadObjectHelper(Register rd,
                         const Object& object,
                         Condition cond,
diff --git a/runtime/vm/compiler/assembler/assembler_arm64.cc b/runtime/vm/compiler/assembler/assembler_arm64.cc
index 00b02db..885229b65 100644
--- a/runtime/vm/compiler/assembler/assembler_arm64.cc
+++ b/runtime/vm/compiler/assembler/assembler_arm64.cc
@@ -22,6 +22,9 @@
 
 DEFINE_FLAG(bool, use_far_branches, false, "Always use far branches");
 
+// For use by LR related macros (e.g. CLOBBERS_LR).
+#define __ this->
+
 namespace compiler {
 
 Assembler::Assembler(ObjectPoolBuilder* object_pool_builder,
@@ -30,14 +33,12 @@
       use_far_branches_(use_far_branches),
       constant_pool_allowed_(false) {
   generate_invoke_write_barrier_wrapper_ = [&](Register reg) {
-    ldr(LR, Address(THR,
-                    target::Thread::write_barrier_wrappers_thread_offset(reg)));
-    blr(LR);
+    Call(Address(THR,
+                 target::Thread::write_barrier_wrappers_thread_offset(reg)));
   };
   generate_invoke_array_write_barrier_ = [&]() {
-    ldr(LR,
+    Call(
         Address(THR, target::Thread::array_write_barrier_entry_point_offset()));
-    blr(LR);
   };
 }
 
@@ -200,7 +201,7 @@
       label->position_ = BindImm19Branch(position, dest);
     }
   }
-  label->BindTo(bound_pc);
+  label->BindTo(bound_pc, lr_state());
 }
 
 static int CountLeadingZeros(uint64_t value, int width) {
@@ -649,14 +650,11 @@
   const intptr_t index =
       object_pool_builder().FindObject(ToObject(target), patchable);
   LoadWordFromPoolIndex(CODE_REG, index);
-  ldr(TMP,
-      FieldAddress(CODE_REG, target::Code::entry_point_offset(entry_kind)));
-  blr(TMP);
+  Call(FieldAddress(CODE_REG, target::Code::entry_point_offset(entry_kind)));
 }
 
 void Assembler::BranchLinkToRuntime() {
-  ldr(LR, Address(THR, target::Thread::call_to_runtime_entry_point_offset()));
-  blr(LR);
+  Call(Address(THR, target::Thread::call_to_runtime_entry_point_offset()));
 }
 
 void Assembler::BranchLinkWithEquivalence(const Code& target,
@@ -665,9 +663,7 @@
   const intptr_t index =
       object_pool_builder().FindObject(ToObject(target), equivalence);
   LoadWordFromPoolIndex(CODE_REG, index);
-  ldr(TMP,
-      FieldAddress(CODE_REG, target::Code::entry_point_offset(entry_kind)));
-  blr(TMP);
+  Call(FieldAddress(CODE_REG, target::Code::entry_point_offset(entry_kind)));
 }
 
 void Assembler::AddImmediate(Register dest, Register rn, int64_t imm) {
@@ -968,26 +964,25 @@
 void Assembler::StoreIntoObjectOffset(Register object,
                                       int32_t offset,
                                       Register value,
-                                      CanBeSmi value_can_be_smi,
-                                      bool lr_reserved) {
+                                      CanBeSmi value_can_be_smi) {
   if (Address::CanHoldOffset(offset - kHeapObjectTag)) {
     StoreIntoObject(object, FieldAddress(object, offset), value,
-                    value_can_be_smi, lr_reserved);
+                    value_can_be_smi);
   } else {
     AddImmediate(TMP, object, offset - kHeapObjectTag);
-    StoreIntoObject(object, Address(TMP), value, value_can_be_smi, lr_reserved);
+    StoreIntoObject(object, Address(TMP), value, value_can_be_smi);
   }
 }
 
 void Assembler::StoreIntoObject(Register object,
                                 const Address& dest,
                                 Register value,
-                                CanBeSmi can_be_smi,
-                                bool lr_reserved) {
+                                CanBeSmi can_be_smi) {
+  const bool spill_lr = lr_state().LRContainsReturnAddress();
   // x.slot = x. Barrier should have be removed at the IL level.
   ASSERT(object != value);
-  ASSERT(object != LR);
-  ASSERT(value != LR);
+  ASSERT(object != LINK_REGISTER);
+  ASSERT(value != LINK_REGISTER);
   ASSERT(object != TMP);
   ASSERT(object != TMP2);
   ASSERT(value != TMP);
@@ -1015,7 +1010,9 @@
   tst(TMP, Operand(BARRIER_MASK));
   b(&done, ZERO);
 
-  if (!lr_reserved) Push(LR);
+  if (spill_lr) {
+    SPILLS_LR_TO_FRAME(Push(LR));
+  }
   Register objectForCall = object;
   if (value != kWriteBarrierValueReg) {
     // Unlikely. Only non-graph intrinsics.
@@ -1041,15 +1038,17 @@
       PopPair(kWriteBarrierValueReg, objectForCall);
     }
   }
-  if (!lr_reserved) Pop(LR);
+  if (spill_lr) {
+    RESTORES_LR_FROM_FRAME(Pop(LR));
+  }
   Bind(&done);
 }
 
 void Assembler::StoreIntoArray(Register object,
                                Register slot,
                                Register value,
-                               CanBeSmi can_be_smi,
-                               bool lr_reserved) {
+                               CanBeSmi can_be_smi) {
+  const bool spill_lr = lr_state().LRContainsReturnAddress();
   ASSERT(object != TMP);
   ASSERT(object != TMP2);
   ASSERT(value != TMP);
@@ -1078,8 +1077,9 @@
        Operand(TMP, LSR, target::ObjectLayout::kBarrierOverlapShift));
   tst(TMP, Operand(BARRIER_MASK));
   b(&done, ZERO);
-  if (!lr_reserved) Push(LR);
-
+  if (spill_lr) {
+    SPILLS_LR_TO_FRAME(Push(LR));
+  }
   if ((object != kWriteBarrierObjectReg) || (value != kWriteBarrierValueReg) ||
       (slot != kWriteBarrierSlotReg)) {
     // Spill and shuffle unimplemented. Currently StoreIntoArray is only used
@@ -1088,7 +1088,9 @@
     UNIMPLEMENTED();
   }
   generate_invoke_array_write_barrier_();
-  if (!lr_reserved) Pop(LR);
+  if (spill_lr) {
+    RESTORES_LR_FROM_FRAME(Pop(LR));
+  }
   Bind(&done);
 }
 
@@ -1324,7 +1326,7 @@
 }
 
 void Assembler::EnterFrame(intptr_t frame_size) {
-  PushPair(FP, LR);  // low: FP, high: LR.
+  SPILLS_LR_TO_FRAME(PushPair(FP, LR));  // low: FP, high: LR.
   mov(FP, SP);
 
   if (frame_size > 0) {
@@ -1334,7 +1336,7 @@
 
 void Assembler::LeaveFrame() {
   mov(SP, FP);
-  PopPair(FP, LR);  // low: FP, high: LR.
+  RESTORES_LR_FROM_FRAME(PopPair(FP, LR));  // low: FP, high: LR.
 }
 
 void Assembler::EnterDartFrame(intptr_t frame_size, Register new_pp) {
@@ -1621,12 +1623,14 @@
 }
 
 void Assembler::EnterCFrame(intptr_t frame_space) {
-  EnterFrame(0);
+  Push(FP);
+  mov(FP, SP);
   ReserveAlignedFrameSpace(frame_space);
 }
 
 void Assembler::LeaveCFrame() {
-  LeaveFrame();
+  mov(SP, FP);
+  Pop(FP);
 }
 
 // R0 receiver, R5 ICData entries array
diff --git a/runtime/vm/compiler/assembler/assembler_arm64.h b/runtime/vm/compiler/assembler/assembler_arm64.h
index 3290499..660fe68 100644
--- a/runtime/vm/compiler/assembler/assembler_arm64.h
+++ b/runtime/vm/compiler/assembler/assembler_arm64.h
@@ -1100,7 +1100,12 @@
   }
 
   void b(int32_t offset) { EmitUnconditionalBranchOp(B, offset); }
-  void bl(int32_t offset) { EmitUnconditionalBranchOp(BL, offset); }
+  void bl(int32_t offset) {
+    // CLOBBERS_LR uses __ to access the assembler.
+#define __ this->
+    CLOBBERS_LR(EmitUnconditionalBranchOp(BL, offset));
+#undef __
+  }
 
   // Branches to the given label if the condition holds.
   // [distance] is ignored on ARM.
@@ -1138,8 +1143,21 @@
 
   // Branch, link, return.
   void br(Register rn) { EmitUnconditionalBranchRegOp(BR, rn); }
-  void blr(Register rn) { EmitUnconditionalBranchRegOp(BLR, rn); }
-  void ret(Register rn = R30) { EmitUnconditionalBranchRegOp(RET, rn); }
+  void blr(Register rn) {
+    // CLOBBERS_LR uses __ to access the assembler.
+#define __ this->
+    CLOBBERS_LR(EmitUnconditionalBranchRegOp(BLR, rn));
+#undef __
+  }
+  void ret(Register rn = kNoRegister2) {
+    if (rn == kNoRegister2) {
+      // READS_RETURN_ADDRESS_FROM_LR uses __ to access the assembler.
+#define __ this->
+      READS_RETURN_ADDRESS_FROM_LR(rn = LR);
+#undef __
+    }
+    EmitUnconditionalBranchRegOp(RET, rn);
+  }
 
   // Breakpoint.
   void brk(uint16_t imm) { EmitExceptionGenOp(BRK, imm); }
@@ -1529,8 +1547,13 @@
       CodeEntryKind entry_kind = CodeEntryKind::kNormal);
 
   void Call(Address target) {
-    ldr(LR, target);
-    blr(LR);
+    // CLOBBERS_LR uses __ to access the assembler.
+#define __ this->
+    CLOBBERS_LR({
+      ldr(LR, target);
+      blr(LR);
+    });
+#undef __
   }
   void Call(const Code& code) { BranchLink(code); }
 
@@ -1632,19 +1655,16 @@
   void StoreIntoObject(Register object,
                        const Address& dest,
                        Register value,
-                       CanBeSmi can_value_be_smi = kValueCanBeSmi,
-                       bool lr_reserved = false);
+                       CanBeSmi can_value_be_smi = kValueCanBeSmi);
   void StoreIntoArray(Register object,
                       Register slot,
                       Register value,
-                      CanBeSmi can_value_be_smi = kValueCanBeSmi,
-                      bool lr_reserved = false);
+                      CanBeSmi can_value_be_smi = kValueCanBeSmi);
 
   void StoreIntoObjectOffset(Register object,
                              int32_t offset,
                              Register value,
-                             CanBeSmi can_value_be_smi = kValueCanBeSmi,
-                             bool lr_reserved = false);
+                             CanBeSmi can_value_be_smi = kValueCanBeSmi);
   void StoreIntoObjectNoBarrier(Register object,
                                 const Address& dest,
                                 Register value);
@@ -1669,6 +1689,9 @@
   bool constant_pool_allowed() const { return constant_pool_allowed_; }
   void set_constant_pool_allowed(bool b) { constant_pool_allowed_ = b; }
 
+  compiler::LRState lr_state() const { return lr_state_; }
+  void set_lr_state(compiler::LRState state) { lr_state_ = state; }
+
   intptr_t FindImmediate(int64_t imm);
   bool CanLoadFromObjectPool(const Object& object) const;
   void LoadNativeEntry(Register dst,
@@ -1728,7 +1751,7 @@
 
   void EnterFrame(intptr_t frame_size);
   void LeaveFrame();
-  void Ret() { ret(LR); }
+  void Ret() { ret(); }
 
   // Emit code to transition between generated mode and native mode.
   //
@@ -1931,6 +1954,8 @@
 
   bool constant_pool_allowed_;
 
+  compiler::LRState lr_state_ = compiler::LRState::OnEntry();
+
   void LoadWordFromPoolIndexFixed(Register dst, intptr_t index);
 
   // Note: the function never clobbers TMP, TMP2 scratch registers.
@@ -2212,6 +2237,7 @@
       } else {
         EmitConditionalBranchOp(op, cond, dest);
       }
+      label->UpdateLRState(lr_state());
     } else {
       const int64_t position = buffer_.Size();
       if (use_far_branches()) {
@@ -2224,7 +2250,7 @@
       } else {
         EmitConditionalBranchOp(op, cond, label->position_);
       }
-      label->LinkTo(position);
+      label->LinkTo(position, lr_state());
     }
   }
 
@@ -2244,6 +2270,7 @@
       } else {
         EmitCompareAndBranchOp(op, rt, dest, sz);
       }
+      label->UpdateLRState(lr_state());
     } else {
       const int64_t position = buffer_.Size();
       if (use_far_branches()) {
@@ -2253,7 +2280,7 @@
       } else {
         EmitCompareAndBranchOp(op, rt, label->position_, sz);
       }
-      label->LinkTo(position);
+      label->LinkTo(position, lr_state());
     }
   }
 
@@ -2273,6 +2300,7 @@
       } else {
         EmitTestAndBranchOp(op, rt, bit_number, dest);
       }
+      label->UpdateLRState(lr_state());
     } else {
       int64_t position = buffer_.Size();
       if (use_far_branches()) {
@@ -2282,7 +2310,7 @@
       } else {
         EmitTestAndBranchOp(op, rt, bit_number, label->position_);
       }
-      label->LinkTo(position);
+      label->LinkTo(position, lr_state());
     }
   }
 
diff --git a/runtime/vm/compiler/assembler/assembler_arm64_test.cc b/runtime/vm/compiler/assembler/assembler_arm64_test.cc
index 8cf9ac2..e469f8e 100644
--- a/runtime/vm/compiler/assembler/assembler_arm64_test.cc
+++ b/runtime/vm/compiler/assembler/assembler_arm64_test.cc
@@ -1901,11 +1901,12 @@
 
 ASSEMBLER_TEST_GENERATE(AdrBlr, assembler) {
   __ movz(R0, Immediate(123), 0);
-  __ add(R3, ZR, Operand(LR));  // Save LR.
+  SPILLS_RETURN_ADDRESS_FROM_LR_TO_REGISTER(
+      __ add(R3, ZR, Operand(LR)));  // Save LR.
   // R1 <- PC + 4*Instr::kInstrSize
   __ adr(R1, Immediate(4 * Instr::kInstrSize));
   __ blr(R1);
-  __ add(LR, ZR, Operand(R3));
+  RESTORES_RETURN_ADDRESS_FROM_REGISTER_TO_LR(__ add(LR, ZR, Operand(R3)));
   __ ret();
 
   // blr goes here.
@@ -4604,12 +4605,12 @@
   __ Push(CODE_REG);
   __ Push(THR);
   __ Push(BARRIER_MASK);
-  __ Push(LR);
+  SPILLS_LR_TO_FRAME(__ Push(LR));
   __ mov(THR, R2);
   __ ldr(BARRIER_MASK, Address(THR, Thread::write_barrier_mask_offset()));
   __ StoreIntoObject(R1, FieldAddress(R1, GrowableObjectArray::data_offset()),
                      R0);
-  __ Pop(LR);
+  RESTORES_LR_FROM_FRAME(__ Pop(LR));
   __ Pop(BARRIER_MASK);
   __ Pop(THR);
   __ Pop(CODE_REG);
diff --git a/runtime/vm/compiler/assembler/assembler_arm_test.cc b/runtime/vm/compiler/assembler/assembler_arm_test.cc
index 9199d5a..0addf7b 100644
--- a/runtime/vm/compiler/assembler/assembler_arm_test.cc
+++ b/runtime/vm/compiler/assembler/assembler_arm_test.cc
@@ -66,7 +66,7 @@
 
 ASSEMBLER_TEST_GENERATE(Simple, assembler) {
   __ mov(R0, Operand(42));
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Simple, test) {
@@ -76,7 +76,7 @@
 
 ASSEMBLER_TEST_GENERATE(MoveNegated, assembler) {
   __ mvn(R0, Operand(42));
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(MoveNegated, test) {
@@ -91,7 +91,7 @@
   __ mov(R0, o);
   EXPECT(Operand::CanHold(0x30000003, &o));
   __ add(R0, R0, o);
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(MoveRotImm, test) {
@@ -102,7 +102,7 @@
 
 ASSEMBLER_TEST_GENERATE(MovImm16, assembler) {
   __ LoadPatchableImmediate(R0, 0x12345678);
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(MovImm16, test) {
@@ -116,7 +116,7 @@
   __ cmp(R0, Operand(0));
   __ LoadImmediate(R0, 0x12345678, EQ);
   __ LoadImmediate(R0, 0x87654321, NE);
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(LoadImmediate, test) {
@@ -128,7 +128,7 @@
 ASSEMBLER_TEST_GENERATE(LoadHalfWordUnaligned, assembler) {
   __ LoadHalfWordUnaligned(R1, R0, TMP);
   __ mov(R0, Operand(R1));
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(LoadHalfWordUnaligned, test) {
@@ -151,7 +151,7 @@
 ASSEMBLER_TEST_GENERATE(LoadHalfWordUnsignedUnaligned, assembler) {
   __ LoadHalfWordUnsignedUnaligned(R1, R0, TMP);
   __ mov(R0, Operand(R1));
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(LoadHalfWordUnsignedUnaligned, test) {
@@ -173,7 +173,7 @@
   __ LoadImmediate(R1, 0xABCD);
   __ StoreWordUnaligned(R1, R0, TMP);
   __ mov(R0, Operand(R1));
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(StoreHalfWordUnaligned, test) {
@@ -201,7 +201,7 @@
 ASSEMBLER_TEST_GENERATE(LoadWordUnaligned, assembler) {
   __ LoadWordUnaligned(R1, R0, TMP);
   __ mov(R0, Operand(R1));
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(LoadWordUnaligned, test) {
@@ -231,7 +231,7 @@
   __ LoadImmediate(R1, 0x12345678);
   __ StoreWordUnaligned(R1, R0, TMP);
   __ mov(R0, Operand(R1));
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(StoreWordUnaligned, test) {
@@ -286,7 +286,7 @@
     __ vmovrrd(R0, R1, D3);       // R0:R1 = D3, R0:R1 == 41:43
     __ sub(R0, R1, Operand(R0));  // 43-41
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vmov, test) {
@@ -307,7 +307,7 @@
     __ vstrs(S0, Address(R2, (-target::kWordSize * 30)));
     __ ldr(R0, Address(SP, (target::kWordSize * 30), Address::PostIndex));
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(SingleVLoadStore, test) {
@@ -334,7 +334,7 @@
     // as:
     __ ldr(R0, Address(SP, R1, LSL, 5, Address::PostIndex));
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(SingleVShiftLoadStore, test) {
@@ -360,7 +360,7 @@
     __ ldr(R1, Address(R2, (-target::kWordSize * 29)));
     __ ldr(R0, Address(SP, (target::kWordSize * 30), Address::PostIndex));
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(DoubleVLoadStore, test) {
@@ -384,7 +384,7 @@
     __ vdivs(S0, S0, S1);  // 14.7f
     __ vsqrts(S0, S0);     // 3.8340579f
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(SingleFPOperations, test) {
@@ -408,7 +408,7 @@
     __ vdivd(D0, D0, D1);  // 14.7
     __ vsqrtd(D0, D0);     // 3.8340579
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(DoubleFPOperations, test) {
@@ -447,7 +447,7 @@
     __ vmovsr(S3, R3);
     __ vcvtdi(D0, S3);
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(IntToDoubleConversion, test) {
@@ -472,7 +472,7 @@
     __ LoadDImmediate(D2, 1.0 * (1LL << 32), R0);
     __ vmlad(D0, D1, D2);
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(LongToDoubleConversion, test) {
@@ -491,7 +491,7 @@
     __ vmovsr(S3, R3);
     __ vcvtsi(S0, S3);
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(IntToFloatConversion, test) {
@@ -509,7 +509,7 @@
     __ vcvtis(S1, S0);
     __ vmovrs(R0, S1);
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(FloatToIntConversion, test) {
@@ -530,7 +530,7 @@
     __ vcvtid(S0, D0);
     __ vmovrs(R0, S0);
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(DoubleToIntConversion, test) {
@@ -551,7 +551,7 @@
     __ LoadSImmediate(S2, 12.8f);
     __ vcvtds(D0, S2);
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(FloatToDoubleConversion, test) {
@@ -569,7 +569,7 @@
     __ LoadDImmediate(D1, 12.8, R0);
     __ vcvtsd(S0, D1);
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(DoubleToFloatConversion, test) {
@@ -607,7 +607,7 @@
     __ add(R0, R0, Operand(16), VC);
   }
   // R0 is 0 if all tests passed.
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(FloatCompare, test) {
@@ -643,7 +643,7 @@
     __ add(R0, R0, Operand(16), VC);
   }
   // R0 is 0 if all tests passed.
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(DoubleCompare, test) {
@@ -662,7 +662,7 @@
   __ mov(R0, Operand(R0, LSL, 1));
   __ movs(R1, Operand(R1, LSR, 1));
   __ b(&loop_entry, NE);
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Loop, test) {
@@ -677,7 +677,7 @@
   __ b(&skip);
   __ mov(R0, Operand(11));
   __ Bind(&skip);
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(ForwardBranch, test) {
@@ -695,7 +695,7 @@
   __ mov(R0, Operand(R0, LSL, 1));
   __ movs(R1, Operand(R1, LSR, 1));
   __ b(&loop_entry, NE);
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Loop2, test) {
@@ -716,7 +716,7 @@
   __ mov(R0, Operand(R0, LSL, 1));
   __ movs(R1, Operand(R1, LSR, 1));
   __ b(&loop_entry, NE);
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Loop3, test) {
@@ -729,7 +729,7 @@
   __ mov(R1, Operand(123));
   __ Push(R1);
   __ Pop(R0);
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(LoadStore, test) {
@@ -744,7 +744,7 @@
   __ PushRegisterPair(R2, R3);
   __ Pop(R0);
   __ Pop(R1);
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(PushRegisterPair, test) {
@@ -759,7 +759,7 @@
   __ PushRegisterPair(R3, R2);
   __ Pop(R0);
   __ Pop(R1);
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(PushRegisterPairReversed, test) {
@@ -775,7 +775,7 @@
   __ Push(R3);
   __ Push(R2);
   __ PopRegisterPair(R0, R1);
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(PopRegisterPair, test) {
@@ -790,7 +790,7 @@
   __ Push(R3);
   __ Push(R2);
   __ PopRegisterPair(R1, R0);
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(PopRegisterPairReversed, test) {
@@ -811,7 +811,7 @@
   __ tst(IP, Operand(0));
   __ b(&retry, NE);  // NE if context switch occurred between ldrex and strex.
   __ Pop(R0);        // 42
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Semaphore, test) {
@@ -829,7 +829,7 @@
   __ strex(IP, R1, SP);  // IP == 1, failure
   __ Pop(R0);            // 40
   __ add(R0, R0, Operand(IP));
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(FailedSemaphore, test) {
@@ -844,7 +844,7 @@
   __ add(R0, R1, Operand(4));
   __ rsbs(R0, R0, Operand(100));
   __ rsc(R0, R0, Operand(100));
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(AddSub, test) {
@@ -859,7 +859,7 @@
   __ mov(R0, Operand(0));
   __ adds(R2, R2, Operand(R1));
   __ adcs(R0, R0, Operand(R0));
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(AddCarry, test) {
@@ -875,7 +875,7 @@
   __ adds(IP, R2, Operand(R1));  // c_out = 1.
   __ adcs(IP, R2, Operand(R0));  // c_in = 1, c_out = 1.
   __ adc(R0, R0, Operand(R0));   // c_in = 1.
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(AddCarryInOut, test) {
@@ -890,7 +890,7 @@
   __ mov(R0, Operand(0));
   __ subs(R2, R2, Operand(R1));
   __ sbcs(R0, R0, Operand(R0));
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(SubCarry, test) {
@@ -905,7 +905,7 @@
   __ subs(IP, R0, Operand(R1));  // c_out = 1.
   __ sbcs(IP, R0, Operand(R0));  // c_in = 1, c_out = 1.
   __ sbc(R0, R0, Operand(R0));   // c_in = 1.
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(SubCarryInOut, test) {
@@ -920,7 +920,7 @@
   __ adds(IP, R0, Operand(1));  // c_out = 1.
   __ adcs(IP, R1, Operand(0));  // c_in = 1, c_out = 1, v = 1.
   __ mov(R0, Operand(1), VS);
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Overflow, test) {
@@ -935,7 +935,7 @@
   __ and_(R1, R2, Operand(R1));
   __ mov(R3, Operand(42));
   __ orr(R0, R1, Operand(R3));
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(AndOrr, test) {
@@ -948,11 +948,11 @@
   __ mov(R0, Operand(0));
   __ tst(R0, Operand(R1));      // Set zero-flag.
   __ orrs(R0, R0, Operand(1));  // Clear zero-flag.
-  __ bx(LR, EQ);
+  __ Ret(EQ);
   __ mov(R0, Operand(42));
-  __ bx(LR, NE);  // Only this return should fire.
+  __ Ret(NE);  // Only this return should fire.
   __ mov(R0, Operand(2));
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Orrs, test) {
@@ -966,7 +966,7 @@
   __ mov(R2, Operand(40));
   __ mul(R3, R2, R1);
   __ mov(R0, Operand(R3));
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Multiply, test) {
@@ -986,7 +986,7 @@
     __ vmovrs(R1, S0);       // r1 = r0/r2
     __ mls(R0, R1, R2, R0);  // r0 = r0 - r1*r2
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(QuotientRemainder, test) {
@@ -1008,7 +1008,7 @@
   __ mla(R2, IP, R3, R4);
   __ add(R1, R2, Operand(R1));
   __ Pop(R4);
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Multiply64To64, test) {
@@ -1021,7 +1021,7 @@
 
 ASSEMBLER_TEST_GENERATE(Multiply32To64, assembler) {
   __ smull(R0, R1, R0, R2);
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Multiply32To64, test) {
@@ -1034,7 +1034,7 @@
 
 ASSEMBLER_TEST_GENERATE(MultiplyAccumAccum32To64, assembler) {
   __ umaal(R0, R1, R2, R3);
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(MultiplyAccumAccum32To64, test) {
@@ -1066,10 +1066,10 @@
   __ cmp(R1, Operand(3));
   __ b(&error, NE);
   __ mov(R0, Operand(0));
-  __ bx(LR);
+  __ Ret();
   __ Bind(&error);
   __ mov(R0, Operand(1));
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Clz, test) {
@@ -1081,7 +1081,7 @@
 ASSEMBLER_TEST_GENERATE(Rbit, assembler) {
   __ mov(R0, Operand(0x15));
   __ rbit(R0, R0);
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Rbit, test) {
@@ -1100,7 +1100,7 @@
   __ b(&skip, NE);
   __ mov(R0, Operand(0));
   __ Bind(&skip);
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Tst, test) {
@@ -1116,7 +1116,7 @@
   __ mov(R0, Operand(R0, LSL, 1));
   __ mov(R1, Operand(1));
   __ mov(R0, Operand(R0, LSL, R1));
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Lsl, test) {
@@ -1132,7 +1132,7 @@
   __ mov(R0, Operand(R0, LSR, 1));
   __ mov(R1, Operand(1));
   __ mov(R0, Operand(R0, LSR, R1));
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Lsr, test) {
@@ -1147,7 +1147,7 @@
   __ mov(R0, Operand(1));
   __ Lsl(R0, R0, Operand(31));
   __ Lsr(R0, R0, Operand(31));
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Lsr1, test) {
@@ -1162,7 +1162,7 @@
   __ mov(R0, Operand(1));
   __ Lsl(R0, R0, Operand(31));
   __ Asr(R0, R0, Operand(31));
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Asr1, test) {
@@ -1174,7 +1174,7 @@
 ASSEMBLER_TEST_GENERATE(Rsb, assembler) {
   __ mov(R3, Operand(10));
   __ rsb(R0, R3, Operand(42));
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Rsb, test) {
@@ -1218,7 +1218,7 @@
   __ mov(R0, Operand(0));
   __ Bind(&Done);
   __ ldr(R1, Address(SP, (target::kWordSize * 30), Address::PostIndex));
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Ldrh, test) {
@@ -1233,7 +1233,7 @@
   __ str(R1, Address(SP, (-target::kWordSize * 30), Address::PreIndex));
   __ ldrsb(R0, Address(R2, (-target::kWordSize * 30)));
   __ ldr(R1, Address(SP, (target::kWordSize * 30), Address::PostIndex));
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Ldrsb, test) {
@@ -1248,7 +1248,7 @@
   __ str(R1, Address(SP, (-target::kWordSize * 30), Address::PreIndex));
   __ ldrb(R0, Address(R2, (-target::kWordSize * 30)));
   __ ldr(R1, Address(SP, (target::kWordSize * 30), Address::PostIndex));
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Ldrb, test) {
@@ -1263,7 +1263,7 @@
   __ str(R1, Address(SP, (-target::kWordSize * 30), Address::PreIndex));
   __ ldrsh(R0, Address(R2, (-target::kWordSize * 30)));
   __ ldr(R1, Address(SP, (target::kWordSize * 30), Address::PostIndex));
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Ldrsh, test) {
@@ -1278,7 +1278,7 @@
   __ str(R1, Address(SP, (-target::kWordSize * 30), Address::PreIndex));
   __ ldrh(R0, Address(R2, (-target::kWordSize * 30)));
   __ ldr(R1, Address(SP, (target::kWordSize * 30), Address::PostIndex));
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Ldrh1, test) {
@@ -1297,7 +1297,7 @@
   __ add(SP, SP, Operand(target::kWordSize * 30));
   __ sub(R0, R0, Operand(R2));
   __ add(R1, R1, Operand(R3));
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Ldrd, test) {
@@ -1340,7 +1340,7 @@
   __ Pop(R9);                   // Restore R9.
   __ Pop(R9);                   // Restore R9.
   __ Pop(R9);                   // Restore R9.
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Ldm_stm_da, test) {
@@ -1354,7 +1354,7 @@
   __ mov(R1, Operand(target::kWordSize));
   __ str(R2, Address(SP, R1, LSL, 1, Address::NegOffset));
   __ ldr(R0, Address(SP, (-target::kWordSize * 2), Address::Offset));
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(AddressShiftStrLSL1NegOffset, test) {
@@ -1368,7 +1368,7 @@
   __ mov(R1, Operand(target::kWordSize));
   __ str(R2, Address(SP, (-target::kWordSize * 32), Address::Offset));
   __ ldr(R0, Address(SP, R1, LSL, 5, Address::NegOffset));
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(AddressShiftLdrLSL5NegOffset, test) {
@@ -1382,7 +1382,7 @@
   __ mov(R1, Operand(target::kWordSize * 2));
   __ str(R2, Address(SP, R1, LSR, 1, Address::NegOffset));
   __ ldr(R0, Address(SP, -target::kWordSize, Address::Offset));
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(AddressShiftStrLRS1NegOffset, test) {
@@ -1396,7 +1396,7 @@
   __ mov(R1, Operand(target::kWordSize * 2));
   __ str(R2, Address(SP, -target::kWordSize, Address::Offset));
   __ ldr(R0, Address(SP, R1, LSR, 1, Address::NegOffset));
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(AddressShiftLdrLRS1NegOffset, test) {
@@ -1412,7 +1412,7 @@
   __ str(R2, Address(SP, R1, LSL, 5, Address::NegPreIndex));
   __ ldr(R0, Address(R3, (-target::kWordSize * 32), Address::Offset));
   __ mov(SP, Operand(R3));
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(AddressShiftStrLSLNegPreIndex, test) {
@@ -1426,7 +1426,7 @@
   __ mov(R1, Operand(target::kWordSize));
   __ str(R2, Address(SP, (-target::kWordSize * 32), Address::PreIndex));
   __ ldr(R0, Address(SP, R1, LSL, 5, Address::PostIndex));
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(AddressShiftLdrLSLNegPreIndex, test) {
@@ -1478,7 +1478,7 @@
     __ vmstat();
     __ mov(R0, Operand(0), NE);  // Put failure into R0 if NE
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(VstmdVldmd, test) {
@@ -1532,7 +1532,7 @@
     __ vmstat();
     __ mov(R0, Operand(0), NE);  // Put failure value into R0 if NE
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(VstmsVldms, test) {
@@ -1584,7 +1584,7 @@
     __ vmstat();
     __ mov(R0, Operand(0), NE);  // Put failure into R0 if NE
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(VstmdVldmd1, test) {
@@ -1636,7 +1636,7 @@
     __ vmstat();
     __ mov(R0, Operand(0), NE);  // Put failure value into R0 if NE
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(VstmsVldms1, test) {
@@ -1698,7 +1698,7 @@
     // Restore used callee-saved FPU registers.
     __ vldmd(IA_W, SP, D8, 3);
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(VstmdVldmd_off, test) {
@@ -1754,7 +1754,7 @@
     __ vmstat();
     __ mov(R0, Operand(0), NE);  // Put failure value into R0 if NE
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(VstmsVldms_off, test) {
@@ -1772,7 +1772,7 @@
     __ udiv(R2, R0, R1);
     __ mov(R0, Operand(R2));
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Udiv, test) {
@@ -1790,7 +1790,7 @@
     __ sdiv(R2, R0, R1);
     __ mov(R0, Operand(R2));
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Sdiv, test) {
@@ -1808,7 +1808,7 @@
     __ udiv(R2, R0, R1);
     __ mov(R0, Operand(R2));
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Udiv_zero, test) {
@@ -1826,7 +1826,7 @@
     __ sdiv(R2, R0, R1);
     __ mov(R0, Operand(R2));
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Sdiv_zero, test) {
@@ -1844,7 +1844,7 @@
     __ udiv(R2, R0, R1);
     __ mov(R0, Operand(R2));
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Udiv_corner, test) {
@@ -1862,7 +1862,7 @@
     __ sdiv(R2, R0, R1);
     __ mov(R0, Operand(R2));
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Sdiv_corner, test) {
@@ -1882,14 +1882,14 @@
   __ mov(R1, Operand(9));
   __ IntegerDivide(R0, R0, R1, D0, D1);
   HostCPUFeatures::set_integer_division_supported(orig);
-  __ bx(LR);
+  __ Ret();
 #else
   if (TargetCPUFeatures::can_divide()) {
     __ mov(R0, Operand(27));
     __ mov(R1, Operand(9));
     __ IntegerDivide(R0, R0, R1, D0, D1);
   }
-  __ bx(LR);
+  __ Ret();
 #endif
 }
 
@@ -1921,14 +1921,14 @@
     __ IntegerDivide(R0, R0, R1, D0, D1);
     HostCPUFeatures::set_integer_division_supported(orig);
   }
-  __ bx(LR);
+  __ Ret();
 #else
   if (TargetCPUFeatures::can_divide()) {
     __ mov(R0, Operand(27));
     __ mov(R1, Operand(9));
     __ IntegerDivide(R0, R0, R1, D0, D1);
   }
-  __ bx(LR);
+  __ Ret();
 #endif
 }
 
@@ -1955,7 +1955,7 @@
   __ LoadImmediate(R1, -9);
   __ muls(R2, R0, R1);
   __ mov(R0, Operand(42), MI);
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Muls, test) {
@@ -1994,7 +1994,7 @@
     __ add(R0, R0, Operand(R2));
     __ add(R0, R0, Operand(R3));
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vaddqi8, test) {
@@ -2035,7 +2035,7 @@
     __ add(R0, R0, Operand(R2));
     __ add(R0, R0, Operand(R3));
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vaddqi16, test) {
@@ -2076,7 +2076,7 @@
     __ add(R0, R0, Operand(R2));
     __ add(R0, R0, Operand(R3));
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vaddqi32, test) {
@@ -2105,7 +2105,7 @@
 
     __ add(R0, R0, Operand(R2));
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vaddqi64, test) {
@@ -2140,7 +2140,7 @@
     __ LoadImmediate(R0, 1);
     __ Bind(&fail);
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vshlqu64, test) {
@@ -2175,7 +2175,7 @@
     __ LoadImmediate(R0, 1);
     __ Bind(&fail);
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vshlqi64, test) {
@@ -2225,12 +2225,12 @@
     __ b(&fail, NE);
 
     __ LoadImmediate(R0, 1);
-    __ bx(LR);
+    __ Ret();
 
     __ Bind(&fail);
     __ LoadImmediate(R0, 0);
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Mint_shl_ok, test) {
@@ -2280,12 +2280,12 @@
     __ b(&fail, NE);
 
     __ LoadImmediate(R0, 0);
-    __ bx(LR);
+    __ Ret();
 
     __ Bind(&fail);
     __ LoadImmediate(R0, 1);
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Mint_shl_overflow, test) {
@@ -2326,7 +2326,7 @@
     __ add(R0, R0, Operand(R2));
     __ add(R0, R0, Operand(R3));
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vsubqi8, test) {
@@ -2367,7 +2367,7 @@
     __ add(R0, R0, Operand(R2));
     __ add(R0, R0, Operand(R3));
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vsubqi16, test) {
@@ -2408,7 +2408,7 @@
     __ add(R0, R0, Operand(R2));
     __ add(R0, R0, Operand(R3));
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vsubqi32, test) {
@@ -2437,7 +2437,7 @@
 
     __ add(R0, R0, Operand(R2));
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vsubqi64, test) {
@@ -2478,7 +2478,7 @@
     __ add(R0, R0, Operand(R2));
     __ add(R0, R0, Operand(R3));
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vmulqi8, test) {
@@ -2519,7 +2519,7 @@
     __ add(R0, R0, Operand(R2));
     __ add(R0, R0, Operand(R3));
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vmulqi16, test) {
@@ -2560,7 +2560,7 @@
     __ add(R0, R0, Operand(R2));
     __ add(R0, R0, Operand(R3));
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vmulqi32, test) {
@@ -2591,7 +2591,7 @@
     __ vcvtis(S0, S8);
     __ vmovrs(R0, S0);
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vaddqs, test) {
@@ -2622,7 +2622,7 @@
     __ vcvtis(S0, S8);
     __ vmovrs(R0, S0);
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vsubqs, test) {
@@ -2653,7 +2653,7 @@
     __ vcvtis(S0, S8);
     __ vmovrs(R0, S0);
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vmulqs, test) {
@@ -2688,12 +2688,12 @@
 
     __ LoadImmediate(R0, 0);
     __ CompareImmediate(R2, 1);
-    __ bx(LR, NE);
+    __ Ret(NE);
     __ CompareImmediate(R3, 1);
-    __ bx(LR, NE);
+    __ Ret(NE);
     __ LoadImmediate(R0, 42);
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(VtblX, test) {
@@ -2728,12 +2728,12 @@
 
     __ LoadImmediate(R0, 0);
     __ CompareImmediate(R2, 1);
-    __ bx(LR, NE);
+    __ Ret(NE);
     __ CompareImmediate(R3, 1);
-    __ bx(LR, NE);
+    __ Ret(NE);
     __ LoadImmediate(R0, 42);
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(VtblY, test) {
@@ -2768,12 +2768,12 @@
 
     __ LoadImmediate(R0, 0);
     __ CompareImmediate(R2, 1);
-    __ bx(LR, NE);
+    __ Ret(NE);
     __ CompareImmediate(R3, 1);
-    __ bx(LR, NE);
+    __ Ret(NE);
     __ LoadImmediate(R0, 42);
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(VtblZ, test) {
@@ -2808,12 +2808,12 @@
 
     __ LoadImmediate(R0, 0);
     __ CompareImmediate(R2, 1);
-    __ bx(LR, NE);
+    __ Ret(NE);
     __ CompareImmediate(R3, 1);
-    __ bx(LR, NE);
+    __ Ret(NE);
     __ LoadImmediate(R0, 42);
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(VtblW, test) {
@@ -2852,7 +2852,7 @@
     __ add(R0, R0, Operand(R2));
     __ add(R0, R0, Operand(R3));
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Veorq, test) {
@@ -2891,7 +2891,7 @@
     __ add(R0, R0, Operand(R2));
     __ add(R0, R0, Operand(R3));
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vornq, test) {
@@ -2930,7 +2930,7 @@
     __ add(R0, R0, Operand(R2));
     __ add(R0, R0, Operand(R3));
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vorrq, test) {
@@ -2969,7 +2969,7 @@
     __ add(R0, R0, Operand(R2));
     __ add(R0, R0, Operand(R3));
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vandq, test) {
@@ -3003,7 +3003,7 @@
     __ vcvtis(S0, S4);
     __ vmovrs(R0, S0);
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vmovq, test) {
@@ -3022,7 +3022,7 @@
     __ vmvnq(Q2, Q1);          // Q2 <- ~Q1.
     __ vmovrs(R0, S10);        // Now R0 should be 42 again.
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vmvnq, test) {
@@ -3052,7 +3052,7 @@
     __ add(R0, R0, Operand(R2));
     __ add(R0, R0, Operand(R3));
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vdupb, test) {
@@ -3082,7 +3082,7 @@
     __ add(R0, R0, Operand(R2));
     __ add(R0, R0, Operand(R3));
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vduph, test) {
@@ -3112,7 +3112,7 @@
     __ add(R0, R0, Operand(R2));
     __ add(R0, R0, Operand(R3));
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vdupw, test) {
@@ -3142,7 +3142,7 @@
     __ vadds(S0, S0, S2);
     __ vadds(S0, S0, S3);
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vzipqw, test) {
@@ -3184,7 +3184,7 @@
     __ add(R0, R0, Operand(R2));
     __ add(R0, R0, Operand(R3));
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vceqqi32, test) {
@@ -3217,7 +3217,7 @@
     __ add(R0, R0, Operand(R2));
     __ add(R0, R0, Operand(R3));
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vceqqs, test) {
@@ -3258,7 +3258,7 @@
     __ add(R0, R0, Operand(R2));
     __ add(R0, R0, Operand(R3));
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vcgeqi32, test) {
@@ -3299,7 +3299,7 @@
     __ add(R0, R0, Operand(R2));
     __ add(R0, R0, Operand(R3));
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vcugeqi32, test) {
@@ -3332,7 +3332,7 @@
     __ add(R0, R0, Operand(R2));
     __ add(R0, R0, Operand(R3));
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vcgeqs, test) {
@@ -3373,7 +3373,7 @@
     __ add(R0, R0, Operand(R2));
     __ add(R0, R0, Operand(R3));
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vcgtqi32, test) {
@@ -3414,7 +3414,7 @@
     __ add(R0, R0, Operand(R2));
     __ add(R0, R0, Operand(R3));
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vcugtqi32, test) {
@@ -3447,7 +3447,7 @@
     __ add(R0, R0, Operand(R2));
     __ add(R0, R0, Operand(R3));
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vcgtqs, test) {
@@ -3479,7 +3479,7 @@
     __ vcvtis(S0, S8);
     __ vmovrs(R0, S0);
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vminqs, test) {
@@ -3511,7 +3511,7 @@
     __ vcvtis(S0, S8);
     __ vmovrs(R0, S0);
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vmaxqs, test) {
@@ -3530,7 +3530,7 @@
     __ vmovs(S7, S4);
     __ vrecpeqs(Q0, Q1);
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vrecpeqs, test) {
@@ -3556,7 +3556,7 @@
 
     __ vrecpsqs(Q0, Q1, Q2);
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vrecpsqs, test) {
@@ -3583,7 +3583,7 @@
     __ vrecpsqs(Q2, Q1, Q0);
     __ vmulqs(Q0, Q0, Q2);
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Reciprocal, test) {
@@ -3604,7 +3604,7 @@
 
     __ vrsqrteqs(Q0, Q1);
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vrsqrteqs, test) {
@@ -3630,7 +3630,7 @@
 
     __ vrsqrtsqs(Q0, Q1, Q2);
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vrsqrtsqs, test) {
@@ -3661,7 +3661,7 @@
     __ vrsqrtsqs(Q2, Q1, Q2);
     __ vmulqs(Q0, Q0, Q2);
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(ReciprocalSqrt, test) {
@@ -3702,7 +3702,7 @@
     __ vrecpsqs(Q2, Q1, Q0);
     __ vmulqs(Q0, Q0, Q2);
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(SIMDSqrt, test) {
@@ -3747,7 +3747,7 @@
     __ vadds(S0, S0, S2);
     __ vadds(S0, S0, S3);
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(SIMDSqrt2, test) {
@@ -3784,7 +3784,7 @@
     __ vadds(S0, S0, S2);
     __ vadds(S0, S0, S3);
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(SIMDDiv, test) {
@@ -3809,7 +3809,7 @@
     __ vadds(S0, S0, S2);
     __ vadds(S0, S0, S3);
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vabsqs, test) {
@@ -3834,7 +3834,7 @@
     __ vadds(S0, S0, S2);
     __ vadds(S0, S0, S3);
   }
-  __ bx(LR);
+  __ Ret();
 }
 
 ASSEMBLER_TEST_RUN(Vnegqs, test) {
@@ -3852,11 +3852,11 @@
 // R1: growable array.
 // R2: current thread.
 ASSEMBLER_TEST_GENERATE(StoreIntoObject, assembler) {
-  __ PushList((1 << LR) | (1 << THR));
+  SPILLS_LR_TO_FRAME(__ PushList((1 << LR) | (1 << THR)));
   __ mov(THR, Operand(R2));
   __ StoreIntoObject(R1, FieldAddress(R1, GrowableObjectArray::data_offset()),
                      R0);
-  __ PopList((1 << LR) | (1 << THR));
+  RESTORES_LR_FROM_FRAME(__ PopList((1 << LR) | (1 << THR)));
   __ Ret();
 }
 
diff --git a/runtime/vm/compiler/assembler/assembler_base.h b/runtime/vm/compiler/assembler/assembler_base.h
index d240da5..a650f43 100644
--- a/runtime/vm/compiler/assembler/assembler_base.h
+++ b/runtime/vm/compiler/assembler/assembler_base.h
@@ -28,6 +28,152 @@
 
 namespace compiler {
 
+#if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
+// On ARM and ARM64 branch-link family of instructions puts return address
+// into a dedicated register (LR), which called code will then preserve
+// manually if needed. To ensure that LR is not clobbered accidentally we
+// discourage direct use of the register and instead require users to wrap
+// their code in one of the macroses below, which would verify that it is
+// safe to modify LR.
+// We use RELEASE_ASSERT instead of ASSERT because we use LR state (tracked
+// by the assembler) to generate different code sequences for write barriers
+// so we would like to ensure that incorrect code will trigger an assertion
+// instead of producing incorrect code.
+
+// Class representing the state of LR register. In addition to tracking
+// whether LR currently contain return address or not it also tracks
+// entered frames - and whether they preserved a return address or not.
+class LRState {
+ public:
+  LRState(const LRState&) = default;
+  LRState& operator=(const LRState&) = default;
+
+  bool LRContainsReturnAddress() const {
+    RELEASE_ASSERT(!IsUnknown());
+    return (state_ & kLRContainsReturnAddressMask) != 0;
+  }
+
+  LRState SetLRContainsReturnAddress(bool v) const {
+    RELEASE_ASSERT(!IsUnknown());
+    return LRState(frames_, v ? (state_ | 1) : (state_ & ~1));
+  }
+
+  // Returns a |LRState| representing a state after pushing current value
+  // of LR on the stack. LR is assumed clobberable in the new state.
+  LRState EnterFrame() const {
+    RELEASE_ASSERT(!IsUnknown());
+    // 1 bit is used for LR state the rest for frame states.
+    constexpr auto kMaxFrames = (sizeof(state_) * kBitsPerByte) - 1;
+    RELEASE_ASSERT(frames_ < kMaxFrames);
+    // LSB will be clear after the shift meaning that LR can be clobbered.
+    return LRState(frames_ + 1, state_ << 1);
+  }
+
+  // Returns a |LRState| representing a state after popping  LR from the stack.
+  // Note that for inner frames LR would usually be assumed cloberrable
+  // even after leaving a frame. Only outerframe would restore return address
+  // into LR.
+  LRState LeaveFrame() const {
+    RELEASE_ASSERT(!IsUnknown());
+    RELEASE_ASSERT(frames_ > 0);
+    return LRState(frames_ - 1, state_ >> 1);
+  }
+
+  bool IsUnknown() const { return *this == Unknown(); }
+
+  static LRState Unknown() { return LRState(kUnknownMarker, kUnknownMarker); }
+
+  static LRState OnEntry() { return LRState(0, 1); }
+
+  static LRState Clobbered() { return LRState(0, 0); }
+
+  bool operator==(const LRState& other) const {
+    return frames_ == other.frames_ && state_ == other.state_;
+  }
+
+ private:
+  LRState(uint8_t frames, uint8_t state) : frames_(frames), state_(state) {}
+
+  // LR state is encoded in the LSB of state_ bitvector.
+  static constexpr uint8_t kLRContainsReturnAddressMask = 1;
+
+  static constexpr uint8_t kUnknownMarker = 0xFF;
+
+  // Number of frames on the stack or kUnknownMarker when representing
+  // Unknown state.
+  uint8_t frames_ = 0;
+
+  // Bit vector with frames_ + 1 bits: LSB represents LR state, other bits
+  // represent state of LR in each entered frame. Normally this value would
+  // just be (1 << frames_).
+  uint8_t state_ = 1;
+};
+
+// READS_RETURN_ADDRESS_FROM_LR(...) macro verifies that LR contains return
+// address before allowing to use it.
+#define READS_RETURN_ADDRESS_FROM_LR(block)                                    \
+  do {                                                                         \
+    RELEASE_ASSERT(__ lr_state().LRContainsReturnAddress());                   \
+    constexpr Register LR = LR_DO_NOT_USE_DIRECTLY;                            \
+    USE(LR);                                                                   \
+    block;                                                                     \
+  } while (0)
+
+// WRITES_RETURN_ADDRESS_TO_LR(...) macro verifies that LR contains return
+// address before allowing to write into it. LR is considered to still
+// contain return address after this operation.
+#define WRITES_RETURN_ADDRESS_TO_LR(block) READS_RETURN_ADDRESS_FROM_LR(block)
+
+// CLOBBERS_LR(...) checks that LR does *not* contain return address and it is
+// safe to clobber it.
+#define CLOBBERS_LR(block)                                                     \
+  do {                                                                         \
+    RELEASE_ASSERT(!(__ lr_state().LRContainsReturnAddress()));                \
+    constexpr Register LR = LR_DO_NOT_USE_DIRECTLY;                            \
+    USE(LR);                                                                   \
+    block;                                                                     \
+  } while (0)
+
+// SPILLS_RETURN_ADDRESS_FROM_LR_TO_REGISTER(...) checks that LR contains return
+// address, executes |block| and marks that LR can be safely clobbered
+// afterwards (assuming that |block| moved LR value onto into another register).
+#define SPILLS_RETURN_ADDRESS_FROM_LR_TO_REGISTER(block)                       \
+  do {                                                                         \
+    READS_RETURN_ADDRESS_FROM_LR(block);                                       \
+    __ set_lr_state(__ lr_state().SetLRContainsReturnAddress(false));          \
+  } while (0)
+
+// RESTORES_RETURN_ADDRESS_FROM_REGISTER_TO_LR(...) checks that LR does not
+// contain return address, executes |block| and marks LR as containing return
+// address (assuming that |block| restored LR value from another register).
+#define RESTORES_RETURN_ADDRESS_FROM_REGISTER_TO_LR(block)                     \
+  do {                                                                         \
+    CLOBBERS_LR(block);                                                        \
+    __ set_lr_state(__ lr_state().SetLRContainsReturnAddress(true));           \
+  } while (0)
+
+// SPILLS_LR_TO_FRAME(...) executes |block| and updates tracked LR state to
+// record that we entered a frame which preserved LR. LR can be clobbered
+// afterwards.
+#define SPILLS_LR_TO_FRAME(block)                                              \
+  do {                                                                         \
+    constexpr Register LR = LR_DO_NOT_USE_DIRECTLY;                            \
+    USE(LR);                                                                   \
+    block;                                                                     \
+    __ set_lr_state(__ lr_state().EnterFrame());                               \
+  } while (0)
+
+// RESTORE_LR(...) checks that LR does not contain return address, executes
+// |block| and updates tracked LR state to record that we exited a frame.
+// Whether LR contains return address or not after this operation depends on
+// the frame state (only the outermost frame usually restores LR).
+#define RESTORES_LR_FROM_FRAME(block)                                          \
+  do {                                                                         \
+    CLOBBERS_LR(block);                                                        \
+    __ set_lr_state(__ lr_state().LeaveFrame());                               \
+  } while (0)
+#endif  // defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
+
 enum OperandSize {
   // Architecture-independent constants.
   kByte,
@@ -106,20 +252,51 @@
   intptr_t position_;
   intptr_t unresolved_;
   intptr_t unresolved_near_positions_[kMaxUnresolvedBranches];
+#if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
+  // On ARM/ARM64 we track LR state: whether it contains return address or
+  // whether it can be clobbered. To make sure that our tracking it correct
+  // for non linear code sequences we additionally verify at labels that
+  // incomming states are compatible.
+  LRState lr_state_ = LRState::Unknown();
+
+  void UpdateLRState(LRState new_state) {
+    if (lr_state_.IsUnknown()) {
+      lr_state_ = new_state;
+    } else {
+      RELEASE_ASSERT(lr_state_ == new_state);
+    }
+  }
+#endif  // defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
 
   void Reinitialize() { position_ = 0; }
 
-  void BindTo(intptr_t position) {
+#if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
+  void BindTo(intptr_t position, LRState lr_state)
+#else
+  void BindTo(intptr_t position)
+#endif  // defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
+  {
     ASSERT(!IsBound());
     ASSERT(!HasNear());
     position_ = -position - kBias;
     ASSERT(IsBound());
+#if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
+    UpdateLRState(lr_state);
+#endif  // defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
   }
 
-  void LinkTo(intptr_t position) {
+#if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
+  void LinkTo(intptr_t position, LRState lr_state)
+#else
+  void LinkTo(intptr_t position)
+#endif  // defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
+  {
     ASSERT(!IsBound());
     position_ = position + kBias;
     ASSERT(IsLinked());
+#if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
+    UpdateLRState(lr_state);
+#endif  // defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
   }
 
   void NearLinkTo(intptr_t position) {
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler.cc b/runtime/vm/compiler/backend/flow_graph_compiler.cc
index 213bb7a..1f084c1 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler.cc
+++ b/runtime/vm/compiler/backend/flow_graph_compiler.cc
@@ -67,6 +67,34 @@
 DECLARE_FLAG(int, gc_every);
 DECLARE_FLAG(bool, trace_compiler);
 
+#if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
+compiler::LRState ComputeInnerLRState(const FlowGraph& flow_graph) {
+  auto entry = flow_graph.graph_entry();
+  const bool frameless = !entry->NeedsFrame();
+
+  bool has_native_entries = false;
+  for (intptr_t i = 0; i < entry->SuccessorCount(); i++) {
+    if (entry->SuccessorAt(i)->IsNativeEntry()) {
+      has_native_entries = true;
+      break;
+    }
+  }
+
+  auto state = compiler::LRState::OnEntry();
+  if (has_native_entries) {
+    // We will setup three (3) frames on the stack when entering through
+    // native entry. Keep in sync with NativeEntry/NativeReturn.
+    state = state.EnterFrame().EnterFrame();
+  }
+
+  if (!frameless) {
+    state = state.EnterFrame();
+  }
+
+  return state;
+}
+#endif
+
 // Assign locations to incoming arguments, i.e., values pushed above spill slots
 // with PushArgument.  Recursively allocates from outermost to innermost
 // environment.
@@ -553,6 +581,10 @@
     ASSERT(block_order()[1] == flow_graph().graph_entry()->normal_entry());
   }
 
+#if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
+  const auto inner_lr_state = ComputeInnerLRState(flow_graph());
+#endif  // defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
+
   for (intptr_t i = 0; i < block_order().length(); ++i) {
     // Compile the block entry.
     BlockEntryInstr* entry = block_order()[i];
@@ -563,6 +595,16 @@
       continue;
     }
 
+#if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
+    // At the start of every non-entry block we expect return address either
+    // to be  spilled into the frame or to be in the LR register.
+    if (entry->IsFunctionEntry() || entry->IsNativeEntry()) {
+      assembler()->set_lr_state(compiler::LRState::OnEntry());
+    } else {
+      assembler()->set_lr_state(inner_lr_state);
+    }
+#endif  // defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
+
 #if defined(DEBUG)
     if (!is_optimizing()) {
       FrameStateClear();
@@ -701,11 +743,18 @@
 }
 
 void FlowGraphCompiler::GenerateDeferredCode() {
+#if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
+  const auto lr_state = ComputeInnerLRState(flow_graph());
+#endif  // defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
+
   for (intptr_t i = 0; i < slow_path_code_.length(); i++) {
     SlowPathCode* const slow_path = slow_path_code_[i];
     const CombinedCodeStatistics::EntryCounter stats_tag =
         CombinedCodeStatistics::SlowPathCounterFor(
             slow_path->instruction()->tag());
+#if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
+    assembler()->set_lr_state(lr_state);
+#endif  // defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
     set_current_instruction(slow_path->instruction());
     SpecialStatsBegin(stats_tag);
     BeginCodeSourceRange();
@@ -718,6 +767,9 @@
   }
   for (intptr_t i = 0; i < deopt_infos_.length(); i++) {
     BeginCodeSourceRange();
+#if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
+    assembler()->set_lr_state(lr_state);
+#endif  // defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
     deopt_infos_[i]->GenerateCode(this, i);
     EndCodeSourceRange(TokenPosition::kDeferredDeoptInfo);
   }
@@ -1277,6 +1329,8 @@
 }
 
 bool FlowGraphCompiler::TryIntrinsifyHelper() {
+  ASSERT(!flow_graph().IsCompiledForOsr());
+
   compiler::Label exit;
   set_intrinsic_slow_path_label(&exit);
 
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler.h b/runtime/vm/compiler/backend/flow_graph_compiler.h
index ca9a298..cebc344 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler.h
+++ b/runtime/vm/compiler/backend/flow_graph_compiler.h
@@ -461,7 +461,9 @@
   // speculative optimizations and call patching are disabled.
   bool ForcedOptimization() const { return function().ForceOptimize(); }
 
-  const FlowGraph& flow_graph() const { return flow_graph_; }
+  const FlowGraph& flow_graph() const {
+    return intrinsic_mode() ? *intrinsic_flow_graph_ : flow_graph_;
+  }
 
   BlockEntryInstr* current_block() const { return current_block_; }
   void set_current_block(BlockEntryInstr* value) { current_block_ = value; }
@@ -486,6 +488,10 @@
   void ExitIntrinsicMode();
   bool intrinsic_mode() const { return intrinsic_mode_; }
 
+  void set_intrinsic_flow_graph(const FlowGraph& flow_graph) {
+    intrinsic_flow_graph_ = &flow_graph;
+  }
+
   void set_intrinsic_slow_path_label(compiler::Label* label) {
     ASSERT(intrinsic_slow_path_label_ == nullptr || label == nullptr);
     intrinsic_slow_path_label_ = label;
@@ -1195,6 +1201,7 @@
   compiler::Assembler* assembler_;
   const ParsedFunction& parsed_function_;
   const FlowGraph& flow_graph_;
+  const FlowGraph* intrinsic_flow_graph_ = nullptr;
   const GrowableArray<BlockEntryInstr*>& block_order_;
 
 #if defined(DEBUG)
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler_arm.cc b/runtime/vm/compiler/backend/flow_graph_compiler_arm.cc
index 1d85187..31ceee7 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler_arm.cc
+++ b/runtime/vm/compiler/backend/flow_graph_compiler_arm.cc
@@ -198,10 +198,8 @@
   }
 
   ASSERT(deopt_env() != NULL);
-  __ ldr(LR, compiler::Address(
-                 THR, compiler::target::Thread::deoptimize_entry_offset()));
-  __ blx(LR);
-  ASSERT(kReservedCpuRegisters & (1 << LR));
+  __ Call(compiler::Address(
+      THR, compiler::target::Thread::deoptimize_entry_offset()));
   set_pc_offset(assembler->CodeSize());
 #undef __
 }
@@ -309,14 +307,19 @@
                   THR, compiler::target::Thread::optimize_entry_offset()),
               GE);
   }
-  __ Comment("Enter frame");
-  if (flow_graph().IsCompiledForOsr()) {
-    const intptr_t extra_slots = ExtraStackSlotsOnOsrEntry();
-    ASSERT(extra_slots >= 0);
-    __ EnterOsrFrame(extra_slots * compiler::target::kWordSize);
-  } else {
-    ASSERT(StackSize() >= 0);
-    __ EnterDartFrame(StackSize() * compiler::target::kWordSize);
+
+  if (flow_graph().graph_entry()->NeedsFrame()) {
+    __ Comment("Enter frame");
+    if (flow_graph().IsCompiledForOsr()) {
+      const intptr_t extra_slots = ExtraStackSlotsOnOsrEntry();
+      ASSERT(extra_slots >= 0);
+      __ EnterOsrFrame(extra_slots * compiler::target::kWordSize);
+    } else {
+      ASSERT(StackSize() >= 0);
+      __ EnterDartFrame(StackSize() * compiler::target::kWordSize);
+    }
+  } else if (FLAG_use_bare_instructions) {
+    assembler()->set_constant_pool_allowed(true);
   }
 }
 
@@ -526,8 +529,7 @@
       entry_kind == Code::EntryKind::kNormal
           ? Code::entry_point_offset(Code::EntryKind::kMonomorphic)
           : Code::entry_point_offset(Code::EntryKind::kMonomorphicUnchecked);
-  __ ldr(LR, compiler::FieldAddress(CODE_REG, entry_point_offset));
-  __ blx(LR);
+  __ Call(compiler::FieldAddress(CODE_REG, entry_point_offset));
   EmitCallsiteMetadata(token_pos, deopt_id, PcDescriptorsLayout::kIcCall, locs);
   __ Drop(ic_data.SizeWithTypeArgs());
 }
@@ -556,23 +558,22 @@
     if (FLAG_use_bare_instructions) {
       // The AOT runtime will replace the slot in the object pool with the
       // entrypoint address - see clustered_snapshot.cc.
-      __ LoadUniqueObject(LR, StubCode::MegamorphicCall());
+      CLOBBERS_LR(__ LoadUniqueObject(LR, StubCode::MegamorphicCall()));
     } else {
       __ LoadUniqueObject(CODE_REG, StubCode::MegamorphicCall());
-      __ ldr(LR, compiler::FieldAddress(
-                     CODE_REG, compiler::target::Code::entry_point_offset(
-                                   Code::EntryKind::kMonomorphic)));
+      CLOBBERS_LR(
+          __ ldr(LR, compiler::FieldAddress(
+                         CODE_REG, compiler::target::Code::entry_point_offset(
+                                       Code::EntryKind::kMonomorphic))));
     }
     __ LoadUniqueObject(R9, cache);
-    __ blx(LR);
+    CLOBBERS_LR(__ blx(LR));
 
   } else {
     __ LoadUniqueObject(R9, cache);
     __ LoadUniqueObject(CODE_REG, StubCode::MegamorphicCall());
-    __ ldr(LR, compiler::FieldAddress(
-                   CODE_REG,
-                   Code::entry_point_offset(Code::EntryKind::kMonomorphic)));
-    __ blx(LR);
+    __ Call(compiler::FieldAddress(
+        CODE_REG, Code::entry_point_offset(Code::EntryKind::kMonomorphic)));
   }
 
   RecordSafepoint(locs, slow_path_argument_count);
@@ -627,7 +628,7 @@
   if (FLAG_precompiled_mode && FLAG_use_bare_instructions) {
     // The AOT runtime will replace the slot in the object pool with the
     // entrypoint address - see clustered_snapshot.cc.
-    __ LoadUniqueObject(LR, initial_stub);
+    CLOBBERS_LR(__ LoadUniqueObject(LR, initial_stub));
   } else {
     __ LoadUniqueObject(CODE_REG, initial_stub);
     const intptr_t entry_point_offset =
@@ -636,10 +637,11 @@
                   Code::EntryKind::kMonomorphic)
             : compiler::target::Code::entry_point_offset(
                   Code::EntryKind::kMonomorphicUnchecked);
-    __ ldr(LR, compiler::FieldAddress(CODE_REG, entry_point_offset));
+    CLOBBERS_LR(
+        __ ldr(LR, compiler::FieldAddress(CODE_REG, entry_point_offset)));
   }
   __ LoadUniqueObject(R9, data);
-  __ blx(LR);
+  CLOBBERS_LR(__ blx(LR));
 
   EmitCallsiteMetadata(token_pos, DeoptId::kNone, PcDescriptorsLayout::kOther,
                        locs);
@@ -696,20 +698,22 @@
   }
   intptr_t offset = (selector_offset - DispatchTable::OriginElement()) *
                     compiler::target::kWordSize;
-  if (offset == 0) {
-    __ ldr(LR, compiler::Address(DISPATCH_TABLE_REG, cid_reg, LSL,
-                                 compiler::target::kWordSizeLog2));
-  } else {
-    __ add(LR, DISPATCH_TABLE_REG,
-           compiler::Operand(cid_reg, LSL, compiler::target::kWordSizeLog2));
-    if (!Utils::IsAbsoluteUint(12, offset)) {
-      const intptr_t adjust = offset & -(1 << 12);
-      __ AddImmediate(LR, LR, adjust);
-      offset -= adjust;
+  CLOBBERS_LR({
+    if (offset == 0) {
+      __ ldr(LR, compiler::Address(DISPATCH_TABLE_REG, cid_reg, LSL,
+                                   compiler::target::kWordSizeLog2));
+    } else {
+      __ add(LR, DISPATCH_TABLE_REG,
+             compiler::Operand(cid_reg, LSL, compiler::target::kWordSizeLog2));
+      if (!Utils::IsAbsoluteUint(12, offset)) {
+        const intptr_t adjust = offset & -(1 << 12);
+        __ AddImmediate(LR, LR, adjust);
+        offset -= adjust;
+      }
+      __ ldr(LR, compiler::Address(LR, offset));
     }
-    __ ldr(LR, compiler::Address(LR, offset));
-  }
-  __ blx(LR);
+    __ blx(LR);
+  });
 }
 
 Condition FlowGraphCompiler::EmitEqualityRegConstCompare(
@@ -874,16 +878,17 @@
       const intptr_t source_offset = source.ToStackSlotOffset();
       const intptr_t dest_offset = destination.ToStackSlotOffset();
 
-      // LR not used by register allocator.
-      ASSERT(((1 << LR) & kDartAvailableCpuRegs) == 0);
+      CLOBBERS_LR({
+        // LR not used by register allocator.
+        COMPILE_ASSERT(((1 << LR) & kDartAvailableCpuRegs) == 0);
+        // StoreToOffset uses TMP in the case where dest_offset is too large or
+        // small in order to calculate a new base. We fall back to using LR as a
+        // temporary as we know we're in a ParallelMove.
+        const Register temp_reg = LR;
 
-      // StoreToOffset uses TMP in the case where dest_offset is too large or
-      // small in order to calculate a new base. We fall back to using LR as a
-      // temporary as we know we're in a ParallelMove.
-      const Register temp_reg = LR;
-
-      __ LoadFromOffset(temp_reg, source.base_reg(), source_offset);
-      __ StoreToOffset(temp_reg, destination.base_reg(), dest_offset);
+        __ LoadFromOffset(temp_reg, source.base_reg(), source_offset);
+        __ StoreToOffset(temp_reg, destination.base_reg(), dest_offset);
+      });
     }
   } else if (source.IsFpuRegister()) {
     if (destination.IsFpuRegister()) {
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler_arm64.cc b/runtime/vm/compiler/backend/flow_graph_compiler_arm64.cc
index d841daf..668e0e2 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler_arm64.cc
+++ b/runtime/vm/compiler/backend/flow_graph_compiler_arm64.cc
@@ -192,8 +192,7 @@
   }
 
   ASSERT(deopt_env() != NULL);
-  __ ldr(LR, compiler::Address(THR, Thread::deoptimize_entry_offset()));
-  __ blr(LR);
+  __ Call(compiler::Address(THR, Thread::deoptimize_entry_offset()));
   set_pc_offset(assembler->CodeSize());
 #undef __
 }
@@ -300,14 +299,19 @@
     __ br(TMP);
     __ Bind(&dont_optimize);
   }
-  __ Comment("Enter frame");
-  if (flow_graph().IsCompiledForOsr()) {
-    const intptr_t extra_slots = ExtraStackSlotsOnOsrEntry();
-    ASSERT(extra_slots >= 0);
-    __ EnterOsrFrame(extra_slots * kWordSize);
-  } else {
-    ASSERT(StackSize() >= 0);
-    __ EnterDartFrame(StackSize() * kWordSize);
+
+  if (flow_graph().graph_entry()->NeedsFrame()) {
+    __ Comment("Enter frame");
+    if (flow_graph().IsCompiledForOsr()) {
+      const intptr_t extra_slots = ExtraStackSlotsOnOsrEntry();
+      ASSERT(extra_slots >= 0);
+      __ EnterOsrFrame(extra_slots * kWordSize);
+    } else {
+      ASSERT(StackSize() >= 0);
+      __ EnterDartFrame(StackSize() * kWordSize);
+    }
+  } else if (FLAG_use_bare_instructions) {
+    assembler()->set_constant_pool_allowed(true);
   }
 }
 
@@ -515,8 +519,7 @@
       entry_kind == Code::EntryKind::kNormal
           ? Code::entry_point_offset(Code::EntryKind::kMonomorphic)
           : Code::entry_point_offset(Code::EntryKind::kMonomorphicUnchecked);
-  __ ldr(LR, compiler::FieldAddress(CODE_REG, entry_point_offset));
-  __ blr(LR);
+  __ Call(compiler::FieldAddress(CODE_REG, entry_point_offset));
   EmitCallsiteMetadata(token_pos, deopt_id, PcDescriptorsLayout::kIcCall, locs);
   __ Drop(ic_data.SizeWithTypeArgs());
 }
@@ -550,14 +553,14 @@
   if (FLAG_precompiled_mode && FLAG_use_bare_instructions) {
     // The AOT runtime will replace the slot in the object pool with the
     // entrypoint address - see clustered_snapshot.cc.
-    __ LoadDoubleWordFromPoolIndex(R5, LR, data_index);
+    CLOBBERS_LR(__ LoadDoubleWordFromPoolIndex(R5, LR, data_index));
   } else {
     __ LoadDoubleWordFromPoolIndex(R5, CODE_REG, data_index);
-    __ ldr(LR, compiler::FieldAddress(
-                   CODE_REG,
-                   Code::entry_point_offset(Code::EntryKind::kMonomorphic)));
+    CLOBBERS_LR(__ ldr(LR, compiler::FieldAddress(
+                               CODE_REG, Code::entry_point_offset(
+                                             Code::EntryKind::kMonomorphic))));
   }
-  __ blr(LR);
+  CLOBBERS_LR(__ blr(LR));
 
   RecordSafepoint(locs, slow_path_argument_count);
   const intptr_t deopt_id_after = DeoptId::ToDeoptAfter(deopt_id);
@@ -619,7 +622,7 @@
   if (FLAG_precompiled_mode && FLAG_use_bare_instructions) {
     // The AOT runtime will replace the slot in the object pool with the
     // entrypoint address - see clustered_snapshot.cc.
-    __ LoadDoubleWordFromPoolIndex(R5, LR, data_index);
+    CLOBBERS_LR(__ LoadDoubleWordFromPoolIndex(R5, LR, data_index));
   } else {
     __ LoadDoubleWordFromPoolIndex(R5, CODE_REG, data_index);
     const intptr_t entry_point_offset =
@@ -628,9 +631,10 @@
                   Code::EntryKind::kMonomorphic)
             : compiler::target::Code::entry_point_offset(
                   Code::EntryKind::kMonomorphicUnchecked);
-    __ ldr(LR, compiler::FieldAddress(CODE_REG, entry_point_offset));
+    CLOBBERS_LR(
+        __ ldr(LR, compiler::FieldAddress(CODE_REG, entry_point_offset)));
   }
-  __ blr(LR);
+  CLOBBERS_LR(__ blr(LR));
 
   EmitCallsiteMetadata(token_pos, DeoptId::kNone, PcDescriptorsLayout::kOther,
                        locs);
@@ -687,9 +691,8 @@
   }
   const intptr_t offset = selector_offset - DispatchTable::OriginElement();
   __ AddImmediate(cid_reg, cid_reg, offset);
-  __ ldr(LR, compiler::Address(DISPATCH_TABLE_REG, cid_reg, UXTX,
-                               compiler::Address::Scaled));
-  __ blr(LR);
+  __ Call(compiler::Address(DISPATCH_TABLE_REG, cid_reg, UXTX,
+                            compiler::Address::Scaled));
 }
 
 Condition FlowGraphCompiler::EmitEqualityRegConstCompare(
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler_ia32.cc b/runtime/vm/compiler/backend/flow_graph_compiler_ia32.cc
index e0cb95a..0b012e5 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler_ia32.cc
+++ b/runtime/vm/compiler/backend/flow_graph_compiler_ia32.cc
@@ -439,6 +439,8 @@
 // NOTE: If the entry code shape changes, ReturnAddressLocator in profiler.cc
 // needs to be updated to match.
 void FlowGraphCompiler::EmitFrameEntry() {
+  RELEASE_ASSERT(flow_graph().graph_entry()->NeedsFrame());
+
   const Function& function = parsed_function().function();
   if (CanOptimizeFunction() && function.IsOptimizable() &&
       (!is_optimizing() || may_reoptimize())) {
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler_x64.cc b/runtime/vm/compiler/backend/flow_graph_compiler_x64.cc
index aad34de..7905dec 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler_x64.cc
+++ b/runtime/vm/compiler/backend/flow_graph_compiler_x64.cc
@@ -283,6 +283,13 @@
 // NOTE: If the entry code shape changes, ReturnAddressLocator in profiler.cc
 // needs to be updated to match.
 void FlowGraphCompiler::EmitFrameEntry() {
+  if (!flow_graph().graph_entry()->NeedsFrame()) {
+    if (FLAG_use_bare_instructions) {
+      assembler()->set_constant_pool_allowed(true);
+    }
+    return;
+  }
+
   if (flow_graph().IsCompiledForOsr()) {
     const intptr_t extra_slots = ExtraStackSlotsOnOsrEntry();
     ASSERT(extra_slots >= 0);
diff --git a/runtime/vm/compiler/backend/il.h b/runtime/vm/compiler/backend/il.h
index c1a0446..d4160cc 100644
--- a/runtime/vm/compiler/backend/il.h
+++ b/runtime/vm/compiler/backend/il.h
@@ -51,6 +51,7 @@
 class FlowGraphCompiler;
 class FlowGraphSerializer;
 class FlowGraphVisitor;
+class ForwardInstructionIterator;
 class Instruction;
 class LocalVariable;
 class LoopInfo;
@@ -60,6 +61,7 @@
 class RangeBoundary;
 class SExpList;
 class SExpression;
+class SuccessorsIterable;
 class TypeUsageInfo;
 class UnboxIntegerInstr;
 
@@ -903,6 +905,8 @@
   virtual intptr_t SuccessorCount() const;
   virtual BlockEntryInstr* SuccessorAt(intptr_t index) const;
 
+  inline SuccessorsIterable successors() const;
+
   void Goto(JoinEntryInstr* entry);
 
   virtual const char* DebugName() const = 0;
@@ -1487,6 +1491,19 @@
   // environment) from their definition's use lists for all instructions.
   void ClearAllInstructions();
 
+  class InstructionsIterable {
+   public:
+    explicit InstructionsIterable(BlockEntryInstr* block) : block_(block) {}
+
+    inline ForwardInstructionIterator begin() const;
+    inline ForwardInstructionIterator end() const;
+
+   private:
+    BlockEntryInstr* block_;
+  };
+
+  InstructionsIterable instructions() { return InstructionsIterable(this); }
+
   DEFINE_INSTRUCTION_TYPE_CHECK(BlockEntry)
 
   TO_S_EXPRESSION_SUPPORT
@@ -1551,8 +1568,14 @@
   DISALLOW_COPY_AND_ASSIGN(BlockEntryInstr);
 };
 
-class ForwardInstructionIterator : public ValueObject {
+class ForwardInstructionIterator {
  public:
+  ForwardInstructionIterator(const ForwardInstructionIterator& other) = default;
+  ForwardInstructionIterator& operator=(
+      const ForwardInstructionIterator& other) = default;
+
+  ForwardInstructionIterator() : current_(nullptr) {}
+
   explicit ForwardInstructionIterator(BlockEntryInstr* block_entry)
       : current_(block_entry) {
     Advance();
@@ -1570,10 +1593,16 @@
 
   Instruction* Current() const { return current_; }
 
+  Instruction* operator*() const { return Current(); }
+
   bool operator==(const ForwardInstructionIterator& other) const {
     return current_ == other.current_;
   }
 
+  bool operator!=(const ForwardInstructionIterator& other) const {
+    return !(*this == other);
+  }
+
   ForwardInstructionIterator& operator++() {
     Advance();
     return *this;
@@ -1583,6 +1612,15 @@
   Instruction* current_;
 };
 
+ForwardInstructionIterator BlockEntryInstr::InstructionsIterable::begin()
+    const {
+  return ForwardInstructionIterator(block_);
+}
+
+ForwardInstructionIterator BlockEntryInstr::InstructionsIterable::end() const {
+  return ForwardInstructionIterator();
+}
+
 class BackwardInstructionIterator : public ValueObject {
  public:
   explicit BackwardInstructionIterator(BlockEntryInstr* block_entry)
@@ -1677,6 +1715,10 @@
     spill_slot_count_ = count;
   }
 
+  // Returns true if this flow graph needs a stack frame.
+  bool NeedsFrame() const { return needs_frame_; }
+  void MarkFrameless() { needs_frame_ = false; }
+
   // Number of stack slots reserved for compiling try-catch. For functions
   // without try-catch, this is 0. Otherwise, it is the number of local
   // variables.
@@ -1731,6 +1773,7 @@
   intptr_t entry_count_;
   intptr_t spill_slot_count_;
   intptr_t fixed_slot_count_;  // For try-catch in optimized code.
+  bool needs_frame_ = true;
 
   DISALLOW_COPY_AND_ASSIGN(GraphEntryInstr);
 };
@@ -9527,6 +9570,38 @@
   return (constant == nullptr) || constant->value().raw() == value.raw();
 }
 
+class SuccessorsIterable {
+ public:
+  struct Iterator {
+    const Instruction* instr;
+    intptr_t index;
+
+    BlockEntryInstr* operator*() const { return instr->SuccessorAt(index); }
+    Iterator& operator++() {
+      index++;
+      return *this;
+    }
+
+    bool operator==(const Iterator& other) {
+      return instr == other.instr && index == other.index;
+    }
+
+    bool operator!=(const Iterator& other) { return !(*this == other); }
+  };
+
+  explicit SuccessorsIterable(const Instruction* instr) : instr_(instr) {}
+
+  Iterator begin() const { return {instr_, 0}; }
+  Iterator end() const { return {instr_, instr_->SuccessorCount()}; }
+
+ private:
+  const Instruction* instr_;
+};
+
+SuccessorsIterable Instruction::successors() const {
+  return SuccessorsIterable(this);
+}
+
 }  // namespace dart
 
 #endif  // RUNTIME_VM_COMPILER_BACKEND_IL_H_
diff --git a/runtime/vm/compiler/backend/il_arm.cc b/runtime/vm/compiler/backend/il_arm.cc
index eaf19a7..5b646c98 100644
--- a/runtime/vm/compiler/backend/il_arm.cc
+++ b/runtime/vm/compiler/backend/il_arm.cc
@@ -379,9 +379,11 @@
     // At this point there are no pending buffered registers.
     // Use LR as it's the highest free register, it is not allocatable and
     // it is clobbered by the call.
-    static_assert(((1 << LR) & kDartAvailableCpuRegs) == 0,
-                  "LR should not be allocatable");
-    return LR;
+    CLOBBERS_LR({
+      static_assert(((1 << LR) & kDartAvailableCpuRegs) == 0,
+                    "LR should not be allocatable");
+      return LR;
+    });
   }
 
  private:
@@ -482,8 +484,7 @@
     ASSERT(result == CallingConventions::kReturnFpuReg);
   }
 
-  if (compiler->intrinsic_mode()) {
-    // Intrinsics don't have a frame.
+  if (!compiler->flow_graph().graph_entry()->NeedsFrame()) {
     __ Ret();
     return;
   }
@@ -1359,6 +1360,7 @@
   __ PopRegister(TMP);
 }
 
+// Keep in sync with NativeEntryInstr::EmitNativeCode.
 void NativeReturnInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
   EmitReturnMoves(compiler);
 
@@ -1395,10 +1397,10 @@
 #endif
 
   // Leave the entry frame.
-  __ LeaveFrame(1 << LR | 1 << FP);
+  RESTORES_LR_FROM_FRAME(__ LeaveFrame(1 << LR | 1 << FP));
 
   // Leave the dummy frame holding the pushed arguments.
-  __ LeaveFrame(1 << LR | 1 << FP);
+  RESTORES_LR_FROM_FRAME(__ LeaveFrame(1 << LR | 1 << FP));
 
   __ Ret();
 
@@ -1406,6 +1408,7 @@
   __ set_constant_pool_allowed(true);
 }
 
+// Keep in sync with NativeReturnInstr::EmitNativeCode and ComputeInnerLRState.
 void NativeEntryInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
   // Constant pool cannot be used until we enter the actual Dart frame.
   __ set_constant_pool_allowed(false);
@@ -1414,13 +1417,13 @@
 
   // Create a dummy frame holding the pushed arguments. This simplifies
   // NativeReturnInstr::EmitNativeCode.
-  __ EnterFrame((1 << FP) | (1 << LR), 0);
+  SPILLS_LR_TO_FRAME(__ EnterFrame((1 << FP) | (1 << LR), 0));
 
   // Save the argument registers, in reverse order.
   SaveArguments(compiler);
 
   // Enter the entry frame.
-  __ EnterFrame((1 << FP) | (1 << LR), 0);
+  SPILLS_LR_TO_FRAME(__ EnterFrame((1 << FP) | (1 << LR), 0));
 
   // Save a space for the code object.
   __ PushImmediate(0);
@@ -1515,9 +1518,12 @@
 
   // Load a dummy return address which suggests that we are inside of
   // InvokeDartCodeStub. This is how the stack walker detects an entry frame.
-  __ LoadFromOffset(LR, THR,
-                    compiler::target::Thread::invoke_dart_code_stub_offset());
-  __ LoadFieldFromOffset(LR, LR, compiler::target::Code::entry_point_offset());
+  CLOBBERS_LR({
+    __ LoadFromOffset(LR, THR,
+                      compiler::target::Thread::invoke_dart_code_stub_offset());
+    __ LoadFieldFromOffset(LR, LR,
+                           compiler::target::Code::entry_point_offset());
+  });
 
   FunctionEntryInstr::EmitNativeCode(compiler);
 }
@@ -2189,8 +2195,7 @@
     case kArrayCid:
       if (ShouldEmitStoreBarrier()) {
         const Register value = locs()->in(2).reg();
-        __ StoreIntoArray(array, temp, value, CanValueBeSmi(),
-                          /*lr_reserved=*/!compiler->intrinsic_mode());
+        __ StoreIntoArray(array, temp, value, CanValueBeSmi());
       } else if (locs()->in(2).IsConstant()) {
         const Object& constant = locs()->in(2).constant();
         __ StoreIntoObjectNoBarrier(array, compiler::Address(temp), constant);
@@ -3012,13 +3017,8 @@
 
   if (ShouldEmitStoreBarrier()) {
     const Register value_reg = locs()->in(1).reg();
-    // In intrinsic mode, there is no stack frame and the function will return
-    // by executing 'ret LR' directly. Therefore we cannot overwrite LR. (see
-    // ReturnInstr::EmitNativeCode).
-    ASSERT(!locs()->live_registers()->Contains(Location::RegisterLocation(LR)));
     __ StoreIntoObjectOffset(instance_reg, offset_in_bytes, value_reg,
-                             CanValueBeSmi(),
-                             /*lr_reserved=*/!compiler->intrinsic_mode());
+                             CanValueBeSmi());
   } else {
     if (locs()->in(1).IsConstant()) {
       __ StoreIntoObjectNoBarrierOffset(instance_reg, offset_in_bytes,
@@ -3710,7 +3710,6 @@
   const intptr_t kNumInputs = 0;
   const intptr_t kNumTemps = 2;
   const bool using_shared_stub = UseSharedSlowPathStub(opt);
-  ASSERT((kReservedCpuRegisters & (1 << LR)) != 0);
   LocationSummary* summary = new (zone)
       LocationSummary(zone, kNumInputs, kNumTemps,
                       using_shared_stub ? LocationSummary::kCallOnSharedSlowPath
@@ -3756,8 +3755,7 @@
       const uword entry_point_offset = compiler::target::Thread::
           stack_overflow_shared_stub_entry_point_offset(
               instruction()->locs()->live_registers()->FpuRegisterCount() > 0);
-      __ ldr(LR, compiler::Address(THR, entry_point_offset));
-      __ blx(LR);
+      __ Call(compiler::Address(THR, entry_point_offset));
       compiler->RecordSafepoint(instruction()->locs(), kNumSlowPathArgs);
       compiler->RecordCatchEntryMoves();
       compiler->AddDescriptor(
diff --git a/runtime/vm/compiler/backend/il_arm64.cc b/runtime/vm/compiler/backend/il_arm64.cc
index 28e03b5..d103107 100644
--- a/runtime/vm/compiler/backend/il_arm64.cc
+++ b/runtime/vm/compiler/backend/il_arm64.cc
@@ -24,7 +24,7 @@
 #include "vm/symbols.h"
 #include "vm/type_testing_stubs.h"
 
-#define __ compiler->assembler()->
+#define __ (compiler->assembler())->
 #define Z (compiler->zone())
 
 namespace dart {
@@ -303,14 +303,16 @@
   }
 
   // Returns free temp register to hold argument value.
-  Register GetFreeTempRegister() {
-    // While pushing arguments only Push, PushPair, LoadObject and
-    // LoadFromOffset are used. They do not clobber TMP or LR.
-    static_assert(((1 << LR) & kDartAvailableCpuRegs) == 0,
-                  "LR should not be allocatable");
-    static_assert(((1 << TMP) & kDartAvailableCpuRegs) == 0,
-                  "TMP should not be allocatable");
-    return (pending_register_ == TMP) ? LR : TMP;
+  Register GetFreeTempRegister(FlowGraphCompiler* compiler) {
+    CLOBBERS_LR({
+      // While pushing arguments only Push, PushPair, LoadObject and
+      // LoadFromOffset are used. They do not clobber TMP or LR.
+      static_assert(((1 << LR) & kDartAvailableCpuRegs) == 0,
+                    "LR should not be allocatable");
+      static_assert(((1 << TMP) & kDartAvailableCpuRegs) == 0,
+                    "TMP should not be allocatable");
+      return (pending_register_ == TMP) ? LR : TMP;
+    });
   }
 
  private:
@@ -336,7 +338,7 @@
         if (compiler::IsSameObject(compiler::NullObject(), value.constant())) {
           reg = NULL_REG;
         } else {
-          reg = pusher.GetFreeTempRegister();
+          reg = pusher.GetFreeTempRegister(compiler);
           __ LoadObject(reg, value.constant());
         }
       } else if (value.IsFpuRegister()) {
@@ -346,7 +348,7 @@
       } else {
         ASSERT(value.IsStackSlot());
         const intptr_t value_offset = value.ToStackSlotOffset();
-        reg = pusher.GetFreeTempRegister();
+        reg = pusher.GetFreeTempRegister(compiler);
         __ LoadFromOffset(reg, value.base_reg(), value_offset);
       }
       pusher.PushRegister(compiler, reg);
@@ -390,8 +392,7 @@
     ASSERT(result == CallingConventions::kReturnFpuReg);
   }
 
-  if (compiler->intrinsic_mode()) {
-    // Intrinsics don't have a frame.
+  if (!compiler->flow_graph().graph_entry()->NeedsFrame()) {
     __ ret();
     return;
   }
@@ -1197,6 +1198,7 @@
   __ set_constant_pool_allowed(true);
 }
 
+// Keep in sync with NativeEntryInstr::EmitNativeCode.
 void NativeReturnInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
   EmitReturnMoves(compiler);
 
@@ -1242,6 +1244,7 @@
   __ set_constant_pool_allowed(true);
 }
 
+// Keep in sync with NativeReturnInstr::EmitNativeCode and ComputeInnerLRState.
 void NativeEntryInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
   // Constant pool cannot be used until we enter the actual Dart frame.
   __ set_constant_pool_allowed(false);
@@ -1363,9 +1366,12 @@
 
   // Load a dummy return address which suggests that we are inside of
   // InvokeDartCodeStub. This is how the stack walker detects an entry frame.
-  __ LoadFromOffset(LR, THR,
-                    compiler::target::Thread::invoke_dart_code_stub_offset());
-  __ LoadFieldFromOffset(LR, LR, compiler::target::Code::entry_point_offset());
+  CLOBBERS_LR({
+    __ LoadFromOffset(LR, THR,
+                      compiler::target::Thread::invoke_dart_code_stub_offset());
+    __ LoadFieldFromOffset(LR, LR,
+                           compiler::target::Code::entry_point_offset());
+  });
 
   FunctionEntryInstr::EmitNativeCode(compiler);
 }
@@ -1904,8 +1910,7 @@
                                           Smi::Cast(index.constant()).Value());
     }
     const Register value = locs()->in(2).reg();
-    __ StoreIntoArray(array, temp, value, CanValueBeSmi(),
-                      /*lr_reserved=*/!compiler->intrinsic_mode());
+    __ StoreIntoArray(array, temp, value, CanValueBeSmi());
     return;
   }
 
@@ -2347,8 +2352,7 @@
   BoxAllocationSlowPath::Allocate(compiler, instruction, cls, box_reg, temp);
   __ MoveRegister(temp, box_reg);
   __ StoreIntoObjectOffset(instance_reg, offset, temp,
-                           compiler::Assembler::kValueIsNotSmi,
-                           /*lr_reserved=*/!compiler->intrinsic_mode());
+                           compiler::Assembler::kValueIsNotSmi);
   __ Bind(&done);
 }
 
@@ -2454,8 +2458,7 @@
       BoxAllocationSlowPath::Allocate(compiler, this, *cls, temp, temp2);
       __ MoveRegister(temp2, temp);
       __ StoreIntoObjectOffset(instance_reg, offset_in_bytes, temp2,
-                               compiler::Assembler::kValueIsNotSmi,
-                               /*lr_reserved=*/!compiler->intrinsic_mode());
+                               compiler::Assembler::kValueIsNotSmi);
     } else {
       __ LoadFieldFromOffset(temp, instance_reg, offset_in_bytes);
     }
@@ -2562,13 +2565,8 @@
 
   if (ShouldEmitStoreBarrier()) {
     const Register value_reg = locs()->in(1).reg();
-    // In intrinsic mode, there is no stack frame and the function will return
-    // by executing 'ret LR' directly. Therefore we cannot overwrite LR. (see
-    // ReturnInstr::EmitNativeCode).
-    ASSERT((kDartAvailableCpuRegs & (1 << LR)) == 0);
     __ StoreIntoObjectOffset(instance_reg, offset_in_bytes, value_reg,
-                             CanValueBeSmi(),
-                             /*lr_reserved=*/!compiler->intrinsic_mode());
+                             CanValueBeSmi());
   } else {
     if (locs()->in(1).IsConstant()) {
       __ StoreIntoObjectOffsetNoBarrier(instance_reg, offset_in_bytes,
@@ -3226,7 +3224,6 @@
   const intptr_t kNumInputs = 0;
   const intptr_t kNumTemps = 1;
   const bool using_shared_stub = UseSharedSlowPathStub(opt);
-  ASSERT((kReservedCpuRegisters & (1 << LR)) != 0);
   LocationSummary* summary = new (zone)
       LocationSummary(zone, kNumInputs, kNumTemps,
                       using_shared_stub ? LocationSummary::kCallOnSharedSlowPath
@@ -3282,8 +3279,7 @@
         const uword entry_point_offset =
             Thread::stack_overflow_shared_stub_entry_point_offset(
                 locs->live_registers()->FpuRegisterCount() > 0);
-        __ ldr(LR, compiler::Address(THR, entry_point_offset));
-        __ blr(LR);
+        __ Call(compiler::Address(THR, entry_point_offset));
       }
       compiler->RecordSafepoint(locs, kNumSlowPathArgs);
       compiler->RecordCatchEntryMoves();
diff --git a/runtime/vm/compiler/backend/il_ia32.cc b/runtime/vm/compiler/backend/il_ia32.cc
index d390e63..d85a5f3 100644
--- a/runtime/vm/compiler/backend/il_ia32.cc
+++ b/runtime/vm/compiler/backend/il_ia32.cc
@@ -234,8 +234,7 @@
   Register result = locs()->in(0).reg();
   ASSERT(result == EAX);
 
-  if (compiler->intrinsic_mode()) {
-    // Intrinsics don't have a frame.
+  if (!compiler->flow_graph().graph_entry()->NeedsFrame()) {
     __ ret();
     return;
   }
@@ -262,6 +261,7 @@
   __ ret();
 }
 
+// Keep in sync with NativeEntryInstr::EmitNativeCode.
 void NativeReturnInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
   EmitReturnMoves(compiler);
 
@@ -1056,6 +1056,7 @@
   __ popl(temp);
 }
 
+// Keep in sync with NativeReturnInstr::EmitNativeCode.
 void NativeEntryInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
   __ Bind(compiler->GetJumpLabel(this));
 
diff --git a/runtime/vm/compiler/backend/il_x64.cc b/runtime/vm/compiler/backend/il_x64.cc
index dfa039f..6da1cd6 100644
--- a/runtime/vm/compiler/backend/il_x64.cc
+++ b/runtime/vm/compiler/backend/il_x64.cc
@@ -305,8 +305,7 @@
     ASSERT(result == CallingConventions::kReturnFpuReg);
   }
 
-  if (compiler->intrinsic_mode()) {
-    // Intrinsics don't have a frame.
+  if (!compiler->flow_graph().graph_entry()->NeedsFrame()) {
     __ ret();
     return;
   }
@@ -341,6 +340,7 @@
     CallingConventions::kCalleeSaveCpuRegisters,
     CallingConventions::kCalleeSaveXmmRegisters);
 
+// Keep in sync with NativeEntryInstr::EmitNativeCode.
 void NativeReturnInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
   EmitReturnMoves(compiler);
 
@@ -1122,6 +1122,7 @@
   __ popq(TMP);
 }
 
+// Keep in sync with NativeReturnInstr::EmitNativeCode.
 void NativeEntryInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
   __ Bind(compiler->GetJumpLabel(this));
 
diff --git a/runtime/vm/compiler/backend/linearscan.cc b/runtime/vm/compiler/backend/linearscan.cc
index ecdac22..4070e5d 100644
--- a/runtime/vm/compiler/backend/linearscan.cc
+++ b/runtime/vm/compiler/backend/linearscan.cc
@@ -702,47 +702,12 @@
     }
   }
 
-  if (defn->IsParameter()) {
-    // Only function entries may have unboxed parameters, possibly making the
-    // parameters size different from the number of parameters on 32-bit
-    // architectures.
-    const intptr_t parameters_size = block->IsFunctionEntry()
-                                         ? flow_graph_.direct_parameters_size()
-                                         : flow_graph_.num_direct_parameters();
-    ParameterInstr* param = defn->AsParameter();
-    intptr_t slot_index =
-        param->param_offset() + (second_location_for_definition ? -1 : 0);
-    ASSERT(slot_index >= 0);
-    if (param->base_reg() == FPREG) {
-      // Slot index for the rightmost fixed parameter is -1.
-      slot_index -= parameters_size;
-    } else {
-      // Slot index for a "frameless" parameter is reversed.
-      ASSERT(param->base_reg() == SPREG);
-      ASSERT(slot_index < parameters_size);
-      slot_index = parameters_size - 1 - slot_index;
-    }
-
-    if (param->base_reg() == FPREG) {
-      slot_index =
-          compiler::target::frame_layout.FrameSlotForVariableIndex(-slot_index);
-    } else {
-      ASSERT(param->base_reg() == SPREG);
-      slot_index += compiler::target::frame_layout.last_param_from_entry_sp;
-    }
-
-    if (param->representation() == kUnboxedInt64 ||
-        param->representation() == kTagged) {
-      const auto location = Location::StackSlot(slot_index, param->base_reg());
-      range->set_assigned_location(location);
-      range->set_spill_slot(location);
-    } else {
-      ASSERT(param->representation() == kUnboxedDouble);
-      const auto location =
-          Location::DoubleStackSlot(slot_index, param->base_reg());
-      range->set_assigned_location(location);
-      range->set_spill_slot(location);
-    }
+  if (auto param = defn->AsParameter()) {
+    const auto location =
+        ComputeParameterLocation(block, param, param->base_reg(),
+                                 second_location_for_definition ? 1 : 0);
+    range->set_assigned_location(location);
+    range->set_spill_slot(location);
   } else if (defn->IsSpecialParameter()) {
     SpecialParameterInstr* param = defn->AsSpecialParameter();
     ASSERT(param->kind() == SpecialParameterInstr::kArgDescriptor);
@@ -3011,6 +2976,164 @@
   }
 }
 
+Location FlowGraphAllocator::ComputeParameterLocation(BlockEntryInstr* block,
+                                                      ParameterInstr* param,
+                                                      Register base_reg,
+                                                      intptr_t pair_index) {
+  ASSERT(pair_index == 0 || param->HasPairRepresentation());
+
+  // Only function entries may have unboxed parameters, possibly making the
+  // parameters size different from the number of parameters on 32-bit
+  // architectures.
+  const intptr_t parameters_size = block->IsFunctionEntry()
+                                       ? flow_graph_.direct_parameters_size()
+                                       : flow_graph_.num_direct_parameters();
+  intptr_t slot_index = param->param_offset() - pair_index;
+  ASSERT(slot_index >= 0);
+  if (base_reg == FPREG) {
+    // Slot index for the rightmost fixed parameter is -1.
+    slot_index -= parameters_size;
+  } else {
+    // Slot index for a "frameless" parameter is reversed.
+    ASSERT(base_reg == SPREG);
+    ASSERT(slot_index < parameters_size);
+    slot_index = parameters_size - 1 - slot_index;
+  }
+
+  if (base_reg == FPREG) {
+    slot_index =
+        compiler::target::frame_layout.FrameSlotForVariableIndex(-slot_index);
+  } else {
+    ASSERT(base_reg == SPREG);
+    slot_index += compiler::target::frame_layout.last_param_from_entry_sp;
+  }
+
+  if (param->representation() == kUnboxedInt64 ||
+      param->representation() == kTagged) {
+    return Location::StackSlot(slot_index, base_reg);
+  } else {
+    ASSERT(param->representation() == kUnboxedDouble);
+    return Location::DoubleStackSlot(slot_index, base_reg);
+  }
+}
+
+void FlowGraphAllocator::RemoveFrameIfNotNeeded() {
+  // Intrinsic functions are naturally frameless.
+  if (intrinsic_mode_) {
+    flow_graph_.graph_entry()->MarkFrameless();
+    return;
+  }
+
+  // For now only AOT compiled code in bare instructions mode supports
+  // frameless functions. Outside of bare instructions mode we need to preserve
+  // caller PP - so all functions need a frame if they have their own pool which
+  // is hard to determine at this stage.
+  if (!(FLAG_precompiled_mode && FLAG_use_bare_instructions)) {
+    return;
+  }
+
+  // Optional parameter handling needs special changes to become frameless.
+  // Specifically we need to rebase IL instructions which directly access frame
+  // ({Load,Store}IndexedUnsafeInstr) to use SP rather than FP.
+  // For now just always give such functions a frame.
+  if (flow_graph_.parsed_function().function().HasOptionalParameters()) {
+    return;
+  }
+
+  // If we have spills we need to create a frame.
+  if (flow_graph_.graph_entry()->spill_slot_count() > 0) {
+    return;
+  }
+
+#if defined(TARGET_ARCH_ARM64) || defined(TARGET_ARCH_ARM)
+  bool has_write_barrier_call = false;
+#endif
+  for (auto block : flow_graph_.reverse_postorder()) {
+    for (auto instruction : block->instructions()) {
+      if (instruction->HasLocs() && instruction->locs()->can_call()) {
+        // Function contains a call and thus needs a frame.
+        return;
+      }
+
+      // Some instructions contain write barriers inside, which can call
+      // a helper stub. This however is a leaf call and we can entirely
+      // ignore it on ia32 and x64, because return address is always on the
+      // stack. On ARM/ARM64 however return address is in LR and needs to
+      // be explicitly preserved in the frame. Write barrier instruction
+      // sequence has explicit support for emitting LR spill/restore
+      // if necessary, however for code size purposes it does not make
+      // sense to make function frameless if it contains more than 1
+      // write barrier invocation.
+#if defined(TARGET_ARCH_ARM64) || defined(TARGET_ARCH_ARM)
+      if (auto store_field = instruction->AsStoreInstanceField()) {
+        if (store_field->ShouldEmitStoreBarrier()) {
+          if (has_write_barrier_call) {
+            // We already have at least one write barrier call.
+            // For code size purposes it is better if we have copy of
+            // LR spill/restore.
+            return;
+          }
+          has_write_barrier_call = true;
+        }
+      }
+
+      if (auto store_indexed = instruction->AsStoreIndexed()) {
+        if (store_indexed->ShouldEmitStoreBarrier()) {
+          if (has_write_barrier_call) {
+            // We already have at least one write barrier call.
+            // For code size purposes it is better if we have copy of
+            // LR spill/restore.
+            return;
+          }
+          has_write_barrier_call = true;
+        }
+      }
+#endif
+    }
+  }
+
+  // Good to go. No need to setup a frame due to the call.
+  auto entry = flow_graph_.graph_entry();
+
+  entry->MarkFrameless();
+
+  // Fix location of parameters to use SP as their base register instead of FP.
+  auto fix_location_for = [&](BlockEntryInstr* block, ParameterInstr* param,
+                              intptr_t vreg, intptr_t pair_index) {
+    auto fp_relative =
+        ComputeParameterLocation(block, param, FPREG, pair_index);
+    auto sp_relative =
+        ComputeParameterLocation(block, param, SPREG, pair_index);
+    for (LiveRange* range = GetLiveRange(vreg); range != nullptr;
+         range = range->next_sibling()) {
+      if (range->assigned_location().Equals(fp_relative)) {
+        range->set_assigned_location(sp_relative);
+        range->set_spill_slot(sp_relative);
+        for (UsePosition* use = range->first_use(); use != nullptr;
+             use = use->next()) {
+          ASSERT(use->location_slot()->Equals(fp_relative));
+          *use->location_slot() = sp_relative;
+        }
+      }
+    }
+  };
+
+  for (auto block : entry->successors()) {
+    if (FunctionEntryInstr* entry = block->AsFunctionEntry()) {
+      for (auto defn : *entry->initial_definitions()) {
+        if (auto param = defn->AsParameter()) {
+          const auto vreg = param->ssa_temp_index();
+          fix_location_for(block, param, vreg, 0);
+          if (param->HasPairRepresentation()) {
+            fix_location_for(block, param, ToSecondPairVreg(vreg),
+                             /*pair_index=*/1);
+          }
+        }
+      }
+    }
+  }
+}
+
 void FlowGraphAllocator::AllocateRegisters() {
   CollectRepresentations();
 
@@ -3055,13 +3178,16 @@
   PrepareForAllocation(Location::kFpuRegister, kNumberOfFpuRegisters,
                        unallocated_xmm_, fpu_regs_, blocked_fpu_registers_);
   AllocateUnallocatedRanges();
-  ResolveControlFlow();
 
   GraphEntryInstr* entry = block_order_[0]->AsGraphEntry();
   ASSERT(entry != NULL);
   intptr_t double_spill_slot_count = spill_slots_.length() * kDoubleSpillFactor;
   entry->set_spill_slot_count(cpu_spill_slot_count_ + double_spill_slot_count);
 
+  RemoveFrameIfNotNeeded();
+
+  ResolveControlFlow();
+
   if (FLAG_print_ssa_liveranges && CompilerState::ShouldTrace()) {
     const Function& function = flow_graph_.function();
 
diff --git a/runtime/vm/compiler/backend/linearscan.h b/runtime/vm/compiler/backend/linearscan.h
index 9843b0b..2d6e919 100644
--- a/runtime/vm/compiler/backend/linearscan.h
+++ b/runtime/vm/compiler/backend/linearscan.h
@@ -172,6 +172,8 @@
   void AllocateUnallocatedRanges();
   void AdvanceActiveIntervals(const intptr_t start);
 
+  void RemoveFrameIfNotNeeded();
+
   // Connect split siblings over non-linear control flow edges.
   void ResolveControlFlow();
 
@@ -260,6 +262,11 @@
 
   void PrintLiveRanges();
 
+  Location ComputeParameterLocation(BlockEntryInstr* block,
+                                    ParameterInstr* param,
+                                    Register base_reg,
+                                    intptr_t pair_index);
+
   const FlowGraph& flow_graph_;
 
   ReachingDefs reaching_defs_;
diff --git a/runtime/vm/compiler/graph_intrinsifier.cc b/runtime/vm/compiler/graph_intrinsifier.cc
index 2e0c310..a06ab1e 100644
--- a/runtime/vm/compiler/graph_intrinsifier.cc
+++ b/runtime/vm/compiler/graph_intrinsifier.cc
@@ -46,8 +46,6 @@
   // `compiler->is_optimizing()` is set to true during EmitNativeCode.
   GraphInstrinsicCodeGenScope optimizing_scope(compiler);
 
-  // The FlowGraph here is constructed by the intrinsics builder methods, and
-  // is different from compiler->flow_graph(), the original method's flow graph.
   compiler->assembler()->Comment("Graph intrinsic begin");
   for (intptr_t i = 0; i < graph->reverse_postorder().length(); i++) {
     BlockEntryInstr* block = graph->reverse_postorder()[i];
@@ -95,6 +93,8 @@
 
   FlowGraph* graph =
       new FlowGraph(parsed_function, graph_entry, block_id, prologue_info);
+  compiler->set_intrinsic_flow_graph(*graph);
+
   const Function& function = parsed_function.function();
 
   switch (function.recognized_kind()) {
diff --git a/runtime/vm/compiler/graph_intrinsifier_arm.cc b/runtime/vm/compiler/graph_intrinsifier_arm.cc
index 1cebb33..baea3aa 100644
--- a/runtime/vm/compiler/graph_intrinsifier_arm.cc
+++ b/runtime/vm/compiler/graph_intrinsifier_arm.cc
@@ -23,14 +23,16 @@
   COMPILE_ASSERT(IsAbiPreservedRegister(CALLEE_SAVED_TEMP));
 
   // Save LR by moving it to a callee saved temporary register.
-  assembler->Comment("IntrinsicCallPrologue");
-  assembler->mov(CALLEE_SAVED_TEMP, Operand(LR));
+  __ Comment("IntrinsicCallPrologue");
+  SPILLS_RETURN_ADDRESS_FROM_LR_TO_REGISTER(
+      __ mov(CALLEE_SAVED_TEMP, Operand(LR)));
 }
 
 void GraphIntrinsifier::IntrinsicCallEpilogue(Assembler* assembler) {
   // Restore LR.
-  assembler->Comment("IntrinsicCallEpilogue");
-  assembler->mov(LR, Operand(CALLEE_SAVED_TEMP));
+  __ Comment("IntrinsicCallEpilogue");
+  RESTORES_RETURN_ADDRESS_FROM_REGISTER_TO_LR(
+      __ mov(LR, Operand(CALLEE_SAVED_TEMP)));
 }
 
 #undef __
diff --git a/runtime/vm/compiler/graph_intrinsifier_arm64.cc b/runtime/vm/compiler/graph_intrinsifier_arm64.cc
index 99d33b4..27be6fa 100644
--- a/runtime/vm/compiler/graph_intrinsifier_arm64.cc
+++ b/runtime/vm/compiler/graph_intrinsifier_arm64.cc
@@ -27,15 +27,15 @@
   COMPILE_ASSERT(CALLEE_SAVED_TEMP2 != CODE_REG);
   COMPILE_ASSERT(CALLEE_SAVED_TEMP2 != ARGS_DESC_REG);
 
-  assembler->Comment("IntrinsicCallPrologue");
-  assembler->mov(CALLEE_SAVED_TEMP, LR);
-  assembler->mov(CALLEE_SAVED_TEMP2, ARGS_DESC_REG);
+  __ Comment("IntrinsicCallPrologue");
+  SPILLS_RETURN_ADDRESS_FROM_LR_TO_REGISTER(__ mov(CALLEE_SAVED_TEMP, LR));
+  __ mov(CALLEE_SAVED_TEMP2, ARGS_DESC_REG);
 }
 
 void GraphIntrinsifier::IntrinsicCallEpilogue(Assembler* assembler) {
-  assembler->Comment("IntrinsicCallEpilogue");
-  assembler->mov(LR, CALLEE_SAVED_TEMP);
-  assembler->mov(ARGS_DESC_REG, CALLEE_SAVED_TEMP2);
+  __ Comment("IntrinsicCallEpilogue");
+  RESTORES_RETURN_ADDRESS_FROM_REGISTER_TO_LR(__ mov(LR, CALLEE_SAVED_TEMP));
+  __ mov(ARGS_DESC_REG, CALLEE_SAVED_TEMP2);
 }
 
 #undef __
diff --git a/runtime/vm/compiler/intrinsifier.cc b/runtime/vm/compiler/intrinsifier.cc
index 9931e91..78dc4fb 100644
--- a/runtime/vm/compiler/intrinsifier.cc
+++ b/runtime/vm/compiler/intrinsifier.cc
@@ -260,7 +260,6 @@
     return false;
   }
 
-  ASSERT(!compiler->flow_graph().IsCompiledForOsr());
   if (GraphIntrinsifier::GraphIntrinsify(parsed_function, compiler)) {
     return compiler->intrinsic_slow_path_label()->IsUnused();
   }
diff --git a/runtime/vm/compiler/stub_code_compiler.cc b/runtime/vm/compiler/stub_code_compiler.cc
index 8295e00..9fb6e93 100644
--- a/runtime/vm/compiler/stub_code_compiler.cc
+++ b/runtime/vm/compiler/stub_code_compiler.cc
@@ -118,6 +118,11 @@
   __ Ret();
 
   if (is_final) {
+#if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
+    // We are jumping over LeaveStubFrame so restore LR state to match one
+    // at the jump point.
+    __ set_lr_state(compiler::LRState::OnEntry().EnterFrame());
+#endif  // defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
     __ Bind(&throw_exception);
     __ PushObject(NullObject());  // Make room for (unused) result.
     __ PushRegister(kFieldReg);
diff --git a/runtime/vm/compiler/stub_code_compiler_arm.cc b/runtime/vm/compiler/stub_code_compiler_arm.cc
index 44b632d..fd13ab7 100644
--- a/runtime/vm/compiler/stub_code_compiler_arm.cc
+++ b/runtime/vm/compiler/stub_code_compiler_arm.cc
@@ -183,7 +183,7 @@
 
   // To make the stack map calculation architecture independent we do the same
   // as on intel.
-  __ Push(LR);
+  READS_RETURN_ADDRESS_FROM_LR(__ Push(LR));
   __ PushRegisters(all_registers);
   __ ldr(CODE_REG, Address(THR, self_code_stub_offset_from_thread));
   __ EnterStubFrame();
@@ -195,7 +195,7 @@
   __ LeaveStubFrame();
   __ PopRegisters(all_registers);
   __ Drop(1);  // We use the LR restored via LeaveStubFrame.
-  __ bx(LR);
+  READS_RETURN_ADDRESS_FROM_LR(__ bx(LR));
 }
 
 void StubCodeCompiler::GenerateSharedStub(
@@ -208,7 +208,9 @@
   ASSERT(!store_runtime_result_in_result_register || allow_return);
   auto perform_runtime_call = [&]() {
     if (store_runtime_result_in_result_register) {
-      __ PushRegister(LR);
+      // Reserve space for the result on the stack. This needs to be a GC
+      // safe value.
+      __ PushImmediate(Smi::RawValue(0));
     }
     __ CallRuntime(*target, /*argument_count=*/0);
     if (store_runtime_result_in_result_register) {
@@ -307,11 +309,11 @@
   all_registers.AddAllGeneralRegisters();
   __ PushRegisters(all_registers);
 
-  __ EnterFrame((1 << FP) | (1 << LR), 0);
+  SPILLS_LR_TO_FRAME(__ EnterFrame((1 << FP) | (1 << LR), 0));
   __ ReserveAlignedFrameSpace(0);
   __ ldr(R0, Address(THR, kEnterSafepointRuntimeEntry.OffsetFromThread()));
   __ blx(R0);
-  __ LeaveFrame((1 << FP) | (1 << LR), 0);
+  RESTORES_LR_FROM_FRAME(__ LeaveFrame((1 << FP) | (1 << LR), 0));
 
   __ PopRegisters(all_registers);
   __ Ret();
@@ -322,7 +324,7 @@
   all_registers.AddAllGeneralRegisters();
   __ PushRegisters(all_registers);
 
-  __ EnterFrame((1 << FP) | (1 << LR), 0);
+  SPILLS_LR_TO_FRAME(__ EnterFrame((1 << FP) | (1 << LR), 0));
   __ ReserveAlignedFrameSpace(0);
 
   // Set the execution state to VM while waiting for the safepoint to end.
@@ -333,7 +335,7 @@
 
   __ ldr(R0, Address(THR, kExitSafepointRuntimeEntry.OffsetFromThread()));
   __ blx(R0);
-  __ LeaveFrame((1 << FP) | (1 << LR), 0);
+  RESTORES_LR_FROM_FRAME(__ LeaveFrame((1 << FP) | (1 << LR), 0));
 
   __ PopRegisters(all_registers);
   __ Ret();
@@ -353,7 +355,7 @@
   COMPILE_ASSERT(IsAbiPreservedRegister(R4));
 
   // TransitionGeneratedToNative might clobber LR if it takes the slow path.
-  __ mov(R4, Operand(LR));
+  SPILLS_RETURN_ADDRESS_FROM_LR_TO_REGISTER(__ mov(R4, Operand(LR)));
 
   __ LoadImmediate(R9, target::Thread::exit_through_ffi());
   __ TransitionGeneratedToNative(R8, FPREG, R9 /*volatile*/, NOTFP,
@@ -402,7 +404,8 @@
 
   // Save THR (callee-saved), R4 & R5 (temporaries, callee-saved), and LR.
   COMPILE_ASSERT(StubCodeCompiler::kNativeCallbackTrampolineStackDelta == 4);
-  __ PushList((1 << LR) | (1 << THR) | (1 << R4) | (1 << R5));
+  SPILLS_LR_TO_FRAME(
+      __ PushList((1 << LR) | (1 << THR) | (1 << R4) | (1 << R5)));
 
   // Don't rely on TMP being preserved by assembler macros anymore.
   __ mov(R4, Operand(TMP));
@@ -578,8 +581,7 @@
   __ mov(R1, Operand(R9));  // Pass the function entrypoint to call.
 
   // Call native function invocation wrapper or redirection via simulator.
-  __ ldr(LR, wrapper);
-  __ blx(LR);
+  __ Call(wrapper);
 
   // Mark that the thread is executing Dart code.
   __ LoadImmediate(R2, VMTag::kDartTagId);
@@ -910,7 +912,7 @@
   __ LoadImmediate(IP, kZapCodeReg);
   __ Push(IP);
   // Return address for "call" to deopt stub.
-  __ LoadImmediate(LR, kZapReturnAddress);
+  WRITES_RETURN_ADDRESS_TO_LR(__ LoadImmediate(LR, kZapReturnAddress));
   __ ldr(CODE_REG,
          Address(THR, target::Thread::lazy_deopt_from_return_stub_offset()));
   GenerateDeoptimizationSequence(assembler, kLazyDeoptFromReturn);
@@ -925,7 +927,7 @@
   __ LoadImmediate(IP, kZapCodeReg);
   __ Push(IP);
   // Return address for "call" to deopt stub.
-  __ LoadImmediate(LR, kZapReturnAddress);
+  WRITES_RETURN_ADDRESS_TO_LR(__ LoadImmediate(LR, kZapReturnAddress));
   __ ldr(CODE_REG,
          Address(THR, target::Thread::lazy_deopt_from_throw_stub_offset()));
   GenerateDeoptimizationSequence(assembler, kLazyDeoptFromThrow);
@@ -1112,13 +1114,13 @@
   // Pop arguments; result is popped in IP.
   __ PopList((1 << R1) | (1 << R2) | (1 << IP));  // R2 is restored.
   __ mov(R0, Operand(IP));
+  __ LeaveStubFrame();
 
   // Write-barrier elimination might be enabled for this array (depending on the
   // array length). To be sure we will check if the allocated object is in old
   // space and if so call a leaf runtime to add it to the remembered set.
   EnsureIsNewOrRemembered(assembler);
 
-  __ LeaveStubFrame();
   __ Ret();
 }
 
@@ -1172,8 +1174,8 @@
 //   R2 : arguments array.
 //   R3 : current thread.
 void StubCodeCompiler::GenerateInvokeDartCodeStub(Assembler* assembler) {
-  __ Push(LR);  // Marker for the profiler.
-  __ EnterFrame((1 << FP) | (1 << LR), 0);
+  READS_RETURN_ADDRESS_FROM_LR(__ Push(LR));  // Marker for the profiler.
+  SPILLS_LR_TO_FRAME(__ EnterFrame((1 << FP) | (1 << LR), 0));
 
   // Push code object to PC marker slot.
   __ ldr(IP, Address(R3, target::Thread::invoke_dart_code_stub_offset()));
@@ -1293,7 +1295,7 @@
   __ set_constant_pool_allowed(false);
 
   // Restore the frame pointer and return.
-  __ LeaveFrame((1 << FP) | (1 << LR));
+  RESTORES_LR_FROM_FRAME(__ LeaveFrame((1 << FP) | (1 << LR)));
   __ Drop(1);
   __ Ret();
 }
@@ -1507,19 +1509,17 @@
 }
 
 void StubCodeCompiler::GenerateWriteBarrierWrappersStub(Assembler* assembler) {
-  RegList saved = (1 << LR) | (1 << kWriteBarrierObjectReg);
   for (intptr_t i = 0; i < kNumberOfCpuRegisters; ++i) {
     if ((kDartAvailableCpuRegs & (1 << i)) == 0) continue;
 
     Register reg = static_cast<Register>(i);
     intptr_t start = __ CodeSize();
-    __ PushList(saved);
+    SPILLS_LR_TO_FRAME(__ PushList((1 << LR) | (1 << kWriteBarrierObjectReg)));
     __ mov(kWriteBarrierObjectReg, Operand(reg));
-    __ ldr(LR,
-           Address(THR, target::Thread::write_barrier_entry_point_offset()));
-    __ blx(LR);
-    __ PopList(saved);
-    __ bx(LR);
+    __ Call(Address(THR, target::Thread::write_barrier_entry_point_offset()));
+    RESTORES_LR_FROM_FRAME(
+        __ PopList((1 << LR) | (1 << kWriteBarrierObjectReg)));
+    READS_RETURN_ADDRESS_FROM_LR(__ bx(LR));
     intptr_t end = __ CodeSize();
 
     RELEASE_ASSERT(end - start == kStoreBufferWrapperSize);
@@ -2915,7 +2915,8 @@
 // The arguments are stored in the Thread object.
 // Does not return.
 void StubCodeCompiler::GenerateRunExceptionHandlerStub(Assembler* assembler) {
-  __ LoadFromOffset(LR, THR, target::Thread::resume_pc_offset());
+  WRITES_RETURN_ADDRESS_TO_LR(
+      __ LoadFromOffset(LR, THR, target::Thread::resume_pc_offset()));
 
   word offset_from_thread = 0;
   bool ok = target::CanLoadFromThread(NullObject(), &offset_from_thread);
@@ -2930,7 +2931,8 @@
   __ LoadFromOffset(R1, THR, target::Thread::active_stacktrace_offset());
   __ StoreToOffset(R2, THR, target::Thread::active_stacktrace_offset());
 
-  __ bx(LR);  // Jump to the exception handler code.
+  READS_RETURN_ADDRESS_FROM_LR(
+      __ bx(LR));  // Jump to the exception handler code.
 }
 
 // Deoptimize a frame on the call stack before rewinding.
@@ -2942,7 +2944,8 @@
   __ Push(IP);
 
   // Load the deopt pc into LR.
-  __ LoadFromOffset(LR, THR, target::Thread::resume_pc_offset());
+  WRITES_RETURN_ADDRESS_TO_LR(
+      __ LoadFromOffset(LR, THR, target::Thread::resume_pc_offset()));
   GenerateDeoptimizationSequence(assembler, kEagerDeopt);
 
   // After we have deoptimized, jump to the correct frame.
diff --git a/runtime/vm/compiler/stub_code_compiler_arm64.cc b/runtime/vm/compiler/stub_code_compiler_arm64.cc
index 6a09f48..15da38d 100644
--- a/runtime/vm/compiler/stub_code_compiler_arm64.cc
+++ b/runtime/vm/compiler/stub_code_compiler_arm64.cc
@@ -194,7 +194,7 @@
 
   // To make the stack map calculation architecture independent we do the same
   // as on intel.
-  __ Push(LR);
+  READS_RETURN_ADDRESS_FROM_LR(__ Push(LR));
   __ PushRegisters(all_registers);
   __ ldr(CODE_REG, Address(THR, self_code_stub_offset_from_thread));
   __ EnterStubFrame();
@@ -206,7 +206,7 @@
   __ LeaveStubFrame();
   __ PopRegisters(all_registers);
   __ Drop(1);  // We use the LR restored via LeaveStubFrame.
-  __ ret(LR);
+  READS_RETURN_ADDRESS_FROM_LR(__ ret(LR));
 }
 
 void StubCodeCompiler::GenerateSharedStub(
@@ -302,7 +302,7 @@
     Assembler* assembler) {
   COMPILE_ASSERT(IsAbiPreservedRegister(R19));
 
-  __ mov(R19, LR);
+  SPILLS_RETURN_ADDRESS_FROM_LR_TO_REGISTER(__ mov(R19, LR));
   __ LoadImmediate(R10, target::Thread::exit_through_ffi());
   __ TransitionGeneratedToNative(R9, FPREG, R10 /*volatile*/,
                                  /*enter_safepoint=*/true);
@@ -368,7 +368,8 @@
   // Save THR (callee-saved) and LR on real real C stack (CSP). Keeps it
   // aligned.
   COMPILE_ASSERT(StubCodeCompiler::kNativeCallbackTrampolineStackDelta == 2);
-  __ stp(THR, LR, Address(CSP, -2 * target::kWordSize, Address::PairPreIndex));
+  SPILLS_LR_TO_FRAME(__ stp(
+      THR, LR, Address(CSP, -2 * target::kWordSize, Address::PairPreIndex)));
 
   COMPILE_ASSERT(!IsArgumentRegister(THR));
 
@@ -442,7 +443,8 @@
   __ EnterSafepoint(/*scratch=*/R9);
 
   // Pop LR and THR from the real stack (CSP).
-  __ ldp(THR, LR, Address(CSP, 2 * target::kWordSize, Address::PairPostIndex));
+  RESTORES_LR_FROM_FRAME(__ ldp(
+      THR, LR, Address(CSP, 2 * target::kWordSize, Address::PairPostIndex)));
 
   __ ret();
 
@@ -674,8 +676,7 @@
   __ mov(R1, R5);  // Pass the function entrypoint to call.
 
   // Call native function invocation wrapper or redirection via simulator.
-  __ ldr(LR, wrapper);
-  __ blr(LR);
+  __ Call(wrapper);
 
   // Restore SP and CSP.
   __ mov(SP, CSP);
@@ -1020,7 +1021,7 @@
   __ LoadImmediate(TMP, kZapCodeReg);
   __ Push(TMP);
   // Return address for "call" to deopt stub.
-  __ LoadImmediate(LR, kZapReturnAddress);
+  WRITES_RETURN_ADDRESS_TO_LR(__ LoadImmediate(LR, kZapReturnAddress));
   __ ldr(CODE_REG,
          Address(THR, target::Thread::lazy_deopt_from_return_stub_offset()));
   GenerateDeoptimizationSequence(assembler, kLazyDeoptFromReturn);
@@ -1035,7 +1036,7 @@
   __ LoadImmediate(TMP, kZapCodeReg);
   __ Push(TMP);
   // Return address for "call" to deopt stub.
-  __ LoadImmediate(LR, kZapReturnAddress);
+  WRITES_RETURN_ADDRESS_TO_LR(__ LoadImmediate(LR, kZapReturnAddress));
   __ ldr(CODE_REG,
          Address(THR, target::Thread::lazy_deopt_from_throw_stub_offset()));
   GenerateDeoptimizationSequence(assembler, kLazyDeoptFromThrow);
@@ -1241,13 +1242,13 @@
   __ Pop(R1);
   __ Pop(R2);
   __ Pop(R0);
+  __ LeaveStubFrame();
 
   // Write-barrier elimination might be enabled for this array (depending on the
   // array length). To be sure we will check if the allocated object is in old
   // space and if so call a leaf runtime to add it to the remembered set.
   EnsureIsNewOrRemembered(assembler);
 
-  __ LeaveStubFrame();
   __ ret();
 }
 
@@ -1307,7 +1308,7 @@
   // from over-writing Dart frames.
   __ mov(SP, CSP);
   __ SetupCSPFromThread(R3);
-  __ Push(LR);  // Marker for the profiler.
+  READS_RETURN_ADDRESS_FROM_LR(__ Push(LR));  // Marker for the profiler.
   __ EnterFrame(0);
 
   // Push code object to PC marker slot.
@@ -1652,15 +1653,13 @@
 
     Register reg = static_cast<Register>(i);
     intptr_t start = __ CodeSize();
-    __ Push(LR);
+    SPILLS_LR_TO_FRAME(__ Push(LR));
     __ Push(kWriteBarrierObjectReg);
     __ mov(kWriteBarrierObjectReg, reg);
-    __ ldr(LR,
-           Address(THR, target::Thread::write_barrier_entry_point_offset()));
-    __ blr(LR);
+    __ Call(Address(THR, target::Thread::write_barrier_entry_point_offset()));
     __ Pop(kWriteBarrierObjectReg);
-    __ Pop(LR);
-    __ ret(LR);
+    RESTORES_LR_FROM_FRAME(__ Pop(LR));
+    READS_RETURN_ADDRESS_FROM_LR(__ ret(LR));
     intptr_t end = __ CodeSize();
 
     RELEASE_ASSERT(end - start == kStoreBufferWrapperSize);
@@ -2993,7 +2992,7 @@
 void StubCodeCompiler::GenerateJumpToFrameStub(Assembler* assembler) {
   ASSERT(kExceptionObjectReg == R0);
   ASSERT(kStackTraceObjectReg == R1);
-  // TransitionGeneratedToNative might clobber LR if it takes the slow path.
+  __ set_lr_state(compiler::LRState::Clobbered());
   __ mov(CALLEE_SAVED_TEMP, R0);  // Program counter.
   __ mov(SP, R1);                 // Stack pointer.
   __ mov(FP, R2);                 // Frame_pointer.
@@ -3038,7 +3037,8 @@
 // The arguments are stored in the Thread object.
 // Does not return.
 void StubCodeCompiler::GenerateRunExceptionHandlerStub(Assembler* assembler) {
-  __ LoadFromOffset(LR, THR, target::Thread::resume_pc_offset());
+  WRITES_RETURN_ADDRESS_TO_LR(
+      __ LoadFromOffset(LR, THR, target::Thread::resume_pc_offset()));
 
   word offset_from_thread = 0;
   bool ok = target::CanLoadFromThread(NullObject(), &offset_from_thread);
@@ -3065,7 +3065,8 @@
   __ Push(TMP);
 
   // Load the deopt pc into LR.
-  __ LoadFromOffset(LR, THR, target::Thread::resume_pc_offset());
+  WRITES_RETURN_ADDRESS_TO_LR(
+      __ LoadFromOffset(LR, THR, target::Thread::resume_pc_offset()));
   GenerateDeoptimizationSequence(assembler, kEagerDeopt);
 
   // After we have deoptimized, jump to the correct frame.
diff --git a/runtime/vm/constants_arm.h b/runtime/vm/constants_arm.h
index 1605725..cf7b8a1 100644
--- a/runtime/vm/constants_arm.h
+++ b/runtime/vm/constants_arm.h
@@ -16,6 +16,20 @@
 
 namespace dart {
 
+// LR register should not be used directly in handwritten assembly patterns,
+// because it might contain return address. Instead use macross CLOBBERS_LR,
+// SPILLS_RETURN_ADDRESS_FROM_LR_TO_REGISTER,
+// RESTORES_RETURN_ADDRESS_FROM_REGISTER_TO_LR, SPILLS_LR_TO_FRAME,
+// RESTORES_LR_FROM_FRAME, READS_RETURN_ADDRESS_FROM_LR,
+// WRITES_RETURN_ADDRESS_TO_LR to get access to LR constant in a checked way.
+//
+// To prevent accidental use of LR constant we rename it to
+// LR_DO_NOT_USE_DIRECTLY (while keeping the code in this file and other files
+// which are permitted to access LR constant the same by defining LR as
+// LR_DO_NOT_USE_DIRECTLY). You can also use LINK_REGISTER if you need
+// to compare LR register code.
+#define LR LR_DO_NOT_USE_DIRECTLY
+
 // We support both VFPv3-D16 and VFPv3-D32 profiles, but currently only one at
 // a time.
 #if defined(__ARM_ARCH_7A__)
@@ -94,7 +108,7 @@
 #endif
   IP = R12,
   SP = R13,
-  LR = R14,
+  LR = R14,  // Note: direct access to this constant is not allowed. See above.
   PC = R15,
 };
 
@@ -289,7 +303,6 @@
 const Register DISPATCH_TABLE_REG = NOTFP;  // Dispatch table register.
 const Register SPREG = SP;                  // Stack pointer register.
 const Register FPREG = FP;                  // Frame pointer register.
-const Register LRREG = LR;                  // Link register.
 const Register ARGS_DESC_REG = R4;
 const Register CODE_REG = R6;
 const Register THR = R10;  // Caches current thread in generated code.
@@ -1035,6 +1048,22 @@
 constexpr uword kBreakInstructionFiller = 0xE1200070;   // bkpt #0
 constexpr uword kDataMemoryBarrier = 0xf57ff050 | 0xb;  // dmb ish
 
+struct LinkRegister {
+  const int32_t code = LR;
+};
+
+constexpr bool operator==(Register r, LinkRegister) {
+  return r == LR;
+}
+
+constexpr bool operator!=(Register r, LinkRegister lr) {
+  return !(r == lr);
+}
+
+#undef LR
+
+#define LINK_REGISTER (LinkRegister())
+
 }  // namespace dart
 
 #endif  // RUNTIME_VM_CONSTANTS_ARM_H_
diff --git a/runtime/vm/constants_arm64.h b/runtime/vm/constants_arm64.h
index 21a5b94..17288cf 100644
--- a/runtime/vm/constants_arm64.h
+++ b/runtime/vm/constants_arm64.h
@@ -16,6 +16,20 @@
 
 namespace dart {
 
+// LR register should not be used directly in handwritten assembly patterns,
+// because it might contain return address. Instead use macross CLOBBERS_LR,
+// SPILLS_RETURN_ADDRESS_FROM_LR_TO_REGISTER,
+// RESTORES_RETURN_ADDRESS_FROM_REGISTER_TO_LR, SPILLS_LR_TO_FRAME,
+// RESTORES_LR_FROM_FRAME, READS_RETURN_ADDRESS_FROM_LR,
+// WRITES_RETURN_ADDRESS_TO_LR to get access to LR constant in a checked way.
+//
+// To prevent accidental use of LR constant we rename it to
+// LR_DO_NOT_USE_DIRECTLY (while keeping the code in this file and other files
+// which are permitted to access LR constant the same by defining LR as
+// LR_DO_NOT_USE_DIRECTLY). You can also use LINK_REGISTER if you need
+// to compare LR register code.
+#define LR LR_DO_NOT_USE_DIRECTLY
+
 enum Register {
   R0 = 0,
   R1 = 1,
@@ -51,6 +65,7 @@
   R31 = 31,  // ZR, CSP
   kNumberOfCpuRegisters = 32,
   kNoRegister = -1,
+  kNoRegister2 = -2,
 
   // These registers both use the encoding R31, but to avoid mistakes we give
   // them different values, and then translate before encoding.
@@ -62,7 +77,7 @@
   IP1 = R17,
   SP = R15,
   FP = R29,
-  LR = R30,
+  LR = R30,  // Note: direct access to this constant is not allowed. See above.
 };
 
 enum VRegister {
@@ -122,7 +137,6 @@
 const Register CODE_REG = R24;
 const Register FPREG = FP;          // Frame pointer register.
 const Register SPREG = R15;         // Stack pointer register.
-const Register LRREG = LR;          // Link register.
 const Register ARGS_DESC_REG = R4;  // Arguments descriptor register.
 const Register THR = R26;           // Caches current thread in generated code.
 const Register CALLEE_SAVED_TEMP = R19;
@@ -1373,6 +1387,24 @@
 
 const uint64_t kBreakInstructionFiller = 0xD4200000D4200000L;  // brk #0; brk #0
 
+struct LinkRegister {};
+
+constexpr bool operator==(Register r, LinkRegister) {
+  return r == LR;
+}
+
+constexpr bool operator!=(Register r, LinkRegister lr) {
+  return !(r == lr);
+}
+
+inline Register ConcreteRegister(LinkRegister) {
+  return LR;
+}
+
+#undef LR
+
+#define LINK_REGISTER (LinkRegister())
+
 }  // namespace dart
 
 #endif  // RUNTIME_VM_CONSTANTS_ARM64_H_
diff --git a/runtime/vm/image_snapshot.cc b/runtime/vm/image_snapshot.cc
index 84d523c..8b58acb 100644
--- a/runtime/vm/image_snapshot.cc
+++ b/runtime/vm/image_snapshot.cc
@@ -1293,8 +1293,8 @@
   assembly_stream_->WriteString(".cfi_escape 0x10, 31, 2, 0x23, 16\n");
 
 #elif defined(TARGET_ARCH_ARM64)
-  COMPILE_ASSERT(FP == R29);
-  COMPILE_ASSERT(LR == R30);
+  COMPILE_ASSERT(R29 == FP);
+  COMPILE_ASSERT(R30 == LINK_REGISTER);
   assembly_stream_->WriteString(".cfi_def_cfa x29, 0\n");  // CFA is fp+0
   assembly_stream_->WriteString(
       ".cfi_offset x29, 0\n");  // saved fp is *(CFA+0)
diff --git a/runtime/vm/instructions_arm.cc b/runtime/vm/instructions_arm.cc
index 83ae8f5..bfeaf1c 100644
--- a/runtime/vm/instructions_arm.cc
+++ b/runtime/vm/instructions_arm.cc
@@ -310,7 +310,7 @@
 
   InstructionPattern::DecodeLoadWordFromPool(data_load_end, &reg,
                                              &target_pool_index_);
-  ASSERT(reg == LR);
+  ASSERT(reg == LINK_REGISTER);
 }
 
 CodePtr BareSwitchableCallPattern::target() const {
@@ -342,7 +342,7 @@
   const int32_t B24 = 1 << 24;
   int32_t instruction = (static_cast<int32_t>(AL) << kConditionShift) | B24 |
                         B21 | (0xfff << 8) | B4 |
-                        (static_cast<int32_t>(LR) << kRmShift);
+                        (LINK_REGISTER.code << kRmShift);
   return bx_lr->InstructionBits() == instruction;
 }
 
diff --git a/runtime/vm/instructions_arm64.cc b/runtime/vm/instructions_arm64.cc
index 2eed6c0..632e69a 100644
--- a/runtime/vm/instructions_arm64.cc
+++ b/runtime/vm/instructions_arm64.cc
@@ -449,7 +449,7 @@
   InstructionPattern::DecodeLoadDoubleWordFromPool(
       pc - Instr::kInstrSize, &ic_data_reg, &code_reg, &pool_index);
   ASSERT(ic_data_reg == R5);
-  ASSERT(code_reg == LR);
+  ASSERT(code_reg == LINK_REGISTER);
 
   data_pool_index_ = pool_index;
   target_pool_index_ = pool_index + 1;
@@ -479,7 +479,7 @@
 
 bool ReturnPattern::IsValid() const {
   Instr* bx_lr = Instr::At(pc_);
-  const Register crn = ConcreteRegister(LR);
+  const Register crn = ConcreteRegister(LINK_REGISTER);
   const int32_t instruction = RET | (static_cast<int32_t>(crn) << kRnShift);
   return bx_lr->InstructionBits() == instruction;
 }
diff --git a/runtime/vm/instructions_arm64_test.cc b/runtime/vm/instructions_arm64_test.cc
index 4a6528c..2076c45 100644
--- a/runtime/vm/instructions_arm64_test.cc
+++ b/runtime/vm/instructions_arm64_test.cc
@@ -16,9 +16,11 @@
 #define __ assembler->
 
 ASSEMBLER_TEST_GENERATE(Call, assembler) {
-  // Code accessing pp is generated, but not executed. Uninitialized pp is OK.
-  __ set_constant_pool_allowed(true);
+  // Code is generated, but not executed. Just parsed with CallPattern
+  __ set_constant_pool_allowed(true);  // Uninitialized pp is OK.
+  SPILLS_LR_TO_FRAME({});              // Clobbered LR is OK.
   __ BranchLinkPatchable(StubCode::InvokeDartCode());
+  RESTORES_LR_FROM_FRAME({});  // Clobbered LR is OK.
   __ ret();
 }
 
diff --git a/runtime/vm/instructions_arm_test.cc b/runtime/vm/instructions_arm_test.cc
index e9fc409..c254353 100644
--- a/runtime/vm/instructions_arm_test.cc
+++ b/runtime/vm/instructions_arm_test.cc
@@ -16,9 +16,11 @@
 #define __ assembler->
 
 ASSEMBLER_TEST_GENERATE(Call, assembler) {
-  // Code accessing pp is generated, but not executed. Uninitialized pp is OK.
-  __ set_constant_pool_allowed(true);
+  // Code is generated, but not executed. Just parsed with CallPattern.
+  __ set_constant_pool_allowed(true);  // Uninitialized pp is OK.
+  SPILLS_LR_TO_FRAME({});              // Clobbered LR is OK.
   __ BranchLinkPatchable(StubCode::InvokeDartCode());
+  RESTORES_LR_FROM_FRAME({});  // Clobbered LR is OK.
   __ Ret();
 }
 
diff --git a/runtime/vm/raw_object.h b/runtime/vm/raw_object.h
index 15af026..19a1934 100644
--- a/runtime/vm/raw_object.h
+++ b/runtime/vm/raw_object.h
@@ -568,6 +568,38 @@
     }
   }
 
+  template <typename type, std::memory_order order = std::memory_order_relaxed>
+  void StoreArrayPointer(type const* addr, type value) {
+    reinterpret_cast<std::atomic<type>*>(const_cast<type*>(addr))
+        ->store(value, order);
+    if (value->IsHeapObject()) {
+      CheckArrayPointerStore(addr, value, Thread::Current());
+    }
+  }
+
+  template <typename type>
+  void StoreArrayPointer(type const* addr, type value, Thread* thread) {
+    *const_cast<type*>(addr) = value;
+    if (value->IsHeapObject()) {
+      CheckArrayPointerStore(addr, value, thread);
+    }
+  }
+
+  template <typename type, std::memory_order order = std::memory_order_relaxed>
+  type LoadSmi(type const* addr) const {
+    return reinterpret_cast<std::atomic<type>*>(const_cast<type*>(addr))
+        ->load(order);
+  }
+  // Use for storing into an explicitly Smi-typed field of an object
+  // (i.e., both the previous and new value are Smis).
+  template <std::memory_order order = std::memory_order_relaxed>
+  void StoreSmi(SmiPtr const* addr, SmiPtr value) {
+    // Can't use Contains, as array length is initialized through this method.
+    ASSERT(reinterpret_cast<uword>(addr) >= ObjectLayout::ToAddr(this));
+    reinterpret_cast<std::atomic<SmiPtr>*>(const_cast<SmiPtr*>(addr))
+        ->store(value, order);
+  }
+
  private:
   DART_FORCE_INLINE
   void CheckHeapPointerStore(ObjectPtr value, Thread* thread) {
@@ -597,23 +629,6 @@
     }
   }
 
-  template <typename type, std::memory_order order = std::memory_order_relaxed>
-  void StoreArrayPointer(type const* addr, type value) {
-    reinterpret_cast<std::atomic<type>*>(const_cast<type*>(addr))
-        ->store(value, order);
-    if (value->IsHeapObject()) {
-      CheckArrayPointerStore(addr, value, Thread::Current());
-    }
-  }
-
-  template <typename type>
-  void StoreArrayPointer(type const* addr, type value, Thread* thread) {
-    *const_cast<type*>(addr) = value;
-    if (value->IsHeapObject()) {
-      CheckArrayPointerStore(addr, value, thread);
-    }
-  }
-
   template <typename type>
   DART_FORCE_INLINE void CheckArrayPointerStore(type const* addr,
                                                 ObjectPtr value,
@@ -650,26 +665,9 @@
     }
   }
 
- protected:
-  template <typename type, std::memory_order order = std::memory_order_relaxed>
-  type LoadSmi(type const* addr) const {
-    return reinterpret_cast<std::atomic<type>*>(const_cast<type*>(addr))
-        ->load(order);
-  }
-  // Use for storing into an explicitly Smi-typed field of an object
-  // (i.e., both the previous and new value are Smis).
-  template <std::memory_order order = std::memory_order_relaxed>
-  void StoreSmi(SmiPtr const* addr, SmiPtr value) {
-    // Can't use Contains, as array length is initialized through this method.
-    ASSERT(reinterpret_cast<uword>(addr) >= ObjectLayout::ToAddr(this));
-    reinterpret_cast<std::atomic<SmiPtr>*>(const_cast<SmiPtr*>(addr))
-        ->store(value, order);
-  }
-
   friend class StoreBufferUpdateVisitor;  // RememberCard
   void RememberCard(ObjectPtr const* slot);
 
- private:
   friend class Array;
   friend class ByteBuffer;
   friend class CidRewriteVisitor;
diff --git a/runtime/vm/simulator_arm.cc b/runtime/vm/simulator_arm.cc
index 316a56b..020839e 100644
--- a/runtime/vm/simulator_arm.cc
+++ b/runtime/vm/simulator_arm.cc
@@ -23,6 +23,11 @@
 
 namespace dart {
 
+// constants_arm.h does not define LR constant to prevent accidental direct use
+// of it during code generation. However using LR directly is okay in this
+// file because it is a simulator.
+constexpr Register LR = LR_DO_NOT_USE_DIRECTLY;
+
 DEFINE_FLAG(uint64_t,
             trace_sim_after,
             ULLONG_MAX,
diff --git a/runtime/vm/simulator_arm64.cc b/runtime/vm/simulator_arm64.cc
index b80c933..24936ec 100644
--- a/runtime/vm/simulator_arm64.cc
+++ b/runtime/vm/simulator_arm64.cc
@@ -22,6 +22,11 @@
 
 namespace dart {
 
+// constants_arm64.h does not define LR constant to prevent accidental direct
+// use of it during code generation. However using LR directly is okay in this
+// file because it is a simulator.
+constexpr Register LR = LR_DO_NOT_USE_DIRECTLY;
+
 DEFINE_FLAG(uint64_t,
             trace_sim_after,
             ULLONG_MAX,
diff --git a/runtime/vm/source_report.cc b/runtime/vm/source_report.cc
index 3f074ff..b52f017 100644
--- a/runtime/vm/source_report.cc
+++ b/runtime/vm/source_report.cc
@@ -634,13 +634,6 @@
   // Now output the wanted constant coverage.
   for (intptr_t i = 0; i < local_script_table_entries->length(); i++) {
     const Script* script = local_script_table_entries->At(i)->script;
-    bool script_ok = true;
-    if (script_ != NULL && !script_->IsNull()) {
-      if (script->raw() != script_->raw()) {
-        // This is the wrong script.
-        script_ok = false;
-      }
-    }
 
     // Whether we want *this* script or not we need to look at the constant
     // constructor coverage. Any of those could be in a script we *do* want.
diff --git a/tests/lib/lib.status b/tests/lib/lib.status
index 43d6d0e..40c7ad5 100644
--- a/tests/lib/lib.status
+++ b/tests/lib/lib.status
@@ -37,6 +37,7 @@
 isolate/deferred_in_isolate2_test: Skip # Issue 16898. Deferred loading does not work from an isolate in CSP-mode
 js/instanceof_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
 js/method_call_on_object_test: SkipByDesign # Issue 42085.
+js/mock_test/*: SkipByDesign # Issue 42085.
 js/parameters_test: SkipByDesign # Issue 42085.
 
 [ $compiler != dart2js && $compiler != dartdevk ]
diff --git a/tools/VERSION b/tools/VERSION
index ad81f86..e227f33 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 12
 PATCH 0
-PRERELEASE 142
+PRERELEASE 143
 PRERELEASE_PATCH 0
\ No newline at end of file