diff --git a/clang/test/CodeGen/bpf-context-access-marker-sinking-1.c b/clang/test/CodeGen/bpf-context-access-marker-sinking-1.c new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/bpf-context-access-marker-sinking-1.c @@ -0,0 +1,55 @@ +// REQUIRES: bpf-registered-target +// RUN: %clang_cc1 %s -O1 -triple bpf -emit-llvm \ +// RUN: -debug-info-kind=limited -o - | FileCheck %s + +// This test is related to the issue described in the following thread: +// +// https://lore.kernel.org/bpf/CAA-VZPmxh8o8EBcJ=m-DH4ytcxDFmo0JKsm1p1gf40kS0CE3NQ@mail.gmail.com/T/#m4b9ce2ce73b34f34172328f975235fc6f19841b6 +// +// Verifies that calls to bpf.llvm.passthrough are placed after loads +// from parameter marked with __attribute__((btf_decl_tag("ctx"))). +// +// See lib/Target/BPF/BPFContextAccessMarkerPass.cpp for additional details. + +#define __ctx__ __attribute__((btf_decl_tag("ctx"))) + +struct bpf_sock { + int bound_dev_if; + int family; +}; + +struct bpf_sockopt { + struct bpf_sock *sk; + int level; + int optlen; +}; + +__attribute__((noinline)) +static int f(int x) { + return x + 1; +} + +__attribute__((section("cgroup/getsockopt"))) +int _getsockopt(struct bpf_sockopt *ctx __ctx__) +{ + unsigned g = 0; + switch (ctx->level) { +// CHECK: %level = getelementptr inbounds %struct.bpf_sockopt, ptr %ctx +// CHECK: [[r0:%[0-9]+]] = load i32, ptr %level +// CHECK: [[r1:%[0-9]+]] = tail call i32 @llvm.bpf.passthrough.i32.i32({{.*}}, i32 [[r0]]) + case 10: + g = f(ctx->sk->family); + break; +// CHECK: [[r2:%[0-9]+]] = load ptr, ptr %ctx +// CHECK: %family = getelementptr inbounds %struct.bpf_sock, ptr [[r2]] +// CHECK: [[r3:%[0-9]+]] = load i32, ptr %family +// CHECK: [[r4:%[0-9]+]] = tail call i32 @llvm.bpf.passthrough.i32.i32({{.*}}, i32 [[r3]]) + case 20: + g = f(ctx->optlen); + break; +// CHECK: %optlen = getelementptr inbounds %struct.bpf_sockopt, ptr %ctx +// CHECK: [[r5:%[0-9]+]] = load i32, ptr %optlen +// CHECK: [[r6:%[0-9]+]] = tail call i32 @llvm.bpf.passthrough.i32.i32({{.*}}, i32 [[r5]]) + } + return g % 2; +} diff --git a/clang/test/CodeGen/bpf-context-access-marker-sinking-2.c b/clang/test/CodeGen/bpf-context-access-marker-sinking-2.c new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/bpf-context-access-marker-sinking-2.c @@ -0,0 +1,55 @@ +// REQUIRES: bpf-registered-target +// RUN: %clang_cc1 %s -O1 -triple bpf -emit-llvm \ +// RUN: -debug-info-kind=limited -o - | FileCheck %s + +// This test is related to the issue described in the following thread: +// +// https://lore.kernel.org/bpf/CAA-VZPmxh8o8EBcJ=m-DH4ytcxDFmo0JKsm1p1gf40kS0CE3NQ@mail.gmail.com/T/#m4b9ce2ce73b34f34172328f975235fc6f19841b6 +// +// Verifies that calls to bpf.llvm.passthrough are placed after loads +// from parameter marked with __attribute__((btf_decl_tag("ctx"))) and +// do not interfere with the __attribute__((preserve_access_index)). +// +// For additional details refer to: +// - lib/Target/BPF/BPFContextAccessMarkerPass.cpp +// - lib/Target/BPF/BPFAbstractMemberAccess.cpp + +#define __reloc__ __attribute__((preserve_access_index)) +#define __ctx__ __attribute__((btf_decl_tag("ctx"))) + +struct bpf_sock_ops { + int op; + int bpf_sock_ops_cb_flags; +} __reloc__; + +__attribute__((noinline)) +static int f(int x) { + return x + 1; +} + +int g; + +__attribute__((section("sockops"))) +int h(struct bpf_sock_ops *i __ctx__) { + switch (i->op) { +// CHECK: [[r0:%[0-9]+]] = load i64, ptr @"llvm.bpf_sock_ops:0:0$0:0" +// CHECK: [[r1:%[0-9]+]] = getelementptr i8, ptr %i, i64 [[r0]] +// CHECK: [[r2:%[0-9]+]] = load i32, ptr [[r1]] +// CHECK: [[r3:%[0-9]+]] = tail call i32 @llvm.bpf.passthrough.i32.i32({{.*}}, i32 [[r2]]) + case 10: + g = f(i->bpf_sock_ops_cb_flags); + break; +// CHECK [[r4:%[0-9]+]] = load i64, ptr @"llvm.bpf_sock_ops:0:4$0:1" +// CHECK [[r5:%[0-9]+]] = getelementptr i8, ptr %i, i64 [[r4]] +// CHECK [[r6:%[0-9]+]] = load i32, ptr [[r5]] +// CHECK [[r7:%[0-9]+]] = tail call i32 @llvm.bpf.passthrough.i32.i32({{.*}}, i32 [[r6]]) + case 20: + g = f(i->bpf_sock_ops_cb_flags); + break; +// CHECK [[r8:%[0-9]+]] = load i64, ptr @"llvm.bpf_sock_ops:0:4$0:1" +// CHECK [[r9:%[0-9]+]] = getelementptr i8, ptr %i, i64 [[r8]] +// CHECK [[r10:%[0-9]+]] = load i32, ptr [[r9]] +// CHECK [[r11:%[0-9]+]] = tail call i32 @llvm.bpf.passthrough.i32.i32({{.*}}, i32 [[r10]]) + } + return 0; +} diff --git a/llvm/lib/Target/BPF/BPF.h b/llvm/lib/Target/BPF/BPF.h --- a/llvm/lib/Target/BPF/BPF.h +++ b/llvm/lib/Target/BPF/BPF.h @@ -30,6 +30,7 @@ FunctionPass *createBPFMIPeepholeTruncElimPass(); FunctionPass *createBPFMIPreEmitPeepholePass(); FunctionPass *createBPFMIPreEmitCheckingPass(); +FunctionPass *createBPFContextAccessMarkerPass(); void initializeBPFAdjustOptPass(PassRegistry&); void initializeBPFCheckAndAdjustIRPass(PassRegistry&); @@ -42,6 +43,7 @@ void initializeBPFMIPeepholeTruncElimPass(PassRegistry&); void initializeBPFMIPreEmitPeepholePass(PassRegistry&); void initializeBPFMIPreEmitCheckingPass(PassRegistry&); +void initializeBPFContextAccessMarkerLegacyPassPass(PassRegistry &); class BPFAbstractMemberAccessPass : public PassInfoMixin { @@ -72,6 +74,13 @@ public: PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM); }; + +class BPFContextAccessMarkerPass + : public PassInfoMixin { +public: + PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM); +}; + } // namespace llvm #endif diff --git a/llvm/lib/Target/BPF/BPFAbstractMemberAccess.cpp b/llvm/lib/Target/BPF/BPFAbstractMemberAccess.cpp --- a/llvm/lib/Target/BPF/BPFAbstractMemberAccess.cpp +++ b/llvm/lib/Target/BPF/BPFAbstractMemberAccess.cpp @@ -100,16 +100,22 @@ Instruction *BPFCoreSharedInfo::insertPassThrough(Module *M, BasicBlock *BB, Instruction *Input, - Instruction *Before) { + BasicBlock::InstListType::iterator Where) { Function *Fn = Intrinsic::getDeclaration( M, Intrinsic::bpf_passthrough, {Input->getType(), Input->getType()}); Constant *SeqNumVal = ConstantInt::get(Type::getInt32Ty(BB->getContext()), BPFCoreSharedInfo::SeqNum++); auto *NewInst = CallInst::Create(Fn, {SeqNumVal, Input}); - BB->getInstList().insert(Before->getIterator(), NewInst); + BB->getInstList().insert(Where, NewInst); return NewInst; } + +Instruction *BPFCoreSharedInfo::insertPassThrough(Module *M, BasicBlock *BB, + Instruction *Input, + Instruction *Before) { + return insertPassThrough(M, BB, Input, Before->getIterator()); +} } // namespace llvm using namespace llvm; diff --git a/llvm/lib/Target/BPF/BPFCORE.h b/llvm/lib/Target/BPF/BPFCORE.h --- a/llvm/lib/Target/BPF/BPFCORE.h +++ b/llvm/lib/Target/BPF/BPFCORE.h @@ -10,6 +10,7 @@ #define LLVM_LIB_TARGET_BPF_BPFCORE_H #include "llvm/ADT/StringRef.h" +#include "llvm/IR/BasicBlock.h" namespace llvm { @@ -71,6 +72,10 @@ static Instruction *insertPassThrough(Module *M, BasicBlock *BB, Instruction *Input, Instruction *Before); + + static Instruction * + insertPassThrough(Module *M, BasicBlock *BB, Instruction *Input, + BasicBlock::InstListType::iterator Where); }; } // namespace llvm diff --git a/llvm/lib/Target/BPF/BPFContextAccessMarkerPass.cpp b/llvm/lib/Target/BPF/BPFContextAccessMarkerPass.cpp new file mode 100644 --- /dev/null +++ b/llvm/lib/Target/BPF/BPFContextAccessMarkerPass.cpp @@ -0,0 +1,356 @@ +//===------ BPFContextAccessMarkerPass.cpp --------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// BPF verifier limits access patterns allowed for BPF program +// parameter passed in a context register (r1). +// Only BASE + static-offset memory accesses are allowed. +// +// The goal of the BPFContextAccessMarkerPass is to ensure that +// SimplifyCFGPass optimization pass will not generate the code that +// uses unsupported access patterns for context parameter. +// +// The following code is used as a running example: +// +// #define __ctx__ __attribute__((btf_decl_tag("ctx"))) +// +// struct bpf_sock { +// int bound_dev_if; +// int family; +// }; +// +// struct bpf_sockopt { +// struct bpf_sock *sk; +// int level; +// int optlen; +// }; +// +// __attribute__((noinline)) +// static int f(int x) { ... } +// +// __attribute__((section("cgroup/getsockopt"))) +// int _getsockopt(struct bpf_sockopt *ctx __ctx__) +// { +// unsigned g = 0; +// switch (ctx->level) { +// case 10: +// g = f(ctx->sk->family); +// break; +// case 20: +// g = f(ctx->optlen); +// break; +// } +// return g % 2; +// } +// +// Here the attribute btf_decl_tag("ctx") marks a context parameter. +// The initial (simplified) IR for function _getsockopt looks as follows: +// +// define dso_local i32 @_getsockopt(ptr noundef %ctx) +// ... +// sw.bb: +// %1 = load ptr, ptr %ctx ;; +// %family = getelementptr inbounds %struct.bpf_sock, ptr %1 ;; access to ctx->sk->family +// %2 = load i32, ptr %family ;; (a) +// %call = call i32 @f(i32 noundef %2) +// br label %sw.epilog +// +// sw.bb1: +// %optlen = getelementptr inbounds %struct.bpf_sockopt, ptr %ctx ;; access to ctx->optlen +// %3 = load i32, ptr %optlen ;; (b) +// %call2 = call i32 @f(i32 noundef %3) +// br label %sw.epilog +// +// sw.epilog: +// ... +// +// W/o additional code motion machine code for field accesses would +// looks as follows: +// +// ... +// $r1 = LDW $r1, 4 ;; for ctx->sk->family +// ... +// $r1 = LDW $r1, 12 ;; for ctx->optlen +// +// Which matches the pattern allowed by BPF verifier. +// +// However, SimplifyCFGPass may rewrite the above IR separating +// getelementptr and load instructions as shown below: +// +// ... +// sw.bb: +// %1 = load ptr, ptr %ctx +// %family = getelementptr inbounds %struct.bpf_sock, ptr %1 +// br label %sw.epilog.sink.split +// +// sw.bb1: +// %optlen = getelementptr inbounds %struct.bpf_sockopt, ptr %ctx +// br label %sw.epilog.sink.split +// +// sw.epilog.sink.split: +// %optlen.sink = phi ptr [ %optlen, %sw.bb1 ], [ %family, %sw.bb ] +// %2 = load i32, ptr %optlen.sink ;; (c) +// %call2 = call fastcc i32 @f(i32 noundef %2) +// br label %sw.epilog +// +// sw.epilog: +// ... +// +// Note that load instructions (a) and (b) are replaced by a single +// load instruction (c) that gets it's value from a PHI node. The two +// calls to @f are also replaced by a single call that uses result of +// (c). This is done by a code sinking part of the +// SimplifyCFGPass. This leads to the following machine code: +// +// bb.2.sw.bb: +// $r1 = LDD $r1, 0 +// $r1 = ADD_ri $r1, 4 +// JMP %bb.4 +// +// bb.3.sw.bb1: +// $r1 = ADD_ri $r1, 12 +// +// bb.4.sw.epilog.sink.split: +// $r1 = LDW $r1, 0 +// JAL @f +// +// Here the offset is dynamically added to r1 (context register), this +// access pattern is not allowed by BPF verifier. +// +// To prevent the undesired code motion by SimplifyCFGPass the +// BPFContextAccessMarkerPass inserts a call to llvm.bpf.passthrough +// function after each load of a value from location that aliases the +// context parameter. llvm.bpf.passthrough accepts a unique integer +// constant as one of the parameters, thus preventing the common code +// sinking after certain position. +// +// E.g. the IR from above is transformed as follows: +// +// sw.bb: +// %2 = load ptr, ptr %ctx, align 8 +// %family = getelementptr inbounds %struct.bpf_sock, ptr %2 +// %3 = load i32, ptr %family, align 4 +// %4 = call i32 @llvm.bpf.passthrough.i32.i32(i32 2, i32 %3) ;; <-- added call +// %call = call i32 @f(i32 noundef %4) +// br label %sw.epilog +// +// sw.bb1: +// %optlen = getelementptr inbounds %struct.bpf_sockopt, ptr %ctx +// %5 = load i32, ptr %optlen +// %6 = call i32 @llvm.bpf.passthrough.i32.i32(i32 3, i32 %5) ;; <-- added call +// %call2 = call i32 @f(i32 noundef %6) +// br label %sw.epilog +// +// sw.epilog: +// ... + +#include "BPF.h" +#include "BPFCORE.h" +#include "llvm/ADT/None.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/SmallSet.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Analysis/AliasAnalysis.h" +#include "llvm/IR/DebugInfoMetadata.h" +#include "llvm/IR/InstIterator.h" +#include "llvm/IR/Instruction.h" +#include "llvm/IR/Instructions.h" +#include "llvm/IR/Metadata.h" +#include "llvm/IR/PassManager.h" +#include "llvm/InitializePasses.h" +#include "llvm/Pass.h" +#include + +#define DEBUG_TYPE "bpf-context-access-marker" + +using namespace llvm; + +static Metadata *findMetadata(Metadata *Root, + llvm::function_ref Fn) { + if (!Root) + return nullptr; + if (Fn(Root)) + return Root; + if (auto *Node = dyn_cast(Root)) + for (auto &Operand : Node->operands()) + if (auto *Result = findMetadata(Operand.get(), Fn)) + return Result; + return nullptr; +} + +static bool hasContextTag(Metadata *Root) { + return findMetadata(Root, [](Metadata *MD) { + auto *Node = dyn_cast(MD); + if (!Node || Node->operands().size() != 2) + return false; + auto *Name = dyn_cast(Node->getOperand(0)); + auto *Val = dyn_cast(Node->getOperand(1)); + return Name + && Val + && Name->getString().equals("btf_decl_tag") + && Val->getString().equals("ctx"); + }); +} + +static SmallVector collectContextParams(Function &F) { + SmallVector CtxParams; + + if (DISubprogram *SP = F.getSubprogram()) { + for (const DINode *DN : SP->getRetainedNodes()) { + if (const auto *DV = dyn_cast(DN)) { + if (DV->isParameter() && hasContextTag(DV->getRawAnnotations())) { + auto *Arg = F.getArg(DV->getArg() - 1); + if (Arg->getType()->isPointerTy()) + CtxParams.push_back(Arg); + } + } + } + } + + return CtxParams; +} + +static std::set collectContexParamAlaiases(Function &F, + AliasAnalysis &AA, + SmallVector &CtxParams) { + std::set CtxParamAliases; + for (auto *V : CtxParams) + CtxParamAliases.insert(V); + + for (Instruction &Inst : instructions(F)) { + for (auto *Arg : CtxParams) { + if (AA.alias(&Inst, Arg) == AliasResult::NoAlias) + continue; + CtxParamAliases.insert(&Inst); + } + } + + return CtxParamAliases; +} + +static bool insertPassThroughCalls(Function &F, std::set &CtxParamAliases) { + auto *Module = F.getParent(); + bool Changed = false; + + std::set InsertPassthroughAfter; + for (auto *V : CtxParamAliases) + for (auto &U : V->uses()) + if (auto *Load = dyn_cast(U.getUser())) + InsertPassthroughAfter.insert(Load); + + for (auto *Load : InsertPassthroughAfter) { + if (!Load->getNextNode()) + continue; + auto *BB = Load->getParent(); + + Instruction *LoadPassTrhoughInst = + BPFCoreSharedInfo::insertPassThrough(Module, BB, Load, ++Load->getIterator()); + + Load->replaceUsesWithIf(LoadPassTrhoughInst, [&](Use &OtherU) { + return LoadPassTrhoughInst != OtherU.getUser(); + }); + + CtxParamAliases.insert(LoadPassTrhoughInst); + Changed = true; + } + + return Changed; +} + +static bool isPassThroughCall(Instruction *I) { + auto *Call = dyn_cast(I); + if (!Call) + return false; + auto *Func = Call->getCalledFunction(); + if (!Func) + return false; + return Func->getName().startswith("llvm.bpf.passthrough"); +} + +static void removeRedundantPassThroughCalls(Function &F, + std::set &CtxParamAliases) { + for (auto &BB : F.getBasicBlockList()) { + Instruction *PrevPassThrough = nullptr; + for (auto &I : BB.getInstList()) { + if (CtxParamAliases.count(&I) && isPassThroughCall(&I)) { + if (PrevPassThrough) { + auto *Input = PrevPassThrough->getOperand(1); + PrevPassThrough->replaceAllUsesWith(Input); + PrevPassThrough->eraseFromParent(); + } + PrevPassThrough = &I; + } + } + } +} + +static bool insertContextAccessMarkers(Function &F, AliasAnalysis &AA) { + LLVM_DEBUG(dbgs() << "********** Context Access Markers ************\n"); + + auto *Module = F.getParent(); + if (!Module) + return false; + + // The btf_decl_tag attribute is saved in debug info + if (Module->debug_compile_units().empty()) + return false; + + auto CtxParams = collectContextParams(F); + + LLVM_DEBUG(dbgs() << "There are " << CtxParams.size() << " context parameters\n"); + + if (CtxParams.empty()) + return false; + + auto CtxParamAliases = collectContexParamAlaiases(F, AA, CtxParams); + + if (!insertPassThroughCalls(F, CtxParamAliases)) + return false; + + removeRedundantPassThroughCalls(F, CtxParamAliases); + + return true; +} + +namespace { + +class BPFContextAccessMarkerLegacyPass final : public FunctionPass { +public: + static char ID; + + BPFContextAccessMarkerLegacyPass() : FunctionPass(ID) {} + + bool runOnFunction(Function &F) override { + auto &AA = getAnalysis().getAAResults(); + return insertContextAccessMarkers(F, AA); + } + + void getAnalysisUsage(AnalysisUsage &AU) const override { + AU.addRequired(); + } +}; + +} // End anonymous namespace + +char BPFContextAccessMarkerLegacyPass::ID = 0; +INITIALIZE_PASS_BEGIN(BPFContextAccessMarkerLegacyPass, DEBUG_TYPE, + "BPF Context Access Marker", false, false) +INITIALIZE_PASS_DEPENDENCY(AAResultsWrapperPass) +INITIALIZE_PASS_END(BPFContextAccessMarkerLegacyPass, DEBUG_TYPE, + "BPF Context Access Marker", false, false) + + +FunctionPass *llvm::createBPFContextAccessMarkerPass() { + return new BPFContextAccessMarkerLegacyPass(); +} + +PreservedAnalyses llvm::BPFContextAccessMarkerPass::run(Function &F, + FunctionAnalysisManager &AM) { + return insertContextAccessMarkers(F, AM.getResult(F)) + ? PreservedAnalyses::none() + : PreservedAnalyses::all(); +} diff --git a/llvm/lib/Target/BPF/BPFTargetMachine.cpp b/llvm/lib/Target/BPF/BPFTargetMachine.cpp --- a/llvm/lib/Target/BPF/BPFTargetMachine.cpp +++ b/llvm/lib/Target/BPF/BPFTargetMachine.cpp @@ -48,6 +48,7 @@ initializeBPFCheckAndAdjustIRPass(PR); initializeBPFMIPeepholePass(PR); initializeBPFMIPeepholeTruncElimPass(PR); + initializeBPFContextAccessMarkerLegacyPassPass(PR); } // DataLayout: little or big endian @@ -108,6 +109,7 @@ [&](const PassManagerBuilder &, legacy::PassManagerBase &PM) { PM.add(createBPFAbstractMemberAccess(this)); PM.add(createBPFPreserveDIType()); + PM.add(createBPFContextAccessMarkerPass()); PM.add(createBPFIRPeephole()); }); @@ -130,6 +132,7 @@ FunctionPassManager FPM; FPM.addPass(BPFAbstractMemberAccessPass(this)); FPM.addPass(BPFPreserveDITypePass()); + FPM.addPass(BPFContextAccessMarkerPass()); FPM.addPass(BPFIRPeepholePass()); MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM))); }); diff --git a/llvm/lib/Target/BPF/CMakeLists.txt b/llvm/lib/Target/BPF/CMakeLists.txt --- a/llvm/lib/Target/BPF/CMakeLists.txt +++ b/llvm/lib/Target/BPF/CMakeLists.txt @@ -16,6 +16,7 @@ add_llvm_target(BPFCodeGen BPFAbstractMemberAccess.cpp + BPFContextAccessMarkerPass.cpp BPFAdjustOpt.cpp BPFAsmPrinter.cpp BPFCheckAndAdjustIR.cpp diff --git a/llvm/test/CodeGen/BPF/context-access-marker.ll b/llvm/test/CodeGen/BPF/context-access-marker.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/BPF/context-access-marker.ll @@ -0,0 +1,437 @@ +; RUN: opt -O2 -S %s | FileCheck %s +; +; This test verifies that llvm.bpf.passthrough calls are inserted by +; BPFContextAccessMarkerPass after access to the fields of function +; parameters marked with __attribute__((btf_decl_tag("ctx"))). +; +; Compile command: +; clang -g -target bpf -O2 -Xclang -disable-llvm-passes -S -emit-llvm t.c -o t.ll +; +; Source: +; +; #define __CTX__ __attribute__((btf_decl_tag("ctx"))) + +; struct inner { +; int a; +; int b; +; }; + +; struct outer { +; int first; +; struct inner inner_inline; +; struct inner *inner_ptr; +; int butlast; +; int last; +; }; + +; int marker_expected(struct outer *ctx __CTX__) { +; return ctx->last; +; } + +; int no_marker_expected_1(struct outer *non_ctx) { +; return non_ctx->last; +; } + +; extern struct outer *magic(); + +; int no_marker_expected_2(struct outer *ctx __CTX__) { +; struct outer *x = magic(); +; return x->last; +; } + +; int no_marker_expected_3(struct outer *ctx __CTX__, struct outer *non_ctx) { +; return non_ctx->last; +; } + +; int one_marker_expected_1(struct outer *ctx __CTX__) { +; return ctx->inner_inline.b; +; } + +; int one_marker_expected_2(struct outer *ctx __CTX__) { +; return ctx->inner_ptr->b; +; } + +; int one_marker_in_each_branch(struct outer *ctx __CTX__) { +; if (ctx->first) { +; return ctx->butlast; +; } else { +; return ctx->last; +; } +; } + +; extern int magic2(int x); + +; int do_not_sink(struct outer *ctx __CTX__) { +; if (ctx->first) +; return magic2(ctx->butlast); +; else +; return magic2(ctx->last); +; } + +; ModuleID = 't.c' +source_filename = "t.c" +target datalayout = "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128" +target triple = "bpf" + +%struct.outer = type { i32, %struct.inner, ptr, i32, i32 } +%struct.inner = type { i32, i32 } + +; Function Attrs: nounwind +define dso_local i32 @marker_expected(ptr noundef %ctx) #0 !dbg !7 { +entry: + %ctx.addr = alloca ptr, align 8 + store ptr %ctx, ptr %ctx.addr, align 8, !tbaa !28 + call void @llvm.dbg.declare(metadata ptr %ctx.addr, metadata !25, metadata !DIExpression()), !dbg !32 + %0 = load ptr, ptr %ctx.addr, align 8, !dbg !33, !tbaa !28 + %last = getelementptr inbounds %struct.outer, ptr %0, i32 0, i32 4, !dbg !34 + %1 = load i32, ptr %last, align 4, !dbg !34, !tbaa !35 + ret i32 %1, !dbg !39 +; CHECK: [[r0:%[0-9]+]] = load i32, ptr %last +; CHECK-NEXT: [[r1:%[0-9]+]] = tail call i32 @llvm.bpf.passthrough.i32.i32({{.*}}, i32 [[r0]]) +; CHECK-NEXT: ret i32 [[r1]] +} + +; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn +declare void @llvm.dbg.declare(metadata, metadata, metadata) #1 + +; Function Attrs: nounwind +define dso_local i32 @no_marker_expected_1(ptr noundef %non_ctx) #0 !dbg !40 { +entry: + %non_ctx.addr = alloca ptr, align 8 + store ptr %non_ctx, ptr %non_ctx.addr, align 8, !tbaa !28 + call void @llvm.dbg.declare(metadata ptr %non_ctx.addr, metadata !42, metadata !DIExpression()), !dbg !43 + %0 = load ptr, ptr %non_ctx.addr, align 8, !dbg !44, !tbaa !28 + %last = getelementptr inbounds %struct.outer, ptr %0, i32 0, i32 4, !dbg !45 + %1 = load i32, ptr %last, align 4, !dbg !45, !tbaa !35 + ret i32 %1, !dbg !46 +; CHECK: %last = getelementptr inbounds %struct.outer, ptr %non_ctx, i64 0, i32 4 +; CHECK-NEXT: [[r0]] = load i32, ptr %last +; CHECK-NEXT: ret i32 [[r0]] +} + +; Function Attrs: nounwind +define dso_local i32 @no_marker_expected_2(ptr noundef %ctx) #0 !dbg !47 { +entry: + %ctx.addr = alloca ptr, align 8 + %x = alloca ptr, align 8 + store ptr %ctx, ptr %ctx.addr, align 8, !tbaa !28 + call void @llvm.dbg.declare(metadata ptr %ctx.addr, metadata !49, metadata !DIExpression()), !dbg !51 + call void @llvm.lifetime.start.p0(i64 8, ptr %x) #4, !dbg !52 + call void @llvm.dbg.declare(metadata ptr %x, metadata !50, metadata !DIExpression()), !dbg !53 + %call = call ptr @magic(), !dbg !54 + store ptr %call, ptr %x, align 8, !dbg !53, !tbaa !28 + %0 = load ptr, ptr %x, align 8, !dbg !55, !tbaa !28 + %last = getelementptr inbounds %struct.outer, ptr %0, i32 0, i32 4, !dbg !56 + %1 = load i32, ptr %last, align 4, !dbg !56, !tbaa !35 + call void @llvm.lifetime.end.p0(i64 8, ptr %x) #4, !dbg !57 + ret i32 %1, !dbg !58 +; CHECK: %call = tail call ptr @magic() +; CHECK: %last = getelementptr inbounds %struct.outer, ptr %call, i64 0, i32 4 +; CHECK-NEXT: [[r0]] = load i32, ptr %last +; +; ----> excessive marker present due to current implementation limitations +; CHECK-NEXT: [[r1]] = tail call i32 @llvm.bpf.passthrough.i32.i32({{.*}}, i32 [[r0]]) +; ---- +; +; CHECK-NEXT: ret i32 [[r1]] +} + +; Function Attrs: argmemonly nocallback nofree nosync nounwind willreturn +declare void @llvm.lifetime.start.p0(i64 immarg, ptr nocapture) #2 + +declare dso_local ptr @magic(...) #3 + +; Function Attrs: argmemonly nocallback nofree nosync nounwind willreturn +declare void @llvm.lifetime.end.p0(i64 immarg, ptr nocapture) #2 + +; Function Attrs: nounwind +define dso_local i32 @no_marker_expected_3(ptr noundef %ctx, ptr noundef %non_ctx) #0 !dbg !59 { +entry: + %ctx.addr = alloca ptr, align 8 + %non_ctx.addr = alloca ptr, align 8 + store ptr %ctx, ptr %ctx.addr, align 8, !tbaa !28 + call void @llvm.dbg.declare(metadata ptr %ctx.addr, metadata !63, metadata !DIExpression()), !dbg !65 + store ptr %non_ctx, ptr %non_ctx.addr, align 8, !tbaa !28 + call void @llvm.dbg.declare(metadata ptr %non_ctx.addr, metadata !64, metadata !DIExpression()), !dbg !66 + %0 = load ptr, ptr %non_ctx.addr, align 8, !dbg !67, !tbaa !28 + %last = getelementptr inbounds %struct.outer, ptr %0, i32 0, i32 4, !dbg !68 + %1 = load i32, ptr %last, align 4, !dbg !68, !tbaa !35 + ret i32 %1, !dbg !69 +; CHECK: %last = getelementptr inbounds %struct.outer, ptr %non_ctx, i64 0, i32 4 +; CHECK-NEXT: [[r0]] = load i32, ptr %last +; +; ----> excessive marker present due to current implementation limitations +; CHECK-NEXT: [[r1]] = tail call i32 @llvm.bpf.passthrough.i32.i32({{.*}}, i32 [[r0]]) +; ---- +; +; CHECK-NEXT: ret i32 [[r1]] +} + +; Function Attrs: nounwind +define dso_local i32 @one_marker_expected_1(ptr noundef %ctx) #0 !dbg !70 { +entry: + %ctx.addr = alloca ptr, align 8 + store ptr %ctx, ptr %ctx.addr, align 8, !tbaa !28 + call void @llvm.dbg.declare(metadata ptr %ctx.addr, metadata !72, metadata !DIExpression()), !dbg !73 + %0 = load ptr, ptr %ctx.addr, align 8, !dbg !74, !tbaa !28 + %inner_inline = getelementptr inbounds %struct.outer, ptr %0, i32 0, i32 1, !dbg !75 + %b = getelementptr inbounds %struct.inner, ptr %inner_inline, i32 0, i32 1, !dbg !76 + %1 = load i32, ptr %b, align 4, !dbg !76, !tbaa !77 + ret i32 %1, !dbg !78 +; CHECK: %b = getelementptr inbounds %struct.outer, ptr %ctx, i64 0, i32 1, i32 1 +; CHECK-NEXT: [[r0]] = load i32, ptr %b +; CHECK-NEXT: [[r1]] = tail call i32 @llvm.bpf.passthrough.i32.i32({{.*}}, i32 [[r0]]) +; CHECK-NEXT: ret i32 [[r1]] +} + +; Function Attrs: nounwind +define dso_local i32 @one_marker_expected_2(ptr noundef %ctx) #0 !dbg !79 { +entry: + %ctx.addr = alloca ptr, align 8 + store ptr %ctx, ptr %ctx.addr, align 8, !tbaa !28 + call void @llvm.dbg.declare(metadata ptr %ctx.addr, metadata !81, metadata !DIExpression()), !dbg !82 + %0 = load ptr, ptr %ctx.addr, align 8, !dbg !83, !tbaa !28 + %inner_ptr = getelementptr inbounds %struct.outer, ptr %0, i32 0, i32 2, !dbg !84 + %1 = load ptr, ptr %inner_ptr, align 8, !dbg !84, !tbaa !85 + %b = getelementptr inbounds %struct.inner, ptr %1, i32 0, i32 1, !dbg !86 + %2 = load i32, ptr %b, align 4, !dbg !86, !tbaa !87 + ret i32 %2, !dbg !88 +; CHECK: %inner_ptr = getelementptr inbounds %struct.outer, ptr %ctx, i64 0, i32 2 +; CHECK-NEXT: [[r0]] = load ptr, ptr %inner_ptr +; CHECK-NEXT: %b = getelementptr inbounds %struct.inner, ptr [[r0]], i64 0, i32 1 +; CHECK-NEXT: [[r1]] = load i32, ptr %b +; CHECK-NEXT: [[r2:%[0-9]+]] = tail call i32 @llvm.bpf.passthrough.i32.i32({{.*}}, i32 [[r1]]) +; CHECK-NEXT: ret i32 [[r2]] +} + +; Function Attrs: nounwind +define dso_local i32 @one_marker_in_each_branch(ptr noundef %ctx) #0 !dbg !89 { +entry: + %retval = alloca i32, align 4 + %ctx.addr = alloca ptr, align 8 + store ptr %ctx, ptr %ctx.addr, align 8, !tbaa !28 + call void @llvm.dbg.declare(metadata ptr %ctx.addr, metadata !91, metadata !DIExpression()), !dbg !92 + %0 = load ptr, ptr %ctx.addr, align 8, !dbg !93, !tbaa !28 + %first = getelementptr inbounds %struct.outer, ptr %0, i32 0, i32 0, !dbg !95 + %1 = load i32, ptr %first, align 8, !dbg !95, !tbaa !96 + %tobool = icmp ne i32 %1, 0, !dbg !93 + br i1 %tobool, label %if.then, label %if.else, !dbg !97 +; CHECK: [[r0]] = load i32, ptr %ctx +; CHECK-NEXT: [[r1]] = tail call i32 @llvm.bpf.passthrough.i32.i32({{.*}}, i32 [[r0]]) +; CHECK-NEXT: %tobool.not = icmp eq i32 [[r1]], 0 +; CHECK-NEXT: br i1 %tobool.not, label %if.else, label %if.then + +if.then: ; preds = %entry + %2 = load ptr, ptr %ctx.addr, align 8, !dbg !98, !tbaa !28 + %butlast = getelementptr inbounds %struct.outer, ptr %2, i32 0, i32 3, !dbg !100 + %3 = load i32, ptr %butlast, align 8, !dbg !100, !tbaa !101 + store i32 %3, ptr %retval, align 4, !dbg !102 + br label %return, !dbg !102 +; CHECK: %butlast = getelementptr inbounds %struct.outer, ptr %ctx, i64 0, i32 3 +; CHECK-NEXT: [[r2]] = load i32, ptr %butlast +; CHECK-NEXT: [[r3:%[0-9]+]] = tail call i32 @llvm.bpf.passthrough.i32.i32({{.*}}, i32 [[r2]]) +; CHECK-NEXT: br label %return + +if.else: ; preds = %entry + %4 = load ptr, ptr %ctx.addr, align 8, !dbg !103, !tbaa !28 + %last = getelementptr inbounds %struct.outer, ptr %4, i32 0, i32 4, !dbg !105 + %5 = load i32, ptr %last, align 4, !dbg !105, !tbaa !35 + store i32 %5, ptr %retval, align 4, !dbg !106 + br label %return, !dbg !106 +; CHECK: %last = getelementptr inbounds %struct.outer, ptr %ctx, i64 0, i32 4 +; CHECK-NEXT: [[r4:%[0-9]+]] = load i32, ptr %last +; CHECK-NEXT: [[r5:%[0-9]+]] = tail call i32 @llvm.bpf.passthrough.i32.i32({{.*}}, i32 [[r4]]) +; CHECK-NEXT: br label %return + +return: ; preds = %if.else, %if.then + %6 = load i32, ptr %retval, align 4, !dbg !107 + ret i32 %6, !dbg !107 +} + +; Function Attrs: nounwind +define dso_local i32 @do_not_sink(ptr noundef %ctx) #0 !dbg !108 { +entry: + %retval = alloca i32, align 4 + %ctx.addr = alloca ptr, align 8 + store ptr %ctx, ptr %ctx.addr, align 8, !tbaa !28 + call void @llvm.dbg.declare(metadata ptr %ctx.addr, metadata !110, metadata !DIExpression()), !dbg !111 + %0 = load ptr, ptr %ctx.addr, align 8, !dbg !112, !tbaa !28 + %first = getelementptr inbounds %struct.outer, ptr %0, i32 0, i32 0, !dbg !114 + %1 = load i32, ptr %first, align 8, !dbg !114, !tbaa !96 + %tobool = icmp ne i32 %1, 0, !dbg !112 + br i1 %tobool, label %if.then, label %if.else, !dbg !115 +; CHECK: [[r0]] = load i32, ptr %ctx +; CHECK-NEXT: [[r1]] = tail call i32 @llvm.bpf.passthrough.i32.i32({{.*}}, i32 [[r0]]) +; CHECK-NEXT: %tobool.not = icmp eq i32 [[r1]], 0 +; CHECK-NEXT: br i1 %tobool.not, label %if.else, label %if.then + +if.then: ; preds = %entry + %2 = load ptr, ptr %ctx.addr, align 8, !dbg !116, !tbaa !28 + %butlast = getelementptr inbounds %struct.outer, ptr %2, i32 0, i32 3, !dbg !117 + %3 = load i32, ptr %butlast, align 8, !dbg !117, !tbaa !101 + %call = call i32 @magic2(i32 noundef %3), !dbg !118 + store i32 %call, ptr %retval, align 4, !dbg !119 + br label %return, !dbg !119 +; CHECK: %butlast = getelementptr inbounds %struct.outer, ptr %ctx, i64 0, i32 3 +; CHECK-NEXT: [[r2]] = load i32, ptr %butlast +; CHECK-NEXT: [[r3]] = tail call i32 @llvm.bpf.passthrough.i32.i32({{.*}}, i32 [[r2]]) +; CHECK-NEXT: br label %return + +if.else: ; preds = %entry + %4 = load ptr, ptr %ctx.addr, align 8, !dbg !120, !tbaa !28 + %last = getelementptr inbounds %struct.outer, ptr %4, i32 0, i32 4, !dbg !121 + %5 = load i32, ptr %last, align 4, !dbg !121, !tbaa !35 + %call1 = call i32 @magic2(i32 noundef %5), !dbg !122 + store i32 %call1, ptr %retval, align 4, !dbg !123 + br label %return, !dbg !123 +; CHECK: %last = getelementptr inbounds %struct.outer, ptr %ctx, i64 0, i32 4 +; CHECK-NEXT: [[r4]] = load i32, ptr %last +; CHECK-NEXT: [[r5]] = tail call i32 @llvm.bpf.passthrough.i32.i32({{.*}}, i32 [[r4]]) +; CHECK-NEXT: br label %return + +return: ; preds = %if.else, %if.then + %6 = load i32, ptr %retval, align 4, !dbg !124 + ret i32 %6, !dbg !124 +} + +declare !dbg !125 dso_local i32 @magic2(i32 noundef) #3 + +attributes #0 = { nounwind "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { nocallback nofree nosync nounwind readnone speculatable willreturn } +attributes #2 = { argmemonly nocallback nofree nosync nounwind willreturn } +attributes #3 = { "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #4 = { nounwind } + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!2, !3, !4, !5} +!llvm.ident = !{!6} + +!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang version 16.0.0 (https://github.com/llvm/llvm-project.git e07a8155f5168fdaff9346152d7805a47cb49405)", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None) +!1 = !DIFile(filename: "t.c", directory: "/home/eddy/work/tasks/reloc-offset-tmp-issue", checksumkind: CSK_MD5, checksum: "540f554779880a37768d1ec3ae986373") +!2 = !{i32 7, !"Dwarf Version", i32 5} +!3 = !{i32 2, !"Debug Info Version", i32 3} +!4 = !{i32 1, !"wchar_size", i32 4} +!5 = !{i32 7, !"frame-pointer", i32 2} +!6 = !{!"clang version 16.0.0 (https://github.com/llvm/llvm-project.git e07a8155f5168fdaff9346152d7805a47cb49405)"} +!7 = distinct !DISubprogram(name: "marker_expected", scope: !1, file: !1, line: 16, type: !8, scopeLine: 16, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !24) +!8 = !DISubroutineType(types: !9) +!9 = !{!10, !11} +!10 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!11 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !12, size: 64) +!12 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "outer", file: !1, line: 8, size: 256, elements: !13) +!13 = !{!14, !15, !20, !22, !23} +!14 = !DIDerivedType(tag: DW_TAG_member, name: "first", scope: !12, file: !1, line: 9, baseType: !10, size: 32) +!15 = !DIDerivedType(tag: DW_TAG_member, name: "inner_inline", scope: !12, file: !1, line: 10, baseType: !16, size: 64, offset: 32) +!16 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "inner", file: !1, line: 3, size: 64, elements: !17) +!17 = !{!18, !19} +!18 = !DIDerivedType(tag: DW_TAG_member, name: "a", scope: !16, file: !1, line: 4, baseType: !10, size: 32) +!19 = !DIDerivedType(tag: DW_TAG_member, name: "b", scope: !16, file: !1, line: 5, baseType: !10, size: 32, offset: 32) +!20 = !DIDerivedType(tag: DW_TAG_member, name: "inner_ptr", scope: !12, file: !1, line: 11, baseType: !21, size: 64, offset: 128) +!21 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !16, size: 64) +!22 = !DIDerivedType(tag: DW_TAG_member, name: "butlast", scope: !12, file: !1, line: 12, baseType: !10, size: 32, offset: 192) +!23 = !DIDerivedType(tag: DW_TAG_member, name: "last", scope: !12, file: !1, line: 13, baseType: !10, size: 32, offset: 224) +!24 = !{!25} +!25 = !DILocalVariable(name: "ctx", arg: 1, scope: !7, file: !1, line: 16, type: !11, annotations: !26) +!26 = !{!27} +!27 = !{!"btf_decl_tag", !"ctx"} +!28 = !{!29, !29, i64 0} +!29 = !{!"any pointer", !30, i64 0} +!30 = !{!"omnipotent char", !31, i64 0} +!31 = !{!"Simple C/C++ TBAA"} +!32 = !DILocation(line: 16, column: 35, scope: !7) +!33 = !DILocation(line: 17, column: 10, scope: !7) +!34 = !DILocation(line: 17, column: 15, scope: !7) +!35 = !{!36, !37, i64 28} +!36 = !{!"outer", !37, i64 0, !38, i64 4, !29, i64 16, !37, i64 24, !37, i64 28} +!37 = !{!"int", !30, i64 0} +!38 = !{!"inner", !37, i64 0, !37, i64 4} +!39 = !DILocation(line: 17, column: 3, scope: !7) +!40 = distinct !DISubprogram(name: "no_marker_expected_1", scope: !1, file: !1, line: 20, type: !8, scopeLine: 20, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !41) +!41 = !{!42} +!42 = !DILocalVariable(name: "non_ctx", arg: 1, scope: !40, file: !1, line: 20, type: !11) +!43 = !DILocation(line: 20, column: 40, scope: !40) +!44 = !DILocation(line: 21, column: 10, scope: !40) +!45 = !DILocation(line: 21, column: 19, scope: !40) +!46 = !DILocation(line: 21, column: 3, scope: !40) +!47 = distinct !DISubprogram(name: "no_marker_expected_2", scope: !1, file: !1, line: 26, type: !8, scopeLine: 26, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !48) +!48 = !{!49, !50} +!49 = !DILocalVariable(name: "ctx", arg: 1, scope: !47, file: !1, line: 26, type: !11, annotations: !26) +!50 = !DILocalVariable(name: "x", scope: !47, file: !1, line: 27, type: !11) +!51 = !DILocation(line: 26, column: 40, scope: !47) +!52 = !DILocation(line: 27, column: 3, scope: !47) +!53 = !DILocation(line: 27, column: 17, scope: !47) +!54 = !DILocation(line: 27, column: 21, scope: !47) +!55 = !DILocation(line: 28, column: 10, scope: !47) +!56 = !DILocation(line: 28, column: 13, scope: !47) +!57 = !DILocation(line: 29, column: 1, scope: !47) +!58 = !DILocation(line: 28, column: 3, scope: !47) +!59 = distinct !DISubprogram(name: "no_marker_expected_3", scope: !1, file: !1, line: 31, type: !60, scopeLine: 31, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !62) +!60 = !DISubroutineType(types: !61) +!61 = !{!10, !11, !11} +!62 = !{!63, !64} +!63 = !DILocalVariable(name: "ctx", arg: 1, scope: !59, file: !1, line: 31, type: !11, annotations: !26) +!64 = !DILocalVariable(name: "non_ctx", arg: 2, scope: !59, file: !1, line: 31, type: !11) +!65 = !DILocation(line: 31, column: 40, scope: !59) +!66 = !DILocation(line: 31, column: 67, scope: !59) +!67 = !DILocation(line: 32, column: 10, scope: !59) +!68 = !DILocation(line: 32, column: 19, scope: !59) +!69 = !DILocation(line: 32, column: 3, scope: !59) +!70 = distinct !DISubprogram(name: "one_marker_expected_1", scope: !1, file: !1, line: 35, type: !8, scopeLine: 35, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !71) +!71 = !{!72} +!72 = !DILocalVariable(name: "ctx", arg: 1, scope: !70, file: !1, line: 35, type: !11, annotations: !26) +!73 = !DILocation(line: 35, column: 41, scope: !70) +!74 = !DILocation(line: 36, column: 10, scope: !70) +!75 = !DILocation(line: 36, column: 15, scope: !70) +!76 = !DILocation(line: 36, column: 28, scope: !70) +!77 = !{!36, !37, i64 8} +!78 = !DILocation(line: 36, column: 3, scope: !70) +!79 = distinct !DISubprogram(name: "one_marker_expected_2", scope: !1, file: !1, line: 39, type: !8, scopeLine: 39, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !80) +!80 = !{!81} +!81 = !DILocalVariable(name: "ctx", arg: 1, scope: !79, file: !1, line: 39, type: !11, annotations: !26) +!82 = !DILocation(line: 39, column: 41, scope: !79) +!83 = !DILocation(line: 40, column: 10, scope: !79) +!84 = !DILocation(line: 40, column: 15, scope: !79) +!85 = !{!36, !29, i64 16} +!86 = !DILocation(line: 40, column: 26, scope: !79) +!87 = !{!38, !37, i64 4} +!88 = !DILocation(line: 40, column: 3, scope: !79) +!89 = distinct !DISubprogram(name: "one_marker_in_each_branch", scope: !1, file: !1, line: 43, type: !8, scopeLine: 43, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !90) +!90 = !{!91} +!91 = !DILocalVariable(name: "ctx", arg: 1, scope: !89, file: !1, line: 43, type: !11, annotations: !26) +!92 = !DILocation(line: 43, column: 45, scope: !89) +!93 = !DILocation(line: 44, column: 7, scope: !94) +!94 = distinct !DILexicalBlock(scope: !89, file: !1, line: 44, column: 7) +!95 = !DILocation(line: 44, column: 12, scope: !94) +!96 = !{!36, !37, i64 0} +!97 = !DILocation(line: 44, column: 7, scope: !89) +!98 = !DILocation(line: 45, column: 12, scope: !99) +!99 = distinct !DILexicalBlock(scope: !94, file: !1, line: 44, column: 19) +!100 = !DILocation(line: 45, column: 17, scope: !99) +!101 = !{!36, !37, i64 24} +!102 = !DILocation(line: 45, column: 5, scope: !99) +!103 = !DILocation(line: 47, column: 12, scope: !104) +!104 = distinct !DILexicalBlock(scope: !94, file: !1, line: 46, column: 10) +!105 = !DILocation(line: 47, column: 17, scope: !104) +!106 = !DILocation(line: 47, column: 5, scope: !104) +!107 = !DILocation(line: 49, column: 1, scope: !89) +!108 = distinct !DISubprogram(name: "do_not_sink", scope: !1, file: !1, line: 53, type: !8, scopeLine: 53, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !109) +!109 = !{!110} +!110 = !DILocalVariable(name: "ctx", arg: 1, scope: !108, file: !1, line: 53, type: !11, annotations: !26) +!111 = !DILocation(line: 53, column: 31, scope: !108) +!112 = !DILocation(line: 54, column: 7, scope: !113) +!113 = distinct !DILexicalBlock(scope: !108, file: !1, line: 54, column: 7) +!114 = !DILocation(line: 54, column: 12, scope: !113) +!115 = !DILocation(line: 54, column: 7, scope: !108) +!116 = !DILocation(line: 55, column: 19, scope: !113) +!117 = !DILocation(line: 55, column: 24, scope: !113) +!118 = !DILocation(line: 55, column: 12, scope: !113) +!119 = !DILocation(line: 55, column: 5, scope: !113) +!120 = !DILocation(line: 57, column: 19, scope: !113) +!121 = !DILocation(line: 57, column: 24, scope: !113) +!122 = !DILocation(line: 57, column: 12, scope: !113) +!123 = !DILocation(line: 57, column: 5, scope: !113) +!124 = !DILocation(line: 58, column: 1, scope: !108) +!125 = !DISubprogram(name: "magic2", scope: !1, file: !1, line: 51, type: !126, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !128) +!126 = !DISubroutineType(types: !127) +!127 = !{!10, !10} +!128 = !{}