| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,156 @@ | ||
| // NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py | ||
| // REQUIRES: x86-registered-target | ||
| // RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -O0 -emit-llvm %s -o - | FileCheck %s | ||
|
|
||
| // CHECK-LABEL: @test0( | ||
| // CHECK-NEXT: entry: | ||
| // CHECK-NEXT: [[RET:%.*]] = alloca i32, align 4 | ||
| // CHECK-NEXT: [[TMP0:%.*]] = callbr i32 asm "", "=r,!i,~{dirflag},~{fpsr},~{flags}"() #[[ATTR1:[0-9]+]] | ||
| // CHECK-NEXT: to label [[ASM_FALLTHROUGH:%.*]] [label %z.split], !srcloc !2 | ||
| // CHECK: asm.fallthrough: | ||
| // CHECK-NEXT: store i32 [[TMP0]], ptr [[RET]], align 4 | ||
| // CHECK-NEXT: store i32 42, ptr [[RET]], align 4 | ||
| // CHECK-NEXT: br label [[Z:%.*]] | ||
| // CHECK: z: | ||
| // CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[RET]], align 4 | ||
| // CHECK-NEXT: ret i32 [[TMP1]] | ||
| // CHECK: z.split: | ||
| // CHECK-NEXT: store i32 [[TMP0]], ptr [[RET]], align 4 | ||
| // CHECK-NEXT: br label [[Z]] | ||
| // | ||
| int test0 (void) { | ||
| int ret; | ||
| asm goto ("" : "=r"(ret):::z); | ||
| ret = 42; | ||
| z: | ||
| return ret; | ||
| } | ||
|
|
||
| // CHECK-LABEL: @test1( | ||
| // CHECK-NEXT: entry: | ||
| // CHECK-NEXT: [[RET:%.*]] = alloca i32, align 4 | ||
| // CHECK-NEXT: [[B:%.*]] = alloca i32, align 4 | ||
| // CHECK-NEXT: [[TMP0:%.*]] = callbr { i32, i32 } asm "", "=r,=r,!i,~{dirflag},~{fpsr},~{flags}"() #[[ATTR1]] | ||
| // CHECK-NEXT: to label [[ASM_FALLTHROUGH:%.*]] [label %z.split], !srcloc !3 | ||
| // CHECK: asm.fallthrough: | ||
| // CHECK-NEXT: [[ASMRESULT:%.*]] = extractvalue { i32, i32 } [[TMP0]], 0 | ||
| // CHECK-NEXT: [[ASMRESULT1:%.*]] = extractvalue { i32, i32 } [[TMP0]], 1 | ||
| // CHECK-NEXT: store i32 [[ASMRESULT]], ptr [[RET]], align 4 | ||
| // CHECK-NEXT: store i32 [[ASMRESULT1]], ptr [[B]], align 4 | ||
| // CHECK-NEXT: store i32 42, ptr [[RET]], align 4 | ||
| // CHECK-NEXT: br label [[Z:%.*]] | ||
| // CHECK: z: | ||
| // CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[RET]], align 4 | ||
| // CHECK-NEXT: ret i32 [[TMP1]] | ||
| // CHECK: z.split: | ||
| // CHECK-NEXT: [[ASMRESULT2:%.*]] = extractvalue { i32, i32 } [[TMP0]], 0 | ||
| // CHECK-NEXT: [[ASMRESULT3:%.*]] = extractvalue { i32, i32 } [[TMP0]], 1 | ||
| // CHECK-NEXT: store i32 [[ASMRESULT2]], ptr [[RET]], align 4 | ||
| // CHECK-NEXT: store i32 [[ASMRESULT3]], ptr [[B]], align 4 | ||
| // CHECK-NEXT: br label [[Z]] | ||
| // | ||
| int test1 (void) { | ||
| int ret, b; | ||
| asm goto ("" : "=r"(ret), "=r"(b):::z); | ||
| ret = 42; | ||
| z: | ||
| return ret; | ||
| } | ||
|
|
||
| // CHECK-LABEL: @test2( | ||
| // CHECK-NEXT: entry: | ||
| // CHECK-NEXT: [[RET:%.*]] = alloca i32, align 4 | ||
| // CHECK-NEXT: [[B:%.*]] = alloca i32, align 4 | ||
| // CHECK-NEXT: [[TMP0:%.*]] = callbr { i32, i32 } asm "", "=r,=r,!i,~{dirflag},~{fpsr},~{flags}"() #[[ATTR1]] | ||
| // CHECK-NEXT: to label [[ASM_FALLTHROUGH:%.*]] [label %z.split], !srcloc !4 | ||
| // CHECK: asm.fallthrough: | ||
| // CHECK-NEXT: [[ASMRESULT:%.*]] = extractvalue { i32, i32 } [[TMP0]], 0 | ||
| // CHECK-NEXT: [[ASMRESULT1:%.*]] = extractvalue { i32, i32 } [[TMP0]], 1 | ||
| // CHECK-NEXT: store i32 [[ASMRESULT]], ptr [[RET]], align 4 | ||
| // CHECK-NEXT: store i32 [[ASMRESULT1]], ptr [[B]], align 4 | ||
| // CHECK-NEXT: [[TMP1:%.*]] = callbr { i32, i32 } asm "", "=r,=r,!i,~{dirflag},~{fpsr},~{flags}"() #[[ATTR1]] | ||
| // CHECK-NEXT: to label [[ASM_FALLTHROUGH4:%.*]] [label %z.split9], !srcloc !5 | ||
| // CHECK: asm.fallthrough4: | ||
| // CHECK-NEXT: [[ASMRESULT5:%.*]] = extractvalue { i32, i32 } [[TMP1]], 0 | ||
| // CHECK-NEXT: [[ASMRESULT6:%.*]] = extractvalue { i32, i32 } [[TMP1]], 1 | ||
| // CHECK-NEXT: store i32 [[ASMRESULT5]], ptr [[RET]], align 4 | ||
| // CHECK-NEXT: store i32 [[ASMRESULT6]], ptr [[B]], align 4 | ||
| // CHECK-NEXT: br label [[Z:%.*]] | ||
| // CHECK: z: | ||
| // CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[RET]], align 4 | ||
| // CHECK-NEXT: ret i32 [[TMP2]] | ||
| // CHECK: z.split: | ||
| // CHECK-NEXT: [[ASMRESULT2:%.*]] = extractvalue { i32, i32 } [[TMP0]], 0 | ||
| // CHECK-NEXT: [[ASMRESULT3:%.*]] = extractvalue { i32, i32 } [[TMP0]], 1 | ||
| // CHECK-NEXT: store i32 [[ASMRESULT2]], ptr [[RET]], align 4 | ||
| // CHECK-NEXT: store i32 [[ASMRESULT3]], ptr [[B]], align 4 | ||
| // CHECK-NEXT: br label [[Z]] | ||
| // CHECK: z.split9: | ||
| // CHECK-NEXT: [[ASMRESULT7:%.*]] = extractvalue { i32, i32 } [[TMP1]], 0 | ||
| // CHECK-NEXT: [[ASMRESULT8:%.*]] = extractvalue { i32, i32 } [[TMP1]], 1 | ||
| // CHECK-NEXT: store i32 [[ASMRESULT7]], ptr [[RET]], align 4 | ||
| // CHECK-NEXT: store i32 [[ASMRESULT8]], ptr [[B]], align 4 | ||
| // CHECK-NEXT: br label [[Z]] | ||
| // | ||
| int test2 (void) { | ||
| int ret, b; | ||
| asm goto ("" : "=r"(ret), "=r"(b):::z); | ||
| asm goto ("" : "=r"(ret), "=r"(b):::z); | ||
| z: | ||
| return ret; | ||
| } | ||
| // CHECK-LABEL: @test3( | ||
| // CHECK-NEXT: entry: | ||
| // CHECK-NEXT: [[RETVAL:%.*]] = alloca i32, align 4 | ||
| // CHECK-NEXT: [[OUT1_ADDR:%.*]] = alloca i32, align 4 | ||
| // CHECK-NEXT: store i32 [[OUT1:%.*]], ptr [[OUT1_ADDR]], align 4 | ||
| // CHECK-NEXT: [[TMP0:%.*]] = callbr i32 asm "", "=r,!i,!i,~{dirflag},~{fpsr},~{flags}"() #[[ATTR1]] | ||
| // CHECK-NEXT: to label [[ASM_FALLTHROUGH:%.*]] [label [[LABEL_TRUE_SPLIT:%.*]], label %loop.split], !srcloc !6 | ||
| // CHECK: asm.fallthrough: | ||
| // CHECK-NEXT: store i32 [[TMP0]], ptr [[OUT1_ADDR]], align 4 | ||
| // CHECK-NEXT: store i32 0, ptr [[RETVAL]], align 4 | ||
| // CHECK-NEXT: br label [[RETURN:%.*]] | ||
| // CHECK: label_true.split: | ||
| // CHECK-NEXT: store i32 [[TMP0]], ptr [[OUT1_ADDR]], align 4 | ||
| // CHECK-NEXT: br label [[LABEL_TRUE:%.*]] | ||
| // CHECK: loop.split: | ||
| // CHECK-NEXT: store i32 [[TMP0]], ptr [[OUT1_ADDR]], align 4 | ||
| // CHECK-NEXT: br label [[LOOP:%.*]] | ||
| // CHECK: loop: | ||
| // CHECK-NEXT: store i32 0, ptr [[RETVAL]], align 4 | ||
| // CHECK-NEXT: br label [[RETURN]] | ||
| // CHECK: label_true: | ||
| // CHECK-NEXT: store i32 1, ptr [[RETVAL]], align 4 | ||
| // CHECK-NEXT: br label [[RETURN]] | ||
| // CHECK: return: | ||
| // CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[RETVAL]], align 4 | ||
| // CHECK-NEXT: ret i32 [[TMP1]] | ||
| // | ||
| int test3 (int out1) { | ||
| asm goto("" : "=r"(out1)::: label_true, loop); | ||
| return 0; | ||
| loop: | ||
| return 0; | ||
| label_true: | ||
| return 1; | ||
| } | ||
|
|
||
| // CHECK-LABEL: @test4( | ||
| // CHECK-NEXT: entry: | ||
| // CHECK-NEXT: [[X:%.*]] = alloca i32, align 4 | ||
| // CHECK-NEXT: br label [[FOO:%.*]] | ||
| // CHECK: foo: | ||
| // CHECK-NEXT: [[TMP0:%.*]] = callbr i32 asm "", "=r,!i,~{dirflag},~{fpsr},~{flags}"() #[[ATTR1]] | ||
| // CHECK-NEXT: to label [[ASM_FALLTHROUGH:%.*]] [label %foo.split], !srcloc !7 | ||
| // CHECK: asm.fallthrough: | ||
| // CHECK-NEXT: store i32 [[TMP0]], ptr [[X]], align 4 | ||
| // CHECK-NEXT: ret void | ||
| // CHECK: foo.split: | ||
| // CHECK-NEXT: store i32 [[TMP0]], ptr [[X]], align 4 | ||
| // CHECK-NEXT: br label [[FOO]] | ||
| // | ||
| void test4 (void) { | ||
| int x; | ||
| foo: | ||
| asm goto ("" : "=r"(x):::foo); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,230 @@ | ||
| //===-- CallBrPrepare - Prepare callbr for code generation ----------------===// | ||
| // | ||
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
| // See https://llvm.org/LICENSE.txt for license information. | ||
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
| // | ||
| // This pass lowers callbrs in LLVM IR in order to to assist SelectionDAG's | ||
| // codegen. | ||
| // | ||
| // In particular, this pass assists in inserting register copies for the output | ||
| // values of a callbr along the edges leading to the indirect target blocks. | ||
| // Though the output SSA value is defined by the callbr instruction itself in | ||
| // the IR representation, the value cannot be copied to the appropriate virtual | ||
| // registers prior to jumping to an indirect label, since the jump occurs | ||
| // within the user-provided assembly blob. | ||
| // | ||
| // Instead, those copies must occur separately at the beginning of each | ||
| // indirect target. That requires that we create a separate SSA definition in | ||
| // each of them (via llvm.callbr.landingpad), and may require splitting | ||
| // critical edges so we have a location to place the intrinsic. Finally, we | ||
| // remap users of the original callbr output SSA value to instead point to the | ||
| // appropriate llvm.callbr.landingpad value. | ||
| // | ||
| // Ideally, this could be done inside SelectionDAG, or in the | ||
| // MachineInstruction representation, without the use of an IR-level intrinsic. | ||
| // But, within the current framework, it’s simpler to implement as an IR pass. | ||
| // (If support for callbr in GlobalISel is implemented, it’s worth considering | ||
| // whether this is still required.) | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "llvm/ADT/ArrayRef.h" | ||
| #include "llvm/ADT/SmallPtrSet.h" | ||
| #include "llvm/ADT/SmallVector.h" | ||
| #include "llvm/ADT/iterator.h" | ||
| #include "llvm/Analysis/CFG.h" | ||
| #include "llvm/CodeGen/Passes.h" | ||
| #include "llvm/IR/BasicBlock.h" | ||
| #include "llvm/IR/Dominators.h" | ||
| #include "llvm/IR/Function.h" | ||
| #include "llvm/IR/IRBuilder.h" | ||
| #include "llvm/IR/Instructions.h" | ||
| #include "llvm/IR/IntrinsicInst.h" | ||
| #include "llvm/IR/Intrinsics.h" | ||
| #include "llvm/InitializePasses.h" | ||
| #include "llvm/Pass.h" | ||
| #include "llvm/Transforms/Utils/BasicBlockUtils.h" | ||
| #include "llvm/Transforms/Utils/SSAUpdater.h" | ||
|
|
||
| using namespace llvm; | ||
|
|
||
| #define DEBUG_TYPE "callbrprepare" | ||
|
|
||
| namespace { | ||
|
|
||
| class CallBrPrepare : public FunctionPass { | ||
| bool SplitCriticalEdges(ArrayRef<CallBrInst *> CBRs, DominatorTree &DT); | ||
| bool InsertIntrinsicCalls(ArrayRef<CallBrInst *> CBRs, | ||
| DominatorTree &DT) const; | ||
| void UpdateSSA(DominatorTree &DT, CallBrInst *CBR, CallInst *Intrinsic, | ||
| SSAUpdater &SSAUpdate) const; | ||
|
|
||
| public: | ||
| CallBrPrepare() : FunctionPass(ID) {} | ||
| void getAnalysisUsage(AnalysisUsage &AU) const override; | ||
| bool runOnFunction(Function &Fn) override; | ||
| static char ID; | ||
| }; | ||
|
|
||
| } // end anonymous namespace | ||
|
|
||
| char CallBrPrepare::ID = 0; | ||
| INITIALIZE_PASS_BEGIN(CallBrPrepare, DEBUG_TYPE, "Prepare callbr", false, false) | ||
| INITIALIZE_PASS_DEPENDENCY(DominatorTreeWrapperPass) | ||
| INITIALIZE_PASS_END(CallBrPrepare, DEBUG_TYPE, "Prepare callbr", false, false) | ||
|
|
||
| FunctionPass *llvm::createCallBrPass() { return new CallBrPrepare(); } | ||
|
|
||
| void CallBrPrepare::getAnalysisUsage(AnalysisUsage &AU) const { | ||
| AU.addPreserved<DominatorTreeWrapperPass>(); | ||
| } | ||
|
|
||
| static SmallVector<CallBrInst *, 2> FindCallBrs(Function &Fn) { | ||
| SmallVector<CallBrInst *, 2> CBRs; | ||
| for (BasicBlock &BB : Fn) | ||
| if (auto *CBR = dyn_cast<CallBrInst>(BB.getTerminator())) | ||
| if (!CBR->getType()->isVoidTy() && !CBR->use_empty()) | ||
| CBRs.push_back(CBR); | ||
| return CBRs; | ||
| } | ||
|
|
||
| bool CallBrPrepare::SplitCriticalEdges(ArrayRef<CallBrInst *> CBRs, | ||
| DominatorTree &DT) { | ||
| bool Changed = false; | ||
| CriticalEdgeSplittingOptions Options(&DT); | ||
| Options.setMergeIdenticalEdges(); | ||
|
|
||
| // The indirect destination might be duplicated between another parameter... | ||
| // %0 = callbr ... [label %x, label %x] | ||
| // ...hence MergeIdenticalEdges and AllowIndentical edges, but we don't need | ||
| // to split the default destination if it's duplicated between an indirect | ||
| // destination... | ||
| // %1 = callbr ... to label %x [label %x] | ||
| // ...hence starting at 1 and checking against successor 0 (aka the default | ||
| // destination). | ||
| for (CallBrInst *CBR : CBRs) | ||
| for (unsigned i = 1, e = CBR->getNumSuccessors(); i != e; ++i) | ||
| if (CBR->getSuccessor(i) == CBR->getSuccessor(0) || | ||
| isCriticalEdge(CBR, i, /*AllowIdenticalEdges*/ true)) | ||
| if (SplitKnownCriticalEdge(CBR, i, Options)) | ||
| Changed = true; | ||
| return Changed; | ||
| } | ||
|
|
||
| bool CallBrPrepare::InsertIntrinsicCalls(ArrayRef<CallBrInst *> CBRs, | ||
| DominatorTree &DT) const { | ||
| bool Changed = false; | ||
| SmallPtrSet<const BasicBlock *, 4> Visited; | ||
| IRBuilder<> Builder(CBRs[0]->getContext()); | ||
| for (CallBrInst *CBR : CBRs) { | ||
| if (!CBR->getNumIndirectDests()) | ||
| continue; | ||
|
|
||
| SSAUpdater SSAUpdate; | ||
| SSAUpdate.Initialize(CBR->getType(), CBR->getName()); | ||
| SSAUpdate.AddAvailableValue(CBR->getParent(), CBR); | ||
| SSAUpdate.AddAvailableValue(CBR->getDefaultDest(), CBR); | ||
|
|
||
| for (BasicBlock *IndDest : CBR->getIndirectDests()) { | ||
| if (!Visited.insert(IndDest).second) | ||
| continue; | ||
| Builder.SetInsertPoint(&*IndDest->begin()); | ||
| CallInst *Intrinsic = Builder.CreateIntrinsic( | ||
| CBR->getType(), Intrinsic::callbr_landingpad, {CBR}); | ||
| SSAUpdate.AddAvailableValue(IndDest, Intrinsic); | ||
| UpdateSSA(DT, CBR, Intrinsic, SSAUpdate); | ||
| Changed = true; | ||
| } | ||
| } | ||
| return Changed; | ||
| } | ||
|
|
||
| static bool IsInSameBasicBlock(const Use &U, const BasicBlock *BB) { | ||
| const auto *I = dyn_cast<Instruction>(U.getUser()); | ||
| return I && I->getParent() == BB; | ||
| } | ||
|
|
||
| static void PrintDebugDomInfo(const DominatorTree &DT, const Use &U, | ||
| const BasicBlock *BB, bool IsDefaultDest) { | ||
| if (!isa<Instruction>(U.getUser())) | ||
| return; | ||
| const bool IsDominated = DT.dominates(BB, U); | ||
| LLVM_DEBUG(dbgs() << "Use: " << *U.getUser() << ", in block " | ||
| << cast<Instruction>(U.getUser())->getParent()->getName() | ||
| << ", is " << (IsDominated ? "" : "NOT ") << "dominated by " | ||
| << BB->getName() << " (" << (IsDefaultDest ? "in" : "") | ||
| << "direct)\n"); | ||
| } | ||
|
|
||
| void CallBrPrepare::UpdateSSA(DominatorTree &DT, CallBrInst *CBR, | ||
| CallInst *Intrinsic, | ||
| SSAUpdater &SSAUpdate) const { | ||
|
|
||
| SmallPtrSet<Use *, 4> Visited; | ||
| BasicBlock *DefaultDest = CBR->getDefaultDest(); | ||
| BasicBlock *LandingPad = Intrinsic->getParent(); | ||
|
|
||
| SmallVector<Use *, 4> Uses(make_pointer_range(CBR->uses())); | ||
| for (Use *U : Uses) { | ||
| if (!Visited.insert(U).second) | ||
| continue; | ||
|
|
||
| #ifndef NDEBUG | ||
| PrintDebugDomInfo(DT, *U, LandingPad, /*IsDefaultDest*/ false); | ||
| PrintDebugDomInfo(DT, *U, DefaultDest, /*IsDefaultDest*/ true); | ||
| #endif | ||
|
|
||
| // Don't rewrite the use in the newly inserted intrinsic. | ||
| if (const auto *II = dyn_cast<IntrinsicInst>(U->getUser())) | ||
| if (II->getIntrinsicID() == Intrinsic::callbr_landingpad) | ||
| continue; | ||
|
|
||
| // If the Use is in the same BasicBlock as the Intrinsic call, replace | ||
| // the Use with the value of the Intrinsic call. | ||
| if (IsInSameBasicBlock(*U, LandingPad)) { | ||
| U->set(Intrinsic); | ||
| continue; | ||
| } | ||
|
|
||
| // If the Use is dominated by the default dest, do not touch it. | ||
| if (DT.dominates(DefaultDest, *U)) | ||
| continue; | ||
|
|
||
| SSAUpdate.RewriteUse(*U); | ||
| } | ||
| } | ||
|
|
||
| bool CallBrPrepare::runOnFunction(Function &Fn) { | ||
| bool Changed = false; | ||
| SmallVector<CallBrInst *, 2> CBRs = FindCallBrs(Fn); | ||
|
|
||
| if (CBRs.empty()) | ||
| return Changed; | ||
|
|
||
| // It's highly likely that most programs do not contain CallBrInsts. Follow a | ||
| // similar pattern from SafeStackLegacyPass::runOnFunction to reuse previous | ||
| // domtree analysis if available, otherwise compute it lazily. This avoids | ||
| // forcing Dominator Tree Construction at -O0 for programs that likely do not | ||
| // contain CallBrInsts. It does pessimize programs with callbr at higher | ||
| // optimization levels, as the DominatorTree created here is not reused by | ||
| // subsequent passes. | ||
| DominatorTree *DT; | ||
| std::optional<DominatorTree> LazilyComputedDomTree; | ||
| if (auto *DTWP = getAnalysisIfAvailable<DominatorTreeWrapperPass>()) | ||
| DT = &DTWP->getDomTree(); | ||
| else { | ||
| LazilyComputedDomTree.emplace(Fn); | ||
| DT = &*LazilyComputedDomTree; | ||
| } | ||
|
|
||
| if (SplitCriticalEdges(CBRs, *DT)) | ||
| Changed = true; | ||
|
|
||
| if (InsertIntrinsicCalls(CBRs, *DT)) | ||
| Changed = true; | ||
|
|
||
| return Changed; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| ; NOTE: Assertions have been autogenerated by utils/update_mir_test_checks.py | ||
| ; RUN: llc %s -o - -stop-after=finalize-isel -start-before=ppc-isel | FileCheck %s | ||
|
|
||
| target datalayout = "e-m:e-i64:64-n32:64-S128-v256:256:256-v512:512:512" | ||
| target triple = "powerpc64le-unknown-linux-gnu" | ||
|
|
||
| define void @strncpy_from_kernel_nofault_count() { | ||
| ; CHECK-LABEL: name: strncpy_from_kernel_nofault_count | ||
| ; CHECK: bb.0.entry: | ||
| ; CHECK-NEXT: successors: %bb.1(0x80000000), %bb.3(0x00000000) | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: INLINEASM_BR &"", 0 /* attdialect */, 131082 /* regdef:GPRC */, def %1, 13 /* imm */, %bb.3 | ||
| ; CHECK-NEXT: [[COPY:%[0-9]+]]:gprc = COPY %1 | ||
| ; CHECK-NEXT: B %bb.1 | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: bb.1.asm.fallthrough: | ||
| ; CHECK-NEXT: successors: %bb.2(0x80000000) | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: STB [[COPY]], 0, $zero8 :: (store (s8) into `ptr null`) | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: bb.2.Efault: | ||
| ; CHECK-NEXT: BLR8 implicit $lr8, implicit $rm | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: bb.3.Efault.split (machine-block-address-taken, inlineasm-br-indirect-target): | ||
| ; CHECK-NEXT: successors: %bb.2(0x80000000) | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: STB %1, 0, $zero8 :: (store (s8) into `ptr null`) | ||
| ; CHECK-NEXT: B %bb.2 | ||
| entry: | ||
| %0 = callbr i8 asm "", "=r,!i"() | ||
| to label %asm.fallthrough [label %Efault.split] | ||
|
|
||
| asm.fallthrough: | ||
| store i8 %0, ptr null, align 1 | ||
| br label %Efault | ||
|
|
||
| Efault: | ||
| ret void | ||
|
|
||
| Efault.split: | ||
| %1 = call i8 @llvm.callbr.landingpad.i8(i8 %0) | ||
| store i8 %1, ptr null, align 1 | ||
| br label %Efault | ||
| } | ||
| declare i8 @llvm.callbr.landingpad.i8(i8) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| ; NOTE: Assertions have been autogenerated by utils/update_mir_test_checks.py | ||
| ; RUN: llc -mtriple=i386-linux-gnu %s -o - -stop-after=finalize-isel \ | ||
| ; RUN: -start-before=x86-isel | FileCheck %s | ||
|
|
||
| define i8 @emulator_cmpxchg_emulated() { | ||
| ; CHECK-LABEL: name: emulator_cmpxchg_emulated | ||
| ; CHECK: bb.0.entry: | ||
| ; CHECK-NEXT: successors: %bb.1(0x80000000), %bb.2(0x00000000) | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: [[MOV32rm:%[0-9]+]]:gr32 = MOV32rm $noreg, 1, $noreg, 0, $noreg :: (load (s32) from `ptr null`, align 8) | ||
| ; CHECK-NEXT: INLINEASM_BR &"", 16 /* maystore attdialect */, 2359306 /* regdef:GR32 */, def %2, 2359306 /* regdef:GR32 */, def %3, 2147549193 /* reguse tiedto:$1 */, [[MOV32rm]](tied-def 5), 13 /* imm */, %bb.2 | ||
| ; CHECK-NEXT: [[COPY:%[0-9]+]]:gr32 = COPY $eflags | ||
| ; CHECK-NEXT: $eflags = COPY [[COPY]] | ||
| ; CHECK-NEXT: [[SETCCr:%[0-9]+]]:gr8 = SETCCr 4, implicit $eflags | ||
| ; CHECK-NEXT: [[COPY1:%[0-9]+]]:gr32 = COPY %3 | ||
| ; CHECK-NEXT: JMP_1 %bb.1 | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: bb.1.asm.fallthrough: | ||
| ; CHECK-NEXT: $al = COPY [[SETCCr]] | ||
| ; CHECK-NEXT: RET 0, $al | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: bb.2.efaultu64.split (machine-block-address-taken, inlineasm-br-indirect-target): | ||
| ; CHECK-NEXT: [[SETCCr1:%[0-9]+]]:gr8 = SETCCr 4, implicit $eflags | ||
| ; CHECK-NEXT: $al = COPY [[SETCCr1]] | ||
| ; CHECK-NEXT: RET 0, $al | ||
| entry: | ||
| %0 = load i32, ptr null, align 8 | ||
| %1 = callbr { i8, i32 } asm "", "={@ccz},=r,1,!i"(i32 %0) | ||
| to label %asm.fallthrough [label %efaultu64.split] | ||
|
|
||
| asm.fallthrough: | ||
| %asmresult = extractvalue { i8, i32 } %1, 0 | ||
| %asmresult1 = extractvalue { i8, i32 } %1, 1 | ||
| ret i8 %asmresult | ||
|
|
||
| efaultu64.split: | ||
| %2 = call { i8, i32 } @llvm.callbr.landingpad.sl_i8i32s({ i8, i32 } %1) | ||
| %asmresult2 = extractvalue { i8, i32 } %2, 0 | ||
| %asmresult3 = extractvalue { i8, i32 } %2, 1 | ||
| ret i8 %asmresult2 | ||
| } | ||
|
|
||
| ; Same test but return second value | ||
| define i32 @emulator_cmpxchg_emulated2() { | ||
| ; CHECK-LABEL: name: emulator_cmpxchg_emulated2 | ||
| ; CHECK: bb.0.entry: | ||
| ; CHECK-NEXT: successors: %bb.1(0x80000000), %bb.2(0x00000000) | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: [[MOV32rm:%[0-9]+]]:gr32 = MOV32rm $noreg, 1, $noreg, 0, $noreg :: (load (s32) from `ptr null`, align 8) | ||
| ; CHECK-NEXT: INLINEASM_BR &"", 16 /* maystore attdialect */, 2359306 /* regdef:GR32 */, def %2, 2359306 /* regdef:GR32 */, def %3, 2147549193 /* reguse tiedto:$1 */, [[MOV32rm]](tied-def 5), 13 /* imm */, %bb.2 | ||
| ; CHECK-NEXT: [[COPY:%[0-9]+]]:gr32 = COPY $eflags | ||
| ; CHECK-NEXT: $eflags = COPY [[COPY]] | ||
| ; CHECK-NEXT: [[SETCCr:%[0-9]+]]:gr8 = SETCCr 4, implicit $eflags | ||
| ; CHECK-NEXT: [[COPY1:%[0-9]+]]:gr32 = COPY %3 | ||
| ; CHECK-NEXT: JMP_1 %bb.1 | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: bb.1.asm.fallthrough: | ||
| ; CHECK-NEXT: $eax = COPY [[COPY1]] | ||
| ; CHECK-NEXT: RET 0, $eax | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: bb.2.efaultu64.split (machine-block-address-taken, inlineasm-br-indirect-target): | ||
| ; CHECK-NEXT: $eax = COPY %3 | ||
| ; CHECK-NEXT: RET 0, $eax | ||
| entry: | ||
| %0 = load i32, ptr null, align 8 | ||
| %1 = callbr { i8, i32 } asm "", "={@ccz},=r,1,!i"(i32 %0) | ||
| to label %asm.fallthrough [label %efaultu64.split] | ||
|
|
||
| asm.fallthrough: | ||
| %asmresult = extractvalue { i8, i32 } %1, 0 | ||
| %asmresult1 = extractvalue { i8, i32 } %1, 1 | ||
| ret i32 %asmresult1 | ||
|
|
||
| efaultu64.split: | ||
| %2 = call { i8, i32 } @llvm.callbr.landingpad.sl_i8i32s({ i8, i32 } %1) | ||
| %asmresult2 = extractvalue { i8, i32 } %2, 0 | ||
| %asmresult3 = extractvalue { i8, i32 } %2, 1 | ||
| ret i32 %asmresult3 | ||
| } | ||
|
|
||
| define i64 @multireg() { | ||
| ; CHECK-LABEL: name: multireg | ||
| ; CHECK: bb.0.entry: | ||
| ; CHECK-NEXT: successors: %bb.1(0x80000000), %bb.2(0x00000000) | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: INLINEASM_BR &"", 0 /* attdialect */, 18 /* regdef */, implicit-def $eax, implicit-def $edx, 13 /* imm */, %bb.2 | ||
| ; CHECK-NEXT: [[COPY:%[0-9]+]]:gr32 = COPY $eax | ||
| ; CHECK-NEXT: [[COPY1:%[0-9]+]]:gr32 = COPY $edx | ||
| ; CHECK-NEXT: [[COPY2:%[0-9]+]]:gr32 = COPY [[COPY1]] | ||
| ; CHECK-NEXT: [[COPY3:%[0-9]+]]:gr32 = COPY [[COPY]] | ||
| ; CHECK-NEXT: JMP_1 %bb.1 | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: bb.1.ft: | ||
| ; CHECK-NEXT: $eax = COPY [[COPY3]] | ||
| ; CHECK-NEXT: $edx = COPY [[COPY2]] | ||
| ; CHECK-NEXT: RET 0, $eax, $edx | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: bb.2.split (machine-block-address-taken, inlineasm-br-indirect-target): | ||
| ; CHECK-NEXT: liveins: $eax, $edx | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: [[COPY4:%[0-9]+]]:gr32 = COPY $eax | ||
| ; CHECK-NEXT: [[COPY5:%[0-9]+]]:gr32 = COPY $edx | ||
| ; CHECK-NEXT: $eax = COPY [[COPY4]] | ||
| ; CHECK-NEXT: $edx = COPY [[COPY5]] | ||
| ; CHECK-NEXT: RET 0, $eax, $edx | ||
| entry: | ||
| %0 = callbr i64 asm "", "=A,!i"() to label %ft [label %split] | ||
| ft: | ||
| ret i64 %0 | ||
| split: | ||
| %1 = call i64 @llvm.callbr.landingpad.i64(i64 %0) | ||
| ret i64 %1 | ||
| } | ||
| declare i64 @llvm.callbr.landingpad.i64(i64) | ||
| declare { i8, i32 } @llvm.callbr.landingpad.sl_i8i32s({ i8, i32 }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,368 @@ | ||
| ; NOTE: Assertions have been autogenerated by utils/update_mir_test_checks.py | ||
| ; RUN: llc -mtriple=x86_64-linux-gnu %s -o - -stop-after=finalize-isel \ | ||
| ; RUN: -start-before=x86-isel | FileCheck %s | ||
|
|
||
| ; One virtual register, w/o phi | ||
| define i32 @test0() { | ||
| ; CHECK-LABEL: name: test0 | ||
| ; CHECK: bb.0 (%ir-block.0): | ||
| ; CHECK-NEXT: successors: %bb.1(0x80000000), %bb.2(0x00000000) | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: INLINEASM_BR &"", 0 /* attdialect */, 2359306 /* regdef:GR32 */, def %1, 13 /* imm */, %bb.2 | ||
| ; CHECK-NEXT: [[COPY:%[0-9]+]]:gr32 = COPY %1 | ||
| ; CHECK-NEXT: JMP_1 %bb.1 | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: bb.1.cleanup: | ||
| ; CHECK-NEXT: [[MOV32ri:%[0-9]+]]:gr32 = MOV32ri 42 | ||
| ; CHECK-NEXT: $eax = COPY [[MOV32ri]] | ||
| ; CHECK-NEXT: RET 0, $eax | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: bb.2.z.split (machine-block-address-taken, inlineasm-br-indirect-target): | ||
| ; CHECK-NEXT: $eax = COPY %1 | ||
| ; CHECK-NEXT: RET 0, $eax | ||
| %direct = callbr i32 asm "", "=r,!i"() | ||
| to label %cleanup [label %z.split] | ||
|
|
||
| cleanup: | ||
| ret i32 42 | ||
| z.split: | ||
| %indirect = call i32 @llvm.callbr.landingpad.i32(i32 %direct) | ||
| ret i32 %indirect | ||
| } | ||
|
|
||
| ; One virtual register, w/ phi | ||
| define i32 @test1() { | ||
| ; CHECK-LABEL: name: test1 | ||
| ; CHECK: bb.0.entry: | ||
| ; CHECK-NEXT: successors: %bb.2(0x80000000), %bb.1(0x00000000) | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: [[MOV32ri:%[0-9]+]]:gr32 = MOV32ri 42 | ||
| ; CHECK-NEXT: INLINEASM_BR &"", 0 /* attdialect */, 2359306 /* regdef:GR32 */, def %4, 13 /* imm */, %bb.1 | ||
| ; CHECK-NEXT: [[COPY:%[0-9]+]]:gr32 = COPY %4 | ||
| ; CHECK-NEXT: JMP_1 %bb.2 | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: bb.1.z.split (machine-block-address-taken, inlineasm-br-indirect-target): | ||
| ; CHECK-NEXT: successors: %bb.2(0x80000000) | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: [[COPY1:%[0-9]+]]:gr32 = COPY %4 | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: bb.2.cleanup: | ||
| ; CHECK-NEXT: [[PHI:%[0-9]+]]:gr32 = PHI [[MOV32ri]], %bb.0, [[COPY1]], %bb.1 | ||
| ; CHECK-NEXT: $eax = COPY [[PHI]] | ||
| ; CHECK-NEXT: RET 0, $eax | ||
| entry: | ||
| %direct = callbr i32 asm "", "=r,!i"() | ||
| to label %cleanup [label %z.split] | ||
|
|
||
| z.split: | ||
| %indirect = call i32 @llvm.callbr.landingpad.i32(i32 %direct) | ||
| br label %cleanup | ||
|
|
||
| cleanup: | ||
| %retval.0 = phi i32 [ %indirect, %z.split ], [ 42, %entry ] | ||
| ret i32 %retval.0 | ||
| } | ||
|
|
||
| ; Two virtual registers | ||
| define i32 @test2() { | ||
| ; CHECK-LABEL: name: test2 | ||
| ; CHECK: bb.0.entry: | ||
| ; CHECK-NEXT: successors: %bb.2(0x80000000), %bb.1(0x00000000) | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: [[MOV32ri:%[0-9]+]]:gr32 = MOV32ri 42 | ||
| ; CHECK-NEXT: INLINEASM_BR &"", 0 /* attdialect */, 2359306 /* regdef:GR32 */, def %5, 2359306 /* regdef:GR32 */, def %6, 13 /* imm */, %bb.1 | ||
| ; CHECK-NEXT: [[COPY:%[0-9]+]]:gr32 = COPY %6 | ||
| ; CHECK-NEXT: [[COPY1:%[0-9]+]]:gr32 = COPY %5 | ||
| ; CHECK-NEXT: JMP_1 %bb.2 | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: bb.1.z.split (machine-block-address-taken, inlineasm-br-indirect-target): | ||
| ; CHECK-NEXT: successors: %bb.2(0x80000000) | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: [[COPY2:%[0-9]+]]:gr32 = COPY %5 | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: bb.2.cleanup: | ||
| ; CHECK-NEXT: [[PHI:%[0-9]+]]:gr32 = PHI [[MOV32ri]], %bb.0, [[COPY2]], %bb.1 | ||
| ; CHECK-NEXT: $eax = COPY [[PHI]] | ||
| ; CHECK-NEXT: RET 0, $eax | ||
| entry: | ||
| %direct = callbr { i32, i32 } asm "", "=r,=r,!i"() | ||
| to label %cleanup [label %z.split] | ||
|
|
||
| z.split: | ||
| %indirect = call { i32, i32 } @llvm.callbr.landingpad.sl_i32i32s({ i32, i32 } %direct) | ||
| %asmresult2 = extractvalue { i32, i32 } %indirect, 0 | ||
| br label %cleanup | ||
|
|
||
| cleanup: | ||
| %retval.0 = phi i32 [ %asmresult2, %z.split ], [ 42, %entry ] | ||
| ret i32 %retval.0 | ||
| } | ||
|
|
||
| ; One physical register | ||
| define i32 @test3() { | ||
| ; CHECK-LABEL: name: test3 | ||
| ; CHECK: bb.0.entry: | ||
| ; CHECK-NEXT: successors: %bb.2(0x80000000), %bb.1(0x00000000) | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: [[MOV32ri:%[0-9]+]]:gr32 = MOV32ri 42 | ||
| ; CHECK-NEXT: INLINEASM_BR &"", 0 /* attdialect */, 10 /* regdef */, implicit-def $ebx, 13 /* imm */, %bb.1 | ||
| ; CHECK-NEXT: [[COPY:%[0-9]+]]:gr32 = COPY $ebx | ||
| ; CHECK-NEXT: [[COPY1:%[0-9]+]]:gr32 = COPY [[COPY]] | ||
| ; CHECK-NEXT: JMP_1 %bb.2 | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: bb.1.z.split (machine-block-address-taken, inlineasm-br-indirect-target): | ||
| ; CHECK-NEXT: successors: %bb.2(0x80000000) | ||
| ; CHECK-NEXT: liveins: $ebx | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: [[COPY2:%[0-9]+]]:gr32 = COPY $ebx | ||
| ; CHECK-NEXT: [[COPY3:%[0-9]+]]:gr32 = COPY [[COPY2]] | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: bb.2.cleanup: | ||
| ; CHECK-NEXT: [[PHI:%[0-9]+]]:gr32 = PHI [[MOV32ri]], %bb.0, [[COPY3]], %bb.1 | ||
| ; CHECK-NEXT: $eax = COPY [[PHI]] | ||
| ; CHECK-NEXT: RET 0, $eax | ||
| entry: | ||
| %direct = callbr i32 asm "", "={bx},!i"() | ||
| to label %cleanup [label %z.split] | ||
|
|
||
| z.split: | ||
| %indirect = call i32 @llvm.callbr.landingpad.i32(i32 %direct) | ||
| br label %cleanup | ||
|
|
||
| cleanup: | ||
| %retval.0 = phi i32 [ %indirect, %z.split ], [ 42, %entry ] | ||
| ret i32 %retval.0 | ||
| } | ||
|
|
||
| ; Two physical registers | ||
| define i32 @test4() { | ||
| ; CHECK-LABEL: name: test4 | ||
| ; CHECK: bb.0.entry: | ||
| ; CHECK-NEXT: successors: %bb.2(0x80000000), %bb.1(0x00000000) | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: [[MOV32ri:%[0-9]+]]:gr32 = MOV32ri 42 | ||
| ; CHECK-NEXT: INLINEASM_BR &"", 0 /* attdialect */, 10 /* regdef */, implicit-def $ebx, 10 /* regdef */, implicit-def $edx, 13 /* imm */, %bb.1 | ||
| ; CHECK-NEXT: [[COPY:%[0-9]+]]:gr32 = COPY $ebx | ||
| ; CHECK-NEXT: [[COPY1:%[0-9]+]]:gr32 = COPY $edx | ||
| ; CHECK-NEXT: [[COPY2:%[0-9]+]]:gr32 = COPY [[COPY1]] | ||
| ; CHECK-NEXT: [[COPY3:%[0-9]+]]:gr32 = COPY [[COPY]] | ||
| ; CHECK-NEXT: JMP_1 %bb.2 | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: bb.1.z.split (machine-block-address-taken, inlineasm-br-indirect-target): | ||
| ; CHECK-NEXT: successors: %bb.2(0x80000000) | ||
| ; CHECK-NEXT: liveins: $ebx, $edx | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: [[COPY4:%[0-9]+]]:gr32 = COPY $ebx | ||
| ; CHECK-NEXT: [[COPY5:%[0-9]+]]:gr32 = COPY [[COPY4]] | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: bb.2.cleanup: | ||
| ; CHECK-NEXT: [[PHI:%[0-9]+]]:gr32 = PHI [[MOV32ri]], %bb.0, [[COPY5]], %bb.1 | ||
| ; CHECK-NEXT: $eax = COPY [[PHI]] | ||
| ; CHECK-NEXT: RET 0, $eax | ||
| entry: | ||
| %direct = callbr { i32, i32 } asm "", "={bx},={dx},!i"() | ||
| to label %cleanup [label %z.split] | ||
|
|
||
| z.split: | ||
| %indirect = call { i32, i32 } @llvm.callbr.landingpad.sl_i32i32s({ i32, i32 } %direct) | ||
| %asmresult2 = extractvalue { i32, i32 } %indirect, 0 | ||
| br label %cleanup | ||
|
|
||
| cleanup: | ||
| %retval.0 = phi i32 [ %asmresult2, %z.split ], [ 42, %entry ] | ||
| ret i32 %retval.0 | ||
| } | ||
|
|
||
| ; Test the same destination appearing in the direct/fallthrough branch as the | ||
| ; indirect branch. Physreg. | ||
| define i32 @test5() { | ||
| ; CHECK-LABEL: name: test5 | ||
| ; CHECK: bb.0.entry: | ||
| ; CHECK-NEXT: successors: %bb.1(0x80000000) | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: INLINEASM_BR &"# $0", 0 /* attdialect */, 10 /* regdef */, implicit-def $ebx, 13 /* imm */, %bb.1 | ||
| ; CHECK-NEXT: [[COPY:%[0-9]+]]:gr32 = COPY $ebx | ||
| ; CHECK-NEXT: [[COPY1:%[0-9]+]]:gr32 = COPY [[COPY]] | ||
| ; CHECK-NEXT: JMP_1 %bb.1 | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: bb.1.cleanup (machine-block-address-taken, inlineasm-br-indirect-target): | ||
| ; CHECK-NEXT: liveins: $ebx | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: [[COPY2:%[0-9]+]]:gr32 = COPY $ebx | ||
| ; CHECK-NEXT: $eax = COPY [[COPY2]] | ||
| ; CHECK-NEXT: RET 0, $eax | ||
| entry: | ||
| %direct = callbr i32 asm "# $0", "={bx},!i"() | ||
| to label %cleanup [label %cleanup] | ||
|
|
||
| cleanup: | ||
| %indirect = call i32 @llvm.callbr.landingpad.i32(i32 %direct) | ||
| ret i32 %indirect | ||
| } | ||
|
|
||
| ; "The Devil's cross" (i.e. two asm goto with conflicting physreg constraints | ||
| ; going to the same destination) as expressed by clang. | ||
| define i64 @test6() { | ||
| ; CHECK-LABEL: name: test6 | ||
| ; CHECK: bb.0.entry: | ||
| ; CHECK-NEXT: successors: %bb.1(0x80000000), %bb.3(0x00000000) | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: INLINEASM_BR &"", 0 /* attdialect */, 10 /* regdef */, implicit-def $rdx, 13 /* imm */, %bb.3 | ||
| ; CHECK-NEXT: [[COPY:%[0-9]+]]:gr64 = COPY $rdx | ||
| ; CHECK-NEXT: [[COPY1:%[0-9]+]]:gr64 = COPY [[COPY]] | ||
| ; CHECK-NEXT: JMP_1 %bb.1 | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: bb.1.asm.fallthrough: | ||
| ; CHECK-NEXT: successors: %bb.2(0x80000000), %bb.4(0x00000000) | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: INLINEASM_BR &"", 0 /* attdialect */, 10 /* regdef */, implicit-def $rbx, 13 /* imm */, %bb.4 | ||
| ; CHECK-NEXT: [[COPY2:%[0-9]+]]:gr64 = COPY $rbx | ||
| ; CHECK-NEXT: [[COPY3:%[0-9]+]]:gr64 = COPY [[COPY2]] | ||
| ; CHECK-NEXT: JMP_1 %bb.2 | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: bb.2.foo: | ||
| ; CHECK-NEXT: [[PHI:%[0-9]+]]:gr64 = PHI %3, %bb.3, [[COPY3]], %bb.1, %4, %bb.4 | ||
| ; CHECK-NEXT: $rax = COPY [[PHI]] | ||
| ; CHECK-NEXT: RET 0, $rax | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: bb.3.foo.split (machine-block-address-taken, inlineasm-br-indirect-target): | ||
| ; CHECK-NEXT: successors: %bb.2(0x80000000) | ||
| ; CHECK-NEXT: liveins: $rdx | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: [[COPY4:%[0-9]+]]:gr64 = COPY $rdx | ||
| ; CHECK-NEXT: %3:gr64 = COPY [[COPY4]] | ||
| ; CHECK-NEXT: JMP_1 %bb.2 | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: bb.4.foo.split2 (machine-block-address-taken, inlineasm-br-indirect-target): | ||
| ; CHECK-NEXT: successors: %bb.2(0x80000000) | ||
| ; CHECK-NEXT: liveins: $rbx | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: [[COPY6:%[0-9]+]]:gr64 = COPY $rbx | ||
| ; CHECK-NEXT: %4:gr64 = COPY [[COPY6]] | ||
| ; CHECK-NEXT: JMP_1 %bb.2 | ||
| entry: | ||
| %0 = callbr i64 asm "", "={dx},!i"() | ||
| to label %asm.fallthrough [label %foo.split] | ||
|
|
||
| asm.fallthrough: | ||
| %1 = callbr i64 asm "", "={bx},!i"() | ||
| to label %foo [label %foo.split2] | ||
|
|
||
| foo: | ||
| %x.0 = phi i64 [ %3, %foo.split2 ], [ %2, %foo.split ], [ %1, %asm.fallthrough ] | ||
| ret i64 %x.0 | ||
|
|
||
| foo.split: | ||
| %2 = call i64 @llvm.callbr.landingpad.i64(i64 %0) | ||
| br label %foo | ||
|
|
||
| foo.split2: | ||
| %3 = call i64 @llvm.callbr.landingpad.i64(i64 %1) | ||
| br label %foo | ||
| } | ||
|
|
||
|
|
||
| ; Test a callbr looping back on itself. | ||
| define i32 @test7() { | ||
| ; CHECK-LABEL: name: test7 | ||
| ; CHECK: bb.0.entry: | ||
| ; CHECK-NEXT: successors: %bb.1(0x80000000) | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: [[DEF:%[0-9]+]]:gr32 = IMPLICIT_DEF | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: bb.1.retry: | ||
| ; CHECK-NEXT: successors: %bb.2(0x80000000), %bb.3(0x00000000) | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: [[PHI:%[0-9]+]]:gr32 = PHI [[DEF]], %bb.0, %2, %bb.3 | ||
| ; CHECK-NEXT: [[COPY:%[0-9]+]]:gr32 = COPY [[PHI]] | ||
| ; CHECK-NEXT: INLINEASM_BR &"", 0 /* attdialect */, 10 /* regdef */, implicit-def $edx, 2147483657 /* reguse tiedto:$0 */, [[COPY]](tied-def 3), 13 /* imm */, %bb.3 | ||
| ; CHECK-NEXT: [[COPY1:%[0-9]+]]:gr32 = COPY $edx | ||
| ; CHECK-NEXT: [[COPY2:%[0-9]+]]:gr32 = COPY [[COPY1]] | ||
| ; CHECK-NEXT: JMP_1 %bb.2 | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: bb.2.asm.fallthrough: | ||
| ; CHECK-NEXT: $eax = COPY [[COPY2]] | ||
| ; CHECK-NEXT: RET 0, $eax | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: bb.3.retry.split (machine-block-address-taken, inlineasm-br-indirect-target): | ||
| ; CHECK-NEXT: successors: %bb.1(0x80000000) | ||
| ; CHECK-NEXT: liveins: $edx | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: [[COPY3:%[0-9]+]]:gr32 = COPY $edx | ||
| ; CHECK-NEXT: %2:gr32 = COPY [[COPY3]] | ||
| ; CHECK-NEXT: JMP_1 %bb.1 | ||
| entry: | ||
| br label %retry | ||
|
|
||
| retry: | ||
| %x.0 = phi i32 [ undef, %entry ], [ %1, %retry.split ] | ||
| %0 = callbr i32 asm "", "={dx},0,!i"(i32 %x.0) | ||
| to label %asm.fallthrough [label %retry.split] | ||
|
|
||
| asm.fallthrough: | ||
| ret i32 %0 | ||
|
|
||
| retry.split: | ||
| %1 = call i32 @llvm.callbr.landingpad.i32(i32 %0) | ||
| br label %retry | ||
| } | ||
|
|
||
| ; Test the same destination appearing in the direct/fallthrough branch as the | ||
| ; indirect branch. Same as test5 but with a virtreg rather than a physreg | ||
| ; constraint. | ||
| define i32 @test8() { | ||
| ; CHECK-LABEL: name: test8 | ||
| ; CHECK: bb.0.entry: | ||
| ; CHECK-NEXT: successors: %bb.1(0x80000000) | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: INLINEASM_BR &"# $0", 0 /* attdialect */, 2359306 /* regdef:GR32 */, def %1, 13 /* imm */, %bb.1 | ||
| ; CHECK-NEXT: %0:gr32 = COPY %1 | ||
| ; CHECK-NEXT: JMP_1 %bb.1 | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: bb.1.cleanup (machine-block-address-taken, inlineasm-br-indirect-target): | ||
| ; CHECK-NEXT: $eax = COPY %1 | ||
| ; CHECK-NEXT: RET 0, $eax | ||
| entry: | ||
| %direct = callbr i32 asm "# $0", "=r,!i"() | ||
| to label %cleanup [label %cleanup] | ||
|
|
||
| cleanup: | ||
| %indirect = call i32 @llvm.callbr.landingpad.i32(i32 %direct) | ||
| ret i32 %indirect | ||
| } | ||
|
|
||
| define i64 @condition_code() { | ||
| ; CHECK-LABEL: name: condition_code | ||
| ; CHECK: bb.0 (%ir-block.0): | ||
| ; CHECK-NEXT: successors: %bb.1(0x80000000), %bb.2(0x00000000) | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: INLINEASM_BR &"", 16 /* maystore attdialect */, 2359306 /* regdef:GR32 */, def %1, 13 /* imm */, %bb.2 | ||
| ; CHECK-NEXT: [[SETCCr:%[0-9]+]]:gr8 = SETCCr 4, implicit $eflags | ||
| ; CHECK-NEXT: [[MOVZX32rr8_:%[0-9]+]]:gr32 = MOVZX32rr8 killed [[SETCCr]] | ||
| ; CHECK-NEXT: [[SUBREG_TO_REG:%[0-9]+]]:gr64 = SUBREG_TO_REG 0, killed [[MOVZX32rr8_]], %subreg.sub_32bit | ||
| ; CHECK-NEXT: JMP_1 %bb.1 | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: bb.1.b: | ||
| ; CHECK-NEXT: $rax = COPY [[SUBREG_TO_REG]] | ||
| ; CHECK-NEXT: RET 0, $rax | ||
| ; CHECK-NEXT: {{ $}} | ||
| ; CHECK-NEXT: bb.2.c (machine-block-address-taken, inlineasm-br-indirect-target): | ||
| ; CHECK-NEXT: [[SETCCr1:%[0-9]+]]:gr8 = SETCCr 4, implicit $eflags | ||
| ; CHECK-NEXT: [[MOVZX32rr8_1:%[0-9]+]]:gr32 = MOVZX32rr8 killed [[SETCCr1]] | ||
| ; CHECK-NEXT: [[SUBREG_TO_REG1:%[0-9]+]]:gr64 = SUBREG_TO_REG 0, killed [[MOVZX32rr8_1]], %subreg.sub_32bit | ||
| ; CHECK-NEXT: $rax = COPY [[SUBREG_TO_REG1]] | ||
| ; CHECK-NEXT: RET 0, $rax | ||
| %a = callbr i64 asm "", "={@ccz},!i"() | ||
| to label %b [label %c] | ||
|
|
||
| b: | ||
| ret i64 %a | ||
|
|
||
| c: | ||
| %1 = call i64 @llvm.callbr.landingpad.i64(i64 %a) | ||
| ret i64 %1 | ||
| } | ||
|
|
||
| declare i64 @llvm.callbr.landingpad.i64(i64) | ||
| declare i32 @llvm.callbr.landingpad.i32(i32) | ||
| declare { i32, i32 } @llvm.callbr.landingpad.sl_i32i32s({ i32, i32 }) |