[SubZero] Implement address optimization for MIPS

The patch implements address optimization for MIPS.

R=stichnot@chromium.org

Review URL: https://codereview.chromium.org/2313293002 .

Patch from Jaydeep Patil <jaydeep.patil@imgtec.com>.
diff --git a/src/IceTargetLoweringMIPS32.cpp b/src/IceTargetLoweringMIPS32.cpp
index 6f602c9..237eef4 100644
--- a/src/IceTargetLoweringMIPS32.cpp
+++ b/src/IceTargetLoweringMIPS32.cpp
@@ -2889,7 +2889,234 @@
   lowerAssign(Assign);
 }
 
-void TargetMIPS32::doAddressOptLoad() { UnimplementedError(getFlags()); }
+namespace {
+void dumpAddressOpt(const Cfg *Func, const Variable *Base, int32_t Offset,
+                    const Inst *Reason) {
+  if (!BuildDefs::dump())
+    return;
+  if (!Func->isVerbose(IceV_AddrOpt))
+    return;
+  OstreamLocker _(Func->getContext());
+  Ostream &Str = Func->getContext()->getStrDump();
+  Str << "Instruction: ";
+  Reason->dumpDecorated(Func);
+  Str << "  results in Base=";
+  if (Base)
+    Base->dump(Func);
+  else
+    Str << "<null>";
+  Str << ", Offset=" << Offset << "\n";
+}
+
+bool matchAssign(const VariablesMetadata *VMetadata, Variable **Var,
+                 int32_t *Offset, const Inst **Reason) {
+  // Var originates from Var=SrcVar ==> set Var:=SrcVar
+  if (*Var == nullptr)
+    return false;
+  const Inst *VarAssign = VMetadata->getSingleDefinition(*Var);
+  if (!VarAssign)
+    return false;
+  assert(!VMetadata->isMultiDef(*Var));
+  if (!llvm::isa<InstAssign>(VarAssign))
+    return false;
+
+  Operand *SrcOp = VarAssign->getSrc(0);
+  bool Optimized = false;
+  if (auto *SrcVar = llvm::dyn_cast<Variable>(SrcOp)) {
+    if (!VMetadata->isMultiDef(SrcVar) ||
+        // TODO: ensure SrcVar stays single-BB
+        false) {
+      Optimized = true;
+      *Var = SrcVar;
+    } else if (auto *Const = llvm::dyn_cast<ConstantInteger32>(SrcOp)) {
+      int32_t MoreOffset = Const->getValue();
+      int32_t NewOffset = MoreOffset + *Offset;
+      if (Utils::WouldOverflowAdd(*Offset, MoreOffset))
+        return false;
+      *Var = nullptr;
+      *Offset += NewOffset;
+      Optimized = true;
+    }
+  }
+
+  if (Optimized) {
+    *Reason = VarAssign;
+  }
+
+  return Optimized;
+}
+
+bool isAddOrSub(const Inst *Instr, InstArithmetic::OpKind *Kind) {
+  if (const auto *Arith = llvm::dyn_cast<InstArithmetic>(Instr)) {
+    switch (Arith->getOp()) {
+    default:
+      return false;
+    case InstArithmetic::Add:
+    case InstArithmetic::Sub:
+      *Kind = Arith->getOp();
+      return true;
+    }
+  }
+  return false;
+}
+
+bool matchOffsetBase(const VariablesMetadata *VMetadata, Variable **Base,
+                     int32_t *Offset, const Inst **Reason) {
+  // Base is Base=Var+Const || Base is Base=Const+Var ==>
+  //   set Base=Var, Offset+=Const
+  // Base is Base=Var-Const ==>
+  //   set Base=Var, Offset-=Const
+  if (*Base == nullptr)
+    return false;
+  const Inst *BaseInst = VMetadata->getSingleDefinition(*Base);
+  if (BaseInst == nullptr) {
+    return false;
+  }
+  assert(!VMetadata->isMultiDef(*Base));
+
+  auto *ArithInst = llvm::dyn_cast<const InstArithmetic>(BaseInst);
+  if (ArithInst == nullptr)
+    return false;
+  InstArithmetic::OpKind Kind;
+  if (!isAddOrSub(ArithInst, &Kind))
+    return false;
+  bool IsAdd = Kind == InstArithmetic::Add;
+  Operand *Src0 = ArithInst->getSrc(0);
+  Operand *Src1 = ArithInst->getSrc(1);
+  auto *Var0 = llvm::dyn_cast<Variable>(Src0);
+  auto *Var1 = llvm::dyn_cast<Variable>(Src1);
+  auto *Const0 = llvm::dyn_cast<ConstantInteger32>(Src0);
+  auto *Const1 = llvm::dyn_cast<ConstantInteger32>(Src1);
+  Variable *NewBase = nullptr;
+  int32_t NewOffset = *Offset;
+
+  if (Var0 == nullptr && Const0 == nullptr) {
+    assert(llvm::isa<ConstantRelocatable>(Src0));
+    return false;
+  }
+
+  if (Var1 == nullptr && Const1 == nullptr) {
+    assert(llvm::isa<ConstantRelocatable>(Src1));
+    return false;
+  }
+
+  if (Var0 && Var1)
+    // TODO(jpp): merge base/index splitting into here.
+    return false;
+  if (!IsAdd && Var1)
+    return false;
+  if (Var0)
+    NewBase = Var0;
+  else if (Var1)
+    NewBase = Var1;
+  // Compute the updated constant offset.
+  if (Const0) {
+    int32_t MoreOffset = IsAdd ? Const0->getValue() : -Const0->getValue();
+    if (Utils::WouldOverflowAdd(NewOffset, MoreOffset))
+      return false;
+    NewOffset += MoreOffset;
+  }
+  if (Const1) {
+    int32_t MoreOffset = IsAdd ? Const1->getValue() : -Const1->getValue();
+    if (Utils::WouldOverflowAdd(NewOffset, MoreOffset))
+      return false;
+    NewOffset += MoreOffset;
+  }
+
+  // Update the computed address parameters once we are sure optimization
+  // is valid.
+  *Base = NewBase;
+  *Offset = NewOffset;
+  *Reason = BaseInst;
+  return true;
+}
+} // end of anonymous namespace
+
+OperandMIPS32Mem *TargetMIPS32::formAddressingMode(Type Ty, Cfg *Func,
+                                                   const Inst *LdSt,
+                                                   Operand *Base) {
+  assert(Base != nullptr);
+  int32_t OffsetImm = 0;
+
+  Func->resetCurrentNode();
+  if (Func->isVerbose(IceV_AddrOpt)) {
+    OstreamLocker _(Func->getContext());
+    Ostream &Str = Func->getContext()->getStrDump();
+    Str << "\nAddress mode formation:\t";
+    LdSt->dumpDecorated(Func);
+  }
+
+  if (isVectorType(Ty)) {
+    UnimplementedError(getFlags());
+    return nullptr;
+  }
+
+  auto *BaseVar = llvm::dyn_cast<Variable>(Base);
+  if (BaseVar == nullptr)
+    return nullptr;
+
+  const VariablesMetadata *VMetadata = Func->getVMetadata();
+  const Inst *Reason = nullptr;
+
+  do {
+    if (Reason != nullptr) {
+      dumpAddressOpt(Func, BaseVar, OffsetImm, Reason);
+      Reason = nullptr;
+    }
+
+    if (matchAssign(VMetadata, &BaseVar, &OffsetImm, &Reason)) {
+      continue;
+    }
+
+    if (matchOffsetBase(VMetadata, &BaseVar, &OffsetImm, &Reason)) {
+      continue;
+    }
+  } while (Reason);
+
+  if (BaseVar == nullptr) {
+    // We need base register rather than just OffsetImm. Move the OffsetImm to
+    // BaseVar and form 0(BaseVar) addressing.
+    const Type PointerType = getPointerType();
+    BaseVar = makeReg(PointerType);
+    Context.insert<InstAssign>(BaseVar, Ctx->getConstantInt32(OffsetImm));
+    OffsetImm = 0;
+  } else if (OffsetImm != 0) {
+    // If the OffsetImm is more than signed 16-bit value then add it in the
+    // BaseVar and form 0(BaseVar) addressing.
+    const int32_t PositiveOffset = OffsetImm > 0 ? OffsetImm : -OffsetImm;
+    const InstArithmetic::OpKind Op =
+        OffsetImm > 0 ? InstArithmetic::Add : InstArithmetic::Sub;
+    constexpr bool ZeroExt = false;
+    if (!OperandMIPS32Mem::canHoldOffset(Ty, ZeroExt, OffsetImm)) {
+      const Type PointerType = getPointerType();
+      Variable *T = makeReg(PointerType);
+      Context.insert<InstArithmetic>(Op, T, BaseVar,
+                                     Ctx->getConstantInt32(PositiveOffset));
+      BaseVar = T;
+      OffsetImm = 0;
+    }
+  }
+
+  assert(BaseVar != nullptr);
+  assert(OffsetImm < 0 ? (-OffsetImm & 0x0000ffff) == -OffsetImm
+                       : (OffsetImm & 0x0000ffff) == OffsetImm);
+
+  return OperandMIPS32Mem::create(
+      Func, Ty, BaseVar,
+      llvm::cast<ConstantInteger32>(Ctx->getConstantInt32(OffsetImm)));
+}
+
+void TargetMIPS32::doAddressOptLoad() {
+  Inst *Instr = iteratorToInst(Context.getCur());
+  assert(llvm::isa<InstLoad>(Instr));
+  Variable *Dest = Instr->getDest();
+  Operand *Addr = Instr->getSrc(0);
+  if (OperandMIPS32Mem *Mem =
+          formAddressingMode(Dest->getType(), Func, Instr, Addr)) {
+    Instr->setDeleted();
+    Context.insert<InstLoad>(Dest, Mem);
+  }
+}
 
 void TargetMIPS32::randomlyInsertNop(float Probability,
                                      RandomNumberGenerator &RNG) {
@@ -2959,7 +3186,17 @@
   }
 }
 
-void TargetMIPS32::doAddressOptStore() { UnimplementedError(getFlags()); }
+void TargetMIPS32::doAddressOptStore() {
+  Inst *Instr = iteratorToInst(Context.getCur());
+  assert(llvm::isa<InstStore>(Instr));
+  Operand *Src = Instr->getSrc(0);
+  Operand *Addr = Instr->getSrc(1);
+  if (OperandMIPS32Mem *Mem =
+          formAddressingMode(Src->getType(), Func, Instr, Addr)) {
+    Instr->setDeleted();
+    Context.insert<InstStore>(Src, Mem);
+  }
+}
 
 void TargetMIPS32::lowerSwitch(const InstSwitch *Instr) {
   Operand *Src = Instr->getComparison();
diff --git a/src/IceTargetLoweringMIPS32.h b/src/IceTargetLoweringMIPS32.h
index 5b722f6..717b7d9 100644
--- a/src/IceTargetLoweringMIPS32.h
+++ b/src/IceTargetLoweringMIPS32.h
@@ -714,6 +714,9 @@
 private:
   ENABLE_MAKE_UNIQUE;
 
+  OperandMIPS32Mem *formAddressingMode(Type Ty, Cfg *Func, const Inst *LdSt,
+                                       Operand *Base);
+
   class ComputationTracker {
   public:
     ComputationTracker() = default;
diff --git a/tests_lit/llvm2ice_tests/mips-address-mode-opt.ll b/tests_lit/llvm2ice_tests/mips-address-mode-opt.ll
new file mode 100644
index 0000000..1cb53c1
--- /dev/null
+++ b/tests_lit/llvm2ice_tests/mips-address-mode-opt.ll
@@ -0,0 +1,44 @@
+; This file checks support for address mode optimization.
+; This test file is same as address-mode-opt.ll however the functions in this
+; file are relevant to MIPS only.
+
+; RUN: %if --need=target_MIPS32 --need=allow_dump \
+; RUN:   --command %p2i --filetype=asm --assemble --disassemble \
+; RUN:   --target mips32 -i %s --args -O2 --skip-unimplemented \
+; RUN:   -allow-externally-defined-symbols \
+; RUN:   | %if --need=target_MIPS32 --need=allow_dump \
+; RUN:     --command FileCheck --check-prefix MIPS32 %s
+
+define internal float @load_arg_plus_offset(float* %arg) {
+entry:
+  %arg.int = ptrtoint float* %arg to i32
+  %addr.int = add i32 %arg.int, 16
+  %addr.ptr = inttoptr i32 %addr.int to float*
+  %addr.load = load float, float* %addr.ptr, align 4
+  ret float %addr.load
+}
+; MIPS32-LABEL: load_arg_plus_offset
+; MIPS32: lwc1 $f0,16(a0)
+
+define internal float @load_arg_minus_offset(float* %arg) {
+entry:
+  %arg.int = ptrtoint float* %arg to i32
+  %addr.int = sub i32 %arg.int, 16
+  %addr.ptr = inttoptr i32 %addr.int to float*
+  %addr.load = load float, float* %addr.ptr, align 4
+  ret float %addr.load
+}
+; MIPS32-LABEL: load_arg_minus_offset
+; MIPS32 lwc1 $f0,-16(a0)
+
+define internal float @address_mode_opt_chaining(float* %arg) {
+entry:
+  %arg.int = ptrtoint float* %arg to i32
+  %addr1.int = add i32 12, %arg.int
+  %addr2.int = sub i32 %addr1.int, 4
+  %addr2.ptr = inttoptr i32 %addr2.int to float*
+  %addr2.load = load float, float* %addr2.ptr, align 4
+  ret float %addr2.load
+}
+; MIPS32-LABEL: address_mode_opt_chaining
+; MIPS32 lwc1 $f0,8(a0)