x86_64: add a helper function that displaces an address by a given amount.

The new function will be used in an follow up CL.

Add a gtest that constructs addresses with some initial displacement and
checks that they are equal to addresses constructed in the same way only
without displacement, and then displaced.

Bug: 65872996
Test: m test-art-host-gtest  # changes covered by the new test
Change-Id: Id282707077b513bf5911afad1cdd481a1611b55c
diff --git a/compiler/utils/x86_64/assembler_x86_64.h b/compiler/utils/x86_64/assembler_x86_64.h
index e5a9ce4..5bc6a3d 100644
--- a/compiler/utils/x86_64/assembler_x86_64.h
+++ b/compiler/utils/x86_64/assembler_x86_64.h
@@ -112,6 +112,22 @@
     return value;
   }
 
+  int32_t disp() const {
+    switch (mod()) {
+      case 0:
+        // With mod 00b RBP is special and means disp32 (either in r/m or in SIB base).
+        return (rm() == RBP || (rm() == RSP && base() == RBP)) ? disp32() : 0;
+      case 1:
+        return disp8();
+      case 2:
+        return disp32();
+      default:
+        // Mod 11b means reg/reg, so there is no address and consequently no displacement.
+        DCHECK(false) << "there is no displacement in x86_64 reg/reg operand";
+        UNREACHABLE();
+    }
+  }
+
   bool IsRegister(CpuRegister reg) const {
     return ((encoding_[0] & 0xF8) == 0xC0)  // Addressing mode is register only.
         && ((encoding_[0] & 0x07) == reg.LowBits())  // Register codes match.
@@ -122,6 +138,13 @@
     return fixup_;
   }
 
+  inline bool operator==(const Operand &op) const {
+    return rex_ == op.rex_ &&
+        length_ == op.length_ &&
+        memcmp(encoding_, op.encoding_, length_) == 0 &&
+        fixup_ == op.fixup_;
+  }
+
  protected:
   // Operand can be sub classed (e.g: Address).
   Operand() : rex_(0), length_(0), fixup_(nullptr) { }
@@ -224,7 +247,6 @@
     }
   }
 
-
   Address(CpuRegister index_in, ScaleFactor scale_in, int32_t disp) {
     CHECK_NE(index_in.AsRegister(), RSP);  // Illegal addressing mode.
     SetModRM(0, CpuRegister(RSP));
@@ -280,6 +302,50 @@
     return Absolute(addr.Int32Value(), no_rip);
   }
 
+  // Break the address into pieces and reassemble it again with a new displacement.
+  // Note that it may require a new addressing mode if displacement size is changed.
+  static Address displace(const Address &addr, int32_t disp) {
+    const int32_t new_disp = addr.disp() + disp;
+    const bool sib = addr.rm() == RSP;
+    const bool rbp = RBP == (sib ? addr.base() : addr.rm());
+    Address new_addr;
+    if (addr.mod() == 0 && rbp) {
+      // Special case: mod 00b and RBP in r/m or SIB base => 32-bit displacement.
+      // This case includes RIP-relative addressing.
+      new_addr.SetModRM(0, addr.cpu_rm());
+      if (sib) {
+        new_addr.SetSIB(addr.scale(), addr.cpu_index(), addr.cpu_base());
+      }
+      new_addr.SetDisp32(new_disp);
+    } else if (new_disp == 0 && !rbp) {
+      // Mod 00b (excluding a special case for RBP) => no displacement.
+      new_addr.SetModRM(0, addr.cpu_rm());
+      if (sib) {
+        new_addr.SetSIB(addr.scale(), addr.cpu_index(), addr.cpu_base());
+      }
+    } else if (new_disp >= -128 && new_disp <= 127) {
+      // Mod 01b => 8-bit displacement.
+      new_addr.SetModRM(1, addr.cpu_rm());
+      if (sib) {
+        new_addr.SetSIB(addr.scale(), addr.cpu_index(), addr.cpu_base());
+      }
+      new_addr.SetDisp8(new_disp);
+    } else {
+      // Mod 10b => 32-bit displacement.
+      new_addr.SetModRM(2, addr.cpu_rm());
+      if (sib) {
+        new_addr.SetSIB(addr.scale(), addr.cpu_index(), addr.cpu_base());
+      }
+      new_addr.SetDisp32(new_disp);
+    }
+    new_addr.SetFixup(addr.GetFixup());
+    return new_addr;
+  }
+
+  inline bool operator==(const Address& addr) const {
+    return static_cast<const Operand&>(*this) == static_cast<const Operand&>(addr);
+  }
+
  private:
   Address() {}
 };
diff --git a/compiler/utils/x86_64/assembler_x86_64_test.cc b/compiler/utils/x86_64/assembler_x86_64_test.cc
index 5952a6d..4f37f46 100644
--- a/compiler/utils/x86_64/assembler_x86_64_test.cc
+++ b/compiler/utils/x86_64/assembler_x86_64_test.cc
@@ -2314,6 +2314,51 @@
                      "testl ${imm}, {mem}"), "testli");
 }
 
+// Test that displacing an existing address is the same as constructing a new one with the same
+// initial displacement.
+TEST_F(AssemblerX86_64Test, AddressDisplaceBy) {
+  // Test different displacements, including some 8-bit and 32-bit ones, so that changing
+  // displacement may require a different addressing mode.
+  static const std::vector<int32_t> displacements = {0, 42, -42, 140, -140};
+  // Test with all scale factors.
+  static const std::vector<ScaleFactor> scales = {TIMES_1, TIMES_2, TIMES_4, TIMES_8};
+
+  for (int32_t disp0 : displacements) {  // initial displacement
+    for (int32_t disp : displacements) {  // extra displacement
+      for (const x86_64::CpuRegister* reg : GetRegisters()) {
+        // Test non-SIB addressing.
+        EXPECT_EQ(x86_64::Address::displace(x86_64::Address(*reg, disp0), disp),
+                  x86_64::Address(*reg, disp0 + disp));
+
+        // Test SIB addressing with RBP base.
+        if (reg->AsRegister() != x86_64::RSP) {
+          for (ScaleFactor scale : scales) {
+            EXPECT_EQ(x86_64::Address::displace(x86_64::Address(*reg, scale, disp0), disp),
+                      x86_64::Address(*reg, scale, disp0 + disp));
+          }
+        }
+
+        // Test SIB addressing with different base.
+        for (const x86_64::CpuRegister* index : GetRegisters()) {
+          if (index->AsRegister() == x86_64::RSP) {
+            continue;  // Skip RSP as it cannot be used with this address constructor.
+          }
+          for (ScaleFactor scale : scales) {
+            EXPECT_EQ(x86_64::Address::displace(x86_64::Address(*reg, *index, scale, disp0), disp),
+                      x86_64::Address(*reg, *index, scale, disp0 + disp));
+          }
+        }
+
+        // Test absolute and RIP-relative addressing.
+        EXPECT_EQ(x86_64::Address::displace(x86_64::Address::Absolute(disp0, false), disp),
+                  x86_64::Address::Absolute(disp0 + disp, false));
+        EXPECT_EQ(x86_64::Address::displace(x86_64::Address::Absolute(disp0, true), disp),
+                  x86_64::Address::Absolute(disp0 + disp, true));
+      }
+    }
+  }
+}
+
 class JNIMacroAssemblerX86_64Test : public JNIMacroAssemblerTest<x86_64::X86_64JNIMacroAssembler> {
  public:
   using Base = JNIMacroAssemblerTest<x86_64::X86_64JNIMacroAssembler>;