diff --git a/llvm/include/llvm/Analysis/DXILResource.h b/llvm/include/llvm/Analysis/DXILResource.h index 14cf03c9a3ace..d9b4e968fe3e9 100644 --- a/llvm/include/llvm/Analysis/DXILResource.h +++ b/llvm/include/llvm/Analysis/DXILResource.h @@ -25,6 +25,7 @@ class Value; namespace dxil { class ResourceInfo { +public: struct ResourceBinding { uint32_t RecordID; uint32_t Space; @@ -38,6 +39,10 @@ class ResourceInfo { bool operator!=(const ResourceBinding &RHS) const { return !(*this == RHS); } + bool operator<(const ResourceBinding &RHS) const { + return std::tie(RecordID, Space, LowerBound, Size) < + std::tie(RHS.RecordID, RHS.Space, RHS.LowerBound, RHS.Size); + } }; struct UAVInfo { @@ -50,6 +55,10 @@ class ResourceInfo { std::tie(RHS.GloballyCoherent, RHS.HasCounter, RHS.IsROV); } bool operator!=(const UAVInfo &RHS) const { return !(*this == RHS); } + bool operator<(const UAVInfo &RHS) const { + return std::tie(GloballyCoherent, HasCounter, IsROV) < + std::tie(RHS.GloballyCoherent, RHS.HasCounter, RHS.IsROV); + } }; struct StructInfo { @@ -64,6 +73,9 @@ class ResourceInfo { return std::tie(Stride, AlignLog2) == std::tie(RHS.Stride, RHS.AlignLog2); } bool operator!=(const StructInfo &RHS) const { return !(*this == RHS); } + bool operator<(const StructInfo &RHS) const { + return std::tie(Stride, AlignLog2) < std::tie(RHS.Stride, RHS.AlignLog2); + } }; struct TypedInfo { @@ -75,6 +87,10 @@ class ResourceInfo { std::tie(RHS.ElementTy, RHS.ElementCount); } bool operator!=(const TypedInfo &RHS) const { return !(*this == RHS); } + bool operator<(const TypedInfo &RHS) const { + return std::tie(ElementTy, ElementCount) < + std::tie(RHS.ElementTy, RHS.ElementCount); + } }; struct MSInfo { @@ -82,6 +98,7 @@ class ResourceInfo { bool operator==(const MSInfo &RHS) const { return Count == RHS.Count; } bool operator!=(const MSInfo &RHS) const { return !(*this == RHS); } + bool operator<(const MSInfo &RHS) const { return Count < RHS.Count; } }; struct FeedbackInfo { @@ -89,8 +106,10 @@ class ResourceInfo { bool operator==(const FeedbackInfo &RHS) const { return Type == RHS.Type; } bool operator!=(const FeedbackInfo &RHS) const { return !(*this == RHS); } + bool operator<(const FeedbackInfo &RHS) const { return Type < RHS.Type; } }; +private: // Universal properties. Value *Symbol; StringRef Name; @@ -138,6 +157,7 @@ class ResourceInfo { Binding.LowerBound = LowerBound; Binding.Size = Size; } + const ResourceBinding &getBinding() const { return Binding; } void setUAV(bool GloballyCoherent, bool HasCounter, bool IsROV) { assert(isUAV() && "Not a UAV"); UAVFlags.GloballyCoherent = GloballyCoherent; @@ -168,7 +188,11 @@ class ResourceInfo { MultiSample.Count = Count; } + dxil::ResourceClass getResourceClass() const { return RC; } + bool operator==(const ResourceInfo &RHS) const; + bool operator!=(const ResourceInfo &RHS) const { return !(*this == RHS); } + bool operator<(const ResourceInfo &RHS) const; static ResourceInfo SRV(Value *Symbol, StringRef Name, dxil::ElementType ElementTy, uint32_t ElementCount, @@ -216,7 +240,6 @@ class ResourceInfo { MDTuple *getAsMetadata(LLVMContext &Ctx) const; - ResourceBinding getBinding() const { return Binding; } std::pair getAnnotateProps() const; void print(raw_ostream &OS) const; @@ -224,7 +247,41 @@ class ResourceInfo { } // namespace dxil -using DXILResourceMap = MapVector; +class DXILResourceMap { + SmallVector Resources; + DenseMap CallMap; + unsigned FirstUAV = 0; + unsigned FirstCBuffer = 0; + unsigned FirstSampler = 0; + +public: + using iterator = SmallVector::iterator; + using const_iterator = SmallVector::const_iterator; + + DXILResourceMap( + SmallVectorImpl> &&CIToRI); + + iterator begin() { return Resources.begin(); } + const_iterator begin() const { return Resources.begin(); } + iterator end() { return Resources.end(); } + const_iterator end() const { return Resources.end(); } + + bool empty() const { return Resources.empty(); } + + iterator find(const CallInst *Key) { + auto Pos = CallMap.find(Key); + return Pos == CallMap.end() ? Resources.end() + : (Resources.begin() + Pos->second); + } + + const_iterator find(const CallInst *Key) const { + auto Pos = CallMap.find(Key); + return Pos == CallMap.end() ? Resources.end() + : (Resources.begin() + Pos->second); + } + + void print(raw_ostream &OS) const; +}; class DXILResourceAnalysis : public AnalysisInfoMixin { friend AnalysisInfoMixin; diff --git a/llvm/lib/Analysis/DXILResource.cpp b/llvm/lib/Analysis/DXILResource.cpp index 1b5b051c9db29..2802480481690 100644 --- a/llvm/lib/Analysis/DXILResource.cpp +++ b/llvm/lib/Analysis/DXILResource.cpp @@ -335,27 +335,45 @@ bool ResourceInfo::operator==(const ResourceInfo &RHS) const { if (std::tie(Symbol, Name, Binding, RC, Kind) != std::tie(RHS.Symbol, RHS.Name, RHS.Binding, RHS.RC, RHS.Kind)) return false; - if (isCBuffer()) - return CBufferSize == RHS.CBufferSize; - if (isSampler()) - return SamplerTy == RHS.SamplerTy; - if (isUAV() && UAVFlags != RHS.UAVFlags) + if (isCBuffer() && RHS.isCBuffer() && CBufferSize != RHS.CBufferSize) return false; - - if (isStruct()) - return Struct == RHS.Struct; - if (isFeedback()) - return Feedback == RHS.Feedback; - if (isTyped() && Typed != RHS.Typed) + if (isSampler() && RHS.isSampler() && SamplerTy != RHS.SamplerTy) + return false; + if (isUAV() && RHS.isUAV() && UAVFlags != RHS.UAVFlags) + return false; + if (isStruct() && RHS.isStruct() && Struct != RHS.Struct) + return false; + if (isFeedback() && RHS.isFeedback() && Feedback != RHS.Feedback) + return false; + if (isTyped() && RHS.isTyped() && Typed != RHS.Typed) + return false; + if (isMultiSample() && RHS.isMultiSample() && MultiSample != RHS.MultiSample) return false; - - if (isMultiSample()) - return MultiSample == RHS.MultiSample; - - assert((Kind == ResourceKind::RawBuffer) && "Unhandled resource kind"); return true; } +bool ResourceInfo::operator<(const ResourceInfo &RHS) const { + // Skip the symbol to avoid non-determinism, and the name to keep a consistent + // ordering even when we strip reflection data. + if (std::tie(Binding, RC, Kind) < std::tie(RHS.Binding, RHS.RC, RHS.Kind)) + return true; + if (isCBuffer() && RHS.isCBuffer() && CBufferSize < RHS.CBufferSize) + return true; + if (isSampler() && RHS.isSampler() && SamplerTy < RHS.SamplerTy) + return true; + if (isUAV() && RHS.isUAV() && UAVFlags < RHS.UAVFlags) + return true; + if (isStruct() && RHS.isStruct() && Struct < RHS.Struct) + return true; + if (isFeedback() && RHS.isFeedback() && Feedback < RHS.Feedback) + return true; + if (isTyped() && RHS.isTyped() && Typed < RHS.Typed) + return true; + if (isMultiSample() && RHS.isMultiSample() && MultiSample < RHS.MultiSample) + return true; + return false; +} + MDTuple *ResourceInfo::getAsMetadata(LLVMContext &Ctx) const { SmallVector MDVals; @@ -534,18 +552,10 @@ namespace { class ResourceMapper { Module &M; LLVMContext &Context; - DXILResourceMap &Resources; - - // In DXC, Record ID is unique per resource type. Match that. - uint32_t NextUAV = 0; - uint32_t NextSRV = 0; - uint32_t NextCBuf = 0; - uint32_t NextSmp = 0; + SmallVector> Resources; public: - ResourceMapper(Module &M, - MapVector &Resources) - : M(M), Context(M.getContext()), Resources(Resources) {} + ResourceMapper(Module &M) : M(M), Context(M.getContext()) {} void diagnoseHandle(CallInst *CI, const Twine &Msg, DiagnosticSeverity Severity = DS_Error) { @@ -585,13 +595,11 @@ class ResourceMapper { // TODO: We don't actually keep track of the name right now... StringRef Name = ""; - auto [It, Success] = Resources.try_emplace(CI, RC, Kind, Symbol, Name); - assert(Success && "Mapping the same CallInst again?"); - (void)Success; - // We grab a pointer into the map's storage, which isn't generally safe. - // Since we're just using this to fill in the info the map won't mutate and - // the pointer stays valid for as long as we need it to. - ResourceInfo *RI = &(It->second); + // Note that we return a pointer into the vector's storage. This is okay as + // long as we don't add more elements until we're done with the pointer. + auto &Pair = + Resources.emplace_back(CI, ResourceInfo{RC, Kind, Symbol, Name}); + ResourceInfo *RI = &Pair.second; if (RI->isUAV()) // TODO: We need analysis for GloballyCoherent and HasCounter @@ -658,27 +666,18 @@ class ResourceMapper { if (!RI) return nullptr; - uint32_t NextID; - if (RI->isCBuffer()) - NextID = NextCBuf++; - else if (RI->isSampler()) - NextID = NextSmp++; - else if (RI->isUAV()) - NextID = NextUAV++; - else - NextID = NextSRV++; - uint32_t Space = cast(CI->getArgOperand(0))->getZExtValue(); uint32_t LowerBound = cast(CI->getArgOperand(1))->getZExtValue(); uint32_t Size = cast(CI->getArgOperand(2))->getZExtValue(); - RI->bind(NextID, Space, LowerBound, Size); + // We use a binding ID of zero for now - these will be filled in later. + RI->bind(0U, Space, LowerBound, Size); return RI; } - void mapResources() { + DXILResourceMap mapResources() { for (Function &F : M.functions()) { if (!F.isDeclaration()) continue; @@ -697,11 +696,68 @@ class ResourceMapper { break; } } + + return DXILResourceMap(std::move(Resources)); } }; } // namespace +DXILResourceMap::DXILResourceMap( + SmallVectorImpl> &&CIToRI) { + if (CIToRI.empty()) + return; + + llvm::stable_sort(CIToRI, [](auto &LHS, auto &RHS) { + // Sort by resource class first for grouping purposes, and then by the rest + // of the fields so that we can remove duplicates. + ResourceClass LRC = LHS.second.getResourceClass(); + ResourceClass RRC = RHS.second.getResourceClass(); + return std::tie(LRC, LHS.second) < std::tie(RRC, RHS.second); + }); + for (auto [CI, RI] : CIToRI) { + if (Resources.empty() || RI != Resources.back()) + Resources.push_back(RI); + CallMap[CI] = Resources.size() - 1; + } + + unsigned Size = Resources.size(); + // In DXC, Record ID is unique per resource type. Match that. + FirstUAV = FirstCBuffer = FirstSampler = Size; + uint32_t NextID = 0; + for (unsigned I = 0, E = Size; I != E; ++I) { + ResourceInfo &RI = Resources[I]; + if (RI.isUAV() && FirstUAV == Size) { + FirstUAV = I; + NextID = 0; + } else if (RI.isCBuffer() && FirstCBuffer == Size) { + FirstCBuffer = I; + NextID = 0; + } else if (RI.isSampler() && FirstSampler == Size) { + FirstSampler = I; + NextID = 0; + } + + // Adjust the resource binding to use the next ID. + const ResourceInfo::ResourceBinding &Binding = RI.getBinding(); + RI.bind(NextID++, Binding.Space, Binding.LowerBound, Binding.Size); + } +} + +void DXILResourceMap::print(raw_ostream &OS) const { + for (unsigned I = 0, E = Resources.size(); I != E; ++I) { + OS << "Binding " << I << ":\n"; + Resources[I].print(OS); + OS << "\n"; + } + + for (const auto &[CI, Index] : CallMap) { + OS << "Call bound to " << Index << ":"; + CI->print(OS); + OS << "\n"; + } +} + //===----------------------------------------------------------------------===// // DXILResourceAnalysis and DXILResourcePrinterPass @@ -710,24 +766,14 @@ AnalysisKey DXILResourceAnalysis::Key; DXILResourceMap DXILResourceAnalysis::run(Module &M, ModuleAnalysisManager &AM) { - DXILResourceMap Data; - ResourceMapper(M, Data).mapResources(); + DXILResourceMap Data = ResourceMapper(M).mapResources(); return Data; } PreservedAnalyses DXILResourcePrinterPass::run(Module &M, ModuleAnalysisManager &AM) { - DXILResourceMap &Data = - AM.getResult(M); - - for (const auto &[Handle, Info] : Data) { - OS << "Binding for "; - Handle->print(OS); - OS << "\n"; - Info.print(OS); - OS << "\n"; - } - + DXILResourceMap &DRM = AM.getResult(M); + DRM.print(OS); return PreservedAnalyses::all(); } @@ -745,8 +791,7 @@ void DXILResourceWrapperPass::getAnalysisUsage(AnalysisUsage &AU) const { } bool DXILResourceWrapperPass::runOnModule(Module &M) { - ResourceMap.reset(new DXILResourceMap()); - ResourceMapper(M, *ResourceMap).mapResources(); + ResourceMap.reset(new DXILResourceMap(ResourceMapper(M).mapResources())); return false; } @@ -757,13 +802,7 @@ void DXILResourceWrapperPass::print(raw_ostream &OS, const Module *) const { OS << "No resource map has been built!\n"; return; } - for (const auto &[Handle, Info] : *ResourceMap) { - OS << "Binding for "; - Handle->print(OS); - OS << "\n"; - Info.print(OS); - OS << "\n"; - } + ResourceMap->print(OS); } #if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP) diff --git a/llvm/test/Analysis/DXILResource/buffer-frombinding.ll b/llvm/test/Analysis/DXILResource/buffer-frombinding.ll index 65802c6d1ff87..b26a185423597 100644 --- a/llvm/test/Analysis/DXILResource/buffer-frombinding.ll +++ b/llvm/test/Analysis/DXILResource/buffer-frombinding.ll @@ -3,55 +3,48 @@ @G = external constant <4 x float>, align 4 define void @test_typedbuffer() { - ; RWBuffer Buf : register(u5, space3) - %typed0 = call target("dx.TypedBuffer", <4 x float>, 1, 0, 0) - @llvm.dx.handle.fromBinding.tdx.TypedBuffer_f32_1_0( - i32 3, i32 5, i32 1, i32 0, i1 false) - ; CHECK: Binding for %typed0 + ; ByteAddressBuffer Buf : register(t8, space1) + %srv0 = call target("dx.RawBuffer", i8, 0, 0) + @llvm.dx.handle.fromBinding.tdx.RawBuffer_i8_0_0t( + i32 1, i32 8, i32 1, i32 0, i1 false) + ; CHECK: Binding [[SRV0:[0-9]+]]: ; CHECK: Symbol: ptr undef ; CHECK: Name: "" ; CHECK: Binding: ; CHECK: Record ID: 0 - ; CHECK: Space: 3 - ; CHECK: Lower Bound: 5 + ; CHECK: Space: 1 + ; CHECK: Lower Bound: 8 ; CHECK: Size: 1 - ; CHECK: Class: UAV - ; CHECK: Kind: TypedBuffer - ; CHECK: Globally Coherent: 0 - ; CHECK: HasCounter: 0 - ; CHECK: IsROV: 0 - ; CHECK: Element Type: f32 - ; CHECK: Element Count: 4 + ; CHECK: Class: SRV + ; CHECK: Kind: RawBuffer - ; RWBuffer Buf : register(u7, space2) - %typed1 = call target("dx.TypedBuffer", i32, 1, 0, 1) - @llvm.dx.handle.fromBinding.tdx.TypedBuffer_i32_1_0t( - i32 2, i32 7, i32 1, i32 0, i1 false) - ; CHECK: Binding for %typed1 + ; struct S { float4 a; uint4 b; }; + ; StructuredBuffer Buf : register(t2, space4) + %srv1 = call target("dx.RawBuffer", {<4 x float>, <4 x i32>}, 0, 0) + @llvm.dx.handle.fromBinding.tdx.RawBuffer_sl_v4f32v4i32s_0_0t( + i32 4, i32 2, i32 1, i32 0, i1 false) + ; CHECK: Binding [[SRV1:[0-9]+]]: ; CHECK: Symbol: ptr undef ; CHECK: Name: "" ; CHECK: Binding: ; CHECK: Record ID: 1 - ; CHECK: Space: 2 - ; CHECK: Lower Bound: 7 + ; CHECK: Space: 4 + ; CHECK: Lower Bound: 2 ; CHECK: Size: 1 - ; CHECK: Class: UAV - ; CHECK: Kind: TypedBuffer - ; CHECK: Globally Coherent: 0 - ; CHECK: HasCounter: 0 - ; CHECK: IsROV: 0 - ; CHECK: Element Type: i32 - ; CHECK: Element Count: 1 + ; CHECK: Class: SRV + ; CHECK: Kind: StructuredBuffer + ; CHECK: Buffer Stride: 32 + ; CHECK: Alignment: 4 ; Buffer Buf[24] : register(t3, space5) - %typed2 = call target("dx.TypedBuffer", <4 x i32>, 0, 0, 0) + %srv2 = call target("dx.TypedBuffer", <4 x i32>, 0, 0, 0) @llvm.dx.handle.fromBinding.tdx.TypedBuffer_i32_0_0t( i32 5, i32 3, i32 24, i32 0, i1 false) - ; CHECK: Binding for %typed2 + ; CHECK: Binding [[SRV2:[0-9]+]]: ; CHECK: Symbol: ptr undef ; CHECK: Name: "" ; CHECK: Binding: - ; CHECK: Record ID: 0 + ; CHECK: Record ID: 2 ; CHECK: Space: 5 ; CHECK: Lower Bound: 3 ; CHECK: Size: 24 @@ -60,67 +53,82 @@ define void @test_typedbuffer() { ; CHECK: Element Type: u32 ; CHECK: Element Count: 4 - ret void -} + ; RWBuffer Buf : register(u7, space2) + %uav0 = call target("dx.TypedBuffer", i32, 1, 0, 1) + @llvm.dx.handle.fromBinding.tdx.TypedBuffer_i32_1_0t( + i32 2, i32 7, i32 1, i32 0, i1 false) + ; CHECK: Binding [[UAV0:[0-9]+]]: + ; CHECK: Symbol: ptr undef + ; CHECK: Name: "" + ; CHECK: Binding: + ; CHECK: Record ID: 0 + ; CHECK: Space: 2 + ; CHECK: Lower Bound: 7 + ; CHECK: Size: 1 + ; CHECK: Class: UAV + ; CHECK: Kind: TypedBuffer + ; CHECK: Globally Coherent: 0 + ; CHECK: HasCounter: 0 + ; CHECK: IsROV: 0 + ; CHECK: Element Type: i32 + ; CHECK: Element Count: 1 -define void @test_structbuffer() { - ; struct S { float4 a; uint4 b; }; - ; StructuredBuffer Buf : register(t2, space4) - %struct0 = call target("dx.RawBuffer", {<4 x float>, <4 x i32>}, 0, 0) - @llvm.dx.handle.fromBinding.tdx.RawBuffer_sl_v4f32v4i32s_0_0t( - i32 4, i32 2, i32 1, i32 0, i1 false) - ; CHECK: Binding for %struct0 + ; RWBuffer Buf : register(u5, space3) + %uav1 = call target("dx.TypedBuffer", <4 x float>, 1, 0, 0) + @llvm.dx.handle.fromBinding.tdx.TypedBuffer_f32_1_0( + i32 3, i32 5, i32 1, i32 0, i1 false) + ; CHECK: Binding [[UAV1:[0-9]+]]: ; CHECK: Symbol: ptr undef ; CHECK: Name: "" ; CHECK: Binding: ; CHECK: Record ID: 1 - ; CHECK: Space: 4 - ; CHECK: Lower Bound: 2 + ; CHECK: Space: 3 + ; CHECK: Lower Bound: 5 ; CHECK: Size: 1 - ; CHECK: Class: SRV - ; CHECK: Kind: StructuredBuffer - ; CHECK: Buffer Stride: 32 - ; CHECK: Alignment: 4 - - ret void -} + ; CHECK: Class: UAV + ; CHECK: Kind: TypedBuffer + ; CHECK: Globally Coherent: 0 + ; CHECK: HasCounter: 0 + ; CHECK: IsROV: 0 + ; CHECK: Element Type: f32 + ; CHECK: Element Count: 4 -define void @test_bytebuffer() { - ; ByteAddressBuffer Buf : register(t8, space1) - %byteaddr0 = call target("dx.RawBuffer", i8, 0, 0) - @llvm.dx.handle.fromBinding.tdx.RawBuffer_i8_0_0t( - i32 1, i32 8, i32 1, i32 0, i1 false) - ; CHECK: Binding for %byteaddr0 + ; RWBuffer BufferArray[10] : register(u0, space4) + ; RWBuffer Buf = BufferArray[0] + %uav2_1 = call target("dx.TypedBuffer", <4 x float>, 1, 0, 0) + @llvm.dx.handle.fromBinding.tdx.TypedBuffer_f32_1_0( + i32 4, i32 0, i32 10, i32 0, i1 false) + ; RWBuffer Buf = BufferArray[5] + %uav2_2 = call target("dx.TypedBuffer", <4 x float>, 1, 0, 0) + @llvm.dx.handle.fromBinding.tdx.TypedBuffer_f32_1_0( + i32 4, i32 0, i32 10, i32 5, i1 false) + ; CHECK: Binding [[UAV2:[0-9]+]]: ; CHECK: Symbol: ptr undef ; CHECK: Name: "" ; CHECK: Binding: ; CHECK: Record ID: 2 - ; CHECK: Space: 1 - ; CHECK: Lower Bound: 8 - ; CHECK: Size: 1 - ; CHECK: Class: SRV - ; CHECK: Kind: RawBuffer + ; CHECK: Space: 4 + ; CHECK: Lower Bound: 0 + ; CHECK: Size: 10 + ; CHECK: Class: UAV + ; CHECK: Kind: TypedBuffer + ; CHECK: Globally Coherent: 0 + ; CHECK: HasCounter: 0 + ; CHECK: IsROV: 0 + ; CHECK: Element Type: f32 + ; CHECK: Element Count: 4 + + ; CHECK-NOT: Binding {{[0-9]+}}: ret void } -; Note: We need declarations for each handle.fromBinding in the same -; order as they appear in source to ensure that we can put our CHECK -; lines along side the thing they're checking. -declare target("dx.TypedBuffer", <4 x float>, 1, 0, 0) - @llvm.dx.handle.fromBinding.tdx.TypedBuffer_v4f32_1_0_0t( - i32, i32, i32, i32, i1) #0 -declare target("dx.TypedBuffer", i32, 1, 0, 1) - @llvm.dx.handle.fromBinding.tdx.TypedBuffer_i32_1_0_1t( - i32, i32, i32, i32, i1) #0 -declare target("dx.TypedBuffer", <4 x i32>, 0, 0, 0) - @llvm.dx.handle.fromBinding.tdx.TypedBuffer_v4i32_0_0_0t( - i32, i32, i32, i32, i1) #0 -declare target("dx.RawBuffer", { <4 x float>, <4 x i32> }, 0, 0) - @llvm.dx.handle.fromBinding.tdx.RawBuffer_sl_v4f32v4i32s_0_0t( - i32, i32, i32, i32, i1) #0 -declare target("dx.RawBuffer", i8, 0, 0) - @llvm.dx.handle.fromBinding.tdx.RawBuffer_i8_0_0t( - i32, i32, i32, i32, i1) #0 +; CHECK-DAG: Call bound to [[SRV0]]: %srv0 = +; CHECK-DAG: Call bound to [[SRV1]]: %srv1 = +; CHECK-DAG: Call bound to [[SRV2]]: %srv2 = +; CHECK-DAG: Call bound to [[UAV0]]: %uav0 = +; CHECK-DAG: Call bound to [[UAV1]]: %uav1 = +; CHECK-DAG: Call bound to [[UAV2]]: %uav2_1 = +; CHECK-DAG: Call bound to [[UAV2]]: %uav2_2 = attributes #0 = { nocallback nofree nosync nounwind willreturn memory(none) }