[vm/arm/arm64] Remove unpredictable (and sometimes illegal) instructions on ARM[64].

Also strengthen asserts to avoid regression.

Change-Id: I93ed9dac5cda9a01eba6ecdd19f78b11ed584114
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/106904
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Commit-Queue: Samir Jindel <sjindel@google.com>
diff --git a/runtime/vm/compiler/assembler/assembler_arm.cc b/runtime/vm/compiler/assembler/assembler_arm.cc
index 1c06796..0898d42 100644
--- a/runtime/vm/compiler/assembler/assembler_arm.cc
+++ b/runtime/vm/compiler/assembler/assembler_arm.cc
@@ -131,7 +131,8 @@
                           Address ad) {
   ASSERT(rd != kNoRegister);
   ASSERT(cond != kNoCondition);
-  ASSERT(!ad.has_writeback() || (ad.rn() != rd));  // Unpredictable.
+  // Unpredictable, illegal on some microarchitectures.
+  ASSERT(!ad.has_writeback() || (ad.rn() != rd));
 
   int32_t encoding = (static_cast<int32_t>(cond) << kConditionShift) | B26 |
                      (ad.kind() == Address::Immediate ? 0 : B25) |
@@ -146,6 +147,9 @@
                                       Address ad) {
   ASSERT(rd != kNoRegister);
   ASSERT(cond != kNoCondition);
+  // Unpredictable, illegal on some microarchitectures.
+  ASSERT(!ad.has_writeback() || (ad.rn() != rd));
+
   int32_t encoding = (static_cast<int32_t>(cond) << kConditionShift) | mode |
                      ArmEncode::Rd(rd) | ad.encoding3();
   Emit(encoding);
@@ -158,6 +162,8 @@
                                RegList regs) {
   ASSERT(base != kNoRegister);
   ASSERT(cond != kNoCondition);
+  // Unpredictable, illegal on some microarchitectures.
+  ASSERT(!Address::has_writeback(am) || !(regs & (1 << base)));
   int32_t encoding = (static_cast<int32_t>(cond) << kConditionShift) | B27 |
                      am | (load ? L : 0) | ArmEncode::Rn(base) | regs;
   Emit(encoding);
diff --git a/runtime/vm/compiler/assembler/assembler_arm.h b/runtime/vm/compiler/assembler/assembler_arm.h
index 19ba44f..13ea001 100644
--- a/runtime/vm/compiler/assembler/assembler_arm.h
+++ b/runtime/vm/compiler/assembler/assembler_arm.h
@@ -310,6 +310,21 @@
            (mode() == NegPreIndex) || (mode() == NegPostIndex);
   }
 
+  static bool has_writeback(BlockAddressMode am) {
+    switch (am) {
+      case DA:
+      case IA:
+      case DB:
+      case IB:
+        return false;
+      case DA_W:
+      case IA_W:
+      case DB_W:
+      case IB_W:
+        return true;
+    }
+  }
+
   uint32_t encoding() const { return encoding_; }
 
   // Encoding for addressing mode 3.
diff --git a/runtime/vm/compiler/assembler/assembler_arm64.h b/runtime/vm/compiler/assembler/assembler_arm64.h
index b8808f5..26f8cb9 100644
--- a/runtime/vm/compiler/assembler/assembler_arm64.h
+++ b/runtime/vm/compiler/assembler/assembler_arm64.h
@@ -110,6 +110,17 @@
     Unknown,
   };
 
+  // If we are doing pre-/post-indexing, and the base and result registers are
+  // the same, then the result is unpredictable. This kind of instruction is
+  // actually illegal on some microarchitectures.
+  bool can_writeback_to(Register r) const {
+    if (type() == PreIndex || type() == PostIndex || type() == PairPreIndex ||
+        type() == PairPostIndex) {
+      return base() != r;
+    }
+    return true;
+  }
+
   // Offset is in bytes. For the unsigned imm12 case, we unscale based on the
   // operand size, and assert that offset is aligned accordingly.
   // For the smaller signed imm9 case, the offset is the number of bytes, but
@@ -869,12 +880,6 @@
       ASSERT(sz == kDoubleWord);
       EmitLoadRegLiteral(LDRpc, rt, a, sz);
     } else {
-      // If we are doing pre-/post-indexing, and the base and result registers
-      // are the same, then the result of the load will be clobbered by the
-      // writeback, which is unlikely to be useful.
-      ASSERT(((a.type() != Address::PreIndex) &&
-              (a.type() != Address::PostIndex)) ||
-             (rt != a.base()));
       if (IsSignedOperand(sz)) {
         EmitLoadStoreReg(LDRS, rt, a, sz);
       } else {
@@ -2067,6 +2072,9 @@
                         Register rt,
                         Address a,
                         OperandSize sz) {
+    // Unpredictable, illegal on some microarchitectures.
+    ASSERT((op != LDR && op != STR && op != LDRS) || a.can_writeback_to(rt));
+
     const int32_t size = Log2OperandSizeBytes(sz);
     const int32_t encoding =
         op | ((size & 0x3) << kSzShift) | Arm64Encode::Rt(rt) | a.encoding();
@@ -2089,6 +2097,10 @@
                             Register rt2,
                             Address a,
                             OperandSize sz) {
+    // Unpredictable, illegal on some microarchitectures.
+    ASSERT(a.can_writeback_to(rt) && a.can_writeback_to(rt2));
+    ASSERT(op != LDP || rt != rt2);
+
     ASSERT((sz == kDoubleWord) || (sz == kWord) || (sz == kUnsignedWord));
     ASSERT((rt != CSP) && (rt != R31));
     ASSERT((rt2 != CSP) && (rt2 != R31));
diff --git a/runtime/vm/compiler/stub_code_compiler_arm64.cc b/runtime/vm/compiler/stub_code_compiler_arm64.cc
index 2f3b1b0..26d8c00 100644
--- a/runtime/vm/compiler/stub_code_compiler_arm64.cc
+++ b/runtime/vm/compiler/stub_code_compiler_arm64.cc
@@ -782,6 +782,12 @@
       COMPILE_ASSERT(R25 > CODE_REG);
       __ ldr(R25, Address(FP, 2 * target::kWordSize));
       __ str(R25, Address(SP, -1 * target::kWordSize, Address::PreIndex));
+    } else if (r == R15) {
+      // Because we save registers in decreasing order, IP0 will already be
+      // saved.
+      COMPILE_ASSERT(IP0 == R16);
+      __ mov(IP0, R15);
+      __ str(IP0, Address(SP, -1 * target::kWordSize, Address::PreIndex));
     } else {
       __ str(r, Address(SP, -1 * target::kWordSize, Address::PreIndex));
     }