Files
clang-p2996/llvm/test/CodeGen/ARM/execute-only-save-cpsr.mir
Oliver Stannard 40614e1c14 [ARM] Save and restore CPSR around tMOVimm32
When resolving a frame index with a large offset for v6M execute-only,
we emit a tMOVimm32 pseudo-instruction, which later gets lowered to a
sequence of instructions, all of which are flag-setting. However, a
frame index may be generated for a register spill or reload instruction,
which can be inserted at a point where CPSR is live. This patch inserts
MRS and MSR instructions around the tMOVimm32 to save and restore the
value of CPSR, if CPSR is live at that point.

This may need up to two virtual registers (one to build the immediate
value, one to save CPSR) during frame index lowering, which happens
after register allocation, so we need to ensure two spill slots are
avilable to the register scavenger to ensure it can free up enough
registers for this.

There is no test for the emission (or not) of the MRS/MSR pair, because
it requires a spill or reload to be inserted at a point where CPSR is
live, which requires a large, complex function and is fragile enough
that any optimisation changes will break the test. This bug was easily
found by csmith with -verify-machineinstrs, which I now run regularly on
v6M execute-only (and many other combinations).

Patch by John Brawn and myself.

Reviewed By: stuij

Differential Revision: https://reviews.llvm.org/D158404
2023-08-24 14:15:02 +01:00

442 lines
13 KiB
YAML

# RUN: llc -run-pass=prologepilog %s -o - | FileCheck %s
# Tests to check that CPSR is saved and restored if live when we emit tMOVi32imm
# to resolve a frame offset.
--- |
target triple = "thumbv6m-arm-none-eabi"
define void @test_def_in_block(i32 %x) #0 {
entry:
%var = alloca i32, align 4
%dummy = alloca [2048 x i8], align 1
%cmp = icmp eq i32 %x, 0
store i32 %x, ptr %var, align 4
br i1 %cmp, label %if.then, label %if.end
if.then:
br label %if.end
if.end:
ret void
}
define void @test_live_in(i32 %x) #0 {
entry:
%var = alloca i32, align 4
%dummy = alloca [2048 x i8], align 1
%cmp = icmp eq i32 %x, 0
br i1 %cmp, label %if.then, label %if.end
if.then:
store i32 %x, ptr %var, align 4
%cmp1 = icmp slt i32 %x, 0
br i1 %cmp1, label %if.then2, label %if.end
if.then2:
br label %if.end
if.end:
ret void
}
define void @test_live_out(i32 %x) #0 {
entry:
%var = alloca i32, align 4
%dummy = alloca [2048 x i8], align 1
%cmp = icmp eq i32 %x, 0
store i32 %x, ptr %var, align 4
br label %if.then
if.then:
br i1 %cmp, label %if.then2, label %if.end
if.then2:
br label %if.end
if.end:
ret void
}
define void @test_live_out_def_after_mov(i32 %x) #0 {
entry:
%var = alloca i32, align 4
%dummy = alloca [2048 x i8], align 1
store i32 %x, ptr %var, align 4
%cmp = icmp eq i32 %x, 0
br label %if.then
if.then:
br i1 %cmp, label %if.then2, label %if.end
if.then2:
br label %if.end
if.end:
ret void
}
attributes #0 = { "target-features"="+execute-only" }
...
---
name: test_def_in_block
alignment: 2
exposesReturnsTwice: false
legalized: false
regBankSelected: false
selected: false
failedISel: false
tracksRegLiveness: true
hasWinCFI: false
callsEHReturn: false
callsUnwindInit: false
hasEHCatchret: false
hasEHScopes: false
hasEHFunclets: false
isOutlined: false
debugInstrRef: false
failsVerification: false
tracksDebugUserValues: false
registers: []
liveins:
- { reg: '$r0', virtual-reg: '' }
frameInfo:
isFrameAddressTaken: false
isReturnAddressTaken: false
hasStackMap: false
hasPatchPoint: false
stackSize: 0
offsetAdjustment: 0
maxAlignment: 4
adjustsStack: false
hasCalls: false
stackProtector: ''
functionContext: ''
maxCallFrameSize: 0
cvBytesOfCalleeSavedRegisters: 0
hasOpaqueSPAdjustment: false
hasVAStart: false
hasMustTailInVarArgFunc: false
hasTailCall: false
localFrameSize: 2052
savePoint: ''
restorePoint: ''
fixedStack: []
stack:
- { id: 0, name: var, type: default, offset: 0, size: 4, alignment: 4,
stack-id: default, callee-saved-register: '', callee-saved-restored: true,
local-offset: -4, debug-info-variable: '', debug-info-expression: '',
debug-info-location: '' }
- { id: 1, name: dummy, type: default, offset: 0, size: 2048, alignment: 1,
stack-id: default, callee-saved-register: '', callee-saved-restored: true,
local-offset: -2052, debug-info-variable: '', debug-info-expression: '',
debug-info-location: '' }
entry_values: []
callSites: []
debugValueSubstitutions: []
constants: []
machineFunctionInfo: {}
body: |
bb.0.entry:
successors: %bb.1(0x40000000), %bb.2(0x40000000)
liveins: $r0
; CHECK-LABEL: name: test_def_in_block
; CHECK-LABEL: bb.0.entry:
; CHECK: tCMPi8 renamable $r0, 0, 14 /* CC::al */, $noreg, implicit-def $cpsr
; CHECK-NEXT: $r2 = t2MRS_M 2048, 14 /* CC::al */, $noreg, implicit $cpsr
; CHECK-NEXT: $r1 = tMOVi32imm 2048, implicit-def $cpsr
; CHECK-NEXT: t2MSR_M 2048, killed $r2, 14 /* CC::al */, $noreg, implicit-def $cpsr
; CHECK-NEXT: $r1 = tADDhirr $r1, killed $sp, 14 /* CC::al */, $noreg
; CHECK-NEXT: tSTRi renamable $r0, killed $r1, 0, 14 /* CC::al */, $noreg :: (store (s32) into %ir.var)
tCMPi8 renamable $r0, 0, 14 /* CC::al */, $noreg, implicit-def $cpsr
tSTRspi renamable $r0, %stack.0.var, 0, 14 /* CC::al */, $noreg :: (store (s32) into %ir.var)
tBcc %bb.1, 1 /* CC::ne */, killed $cpsr
tB %bb.2, 14 /* CC::al */, $noreg
bb.1.if.then:
successors: %bb.2(0x80000000)
tB %bb.2, 14 /* CC::al */, $noreg
bb.2.if.end:
tBX_RET 14 /* CC::al */, $noreg
...
---
name: test_live_in
alignment: 2
exposesReturnsTwice: false
legalized: false
regBankSelected: false
selected: false
failedISel: false
tracksRegLiveness: true
hasWinCFI: false
callsEHReturn: false
callsUnwindInit: false
hasEHCatchret: false
hasEHScopes: false
hasEHFunclets: false
isOutlined: false
debugInstrRef: false
failsVerification: false
tracksDebugUserValues: false
registers: []
liveins:
- { reg: '$r0', virtual-reg: '' }
frameInfo:
isFrameAddressTaken: false
isReturnAddressTaken: false
hasStackMap: false
hasPatchPoint: false
stackSize: 0
offsetAdjustment: 0
maxAlignment: 4
adjustsStack: false
hasCalls: false
stackProtector: ''
functionContext: ''
maxCallFrameSize: 0
cvBytesOfCalleeSavedRegisters: 0
hasOpaqueSPAdjustment: false
hasVAStart: false
hasMustTailInVarArgFunc: false
hasTailCall: false
localFrameSize: 2052
savePoint: ''
restorePoint: ''
fixedStack: []
stack:
- { id: 0, name: var, type: default, offset: 0, size: 4, alignment: 4,
stack-id: default, callee-saved-register: '', callee-saved-restored: true,
local-offset: -4, debug-info-variable: '', debug-info-expression: '',
debug-info-location: '' }
- { id: 1, name: dummy, type: default, offset: 0, size: 2048, alignment: 1,
stack-id: default, callee-saved-register: '', callee-saved-restored: true,
local-offset: -2052, debug-info-variable: '', debug-info-expression: '',
debug-info-location: '' }
entry_values: []
callSites: []
debugValueSubstitutions: []
constants: []
machineFunctionInfo: {}
body: |
bb.0.entry:
successors: %bb.1(0x40000000), %bb.3(0x40000000)
liveins: $r0
tCMPi8 renamable $r0, 0, 14 /* CC::al */, $noreg, implicit-def $cpsr
tBcc %bb.3, 1 /* CC::ne */, $cpsr
tB %bb.1, 14 /* CC::al */, $noreg
bb.1.if.then:
successors: %bb.2(0x40000000), %bb.3(0x40000000)
liveins: $r0, $cpsr
; CHECK-LABEL: name: test_live_in
; CHECK-LABEL: bb.1.if.then:
; CHECK: $r2 = t2MRS_M 2048, 14 /* CC::al */, $noreg, implicit $cpsr
; CHECK-NEXT: $r1 = tMOVi32imm 2048, implicit-def $cpsr
; CHECK-NEXT: t2MSR_M 2048, killed $r2, 14 /* CC::al */, $noreg, implicit-def $cpsr
; CHECK-NEXT: $r1 = tADDhirr $r1, killed $sp, 14 /* CC::al */, $noreg
; CHECK-NEXT: tSTRi renamable $r0, killed $r1, 0, 14 /* CC::al */, $noreg :: (store (s32) into %ir.var)
tSTRspi renamable $r0, %stack.0.var, 0, 14 /* CC::al */, $noreg :: (store (s32) into %ir.var)
tBcc %bb.3, 5 /* CC::pl */, killed $cpsr
tB %bb.2, 14 /* CC::al */, $noreg
bb.2.if.then2:
successors: %bb.3(0x80000000)
tB %bb.3, 14 /* CC::al */, $noreg
bb.3.if.end:
tBX_RET 14 /* CC::al */, $noreg
...
---
name: test_live_out
alignment: 2
exposesReturnsTwice: false
legalized: false
regBankSelected: false
selected: false
failedISel: false
tracksRegLiveness: true
hasWinCFI: false
callsEHReturn: false
callsUnwindInit: false
hasEHCatchret: false
hasEHScopes: false
hasEHFunclets: false
isOutlined: false
debugInstrRef: false
failsVerification: false
tracksDebugUserValues: false
registers: []
liveins:
- { reg: '$r0', virtual-reg: '' }
frameInfo:
isFrameAddressTaken: false
isReturnAddressTaken: false
hasStackMap: false
hasPatchPoint: false
stackSize: 0
offsetAdjustment: 0
maxAlignment: 4
adjustsStack: false
hasCalls: false
stackProtector: ''
functionContext: ''
maxCallFrameSize: 0
cvBytesOfCalleeSavedRegisters: 0
hasOpaqueSPAdjustment: false
hasVAStart: false
hasMustTailInVarArgFunc: false
hasTailCall: false
localFrameSize: 2052
savePoint: ''
restorePoint: ''
fixedStack: []
stack:
- { id: 0, name: var, type: default, offset: 0, size: 4, alignment: 4,
stack-id: default, callee-saved-register: '', callee-saved-restored: true,
local-offset: -4, debug-info-variable: '', debug-info-expression: '',
debug-info-location: '' }
- { id: 1, name: dummy, type: default, offset: 0, size: 2048, alignment: 1,
stack-id: default, callee-saved-register: '', callee-saved-restored: true,
local-offset: -2052, debug-info-variable: '', debug-info-expression: '',
debug-info-location: '' }
entry_values: []
callSites: []
debugValueSubstitutions: []
constants: []
machineFunctionInfo: {}
body: |
bb.0.entry:
successors: %bb.1(0x40000000)
liveins: $r0
; CHECK-LABEL: name: test_live_out
; CHECK-LABEL: bb.0.entry:
; CHECK: tCMPi8 renamable $r0, 0, 14 /* CC::al */, $noreg, implicit-def $cpsr
; CHECK-NEXT: $r2 = t2MRS_M 2048, 14 /* CC::al */, $noreg, implicit $cpsr
; CHECK-NEXT: $r1 = tMOVi32imm 2048, implicit-def $cpsr
; CHECK-NEXT: t2MSR_M 2048, killed $r2, 14 /* CC::al */, $noreg, implicit-def $cpsr
; CHECK-NEXT: $r1 = tADDhirr $r1, killed $sp, 14 /* CC::al */, $noreg
; CHECK-NEXT: tSTRi renamable $r0, killed $r1, 0, 14 /* CC::al */, $noreg :: (store (s32) into %ir.var)
tCMPi8 renamable $r0, 0, 14 /* CC::al */, $noreg, implicit-def $cpsr
tSTRspi renamable $r0, %stack.0.var, 0, 14 /* CC::al */, $noreg :: (store (s32) into %ir.var)
tB %bb.1, 14 /* CC::al */, $noreg
bb.1.if.then:
successors: %bb.2(0x40000000), %bb.3(0x40000000)
liveins: $cpsr
tBcc %bb.3, 5 /* CC::pl */, killed $cpsr
tB %bb.2, 14 /* CC::al */, $noreg
bb.2.if.then2:
successors: %bb.3(0x80000000)
tB %bb.3, 14 /* CC::al */, $noreg
bb.3.if.end:
tBX_RET 14 /* CC::al */, $noreg
...
---
name: test_live_out_def_after_mov
alignment: 2
exposesReturnsTwice: false
legalized: false
regBankSelected: false
selected: false
failedISel: false
tracksRegLiveness: true
hasWinCFI: false
callsEHReturn: false
callsUnwindInit: false
hasEHCatchret: false
hasEHScopes: false
hasEHFunclets: false
isOutlined: false
debugInstrRef: false
failsVerification: false
tracksDebugUserValues: false
registers: []
liveins:
- { reg: '$r0', virtual-reg: '' }
frameInfo:
isFrameAddressTaken: false
isReturnAddressTaken: false
hasStackMap: false
hasPatchPoint: false
stackSize: 0
offsetAdjustment: 0
maxAlignment: 4
adjustsStack: false
hasCalls: false
stackProtector: ''
functionContext: ''
maxCallFrameSize: 0
cvBytesOfCalleeSavedRegisters: 0
hasOpaqueSPAdjustment: false
hasVAStart: false
hasMustTailInVarArgFunc: false
hasTailCall: false
localFrameSize: 2052
savePoint: ''
restorePoint: ''
fixedStack: []
stack:
- { id: 0, name: var, type: default, offset: 0, size: 4, alignment: 4,
stack-id: default, callee-saved-register: '', callee-saved-restored: true,
local-offset: -4, debug-info-variable: '', debug-info-expression: '',
debug-info-location: '' }
- { id: 1, name: dummy, type: default, offset: 0, size: 2048, alignment: 1,
stack-id: default, callee-saved-register: '', callee-saved-restored: true,
local-offset: -2052, debug-info-variable: '', debug-info-expression: '',
debug-info-location: '' }
entry_values: []
callSites: []
debugValueSubstitutions: []
constants: []
machineFunctionInfo: {}
body: |
bb.0.entry:
successors: %bb.1(0x40000000)
liveins: $r0
; Here the live-out cpsr is defined after the tMOVi32imm, so cpsr doesn't
; need to be saved.
; CHECK-LABEL: name: test_live_out
; CHECK-LABEL: bb.0.entry:
; CHECK: $r1 = tMOVi32imm 2048, implicit-def $cpsr
; CHECK-NEXT: $r1 = tADDhirr $r1, killed $sp, 14 /* CC::al */, $noreg
; CHECK-NEXT: tSTRi renamable $r0, killed $r1, 0, 14 /* CC::al */, $noreg :: (store (s32) into %ir.var)
; CHECK-NEXT: tCMPi8 renamable $r0, 0, 14 /* CC::al */, $noreg, implicit-def $cpsr
tSTRspi renamable $r0, %stack.0.var, 0, 14 /* CC::al */, $noreg :: (store (s32) into %ir.var)
tCMPi8 renamable $r0, 0, 14 /* CC::al */, $noreg, implicit-def $cpsr
tB %bb.1, 14 /* CC::al */, $noreg
bb.1.if.then:
successors: %bb.2(0x40000000), %bb.3(0x40000000)
liveins: $cpsr
tBcc %bb.3, 5 /* CC::pl */, killed $cpsr
tB %bb.2, 14 /* CC::al */, $noreg
bb.2.if.then2:
successors: %bb.3(0x80000000)
tB %bb.3, 14 /* CC::al */, $noreg
bb.3.if.end:
tBX_RET 14 /* CC::al */, $noreg
...