diff --git a/libcxx/include/memory b/libcxx/include/memory --- a/libcxx/include/memory +++ b/libcxx/include/memory @@ -2409,6 +2409,7 @@ unique_ptr(pointer __p, _BadRValRefType<_Dummy> __d) = delete; _LIBCPP_INLINE_VISIBILITY + __attribute__((abi_tag("__SEMANTICS_unique_ptr_move_const"))) unique_ptr(unique_ptr&& __u) _NOEXCEPT : __ptr_(__u.release(), _VSTD::forward(__u.get_deleter())) { } @@ -2418,6 +2419,7 @@ class = _EnableIfDeleterConvertible<_Ep> > _LIBCPP_INLINE_VISIBILITY + __attribute__((abi_tag("__SEMANTICS_unique_ptr_move_const"))) unique_ptr(unique_ptr<_Up, _Ep>&& __u) _NOEXCEPT : __ptr_(__u.release(), _VSTD::forward<_Ep>(__u.get_deleter())) {} @@ -2468,6 +2470,7 @@ _LIBCPP_INLINE_VISIBILITY + __attribute__((abi_tag("__SEMANTICS_unique_ptr_destroy"))) ~unique_ptr() { reset(); } _LIBCPP_INLINE_VISIBILITY @@ -2652,6 +2655,7 @@ unique_ptr(_Pp __p, _BadRValRefType<_Dummy> __d) = delete; _LIBCPP_INLINE_VISIBILITY + __attribute__((abi_tag("__SEMANTICS_unique_ptr_move_const"))) unique_ptr(unique_ptr&& __u) _NOEXCEPT : __ptr_(__u.release(), _VSTD::forward(__u.get_deleter())) { } @@ -2668,6 +2672,7 @@ class = _EnableIfDeleterConvertible<_Ep> > _LIBCPP_INLINE_VISIBILITY + __attribute__((abi_tag("__SEMANTICS_unique_ptr_move_const"))) unique_ptr(unique_ptr<_Up, _Ep>&& __u) _NOEXCEPT : __ptr_(__u.release(), _VSTD::forward<_Ep>(__u.get_deleter())) { } @@ -2691,6 +2696,7 @@ public: _LIBCPP_INLINE_VISIBILITY + __attribute__((abi_tag("__SEMANTICS_unique_ptr_destroy"))) ~unique_ptr() { reset(); } _LIBCPP_INLINE_VISIBILITY @@ -2757,6 +2763,24 @@ }; +template +inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR +__attribute__((abi_tag("__SEMANTICS_unique_ptr_global_move"))) +unique_ptr<_Tp>&& +move(unique_ptr<_Tp>& __t) _NOEXCEPT +{ + return static_cast&&>(__t); +} + +template +inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR +__attribute__((abi_tag("__SEMANTICS_unique_ptr_global_move"))) +unique_ptr<_Tp>&& +move(unique_ptr<_Tp>&& __t) _NOEXCEPT +{ + return __t; +} + template inline _LIBCPP_INLINE_VISIBILITY typename enable_if< diff --git a/llvm/include/llvm/InitializePasses.h b/llvm/include/llvm/InitializePasses.h --- a/llvm/include/llvm/InitializePasses.h +++ b/llvm/include/llvm/InitializePasses.h @@ -437,6 +437,7 @@ void initializeWriteBitcodePassPass(PassRegistry&); void initializeWriteThinLTOBitcodePass(PassRegistry&); void initializeXRayInstrumentationPass(PassRegistry&); +void initializeSmartPtrLifetimePass(PassRegistry &); } // end namespace llvm diff --git a/llvm/include/llvm/LinkAllPasses.h b/llvm/include/llvm/LinkAllPasses.h --- a/llvm/include/llvm/LinkAllPasses.h +++ b/llvm/include/llvm/LinkAllPasses.h @@ -232,6 +232,7 @@ (void) llvm::createInjectTLIMappingsLegacyPass(); (void) llvm::createUnifyLoopExitsPass(); (void) llvm::createFixIrreduciblePass(); + (void)llvm::createSmartPtrLifetimePass(); (void)new llvm::IntervalPartition(); (void)new llvm::ScalarEvolutionWrapperPass(); diff --git a/llvm/include/llvm/Transforms/Scalar.h b/llvm/include/llvm/Transforms/Scalar.h --- a/llvm/include/llvm/Transforms/Scalar.h +++ b/llvm/include/llvm/Transforms/Scalar.h @@ -523,6 +523,14 @@ // transformations. // Pass *createWarnMissedTransformationsPass(); + +//===----------------------------------------------------------------------===// +// +// SmartPtrLifetime - This pass performs basic memory lifetime optimizations on +// C +// ++ smart pointers. +// +FunctionPass *createSmartPtrLifetimePass(); } // End llvm namespace #endif diff --git a/llvm/lib/Transforms/IPO/PassManagerBuilder.cpp b/llvm/lib/Transforms/IPO/PassManagerBuilder.cpp --- a/llvm/lib/Transforms/IPO/PassManagerBuilder.cpp +++ b/llvm/lib/Transforms/IPO/PassManagerBuilder.cpp @@ -602,6 +602,8 @@ // for the entire SCC pass run below. MPM.add(createGlobalsAAWrapperPass()); + MPM.add(createSmartPtrLifetimePass()); + // Start of CallGraph SCC passes. MPM.add(createPruneEHPass()); // Remove dead EH info bool RunInliner = false; diff --git a/llvm/lib/Transforms/Scalar/CMakeLists.txt b/llvm/lib/Transforms/Scalar/CMakeLists.txt --- a/llvm/lib/Transforms/Scalar/CMakeLists.txt +++ b/llvm/lib/Transforms/Scalar/CMakeLists.txt @@ -74,6 +74,7 @@ StructurizeCFG.cpp TailRecursionElimination.cpp WarnMissedTransforms.cpp + SmartPtrLifetime.cpp ADDITIONAL_HEADER_DIRS ${LLVM_MAIN_INCLUDE_DIR}/llvm/Transforms diff --git a/llvm/lib/Transforms/Scalar/Scalar.cpp b/llvm/lib/Transforms/Scalar/Scalar.cpp --- a/llvm/lib/Transforms/Scalar/Scalar.cpp +++ b/llvm/lib/Transforms/Scalar/Scalar.cpp @@ -112,6 +112,7 @@ initializeLoopVersioningPassPass(Registry); initializeEntryExitInstrumenterPass(Registry); initializePostInlineEntryExitInstrumenterPass(Registry); + initializeSmartPtrLifetimePass(Registry); } void LLVMAddLoopSimplifyCFGPass(LLVMPassManagerRef PM) { @@ -298,3 +299,7 @@ void LLVMAddUnifyFunctionExitNodesPass(LLVMPassManagerRef PM) { unwrap(PM)->add(createUnifyFunctionExitNodesPass()); } + +void LLVMAddSmartPtrLifetimePass(LLVMPassManagerRef PM) { + unwrap(PM)->add(createSmartPtrLifetimePass()); +} diff --git a/llvm/lib/Transforms/Scalar/SmartPtrLifetime.cpp b/llvm/lib/Transforms/Scalar/SmartPtrLifetime.cpp new file mode 100644 --- /dev/null +++ b/llvm/lib/Transforms/Scalar/SmartPtrLifetime.cpp @@ -0,0 +1,235 @@ +//===- DeadStoreElimination.cpp - Fast Dead Store Elimination -------------===// +// +// 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 file implements basic smart pointer lifetime optimizations. +// +//===----------------------------------------------------------------------===// + +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/PostOrderIterator.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/Statistic.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Analysis/PostDominators.h" +#include "llvm/IR/Argument.h" +#include "llvm/IR/BasicBlock.h" +#include "llvm/IR/Function.h" +#include "llvm/IR/InstIterator.h" +#include "llvm/IR/InstrTypes.h" +#include "llvm/IR/Instruction.h" +#include "llvm/IR/Instructions.h" +#include "llvm/IR/IntrinsicInst.h" +#include "llvm/IR/Intrinsics.h" +#include "llvm/IR/LLVMContext.h" +#include "llvm/IR/Module.h" +#include "llvm/IR/PassManager.h" +#include "llvm/IR/Value.h" +#include "llvm/InitializePasses.h" +#include "llvm/Pass.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/Debug.h" +#include "llvm/Transforms/Utils/Local.h" + +using namespace llvm; + +#define DEBUG_TYPE "smart-ptr-lifetime-opts" + +STATISTIC(NumUniquePtrDestructorsRemoved, + "Number of unique_ptr destructors that were able to be removed"); + +//===----------------------------------------------------------------------===// +// +//===----------------------------------------------------------------------===// + +namespace { + +/// The "Lifetime" state information for memory. +enum class Lifetime { + Unknown, // Unkown may be "Live" + MovedTo, + Empty // Empty means it was either "MovedFrom" or "Deleted". +}; + +/// Various known functions we will use to classify call instructions. +enum class KnownFunc { Unknown, GlobalMove, MoveConst, Destructor }; + +/// Classify a function based on the functions \p Name. +static KnownFunc lookupFunc(StringRef Name) { + // Fastpath: because we call lookupFunc on every CallInst. Make sure there's + // a semantics tag before we make any other lookups. + if (!Name.contains("__SEMANTICS")) + return KnownFunc::Unknown; + if (Name.contains("__SEMANTICS_unique_ptr_global_move")) + return KnownFunc::GlobalMove; + if (Name.contains("__SEMANTICS_unique_ptr_move")) + return KnownFunc::MoveConst; + if (Name.contains("__SEMANTICS_unique_ptr_destroy")) + return KnownFunc::Destructor; + return KnownFunc::Unknown; +} + +/// Check if the \p Op is passed through std::move. If so, return std::move's first (and only) operand. +/// Otherwise, return \p Op. +static Value *lookThroughNoopCast(Value *Op) { + if (CallInst *Call = dyn_cast(Op)) { + if (Call->getCalledFunction() && + lookupFunc(Call->getCalledFunction()->getName()) == KnownFunc::GlobalMove) + return Call->getArgOperand(0); + } + return Op; +} + +/// Set the lifetime of the unique_ptr move constructor arguments. The arguments +/// are from the move constructor, \p Call. The lifetime information is stored +/// in \p LifetimeLookup. +/// +/// The "To" argument (first) is "moved to" meaning it is live but also, if the +/// next use of the value "To" is a call, we know that call will call the +/// destructor. +/// +/// The "From" argument (second) is "empty" meaning the pointer is a nullptr. In +/// this case, the following destructor will be a noop so we can remove it. +/// TODO: in the future, we can null-out the unique_ptr here which may help +/// later optimization passes. +static void mapMoveConstArgs(CallInst *Call, + DenseMap &LifetimeLookup) { + Value *To = Call->getArgOperand(0); + Value *From = Call->getArgOperand(1); + LifetimeLookup[To] = Lifetime::MovedTo; + LifetimeLookup[From] = Lifetime::Empty; + // The "From" argument is ususally passed through "std::move" so we check if + // we can trace the value back any further. Then, also mark that value as + // empty. + From = lookThroughNoopCast(From); + LifetimeLookup[From] = Lifetime::Empty; +} + +/// Try to determin if we know the destructor is going to be a noop. The +/// destructor is \p Call. The lifetime information is stored in \p +/// LifetimeLookup. +/// +/// If the lifetime of the pointer is known, and empty, it return true (the +/// destructor is noop). Otherwise, set the lifetime "Empty" because after this +/// call, the unique_ptr will hold a nullptr and return false. +static bool isDestructorNoop(CallInst *Call, + DenseMap &LifetimeLookup) { + Value *UniquePtr = Call->getArgOperand(0); + Lifetime &UniquePtrLifetime = LifetimeLookup[UniquePtr]; + // If we knwo the unique_ptr holds nullptr, the destructor is a noop. + if (UniquePtrLifetime == Lifetime::Empty) + return true; + // Otherwise, mark it as dead. + UniquePtrLifetime = Lifetime::Empty; + return false; +} + +/// A legacy pass for the legacy pass manager that wraps \c DSEPass. +class SmartPtrLifetime : public FunctionPass { +public: + static char ID; // Pass identification, replacement for typeid + + SmartPtrLifetime() : FunctionPass(ID) { + initializeSmartPtrLifetimePass(*PassRegistry::getPassRegistry()); + } + + bool runOnFunction(Function &F) override { + bool Changed = false; + SmallVector DeadInsts; + DenseMap LifetimeLookup; + + auto InvalidateUnkown = [&](Instruction &Inst) { + // If there are any other uses of tracked values, those are unknown so, + // assume they make the pointer live (or "Unkown"). + for (size_t OpIdx = 0; OpIdx < Inst.getNumOperands(); ++OpIdx) { + Value *Op = Inst.getOperand(OpIdx); + auto FoundItr = llvm::find_if(LifetimeLookup, [Op](auto Both) { + return Both.getFirst() == Op; + }); + if (FoundItr != LifetimeLookup.end()) + FoundItr->getSecond() = Lifetime::Unknown; + } + }; + + for (BasicBlock &Block : F) { + for (Instruction &Inst : Block) { + // If this is a call instruction, see if it gives us any lifetime + // information. + if (CallInst *Call = dyn_cast(&Inst)) { + // If we can't find the called function, invalidate any operands and + // skip the call. + if (!Call->getCalledFunction()) { + InvalidateUnkown(Inst); + continue; + } + StringRef Name = Call->getCalledFunction()->getName(); + KnownFunc Func = lookupFunc(Name); + if (Func == KnownFunc::MoveConst) { + mapMoveConstArgs(Call, LifetimeLookup); + // TODO: We can lower the move constructor, replacing it with a + // store. + continue; + } else if (Func == KnownFunc::Destructor) { + if (isDestructorNoop(Call, LifetimeLookup)) { + DeadInsts.push_back(Call); + ++NumUniquePtrDestructorsRemoved; + } + continue; + } + // If this call uses a value we're keeping track of. + for (size_t ArgIdx = 0; ArgIdx < Call->getNumArgOperands(); + ++ArgIdx) { + Value *Arg = Call->getArgOperand(ArgIdx); + auto FoundItr = llvm::find_if(LifetimeLookup, [Arg](auto Both) { + return Both.getFirst() == Arg; + }); + if (FoundItr == LifetimeLookup.end()) + continue; + Lifetime &TrackedLifetime = FoundItr->getSecond(); + // If we moved into this value and then passed it as an arg, we + // know that it will be deleted in the context it was passed into. + if (TrackedLifetime == Lifetime::MovedTo) + TrackedLifetime = Lifetime::Empty; + else + // Otherwise, we have no idea what might happen to the operand. + TrackedLifetime = Lifetime::Unknown; + } + // Nothing more to be done with this instruction; continue. + continue; + } + // If the instruction is unkown, invalidate any operands that we are + // tracking. + InvalidateUnkown(Inst); + } + // We're in a new block, set all tracked values to "Unknown" because we + // don't know what block we may have come from. + // TODO: in the future we may be able to dominance analysis to safely + // perform multi-block optimizations. + for (auto &Both : LifetimeLookup) + Both.getSecond() = Lifetime::Unknown; + } + + Changed = !DeadInsts.empty(); + for (Instruction *I : DeadInsts) + I->eraseFromParent(); + + return Changed; + } + + void getAnalysisUsage(AnalysisUsage &AU) const override {} +}; + +} // end anonymous namespace + +char SmartPtrLifetime::ID = 0; + +INITIALIZE_PASS(SmartPtrLifetime, "smart-ptr-lifetime-opts", + "Smart Pointer Lifetime Optimizations", false, false) + +FunctionPass *llvm::createSmartPtrLifetimePass() { + return new SmartPtrLifetime(); +} diff --git a/llvm/test/Transforms/SmartPtrLifetime/simple.ll b/llvm/test/Transforms/SmartPtrLifetime/simple.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Transforms/SmartPtrLifetime/simple.ll @@ -0,0 +1,144 @@ +; RUN: opt < %s -smart-ptr-lifetime-opts -S | FileCheck %s + +target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-apple-macosx10.15.0" + +%struct.unique_ptr = type { i32* } + +declare noalias nonnull i8* @new(i64) + +declare void @init(%struct.unique_ptr*, i32*) + +declare i32* @get_ptr(%struct.unique_ptr*) + +declare void @guaranteed(%struct.unique_ptr* ) + +declare %struct.unique_ptr* @__SEMANTICS_unique_ptr_global_move(%struct.unique_ptr*) + +declare void @__SEMANTICS_unique_ptr_move_const(%struct.unique_ptr*, %struct.unique_ptr*) + +declare void @owner(%struct.unique_ptr* %agg.tmp) + +declare void @__SEMANTICS_unique_ptr_destroy(%struct.unique_ptr*) + +; CHECK-LABEL: @test_gauranteed_and_owned +; CHECK: [[P1:%.*]] = alloca %struct.unique_ptr +; CHECK: [[P_TMP:%.*]] = alloca %struct.unique_ptr +; CHECK: [[NEW_MEM:%.*]] = call noalias nonnull i8* @new +; CHECK: [[I_PTR:%.*]] = bitcast i8* [[NEW_MEM]] to i32* +; CHECK: call void @init(%struct.unique_ptr* [[P1]], i32* [[I_PTR]]) +; CHECK: [[RAW_PTR:%.*]] = call i32* @get_ptr(%struct.unique_ptr* [[P1]]) +; CHECK: store i32 42, i32* [[RAW_PTR]] +; CHECK: call void @guaranteed(%struct.unique_ptr* [[P1]]) +; CHECK: [[MOVED:%.*]] = call %struct.unique_ptr* @__SEMANTICS_unique_ptr_global_move(%struct.unique_ptr* [[P1]]) +; CHECK: call void @__SEMANTICS_unique_ptr_move_const(%struct.unique_ptr* [[P_TMP]], %struct.unique_ptr* [[MOVED]]) +; CHECK: call void @owner(%struct.unique_ptr* [[P_TMP]]) +; Check that there are no calls to "__SEMANTICS_unique_ptr_destroy." +; CHECK-NEXT: ret void +define void @test_gauranteed_and_owned() { +entry: + %p = alloca %struct.unique_ptr, align 8 + %agg.tmp = alloca %struct.unique_ptr, align 8 + %call = call noalias nonnull i8* @new(i64 16) + %0 = bitcast i8* %call to i32* + call void @init(%struct.unique_ptr* %p, i32* %0) + %call1 = call i32* @get_ptr(%struct.unique_ptr* %p) + store i32 42, i32* %call1, align 4 + call void @guaranteed(%struct.unique_ptr* %p) + %call2 = call %struct.unique_ptr* @__SEMANTICS_unique_ptr_global_move(%struct.unique_ptr* %p) + call void @__SEMANTICS_unique_ptr_move_const(%struct.unique_ptr* %agg.tmp, %struct.unique_ptr* %call2) + call void @owner(%struct.unique_ptr* %agg.tmp) + call void @__SEMANTICS_unique_ptr_destroy(%struct.unique_ptr* %agg.tmp) + call void @__SEMANTICS_unique_ptr_destroy(%struct.unique_ptr* %p) + ret void +} + +; CHECK-LABEL: @test_no_global_move +; CHECK: [[P1:%.*]] = alloca %struct.unique_ptr +; CHECK: [[P_TMP:%.*]] = alloca %struct.unique_ptr +; CHECK: [[NEW_MEM:%.*]] = call noalias nonnull i8* @new +; CHECK: [[I_PTR:%.*]] = bitcast i8* [[NEW_MEM]] to i32* +; CHECK: call void @init(%struct.unique_ptr* [[P1]], i32* [[I_PTR]]) +; CHECK: [[RAW_PTR:%.*]] = call i32* @get_ptr(%struct.unique_ptr* [[P1]]) +; CHECK: store i32 42, i32* [[RAW_PTR]] +; CHECK: call void @guaranteed(%struct.unique_ptr* [[P1]]) +; CHECK: call void @__SEMANTICS_unique_ptr_move_const(%struct.unique_ptr* [[P_TMP]], %struct.unique_ptr* [[P1]]) +; CHECK: call void @owner(%struct.unique_ptr* [[P_TMP]]) +; Check that there are no calls to "__SEMANTICS_unique_ptr_destroy." +; CHECK-NEXT: ret void +define void @test_no_global_move() { +entry: + %p = alloca %struct.unique_ptr, align 8 + %agg.tmp = alloca %struct.unique_ptr, align 8 + %call = call noalias nonnull i8* @new(i64 16) + %0 = bitcast i8* %call to i32* + call void @init(%struct.unique_ptr* %p, i32* %0) + %call1 = call i32* @get_ptr(%struct.unique_ptr* %p) + store i32 42, i32* %call1, align 4 + call void @guaranteed(%struct.unique_ptr* %p) + call void @__SEMANTICS_unique_ptr_move_const(%struct.unique_ptr* %agg.tmp, %struct.unique_ptr* %p) + call void @owner(%struct.unique_ptr* %agg.tmp) + call void @__SEMANTICS_unique_ptr_destroy(%struct.unique_ptr* %agg.tmp) + call void @__SEMANTICS_unique_ptr_destroy(%struct.unique_ptr* %p) + ret void +} + +; CHECK-LABEL: @test_owned_then_guaranteed +; CHECK: [[P1:%.*]] = alloca %struct.unique_ptr +; CHECK: [[P_TMP:%.*]] = alloca %struct.unique_ptr +; CHECK: [[NEW_MEM:%.*]] = call noalias nonnull i8* @new +; CHECK: [[I_PTR:%.*]] = bitcast i8* [[NEW_MEM]] to i32* +; CHECK: call void @init(%struct.unique_ptr* [[P1]], i32* [[I_PTR]]) +; CHECK: [[RAW_PTR:%.*]] = call i32* @get_ptr(%struct.unique_ptr* [[P1]]) +; CHECK: store i32 42, i32* [[RAW_PTR]] +; CHECK: [[MOVED:%.*]] = call %struct.unique_ptr* @__SEMANTICS_unique_ptr_global_move(%struct.unique_ptr* [[P1]]) +; CHECK: call void @__SEMANTICS_unique_ptr_move_const(%struct.unique_ptr* [[P_TMP]], %struct.unique_ptr* [[MOVED]]) +; CHECK: call void @owner(%struct.unique_ptr* [[P_TMP]]) +; CHECK-NEXT: call void @guaranteed(%struct.unique_ptr* [[P1]]) +; CHECK-NEXT: call void @__SEMANTICS_unique_ptr_destroy(%struct.unique_ptr* [[P1]]) +; CHECK-NEXT: ret void +define void @test_owned_then_guaranteed() { +entry: + %p = alloca %struct.unique_ptr, align 8 + %agg.tmp = alloca %struct.unique_ptr, align 8 + %call = call noalias nonnull i8* @new(i64 16) + %0 = bitcast i8* %call to i32* + call void @init(%struct.unique_ptr* %p, i32* %0) + %call1 = call i32* @get_ptr(%struct.unique_ptr* %p) + store i32 42, i32* %call1, align 4 + %call2 = call %struct.unique_ptr* @__SEMANTICS_unique_ptr_global_move(%struct.unique_ptr* %p) + call void @__SEMANTICS_unique_ptr_move_const(%struct.unique_ptr* %agg.tmp, %struct.unique_ptr* %call2) + call void @owner(%struct.unique_ptr* %agg.tmp) + ; This function is unkown, it could call reset or release so, we can't optimize p's destructor. + call void @guaranteed(%struct.unique_ptr* %p) + ; We can remove this destructor, though. + call void @__SEMANTICS_unique_ptr_destroy(%struct.unique_ptr* %agg.tmp) + call void @__SEMANTICS_unique_ptr_destroy(%struct.unique_ptr* %p) + ret void +} + +; We can't yet optimize across multiple blocks. +; CHECK-LABEL: @test_multiblock +; CHECK: next: +; CHECK-NEXT: __SEMANTICS_unique_ptr_destroy +; CHECK-NEXT: __SEMANTICS_unique_ptr_destroy +; CHECK-NEXT: ret void +define void @test_multiblock() { +entry: + %p = alloca %struct.unique_ptr, align 8 + %agg.tmp = alloca %struct.unique_ptr, align 8 + %call = call noalias nonnull i8* @new(i64 16) + %0 = bitcast i8* %call to i32* + call void @init(%struct.unique_ptr* %p, i32* %0) + %call1 = call i32* @get_ptr(%struct.unique_ptr* %p) + store i32 42, i32* %call1, align 4 + %call2 = call %struct.unique_ptr* @__SEMANTICS_unique_ptr_global_move(%struct.unique_ptr* %p) + call void @__SEMANTICS_unique_ptr_move_const(%struct.unique_ptr* %agg.tmp, %struct.unique_ptr* %call2) + call void @owner(%struct.unique_ptr* %agg.tmp) + br label %next + +next: + call void @__SEMANTICS_unique_ptr_destroy(%struct.unique_ptr* %agg.tmp) + call void @__SEMANTICS_unique_ptr_destroy(%struct.unique_ptr* %p) + ret void +}