Commit 28068adb by Jan Voung

ARM: Add a postRA pass to legalize stack offsets. Greedy approach (reserve IP).

Make a post-register allocation and post-addProlog pass to go through variables with stack offsets and legalize them in case the offsets are not encodeable. The naive approach is to reserve IP, and use IP to movw/movt the offset, then add/sub the frame/stack pointer to IP and use IP as the new base instead of the frame/stack pointer. We do some amount of CSE within a basic block, and share the IP base pointer when it is (a) within range for later stack references, and (b) IP hasn't been clobbered (e.g., by a function call). I chose to do this greedy approach for both Om1 and O2, since it should just be a linear pass, and it reduces the amount of variables/instructions created compared to the super-naive peephole approach (so might be faster?). Introduce a test-only flag and use that to artificially bloat the stack frame so that spill offsets are out of range for ARM. Use that flag for cross tests to stress this new code a bit more (than would have been stressed by simply doing a lit test + FileCheck). Also, the previous version of emitVariable() was using the Var's type to determine the range (only +/- 255 for i16, vs +/- 4095 for i32), even though mov's emit() always uses a full 32-bit "ldr" instead of a 16-bit "ldrh". Use a common legality check, which uses the stackSlotType instead of the Var's type. This previously caused the test_bitmanip to spuriously complain, even though the offsets for Om1 were "only" in the 300 byte range. With this fixed, we can then enable the test_bitmanip test too. BUG= https://code.google.com/p/nativeclient/issues/detail?id=4076 R=stichnot@chromium.org Review URL: https://codereview.chromium.org/1241763002 .
parent 969f6a33
...@@ -349,6 +349,7 @@ check-xtest: $(OBJDIR)/pnacl-sz make_symlink runtime ...@@ -349,6 +349,7 @@ check-xtest: $(OBJDIR)/pnacl-sz make_symlink runtime
-i x8632,sandbox,sse4.1,Om1 \ -i x8632,sandbox,sse4.1,Om1 \
-i arm32,native,neon,simple_loop \ -i arm32,native,neon,simple_loop \
-i arm32,native,neon,mem_intrin \ -i arm32,native,neon,mem_intrin \
-i arm32,native,neon,test_bitmanip \
-i arm32,native,neon,test_stacksave \ -i arm32,native,neon,test_stacksave \
-i arm32,native,neon,test_strengthreduce -i arm32,native,neon,test_strengthreduce
PNACL_BIN_PATH=$(PNACL_BIN_PATH) \ PNACL_BIN_PATH=$(PNACL_BIN_PATH) \
......
...@@ -29,8 +29,13 @@ def main(): ...@@ -29,8 +29,13 @@ def main():
'arm32': targets.ARM32Target } 'arm32': targets.ARM32Target }
arch_sz_flags = { 'x8632': [], arch_sz_flags = { 'x8632': [],
'x8664': [], 'x8664': [],
# TODO(jvoung): remove skip-unimplemented when implemented # TODO(jvoung): remove skip-unimplemented when
'arm32': ['--skip-unimplemented'] # implemented.
# For ARM, test a large stack offset as well, until we
# are more confident. +/- 4095 is the limit, so test
# somewhere near that boundary.
'arm32': ['--skip-unimplemented',
'--test-stack-extra', '4084']
} }
arch_llc_flags_extra = { arch_llc_flags_extra = {
# Use sse2 instructions regardless of input -mattr # Use sse2 instructions regardless of input -mattr
......
...@@ -119,8 +119,8 @@ public: ...@@ -119,8 +119,8 @@ public:
/// Create a new Variable with a particular type and an optional /// Create a new Variable with a particular type and an optional
/// name. The Node argument is the node where the variable is defined. /// name. The Node argument is the node where the variable is defined.
// TODO(jpp): untemplate this with two separate methods: makeVariable and // TODO(jpp): untemplate this with separate methods: makeVariable,
// makeSpillVariable. // makeSpillVariable, and makeStackVariable.
template <typename T = Variable> T *makeVariable(Type Ty) { template <typename T = Variable> T *makeVariable(Type Ty) {
SizeT Index = Variables.size(); SizeT Index = Variables.size();
T *Var = T::create(this, Ty, Index); T *Var = T::create(this, Ty, Index);
......
...@@ -145,6 +145,13 @@ cl::opt<Ice::TargetArch> TargetArch( ...@@ -145,6 +145,13 @@ cl::opt<Ice::TargetArch> TargetArch(
clEnumValN(Ice::Target_MIPS32, "mips", "mips32"), clEnumValN(Ice::Target_MIPS32, "mips", "mips32"),
clEnumValN(Ice::Target_MIPS32, "mips32", "mips32 (same as mips)"), clEnumValN(Ice::Target_MIPS32, "mips32", "mips32 (same as mips)"),
clEnumValEnd)); clEnumValEnd));
cl::opt<uint32_t> TestStackExtra(
"test-stack-extra",
cl::desc(
"Extra amount of stack to add to the frame in bytes (for testing)."),
cl::init(0));
cl::opt<Ice::TargetInstructionSet> TargetInstructionSet( cl::opt<Ice::TargetInstructionSet> TargetInstructionSet(
"mattr", cl::desc("Target architecture attributes"), "mattr", cl::desc("Target architecture attributes"),
cl::init(Ice::BaseInstructionSet), cl::init(Ice::BaseInstructionSet),
...@@ -364,6 +371,7 @@ void ClFlags::resetClFlags(ClFlags &OutFlags) { ...@@ -364,6 +371,7 @@ void ClFlags::resetClFlags(ClFlags &OutFlags) {
OutFlags.RandomizeAndPoolImmediatesThreshold = 0xffff; OutFlags.RandomizeAndPoolImmediatesThreshold = 0xffff;
OutFlags.ReorderFunctionsWindowSize = 8; OutFlags.ReorderFunctionsWindowSize = 8;
OutFlags.TArch = TargetArch_NUM; OutFlags.TArch = TargetArch_NUM;
OutFlags.TestStackExtra = 0;
OutFlags.VMask = IceV_None; OutFlags.VMask = IceV_None;
// IceString fields. // IceString fields.
OutFlags.DefaultFunctionPrefix = ""; OutFlags.DefaultFunctionPrefix = "";
...@@ -421,6 +429,7 @@ void ClFlags::getParsedClFlags(ClFlags &OutFlags) { ...@@ -421,6 +429,7 @@ void ClFlags::getParsedClFlags(ClFlags &OutFlags) {
OutFlags.setTargetArch(::TargetArch); OutFlags.setTargetArch(::TargetArch);
OutFlags.setTargetInstructionSet(::TargetInstructionSet); OutFlags.setTargetInstructionSet(::TargetInstructionSet);
OutFlags.setTestPrefix(::TestPrefix); OutFlags.setTestPrefix(::TestPrefix);
OutFlags.setTestStackExtra(::TestStackExtra);
OutFlags.setTimeEachFunction(::TimeEachFunction); OutFlags.setTimeEachFunction(::TimeEachFunction);
OutFlags.setTimingFocusOn(::TimingFocusOn); OutFlags.setTimingFocusOn(::TimingFocusOn);
OutFlags.setTranslateOnly(::TranslateOnly); OutFlags.setTranslateOnly(::TranslateOnly);
......
...@@ -132,6 +132,14 @@ public: ...@@ -132,6 +132,14 @@ public:
void setTargetInstructionSet(TargetInstructionSet NewValue) { void setTargetInstructionSet(TargetInstructionSet NewValue) {
TInstrSet = NewValue; TInstrSet = NewValue;
} }
uint32_t getTestStackExtra() const {
return BuildDefs::minimal() ? 0 : TestStackExtra;
}
void setTestStackExtra(uint32_t NewValue) {
if (BuildDefs::minimal())
return;
TestStackExtra = NewValue;
}
VerboseMask getVerbose() const { VerboseMask getVerbose() const {
return BuildDefs::dump() ? VMask : (VerboseMask)IceV_None; return BuildDefs::dump() ? VMask : (VerboseMask)IceV_None;
...@@ -251,6 +259,7 @@ private: ...@@ -251,6 +259,7 @@ private:
int RandomNopProbabilityAsPercentage; int RandomNopProbabilityAsPercentage;
uint32_t ReorderFunctionsWindowSize; uint32_t ReorderFunctionsWindowSize;
TargetArch TArch; TargetArch TArch;
uint32_t TestStackExtra;
TargetInstructionSet TInstrSet; TargetInstructionSet TInstrSet;
VerboseMask VMask; VerboseMask VMask;
......
...@@ -407,7 +407,9 @@ template <> void InstARM32Mov::emit(const Cfg *Func) const { ...@@ -407,7 +407,9 @@ template <> void InstARM32Mov::emit(const Cfg *Func) const {
Operand *Src0 = getSrc(0); Operand *Src0 = getSrc(0);
if (const auto *Src0V = llvm::dyn_cast<Variable>(Src0)) { if (const auto *Src0V = llvm::dyn_cast<Variable>(Src0)) {
if (!Src0V->hasReg()) { if (!Src0V->hasReg()) {
Opcode = IceString("ldr"); // Always use the whole stack slot. // Always use the whole stack slot. A 32-bit load has a larger range
// of offsets than 16-bit, etc.
Opcode = IceString("ldr");
} }
} else { } else {
if (llvm::isa<OperandARM32Mem>(Src0)) if (llvm::isa<OperandARM32Mem>(Src0))
......
...@@ -20,6 +20,9 @@ ...@@ -20,6 +20,9 @@
// TODO(jvoung): Allow r9 to be isInt when sandboxing is turned off // TODO(jvoung): Allow r9 to be isInt when sandboxing is turned off
// (native mode). // (native mode).
// //
// IP is not considered isInt to reserve it as a scratch register. A scratch
// register is useful for expanding instructions post-register allocation.
//
// LR is not considered isInt to avoid being allocated as a register. // LR is not considered isInt to avoid being allocated as a register.
// It is technically preserved, but save/restore is handled separately, // It is technically preserved, but save/restore is handled separately,
// based on whether or not the function MaybeLeafFunc. // based on whether or not the function MaybeLeafFunc.
...@@ -37,7 +40,7 @@ ...@@ -37,7 +40,7 @@
X(Reg_r9, = Reg_r0 + 9, "r9", 0, 1, 0, 0, 0, 0) \ X(Reg_r9, = Reg_r0 + 9, "r9", 0, 1, 0, 0, 0, 0) \
X(Reg_r10, = Reg_r0 + 10, "r10", 0, 1, 0, 0, 1, 0) \ X(Reg_r10, = Reg_r0 + 10, "r10", 0, 1, 0, 0, 1, 0) \
X(Reg_fp, = Reg_r0 + 11, "fp", 0, 1, 0, 1, 1, 0) \ X(Reg_fp, = Reg_r0 + 11, "fp", 0, 1, 0, 1, 1, 0) \
X(Reg_ip, = Reg_r0 + 12, "ip", 1, 0, 0, 0, 1, 0) \ X(Reg_ip, = Reg_r0 + 12, "ip", 1, 0, 0, 0, 0, 0) \
X(Reg_sp, = Reg_r0 + 13, "sp", 0, 0, 1, 0, 0, 0) \ X(Reg_sp, = Reg_r0 + 13, "sp", 0, 0, 1, 0, 0, 0) \
X(Reg_lr, = Reg_r0 + 14, "lr", 0, 0, 0, 0, 0, 0) \ X(Reg_lr, = Reg_r0 + 14, "lr", 0, 0, 0, 0, 0, 0) \
X(Reg_pc, = Reg_r0 + 15, "pc", 0, 0, 0, 0, 0, 0) \ X(Reg_pc, = Reg_r0 + 15, "pc", 0, 0, 0, 0, 0, 0) \
......
...@@ -242,6 +242,35 @@ private: ...@@ -242,6 +242,35 @@ private:
Operand *ShiftAmt; Operand *ShiftAmt;
}; };
/// StackVariable represents a Var that isn't assigned a register (stack-only).
/// It is assigned a stack slot, but the slot's offset may be too large to
/// represent in the native addressing mode, and so it has a separate
/// base register from SP/FP, where the offset from that base register is
/// then in range.
class StackVariable final : public Variable {
StackVariable() = delete;
StackVariable(const StackVariable &) = delete;
StackVariable &operator=(const StackVariable &) = delete;
public:
static StackVariable *create(Cfg *Func, Type Ty, SizeT Index) {
return new (Func->allocate<StackVariable>()) StackVariable(Ty, Index);
}
const static OperandKind StackVariableKind =
static_cast<OperandKind>(kVariable_Target);
static bool classof(const Operand *Operand) {
return Operand->getKind() == StackVariableKind;
}
void setBaseRegNum(int32_t RegNum) { BaseRegNum = RegNum; }
int32_t getBaseRegNum() const override { return BaseRegNum; }
// Inherit dump() and emit() from Variable.
private:
StackVariable(Type Ty, SizeT Index)
: Variable(StackVariableKind, Ty, Index) {}
int32_t BaseRegNum = Variable::NoRegister;
};
/// Base class for ARM instructions. While most ARM instructions can be /// Base class for ARM instructions. While most ARM instructions can be
/// conditionally executed, a few of them are not predicable (halt, /// conditionally executed, a few of them are not predicable (halt,
/// memory barriers, etc.). /// memory barriers, etc.).
...@@ -778,6 +807,7 @@ public: ...@@ -778,6 +807,7 @@ public:
void emitIAS(const Cfg *Func) const override; void emitIAS(const Cfg *Func) const override;
void dump(const Cfg *Func) const override; void dump(const Cfg *Func) const override;
static bool classof(const Inst *Inst) { return isClassof(Inst, Adjuststack); } static bool classof(const Inst *Inst) { return isClassof(Inst, Adjuststack); }
SizeT getAmount() const { return Amount; }
private: private:
InstARM32AdjustStack(Cfg *Func, Variable *SP, SizeT Amount, InstARM32AdjustStack(Cfg *Func, Variable *SP, SizeT Amount,
......
...@@ -409,9 +409,11 @@ void Variable::dump(const Cfg *Func, Ostream &Str) const { ...@@ -409,9 +409,11 @@ void Variable::dump(const Cfg *Func, Ostream &Str) const {
} else if (Func->getTarget()->hasComputedFrame()) { } else if (Func->getTarget()->hasComputedFrame()) {
if (Func->isVerbose(IceV_RegOrigins)) if (Func->isVerbose(IceV_RegOrigins))
Str << ":"; Str << ":";
int32_t BaseRegisterNumber = getBaseRegNum();
if (BaseRegisterNumber == NoRegister)
BaseRegisterNumber = Func->getTarget()->getFrameOrStackReg();
Str << "[" Str << "["
<< Func->getTarget()->getRegName( << Func->getTarget()->getRegName(BaseRegisterNumber, IceType_i32);
Func->getTarget()->getFrameOrStackReg(), IceType_i32);
int32_t Offset = getStackOffset(); int32_t Offset = getStackOffset();
if (Offset) { if (Offset) {
if (Offset > 0) if (Offset > 0)
......
...@@ -506,6 +506,9 @@ public: ...@@ -506,6 +506,9 @@ public:
using Operand::dump; using Operand::dump;
void dump(const Cfg *Func, Ostream &Str) const override; void dump(const Cfg *Func, Ostream &Str) const override;
/// Return reg num of base register, if different from stack/frame register.
virtual int32_t getBaseRegNum() const { return NoRegister; }
static bool classof(const Operand *Operand) { static bool classof(const Operand *Operand) {
OperandKind Kind = Operand->getKind(); OperandKind Kind = Operand->getKind();
return Kind >= kVariable && Kind <= kVariable_Num; return Kind >= kVariable && Kind <= kVariable_Num;
......
...@@ -359,6 +359,9 @@ void TargetLowering::getVarStackSlotParams( ...@@ -359,6 +359,9 @@ void TargetLowering::getVarStackSlotParams(
*SpillAreaSizeBytes += Increment; *SpillAreaSizeBytes += Increment;
} }
} }
// For testing legalization of large stack offsets on targets with limited
// offset bits in instruction encodings, add some padding.
*SpillAreaSizeBytes += Ctx->getFlags().getTestStackExtra();
} }
void TargetLowering::alignStackSpillAreas(uint32_t SpillAreaStartOffset, void TargetLowering::alignStackSpillAreas(uint32_t SpillAreaStartOffset,
...@@ -390,10 +393,21 @@ void TargetLowering::assignVarStackSlots(VarList &SortedSpilledVariables, ...@@ -390,10 +393,21 @@ void TargetLowering::assignVarStackSlots(VarList &SortedSpilledVariables,
size_t GlobalsAndSubsequentPaddingSize, size_t GlobalsAndSubsequentPaddingSize,
bool UsesFramePointer) { bool UsesFramePointer) {
const VariablesMetadata *VMetadata = Func->getVMetadata(); const VariablesMetadata *VMetadata = Func->getVMetadata();
// For testing legalization of large stack offsets on targets with limited
// offset bits in instruction encodings, add some padding. This assumes that
// SpillAreaSizeBytes has accounted for the extra test padding.
// When UseFramePointer is true, the offset depends on the padding,
// not just the SpillAreaSizeBytes. On the other hand, when UseFramePointer
// is false, the offsets depend on the gap between SpillAreaSizeBytes
// and SpillAreaPaddingBytes, so we don't increment that.
size_t TestPadding = Ctx->getFlags().getTestStackExtra();
if (UsesFramePointer)
SpillAreaPaddingBytes += TestPadding;
size_t GlobalsSpaceUsed = SpillAreaPaddingBytes; size_t GlobalsSpaceUsed = SpillAreaPaddingBytes;
size_t NextStackOffset = SpillAreaPaddingBytes; size_t NextStackOffset = SpillAreaPaddingBytes;
std::vector<size_t> LocalsSize(Func->getNumNodes()); std::vector<size_t> LocalsSize(Func->getNumNodes());
const bool SimpleCoalescing = !callsReturnsTwice(); const bool SimpleCoalescing = !callsReturnsTwice();
for (Variable *Var : SortedSpilledVariables) { for (Variable *Var : SortedSpilledVariables) {
size_t Increment = typeWidthInBytesOnStack(Var->getType()); size_t Increment = typeWidthInBytesOnStack(Var->getType());
if (SimpleCoalescing && VMetadata->isTracked(Var)) { if (SimpleCoalescing && VMetadata->isTracked(Var)) {
......
...@@ -93,6 +93,24 @@ private: ...@@ -93,6 +93,24 @@ private:
void advanceForward(InstList::iterator &I) const; void advanceForward(InstList::iterator &I) const;
}; };
/// A helper class to advance the LoweringContext at each loop iteration.
class PostIncrLoweringContext {
PostIncrLoweringContext() = delete;
PostIncrLoweringContext(const PostIncrLoweringContext &) = delete;
PostIncrLoweringContext &operator=(const PostIncrLoweringContext &) = delete;
public:
explicit PostIncrLoweringContext(LoweringContext &Context)
: Context(Context) {}
~PostIncrLoweringContext() {
Context.advanceCur();
Context.advanceNext();
}
private:
LoweringContext &Context;
};
class TargetLowering { class TargetLowering {
TargetLowering() = delete; TargetLowering() = delete;
TargetLowering(const TargetLowering &) = delete; TargetLowering(const TargetLowering &) = delete;
......
...@@ -72,6 +72,8 @@ public: ...@@ -72,6 +72,8 @@ public:
SizeT getFrameOrStackReg() const override { SizeT getFrameOrStackReg() const override {
return UsesFramePointer ? RegARM32::Reg_fp : RegARM32::Reg_sp; return UsesFramePointer ? RegARM32::Reg_fp : RegARM32::Reg_sp;
} }
SizeT getReservedTmpReg() const { return RegARM32::Reg_ip; }
size_t typeWidthInBytesOnStack(Type Ty) const override { size_t typeWidthInBytesOnStack(Type Ty) const override {
// Round up to the next multiple of 4 bytes. In particular, i1, // Round up to the next multiple of 4 bytes. In particular, i1,
// i8, and i16 are rounded up to 4 bytes. // i8, and i16 are rounded up to 4 bytes.
...@@ -380,6 +382,16 @@ protected: ...@@ -380,6 +382,16 @@ protected:
Context.insert(InstARM32Uxt::create(Func, Dest, Src0, Pred)); Context.insert(InstARM32Uxt::create(Func, Dest, Src0, Pred));
} }
/// Run a pass through stack variables and ensure that the offsets are legal.
/// If the offset is not legal, use a new base register that accounts for
/// the offset, such that the addressing mode offset bits are now legal.
void legalizeStackSlots();
/// Returns true if the given Offset can be represented in a stack ldr/str.
bool isLegalVariableStackOffset(int32_t Offset) const;
/// Assuming Var needs its offset legalized, define a new base register
/// centered on the given Var's offset and use it.
StackVariable *legalizeVariableSlot(Variable *Var, Variable *OrigBaseReg);
TargetARM32Features CPUFeatures; TargetARM32Features CPUFeatures;
bool UsesFramePointer = false; bool UsesFramePointer = false;
bool NeedsStackAlignment = false; bool NeedsStackAlignment = false;
......
...@@ -261,6 +261,8 @@ void TargetMIPS32::emitJumpTable(const Cfg *Func, ...@@ -261,6 +261,8 @@ void TargetMIPS32::emitJumpTable(const Cfg *Func,
} }
void TargetMIPS32::emitVariable(const Variable *Var) const { void TargetMIPS32::emitVariable(const Variable *Var) const {
if (!BuildDefs::dump())
return;
Ostream &Str = Ctx->getStrEmit(); Ostream &Str = Ctx->getStrEmit();
(void)Var; (void)Var;
(void)Str; (void)Str;
......
...@@ -751,6 +751,8 @@ IceString TargetX86Base<Machine>::getRegName(SizeT RegNum, Type Ty) const { ...@@ -751,6 +751,8 @@ IceString TargetX86Base<Machine>::getRegName(SizeT RegNum, Type Ty) const {
template <class Machine> template <class Machine>
void TargetX86Base<Machine>::emitVariable(const Variable *Var) const { void TargetX86Base<Machine>::emitVariable(const Variable *Var) const {
if (!BuildDefs::dump())
return;
Ostream &Str = Ctx->getStrEmit(); Ostream &Str = Ctx->getStrEmit();
if (Var->hasReg()) { if (Var->hasReg()) {
Str << "%" << getRegName(Var->getRegNum(), Var->getType()); Str << "%" << getRegName(Var->getRegNum(), Var->getType());
...@@ -760,12 +762,16 @@ void TargetX86Base<Machine>::emitVariable(const Variable *Var) const { ...@@ -760,12 +762,16 @@ void TargetX86Base<Machine>::emitVariable(const Variable *Var) const {
llvm_unreachable("Infinite-weight Variable has no register assigned"); llvm_unreachable("Infinite-weight Variable has no register assigned");
} }
int32_t Offset = Var->getStackOffset(); int32_t Offset = Var->getStackOffset();
int32_t BaseRegNum = Var->getBaseRegNum();
if (BaseRegNum == Variable::NoRegister) {
BaseRegNum = getFrameOrStackReg();
if (!hasFramePointer()) if (!hasFramePointer())
Offset += getStackAdjustment(); Offset += getStackAdjustment();
}
if (Offset) if (Offset)
Str << Offset; Str << Offset;
const Type FrameSPTy = IceType_i32; const Type FrameSPTy = IceType_i32;
Str << "(%" << getRegName(getFrameOrStackReg(), FrameSPTy) << ")"; Str << "(%" << getRegName(BaseRegNum, FrameSPTy) << ")";
} }
template <class Machine> template <class Machine>
...@@ -777,10 +783,14 @@ TargetX86Base<Machine>::stackVarToAsmOperand(const Variable *Var) const { ...@@ -777,10 +783,14 @@ TargetX86Base<Machine>::stackVarToAsmOperand(const Variable *Var) const {
llvm_unreachable("Infinite-weight Variable has no register assigned"); llvm_unreachable("Infinite-weight Variable has no register assigned");
} }
int32_t Offset = Var->getStackOffset(); int32_t Offset = Var->getStackOffset();
int32_t BaseRegNum = Var->getBaseRegNum();
if (Var->getBaseRegNum() == Variable::NoRegister) {
BaseRegNum = getFrameOrStackReg();
if (!hasFramePointer()) if (!hasFramePointer())
Offset += getStackAdjustment(); Offset += getStackAdjustment();
}
return typename Traits::Address( return typename Traits::Address(
Traits::RegisterSet::getEncodedGPR(getFrameOrStackReg()), Offset); Traits::RegisterSet::getEncodedGPR(BaseRegNum), Offset);
} }
template <class Machine> void TargetX86Base<Machine>::lowerArguments() { template <class Machine> void TargetX86Base<Machine>::lowerArguments() {
......
; This tries to create variables with very large stack offsets.
; This requires a lot of variables/register pressure. To simplify this
; we assume poor register allocation from Om1, and a flag that forces
; the frame to add K amount of unused stack for testing.
; We only need to test ARM and other architectures which have limited space
; for specifying an offset within an instruction.
; RUN: %if --need=target_ARM32 --need=allow_dump \
; RUN: --command %p2i --filetype=asm --assemble --disassemble --target arm32 \
; RUN: -i %s --args -Om1 --skip-unimplemented --test-stack-extra 4096 \
; RUN: | %if --need=target_ARM32 --need=allow_dump \
; RUN: --command FileCheck --check-prefix ARM32 %s
declare i64 @dummy(i32 %t1, i32 %t2, i32 %t3, i64 %t4, i64 %t5)
; Test a function that requires lots of stack (due to test flag), and uses
; SP as the base register (originally).
define internal i64 @lotsOfStack(i32 %a, i32 %b, i32 %c, i32 %d) {
entry:
%t1 = xor i32 %a, %b
%t2 = or i32 %c, %d
%cmp = icmp eq i32 %t1, %t2
br i1 %cmp, label %br_1, label %br_2
br_1:
%x1 = zext i32 %t1 to i64
%y1 = ashr i64 %x1, 17
; Use some stack during the call, so that references to %t1 and %t2's
; stack slots require stack adjustment.
%r1 = call i64 @dummy(i32 123, i32 321, i32 %t2, i64 %x1, i64 %y1)
%z1 = sub i64 %r1, %y1
br label %end
br_2:
%x2 = zext i32 %t2 to i64
%y2 = and i64 %x2, 123
%r2 = call i64 @dummy(i32 123, i32 321, i32 %t2, i64 %x2, i64 %y2)
%z2 = and i64 %r2, %y2
br label %end
end:
%x3 = phi i64 [ %x1, %br_1 ], [ %x2, %br_2 ]
%z3 = phi i64 [ %z1, %br_1 ], [ %z2, %br_2 ]
%r3 = and i64 %x3, %z3
ret i64 %r3
}
; ARM32-LABEL: lotsOfStack
; ARM32-NOT: mov fp, sp
; ARM32: movw ip, #4{{.*}}
; ARM32-NEXT: sub sp, sp, ip
; ARM32: movw ip, #4232
; ARM32-NEXT: add ip, sp, ip
; ARM32-NOT: movw ip
; %t2 is the result of the "or", and %t2 will be passed via r1 to the call.
; Use that to check the stack offset of %t2. The first offset and the
; later offset right before the call should be 16 bytes apart,
; because of the sub sp, sp, #16.
; ARM32: orr [[REG:r.*]], {{.*}},
; I.e., the slot for t2 is (sp0 + 4232 - 20) == sp0 + 4212.
; ARM32: str [[REG]], [ip, #-20]
; ARM32: b {{[a-f0-9]+}}
; Now skip ahead to where the call in br_1 begins, to check how %t2 is used.
; ARM32: movw ip, #4216
; ARM32-NEXT: add ip, sp, ip
; ARM32: sub sp, sp, #16
; Now sp1 = sp0 - 16, but ip is still in terms of sp0.
; So, sp0 + 4212 == ip - 4.
; ARM32: ldr r2, [ip, #-4]
; ARM32: bl {{.*}} dummy
; ARM32: add sp, sp
; The call clobbers ip, so we need to re-create the base register.
; ARM32: movw ip, #4{{.*}}
; ARM32: b {{[a-f0-9]+}}
; ARM32: bl {{.*}} dummy
; Similar, but test a function that uses FP as the base register (originally).
define internal i64 @usesFrameReg(i32 %a, i32 %b, i32 %c, i32 %d) {
entry:
%p = alloca i8, i32 %d, align 4
%t1 = xor i32 %a, %b
%t2 = or i32 %c, %d
%cmp = icmp eq i32 %t1, %t2
br i1 %cmp, label %br_1, label %br_2
br_1:
%x1 = zext i32 %t1 to i64
%y1 = ashr i64 %x1, 17
%p32 = ptrtoint i8* %p to i32
%r1 = call i64 @dummy(i32 %p32, i32 321, i32 %t2, i64 %x1, i64 %y1)
%z1 = sub i64 %r1, %y1
br label %end
br_2:
%x2 = zext i32 %t2 to i64
%y2 = and i64 %x2, 123
%r2 = call i64 @dummy(i32 123, i32 321, i32 %d, i64 %x2, i64 %y2)
%z2 = and i64 %r2, %y2
br label %end
end:
%x3 = phi i64 [ %x1, %br_1 ], [ %x2, %br_2 ]
%z3 = phi i64 [ %z1, %br_1 ], [ %z2, %br_2 ]
%r3 = and i64 %x3, %z3
ret i64 %r3
}
; ARM32-LABEL: usesFrameReg
; ARM32: mov fp, sp
; ARM32: movw ip, #4{{.*}}
; ARM32-NEXT: sub sp, sp, ip
; ARM32: movw ip, #4100
; ARM32-NEXT: sub ip, fp, ip
; ARM32-NOT: movw ip
; %t2 is the result of the "or", and %t2 will be passed via r1 to the call.
; Use that to check the stack offset of %t2. It should be the same offset
; even after sub sp, sp, #16, because the base register was originally
; the FP and not the SP.
; ARM32: orr [[REG:r.*]], {{.*}},
; I.e., the slot for t2 is (fp0 - 4100 -24) == fp0 - 4124
; ARM32: str [[REG]], [ip, #-24]
; ARM32: b {{[a-f0-9]+}}
; Now skip ahead to where the call in br_1 begins, to check how %t2 is used.
; ARM32: movw ip, #4120
; ARM32-NEXT: sub ip, fp, ip
; ARM32: sub sp, sp, #16
; Now sp1 = sp0 - 16, but ip is still in terms of fp0.
; So, fp0 - 4124 == ip - 4.
; ARM32: ldr r2, [ip, #-4]
; ARM32: bl {{.*}} dummy
; ARM32: add sp, sp
; The call clobbers ip, so we need to re-create the base register.
; ARM32: movw ip, #4{{.*}}
; ARM32: b {{[a-f0-9]+}}
; ARM32: bl {{.*}} dummy
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