diff --git a/clang/docs/ClangCommandLineReference.rst b/clang/docs/ClangCommandLineReference.rst --- a/clang/docs/ClangCommandLineReference.rst +++ b/clang/docs/ClangCommandLineReference.rst @@ -3260,6 +3260,11 @@ Allow memory accesses to be unaligned (AArch32/AArch64 only) +.. option:: -mno-bti-at-return-twice + +Do not add a BTI instruction after a setjmp or other return-twice construct (Arm +only) + Hexagon ------- .. option:: -mieee-rnd-near diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -3323,6 +3323,11 @@ def mmark_bti_property : Flag<["-"], "mmark-bti-property">, Group, HelpText<"Add .note.gnu.property with BTI to assembly files (AArch64 only)">; +def mno_bti_at_return_twice : Flag<["-"], "mno-bti-at-return-twice">, + Group, + HelpText<"Do not add a BTI instruction after a setjmp or other" + " return-twice construct (Arm only)">; + foreach i = {1-31} in def ffixed_x#i : Flag<["-"], "ffixed-x"#i>, Group, HelpText<"Reserve the x"#i#" register (AArch64/RISC-V only)">; diff --git a/clang/lib/Driver/ToolChains/Arch/ARM.cpp b/clang/lib/Driver/ToolChains/Arch/ARM.cpp --- a/clang/lib/Driver/ToolChains/Arch/ARM.cpp +++ b/clang/lib/Driver/ToolChains/Arch/ARM.cpp @@ -861,6 +861,8 @@ } } + if (Args.getLastArg(options::OPT_mno_bti_at_return_twice)) + Features.push_back("+no-bti-at-return-twice"); } std::string arm::getARMArch(StringRef Arch, const llvm::Triple &Triple) { diff --git a/clang/test/Driver/arm-bti-return-twice.c b/clang/test/Driver/arm-bti-return-twice.c new file mode 100644 --- /dev/null +++ b/clang/test/Driver/arm-bti-return-twice.c @@ -0,0 +1,7 @@ +// RUN: %clang -target arm-arm-none-eabi -march=armv8-m.main -mbranch-protection=bti \ +// RUN: -mno-bti-at-return-twice -### %s 2>&1 | FileCheck %s --check-prefix=FEAT +// RUN: %clang -target arm-arm-none-eabi -march=armv8-m.main -mbranch-protection=bti \ +// RUN: -### %s 2>&1 | FileCheck %s --check-prefix=NOFEAT + +// FEAT: "+no-bti-at-return-twice" +// NOFEAT-NOT: "+no-bti-at-return-twice" diff --git a/llvm/lib/Target/ARM/ARM.td b/llvm/lib/Target/ARM/ARM.td --- a/llvm/lib/Target/ARM/ARM.td +++ b/llvm/lib/Target/ARM/ARM.td @@ -446,6 +446,11 @@ "Enable Pointer Authentication and Branch " "Target Identification">; +def FeatureNoBTIAtReturnTwice : SubtargetFeature<"no-bti-at-return-twice", + "NoBTIAtReturnTwice", "true", + "Don't place a BTI instruction " + "after a return-twice">; + //===----------------------------------------------------------------------===// // ARM architecture class // diff --git a/llvm/lib/Target/ARM/ARMExpandPseudoInsts.cpp b/llvm/lib/Target/ARM/ARMExpandPseudoInsts.cpp --- a/llvm/lib/Target/ARM/ARMExpandPseudoInsts.cpp +++ b/llvm/lib/Target/ARM/ARMExpandPseudoInsts.cpp @@ -3069,6 +3069,21 @@ MI.eraseFromParent(); return true; } + case ARM::t2CALL_BTI: { + MachineFunction &MF = *MI.getMF(); + MachineInstrBuilder MIB = BuildMI(MF, MI.getDebugLoc(), TII->get(ARM::tBL)); + MIB.cloneMemRefs(MI); + for (unsigned i = 0; i < MI.getNumOperands(); ++i) + MIB.add(MI.getOperand(i)); + if (MI.isCandidateForCallSiteEntry()) + MF.moveCallSiteInfo(&MI, MIB.getInstr()); + MIBundleBuilder Bundler(MBB, MI); + Bundler.append(MIB); + Bundler.append(BuildMI(MF, MI.getDebugLoc(), TII->get(ARM::t2BTI))); + finalizeBundle(MBB, Bundler.begin(), Bundler.end()); + MI.eraseFromParent(); + return true; + } case ARM::LOADDUAL: case ARM::STOREDUAL: { Register PairReg = MI.getOperand(0).getReg(); diff --git a/llvm/lib/Target/ARM/ARMISelLowering.h b/llvm/lib/Target/ARM/ARMISelLowering.h --- a/llvm/lib/Target/ARM/ARMISelLowering.h +++ b/llvm/lib/Target/ARM/ARMISelLowering.h @@ -69,6 +69,7 @@ CALL_PRED, // Function call that's predicable. CALL_NOLINK, // Function call with branch not branch-and-link. tSECALL, // CMSE non-secure function call. + t2CALL_BTI, // Thumb function call followed by BTI instruction. BRCOND, // Conditional branch. BR_JT, // Jumptable branch. BR2_JT, // Jumptable branch (2 level - jumptable entry is a jump). diff --git a/llvm/lib/Target/ARM/ARMISelLowering.cpp b/llvm/lib/Target/ARM/ARMISelLowering.cpp --- a/llvm/lib/Target/ARM/ARMISelLowering.cpp +++ b/llvm/lib/Target/ARM/ARMISelLowering.cpp @@ -1643,6 +1643,7 @@ MAKE_CASE(ARMISD::CALL_PRED) MAKE_CASE(ARMISD::CALL_NOLINK) MAKE_CASE(ARMISD::tSECALL) + MAKE_CASE(ARMISD::t2CALL_BTI) MAKE_CASE(ARMISD::BRCOND) MAKE_CASE(ARMISD::BR_JT) MAKE_CASE(ARMISD::BR2_JT) @@ -2304,6 +2305,12 @@ bool isCmseNSCall = false; bool isSibCall = false; bool PreferIndirect = false; + bool GuardWithBTI = false; + + // Lower 'returns_twice' calls to a pseudo-instruction. + if (CLI.CB && CLI.CB->getAttributes().hasFnAttr(Attribute::ReturnsTwice) + && !Subtarget->getNoBTIAtReturnTwice()) + GuardWithBTI = AFI->branchTargetEnforcement(); // Determine whether this is a non-secure function call. if (CLI.CB && CLI.CB->getAttributes().hasFnAttr("cmse_nonsecure_call")) @@ -2709,7 +2716,9 @@ // FIXME: handle tail calls differently. unsigned CallOpc; if (Subtarget->isThumb()) { - if (isCmseNSCall) + if (GuardWithBTI) + CallOpc = ARMISD::t2CALL_BTI; + else if (isCmseNSCall) CallOpc = ARMISD::tSECALL; else if ((!isDirect || isARMFunc) && !Subtarget->hasV5TOps()) CallOpc = ARMISD::CALL_NOLINK; diff --git a/llvm/lib/Target/ARM/ARMInstrThumb2.td b/llvm/lib/Target/ARM/ARMInstrThumb2.td --- a/llvm/lib/Target/ARM/ARMInstrThumb2.td +++ b/llvm/lib/Target/ARM/ARMInstrThumb2.td @@ -5718,3 +5718,10 @@ def t2AUT : PACBTIHintSpaceUseInst<"aut", 0b00101101> { let hasSideEffects = 1; } + +def ARMt2CallBTI : SDNode<"ARMISD::t2CALL_BTI", SDT_ARMcall, + [SDNPHasChain, SDNPOptInGlue, SDNPOutGlue, SDNPVariadic]>; + +def t2CALL_BTI : PseudoInst<(outs), (ins pred:$p, thumb_bl_target:$func), + IIC_Br, [(ARMt2CallBTI tglobaladdr:$func)]>, + Requires<[IsThumb2]>, Sched<[WriteBrL]>; diff --git a/llvm/lib/Target/ARM/ARMSubtarget.h b/llvm/lib/Target/ARM/ARMSubtarget.h --- a/llvm/lib/Target/ARM/ARMSubtarget.h +++ b/llvm/lib/Target/ARM/ARMSubtarget.h @@ -533,6 +533,10 @@ /// Selected instruction itineraries (one entry per itinerary class.) InstrItineraryData InstrItins; + /// NoBTIAtReturnTwice - Don't place a BTI instruction after + /// return-twice constructs (setjmp) + bool NoBTIAtReturnTwice = false; + /// Options passed via command line that could influence the target const TargetOptions &Options; @@ -947,6 +951,8 @@ bool hardenSlsRetBr() const { return HardenSlsRetBr; } bool hardenSlsBlr() const { return HardenSlsBlr; } bool hardenSlsNoComdat() const { return HardenSlsNoComdat; } + + bool getNoBTIAtReturnTwice() const { return NoBTIAtReturnTwice; } }; } // end namespace llvm diff --git a/llvm/test/CodeGen/ARM/setjmp-bti-basic.ll b/llvm/test/CodeGen/ARM/setjmp-bti-basic.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/ARM/setjmp-bti-basic.ll @@ -0,0 +1,50 @@ +; RUN: llc -mtriple=thumbv8.1m.main-arm-none-eabi < %s | FileCheck %s --check-prefix=BTI +; RUN: llc -mtriple=thumbv8.1m.main-arm-none-eabi -mattr=+no-bti-at-return-twice < %s | \ +; RUN: FileCheck %s --check-prefix=NOBTI + +; C source +; -------- +; jmp_buf buf; +; +; extern void bar(int x); +; +; int foo(int x) { +; if (setjmp(buf)) +; x = 0; +; else +; bar(x); +; return x; +; } + +@buf = global [20 x i64] zeroinitializer, align 8 + +define i32 @foo(i32 %x) { +; BTI-LABEL: foo: +; BTI: bl setjmp +; BTI-NEXT: bti +; NOBTI-LABEL: foo: +; NOBTI: bl setjmp +; NOBTI-NOT: bti + +entry: + %call = call i32 @setjmp(i64* getelementptr inbounds ([20 x i64], [20 x i64]* @buf, i32 0, i32 0)) #0 + %tobool.not = icmp eq i32 %call, 0 + br i1 %tobool.not, label %if.else, label %if.end + +if.else: ; preds = %entry + call void @bar(i32 %x) + br label %if.end + +if.end: ; preds = %entry, %if.else + %x.addr.0 = phi i32 [ %x, %if.else ], [ 0, %entry ] + ret i32 %x.addr.0 +} + +declare void @bar(i32) +declare i32 @setjmp(i64*) #0 + +attributes #0 = { returns_twice } + +!llvm.module.flags = !{!0} + +!0 = !{i32 1, !"branch-target-enforcement", i32 1} diff --git a/llvm/test/CodeGen/ARM/setjmp-bti-outliner.ll b/llvm/test/CodeGen/ARM/setjmp-bti-outliner.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/ARM/setjmp-bti-outliner.ll @@ -0,0 +1,92 @@ +; RUN: llc -mtriple=thumbv8.1m.main-arm-none-eabi -enable-machine-outliner < %s | \ +; RUN: FileCheck %s --check-prefix=BTI +; RUN: llc -mtriple=thumbv8.1m.main-arm-none-eabi -enable-machine-outliner -mattr=+no-bti-at-return-twice < %s | FileCheck %s --check-prefix=NOBTI + +; C source +; -------- +; jmp_buf buf; +; +; extern void h(int a, int b, int *c); +; +; int f(int a, int b, int c, int d) { +; if (setjmp(buf) != 0) +; return -1; +; h(a, b, &a); +; return 2 + a * (a + b) / (c + d); +; } +; +; int g(int a, int b, int c, int d) { +; if (setjmp(buf) != 0) +; return -1; +; h(a, b, &a); +; return 1 + a * (a + b) / (c + d); +; } + +@buf = global [20 x i64] zeroinitializer, align 8 + +define i32 @f(i32 %a, i32 %b, i32 %c, i32 %d) { +; BTI-LABEL: f: +; BTI: bl OUTLINED_FUNCTION_0 +; BTI-NEXT: bti +; NOBTI-LABEL: f: +; NOBTI: bl OUTLINED_FUNCTION_0 +; NOBTI-NEXT: cbz r0, .LBB0_2 +entry: + %a.addr = alloca i32, align 4 + store i32 %a, i32* %a.addr, align 4 + %call = call i32 @setjmp(i64* getelementptr inbounds ([20 x i64], [20 x i64]* @buf, i32 0, i32 0)) #0 + %cmp.not = icmp eq i32 %call, 0 + br i1 %cmp.not, label %if.end, label %return + +if.end: ; preds = %entry + call void @h(i32 %a, i32 %b, i32* nonnull %a.addr) + %0 = load i32, i32* %a.addr, align 4 + %add = add nsw i32 %0, %b + %mul = mul nsw i32 %add, %0 + %add1 = add nsw i32 %d, %c + %div = sdiv i32 %mul, %add1 + %add2 = add nsw i32 %div, 2 + br label %return + +return: ; preds = %entry, %if.end + %retval.0 = phi i32 [ %add2, %if.end ], [ -1, %entry ] + ret i32 %retval.0 +} + +define i32 @g(i32 %a, i32 %b, i32 %c, i32 %d) { +; BTI-LABEL: g: +; BTI: bl OUTLINED_FUNCTION_0 +; BTI-NEXT: bti +; NOBTI-LABEL: g: +; NOBTI: bl OUTLINED_FUNCTION_0 +; NOBTI-NEXT: cbz r0, .LBB1_2 +entry: + %a.addr = alloca i32, align 4 + store i32 %a, i32* %a.addr, align 4 + %call = call i32 @setjmp(i64* getelementptr inbounds ([20 x i64], [20 x i64]* @buf, i32 0, i32 0)) #0 + %cmp.not = icmp eq i32 %call, 0 + br i1 %cmp.not, label %if.end, label %return + +if.end: ; preds = %entry + call void @h(i32 %a, i32 %b, i32* nonnull %a.addr) + %0 = load i32, i32* %a.addr, align 4 + %add = add nsw i32 %0, %b + %mul = mul nsw i32 %add, %0 + %add1 = add nsw i32 %d, %c + %div = sdiv i32 %mul, %add1 + %add2 = add nsw i32 %div, 1 + br label %return + +return: ; preds = %entry, %if.end + %retval.0 = phi i32 [ %add2, %if.end ], [ -1, %entry ] + ret i32 %retval.0 +} + +declare void @h(i32, i32, i32*) +declare i32 @setjmp(i64*) #0 + +attributes #0 = { returns_twice } + +!llvm.module.flags = !{!0} + +!0 = !{i32 1, !"branch-target-enforcement", i32 1}