From bfdc19e77868b849b5c636bf0512970264aef571 Mon Sep 17 00:00:00 2001 From: Nemanja Ivanovic Date: Mon, 14 Dec 2020 11:33:46 -0600 Subject: [PATCH] [PowerPC] Restore stack ptr from frame ptr with setjmp If a function happens to: - call setjmp - do a 16-byte stack allocation - call a function that sets up a stack frame and longjmp's back The stack pointer that is restores by setjmp will no longer point to a valid back chain. According to the ABI, stack accesses in such a function are to be frame pointer based - so it is an error (quite obviously) to restore the stack from the back chain. We already restore the stack from the frame pointer when there are calls to fast_cc functions. We just need to also do that when there are calls to setjmp. This patch simply does that. This was pointed out by the Julia team. Differential revision: https://reviews.llvm.org/D92906 --- llvm/lib/Target/PowerPC/PPCFrameLowering.cpp | 15 +- .../PowerPC/stack-restore-with-setjmp.ll | 156 ++++++++++++++++++ 2 files changed, 164 insertions(+), 7 deletions(-) create mode 100644 llvm/test/CodeGen/PowerPC/stack-restore-with-setjmp.ll diff --git a/llvm/lib/Target/PowerPC/PPCFrameLowering.cpp b/llvm/lib/Target/PowerPC/PPCFrameLowering.cpp index 7df2f6dc9252..b93322c15534 100644 --- a/llvm/lib/Target/PowerPC/PPCFrameLowering.cpp +++ b/llvm/lib/Target/PowerPC/PPCFrameLowering.cpp @@ -375,9 +375,10 @@ bool PPCFrameLowering::needsFP(const MachineFunction &MF) const { return false; return MF.getTarget().Options.DisableFramePointerElim(MF) || - MFI.hasVarSizedObjects() || MFI.hasStackMap() || MFI.hasPatchPoint() || - (MF.getTarget().Options.GuaranteedTailCallOpt && - MF.getInfo()->hasFastCall()); + MFI.hasVarSizedObjects() || MFI.hasStackMap() || MFI.hasPatchPoint() || + MF.exposesReturnsTwice() || + (MF.getTarget().Options.GuaranteedTailCallOpt && + MF.getInfo()->hasFastCall()); } void PPCFrameLowering::replaceFPWithRealFP(MachineFunction &MF) const { @@ -584,8 +585,8 @@ bool PPCFrameLowering::stackUpdateCanBeMoved(MachineFunction &MF) const { // Frame pointers and base pointers complicate matters so don't do anything // if we have them. For example having a frame pointer will sometimes require // a copy of r1 into r31 and that makes keeping track of updates to r1 more - // difficult. - if (hasFP(MF) || RegInfo->hasBasePointer(MF)) + // difficult. Similar situation exists with setjmp. + if (hasFP(MF) || RegInfo->hasBasePointer(MF) || MF.exposesReturnsTwice()) return false; // Calls to fast_cc functions use different rules for passing parameters on @@ -1646,8 +1647,8 @@ void PPCFrameLowering::emitEpilogue(MachineFunction &MF, // If this function contained a fastcc call and GuaranteedTailCallOpt is // enabled (=> hasFastCall()==true) the fastcc call might contain a tail // call which invalidates the stack pointer value in SP(0). So we use the - // value of R31 in this case. - if (FI->hasFastCall()) { + // value of R31 in this case. Similar situation exists with setjmp. + if (FI->hasFastCall() || MF.exposesReturnsTwice()) { assert(HasFP && "Expecting a valid frame pointer."); if (!HasRedZone) RBReg = FPReg; diff --git a/llvm/test/CodeGen/PowerPC/stack-restore-with-setjmp.ll b/llvm/test/CodeGen/PowerPC/stack-restore-with-setjmp.ll new file mode 100644 index 000000000000..9928a111734b --- /dev/null +++ b/llvm/test/CodeGen/PowerPC/stack-restore-with-setjmp.ll @@ -0,0 +1,156 @@ +; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py +; RUN: llc < %s -mtriple=powerpc64le-- -verify-machineinstrs | FileCheck %s +; RUN: llc < %s -mtriple=powerpc64-- -verify-machineinstrs | FileCheck %s \ +; RUN: --check-prefix=BE +%struct.__jmp_buf_tag = type { [64 x i64], i32, %struct.__sigset_t, [8 x i8] } +%struct.__sigset_t = type { [16 x i64] } + +@.str = private unnamed_addr constant [33 x i8] c"Successfully returned from main\0A\00", align 1 + +; Function Attrs: nounwind +define dso_local signext i32 @main(i32 signext %argc, i8** nocapture readnone %argv) local_unnamed_addr #0 { +; CHECK-LABEL: main: +; CHECK: # %bb.0: # %entry +; CHECK-NEXT: mfocrf 12, 32 +; CHECK-NEXT: mflr 0 +; CHECK-NEXT: std 31, -8(1) +; CHECK-NEXT: std 0, 16(1) +; CHECK-NEXT: stw 12, 8(1) +; CHECK-NEXT: stdu 1, -784(1) +; CHECK-NEXT: # kill: def $r3 killed $r3 killed $x3 +; CHECK-NEXT: cmpwi 2, 3, 2 +; CHECK-NEXT: mr 31, 1 +; CHECK-NEXT: li 3, 0 +; CHECK-NEXT: blt 2, .LBB0_3 +; CHECK-NEXT: # %bb.1: # %if.end +; CHECK-NEXT: addi 3, 31, 112 +; CHECK-NEXT: bl _setjmp +; CHECK-NEXT: nop +; CHECK-NEXT: crmove 20, 10 +; CHECK-NEXT: # kill: def $r3 killed $r3 killed $x3 +; CHECK-NEXT: cmpwi 3, 0 +; CHECK-NEXT: crorc 20, 10, 2 +; CHECK-NEXT: crmove 21, 2 +; CHECK-NEXT: bc 4, 20, .LBB0_4 +; CHECK-NEXT: # %bb.2: # %if.end5 +; CHECK-NEXT: addis 3, 2, .L.str@toc@ha +; CHECK-NEXT: addi 3, 3, .L.str@toc@l +; CHECK-NEXT: bl printf +; CHECK-NEXT: nop +; CHECK-NEXT: # kill: def $r3 killed $r3 killed $x3 +; CHECK-NEXT: .LBB0_3: # %return +; CHECK-NEXT: extsw 3, 3 +; CHECK-NEXT: addi 1, 31, 784 +; CHECK-NEXT: ld 0, 16(1) +; CHECK-NEXT: lwz 12, 8(1) +; CHECK-NEXT: ld 31, -8(1) +; CHECK-NEXT: mtocrf 32, 12 +; CHECK-NEXT: mtlr 0 +; CHECK-NEXT: blr +; CHECK-NEXT: .LBB0_4: # %if.then3 +; CHECK-NEXT: ld 4, 0(1) +; CHECK-NEXT: stdu 4, -16(1) +; CHECK-NEXT: addi 3, 1, 96 +; CHECK-NEXT: li 4, -1 +; CHECK-NEXT: stb 4, 0(3) +; CHECK-NEXT: addi 4, 31, 112 +; CHECK-NEXT: bl test +; CHECK-NEXT: nop +; +; BE-LABEL: main: +; BE: # %bb.0: # %entry +; BE-NEXT: mflr 0 +; BE-NEXT: std 31, -8(1) +; BE-NEXT: std 0, 16(1) +; BE-NEXT: mfcr 12 +; BE-NEXT: stw 12, 8(1) +; BE-NEXT: stdu 1, -800(1) +; BE-NEXT: li 4, 0 +; BE-NEXT: # kill: def $r3 killed $r3 killed $x3 +; BE-NEXT: cmpwi 2, 3, 2 +; BE-NEXT: mr 3, 4 +; BE-NEXT: mr 31, 1 +; BE-NEXT: blt 2, .LBB0_3 +; BE-NEXT: # %bb.1: # %if.end +; BE-NEXT: addi 3, 31, 128 +; BE-NEXT: bl _setjmp +; BE-NEXT: nop +; BE-NEXT: crmove 20, 10 +; BE-NEXT: # kill: def $r3 killed $r3 killed $x3 +; BE-NEXT: cmpwi 3, 0 +; BE-NEXT: crorc 20, 10, 2 +; BE-NEXT: crmove 21, 2 +; BE-NEXT: bc 4, 20, .LBB0_4 +; BE-NEXT: # %bb.2: # %if.end5 +; BE-NEXT: addis 3, 2, .L.str@toc@ha +; BE-NEXT: addi 3, 3, .L.str@toc@l +; BE-NEXT: bl printf +; BE-NEXT: nop +; BE-NEXT: # kill: def $r3 killed $r3 killed $x3 +; BE-NEXT: .LBB0_3: # %return +; BE-NEXT: extsw 3, 3 +; BE-NEXT: addi 1, 31, 800 +; BE-NEXT: ld 0, 16(1) +; BE-NEXT: lwz 12, 8(1) +; BE-NEXT: ld 31, -8(1) +; BE-NEXT: mtlr 0 +; BE-NEXT: mtcrf 32, 12 # cr2 +; BE-NEXT: blr +; BE-NEXT: .LBB0_4: # %if.then3 +; BE-NEXT: ld 4, 0(1) +; BE-NEXT: stdu 4, -16(1) +; BE-NEXT: addi 3, 1, 112 +; BE-NEXT: li 4, -1 +; BE-NEXT: stb 4, 0(3) +; BE-NEXT: addi 4, 31, 128 +; BE-NEXT: bl test +; BE-NEXT: nop +entry: + %env_buffer = alloca [1 x %struct.__jmp_buf_tag], align 16 + %cmp = icmp slt i32 %argc, 2 + br i1 %cmp, label %return, label %if.end + +if.end: ; preds = %entry + %0 = bitcast [1 x %struct.__jmp_buf_tag]* %env_buffer to i8* + call void @llvm.lifetime.start.p0i8(i64 656, i8* nonnull %0) #5 + %arraydecay = getelementptr inbounds [1 x %struct.__jmp_buf_tag], [1 x %struct.__jmp_buf_tag]* %env_buffer, i64 0, i64 0 + %call = call signext i32 @_setjmp(%struct.__jmp_buf_tag* nonnull %arraydecay) #6 + %cmp1 = icmp ne i32 %argc, 2 + %cmp2 = icmp eq i32 %call, 0 + %or.cond = and i1 %cmp1, %cmp2 + br i1 %or.cond, label %if.then3, label %if.end5 + +if.then3: ; preds = %if.end + %1 = alloca [8 x i8], align 16 + %.sub = getelementptr inbounds [8 x i8], [8 x i8]* %1, i64 0, i64 0 + store i8 -1, i8* %.sub, align 16 + call void @test(i8* nonnull %.sub, %struct.__jmp_buf_tag* nonnull %arraydecay) #7 + unreachable + +if.end5: ; preds = %if.end + %call6 = call signext i32 (i8*, ...) @printf(i8* nonnull dereferenceable(1) getelementptr inbounds ([33 x i8], [33 x i8]* @.str, i64 0, i64 0)) + call void @llvm.lifetime.end.p0i8(i64 656, i8* nonnull %0) #5 + br label %return + +return: ; preds = %entry, %if.end5 + %retval.0 = phi i32 [ %call6, %if.end5 ], [ 0, %entry ] + ret i32 %retval.0 +} + +; Function Attrs: argmemonly nofree nosync nounwind willreturn +declare void @llvm.lifetime.start.p0i8(i64 immarg, i8* nocapture) + +; Function Attrs: nounwind returns_twice +declare signext i32 @_setjmp(%struct.__jmp_buf_tag*) local_unnamed_addr + +; Function Attrs: noreturn +declare void @test(i8*, %struct.__jmp_buf_tag*) local_unnamed_addr + +; Function Attrs: nofree nounwind +declare noundef signext i32 @printf(i8* nocapture noundef readonly, ...) local_unnamed_addr + +; Function Attrs: argmemonly nofree nosync nounwind willreturn +declare void @llvm.lifetime.end.p0i8(i64 immarg, i8* nocapture) + +attributes #0 = { nounwind } +attributes #6 = { nounwind returns_twice }