Commit b3401d27 by Jan Voung

Subzero ARM: lowerArguments (GPR), basic legalize(), and lowerRet(i32, i64).

Adds basic assignment instructions, mov, movn, movw, movt, ldr, etc. in order to copy around the first few integer (i32, i64) arguments out of r0 - r3, and then return then. The "mov" instruction is a bit special and can actually be a "str" when the dest is a stack slot. Model the Memory operand types, and the "flexible Operand2". Add a few tests demonstrating the flexibility of the immediate encoding. BUG= https://code.google.com/p/nativeclient/issues/detail?id=4076 R=stichnot@chromium.org Review URL: https://codereview.chromium.org/1127963004
parent a59ae6ff
......@@ -873,4 +873,20 @@ void InstTarget::dump(const Cfg *Func) const {
Inst::dump(Func);
}
bool checkForRedundantAssign(const Variable *Dest, const Operand *Source) {
const auto SrcVar = llvm::dyn_cast<const Variable>(Source);
if (!SrcVar)
return false;
if (Dest->hasReg() && Dest->getRegNum() == SrcVar->getRegNum()) {
// TODO: On x86-64, instructions like "mov eax, eax" are used to
// clear the upper 32 bits of rax. We need to recognize and
// preserve these.
return true;
}
if (!Dest->hasReg() && !SrcVar->hasReg() &&
Dest->getStackOffset() == SrcVar->getStackOffset())
return true;
return false;
}
} // end of namespace Ice
......@@ -67,6 +67,10 @@ public:
FakeKill, // not part of LLVM/PNaCl bitcode
Target // target-specific low-level ICE
// Anything >= Target is an InstTarget subclass.
// Note that the value-spaces are shared across targets.
// To avoid confusion over the definition of shared values,
// an object specific to one target should never be passed
// to a different target.
};
InstKind getKind() const { return Kind; }
......@@ -926,6 +930,8 @@ protected:
~InstTarget() override {}
};
bool checkForRedundantAssign(const Variable *Dest, const Operand *Source);
} // end of namespace Ice
namespace llvm {
......
......@@ -37,12 +37,57 @@ const struct TypeARM32Attributes_ {
#undef X
};
const struct InstARM32ShiftAttributes_ {
const char *EmitString;
} InstARM32ShiftAttributes[] = {
#define X(tag, emit) \
{ emit } \
,
ICEINSTARM32SHIFT_TABLE
#undef X
};
} // end of anonymous namespace
const char *InstARM32::getWidthString(Type Ty) {
return TypeARM32Attributes[Ty].WidthString;
}
void emitTwoAddr(const char *Opcode, const Inst *Inst, const Cfg *Func) {
if (!ALLOW_DUMP)
return;
Ostream &Str = Func->getContext()->getStrEmit();
assert(Inst->getSrcSize() == 2);
Variable *Dest = Inst->getDest();
assert(Dest == Inst->getSrc(0));
Operand *Src1 = Inst->getSrc(1);
Str << "\t" << Opcode << "\t";
Dest->emit(Func);
Str << ", ";
Src1->emit(Func);
}
OperandARM32Mem::OperandARM32Mem(Cfg * /* Func */, Type Ty, Variable *Base,
ConstantInteger32 *ImmOffset, AddrMode Mode)
: OperandARM32(kMem, Ty), Base(Base), ImmOffset(ImmOffset), Index(nullptr),
ShiftOp(kNoShift), ShiftAmt(0), Mode(Mode) {
// The Neg modes are only needed for Reg +/- Reg.
assert(!isNegAddrMode());
NumVars = 1;
Vars = &this->Base;
}
OperandARM32Mem::OperandARM32Mem(Cfg *Func, Type Ty, Variable *Base,
Variable *Index, ShiftKind ShiftOp,
uint16_t ShiftAmt, AddrMode Mode)
: OperandARM32(kMem, Ty), Base(Base), ImmOffset(0), Index(Index),
ShiftOp(ShiftOp), ShiftAmt(ShiftAmt), Mode(Mode) {
NumVars = 2;
Vars = Func->allocateArrayOf<Variable *>(2);
Vars[0] = Base;
Vars[1] = Index;
}
bool OperandARM32Mem::canHoldOffset(Type Ty, bool SignExt, int32_t Offset) {
int32_t Bits = SignExt ? TypeARM32Attributes[Ty].SExtAddrOffsetBits
: TypeARM32Attributes[Ty].ZExtAddrOffsetBits;
......@@ -55,6 +100,52 @@ bool OperandARM32Mem::canHoldOffset(Type Ty, bool SignExt, int32_t Offset) {
return Utils::IsAbsoluteUint(Bits, Offset);
}
OperandARM32FlexImm::OperandARM32FlexImm(Cfg * /* Func */, Type Ty,
uint32_t Imm, uint32_t RotateAmt)
: OperandARM32Flex(kFlexImm, Ty), Imm(Imm), RotateAmt(RotateAmt) {
NumVars = 0;
Vars = nullptr;
}
bool OperandARM32FlexImm::canHoldImm(uint32_t Immediate, uint32_t *RotateAmt,
uint32_t *Immed_8) {
// Avoid the more expensive test for frequent small immediate values.
if (Immediate <= 0xFF) {
*RotateAmt = 0;
*Immed_8 = Immediate;
return true;
}
// Note that immediate must be unsigned for the test to work correctly.
for (int Rot = 1; Rot < 16; Rot++) {
uint32_t Imm8 = Utils::rotateLeft32(Immediate, 2 * Rot);
if (Imm8 <= 0xFF) {
*RotateAmt = Rot;
*Immed_8 = Imm8;
return true;
}
}
return false;
}
OperandARM32FlexReg::OperandARM32FlexReg(Cfg *Func, Type Ty, Variable *Reg,
ShiftKind ShiftOp, Operand *ShiftAmt)
: OperandARM32Flex(kFlexReg, Ty), Reg(Reg), ShiftOp(ShiftOp),
ShiftAmt(ShiftAmt) {
NumVars = 1;
Variable *ShiftVar = llvm::dyn_cast_or_null<Variable>(ShiftAmt);
if (ShiftVar)
++NumVars;
Vars = Func->allocateArrayOf<Variable *>(NumVars);
Vars[0] = Reg;
if (ShiftVar)
Vars[1] = ShiftVar;
}
InstARM32Ldr::InstARM32Ldr(Cfg *Func, Variable *Dest, OperandARM32Mem *Mem)
: InstARM32(Func, InstARM32::Ldr, 1, Dest) {
addSource(Mem);
}
InstARM32Ret::InstARM32Ret(Cfg *Func, Variable *LR, Variable *Source)
: InstARM32(Func, InstARM32::Ret, Source ? 2 : 1, nullptr) {
addSource(LR);
......@@ -64,6 +155,14 @@ InstARM32Ret::InstARM32Ret(Cfg *Func, Variable *LR, Variable *Source)
// ======================== Dump routines ======================== //
// Two-addr ops
template <> const char *InstARM32Movt::Opcode = "movt";
// Unary ops
template <> const char *InstARM32Movw::Opcode = "movw";
template <> const char *InstARM32Mvn::Opcode = "mvn";
// Mov-like ops
template <> const char *InstARM32Mov::Opcode = "mov";
void InstARM32::dump(const Cfg *Func) const {
if (!ALLOW_DUMP)
return;
......@@ -72,6 +171,101 @@ void InstARM32::dump(const Cfg *Func) const {
Inst::dump(Func);
}
template <> void InstARM32Mov::emit(const Cfg *Func) const {
if (!ALLOW_DUMP)
return;
Ostream &Str = Func->getContext()->getStrEmit();
assert(getSrcSize() == 1);
Variable *Dest = getDest();
if (Dest->hasReg()) {
Str << "\t"
<< "mov"
<< "\t";
getDest()->emit(Func);
Str << ", ";
getSrc(0)->emit(Func);
} else {
Variable *Src0 = llvm::cast<Variable>(getSrc(0));
assert(Src0->hasReg());
Str << "\t"
<< "str"
<< "\t";
Src0->emit(Func);
Str << ", ";
Dest->emit(Func);
}
}
template <> void InstARM32Mov::emitIAS(const Cfg *Func) const {
assert(getSrcSize() == 1);
(void)Func;
llvm_unreachable("Not yet implemented");
}
void InstARM32Ldr::emit(const Cfg *Func) const {
if (!ALLOW_DUMP)
return;
Ostream &Str = Func->getContext()->getStrEmit();
assert(getSrcSize() == 1);
assert(getDest()->hasReg());
Type Ty = getSrc(0)->getType();
Str << "\t"
<< "ldr" << getWidthString(Ty) << "\t";
getDest()->emit(Func);
Str << ", ";
getSrc(0)->emit(Func);
}
void InstARM32Ldr::emitIAS(const Cfg *Func) const {
assert(getSrcSize() == 2);
(void)Func;
llvm_unreachable("Not yet implemented");
}
void InstARM32Ldr::dump(const Cfg *Func) const {
if (!ALLOW_DUMP)
return;
Ostream &Str = Func->getContext()->getStrDump();
dumpDest(Func);
Str << "ldr." << getSrc(0)->getType() << " ";
dumpSources(Func);
}
template <> void InstARM32Movw::emit(const Cfg *Func) const {
if (!ALLOW_DUMP)
return;
Ostream &Str = Func->getContext()->getStrEmit();
assert(getSrcSize() == 1);
Str << "\t" << Opcode << "\t";
getDest()->emit(Func);
Str << ", ";
Constant *Src0 = llvm::cast<Constant>(getSrc(0));
if (auto CR = llvm::dyn_cast<ConstantRelocatable>(Src0)) {
Str << "#:lower16:";
CR->emitWithoutPrefix(Func->getTarget());
} else {
Src0->emit(Func);
}
}
template <> void InstARM32Movt::emit(const Cfg *Func) const {
if (!ALLOW_DUMP)
return;
Ostream &Str = Func->getContext()->getStrEmit();
assert(getSrcSize() == 2);
Variable *Dest = getDest();
Constant *Src1 = llvm::cast<Constant>(getSrc(1));
Str << "\t" << Opcode << "\t";
Dest->emit(Func);
Str << ", ";
if (auto CR = llvm::dyn_cast<ConstantRelocatable>(Src1)) {
Str << "#:upper16:";
CR->emitWithoutPrefix(Func->getTarget());
} else {
Src1->emit(Func);
}
}
void InstARM32Ret::emit(const Cfg *Func) const {
if (!ALLOW_DUMP)
return;
......@@ -98,4 +292,119 @@ void InstARM32Ret::dump(const Cfg *Func) const {
dumpSources(Func);
}
void OperandARM32Mem::emit(const Cfg *Func) const {
if (!ALLOW_DUMP)
return;
Ostream &Str = Func->getContext()->getStrEmit();
Str << "[";
getBase()->emit(Func);
switch (getAddrMode()) {
case PostIndex:
case NegPostIndex:
Str << "], ";
break;
default:
Str << ", ";
break;
}
if (isRegReg()) {
if (isNegAddrMode()) {
Str << "-";
}
getIndex()->emit(Func);
if (getShiftOp() != kNoShift) {
Str << ", " << InstARM32ShiftAttributes[getShiftOp()].EmitString << " #"
<< getShiftAmt();
}
} else {
getOffset()->emit(Func);
}
switch (getAddrMode()) {
case Offset:
case NegOffset:
Str << "]";
break;
case PreIndex:
case NegPreIndex:
Str << "]!";
break;
case PostIndex:
case NegPostIndex:
// Brace is already closed off.
break;
}
}
void OperandARM32Mem::dump(const Cfg *Func, Ostream &Str) const {
if (!ALLOW_DUMP)
return;
Str << "[";
if (Func)
getBase()->dump(Func);
else
getBase()->dump(Str);
Str << ", ";
if (isRegReg()) {
if (isNegAddrMode()) {
Str << "-";
}
if (Func)
getIndex()->dump(Func);
else
getIndex()->dump(Str);
if (getShiftOp() != kNoShift) {
Str << ", " << InstARM32ShiftAttributes[getShiftOp()].EmitString << " #"
<< getShiftAmt();
}
} else {
getOffset()->dump(Func, Str);
}
Str << "] AddrMode==" << getAddrMode() << "\n";
}
void OperandARM32FlexImm::emit(const Cfg *Func) const {
if (!ALLOW_DUMP)
return;
Ostream &Str = Func->getContext()->getStrEmit();
uint32_t Imm = getImm();
uint32_t RotateAmt = getRotateAmt();
Str << "#" << Utils::rotateRight32(Imm, 2 * RotateAmt);
}
void OperandARM32FlexImm::dump(const Cfg * /* Func */, Ostream &Str) const {
if (!ALLOW_DUMP)
return;
uint32_t Imm = getImm();
uint32_t RotateAmt = getRotateAmt();
Str << "#(" << Imm << " ror 2*" << RotateAmt << ")";
}
void OperandARM32FlexReg::emit(const Cfg *Func) const {
if (!ALLOW_DUMP)
return;
Ostream &Str = Func->getContext()->getStrEmit();
getReg()->emit(Func);
if (getShiftOp() != kNoShift) {
Str << ", " << InstARM32ShiftAttributes[getShiftOp()].EmitString << " ";
getShiftAmt()->emit(Func);
}
}
void OperandARM32FlexReg::dump(const Cfg *Func, Ostream &Str) const {
if (!ALLOW_DUMP)
return;
Variable *Reg = getReg();
if (Func)
Reg->dump(Func);
else
Reg->dump(Str);
if (getShiftOp() != kNoShift) {
Str << ", " << InstARM32ShiftAttributes[getShiftOp()].EmitString << " ";
if (Func)
getShiftAmt()->dump(Func);
else
getShiftAmt()->dump(Str);
}
}
} // end of namespace Ice
......@@ -81,4 +81,14 @@
X(IceType_v4f32, IceType_f32 , "", 0, 0) \
//#define X(tag, elementty, width, sbits, ubits)
// Shifter types for Data-processing operands as defined in section A5.1.2.
#define ICEINSTARM32SHIFT_TABLE \
/* enum value, emit */ \
X(LSL, "lsl") \
X(LSR, "lsr") \
X(ASR, "asr") \
X(ROR, "ror") \
X(RRX, "rrx") \
//#define X(tag, emit)
#endif // SUBZERO_SRC_ICEINSTARM32_DEF
......@@ -889,22 +889,6 @@ void emitIASMovlikeXMM(const Cfg *Func, const Variable *Dest,
}
}
bool checkForRedundantAssign(const Variable *Dest, const Operand *Source) {
const auto SrcVar = llvm::dyn_cast<const Variable>(Source);
if (!SrcVar)
return false;
if (Dest->hasReg() && Dest->getRegNum() == SrcVar->getRegNum()) {
// TODO: On x86-64, instructions like "mov eax, eax" are used to
// clear the upper 32 bits of rax. We need to recognize and
// preserve these.
return true;
}
if (!Dest->hasReg() && !SrcVar->hasReg() &&
Dest->getStackOffset() == SrcVar->getStackOffset())
return true;
return false;
}
// In-place ops
template <> const char *InstX8632Bswap::Opcode = "bswap";
template <> const char *InstX8632Neg::Opcode = "neg";
......
......@@ -956,8 +956,6 @@ private:
static const char *Opcode;
};
bool checkForRedundantAssign(const Variable *Dest, const Operand *Source);
// Base class for assignment instructions
template <InstX8632::InstKindX8632 K>
class InstX8632Movlike : public InstX8632 {
......
......@@ -46,7 +46,10 @@ public:
kVariable_Target, // leave space for target-specific variable kinds
kVariable_Num = kVariable_Target + MaxTargetKinds,
// Target-specific operand classes use kTarget as the starting
// point for their Kind enum space.
// point for their Kind enum space. Note that the value-spaces are shared
// across targets. To avoid confusion over the definition of shared
// values, an object specific to one target should never be passed
// to a different target.
kTarget
};
OperandKind getKind() const { return Kind; }
......
......@@ -228,6 +228,22 @@ void TargetLowering::regAlloc(RegAllocKind Kind) {
LinearScan.scan(RegMask, Ctx->getFlags().shouldRandomizeRegAlloc());
}
void TargetLowering::inferTwoAddress() {
// Find two-address non-SSA instructions where Dest==Src0, and set
// the DestNonKillable flag to keep liveness analysis consistent.
for (auto Inst = Context.getCur(), E = Context.getNext(); Inst != E; ++Inst) {
if (Inst->isDeleted())
continue;
if (Variable *Dest = Inst->getDest()) {
// TODO(stichnot): We may need to consider all source
// operands, not just the first one, if using 3-address
// instructions.
if (Inst->getSrcSize() > 0 && Inst->getSrc(0) == Dest)
Inst->setDestNonKillable();
}
}
}
InstCall *TargetLowering::makeHelperCall(const IceString &Name, Variable *Dest,
SizeT MaxSrcs) {
const bool HasTailCall = false;
......
......@@ -255,6 +255,10 @@ protected:
// expansion before returning.
virtual void postLower() {}
// Find two-address non-SSA instructions and set the DestNonKillable flag
// to keep liveness analysis consistent.
void inferTwoAddress();
// Make a call to an external helper function.
InstCall *makeHelperCall(const IceString &Name, Variable *Dest,
SizeT MaxSrcs);
......
......@@ -65,6 +65,13 @@ public:
void addProlog(CfgNode *Node) override;
void addEpilog(CfgNode *Node) override;
// Ensure that a 64-bit Variable has been split into 2 32-bit
// Variables, creating them if necessary. This is needed for all
// I64 operations.
void split64(Variable *Var);
Operand *loOperand(Operand *Operand);
Operand *hiOperand(Operand *Operand);
protected:
explicit TargetARM32(Cfg *Func);
......@@ -94,16 +101,58 @@ protected:
void doAddressOptLoad() override;
void doAddressOptStore() override;
void randomlyInsertNop(float Probability) override;
enum OperandLegalization {
Legal_None = 0,
Legal_Reg = 1 << 0, // physical register, not stack location
Legal_Flex = 1 << 1, // A flexible operand2, which can hold rotated
// small immediates, or shifted registers.
Legal_Mem = 1 << 2, // includes [r0, r1 lsl #2] as well as [sp, #12]
Legal_All = ~Legal_None
};
typedef uint32_t LegalMask;
Operand *legalize(Operand *From, LegalMask Allowed = Legal_All,
int32_t RegNum = Variable::NoRegister);
Variable *legalizeToVar(Operand *From, int32_t RegNum = Variable::NoRegister);
Variable *makeReg(Type Ty, int32_t RegNum = Variable::NoRegister);
static Type stackSlotType();
Variable *copyToReg(Operand *Src, int32_t RegNum = Variable::NoRegister);
// Returns a vector in a register with the given constant entries.
Variable *makeVectorOfZeros(Type Ty, int32_t RegNum = Variable::NoRegister);
void makeRandomRegisterPermutation(
llvm::SmallVectorImpl<int32_t> &Permutation,
const llvm::SmallBitVector &ExcludeRegisters) const override;
static Type stackSlotType();
// The following are helpers that insert lowered ARM32 instructions
// with minimal syntactic overhead, so that the lowering code can
// look as close to assembly as practical.
void _ldr(Variable *Dest, OperandARM32Mem *Addr) {
Context.insert(InstARM32Ldr::create(Func, Dest, Addr));
}
// If Dest=nullptr is passed in, then a new variable is created,
// marked as infinite register allocation weight, and returned
// through the in/out Dest argument.
void _mov(Variable *&Dest, Operand *Src0,
int32_t RegNum = Variable::NoRegister) {
if (Dest == nullptr)
Dest = makeReg(Src0->getType(), RegNum);
Context.insert(InstARM32Mov::create(Func, Dest, Src0));
}
// The Operand can only be a 16-bit immediate or a ConstantRelocatable
// (with an upper16 relocation).
void _movt(Variable *&Dest, Operand *Src0) {
Context.insert(InstARM32Movt::create(Func, Dest, Src0));
}
void _movw(Variable *&Dest, Operand *Src0) {
Context.insert(InstARM32Movw::create(Func, Dest, Src0));
}
void _mvn(Variable *&Dest, Operand *Src0) {
Context.insert(InstARM32Mvn::create(Func, Dest, Src0));
}
void _ret(Variable *LR, Variable *Src0 = nullptr) {
Context.insert(InstARM32Ret::create(Func, LR, Src0));
}
......
......@@ -4636,7 +4636,7 @@ Operand *TargetX8632::legalize(Operand *From, LegalMask Allowed,
// work, e.g. allow the shl shift amount to be either an immediate
// or in ecx.)
assert(RegNum == Variable::NoRegister || Allowed == Legal_Reg);
if (OperandX8632Mem *Mem = llvm::dyn_cast<OperandX8632Mem>(From)) {
if (auto Mem = llvm::dyn_cast<OperandX8632Mem>(From)) {
// Before doing anything with a Mem operand, we need to ensure
// that the Base and Index components are in physical registers.
Variable *Base = Mem->getBase();
......@@ -4691,7 +4691,7 @@ Operand *TargetX8632::legalize(Operand *From, LegalMask Allowed,
}
return From;
}
if (Variable *Var = llvm::dyn_cast<Variable>(From)) {
if (auto Var = llvm::dyn_cast<Variable>(From)) {
// Check if the variable is guaranteed a physical register. This
// can happen either when the variable is pre-colored or when it is
// assigned infinite weight.
......@@ -4766,19 +4766,7 @@ Variable *TargetX8632::makeReg(Type Type, int32_t RegNum) {
void TargetX8632::postLower() {
if (Ctx->getFlags().getOptLevel() == Opt_m1)
return;
// Find two-address non-SSA instructions where Dest==Src0, and set
// the DestNonKillable flag to keep liveness analysis consistent.
for (auto Inst = Context.getCur(), E = Context.getNext(); Inst != E; ++Inst) {
if (Inst->isDeleted())
continue;
if (Variable *Dest = Inst->getDest()) {
// TODO(stichnot): We may need to consider all source
// operands, not just the first one, if using 3-address
// instructions.
if (Inst->getSrcSize() > 0 && Inst->getSrc(0) == Dest)
Inst->setDestNonKillable();
}
}
inferTwoAddress();
}
void TargetX8632::makeRandomRegisterPermutation(
......
......@@ -78,6 +78,19 @@ public:
return 0;
return Align - Mod;
}
// Precondition: 0 <= shift < 32
static inline uint32_t rotateLeft32(uint32_t value, uint32_t shift) {
if (shift == 0)
return value;
return (value << shift) | (value >> (32 - shift));
}
static inline uint32_t rotateRight32(uint32_t value, uint32_t shift) {
if (shift == 0)
return value;
return (value >> shift) | (value << (32 - shift));
}
};
} // end of namespace Ice
......
; This file checks that Subzero generates code in accordance with the
; calling convention for integers.
; RUN: %p2i -i %s --filetype=obj --disassemble --args -O2 \
; RUN: | FileCheck %s
; TODO(jvoung): Stop skipping unimplemented parts (via --skip-unimplemented)
; once enough infrastructure is in. Also, switch to --filetype=obj
; when possible.
; RUN: %if --need=target_ARM32 --command %p2i --filetype=asm --assemble \
; RUN: --disassemble --target arm32 -i %s --args -O2 --skip-unimplemented \
; RUN: | %if --need=target_ARM32 --command FileCheck --check-prefix ARM32 %s
; For x86-32, integer arguments use the stack.
; For ARM32, integer arguments can be r0-r3. i64 arguments occupy two
; adjacent 32-bit registers, and require the first to be an even register.
; i32
define i32 @test_returning32_arg0(i32 %arg0, i32 %arg1, i32 %arg2, i32 %arg3, i32 %arg4, i32 %arg5, i32 %arg6, i32 %arg7) {
entry:
ret i32 %arg0
}
; CHECK-LABEL: test_returning32_arg0
; CHECK-NEXT: mov eax,{{.*}} [esp+0x4]
; CHECK-NEXT: ret
; ARM32-LABEL: test_returning32_arg0
; ARM32-NEXT: bx lr
define i32 @test_returning32_arg1(i32 %arg0, i32 %arg1, i32 %arg2, i32 %arg3, i32 %arg4, i32 %arg5, i32 %arg6, i32 %arg7) {
entry:
ret i32 %arg1
}
; CHECK-LABEL: test_returning32_arg1
; CHECK-NEXT: mov eax,{{.*}} [esp+0x8]
; CHECK-NEXT: ret
; ARM32-LABEL: test_returning32_arg1
; ARM32-NEXT: mov r0, r1
; ARM32-NEXT: bx lr
define i32 @test_returning32_arg2(i32 %arg0, i32 %arg1, i32 %arg2, i32 %arg3, i32 %arg4, i32 %arg5, i32 %arg6, i32 %arg7) {
entry:
ret i32 %arg2
}
; CHECK-LABEL: test_returning32_arg2
; CHECK-NEXT: mov eax,{{.*}} [esp+0xc]
; CHECK-NEXT: ret
; ARM32-LABEL: test_returning32_arg2
; ARM32-NEXT: mov r0, r2
; ARM32-NEXT: bx lr
define i32 @test_returning32_arg3(i32 %arg0, i32 %arg1, i32 %arg2, i32 %arg3, i32 %arg4, i32 %arg5, i32 %arg6, i32 %arg7) {
entry:
ret i32 %arg3
}
; CHECK-LABEL: test_returning32_arg3
; CHECK-NEXT: mov eax,{{.*}} [esp+0x10]
; CHECK-NEXT: ret
; ARM32-LABEL: test_returning32_arg3
; ARM32-NEXT: mov r0, r3
; ARM32-NEXT: bx lr
define i32 @test_returning32_arg4(i32 %arg0, i32 %arg1, i32 %arg2, i32 %arg3, i32 %arg4, i32 %arg5, i32 %arg6, i32 %arg7) {
entry:
ret i32 %arg4
}
; CHECK-LABEL: test_returning32_arg4
; CHECK-NEXT: mov eax,{{.*}} [esp+0x14]
; CHECK-NEXT: ret
; ARM32-LABEL: test_returning32_arg4
; TODO(jvoung): Toggle this on, once addProlog is done.
; TODOARM32-NEXT: ldr r0, [sp]
; ARM32-NEXT: bx lr
define i32 @test_returning32_arg5(i32 %arg0, i32 %arg1, i32 %arg2, i32 %arg3, i32 %arg4, i32 %arg5, i32 %arg6, i32 %arg7) {
entry:
ret i32 %arg5
}
; CHECK-LABEL: test_returning32_arg5
; CHECK-NEXT: mov eax,{{.*}} [esp+0x18]
; CHECK-NEXT: ret
; ARM32-LABEL: test_returning32_arg5
; TODO(jvoung): Toggle this on, once addProlog is done.
; TODOARM32-NEXT: ldr r0, [sp, #4]
; ARM32-NEXT: bx lr
; i64
define i64 @test_returning64_arg0(i64 %arg0, i64 %arg1, i64 %arg2, i64 %arg3) {
entry:
ret i64 %arg0
}
; CHECK-LABEL: test_returning64_arg0
; CHECK-NEXT: mov {{.*}} [esp+0x4]
; CHECK-NEXT: mov {{.*}} [esp+0x8]
; CHECK: ret
; ARM32-LABEL: test_returning64_arg0
; ARM32-NEXT: bx lr
define i64 @test_returning64_arg1(i64 %arg0, i64 %arg1, i64 %arg2, i64 %arg3) {
entry:
ret i64 %arg1
}
; CHECK-LABEL: test_returning64_arg1
; CHECK-NEXT: mov {{.*}} [esp+0xc]
; CHECK-NEXT: mov {{.*}} [esp+0x10]
; CHECK: ret
; ARM32-LABEL: test_returning64_arg1
; ARM32-NEXT: mov r0, r2
; ARM32-NEXT: mov r1, r3
; ARM32-NEXT: bx lr
define i64 @test_returning64_arg2(i64 %arg0, i64 %arg1, i64 %arg2, i64 %arg3) {
entry:
ret i64 %arg2
}
; CHECK-LABEL: test_returning64_arg2
; CHECK-NEXT: mov {{.*}} [esp+0x14]
; CHECK-NEXT: mov {{.*}} [esp+0x18]
; CHECK: ret
; ARM32-LABEL: test_returning64_arg2
; This could have been a ldm sp, {r0, r1}, but we don't do the ldm optimization.
; TODO(jvoung): enable this once addProlog is done.
; TODOARM32-NEXT: ldr r0, [sp]
; TODOARM32-NEXT: ldr r1, [sp, #4]
; ARM32-NEXT: bx lr
define i64 @test_returning64_arg3(i64 %arg0, i64 %arg1, i64 %arg2, i64 %arg3) {
entry:
ret i64 %arg3
}
; CHECK-LABEL: test_returning64_arg3
; CHECK-NEXT: mov {{.*}} [esp+0x1c]
; CHECK-NEXT: mov {{.*}} [esp+0x20]
; CHECK: ret
; ARM32-LABEL: test_returning64_arg3
; TODO(jvoung): enable this once addProlog is done.
; TODOARM32-NEXT: ldr r0, [sp, #8]
; TODOARM32-NEXT: ldr r1, [sp, #12]
; ARM32-NEXT: bx lr
; Test that on ARM, the i64 arguments start with an even register.
define i64 @test_returning64_even_arg1(i32 %arg0, i64 %arg1, i64 %arg2) {
entry:
ret i64 %arg1
}
; Not padded out x86-32.
; CHECK-LABEL: test_returning64_even_arg1
; CHECK-NEXT: mov {{.*}} [esp+0x8]
; CHECK-NEXT: mov {{.*}} [esp+0xc]
; CHECK: ret
; ARM32-LABEL: test_returning64_even_arg1
; ARM32-NEXT: mov r0, r2
; ARM32-NEXT: mov r1, r3
; ARM32-NEXT: bx lr
define i64 @test_returning64_even_arg1b(i32 %arg0, i32 %arg0b, i64 %arg1, i64 %arg2) {
entry:
ret i64 %arg1
}
; CHECK-LABEL: test_returning64_even_arg1b
; CHECK-NEXT: mov {{.*}} [esp+0xc]
; CHECK-NEXT: mov {{.*}} [esp+0x10]
; CHECK: ret
; ARM32-LABEL: test_returning64_even_arg1b
; ARM32-NEXT: mov r0, r2
; ARM32-NEXT: mov r1, r3
; ARM32-NEXT: bx lr
define i64 @test_returning64_even_arg2(i64 %arg0, i32 %arg1, i64 %arg2) {
entry:
ret i64 %arg2
}
; Not padded out on x86-32.
; CHECK-LABEL: test_returning64_even_arg2
; CHECK-NEXT: mov {{.*}} [esp+0x10]
; CHECK-NEXT: mov {{.*}} [esp+0x14]
; CHECK: ret
; ARM32-LABEL: test_returning64_even_arg2
; TODO(jvoung): enable this once addProlog is done.
; TODOARM32-NEXT: ldr r0, [sp]
; TODOARM32-NEXT: ldr r1, [sp, #4]
; ARM32-NEXT: bx lr
define i64 @test_returning64_even_arg2b(i64 %arg0, i32 %arg1, i32 %arg1b, i64 %arg2) {
entry:
ret i64 %arg2
}
; CHECK-LABEL: test_returning64_even_arg2b
; CHECK-NEXT: mov {{.*}} [esp+0x14]
; CHECK-NEXT: mov {{.*}} [esp+0x18]
; CHECK: ret
; ARM32-LABEL: test_returning64_even_arg2b
; TODO(jvoung): enable this once addProlog is done.
; TODOARM32-NEXT: ldr r0, [sp]
; TODOARM32-NEXT: ldr r1, [sp, #4]
; ARM32-NEXT: bx lr
define i32 @test_returning32_even_arg2(i64 %arg0, i32 %arg1, i32 %arg2) {
entry:
ret i32 %arg2
}
; CHECK-LABEL: test_returning32_even_arg2
; CHECK-NEXT: mov {{.*}} [esp+0x10]
; CHECK-NEXT: ret
; ARM32-LABEL: test_returning32_even_arg2
; ARM32-NEXT: mov r0, r3
; ARM32-NEXT: bx lr
define i32 @test_returning32_even_arg2b(i32 %arg0, i32 %arg1, i32 %arg2, i64 %arg3) {
entry:
ret i32 %arg2
}
; CHECK-LABEL: test_returning32_even_arg2b
; CHECK-NEXT: mov {{.*}} [esp+0xc]
; CHECK-NEXT: ret
; ARM32-LABEL: test_returning32_even_arg2b
; ARM32-NEXT: mov r0, r2
; ARM32-NEXT: bx lr
; The i64 won't fit in a pair of register, and consumes the last register so a
; following i32 can't use that free register.
define i32 @test_returning32_even_arg4(i32 %arg0, i32 %arg1, i32 %arg2, i64 %arg3, i32 %arg4) {
entry:
ret i32 %arg4
}
; CHECK-LABEL: test_returning32_even_arg4
; CHECK-NEXT: mov {{.*}} [esp+0x18]
; CHECK-NEXT: ret
; ARM32-LABEL: test_returning32_even_arg4
; TODO(jvoung): enable this once addProlog is done.
; TODOARM32-NEXT: ldr r0, [sp, #8]
; ARM32-NEXT: bx lr
; Test interleaving float/double and integer (different register streams on ARM).
; TODO(jvoung): Test once the S/D/Q regs are modeled.
; Simple test that returns various immediates. For fixed-width instruction
; sets, some immediates are more complicated than others.
; For x86-32, it shouldn't be a problem.
; RUN: %p2i --filetype=obj --disassemble -i %s --args -O2 | FileCheck %s
; TODO(jvoung): Stop skipping unimplemented parts (via --skip-unimplemented)
; once enough infrastructure is in. Also, switch to --filetype=obj
; when possible.
; RUN: %if --need=target_ARM32 --command %p2i --filetype=asm --assemble \
; RUN: --disassemble --target arm32 -i %s --args -O2 --skip-unimplemented \
; RUN: | %if --need=target_ARM32 --command FileCheck --check-prefix ARM32 %s
; Test 8-bits of all ones rotated right by various amounts (even vs odd).
; ARM has a shifter that allows encoding 8-bits rotated right by even amounts.
; The first few "rotate right" test cases are expressed as shift-left.
define i32 @ret_8bits_shift_left0() {
ret i32 255
}
; CHECK-LABEL: ret_8bits_shift_left0
; CHECK-NEXT: mov eax,0xff
; ARM32-LABEL: ret_8bits_shift_left0
; ARM32-NEXT: mov r0, #255
define i32 @ret_8bits_shift_left1() {
ret i32 510
}
; CHECK-LABEL: ret_8bits_shift_left1
; CHECK-NEXT: mov eax,0x1fe
; ARM32-LABEL: ret_8bits_shift_left1
; ARM32-NEXT: movw r0, #510
define i32 @ret_8bits_shift_left2() {
ret i32 1020
}
; CHECK-LABEL: ret_8bits_shift_left2
; CHECK-NEXT: mov eax,0x3fc
; ARM32-LABEL: ret_8bits_shift_left2
; ARM32-NEXT: mov r0, #1020
define i32 @ret_8bits_shift_left4() {
ret i32 4080
}
; CHECK-LABEL: ret_8bits_shift_left4
; CHECK-NEXT: mov eax,0xff0
; ARM32-LABEL: ret_8bits_shift_left4
; ARM32-NEXT: mov r0, #4080
define i32 @ret_8bits_shift_left14() {
ret i32 4177920
}
; CHECK-LABEL: ret_8bits_shift_left14
; CHECK-NEXT: mov eax,0x3fc000
; ARM32-LABEL: ret_8bits_shift_left14
; ARM32-NEXT: mov r0, #4177920
define i32 @ret_8bits_shift_left15() {
ret i32 8355840
}
; CHECK-LABEL: ret_8bits_shift_left15
; CHECK-NEXT: mov eax,0x7f8000
; ARM32-LABEL: ret_8bits_shift_left15
; ARM32-NEXT: movw r0, #32768
; ARM32-NEXT: movt r0, #127
; Shift 8 bits left by 24 to the i32 limit. This is also ror by 8 bits.
define i32 @ret_8bits_shift_left24() {
ret i32 4278190080
}
; CHECK-LABEL: ret_8bits_shift_left24
; CHECK-NEXT: mov eax,0xff000000
; ARM32-LABEL: ret_8bits_shift_left24
; ARM32-NEXT: mov r0, #-16777216
; ARM32-NEXT: bx lr
; The next few cases wrap around and actually demonstrate the rotation.
define i32 @ret_8bits_ror7() {
ret i32 4261412865
}
; CHECK-LABEL: ret_8bits_ror7
; CHECK-NEXT: mov eax,0xfe000001
; ARM32-LABEL: ret_8bits_ror7
; ARM32-NEXT: movw r0, #1
; ARM32-NEXT: movt r0, #65024
define i32 @ret_8bits_ror6() {
ret i32 4227858435
}
; CHECK-LABEL: ret_8bits_ror6
; CHECK-NEXT: mov eax,0xfc000003
; ARM32-LABEL: ret_8bits_ror6
; ARM32-NEXT: mov r0, #-67108861
; ARM32-NEXT: bx lr
define i32 @ret_8bits_ror5() {
ret i32 4160749575
}
; CHECK-LABEL: ret_8bits_ror5
; CHECK-NEXT: mov eax,0xf8000007
; ARM32-LABEL: ret_8bits_ror5
; ARM32-NEXT: movw r0, #7
; ARM32-NEXT: movt r0, #63488
define i32 @ret_8bits_ror4() {
ret i32 4026531855
}
; CHECK-LABEL: ret_8bits_ror4
; CHECK-NEXT: mov eax,0xf000000f
; ARM32-LABEL: ret_8bits_ror4
; ARM32-NEXT: mov r0, #-268435441
; ARM32-NEXT: bx lr
define i32 @ret_8bits_ror3() {
ret i32 3758096415
}
; CHECK-LABEL: ret_8bits_ror3
; CHECK-NEXT: mov eax,0xe000001f
; ARM32-LABEL: ret_8bits_ror3
; ARM32-NEXT: movw r0, #31
; ARM32-NEXT: movt r0, #57344
define i32 @ret_8bits_ror2() {
ret i32 3221225535
}
; CHECK-LABEL: ret_8bits_ror2
; CHECK-NEXT: mov eax,0xc000003f
; ARM32-LABEL: ret_8bits_ror2
; ARM32-NEXT: mov r0, #-1073741761
; ARM32-NEXT: bx lr
define i32 @ret_8bits_ror1() {
ret i32 2147483775
}
; CHECK-LABEL: ret_8bits_ror1
; CHECK-NEXT: mov eax,0x8000007f
; ARM32-LABEL: ret_8bits_ror1
; ARM32-NEXT: movw r0, #127
; ARM32-NEXT: movt r0, #32768
; Some architectures can handle 16-bits at a time efficiently,
; so also test those.
define i32 @ret_16bits_lower() {
ret i32 65535
}
; CHECK-LABEL: ret_16bits_lower
; CHECK-NEXT: mov eax,0xffff
; ARM32-LABEL: ret_16bits_lower
; ARM32-NEXT: movw r0, #65535
; ARM32-NEXT: bx lr
define i32 @ret_17bits_lower() {
ret i32 131071
}
; CHECK-LABEL: ret_17bits_lower
; CHECK-NEXT: mov eax,0x1ffff
; ARM32-LABEL: ret_17bits_lower
; ARM32-NEXT: movw r0, #65535
; ARM32-NEXT: movt r0, #1
define i32 @ret_16bits_upper() {
ret i32 4294901760
}
; CHECK-LABEL: ret_16bits_upper
; CHECK-NEXT: mov eax,0xffff0000
; ARM32-LABEL: ret_16bits_upper
; ARM32-NEXT: movw r0, #0
; ARM32-NEXT: movt r0, #65535
; Some 32-bit immediates can be inverted, and moved in a single instruction.
define i32 @ret_8bits_inverted_shift_left0() {
ret i32 4294967040
}
; CHECK-LABEL: ret_8bits_inverted_shift_left0
; CHECK-NEXT: mov eax,0xffffff00
; ARM32-LABEL: ret_8bits_inverted_shift_left0
; ARM32-NEXT: mvn r0, #255
; ARM32-NEXT: bx lr
define i32 @ret_8bits_inverted_shift_left24() {
ret i32 16777215
}
; CHECK-LABEL: ret_8bits_inverted_shift_left24
; CHECK-NEXT: mov eax,0xffffff
; ARM32-LABEL: ret_8bits_inverted_shift_left24
; ARM32-NEXT: mvn r0, #-16777216
; ARM32-NEXT: bx lr
define i32 @ret_8bits_inverted_ror2() {
ret i32 1073741760
}
; CHECK-LABEL: ret_8bits_inverted_ror2
; CHECK-NEXT: mov eax,0x3fffffc0
; ARM32-LABEL: ret_8bits_inverted_ror2
; ARM32-NEXT: mvn r0, #-1073741761
; ARM32-NEXT: bx lr
define i32 @ret_8bits_inverted_ror6() {
ret i32 67108860
}
; CHECK-LABEL: ret_8bits_inverted_ror6
; CHECK-NEXT: mov eax,0x3fffffc
; ARM32-LABEL: ret_8bits_inverted_ror6
; ARM32-NEXT: mvn r0, #-67108861
; ARM32-NEXT: bx lr
define i32 @ret_8bits_inverted_ror7() {
ret i32 33554430
}
; CHECK-LABEL: ret_8bits_inverted_ror7
; CHECK-NEXT: mov eax,0x1fffffe
; ARM32-LABEL: ret_8bits_inverted_ror7
; ARM32-NEXT: movw r0, #65534
; ARM32-NEXT: movt r0, #511
; 64-bit immediates.
define i64 @ret_64bits_shift_left0() {
ret i64 1095216660735
}
; CHECK-LABEL: ret_64bits_shift_left0
; CHECK-NEXT: mov eax,0xff
; CHECK-NEXT: mov edx,0xff
; ARM32-LABEL: ret_64bits_shift_left0
; ARM32-NEXT: movw r0, #255
; ARM32-NEXT: movw r1, #255
; A relocatable constant is assumed to require 32-bits along with
; relocation directives.
declare void @_start()
define i32 @ret_addr() {
%ptr = ptrtoint void ()* @_start to i32
ret i32 %ptr
}
; CHECK-LABEL: ret_addr
; CHECK-NEXT: mov eax,0x0 {{.*}} R_386_32 _start
; ARM32-LABEL: ret_addr
; ARM32-NEXT: movw r0, #0 {{.*}} R_ARM_MOVW_ABS_NC _start
; ARM32-NEXT: movt r0, #0 {{.*}} R_ARM_MOVT_ABS _start
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment