diff --git a/clang/lib/Driver/SanitizerArgs.cpp b/clang/lib/Driver/SanitizerArgs.cpp index bf77e748ebd0c..e813efc89073d 100644 --- a/clang/lib/Driver/SanitizerArgs.cpp +++ b/clang/lib/Driver/SanitizerArgs.cpp @@ -783,6 +783,14 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC, << "-ffixed-x18"; } + if ((Kinds & SanitizerKind::ShadowCallStack) && + TC.getTriple().getArch() == llvm::Triple::hexagon && + !Args.hasArg(options::OPT_ffixed_r19) && DiagnoseErrors) { + D.Diag(diag::err_drv_argument_only_allowed_with) + << lastArgumentForMask(D, Args, Kinds & SanitizerKind::ShadowCallStack) + << "-ffixed-r19"; + } + // Report error if there are non-trapping sanitizers that require // c++abi-specific parts of UBSan runtime, and they are not provided by the // toolchain. We don't have a good way to check the latter, so we just diff --git a/clang/lib/Driver/ToolChains/Linux.cpp b/clang/lib/Driver/ToolChains/Linux.cpp index f3d356305245e..71176457c9671 100644 --- a/clang/lib/Driver/ToolChains/Linux.cpp +++ b/clang/lib/Driver/ToolChains/Linux.cpp @@ -1005,6 +1005,8 @@ Linux::getSupportedSanitizers(BoundArch BA, if (IsX86_64 || IsAArch64 || IsRISCV64) { Res |= SanitizerKind::HWAddress; } + if (IsHexagon) + Res |= SanitizerKind::ShadowCallStack; if (IsX86_64 || IsAArch64) { Res |= SanitizerKind::KernelHWAddress; } diff --git a/clang/test/Driver/fsanitize-shadow-call-stack-hexagon.c b/clang/test/Driver/fsanitize-shadow-call-stack-hexagon.c new file mode 100644 index 0000000000000..89d186315d9c1 --- /dev/null +++ b/clang/test/Driver/fsanitize-shadow-call-stack-hexagon.c @@ -0,0 +1,12 @@ +// Test that -fsanitize=shadow-call-stack on Hexagon requires -ffixed-r19. + +// RUN: not %clang --target=hexagon-unknown-linux-musl \ +// RUN: -fsanitize=shadow-call-stack %s -### 2>&1 \ +// RUN: | FileCheck %s --check-prefix=HEXAGON-SCS-NO-R19 + +// RUN: %clang --target=hexagon-unknown-linux-musl \ +// RUN: -fsanitize=shadow-call-stack -ffixed-r19 %s -### 2>&1 \ +// RUN: | FileCheck %s --check-prefix=HEXAGON-SCS-WITH-R19 + +// HEXAGON-SCS-NO-R19: error: invalid argument '-fsanitize=shadow-call-stack' only allowed with '-ffixed-r19' +// HEXAGON-SCS-WITH-R19: "-fsanitize=shadow-call-stack" diff --git a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake index fee5f4a5720ed..30b76b28b4d09 100644 --- a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake +++ b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake @@ -125,7 +125,7 @@ set(ALL_XRAY_SUPPORTED_ARCH ${X86_64} ${ARM32} ${ARM64} ${MIPS32} ${MIPS64} powerpc64le ${HEXAGON} ${LOONGARCH64} ${RISCV32} ${RISCV64} ${S390X}) endif() set(ALL_XRAY_DSO_SUPPORTED_ARCH ${X86_64} ${ARM64}) -set(ALL_SHADOWCALLSTACK_SUPPORTED_ARCH ${ARM64}) +set(ALL_SHADOWCALLSTACK_SUPPORTED_ARCH ${ARM64} ${HEXAGON}) if (UNIX) if (OS_NAME MATCHES "Linux") diff --git a/compiler-rt/test/shadowcallstack/libc_support.h b/compiler-rt/test/shadowcallstack/libc_support.h index 9c8e0566380bc..00a3a38a8780b 100644 --- a/compiler-rt/test/shadowcallstack/libc_support.h +++ b/compiler-rt/test/shadowcallstack/libc_support.h @@ -32,6 +32,32 @@ __attribute__((noinline)) void scs_fputs_stdout(const char *p) { : "x0", "x1", "x2", "x8"); } +#elif defined(__hexagon__) +# ifndef __linux__ +# error Unsupported platform +# endif + +size_t scs_strlen(const char *p) { + size_t retval = 0; + while (*p++) + retval++; + return retval; +} + +__attribute__((noinline)) void scs_fputs_stdout(const char *p) { + size_t len = scs_strlen(p); + __asm__ __volatile__("{\n\t" + " r0 = #1\n\t" // fd = stdout + " r1 = %[buf]\n\t" + " r2 = %[count]\n\t" + " r6 = #64\n\t" // __NR_write + "}\n\t" + "trap0(#1)\n\t" + : + : [buf] "r"(p), [count] "r"(len) + : "r0", "r1", "r2", "r6", "memory"); +} + #else #error Unsupported platform #endif diff --git a/compiler-rt/test/shadowcallstack/lit.cfg.py b/compiler-rt/test/shadowcallstack/lit.cfg.py index 5b95deb1b0986..81151b0b7cbbc 100644 --- a/compiler-rt/test/shadowcallstack/lit.cfg.py +++ b/compiler-rt/test/shadowcallstack/lit.cfg.py @@ -25,6 +25,8 @@ scs_arch_cflags = config.target_cflags if config.target_arch == "aarch64": scs_arch_cflags += " -ffixed-x18 " +elif config.target_arch == "hexagon": + scs_arch_cflags += " -ffixed-r19 " config.substitutions.append( ( "%clang_scs ", @@ -32,5 +34,9 @@ ) ) -if config.target_os not in ["Linux"] or config.target_arch not in ["aarch64", "riscv64"]: +if config.target_os not in ["Linux"] or config.target_arch not in [ + "aarch64", + "hexagon", + "riscv64", +]: config.unsupported = True diff --git a/compiler-rt/test/shadowcallstack/minimal_runtime.h b/compiler-rt/test/shadowcallstack/minimal_runtime.h index fab4fdf8006f7..09a6bcf1efa57 100644 --- a/compiler-rt/test/shadowcallstack/minimal_runtime.h +++ b/compiler-rt/test/shadowcallstack/minimal_runtime.h @@ -19,6 +19,8 @@ static void __shadowcallstack_init() { #if defined(__aarch64__) __asm__ __volatile__("mov x18, %0" ::"r"(stack)); +#elif defined(__hexagon__) + __asm__ __volatile__("r19 = %0" ::"r"(stack)); #else #error Unsupported platform #endif diff --git a/llvm/lib/Target/Hexagon/HexagonFrameLowering.cpp b/llvm/lib/Target/Hexagon/HexagonFrameLowering.cpp index 0f73efc243986..efa4a5f726f00 100644 --- a/llvm/lib/Target/Hexagon/HexagonFrameLowering.cpp +++ b/llvm/lib/Target/Hexagon/HexagonFrameLowering.cpp @@ -21,6 +21,8 @@ #include "llvm/ADT/SetVector.h" #include "llvm/ADT/SmallSet.h" #include "llvm/ADT/SmallVector.h" +#include "llvm/BinaryFormat/Dwarf.h" +#include "llvm/CodeGen/CFIInstBuilder.h" #include "llvm/CodeGen/LivePhysRegs.h" #include "llvm/CodeGen/MachineBasicBlock.h" #include "llvm/CodeGen/MachineDominators.h" @@ -148,6 +150,89 @@ using namespace llvm; +static void emitSCSPrologue(MachineFunction &MF, MachineBasicBlock &MBB, + MachineBasicBlock::iterator MI, + const DebugLoc &DL) { + if (!MF.getFunction().hasFnAttribute(Attribute::ShadowCallStack)) + return; + + const auto &HST = MF.getSubtarget(); + // Hexagon saves LR (R31) via allocframe. If there is no frame, LR is + // not on the regular stack and does not need shadow-stack protection. + if (!HST.getFrameLowering()->hasFP(MF)) + return; + + Register SCSPReg = Hexagon::R19; + if (!MF.getSubtarget().isRegisterReservedByUser(SCSPReg)) + report_fatal_error("Must reserve r19 to use shadow call stack on Hexagon"); + + const auto &HII = *HST.getInstrInfo(); + + // r19 = add(r19, #4) + BuildMI(MBB, MI, DL, HII.get(Hexagon::A2_addi), SCSPReg) + .addReg(SCSPReg) + .addImm(4) + .setMIFlag(MachineInstr::FrameSetup); + // memw(r19 + #-4) = r31 + BuildMI(MBB, MI, DL, HII.get(Hexagon::S2_storeri_io)) + .addReg(SCSPReg) + .addImm(-4) + .addReg(Hexagon::R31) + .setMIFlag(MachineInstr::FrameSetup); + + MBB.addLiveIn(SCSPReg); + + if (!MF.needsFrameMoves()) + return; + + // CFI: DW_CFA_val_expression for the SCS register, DW_OP_bregN -4 + // Tells the unwinder that the SCS register at entry = current value - 4. + const auto &TRI = *MF.getSubtarget().getRegisterInfo(); + unsigned DwarfSCSReg = TRI.getDwarfRegNum(SCSPReg, /*IsEH=*/true); + // DW_OP_breg0..DW_OP_breg31 (0x70..0x8f) are 32 opcodes indexed by + // register number, so the register number must fit in [0, 31]. + assert(DwarfSCSReg < 32 && "SCS register should be < 32"); + const char CFIInst[] = { + (char)dwarf::DW_CFA_val_expression, + (char)DwarfSCSReg, + 2, // expression length + (char)(unsigned)(dwarf::DW_OP_breg0 + DwarfSCSReg), + (char)(-4 & 0x7f), // SLEB128 -4 + }; + CFIInstBuilder(MBB, MI, MachineInstr::FrameSetup) + .buildEscape(StringRef(CFIInst, sizeof(CFIInst))); +} + +static void emitSCSEpilogue(MachineFunction &MF, MachineBasicBlock &MBB, + MachineBasicBlock::iterator MI, + const DebugLoc &DL) { + if (!MF.getFunction().hasFnAttribute(Attribute::ShadowCallStack)) + return; + + // hasFP() is true at both call sites: the non-vararg path in + // insertEpilogueInBlock returns early when !hasFP(), and the vararg+musl + // path is inside the hasFP() branch. Check defensively. + if (!MF.getSubtarget().getFrameLowering()->hasFP(MF)) + report_fatal_error("SCS epilogue requires a frame"); + + Register SCSPReg = Hexagon::R19; + const auto &HII = *MF.getSubtarget().getInstrInfo(); + + // r31 = memw(r19 + #-4) + BuildMI(MBB, MI, DL, HII.get(Hexagon::L2_loadri_io), Hexagon::R31) + .addReg(SCSPReg) + .addImm(-4) + .setMIFlag(MachineInstr::FrameDestroy); + // r19 = add(r19, #-4) + BuildMI(MBB, MI, DL, HII.get(Hexagon::A2_addi), SCSPReg) + .addReg(SCSPReg) + .addImm(-4) + .setMIFlag(MachineInstr::FrameDestroy); + + if (MF.needsFrameMoves()) + CFIInstBuilder(MBB, MI, MachineInstr::FrameDestroy).buildRestore(SCSPReg); +} + static cl::opt DisableDeallocRet("disable-hexagon-dealloc-ret", cl::Hidden, cl::desc("Disable Dealloc Return for Hexagon target")); @@ -511,6 +596,19 @@ void HexagonFrameLowering::emitPrologue(MachineFunction &MF, bool PrologueStubs = false; insertCSRSpillsInBlock(*PrologB, CSI, HRI, PrologueStubs); insertPrologueInBlock(*PrologB, PrologueStubs); + // Insert the SCS prologue after all FrameSetup instructions so that it + // follows allocframe and any CSR spills in the instruction stream. The + // packetizer may still fuse the SCS store with the first call in the + // function, but because Hexagon packets use old-value reads the original + // R31 is always what is stored. + { + MachineBasicBlock::iterator AfterProlog = PrologB->begin(); + while (AfterProlog != PrologB->end() && + AfterProlog->getFlag(MachineInstr::FrameSetup)) + ++AfterProlog; + DebugLoc PrologDL = PrologB->findDebugLoc(AfterProlog); + emitSCSPrologue(MF, *PrologB, AfterProlog, PrologDL); + } updateEntryPaths(MF, *PrologB); if (EpilogB) { @@ -786,6 +884,8 @@ void HexagonFrameLowering::insertEpilogueInBlock(MachineBasicBlock &MBB) const { // Handle EH_RETURN. if (RetOpc == Hexagon::EH_RETURN_JMPR) { + // EH paths overwrite R31 with a handler address; the shadow stack is + // not read on this path, so no SCS epilogue is needed. BuildMI(MBB, InsertPt, dl, HII.get(Hexagon::L2_deallocframe)) .addDef(Hexagon::D15) .addReg(Hexagon::R30); @@ -797,6 +897,10 @@ void HexagonFrameLowering::insertEpilogueInBlock(MachineBasicBlock &MBB) const { // Check for RESTORE_DEALLOC_RET* tail call. Don't emit an extra dealloc- // frame instruction if we encounter it. + // These spill-stub tail calls include r19 in their save range, but SCS + // requires -ffixed-r19, which prevents the allocator from selecting stubs + // that cover r19. The two features are therefore mutually exclusive and no + // SCS epilogue is needed here. if (RetOpc == Hexagon::RESTORE_DEALLOC_RET_JMP_V4 || RetOpc == Hexagon::RESTORE_DEALLOC_RET_JMP_V4_PIC || RetOpc == Hexagon::RESTORE_DEALLOC_RET_JMP_V4_EXT || @@ -816,29 +920,45 @@ void HexagonFrameLowering::insertEpilogueInBlock(MachineBasicBlock &MBB) const { // It is possible that the restoring code is a call to a library function. // All of the restore* functions include "deallocframe", so we need to make // sure that we don't add an extra one. + bool NeedsSCS = MF.getFunction().hasFnAttribute(Attribute::ShadowCallStack); bool NeedsDeallocframe = true; + unsigned PrevOpc = 0; if (!MBB.empty() && InsertPt != MBB.begin()) { MachineBasicBlock::iterator PrevIt = std::prev(InsertPt); - unsigned COpc = PrevIt->getOpcode(); - if (COpc == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4 || - COpc == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_PIC || - COpc == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_EXT || - COpc == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_EXT_PIC || - COpc == Hexagon::PS_call_nr || COpc == Hexagon::PS_callr_nr) + PrevOpc = PrevIt->getOpcode(); + if (PrevOpc == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4 || + PrevOpc == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_PIC || + PrevOpc == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_EXT || + PrevOpc == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_EXT_PIC || + PrevOpc == Hexagon::PS_call_nr || PrevOpc == Hexagon::PS_callr_nr) NeedsDeallocframe = false; } if (!MF.getSubtarget().isEnvironmentMusl() || !MF.getFunction().isVarArg()) { - if (!NeedsDeallocframe) + if (!NeedsDeallocframe) { + // RESTORE_DEALLOC_BEFORE_TAILCALL stubs include r19 in their save range, + // but SCS requires -ffixed-r19 which prevents the allocator from + // selecting stubs that cover r19, so SCS and stubs are mutually + // exclusive. PS_call_nr/PS_callr_nr are noreturn calls so the shadow + // stack entry is never read - no SCS epilogue is needed on either path. + if (NeedsSCS && PrevOpc != Hexagon::PS_call_nr && + PrevOpc != Hexagon::PS_callr_nr) + report_fatal_error("SCS with RESTORE_DEALLOC stub: " + "-ffixed-r19 should have prevented this"); return; + } // If the returning instruction is PS_jmpret, replace it with // dealloc_return, otherwise just add deallocframe. The function // could be returning via a tail call. - if (RetOpc != Hexagon::PS_jmpret || DisableDeallocRet) { + if (RetOpc != Hexagon::PS_jmpret || DisableDeallocRet || NeedsSCS) { BuildMI(MBB, InsertPt, dl, HII.get(Hexagon::L2_deallocframe)) .addDef(Hexagon::D15) .addReg(Hexagon::R30); + // When shadow call stack is active, overwrite R31 restored by + // deallocframe with the shadow-stack copy, then retract the pointer. + if (NeedsSCS) + emitSCSEpilogue(MF, MBB, InsertPt, dl); return; } unsigned NewOpc = Hexagon::L4_return; @@ -858,11 +978,14 @@ void HexagonFrameLowering::insertEpilogueInBlock(MachineBasicBlock &MBB) const { MachineBasicBlock::iterator Term = MBB.getFirstTerminator(); MachineBasicBlock::iterator I = (Term == MBB.begin()) ? MBB.end() : std::prev(Term); - if (I == MBB.end() || - (I->getOpcode() != Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_EXT && - I->getOpcode() != Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_EXT_PIC && - I->getOpcode() != Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4 && - I->getOpcode() != Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_PIC)) + bool HasRestoreStub = + I != MBB.end() && + (I->getOpcode() == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_EXT || + I->getOpcode() == + Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_EXT_PIC || + I->getOpcode() == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4 || + I->getOpcode() == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_PIC); + if (!HasRestoreStub) BuildMI(MBB, InsertPt, dl, HII.get(Hexagon::L2_deallocframe)) .addDef(Hexagon::D15) .addReg(Hexagon::R30); @@ -870,6 +993,11 @@ void HexagonFrameLowering::insertEpilogueInBlock(MachineBasicBlock &MBB) const { BuildMI(MBB, InsertPt, dl, HII.get(Hexagon::A2_addi), SP) .addReg(SP) .addImm(RegisterSavedAreaSizePlusPadding); + // RESTORE_DEALLOC stubs are mutually exclusive with SCS (-ffixed-r19 + // prevents stubs that cover r19), so only emit SCS epilogue when we + // emitted our own deallocframe above. + if (NeedsSCS && !HasRestoreStub) + emitSCSEpilogue(MF, MBB, InsertPt, dl); } } diff --git a/llvm/test/CodeGen/Hexagon/shadow-call-stack.ll b/llvm/test/CodeGen/Hexagon/shadow-call-stack.ll new file mode 100644 index 0000000000000..ae6b254f5ed8e --- /dev/null +++ b/llvm/test/CodeGen/Hexagon/shadow-call-stack.ll @@ -0,0 +1,145 @@ +; RUN: llc -mtriple=hexagon -mattr=+reserved-r19 < %s | FileCheck %s +;; Test that the backend fatally errors without reserved-r19 (backstop for +;; the driver diagnostic in SanitizerArgs.cpp). +; RUN: not --crash llc -mtriple=hexagon < %s 2>&1 | FileCheck %s --check-prefix=ERR +; RUN: llc -mtriple=hexagon -mattr=+reserved-r19 < %s | FileCheck %s --check-prefix=CFI +; RUN: llc -mtriple=hexagon-unknown-linux-musl -mattr=+reserved-r19 < %s | FileCheck %s --check-prefix=MUSL + +;; Leaf function - no LR spill, SCS should not emit any r19 instructions. +; CHECK-LABEL: leaf: +; CHECK-NOT: r19 +; CHECK: jumpr r31 + +;; Non-leaf function - SCS emits prologue (addi + store) and epilogue (load + addi). +;; The SCS store is fused into the same packet as the first call; because +;; Hexagon packets use old-value reads the original R31 is saved regardless. +;; The epilogue load and addi are also in the same packet; the load uses the +;; old (pre-decrement) r19 value per Hexagon packet semantics, and the -4 +;; offset correctly addresses the saved slot. +; CHECK-LABEL: nonleaf: +; CHECK: r19 = add(r19,#4) +; CHECK: call bar +; CHECK: memw(r19+#-4) = r31 +; CHECK: { +; CHECK-DAG: r19 = add(r19,#-4) +; CHECK-DAG: r31 = memw(r19+#-4) +; CHECK: } +; CHECK: jumpr r31 + +;; Multi-call function - only one SCS prologue/epilogue pair, not one per call. +; CHECK-LABEL: twocalls: +; CHECK: r19 = add(r19,#4) +; CHECK: call bar +; CHECK: memw(r19+#-4) = r31 +; CHECK: call bar +; CHECK: { +; CHECK-DAG: r19 = add(r19,#-4) +; CHECK-DAG: r31 = memw(r19+#-4) +; CHECK: } +; CHECK: jumpr r31 + +;; Conditional call (shrink-wrapping): the early-return path is a leaf and +;; has no SCS prologue/epilogue. The call path gets the SCS pair. +; CHECK-LABEL: condcall: +; CHECK: if (!p0.new) jumpr:nt r31 +; CHECK: r19 = add(r19,#4) +; CHECK: call bar +; CHECK: memw(r19+#-4) = r31 +; CHECK: { +; CHECK-DAG: r19 = add(r19,#-4) +; CHECK-DAG: r31 = memw(r19+#-4) +; CHECK: } +; CHECK: jumpr r31 + +;; Tail call - SCS prologue and epilogue are both emitted; the epilogue +;; instructions and the tail jump are fused into the same packet. +; CHECK-LABEL: tailcall: +; CHECK: r19 = add(r19,#4) +; CHECK: memw(r19+#-4) = r31 +; CHECK: { +; CHECK-DAG: r19 = add(r19,#-4) +; CHECK-DAG: r31 = memw(r19+#-4) +; CHECK-DAG: jump bar +; CHECK: } + +;; Noreturn call - SCS prologue is emitted but no SCS epilogue since the +;; function never returns. +; CHECK-LABEL: noret: +; CHECK: r19 = add(r19,#4) +; CHECK: memw(r19+#-4) = r31 +; CHECK-NOT: r31 = memw +; CHECK-NOT: r19 = add(r19,#-4) +; CHECK-LABEL: nonleaf_cfi: + +;; Without r19 reserved, SCS should report an error. +; ERR: Must reserve r19 to use shadow call stack on Hexagon + +;; Non-leaf with uwtable - exercises CFI escape (DW_CFA_val_expression for r19) +;; and cfi_restore on epilogue. +; CFI-LABEL: nonleaf_cfi: +; CFI: r19 = add(r19,#4) +; CFI: memw(r19+#-4) = r31 +; CFI: .cfi_escape 0x16, 0x13, 0x02, 0x83, 0x7c +; CFI: { +; CFI-DAG: r31 = memw(r19+#-4) +; CFI-DAG: r19 = add(r19,#-4) +; CFI: } +; CFI: .cfi_restore r19 +; CFI: jumpr r31 + +;; Musl vararg - exercises the vararg epilogue path with SCS. +; MUSL-LABEL: vararg_musl: +; MUSL: r19 = add(r19,#4) +; MUSL: memw(r19+#-4) = r31 +; MUSL: { +; MUSL-DAG: r19 = add(r19,#-4) +; MUSL-DAG: r31 = memw(r19+#-4) +; MUSL: } +; MUSL: jumpr r31 + +declare void @bar() + +define void @leaf() shadowcallstack nounwind { + ret void +} + +define void @nonleaf() shadowcallstack nounwind { + call void @bar() + ret void +} + +define void @twocalls() shadowcallstack nounwind { + call void @bar() + call void @bar() + ret void +} + +define void @condcall(i1 %cond) shadowcallstack nounwind { + br i1 %cond, label %call, label %ret +call: + call void @bar() + br label %ret +ret: + ret void +} + +define void @tailcall() shadowcallstack nounwind { + call void @bar() + tail call void @bar() + ret void +} + +define void @noret() shadowcallstack nounwind { + call void @bar() noreturn + unreachable +} + +define void @nonleaf_cfi() shadowcallstack uwtable { + call void @bar() + ret void +} + +define void @vararg_musl(i32 %a, ...) shadowcallstack nounwind { + call void @bar() + ret void +}