Version 2.10.0-117.0.dev

Merge commit '62354c15403d42640ec9ed39e7d2e960742f7329' into 'dev'
diff --git a/pkg/_fe_analyzer_shared/lib/src/parser/token_stream_rewriter.dart b/pkg/_fe_analyzer_shared/lib/src/parser/token_stream_rewriter.dart
index c3e261a..df1e467 100644
--- a/pkg/_fe_analyzer_shared/lib/src/parser/token_stream_rewriter.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/parser/token_stream_rewriter.dart
@@ -18,7 +18,7 @@
         Token,
         TokenType;
 
-abstract class TokenStreamRewriter with _TokenStreamMixin {
+abstract class TokenStreamRewriter {
   /// Insert a synthetic open and close parenthesis and return the new synthetic
   /// open parenthesis. If [insertIdentifier] is true, then a synthetic
   /// identifier is included between the open and close parenthesis.
@@ -135,6 +135,26 @@
     return replacement;
   }
 
+  /// Insert a synthetic identifier after [token] and return the new identifier.
+  Token insertSyntheticIdentifier(Token token, [String value]) {
+    return insertToken(
+        token,
+        new SyntheticStringToken(TokenType.IDENTIFIER, value ?? '',
+            token.next.charOffset, /* _length = */ 0));
+  }
+
+  /// Insert a new synthetic [keyword] after [token] and return the new token.
+  Token insertSyntheticKeyword(Token token, Keyword keyword) => insertToken(
+      token, new SyntheticKeywordToken(keyword, token.next.charOffset));
+
+  /// Insert a new simple synthetic token of [newTokenType] after [token]
+  /// and return the new token.
+  Token insertSyntheticToken(Token token, TokenType newTokenType) {
+    assert(newTokenType is! Keyword, 'use insertSyntheticKeyword instead');
+    return insertToken(
+        token, new SyntheticToken(newTokenType, token.next.charOffset));
+  }
+
   Token _setNext(Token setOn, Token nextToken);
   void _setEndGroup(BeginToken setOn, Token endGroup);
   void _setOffset(Token setOn, int offset);
@@ -186,123 +206,6 @@
   }
 }
 
-/// Provides the capability of adding tokens that lead into a token stream
-/// without modifying the original token stream and not setting the any token's
-/// `previous` field.
-class TokenStreamGhostWriter
-    with _TokenStreamMixin
-    implements TokenStreamRewriter {
-  @override
-  Token insertParens(Token token, bool includeIdentifier) {
-    Token next = token.next;
-    int offset = next.charOffset;
-    BeginToken leftParen =
-        next = new SyntheticBeginToken(TokenType.OPEN_PAREN, offset);
-    if (includeIdentifier) {
-      Token identifier = new SyntheticStringToken(
-          TokenType.IDENTIFIER, '', offset, /* _length = */ 0);
-      next.next = identifier;
-      next = identifier;
-    }
-    Token rightParen = new SyntheticToken(TokenType.CLOSE_PAREN, offset);
-    next.next = rightParen;
-    rightParen.next = token.next;
-
-    return leftParen;
-  }
-
-  @override
-  Token insertToken(Token token, Token newToken) {
-    newToken.next = token.next;
-    return newToken;
-  }
-
-  @override
-  Token moveSynthetic(Token token, Token endGroup) {
-    Token newEndGroup =
-        new SyntheticToken(endGroup.type, token.next.charOffset);
-    newEndGroup.next = token.next;
-    return newEndGroup;
-  }
-
-  @override
-  Token replaceTokenFollowing(Token previousToken, Token replacementToken) {
-    Token replacedToken = previousToken.next;
-
-    (replacementToken as SimpleToken).precedingComments =
-        replacedToken.precedingComments;
-
-    _lastTokenInChain(replacementToken).next = replacedToken.next;
-    return replacementToken;
-  }
-
-  /// Given the [firstToken] in a chain of tokens to be inserted, return the
-  /// last token in the chain.
-  Token _lastTokenInChain(Token firstToken) {
-    Token current = firstToken;
-    while (current.next != null && current.next.type != TokenType.EOF) {
-      current = current.next;
-    }
-    return current;
-  }
-
-  @override
-  void _setEndGroup(BeginToken setOn, Token endGroup) {
-    throw new UnimplementedError("_setEndGroup");
-  }
-
-  @override
-  Token _setNext(Token setOn, Token nextToken) {
-    throw new UnimplementedError("_setNext");
-  }
-
-  @override
-  void _setOffset(Token setOn, int offset) {
-    throw new UnimplementedError("_setOffset");
-  }
-
-  @override
-  void _setPrecedingComments(SimpleToken setOn, CommentToken comment) {
-    throw new UnimplementedError("_setPrecedingComments");
-  }
-
-  @override
-  void _setPrevious(Token setOn, Token previous) {
-    throw new UnimplementedError("_setPrevious");
-  }
-
-  @override
-  ReplacementToken replaceNextTokenWithSyntheticToken(
-      Token previousToken, TokenType newTokenType) {
-    throw new UnimplementedError("replaceWithSyntheticToken");
-  }
-}
-
-mixin _TokenStreamMixin {
-  /// Insert a synthetic identifier after [token] and return the new identifier.
-  Token insertSyntheticIdentifier(Token token, [String value]) {
-    return insertToken(
-        token,
-        new SyntheticStringToken(TokenType.IDENTIFIER, value ?? '',
-            token.next.charOffset, /* _length = */ 0));
-  }
-
-  /// Insert a new synthetic [keyword] after [token] and return the new token.
-  Token insertSyntheticKeyword(Token token, Keyword keyword) => insertToken(
-      token, new SyntheticKeywordToken(keyword, token.next.charOffset));
-
-  /// Insert a new simple synthetic token of [newTokenType] after [token]
-  /// and return the new token.
-  Token insertSyntheticToken(Token token, TokenType newTokenType) {
-    assert(newTokenType is! Keyword, 'use insertSyntheticKeyword instead');
-    return insertToken(
-        token, new SyntheticToken(newTokenType, token.next.charOffset));
-  }
-
-  /// Insert [newToken] after [token] and return [newToken].
-  Token insertToken(Token token, Token newToken);
-}
-
 abstract class TokenStreamChange {
   void undo();
 }
diff --git a/pkg/front_end/test/fasta/parser/token_stream_rewriter_test.dart b/pkg/front_end/test/fasta/parser/token_stream_rewriter_test.dart
index c1dae7d..5445a78 100644
--- a/pkg/front_end/test/fasta/parser/token_stream_rewriter_test.dart
+++ b/pkg/front_end/test/fasta/parser/token_stream_rewriter_test.dart
@@ -13,9 +13,9 @@
 
 main() {
   defineReflectiveSuite(() {
-    defineReflectiveTests(TokenStreamGhostWriterTest);
     defineReflectiveTests(TokenStreamRewriterTest_NoPrevious);
     defineReflectiveTests(TokenStreamRewriterTest_UsingPrevious);
+    defineReflectiveTests(TokenStreamRewriterTest_Undoable);
   });
 }
 
@@ -24,13 +24,20 @@
   /// Indicates whether the tests should set up [Token.previous].
   bool get setPrevious;
 
+  TokenStreamRewriter getTokenStreamRewriter();
+
+  void setupDone(Token first) {}
+  void normalTestDone(TokenStreamRewriter rewriter, Token first) {}
+
   void test_insertParens() {
-    var a = _makeToken(0, 'a');
-    var b = _makeToken(1, 'b');
-    var eof = _link([a, b]);
-    var rewriter = new TokenStreamRewriterImpl();
-    var openParen = rewriter.insertParens(a, false);
-    var closeParen = openParen.next;
+    Token a = _makeToken(0, 'a');
+    Token b = _makeToken(1, 'b');
+    Token eof = _link([a, b]);
+    setupDone(a);
+
+    TokenStreamRewriter rewriter = getTokenStreamRewriter();
+    Token openParen = rewriter.insertParens(a, false);
+    Token closeParen = openParen.next;
 
     expect(openParen.lexeme, '(');
     expect(closeParen.lexeme, ')');
@@ -43,16 +50,20 @@
     expect(b.previous, same(closeParen));
     expect(closeParen.previous, same(openParen));
     expect(openParen.previous, same(a));
+
+    normalTestDone(rewriter, a);
   }
 
   void test_insertParensWithIdentifier() {
-    var a = _makeToken(0, 'a');
-    var b = _makeToken(1, 'b');
-    var eof = _link([a, b]);
-    var rewriter = new TokenStreamRewriterImpl();
-    var openParen = rewriter.insertParens(a, true);
-    var identifier = openParen.next;
-    var closeParen = identifier.next;
+    Token a = _makeToken(0, 'a');
+    Token b = _makeToken(1, 'b');
+    Token eof = _link([a, b]);
+    setupDone(a);
+
+    TokenStreamRewriter rewriter = getTokenStreamRewriter();
+    Token openParen = rewriter.insertParens(a, true);
+    Token identifier = openParen.next;
+    Token closeParen = identifier.next;
 
     expect(openParen.lexeme, '(');
     expect(identifier.lexeme, '');
@@ -69,14 +80,18 @@
     expect(closeParen.previous, same(identifier));
     expect(identifier.previous, same(openParen));
     expect(openParen.previous, same(a));
+
+    normalTestDone(rewriter, a);
   }
 
   void test_insertSyntheticIdentifier() {
-    var a = _makeToken(0, 'a');
-    var b = _makeToken(1, 'b');
-    var eof = _link([a, b]);
-    var rewriter = new TokenStreamRewriterImpl();
-    var identifier = rewriter.insertSyntheticIdentifier(a);
+    Token a = _makeToken(0, 'a');
+    Token b = _makeToken(1, 'b');
+    Token eof = _link([a, b]);
+    setupDone(a);
+
+    TokenStreamRewriter rewriter = getTokenStreamRewriter();
+    Token identifier = rewriter.insertSyntheticIdentifier(a);
 
     expect(identifier.lexeme, '');
     expect(identifier.isSynthetic, isTrue);
@@ -87,39 +102,51 @@
 
     expect(b.previous, same(identifier));
     expect(identifier.previous, same(a));
+
+    normalTestDone(rewriter, a);
   }
 
   void test_insertToken_end() {
-    var a = _makeToken(0, 'a');
-    var b = _makeToken(1, 'b');
-    var eof = _link([a]);
-    var rewriter = new TokenStreamRewriterImpl();
+    Token a = _makeToken(0, 'a');
+    Token b = _makeToken(1, 'b');
+    Token eof = _link([a]);
+    setupDone(a);
+
+    TokenStreamRewriter rewriter = getTokenStreamRewriter();
     expect(rewriter.insertToken(a, b), same(b));
     expect(a.next, same(b));
     expect(b.next, same(eof));
     expect(eof.previous, same(b));
     expect(b.previous, same(a));
+
+    normalTestDone(rewriter, a);
   }
 
   void test_insertToken_middle() {
-    var a = _makeToken(0, 'a');
-    var b = _makeToken(1, 'b');
-    var c = _makeToken(2, 'c');
+    Token a = _makeToken(0, 'a');
+    Token b = _makeToken(1, 'b');
+    Token c = _makeToken(2, 'c');
     _link([a, c]);
-    var rewriter = new TokenStreamRewriterImpl();
+    setupDone(a);
+
+    TokenStreamRewriter rewriter = getTokenStreamRewriter();
     rewriter.insertToken(a, b);
     expect(a.next, same(b));
     expect(b.next, same(c));
+
+    normalTestDone(rewriter, a);
   }
 
   void test_insertToken_second_insertion_earlier_in_stream() {
-    var a = _makeToken(0, 'a');
-    var b = _makeToken(1, 'b');
-    var c = _makeToken(2, 'c');
-    var d = _makeToken(3, 'd');
-    var e = _makeToken(4, 'e');
+    Token a = _makeToken(0, 'a');
+    Token b = _makeToken(1, 'b');
+    Token c = _makeToken(2, 'c');
+    Token d = _makeToken(3, 'd');
+    Token e = _makeToken(4, 'e');
     _link([a, c, e]);
-    var rewriter = new TokenStreamRewriterImpl();
+    setupDone(a);
+
+    TokenStreamRewriter rewriter = getTokenStreamRewriter();
     rewriter.insertToken(c, d);
     expect(c.next, same(d));
     expect(d.next, same(e));
@@ -128,18 +155,21 @@
     rewriter.insertToken(a, b);
     expect(a.next, same(b));
     expect(b.next, same(c));
+
+    normalTestDone(rewriter, a);
   }
 
   void test_replaceNextTokenWithSyntheticToken_1() {
-    var a = _makeToken(0, 'a');
-    var b = _makeToken(5, 'b');
+    Token a = _makeToken(0, 'a');
+    StringToken b = _makeToken(5, 'b');
     b.precedingComments = new CommentToken.fromSubstring(
         TokenType.SINGLE_LINE_COMMENT, "Test comment", 1, 9, 1,
         canonicalize: true);
-    var c = _makeToken(10, 'c');
+    Token c = _makeToken(10, 'c');
     _link([a, b, c]);
+    setupDone(a);
 
-    var rewriter = new TokenStreamRewriterImpl();
+    TokenStreamRewriter rewriter = getTokenStreamRewriter();
     ReplacementToken replacement =
         rewriter.replaceNextTokenWithSyntheticToken(a, TokenType.AMPERSAND);
     expect(b.offset, same(replacement.offset));
@@ -149,17 +179,20 @@
     expect(a.next, same(replacement));
     expect(replacement.next, same(c));
     expect(c.next.isEof, true);
+
+    normalTestDone(rewriter, a);
   }
 
   void test_replaceNextTokenWithSyntheticToken_2() {
-    var a = _makeToken(0, 'a');
-    var b = _makeToken(5, 'b');
+    Token a = _makeToken(0, 'a');
+    StringToken b = _makeToken(5, 'b');
     b.precedingComments = new CommentToken.fromSubstring(
         TokenType.SINGLE_LINE_COMMENT, "Test comment", 1, 9, 1,
         canonicalize: true);
     _link([a, b]);
+    setupDone(a);
 
-    var rewriter = new TokenStreamRewriterImpl();
+    TokenStreamRewriter rewriter = getTokenStreamRewriter();
     ReplacementToken replacement =
         rewriter.replaceNextTokenWithSyntheticToken(a, TokenType.AMPERSAND);
     expect(b.offset, same(replacement.offset));
@@ -168,52 +201,67 @@
 
     expect(a.next, same(replacement));
     expect(replacement.next.isEof, true);
+
+    normalTestDone(rewriter, a);
   }
 
   void test_moveSynthetic() {
     ScannerResult scanResult = scanString('Foo(bar; baz=0;');
     expect(scanResult.hasErrors, isTrue);
+    Token firstToken = scanResult.tokens;
+    setupDone(firstToken);
+
     Token open = scanResult.tokens.next.next;
     expect(open.lexeme, '(');
     Token close = open.endGroup;
     expect(close.isSynthetic, isTrue);
     expect(close.next.isEof, isTrue);
-    var rewriter = new TokenStreamRewriterImpl();
+    TokenStreamRewriter rewriter = getTokenStreamRewriter();
 
     Token result = rewriter.moveSynthetic(open.next, close);
     expect(result, close);
     expect(open.endGroup, close);
     expect(open.next.next, close);
     expect(close.next.isEof, isFalse);
+
+    normalTestDone(rewriter, firstToken);
   }
 
   void test_replaceTokenFollowing_multiple() {
-    var a = _makeToken(0, 'a');
-    var b = _makeToken(1, 'b');
-    var c = _makeToken(2, 'c');
-    var d = _makeToken(3, 'd');
-    var e = _makeToken(4, 'e');
-    var f = _makeToken(5, 'f');
+    Token a = _makeToken(0, 'a');
+    Token b = _makeToken(1, 'b');
+    Token c = _makeToken(2, 'c');
+    Token d = _makeToken(3, 'd');
+    Token e = _makeToken(4, 'e');
+    Token f = _makeToken(5, 'f');
     _link([a, b, e, f]);
     _link([c, d]);
-    var rewriter = new TokenStreamRewriterImpl();
+    setupDone(a);
+
+    TokenStreamRewriter rewriter = getTokenStreamRewriter();
     rewriter.replaceTokenFollowing(b, c);
     expect(a.next, same(b));
     expect(b.next, same(c));
     expect(c.next, same(d));
     expect(d.next, same(f));
+
+    normalTestDone(rewriter, a);
   }
 
   void test_replaceTokenFollowing_single() {
-    var a = _makeToken(0, 'a');
-    var b = _makeToken(1, 'b');
-    var c = _makeToken(2, 'c');
-    var d = _makeToken(3, 'd');
+    Token a = _makeToken(0, 'a');
+    Token b = _makeToken(1, 'b');
+    Token c = _makeToken(2, 'c');
+    Token d = _makeToken(3, 'd');
     _link([a, b, d]);
-    var rewriter = new TokenStreamRewriterImpl();
+    setupDone(a);
+
+    TokenStreamRewriter rewriter = getTokenStreamRewriter();
     rewriter.replaceTokenFollowing(a, c);
     expect(a.next, same(c));
     expect(c.next, same(d));
+
+    normalTestDone(rewriter, a);
   }
 
   /// Links together the given [tokens] and adds an EOF token to the end of the
@@ -223,7 +271,7 @@
   Token _link(Iterable<Token> tokens) {
     Token head = new Token.eof(-1);
     if (!setPrevious) head.previous = null;
-    for (var token in tokens) {
+    for (Token token in tokens) {
       head.next = token;
       if (setPrevious) token.previous = head;
       head = token;
@@ -251,6 +299,8 @@
 class TokenStreamRewriterTest_NoPrevious extends TokenStreamRewriterTest {
   @override
   bool get setPrevious => false;
+
+  TokenStreamRewriter getTokenStreamRewriter() => new TokenStreamRewriterImpl();
 }
 
 /// Concrete implementation of [TokenStreamRewriterTest] in which
@@ -263,202 +313,78 @@
 class TokenStreamRewriterTest_UsingPrevious extends TokenStreamRewriterTest {
   @override
   bool get setPrevious => true;
+
+  TokenStreamRewriter getTokenStreamRewriter() => new TokenStreamRewriterImpl();
 }
 
+/// Concrete implementation of [TokenStreamRewriterTest] in which
+/// the [UndoableTokenStreamRewriter] is used.
 @reflectiveTest
-class TokenStreamGhostWriterTest extends TokenStreamRewriterTest {
+class TokenStreamRewriterTest_Undoable extends TokenStreamRewriterTest {
   @override
-  bool get setPrevious => false;
+  bool get setPrevious => true;
 
-  void test_insertParens() {
-    var a = _makeToken(0, 'a');
-    var b = _makeToken(1, 'b');
-    var eof = _link([a, b]);
-    var rewriter = new TokenStreamGhostWriter();
-    var openParen = rewriter.insertParens(a, false);
-    var closeParen = openParen.next;
+  TokenStreamRewriter getTokenStreamRewriter() =>
+      new UndoableTokenStreamRewriter();
 
-    expect(openParen.lexeme, '(');
-    expect(closeParen.lexeme, ')');
+  List<CachedTokenSetup> setup;
 
-    expect(a.next, same(b));
-    expect(openParen.next, same(closeParen));
-    expect(closeParen.next, same(b));
-    expect(b.next, same(eof));
-
-    expect(b.previous, isNull);
-    expect(closeParen.previous, isNull);
-    expect(openParen.previous, isNull);
+  void setupDone(Token first) {
+    setup = [];
+    Token token = first;
+    while (token != null && !token.isEof) {
+      setup.add(new CachedTokenSetup(token));
+      token = token.next;
+    }
   }
 
-  void test_insertParensWithIdentifier() {
-    var a = _makeToken(0, 'a');
-    var b = _makeToken(1, 'b');
-    var eof = _link([a, b]);
-    var rewriter = new TokenStreamGhostWriter();
-    var openParen = rewriter.insertParens(a, true);
-    var identifier = openParen.next;
-    var closeParen = identifier.next;
+  void normalTestDone(TokenStreamRewriter rewriter, Token first) {
+    UndoableTokenStreamRewriter undoableTokenStreamRewriter = rewriter;
+    undoableTokenStreamRewriter.undo();
+    List<CachedTokenSetup> now = [];
+    Token token = first;
+    while (token != null && !token.isEof) {
+      now.add(new CachedTokenSetup(token));
+      token = token.next;
+    }
+    if (setup.length != now.length) {
+      throw "Different length: ${setup.length} vs ${now.length}";
+    }
+    for (int i = 0; i < setup.length; i++) {
+      if (setup[i] != now[i]) {
+        throw "Different at $i: ${setup[i]} vs ${now[i]}";
+      }
+    }
+    setup = null;
+  }
+}
 
-    expect(openParen.lexeme, '(');
-    expect(identifier.lexeme, '');
-    expect(identifier.isSynthetic, isTrue);
-    expect(closeParen.lexeme, ')');
+class CachedTokenSetup {
+  final Token token;
+  final Token prev;
+  final Token next;
+  final Token precedingComments;
 
-    expect(a.next, same(b));
-    expect(openParen.next, same(identifier));
-    expect(identifier.next, same(closeParen));
-    expect(closeParen.next, same(b));
-    expect(b.next, same(eof));
+  CachedTokenSetup(this.token)
+      : prev = token.previous,
+        next = token.next,
+        precedingComments = token.precedingComments;
 
-    expect(b.previous, isNull);
-    expect(closeParen.previous, isNull);
-    expect(identifier.previous, isNull);
-    expect(openParen.previous, isNull);
+  bool operator ==(Object other) {
+    if (other is! CachedTokenSetup) return false;
+    CachedTokenSetup o = other;
+    return token == o.token &&
+        prev == o.prev &&
+        next == o.next &&
+        precedingComments == o.precedingComments;
   }
 
-  void test_insertSyntheticIdentifier() {
-    var a = _makeToken(0, 'a');
-    var b = _makeToken(1, 'b');
-    var eof = _link([a, b]);
-    var rewriter = new TokenStreamGhostWriter();
-    var identifier = rewriter.insertSyntheticIdentifier(a);
-
-    expect(identifier.lexeme, '');
-    expect(identifier.isSynthetic, isTrue);
-
-    expect(a.next, same(b));
-    expect(identifier.next, same(b));
-    expect(b.next, same(eof));
-
-    expect(b.previous, isNull);
-    expect(identifier.previous, isNull);
-  }
-
-  void test_insertToken_end() {
-    var a = _makeToken(0, 'a');
-    var b = _makeToken(1, 'b');
-    var eof = _link([a]);
-    var rewriter = new TokenStreamGhostWriter();
-
-    expect(rewriter.insertToken(a, b), same(b));
-    expect(a.next, same(eof));
-    expect(b.next, same(eof));
-
-    expect(eof.previous, isNull);
-    expect(b.previous, isNull);
-  }
-
-  void test_insertToken_middle() {
-    var a = _makeToken(0, 'a');
-    var b = _makeToken(1, 'b');
-    var c = _makeToken(2, 'c');
-    _link([a, c]);
-
-    var rewriter = new TokenStreamGhostWriter();
-    rewriter.insertToken(a, b);
-    expect(a.next, same(c));
-    expect(b.next, same(c));
-
-    expect(a.previous, isNull);
-    expect(b.previous, isNull);
-    expect(c.previous, isNull);
-  }
-
-  void test_insertToken_second_insertion_earlier_in_stream() {
-    var a = _makeToken(0, 'a');
-    var b = _makeToken(1, 'b');
-    var c = _makeToken(2, 'c');
-    var d = _makeToken(3, 'd');
-    var e = _makeToken(4, 'e');
-    _link([a, c, e]);
-    var rewriter = new TokenStreamGhostWriter();
-
-    rewriter.insertToken(c, d);
-    expect(c.next, same(e));
-    expect(d.next, same(e));
-
-    // The next call to rewriter should be able to find the insertion point
-    // even though it is before the insertion point used above.
-    rewriter.insertToken(a, b);
-    expect(a.next, same(c));
-    expect(b.next, same(c));
-
-    expect(a.previous, isNull);
-    expect(b.previous, isNull);
-    expect(c.previous, isNull);
-    expect(d.previous, isNull);
-    expect(e.previous, isNull);
-  }
-
-  void test_moveSynthetic() {
-    ScannerResult scanResult = scanString('Foo(bar; baz=0;');
-    expect(scanResult.hasErrors, isTrue);
-    Token open = scanResult.tokens.next.next;
-    expect(open.lexeme, '(');
-    Token semicolon = open.next.next;
-    expect(semicolon.lexeme, ';');
-    Token close = open.endGroup;
-    expect(close.isSynthetic, isTrue);
-    expect(close.next.isEof, isTrue);
-    Token semicolon2 = close.previous;
-    expect(semicolon2.lexeme, ';');
-    var rewriter = new TokenStreamGhostWriter();
-
-    Token newClose = rewriter.moveSynthetic(open.next, close);
-    expect(newClose, isNot(same(close)));
-    expect(newClose.next, same(semicolon));
-    expect(open.endGroup, close);
-    expect(open.next.next, semicolon);
-    expect(close.next.isEof, isTrue);
-
-    expect(newClose.previous, isNull);
-    expect(close.next.previous, close);
-    expect(close.previous, semicolon2);
-  }
-
-  void test_replaceTokenFollowing_multiple() {
-    var a = _makeToken(0, 'a');
-    var b = _makeToken(1, 'b');
-    var c = _makeToken(2, 'c');
-    var d = _makeToken(3, 'd');
-    var e = _makeToken(4, 'e');
-    var f = _makeToken(5, 'f');
-    _link([a, b, e, f]);
-    _link([c, d]);
-    var rewriter = new TokenStreamGhostWriter();
-    Token result = rewriter.replaceTokenFollowing(b, c);
-
-    expect(result, same(c));
-    expect(a.next, same(b));
-    expect(b.next, same(e));
-    expect(e.next, same(f));
-    expect(c.next, same(d));
-    expect(d.next, same(f));
-
-    expect(a.previous, isNull);
-    expect(b.previous, isNull);
-    expect(c.previous, isNull);
-    expect(d.previous, isNull);
-    expect(e.previous, isNull);
-  }
-
-  void test_replaceTokenFollowing_single() {
-    var a = _makeToken(0, 'a');
-    var b = _makeToken(1, 'b');
-    var c = _makeToken(2, 'c');
-    var d = _makeToken(3, 'd');
-    _link([a, b, d]);
-    var rewriter = new TokenStreamGhostWriter();
-    Token result = rewriter.replaceTokenFollowing(a, c);
-
-    expect(result, same(c));
-    expect(a.next, same(b));
-    expect(b.next, same(d));
-    expect(c.next, same(d));
-
-    expect(a.previous, isNull);
-    expect(b.previous, isNull);
-    expect(c.previous, isNull);
+  String toString() {
+    return "CachedTokenSetup["
+        "token = $token, "
+        "prev = $prev, "
+        "next = $next, "
+        "precedingComments = $precedingComments"
+        "]";
   }
 }
diff --git a/runtime/tests/vm/dart/write_barrier_register_clobber_test.dart b/runtime/tests/vm/dart/write_barrier_register_clobber_test.dart
new file mode 100644
index 0000000..ceff211
--- /dev/null
+++ b/runtime/tests/vm/dart/write_barrier_register_clobber_test.dart
@@ -0,0 +1,81 @@
+// Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// This test attempts to verify that write barrier slow path does not
+// clobber any live values.
+
+import 'dart:_internal' show VMInternalsForTesting;
+
+import 'package:expect/expect.dart';
+
+class Old {
+  var f;
+  Old(this.f);
+}
+
+@pragma('vm:never-inline')
+int crashy(int v, List<Old> oldies) {
+  // This test attempts to create a lot of live values which would live across
+  // write barrier invocation so that when write-barrier calls runtime and
+  // clobbers a register this is detected.
+  var young = Object();
+  var len = oldies.length;
+  var i = 0;
+  var v00 = v + 0;
+  var v01 = v + 1;
+  var v02 = v + 2;
+  var v03 = v + 3;
+  var v04 = v + 4;
+  var v05 = v + 5;
+  var v06 = v + 6;
+  var v07 = v + 7;
+  var v08 = v + 8;
+  var v09 = v + 9;
+  var v10 = v + 10;
+  var v11 = v + 11;
+  var v12 = v + 12;
+  var v13 = v + 13;
+  var v14 = v + 14;
+  var v15 = v + 15;
+  var v16 = v + 16;
+  var v17 = v + 17;
+  var v18 = v + 18;
+  var v19 = v + 19;
+  while (i < len) {
+    // Eventually this will overflow store buffer and call runtime to acquire
+    // a new block.
+    oldies[i++].f = young;
+  }
+  return v00 +
+      v01 +
+      v02 +
+      v03 +
+      v04 +
+      v05 +
+      v06 +
+      v07 +
+      v08 +
+      v09 +
+      v10 +
+      v11 +
+      v12 +
+      v13 +
+      v14 +
+      v15 +
+      v16 +
+      v17 +
+      v18 +
+      v19;
+}
+
+void main(List<String> args) {
+  final init = args.contains('impossible') ? 1 : 0;
+  final oldies = List<Old>.generate(100000, (i) => Old(""));
+  VMInternalsForTesting.collectAllGarbage();
+  VMInternalsForTesting.collectAllGarbage();
+  Expect.equals(crashy(init, oldies), 190);
+  for (var o in oldies) {
+    Expect.isTrue(o.f is! String);
+  }
+}
diff --git a/runtime/tests/vm/dart_2/write_barrier_register_clobber_test.dart b/runtime/tests/vm/dart_2/write_barrier_register_clobber_test.dart
new file mode 100644
index 0000000..ceff211
--- /dev/null
+++ b/runtime/tests/vm/dart_2/write_barrier_register_clobber_test.dart
@@ -0,0 +1,81 @@
+// Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// This test attempts to verify that write barrier slow path does not
+// clobber any live values.
+
+import 'dart:_internal' show VMInternalsForTesting;
+
+import 'package:expect/expect.dart';
+
+class Old {
+  var f;
+  Old(this.f);
+}
+
+@pragma('vm:never-inline')
+int crashy(int v, List<Old> oldies) {
+  // This test attempts to create a lot of live values which would live across
+  // write barrier invocation so that when write-barrier calls runtime and
+  // clobbers a register this is detected.
+  var young = Object();
+  var len = oldies.length;
+  var i = 0;
+  var v00 = v + 0;
+  var v01 = v + 1;
+  var v02 = v + 2;
+  var v03 = v + 3;
+  var v04 = v + 4;
+  var v05 = v + 5;
+  var v06 = v + 6;
+  var v07 = v + 7;
+  var v08 = v + 8;
+  var v09 = v + 9;
+  var v10 = v + 10;
+  var v11 = v + 11;
+  var v12 = v + 12;
+  var v13 = v + 13;
+  var v14 = v + 14;
+  var v15 = v + 15;
+  var v16 = v + 16;
+  var v17 = v + 17;
+  var v18 = v + 18;
+  var v19 = v + 19;
+  while (i < len) {
+    // Eventually this will overflow store buffer and call runtime to acquire
+    // a new block.
+    oldies[i++].f = young;
+  }
+  return v00 +
+      v01 +
+      v02 +
+      v03 +
+      v04 +
+      v05 +
+      v06 +
+      v07 +
+      v08 +
+      v09 +
+      v10 +
+      v11 +
+      v12 +
+      v13 +
+      v14 +
+      v15 +
+      v16 +
+      v17 +
+      v18 +
+      v19;
+}
+
+void main(List<String> args) {
+  final init = args.contains('impossible') ? 1 : 0;
+  final oldies = List<Old>.generate(100000, (i) => Old(""));
+  VMInternalsForTesting.collectAllGarbage();
+  VMInternalsForTesting.collectAllGarbage();
+  Expect.equals(crashy(init, oldies), 190);
+  for (var o in oldies) {
+    Expect.isTrue(o.f is! String);
+  }
+}
diff --git a/runtime/vm/compiler/assembler/assembler_arm64.cc b/runtime/vm/compiler/assembler/assembler_arm64.cc
index 2118d49..4699f5d 100644
--- a/runtime/vm/compiler/assembler/assembler_arm64.cc
+++ b/runtime/vm/compiler/assembler/assembler_arm64.cc
@@ -1485,7 +1485,7 @@
   StoreToOffset(state, THR, target::Thread::exit_through_ffi_offset());
 }
 
-void Assembler::EnterCallRuntimeFrame(intptr_t frame_size) {
+void Assembler::EnterCallRuntimeFrame(intptr_t frame_size, bool is_leaf) {
   Comment("EnterCallRuntimeFrame");
   EnterFrame(0);
   if (!(FLAG_precompiled_mode && FLAG_use_bare_instructions)) {
@@ -1510,19 +1510,30 @@
     Push(reg);
   }
 
-  ReserveAlignedFrameSpace(frame_size);
+  if (!is_leaf) {  // Leaf calling sequence aligns the stack itself.
+    ReserveAlignedFrameSpace(frame_size);
+  } else {
+    PushPair(kCallLeafRuntimeCalleeSaveScratch1,
+             kCallLeafRuntimeCalleeSaveScratch2);
+  }
 }
 
-void Assembler::LeaveCallRuntimeFrame() {
+void Assembler::LeaveCallRuntimeFrame(bool is_leaf) {
   // SP might have been modified to reserve space for arguments
   // and ensure proper alignment of the stack frame.
   // We need to restore it before restoring registers.
+  const intptr_t fixed_frame_words_without_pc_and_fp =
+      target::frame_layout.dart_fixed_frame_size - 2;
   const intptr_t kPushedRegistersSize =
-      kDartVolatileCpuRegCount * target::kWordSize +
-      kDartVolatileFpuRegCount * target::kWordSize +
-      (target::frame_layout.dart_fixed_frame_size - 2) *
-          target::kWordSize;  // From EnterStubFrame (excluding PC / FP)
+      kDartVolatileFpuRegCount * sizeof(double) +
+      (kDartVolatileCpuRegCount + (is_leaf ? 2 : 0) +
+       fixed_frame_words_without_pc_and_fp) *
+          target::kWordSize;
   AddImmediate(SP, FP, -kPushedRegistersSize);
+  if (is_leaf) {
+    PopPair(kCallLeafRuntimeCalleeSaveScratch1,
+            kCallLeafRuntimeCalleeSaveScratch2);
+  }
   for (int i = kDartLastVolatileCpuReg; i >= kDartFirstVolatileCpuReg; i--) {
     const Register reg = static_cast<Register>(i);
     Pop(reg);
@@ -1547,6 +1558,37 @@
   entry.Call(this, argument_count);
 }
 
+void Assembler::CallRuntimeScope::Call(intptr_t argument_count) {
+  assembler_->CallRuntime(entry_, argument_count);
+}
+
+Assembler::CallRuntimeScope::~CallRuntimeScope() {
+  if (preserve_registers_) {
+    assembler_->LeaveCallRuntimeFrame(entry_.is_leaf());
+    if (restore_code_reg_) {
+      assembler_->Pop(CODE_REG);
+    }
+  }
+}
+
+Assembler::CallRuntimeScope::CallRuntimeScope(Assembler* assembler,
+                                              const RuntimeEntry& entry,
+                                              intptr_t frame_size,
+                                              bool preserve_registers,
+                                              const Address* caller)
+    : assembler_(assembler),
+      entry_(entry),
+      preserve_registers_(preserve_registers),
+      restore_code_reg_(caller != nullptr) {
+  if (preserve_registers_) {
+    if (caller != nullptr) {
+      assembler_->Push(CODE_REG);
+      assembler_->ldr(CODE_REG, *caller);
+    }
+    assembler_->EnterCallRuntimeFrame(frame_size, entry.is_leaf());
+  }
+}
+
 void Assembler::EnterStubFrame() {
   EnterDartFrame(0);
 }
diff --git a/runtime/vm/compiler/assembler/assembler_arm64.h b/runtime/vm/compiler/assembler/assembler_arm64.h
index 5c1fb60..c9bd624 100644
--- a/runtime/vm/compiler/assembler/assembler_arm64.h
+++ b/runtime/vm/compiler/assembler/assembler_arm64.h
@@ -1651,10 +1651,51 @@
   void EnterOsrFrame(intptr_t extra_size, Register new_pp = kNoRegister);
   void LeaveDartFrame(RestorePP restore_pp = kRestoreCallerPP);
 
-  void EnterCallRuntimeFrame(intptr_t frame_size);
-  void LeaveCallRuntimeFrame();
   void CallRuntime(const RuntimeEntry& entry, intptr_t argument_count);
 
+  // Helper method for performing runtime calls from callers requiring manual
+  // register preservation is required (e.g. outside IL instructions marked
+  // as calling).
+  class CallRuntimeScope : public ValueObject {
+   public:
+    CallRuntimeScope(Assembler* assembler,
+                     const RuntimeEntry& entry,
+                     intptr_t frame_size,
+                     bool preserve_registers = true)
+        : CallRuntimeScope(assembler,
+                           entry,
+                           frame_size,
+                           preserve_registers,
+                           /*caller=*/nullptr) {}
+
+    CallRuntimeScope(Assembler* assembler,
+                     const RuntimeEntry& entry,
+                     intptr_t frame_size,
+                     Address caller,
+                     bool preserve_registers = true)
+        : CallRuntimeScope(assembler,
+                           entry,
+                           frame_size,
+                           preserve_registers,
+                           &caller) {}
+
+    void Call(intptr_t argument_count);
+
+    ~CallRuntimeScope();
+
+   private:
+    CallRuntimeScope(Assembler* assembler,
+                     const RuntimeEntry& entry,
+                     intptr_t frame_size,
+                     bool preserve_registers,
+                     const Address* caller);
+
+    Assembler* const assembler_;
+    const RuntimeEntry& entry_;
+    const bool preserve_registers_;
+    const bool restore_code_reg_;
+  };
+
   // Set up a stub frame so that the stack traversal code can easily identify
   // a stub frame.
   void EnterStubFrame();
@@ -2402,6 +2443,11 @@
                              CanBeSmi can_be_smi,
                              BarrierFilterMode barrier_filter_mode);
 
+  // Note: leaf call sequence uses some abi callee save registers as scratch
+  // so they should be manually preserved.
+  void EnterCallRuntimeFrame(intptr_t frame_size, bool is_leaf);
+  void LeaveCallRuntimeFrame(bool is_leaf);
+
   friend class dart::FlowGraphCompiler;
   std::function<void(Register reg)> generate_invoke_write_barrier_wrapper_;
   std::function<void()> generate_invoke_array_write_barrier_;
diff --git a/runtime/vm/compiler/runtime_api.cc b/runtime/vm/compiler/runtime_api.cc
index 8a16400..0aecfbd 100644
--- a/runtime/vm/compiler/runtime_api.cc
+++ b/runtime/vm/compiler/runtime_api.cc
@@ -278,6 +278,10 @@
   return target::Thread::OffsetFromThread(runtime_entry_);
 }
 
+bool RuntimeEntry::is_leaf() const {
+  return runtime_entry_->is_leaf();
+}
+
 namespace target {
 
 const word kOldPageSize = dart::kOldPageSize;
diff --git a/runtime/vm/compiler/runtime_api.h b/runtime/vm/compiler/runtime_api.h
index 4d70b77..1a75739 100644
--- a/runtime/vm/compiler/runtime_api.h
+++ b/runtime/vm/compiler/runtime_api.h
@@ -233,6 +233,8 @@
 
   word OffsetFromThread() const;
 
+  bool is_leaf() const;
+
  protected:
   RuntimeEntry(const dart::RuntimeEntry* runtime_entry,
                RuntimeEntryCallInternal call)
diff --git a/runtime/vm/compiler/stub_code_compiler_arm64.cc b/runtime/vm/compiler/stub_code_compiler_arm64.cc
index 6ff81a8..6f29ad2 100644
--- a/runtime/vm/compiler/stub_code_compiler_arm64.cc
+++ b/runtime/vm/compiler/stub_code_compiler_arm64.cc
@@ -49,16 +49,12 @@
   Label done;
   __ tbnz(&done, R0, target::ObjectAlignment::kNewObjectBitPosition);
 
-  if (preserve_registers) {
-    __ EnterCallRuntimeFrame(0);
-  } else {
-    __ ReserveAlignedFrameSpace(0);
-  }
-  // [R0] already contains first argument.
-  __ mov(R1, THR);
-  __ CallRuntime(kEnsureRememberedAndMarkingDeferredRuntimeEntry, 2);
-  if (preserve_registers) {
-    __ LeaveCallRuntimeFrame();
+  {
+    Assembler::CallRuntimeScope scope(
+        assembler, kEnsureRememberedAndMarkingDeferredRuntimeEntry,
+        /*frame_size=*/0, /*preserve_registers=*/preserve_registers);
+    __ mov(R1, THR);
+    scope.Call(/*argument_count=*/2);
   }
 
   __ Bind(&done);
@@ -1973,16 +1969,13 @@
 
   // Handle overflow: Call the runtime leaf function.
   __ Bind(&overflow);
-  // Setup frame, push callee-saved registers.
-
-  __ Push(CODE_REG);
-  __ ldr(CODE_REG, stub_code);
-  __ EnterCallRuntimeFrame(0 * target::kWordSize);
-  __ mov(R0, THR);
-  __ CallRuntime(kStoreBufferBlockProcessRuntimeEntry, 1);
-  // Restore callee-saved registers, tear down frame.
-  __ LeaveCallRuntimeFrame();
-  __ Pop(CODE_REG);
+  {
+    Assembler::CallRuntimeScope scope(assembler,
+                                      kStoreBufferBlockProcessRuntimeEntry,
+                                      /*frame_size=*/0, stub_code);
+    __ mov(R0, THR);
+    scope.Call(/*argument_count=*/1);
+  }
   __ ret();
 
   __ Bind(&add_to_mark_stack);
@@ -2020,13 +2013,13 @@
   __ ret();
 
   __ Bind(&marking_overflow);
-  __ Push(CODE_REG);
-  __ ldr(CODE_REG, stub_code);
-  __ EnterCallRuntimeFrame(0 * target::kWordSize);
-  __ mov(R0, THR);
-  __ CallRuntime(kMarkingStackBlockProcessRuntimeEntry, 1);
-  __ LeaveCallRuntimeFrame();
-  __ Pop(CODE_REG);
+  {
+    Assembler::CallRuntimeScope scope(assembler,
+                                      kMarkingStackBlockProcessRuntimeEntry,
+                                      /*frame_size=*/0, stub_code);
+    __ mov(R0, THR);
+    scope.Call(/*argument_count=*/1);
+  }
   __ ret();
 
   __ Bind(&lost_race);
@@ -2059,16 +2052,13 @@
 
     // Card table not yet allocated.
     __ Bind(&remember_card_slow);
-    __ Push(CODE_REG);
-    __ PushPair(R0, R1);
-    __ ldr(CODE_REG, stub_code);
-    __ mov(R0, R1);   // Arg0 = Object
-    __ mov(R1, R25);  // Arg1 = Slot
-    __ EnterCallRuntimeFrame(0);
-    __ CallRuntime(kRememberCardRuntimeEntry, 2);
-    __ LeaveCallRuntimeFrame();
-    __ PopPair(R0, R1);
-    __ Pop(CODE_REG);
+    {
+      Assembler::CallRuntimeScope scope(assembler, kRememberCardRuntimeEntry,
+                                        /*frame_size=*/0, stub_code);
+      __ mov(R0, R1);   // Arg0 = Object
+      __ mov(R1, R25);  // Arg1 = Slot
+      scope.Call(/*argument_count=*/2);
+    }
     __ ret();
   }
 }
diff --git a/runtime/vm/constants_arm64.h b/runtime/vm/constants_arm64.h
index 1c71baa..b5340bb 100644
--- a/runtime/vm/constants_arm64.h
+++ b/runtime/vm/constants_arm64.h
@@ -242,21 +242,22 @@
 // See "Procedure Call Standard for the ARM 64-bit Architecture", document
 // number "ARM IHI 0055B", May 22 2013.
 
+#define R(REG) (1 << REG)
+
 // C++ ABI call registers.
-const RegList kAbiArgumentCpuRegs = (1 << R0) | (1 << R1) | (1 << R2) |
-                                    (1 << R3) | (1 << R4) | (1 << R5) |
-                                    (1 << R6) | (1 << R7);
+const RegList kAbiArgumentCpuRegs =
+    R(R0) | R(R1) | R(R2) | R(R3) | R(R4) | R(R5) | R(R6) | R(R7);
 #if defined(TARGET_OS_FUCHSIA)
-const RegList kAbiPreservedCpuRegs =
-    (1 << R18) | (1 << R19) | (1 << R20) | (1 << R21) | (1 << R22) |
-    (1 << R23) | (1 << R24) | (1 << R25) | (1 << R26) | (1 << R27) | (1 << R28);
+const RegList kAbiPreservedCpuRegs = R(R18) | R(R19) | R(R20) | R(R21) |
+                                     R(R22) | R(R23) | R(R24) | R(R25) |
+                                     R(R26) | R(R27) | R(R28);
 const Register kAbiFirstPreservedCpuReg = R18;
 const Register kAbiLastPreservedCpuReg = R28;
 const int kAbiPreservedCpuRegCount = 11;
 #else
-const RegList kAbiPreservedCpuRegs =
-    (1 << R19) | (1 << R20) | (1 << R21) | (1 << R22) | (1 << R23) |
-    (1 << R24) | (1 << R25) | (1 << R26) | (1 << R27) | (1 << R28);
+const RegList kAbiPreservedCpuRegs = R(R19) | R(R20) | R(R21) | R(R22) |
+                                     R(R23) | R(R24) | R(R25) | R(R26) |
+                                     R(R27) | R(R28);
 const Register kAbiFirstPreservedCpuReg = R19;
 const Register kAbiLastPreservedCpuReg = R28;
 const int kAbiPreservedCpuRegCount = 10;
@@ -265,11 +266,11 @@
 const VRegister kAbiLastPreservedFpuReg = V15;
 const int kAbiPreservedFpuRegCount = 8;
 
-const intptr_t kReservedCpuRegisters =
-    (1 << SPREG) |  // Dart SP
-    (1 << FPREG) | (1 << TMP) | (1 << TMP2) | (1 << PP) | (1 << THR) |
-    (1 << LR) | (1 << BARRIER_MASK) | (1 << NULL_REG) | (1 << R31) |  // C++ SP
-    (1 << R18) | (1 << DISPATCH_TABLE_REG);
+const intptr_t kReservedCpuRegisters = R(SPREG) |  // Dart SP
+                                       R(FPREG) | R(TMP) | R(TMP2) | R(PP) |
+                                       R(THR) | R(LR) | R(BARRIER_MASK) |
+                                       R(NULL_REG) | R(R31) |  // C++ SP
+                                       R(R18) | R(DISPATCH_TABLE_REG);
 constexpr intptr_t kNumberOfReservedCpuRegisters = 12;
 // CPU registers available to Dart allocator.
 const RegList kDartAvailableCpuRegs =
@@ -284,9 +285,17 @@
 const int kDartVolatileCpuRegCount = 15;
 const int kDartVolatileFpuRegCount = 24;
 
-constexpr int kStoreBufferWrapperSize = 32;
+// Two callee save scratch registers used by leaf runtime call sequence.
+const Register kCallLeafRuntimeCalleeSaveScratch1 = R23;
+const Register kCallLeafRuntimeCalleeSaveScratch2 = R25;
+static_assert((R(kCallLeafRuntimeCalleeSaveScratch1) & kAbiPreservedCpuRegs) !=
+                  0,
+              "Need callee save scratch register for leaf runtime calls.");
+static_assert((R(kCallLeafRuntimeCalleeSaveScratch2) & kAbiPreservedCpuRegs) !=
+                  0,
+              "Need callee save scratch register for leaf runtime calls.");
 
-#define R(REG) (1 << REG)
+constexpr int kStoreBufferWrapperSize = 32;
 
 class CallingConventions {
  public:
diff --git a/runtime/vm/runtime_entry_arm64.cc b/runtime/vm/runtime_entry_arm64.cc
index 129dae3..5fd14b5 100644
--- a/runtime/vm/runtime_entry_arm64.cc
+++ b/runtime/vm/runtime_entry_arm64.cc
@@ -57,12 +57,12 @@
     // call.
     // This sequence may occur in an intrinsic, so don't use registers an
     // intrinsic must preserve.
-    COMPILE_ASSERT(R23 != CODE_REG);
-    COMPILE_ASSERT(R25 != CODE_REG);
-    COMPILE_ASSERT(R23 != ARGS_DESC_REG);
-    COMPILE_ASSERT(R25 != ARGS_DESC_REG);
-    __ mov(R23, CSP);
-    __ mov(R25, SP);
+    COMPILE_ASSERT(kCallLeafRuntimeCalleeSaveScratch1 != CODE_REG);
+    COMPILE_ASSERT(kCallLeafRuntimeCalleeSaveScratch2 != CODE_REG);
+    COMPILE_ASSERT(kCallLeafRuntimeCalleeSaveScratch1 != ARGS_DESC_REG);
+    COMPILE_ASSERT(kCallLeafRuntimeCalleeSaveScratch2 != ARGS_DESC_REG);
+    __ mov(kCallLeafRuntimeCalleeSaveScratch1, CSP);
+    __ mov(kCallLeafRuntimeCalleeSaveScratch2, SP);
     __ ReserveAlignedFrameSpace(0);
     __ mov(CSP, SP);
     __ ldr(TMP,
@@ -71,8 +71,8 @@
     __ blr(TMP);
     __ LoadImmediate(TMP, VMTag::kDartCompiledTagId);
     __ str(TMP, compiler::Address(THR, Thread::vm_tag_offset()));
-    __ mov(SP, R25);
-    __ mov(CSP, R23);
+    __ mov(SP, kCallLeafRuntimeCalleeSaveScratch2);
+    __ mov(CSP, kCallLeafRuntimeCalleeSaveScratch1);
     ASSERT((kAbiPreservedCpuRegs & (1 << THR)) != 0);
     ASSERT((kAbiPreservedCpuRegs & (1 << PP)) != 0);
   } else {
diff --git a/tools/VERSION b/tools/VERSION
index 72808e9..06eb9dd 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 10
 PATCH 0
-PRERELEASE 116
+PRERELEASE 117
 PRERELEASE_PATCH 0
\ No newline at end of file