diff --git a/clang/lib/CodeGen/CGObjC.cpp b/clang/lib/CodeGen/CGObjC.cpp --- a/clang/lib/CodeGen/CGObjC.cpp +++ b/clang/lib/CodeGen/CGObjC.cpp @@ -23,6 +23,7 @@ #include "clang/Basic/Diagnostic.h" #include "clang/CodeGen/CGFunctionInfo.h" #include "llvm/ADT/STLExtras.h" +#include "llvm/Analysis/ObjCARCRVAttr.h" #include "llvm/BinaryFormat/MachO.h" #include "llvm/IR/DataLayout.h" #include "llvm/IR/InlineAsm.h" @@ -2304,10 +2305,11 @@ // with this marker yet, so leave a breadcrumb for the ARC // optimizer to pick up. } else { - const char *markerKey = "clang.arc.retainAutoreleasedReturnValueMarker"; - if (!CGF.CGM.getModule().getModuleFlag(markerKey)) { + const char *retainRVMarkerKey = llvm::objcarc::getRVMarkerModuleFlagStr(); + if (!CGF.CGM.getModule().getModuleFlag(retainRVMarkerKey)) { auto *str = llvm::MDString::get(CGF.getLLVMContext(), assembly); - CGF.CGM.getModule().addModuleFlag(llvm::Module::Error, markerKey, str); + CGF.CGM.getModule().addModuleFlag(llvm::Module::Error, + retainRVMarkerKey, str); } } } @@ -2317,6 +2319,46 @@ CGF.Builder.CreateCall(marker, None, CGF.getBundlesForFunclet(marker)); } +static llvm::Value *emitOptimizedARCReturnCall(llvm::Value *value, + bool IsRetainRV, + CodeGenFunction &CGF) { + emitAutoreleasedReturnValueMarker(CGF); + + // Annotate the call with attributes instead of emitting retainRV or claimRV + // calls in the IR. We currently do this only when the optimization level + // isn't -O0 since global-isel, which is currently run at -O0, doesn't know + // about the attributes. + + // FIXME: Do this when the target isn't aarch64. + if (CGF.CGM.getCodeGenOpts().OptimizationLevel > 0 && + CGF.CGM.getTarget().getTriple().isAArch64()) { + auto *callBase = cast(value); + llvm::AttributeList attrs = callBase->getAttributes(); + attrs = attrs.addAttribute(CGF.getLLVMContext(), + llvm::AttributeList::ReturnIndex, + llvm::objcarc::getRVAttrKeyStr(), + llvm::objcarc::getRVAttrValStr(IsRetainRV)); + attrs = attrs.addAttribute(CGF.getLLVMContext(), + llvm::AttributeList::ReturnIndex, + llvm::objcarc::getRVMarkerAttrStr()); + callBase->setAttributes(attrs); + return callBase; + } + + bool isNoTail = + CGF.CGM.getTargetCodeGenInfo().markARCOptimizedReturnCallsAsNoTail(); + llvm::CallInst::TailCallKind tailKind = + isNoTail ? llvm::CallInst::TCK_NoTail : llvm::CallInst::TCK_None; + ObjCEntrypoints &EPs = CGF.CGM.getObjCEntrypoints(); + llvm::Function *&EP = IsRetainRV + ? EPs.objc_retainAutoreleasedReturnValue + : EPs.objc_unsafeClaimAutoreleasedReturnValue; + llvm::Intrinsic::ID IID = + IsRetainRV ? llvm::Intrinsic::objc_retainAutoreleasedReturnValue + : llvm::Intrinsic::objc_unsafeClaimAutoreleasedReturnValue; + return emitARCValueOperation(CGF, value, nullptr, EP, IID, tailKind); +} + /// Retain the given object which is the result of a function call. /// call i8* \@objc_retainAutoreleasedReturnValue(i8* %value) /// @@ -2324,15 +2366,7 @@ /// call with completely different semantics. llvm::Value * CodeGenFunction::EmitARCRetainAutoreleasedReturnValue(llvm::Value *value) { - emitAutoreleasedReturnValueMarker(*this); - llvm::CallInst::TailCallKind tailKind = - CGM.getTargetCodeGenInfo().markARCOptimizedReturnCallsAsNoTail() - ? llvm::CallInst::TCK_NoTail - : llvm::CallInst::TCK_None; - return emitARCValueOperation( - *this, value, nullptr, - CGM.getObjCEntrypoints().objc_retainAutoreleasedReturnValue, - llvm::Intrinsic::objc_retainAutoreleasedReturnValue, tailKind); + return emitOptimizedARCReturnCall(value, true, *this); } /// Claim a possibly-autoreleased return value at +0. This is only @@ -2344,15 +2378,7 @@ /// call i8* \@objc_unsafeClaimAutoreleasedReturnValue(i8* %value) llvm::Value * CodeGenFunction::EmitARCUnsafeClaimAutoreleasedReturnValue(llvm::Value *value) { - emitAutoreleasedReturnValueMarker(*this); - llvm::CallInst::TailCallKind tailKind = - CGM.getTargetCodeGenInfo().markARCOptimizedReturnCallsAsNoTail() - ? llvm::CallInst::TCK_NoTail - : llvm::CallInst::TCK_None; - return emitARCValueOperation( - *this, value, nullptr, - CGM.getObjCEntrypoints().objc_unsafeClaimAutoreleasedReturnValue, - llvm::Intrinsic::objc_unsafeClaimAutoreleasedReturnValue, tailKind); + return emitOptimizedARCReturnCall(value, false, *this); } /// Release the given object. diff --git a/clang/test/CodeGenObjC/arc-rv-attr.m b/clang/test/CodeGenObjC/arc-rv-attr.m new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenObjC/arc-rv-attr.m @@ -0,0 +1,167 @@ +// RUN: %clang_cc1 -triple arm64-apple-ios9 -fobjc-runtime=ios-9.0 -fobjc-arc -O -disable-llvm-passes -emit-llvm -o - %s | FileCheck %s -check-prefix=CHECK + +@class A; + +A *makeA(void); + +void test_assign() { + __unsafe_unretained id x; + x = makeA(); +} +// CHECK-LABEL: define{{.*}} void @test_assign() +// CHECK: [[X:%.*]] = alloca i8* +// CHECK: [[T0:%.*]] = call "clang.arc.rv"="claim" "clang.arc.rv_marker" [[A:.*]]* @makeA() +// CHECK-NEXT: [[T1:%.*]] = bitcast [[A]]* [[T0]] to i8* +// CHECK-NEXT: store i8* [[T1]], i8** [[X]] +// CHECK-NEXT: bitcast +// CHECK-NEXT: lifetime.end +// CHECK-NEXT: ret void + +void test_assign_assign() { + __unsafe_unretained id x, y; + x = y = makeA(); +} +// CHECK-LABEL: define{{.*}} void @test_assign_assign() +// CHECK: [[X:%.*]] = alloca i8* +// CHECK: [[Y:%.*]] = alloca i8* +// CHECK: [[T0:%.*]] = call "clang.arc.rv"="claim" "clang.arc.rv_marker" [[A]]* @makeA() +// CHECK-NEXT: [[T1:%.*]] = bitcast [[A]]* [[T0]] to i8* +// CHECK-NEXT: store i8* [[T1]], i8** [[Y]] +// CHECK-NEXT: store i8* [[T1]], i8** [[X]] +// CHECK-NEXT: bitcast +// CHECK-NEXT: lifetime.end +// CHECK-NEXT: bitcast +// CHECK-NEXT: lifetime.end +// CHECK-NEXT: ret void + +void test_strong_assign_assign() { + __strong id x; + __unsafe_unretained id y; + x = y = makeA(); +} +// CHECK-LABEL: define{{.*}} void @test_strong_assign_assign() +// CHECK: [[X:%.*]] = alloca i8* +// CHECK: [[Y:%.*]] = alloca i8* +// CHECK: [[T0:%.*]] = call "clang.arc.rv"="retain" "clang.arc.rv_marker" [[A]]* @makeA() +// CHECK-NEXT: [[T1:%.*]] = bitcast [[A]]* [[T0]] to i8* +// CHECK-NEXT: store i8* [[T1]], i8** [[Y]] +// CHECK-NEXT: [[OLD:%.*]] = load i8*, i8** [[X]] +// CHECK-NEXT: store i8* [[T1]], i8** [[X]] +// CHECK-NEXT: call void @llvm.objc.release(i8* [[OLD]] +// CHECK-NEXT: bitcast +// CHECK-NEXT: lifetime.end +// CHECK-NEXT: [[T0:%.*]] = load i8*, i8** [[X]] +// CHECK-NEXT: call void @llvm.objc.release(i8* [[T0]]) +// CHECK-NEXT: bitcast +// CHECK-NEXT: lifetime.end +// CHECK-NEXT: ret void + +void test_assign_strong_assign() { + __unsafe_unretained id x; + __strong id y; + x = y = makeA(); +} +// CHECK-LABEL: define{{.*}} void @test_assign_strong_assign() +// CHECK: [[X:%.*]] = alloca i8* +// CHECK: [[Y:%.*]] = alloca i8* +// CHECK: [[T0:%.*]] = call "clang.arc.rv"="retain" "clang.arc.rv_marker" [[A]]* @makeA() +// CHECK-NEXT: [[T1:%.*]] = bitcast [[A]]* [[T0]] to i8* +// CHECK-NEXT: [[OLD:%.*]] = load i8*, i8** [[Y]] +// CHECK-NEXT: store i8* [[T1]], i8** [[Y]] +// CHECK-NEXT: call void @llvm.objc.release(i8* [[OLD]] +// CHECK-NEXT: store i8* [[T1]], i8** [[X]] +// CHECK-NEXT: [[T0:%.*]] = load i8*, i8** [[Y]] +// CHECK-NEXT: call void @llvm.objc.release(i8* [[T0]]) +// CHECK-NEXT: bitcast +// CHECK-NEXT: lifetime.end +// CHECK-NEXT: bitcast +// CHECK-NEXT: lifetime.end +// CHECK-NEXT: ret void + +void test_init() { + __unsafe_unretained id x = makeA(); +} +// CHECK-LABEL: define{{.*}} void @test_init() +// CHECK: [[X:%.*]] = alloca i8* +// CHECK: [[T0:%.*]] = call "clang.arc.rv"="claim" "clang.arc.rv_marker" [[A]]* @makeA() +// CHECK-NEXT: [[T1:%.*]] = bitcast [[A]]* [[T0]] to i8* +// CHECK-NEXT: store i8* [[T1]], i8** [[X]] +// CHECK-NEXT: bitcast +// CHECK-NEXT: lifetime.end +// CHECK-NEXT: ret void + +void test_init_assignment() { + __unsafe_unretained id x; + __unsafe_unretained id y = x = makeA(); +} +// CHECK-LABEL: define{{.*}} void @test_init_assignment() +// CHECK: [[X:%.*]] = alloca i8* +// CHECK: [[Y:%.*]] = alloca i8* +// CHECK: [[T0:%.*]] = call "clang.arc.rv"="claim" "clang.arc.rv_marker" [[A]]* @makeA() +// CHECK-NEXT: [[T1:%.*]] = bitcast [[A]]* [[T0]] to i8* +// CHECK-NEXT: store i8* [[T1]], i8** [[X]] +// CHECK-NEXT: store i8* [[T1]], i8** [[Y]] +// CHECK-NEXT: bitcast +// CHECK-NEXT: lifetime.end +// CHECK-NEXT: bitcast +// CHECK-NEXT: lifetime.end +// CHECK-NEXT: ret void + +void test_strong_init_assignment() { + __unsafe_unretained id x; + __strong id y = x = makeA(); +} +// CHECK-LABEL: define{{.*}} void @test_strong_init_assignment() +// CHECK: [[X:%.*]] = alloca i8* +// CHECK: [[Y:%.*]] = alloca i8* +// CHECK: [[T0:%.*]] = call "clang.arc.rv"="retain" "clang.arc.rv_marker" [[A]]* @makeA() +// CHECK-NEXT: [[T1:%.*]] = bitcast [[A]]* [[T0]] to i8* +// CHECK-NEXT: store i8* [[T1]], i8** [[X]] +// CHECK-NEXT: store i8* [[T1]], i8** [[Y]] +// CHECK-NEXT: [[T0:%.*]] = load i8*, i8** [[Y]] +// CHECK-NEXT: call void @llvm.objc.release(i8* [[T0]]) +// CHECK-NEXT: bitcast +// CHECK-NEXT: lifetime.end +// CHECK-NEXT: bitcast +// CHECK-NEXT: lifetime.end +// CHECK-NEXT: ret void + +void test_init_strong_assignment() { + __strong id x; + __unsafe_unretained id y = x = makeA(); +} +// CHECK-LABEL: define{{.*}} void @test_init_strong_assignment() +// CHECK: [[X:%.*]] = alloca i8* +// CHECK: [[Y:%.*]] = alloca i8* +// CHECK: [[T0:%.*]] = call "clang.arc.rv"="retain" "clang.arc.rv_marker" [[A]]* @makeA() +// CHECK-NEXT: [[T1:%.*]] = bitcast [[A]]* [[T0]] to i8* +// CHECK-NEXT: [[OLD:%.*]] = load i8*, i8** [[X]] +// CHECK-NEXT: store i8* [[T1]], i8** [[X]] +// CHECK-NEXT: call void @llvm.objc.release(i8* [[OLD]]) +// CHECK-NEXT: store i8* [[T1]], i8** [[Y]] +// CHECK-NEXT: bitcast +// CHECK-NEXT: lifetime.end +// CHECK-NEXT: [[T0:%.*]] = load i8*, i8** [[X]] +// CHECK-NEXT: call void @llvm.objc.release(i8* [[T0]]) +// CHECK-NEXT: bitcast +// CHECK-NEXT: lifetime.end +// CHECK-NEXT: ret void + +void test_ignored() { + makeA(); +} +// CHECK-LABEL: define{{.*}} void @test_ignored() +// CHECK: [[T0:%.*]] = call "clang.arc.rv"="claim" "clang.arc.rv_marker" [[A]]* @makeA() +// CHECK-NEXT: ret void + +void test_cast_to_void() { + (void) makeA(); +} +// CHECK-LABEL: define{{.*}} void @test_cast_to_void() +// CHECK: [[T0:%.*]] = call "clang.arc.rv"="claim" "clang.arc.rv_marker" [[A]]* @makeA() +// CHECK-NEXT: ret void + +// This is always at the end of the module. + +// CHECK-OPTIMIZED: !llvm.module.flags = !{!0, +// CHECK-OPTIMIZED: !0 = !{i32 1, !"clang.arc.retainAutoreleasedReturnValueMarker", !"mov{{.*}}marker for objc_retainAutoreleaseReturnValue"} diff --git a/clang/test/CodeGenObjC/arc-unsafeclaim.m b/clang/test/CodeGenObjC/arc-unsafeclaim.m --- a/clang/test/CodeGenObjC/arc-unsafeclaim.m +++ b/clang/test/CodeGenObjC/arc-unsafeclaim.m @@ -4,11 +4,10 @@ // Make sure it works on x86-32. // RUN: %clang_cc1 -triple i386-apple-darwin11 -fobjc-runtime=macosx-fragile-10.11 -fobjc-arc -emit-llvm -o - %s | FileCheck %s -check-prefix=CHECK -check-prefix=CHECK-UNOPTIMIZED -check-prefix=CHECK-MARKED -check-prefix=CALL -// Make sure it works on ARM. +// Make sure it works on ARM64. // RUN: %clang_cc1 -triple arm64-apple-ios9 -fobjc-runtime=ios-9.0 -fobjc-arc -emit-llvm -o - %s | FileCheck %s -check-prefix=CHECK -check-prefix=CHECK-UNOPTIMIZED -check-prefix=CHECK-MARKED -check-prefix=CALL -// RUN: %clang_cc1 -triple arm64-apple-ios9 -fobjc-runtime=ios-9.0 -fobjc-arc -O -disable-llvm-passes -emit-llvm -o - %s | FileCheck %s -check-prefix=CHECK -check-prefix=CHECK-OPTIMIZED -check-prefix=CALL -// Make sure it works on ARM64. +// Make sure it works on ARM. // RUN: %clang_cc1 -triple armv7-apple-ios9 -fobjc-runtime=ios-9.0 -fobjc-arc -emit-llvm -o - %s | FileCheck %s -check-prefix=CHECK -check-prefix=CHECK-UNOPTIMIZED -check-prefix=CHECK-MARKED -check-prefix=CALL // RUN: %clang_cc1 -triple armv7-apple-ios9 -fobjc-runtime=ios-9.0 -fobjc-arc -O -disable-llvm-passes -emit-llvm -o - %s | FileCheck %s -check-prefix=CHECK -check-prefix=CHECK-OPTIMIZED -check-prefix=CALL diff --git a/llvm/include/llvm/Analysis/ObjCARCRVAttr.h b/llvm/include/llvm/Analysis/ObjCARCRVAttr.h new file mode 100644 --- /dev/null +++ b/llvm/include/llvm/Analysis/ObjCARCRVAttr.h @@ -0,0 +1,65 @@ +//===- ObjCARCRVAttr.h - ObjC ARC Attribute Analysis ------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// +/// \file +/// This file defines functions which look for or remove attributes retainRV, +/// claimRV, and rv_marker. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIB_ANALYSIS_OBJCARCRVATTR_H +#define LLVM_LIB_ANALYSIS_OBJCARCRVATTR_H + +#include "llvm/IR/InstrTypes.h" + +namespace llvm { +namespace objcarc { + +static inline const char *getRVMarkerModuleFlagStr() { + return "clang.arc.retainAutoreleasedReturnValueMarker"; +} + +static inline const char *getRVAttrKeyStr() { return "clang.arc.rv"; } + +static inline const char *getRVAttrValStr(bool Retain) { + return Retain ? "retain" : "claim"; +} + +static inline const char *getRVMarkerAttrStr() { return "clang.arc.rv_marker"; } + +static inline bool hasRetainRVAttr(const CallBase *CB) { + return CB->getAttribute(llvm::AttributeList::ReturnIndex, getRVAttrKeyStr()) + .getValueAsString() + .equals(getRVAttrValStr(true)); +} +static inline bool hasClaimRVAttr(const CallBase *CB) { + return CB->getAttribute(llvm::AttributeList::ReturnIndex, getRVAttrKeyStr()) + .getValueAsString() + .equals(getRVAttrValStr(false)); +} + +static inline bool hasRetainRVOrClaimRVAttr(const CallBase *CB) { + return hasRetainRVAttr(CB) || hasClaimRVAttr(CB); +} + +static inline bool hasRVMarkerAttr(const CallBase *CB) { + return CB->hasRetAttr(getRVMarkerAttrStr()); +} + +static inline void removeRetainRVOrClaimRVAttr(CallBase *CB, + bool RemoveMarker) { + if (hasRetainRVOrClaimRVAttr(CB)) + CB->removeAttribute(llvm::AttributeList::ReturnIndex, getRVAttrKeyStr()); + + if (RemoveMarker && hasRVMarkerAttr(CB)) + CB->removeAttribute(llvm::AttributeList::ReturnIndex, getRVMarkerAttrStr()); +} + +} // end namespace objcarc +} // end namespace llvm + +#endif diff --git a/llvm/lib/IR/AutoUpgrade.cpp b/llvm/lib/IR/AutoUpgrade.cpp --- a/llvm/lib/IR/AutoUpgrade.cpp +++ b/llvm/lib/IR/AutoUpgrade.cpp @@ -14,14 +14,15 @@ #include "llvm/IR/AutoUpgrade.h" #include "llvm/ADT/StringSwitch.h" +#include "llvm/Analysis/ObjCARCRVAttr.h" #include "llvm/IR/Constants.h" #include "llvm/IR/DIBuilder.h" #include "llvm/IR/DebugInfo.h" #include "llvm/IR/DiagnosticInfo.h" #include "llvm/IR/Function.h" #include "llvm/IR/IRBuilder.h" -#include "llvm/IR/Instruction.h" #include "llvm/IR/InstVisitor.h" +#include "llvm/IR/Instruction.h" #include "llvm/IR/IntrinsicInst.h" #include "llvm/IR/Intrinsics.h" #include "llvm/IR/IntrinsicsAArch64.h" @@ -3997,7 +3998,7 @@ /// returns true if module is modified. static bool UpgradeRetainReleaseMarker(Module &M) { bool Changed = false; - const char *MarkerKey = "clang.arc.retainAutoreleasedReturnValueMarker"; + const char *MarkerKey = objcarc::getRVMarkerModuleFlagStr(); NamedMDNode *ModRetainReleaseMarker = M.getNamedMetadata(MarkerKey); if (ModRetainReleaseMarker) { MDNode *Op = ModRetainReleaseMarker->getOperand(0); diff --git a/llvm/lib/IR/Instruction.cpp b/llvm/lib/IR/Instruction.cpp --- a/llvm/lib/IR/Instruction.cpp +++ b/llvm/lib/IR/Instruction.cpp @@ -11,10 +11,11 @@ //===----------------------------------------------------------------------===// #include "llvm/IR/Instruction.h" -#include "llvm/IR/IntrinsicInst.h" #include "llvm/ADT/DenseSet.h" +#include "llvm/Analysis/ObjCARCRVAttr.h" #include "llvm/IR/Constants.h" #include "llvm/IR/Instructions.h" +#include "llvm/IR/IntrinsicInst.h" #include "llvm/IR/MDBuilder.h" #include "llvm/IR/Operator.h" #include "llvm/IR/Type.h" @@ -572,8 +573,13 @@ return true; case Instruction::Call: case Instruction::Invoke: - case Instruction::CallBr: - return !cast(this)->onlyReadsMemory(); + case Instruction::CallBr: { + if (!cast(this)->onlyReadsMemory()) + return true; + if (auto *CB = dyn_cast(this)) + return objcarc::hasRetainRVOrClaimRVAttr(CB); + return false; + } case Instruction::Load: return !cast(this)->isUnordered(); } diff --git a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp --- a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp +++ b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp @@ -30,6 +30,7 @@ #include "llvm/ADT/StringSwitch.h" #include "llvm/ADT/Triple.h" #include "llvm/ADT/Twine.h" +#include "llvm/Analysis/ObjCARCRVAttr.h" #include "llvm/Analysis/VectorUtils.h" #include "llvm/CodeGen/CallingConvLower.h" #include "llvm/CodeGen/MachineBasicBlock.h" @@ -5641,7 +5642,7 @@ // Calls marked with "rv_marker" are special. They should be expanded to the // call, directly followed by a special marker sequence. Use the CALL_RVMARKER // to do that. - if (CLI.CB && CLI.CB->hasRetAttr("rv_marker")) { + if (CLI.CB && CLI.CB->hasRetAttr(objcarc::getRVMarkerAttrStr())) { assert(!IsTailCall && "tail calls cannot be marked with rv_marker"); CallOpc = AArch64ISD::CALL_RVMARKER; } diff --git a/llvm/lib/Transforms/IPO/DeadArgumentElimination.cpp b/llvm/lib/Transforms/IPO/DeadArgumentElimination.cpp --- a/llvm/lib/Transforms/IPO/DeadArgumentElimination.cpp +++ b/llvm/lib/Transforms/IPO/DeadArgumentElimination.cpp @@ -19,6 +19,7 @@ #include "llvm/Transforms/IPO/DeadArgumentElimination.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/Statistic.h" +#include "llvm/Analysis/ObjCARCRVAttr.h" #include "llvm/IR/Argument.h" #include "llvm/IR/Attributes.h" #include "llvm/IR/BasicBlock.h" @@ -571,6 +572,13 @@ if (NumLiveRetVals == RetCount) continue; + // Don't change the return type of the function if it has retainRV/claimRV. + if (objcarc::hasRetainRVOrClaimRVAttr(CB)) { + NumLiveRetVals = RetCount; + RetValLiveness.assign(RetCount, Live); + continue; + } + // Check all uses of the return value. for (const Use &U : CB->uses()) { if (ExtractValueInst *Ext = dyn_cast(U.getUser())) { diff --git a/llvm/lib/Transforms/ObjCARC/ARCRuntimeEntryPoints.h b/llvm/lib/Transforms/ObjCARC/ARCRuntimeEntryPoints.h --- a/llvm/lib/Transforms/ObjCARC/ARCRuntimeEntryPoints.h +++ b/llvm/lib/Transforms/ObjCARC/ARCRuntimeEntryPoints.h @@ -42,6 +42,7 @@ Autorelease, StoreStrong, RetainRV, + ClaimRV, RetainAutorelease, RetainAutoreleaseRV, }; @@ -61,6 +62,7 @@ Autorelease = nullptr; StoreStrong = nullptr; RetainRV = nullptr; + ClaimRV = nullptr; RetainAutorelease = nullptr; RetainAutoreleaseRV = nullptr; } @@ -85,6 +87,9 @@ case ARCRuntimeEntryPointKind::RetainRV: return getIntrinsicEntryPoint(RetainRV, Intrinsic::objc_retainAutoreleasedReturnValue); + case ARCRuntimeEntryPointKind::ClaimRV: + return getIntrinsicEntryPoint( + ClaimRV, Intrinsic::objc_unsafeClaimAutoreleasedReturnValue); case ARCRuntimeEntryPointKind::RetainAutorelease: return getIntrinsicEntryPoint(RetainAutorelease, Intrinsic::objc_retainAutorelease); @@ -121,6 +126,9 @@ /// Declaration for objc_retainAutoreleasedReturnValue(). Function *RetainRV = nullptr; + /// Declaration for objc_unsafeClaimAutoreleasedReturnValue(). + Function *ClaimRV = nullptr; + /// Declaration for objc_retainAutorelease(). Function *RetainAutorelease = nullptr; diff --git a/llvm/lib/Transforms/ObjCARC/ObjCARC.h b/llvm/lib/Transforms/ObjCARC/ObjCARC.h --- a/llvm/lib/Transforms/ObjCARC/ObjCARC.h +++ b/llvm/lib/Transforms/ObjCARC/ObjCARC.h @@ -22,7 +22,10 @@ #ifndef LLVM_LIB_TRANSFORMS_OBJCARC_OBJCARC_H #define LLVM_LIB_TRANSFORMS_OBJCARC_OBJCARC_H +#include "ARCRuntimeEntryPoints.h" +#include "llvm/Analysis/EHPersonalities.h" #include "llvm/Analysis/ObjCARCAnalysisUtils.h" +#include "llvm/Analysis/ObjCARCRVAttr.h" #include "llvm/Transforms/Utils/Local.h" namespace llvm { @@ -87,6 +90,63 @@ } } +static inline MDString *getRVInstMarker(Module &M) { + const char *MarkerKey = getRVMarkerModuleFlagStr(); + return dyn_cast_or_null(M.getModuleFlag(MarkerKey)); +} + +/// Create a call instruction with the correct funclet token. This should be +/// called instead of calling CallInst::Create directly unless the call is +/// going to be removed from the IR before WinEHPrepare. +CallInst *createCallInstWithColors( + FunctionCallee Func, ArrayRef Args, const Twine &NameStr, + Instruction *InsertBefore, + const DenseMap &BlockColors); + +class BundledRetainClaimRVs { +public: + BundledRetainClaimRVs(ARCRuntimeEntryPoints &P, bool ContractPass) + : EP(P), ContractPass(ContractPass) {} + ~BundledRetainClaimRVs(); + + /// Insert a retainRV/claimRV call to the normal destination blocks of invokes + /// annotated with retainRV/claimRV. If the edge to the normal destination + /// block is a critical edge, split it. + std::pair insertAfterInvokes(Function &F, DominatorTree *DT); + + /// Insert a retainRV/claimRV call. + CallInst *insertRVCall(Instruction *InsertPt, CallBase *AnnotatedCall); + + /// Insert a retainRV/claimRV call with colors. + CallInst *insertRVCallWithColors( + Instruction *InsertPt, CallBase *AnnotatedCall, + const DenseMap &BlockColors); + + /// See if an instruction is a bundled retainRV/claimRV call. + bool contains(const Instruction *I) const { + if (auto *CI = dyn_cast(I)) + return RVCalls.count(CI); + return false; + } + + /// Remove a retainRV/claimRV call entirely. + void eraseInst(CallInst *CI) { + auto It = RVCalls.find(CI); + if (It != RVCalls.end()) { + objcarc::removeRetainRVOrClaimRVAttr(It->second, true); + RVCalls.erase(It); + } + EraseInstruction(CI); + } + +private: + /// A map of inserted retainRV/claimRV calls to annotated calls/invokes. + DenseMap RVCalls; + + ARCRuntimeEntryPoints &EP; + bool ContractPass; +}; + } // end namespace objcarc } // end namespace llvm diff --git a/llvm/lib/Transforms/ObjCARC/ObjCARC.cpp b/llvm/lib/Transforms/ObjCARC/ObjCARC.cpp --- a/llvm/lib/Transforms/ObjCARC/ObjCARC.cpp +++ b/llvm/lib/Transforms/ObjCARC/ObjCARC.cpp @@ -14,7 +14,12 @@ #include "ObjCARC.h" #include "llvm-c/Initialization.h" +#include "llvm/Analysis/ObjCARCRVAttr.h" +#include "llvm/IR/IRBuilder.h" +#include "llvm/IR/InlineAsm.h" +#include "llvm/IR/Instructions.h" #include "llvm/InitializePasses.h" +#include "llvm/Transforms/Utils/BasicBlockUtils.h" namespace llvm { class PassRegistry; @@ -37,3 +42,93 @@ void LLVMInitializeObjCARCOpts(LLVMPassRegistryRef R) { initializeObjCARCOpts(*unwrap(R)); } + +CallInst *llvm::objcarc::createCallInstWithColors( + FunctionCallee Func, ArrayRef Args, const Twine &NameStr, + Instruction *InsertBefore, + const DenseMap &BlockColors) { + FunctionType *FTy = Func.getFunctionType(); + Value *Callee = Func.getCallee(); + SmallVector OpBundles; + + if (!BlockColors.empty()) { + const ColorVector &CV = BlockColors.find(InsertBefore->getParent())->second; + assert(CV.size() == 1 && "non-unique color for block!"); + Instruction *EHPad = CV.front()->getFirstNonPHI(); + if (EHPad->isEHPad()) + OpBundles.emplace_back("funclet", EHPad); + } + + return CallInst::Create(FTy, Callee, Args, OpBundles, NameStr, InsertBefore); +} + +std::pair +BundledRetainClaimRVs::insertAfterInvokes(Function &F, DominatorTree *DT) { + bool Changed = false, CFGChanged = false; + + for (BasicBlock &BB : F) { + auto *I = dyn_cast(BB.getTerminator()); + + if (!I) + continue; + + if (!objcarc::hasRetainRVOrClaimRVAttr(I)) + continue; + + BasicBlock *DestBB = I->getNormalDest(); + + if (!DestBB->getSinglePredecessor()) { + assert(I->getSuccessor(0) == DestBB && + "the normal dest is expected to be the first successor"); + DestBB = llvm::SplitCriticalEdge(I, 0, CriticalEdgeSplittingOptions(DT)); + CFGChanged = true; + } + + // We don't have to call insertRVCallWithColors since DestBB is the normal + // destination of the invoke. + insertRVCall(&*DestBB->getFirstInsertionPt(), I); + Changed = true; + } + + return std::make_pair(Changed, CFGChanged); +} + +CallInst *BundledRetainClaimRVs::insertRVCall(Instruction *InsertPt, + CallBase *AnnotatedCall) { + DenseMap BlockColors; + return insertRVCallWithColors(InsertPt, AnnotatedCall, BlockColors); +} + +CallInst *BundledRetainClaimRVs::insertRVCallWithColors( + Instruction *InsertPt, CallBase *AnnotatedCall, + const DenseMap &BlockColors) { + IRBuilder<> Builder(InsertPt); + bool IsRetainRV = objcarc::hasRetainRVAttr(AnnotatedCall); + Function *Func = EP.get(IsRetainRV ? ARCRuntimeEntryPointKind::RetainRV + : ARCRuntimeEntryPointKind::ClaimRV); + Type *ParamTy = Func->getArg(0)->getType(); + Value *CallArg = Builder.CreateBitCast(AnnotatedCall, ParamTy); + auto *Call = + createCallInstWithColors(Func, CallArg, "", InsertPt, BlockColors); + RVCalls[Call] = AnnotatedCall; + if (ContractPass) + objcarc::removeRetainRVOrClaimRVAttr(AnnotatedCall, false); + return Call; +} + +BundledRetainClaimRVs::~BundledRetainClaimRVs() { + if (ContractPass) { + // At this point, we know that the annotated calls can't be tail calls as + // they are followed by marker instructions and retainRV/claimRV calls. Mark + // them as notail, so that the backend knows these calls can't be tail + // calls. + for (auto P : RVCalls) + if (auto *CI = dyn_cast(P.second)) + CI->setTailCallKind(CallInst::TCK_NoTail); + } else { + for (auto P : RVCalls) + EraseInstruction(P.first); + } + + RVCalls.clear(); +} diff --git a/llvm/lib/Transforms/ObjCARC/ObjCARCContract.cpp b/llvm/lib/Transforms/ObjCARC/ObjCARCContract.cpp --- a/llvm/lib/Transforms/ObjCARC/ObjCARCContract.cpp +++ b/llvm/lib/Transforms/ObjCARC/ObjCARCContract.cpp @@ -32,6 +32,7 @@ #include "llvm/ADT/Statistic.h" #include "llvm/Analysis/AliasAnalysis.h" #include "llvm/Analysis/EHPersonalities.h" +#include "llvm/Analysis/ObjCARCRVAttr.h" #include "llvm/IR/Dominators.h" #include "llvm/IR/InlineAsm.h" #include "llvm/IR/InstIterator.h" @@ -63,13 +64,12 @@ class ObjCARCContract { bool Changed; + bool CFGChanged; AAResults *AA; DominatorTree *DT; ProvenanceAnalysis PA; ARCRuntimeEntryPoints EP; - - /// A flag indicating whether this optimization pass should run. - bool Run; + BundledRetainClaimRVs *BundledInsts = nullptr; /// The inline asm string to insert between calls and RetainRV calls to make /// the optimization work on targets which need it. @@ -98,6 +98,7 @@ public: bool init(Module &M); bool run(Function &F, AAResults *AA, DominatorTree *DT); + bool hasCFGChanged() const { return CFGChanged; } }; class ObjCARCContractLegacyPass : public FunctionPass { @@ -304,32 +305,6 @@ return Retain; } -/// Create a call instruction with the correct funclet token. Should be used -/// instead of calling CallInst::Create directly. -static CallInst * -createCallInst(FunctionType *FTy, Value *Func, ArrayRef Args, - const Twine &NameStr, Instruction *InsertBefore, - const DenseMap &BlockColors) { - SmallVector OpBundles; - if (!BlockColors.empty()) { - const ColorVector &CV = BlockColors.find(InsertBefore->getParent())->second; - assert(CV.size() == 1 && "non-unique color for block!"); - Instruction *EHPad = CV.front()->getFirstNonPHI(); - if (EHPad->isEHPad()) - OpBundles.emplace_back("funclet", EHPad); - } - - return CallInst::Create(FTy, Func, Args, OpBundles, NameStr, InsertBefore); -} - -static CallInst * -createCallInst(FunctionCallee Func, ArrayRef Args, const Twine &NameStr, - Instruction *InsertBefore, - const DenseMap &BlockColors) { - return createCallInst(Func.getFunctionType(), Func.getCallee(), Args, NameStr, - InsertBefore, BlockColors); -} - /// Attempt to merge an objc_release with a store, load, and objc_retain to form /// an objc_storeStrong. An objc_storeStrong: /// @@ -411,7 +386,8 @@ if (Args[1]->getType() != I8X) Args[1] = new BitCastInst(Args[1], I8X, "", Store); Function *Decl = EP.get(ARCRuntimeEntryPointKind::StoreStrong); - CallInst *StoreStrong = createCallInst(Decl, Args, "", Store, BlockColors); + CallInst *StoreStrong = + objcarc::createCallInstWithColors(Decl, Args, "", Store, BlockColors); StoreStrong->setDoesNotThrow(); StoreStrong->setDebugLoc(Store->getDebugLoc()); @@ -456,9 +432,14 @@ case ARCInstKind::RetainRV: case ARCInstKind::ClaimRV: { // If we're compiling for a target which needs a special inline-asm - // marker to do the return value optimization, insert it now. + // marker to do the return value optimization and the retainRV/claimRV call + // wasn't bundled with a call, insert the marker now. if (!RVInstMarker) return false; + + if (BundledInsts->contains(Inst)) + return false; + BasicBlock::iterator BBI = Inst->getIterator(); BasicBlock *InstParent = Inst->getParent(); @@ -486,7 +467,7 @@ RVInstMarker->getString(), /*Constraints=*/"", /*hasSideEffects=*/true); - createCallInst(IA, None, "", Inst, BlockColors); + objcarc::createCallInstWithColors(IA, None, "", Inst, BlockColors); } decline_rv_optimization: return false; @@ -534,16 +515,10 @@ //===----------------------------------------------------------------------===// bool ObjCARCContract::init(Module &M) { - // If nothing in the Module uses ARC, don't do anything. - Run = ModuleHasARC(M); - if (!Run) - return false; - EP.init(&M); // Initialize RVInstMarker. - const char *MarkerKey = "clang.arc.retainAutoreleasedReturnValueMarker"; - RVInstMarker = dyn_cast_or_null(M.getModuleFlag(MarkerKey)); + RVInstMarker = getRVInstMarker(M); return false; } @@ -552,14 +527,16 @@ if (!EnableARCOpts) return false; - // If nothing in the Module uses ARC, don't do anything. - if (!Run) - return false; - - Changed = false; + Changed = CFGChanged = false; AA = A; DT = D; PA.setAA(A); + BundledRetainClaimRVs BRV(EP, true); + BundledInsts = &BRV; + + std::pair R = BundledInsts->insertAfterInvokes(F, DT); + Changed |= R.first; + CFGChanged |= R.second; DenseMap BlockColors; if (F.hasPersonalityFn() && @@ -584,6 +561,13 @@ LLVM_DEBUG(dbgs() << "Visiting: " << *Inst << "\n"); + if (auto *CI = dyn_cast(Inst)) + if (objcarc::hasRetainRVOrClaimRVAttr(CI)) { + BundledInsts->insertRVCallWithColors(&*I, CI, BlockColors); + --I; + Changed = true; + } + // First try to peephole Inst. If there is nothing further we can do in // terms of undoing objc-arc-expand, process the next inst. if (tryToPeepholeInstruction(F, Inst, I, TailOkForStoreStrongs, @@ -733,7 +717,6 @@ void ObjCARCContractLegacyPass::getAnalysisUsage(AnalysisUsage &AU) const { AU.addRequired(); AU.addRequired(); - AU.setPreservesCFG(); } Pass *llvm::createObjCARCContractPass() { @@ -757,9 +740,11 @@ bool Changed = OCAC.run(F, &AM.getResult(F), &AM.getResult(F)); + bool CFGChanged = OCAC.hasCFGChanged(); if (Changed) { PreservedAnalyses PA; - PA.preserveSet(); + if (!CFGChanged) + PA.preserveSet(); return PA; } return PreservedAnalyses::all(); diff --git a/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp b/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp --- a/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp +++ b/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp @@ -41,6 +41,7 @@ #include "llvm/Analysis/ObjCARCAliasAnalysis.h" #include "llvm/Analysis/ObjCARCAnalysisUtils.h" #include "llvm/Analysis/ObjCARCInstKind.h" +#include "llvm/Analysis/ObjCARCRVAttr.h" #include "llvm/IR/BasicBlock.h" #include "llvm/IR/CFG.h" #include "llvm/IR/Constant.h" @@ -483,6 +484,7 @@ /// The main ARC optimization pass. class ObjCARCOpt { bool Changed; + bool CFGChanged; ProvenanceAnalysis PA; /// A cache of references to runtime entry point constants. @@ -492,8 +494,7 @@ /// MDKind identifiers. ARCMDKindCache MDKindCache; - /// A flag indicating whether this optimization pass should run. - bool Run; + BundledRetainClaimRVs *BundledInsts = nullptr; /// A flag indicating whether the optimization that removes or moves /// retain/release pairs should be performed. @@ -573,6 +574,7 @@ void init(Module &M); bool run(Function &F, AAResults &AA); void releaseMemory(); + bool hasCFGChanged() const { return CFGChanged; } }; /// The main ARC optimization pass. @@ -610,8 +612,6 @@ void ObjCARCOptLegacyPass::getAnalysisUsage(AnalysisUsage &AU) const { AU.addRequired(); AU.addRequired(); - // ARC optimization doesn't currently split critical edges. - AU.setPreservesCFG(); } /// Turn objc_retainAutoreleasedReturnValue into objc_retain if the operand is @@ -640,6 +640,9 @@ } } + assert(!BundledInsts->contains(RetainRV) && + "a bundled retainRV's argument should be a call"); + // Turn it to a plain objc_retain. Changed = true; ++NumPeeps; @@ -661,6 +664,9 @@ Function &F, DenseMap &BlockColors, Instruction *Inst, const Value *&Arg, ARCInstKind Class, Instruction *AutoreleaseRV, const Value *&AutoreleaseRVArg) { + if (BundledInsts->contains(Inst)) + return false; + // Must be in the same basic block. assert(Inst->getParent() == AutoreleaseRV->getParent()); @@ -844,6 +850,12 @@ for (inst_iterator I = inst_begin(&F), E = inst_end(&F); I != E; ) { Instruction *Inst = &*I++; + if (auto *CI = dyn_cast(Inst)) + if (objcarc::hasRetainRVOrClaimRVAttr(CI)) { + BundledInsts->insertRVCall(&*I, CI); + Changed = true; + } + ARCInstKind Class = GetBasicARCInstKind(Inst); // Skip this loop if this instruction isn't itself an ARC intrinsic. @@ -922,6 +934,11 @@ // We can delete this call if it takes an inert value. SmallPtrSet VisitedPhis; + if (BundledInsts->contains(Inst)) { + UsedInThisFunction |= 1 << unsigned(Class); + return; + } + if (IsNoopOnGlobal(Class)) if (isInertARCValue(Inst->getOperand(0), VisitedPhis)) { if (!Inst->getType()->isVoidTy()) @@ -1542,7 +1559,7 @@ if (Ptr == Arg) continue; // Handled above. TopDownPtrState &S = MI->second; - if (S.HandlePotentialAlterRefCount(Inst, Ptr, PA, Class)) + if (S.HandlePotentialAlterRefCount(Inst, Ptr, PA, Class, *BundledInsts)) continue; S.HandlePotentialUse(Inst, Ptr, PA, Class); @@ -2343,7 +2360,7 @@ ++NumRets; LLVM_DEBUG(dbgs() << "Erasing: " << *Retain << "\nErasing: " << *Autorelease << "\n"); - EraseInstruction(Retain); + BundledInsts->eraseInst(Retain); EraseInstruction(Autorelease); } } @@ -2376,11 +2393,6 @@ if (!EnableARCOpts) return; - // If nothing in the Module uses ARC, don't do anything. - Run = ModuleHasARC(M); - if (!Run) - return; - // Intuitively, objc_retain and others are nocapture, however in practice // they are not, because they return their argument value. And objc_release // calls finalizers which can have arbitrary side effects. @@ -2394,16 +2406,18 @@ if (!EnableARCOpts) return false; - // If nothing in the Module uses ARC, don't do anything. - if (!Run) - return false; - - Changed = false; + Changed = CFGChanged = false; + BundledRetainClaimRVs BRV(EP, false); + BundledInsts = &BRV; LLVM_DEBUG(dbgs() << "<<< ObjCARCOpt: Visiting Function: " << F.getName() << " >>>" "\n"); + std::pair R = BundledInsts->insertAfterInvokes(F, nullptr); + Changed |= R.first; + CFGChanged |= R.second; + PA.setAA(&AA); #ifndef NDEBUG @@ -2468,9 +2482,11 @@ OCAO.init(*F.getParent()); bool Changed = OCAO.run(F, AM.getResult(F)); + bool CFGChanged = OCAO.hasCFGChanged(); if (Changed) { PreservedAnalyses PA; - PA.preserveSet(); + if (!CFGChanged) + PA.preserveSet(); return PA; } return PreservedAnalyses::all(); diff --git a/llvm/lib/Transforms/ObjCARC/PtrState.h b/llvm/lib/Transforms/ObjCARC/PtrState.h --- a/llvm/lib/Transforms/ObjCARC/PtrState.h +++ b/llvm/lib/Transforms/ObjCARC/PtrState.h @@ -31,6 +31,7 @@ namespace objcarc { class ARCMDKindCache; +class BundledRetainClaimRVs; class ProvenanceAnalysis; /// \enum Sequence @@ -202,7 +203,8 @@ ProvenanceAnalysis &PA, ARCInstKind Class); bool HandlePotentialAlterRefCount(Instruction *Inst, const Value *Ptr, - ProvenanceAnalysis &PA, ARCInstKind Class); + ProvenanceAnalysis &PA, ARCInstKind Class, + const BundledRetainClaimRVs &BundledRVs); }; } // end namespace objcarc diff --git a/llvm/lib/Transforms/ObjCARC/PtrState.cpp b/llvm/lib/Transforms/ObjCARC/PtrState.cpp --- a/llvm/lib/Transforms/ObjCARC/PtrState.cpp +++ b/llvm/lib/Transforms/ObjCARC/PtrState.cpp @@ -11,6 +11,7 @@ #include "ObjCARC.h" #include "llvm/Analysis/ObjCARCAnalysisUtils.h" #include "llvm/Analysis/ObjCARCInstKind.h" +#include "llvm/Analysis/ObjCARCRVAttr.h" #include "llvm/IR/BasicBlock.h" #include "llvm/IR/Instruction.h" #include "llvm/IR/Instructions.h" @@ -280,6 +281,12 @@ InsertAfter = skipDebugIntrinsics(InsertAfter); InsertReverseInsertPt(&*InsertAfter); + + // Don't insert anything between a call/invoke annotated with + // retainRV/claimRV and the retainRV/claimRV call that uses the call result. + if (auto *CB = dyn_cast(Inst)) + if (objcarc::hasRetainRVOrClaimRVAttr(CB)) + SetCFGHazardAfflicted(true); }; // Check for possible direct uses. @@ -377,10 +384,9 @@ llvm_unreachable("Sequence unknown enum value"); } -bool TopDownPtrState::HandlePotentialAlterRefCount(Instruction *Inst, - const Value *Ptr, - ProvenanceAnalysis &PA, - ARCInstKind Class) { +bool TopDownPtrState::HandlePotentialAlterRefCount( + Instruction *Inst, const Value *Ptr, ProvenanceAnalysis &PA, + ARCInstKind Class, const BundledRetainClaimRVs &BundledRVs) { // Check for possible releases. Treat clang.arc.use as a releasing instruction // to prevent sinking a retain past it. if (!CanDecrementRefCount(Inst, Ptr, PA, Class) && @@ -396,6 +402,11 @@ assert(!HasReverseInsertPts()); InsertReverseInsertPt(Inst); + // Don't insert anything between a call/invoke annotated with + // retainRV/claimRV and the retainRV/claimRV call that uses the call result. + if (BundledRVs.contains(Inst)) + SetCFGHazardAfflicted(true); + // One call can't cause a transition from S_Retain to S_CanRelease // and S_CanRelease to S_Use. If we've made the first transition, // we're done. diff --git a/llvm/lib/Transforms/Utils/InlineFunction.cpp b/llvm/lib/Transforms/Utils/InlineFunction.cpp --- a/llvm/lib/Transforms/Utils/InlineFunction.cpp +++ b/llvm/lib/Transforms/Utils/InlineFunction.cpp @@ -27,8 +27,9 @@ #include "llvm/Analysis/CaptureTracking.h" #include "llvm/Analysis/EHPersonalities.h" #include "llvm/Analysis/InstructionSimplify.h" +#include "llvm/Analysis/ObjCARCAnalysisUtils.h" +#include "llvm/Analysis/ObjCARCRVAttr.h" #include "llvm/Analysis/ProfileSummaryInfo.h" -#include "llvm/Transforms/Utils/Local.h" #include "llvm/Analysis/ValueTracking.h" #include "llvm/Analysis/VectorUtils.h" #include "llvm/IR/Argument.h" @@ -61,6 +62,7 @@ #include "llvm/Support/ErrorHandling.h" #include "llvm/Transforms/Utils/AssumeBundleBuilder.h" #include "llvm/Transforms/Utils/Cloning.h" +#include "llvm/Transforms/Utils/Local.h" #include "llvm/Transforms/Utils/ValueMapper.h" #include #include @@ -1611,6 +1613,81 @@ } } +static void +insertRetainOrClaimRVCalls(CallBase &CB, + const SmallVectorImpl &Returns) { + Module *Mod = CB.getParent()->getParent()->getParent(); + bool IsRetainRV = objcarc::hasRetainRVAttr(&CB), IsClaimRV = !IsRetainRV; + bool HasRVMarker = objcarc::hasRVMarkerAttr(&CB); + + for (auto *RI : Returns) { + Value *RetOpnd = llvm::objcarc::GetRCIdentityRoot(RI->getOperand(0)); + BasicBlock::reverse_iterator I = ++(RI->getIterator().getReverse()); + BasicBlock::reverse_iterator EI = RI->getParent()->rend(); + bool InsertRetainCall = IsRetainRV; + IRBuilder<> Builder(RI->getContext()); + + // Walk backwards through the basic block looking for either a matching + // autoreleaseRV call or an unannotated call. + for (; I != EI;) { + auto CurI = I++; + + // Ignore casts. + if (isa(*CurI)) + continue; + + if (auto *II = dyn_cast(&*CurI)) { + if (II->getIntrinsicID() == Intrinsic::objc_autoreleaseReturnValue && + II->hasNUses(0) && + llvm::objcarc::GetRCIdentityRoot(II->getOperand(0)) == RetOpnd) { + // If we've found a matching authoreleaseRV call: + // - If the call is annotated with claimRV, insert a call to + // objc_release and erase the autoreleaseRV call. + // - If the call is annotated with retainRV, just erase the + // autoreleaseRV call. + if (IsClaimRV) { + Builder.SetInsertPoint(II); + Function *IFn = + Intrinsic::getDeclaration(Mod, Intrinsic::objc_release); + Value *BC = + Builder.CreateBitCast(RetOpnd, IFn->getArg(0)->getType()); + Builder.CreateCall(IFn, BC, ""); + } + II->eraseFromParent(); + InsertRetainCall = false; + } + } else if (auto *CI = dyn_cast(&*CurI)) { + if (llvm::objcarc::GetRCIdentityRoot(CI) == RetOpnd && + !objcarc::hasRetainRVOrClaimRVAttr(CI)) { + // If we've found an unannotated call that defines RetOpnd, annotate + // the call with the attributes. + llvm::AttributeList AL = CI->getAttributes(); + AL = AL.addAttribute(CI->getContext(), AttributeList::ReturnIndex, + objcarc::getRVAttrKeyStr(), + objcarc::getRVAttrValStr(IsRetainRV)); + if (HasRVMarker) + AL = AL.addAttribute(CI->getContext(), AttributeList::ReturnIndex, + objcarc::getRVMarkerAttrStr()); + CI->setAttributes(AL); + InsertRetainCall = false; + } + } + + break; + } + + if (InsertRetainCall) { + // The call is annotated with retainRV and we've failed to find a matching + // autoreleaseRV or an annotated call in the callee. Emit a call to + // objc_retain. + Builder.SetInsertPoint(RI); + Function *IFn = Intrinsic::getDeclaration(Mod, Intrinsic::objc_retain); + Value *BC = Builder.CreateBitCast(RetOpnd, IFn->getArg(0)->getType()); + Builder.CreateCall(IFn, BC, ""); + } + } +} + /// This function inlines the called function into the basic block of the /// caller. This returns false if it is not possible to inline this call. /// The program is still in a well defined state if this occurs though. @@ -1806,6 +1883,10 @@ // Remember the first block that is newly cloned over. FirstNewBlock = LastBlock; ++FirstNewBlock; + // Insert retainRV/clainRV runtime calls. + if (objcarc::hasRetainRVOrClaimRVAttr(&CB)) + insertRetainOrClaimRVCalls(CB, Returns); + if (IFI.CallerBFI != nullptr && IFI.CalleeBFI != nullptr) // Update the BFI of blocks cloned into the caller. updateCallerBFI(OrigBB, VMap, IFI.CallerBFI, IFI.CalleeBFI, diff --git a/llvm/test/CodeGen/AArch64/call-rv-marker.ll b/llvm/test/CodeGen/AArch64/call-rv-marker.ll --- a/llvm/test/CodeGen/AArch64/call-rv-marker.ll +++ b/llvm/test/CodeGen/AArch64/call-rv-marker.ll @@ -33,7 +33,7 @@ ; GISEL-NOT: mov x29, x29 ; entry: - %call = call "rv_marker" i8* @foo1() + %call = call "clang.arc.rv_marker" i8* @foo1() ret i8* %call } @@ -49,7 +49,7 @@ entry: %tobool.not = icmp eq i32 %c, 0 %.sink = select i1 %tobool.not, i32 2, i32 1 - %call1 = call "rv_marker" i8* @foo0(i32 %.sink) + %call1 = call "clang.arc.rv_marker" i8* @foo0(i32 %.sink) tail call void @foo2(i8* %call1) ret void } @@ -61,7 +61,7 @@ ; SELDAG-NEXT: mov x29, x29 ; entry: - %call = call "rv_marker" i8* @foo1() + %call = call "clang.arc.rv_marker" i8* @foo1() invoke void @objc_object(i8* %call) #5 to label %invoke.cont unwind label %lpad @@ -87,7 +87,7 @@ %s = alloca %struct.S, align 1 %0 = getelementptr inbounds %struct.S, %struct.S* %s, i64 0, i32 0 call void @llvm.lifetime.start.p0i8(i64 1, i8* nonnull %0) #2 - %call = invoke "rv_marker" i8* @foo1() + %call = invoke "clang.arc.rv_marker" i8* @foo1() to label %invoke.cont unwind label %lpad invoke.cont: ; preds = %entry @@ -127,7 +127,7 @@ ; entry: %0 = load i8* ()*, i8* ()** @fptr, align 8 - %call = call "rv_marker" i8* %0() + %call = call "clang.arc.rv_marker" i8* %0() tail call void @foo2(i8* %call) ret i8* %call } @@ -142,7 +142,7 @@ ; CHECK-NEXT: bl foo ; SELDAG-NEXT: mov x29, x29 ; GISEL-NOT: mov x29, x29 - call "rv_marker" void @foo(i64 %c, i64 %b, i64 %a) + call "clang.arc.rv_marker" void @foo(i64 %c, i64 %b, i64 %a) ret void } diff --git a/llvm/test/Transforms/DeadArgElim/deadretval.ll b/llvm/test/Transforms/DeadArgElim/deadretval.ll --- a/llvm/test/Transforms/DeadArgElim/deadretval.ll +++ b/llvm/test/Transforms/DeadArgElim/deadretval.ll @@ -1,4 +1,8 @@ -; RUN: opt < %s -deadargelim -S | not grep DEAD +; RUN: opt < %s -deadargelim -S | FileCheck %s + +@g0 = global i8 0, align 8 + +; CHECK-NOT: DEAD ; Dead arg only used by dead retval define internal i32 @test(i32 %DEADARG) { @@ -16,3 +20,19 @@ ret i32 %Y } +; The callee function's return type shouldn't be changed if there is a call +; annotated with "clang.arc.rv"="retain" or "clang.arc.rv"="claim". + +; CHECK-LABEL: define internal i8* @callee4() + +define internal i8* @callee4(i8* %a0) { + ret i8* @g0; +} + +; CHECK-LABEL: define i8* @test4( +; CHECK: tail call "clang.arc.rv"="retain" i8* @callee4() + +define i8* @test4() { + %call = tail call "clang.arc.rv"="retain" i8* @callee4(i8* @g0) + ret i8* @g0 +} diff --git a/llvm/test/Transforms/Inline/inline-retainRV-call.ll b/llvm/test/Transforms/Inline/inline-retainRV-call.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Transforms/Inline/inline-retainRV-call.ll @@ -0,0 +1,175 @@ +; RUN: opt < %s -inline -S | FileCheck %s + +@g0 = global i8* null, align 8 +declare i8* @foo0() + +define i8* @callee0_autoreleaseRV() { + %call = call "clang.arc.rv"="retain" i8* @foo0() + %1 = tail call i8* @llvm.objc.autoreleaseReturnValue(i8* %call) + ret i8* %call +} + +; CHECK-LABEL: define void @test0_autoreleaseRV( +; CHECK: call "clang.arc.rv"="retain" i8* @foo0() + +define void @test0_autoreleaseRV() { + %call = call "clang.arc.rv"="retain" "clang.arc.rv_marker" i8* @callee0_autoreleaseRV() + ret void +} + +; CHECK-LABEL: define void @test0_claimRV_autoreleaseRV( +; CHECK: %[[CALL:.*]] = call "clang.arc.rv"="retain" i8* @foo0() +; CHECK: call void @llvm.objc.release(i8* %[[CALL]]) +; CHECK-NEXT: ret void + +define void @test0_claimRV_autoreleaseRV() { + %call = call "clang.arc.rv"="claim" "clang.arc.rv_marker" i8* @callee0_autoreleaseRV() + ret void +} + +; CHECK-LABEL: define void @test1_autoreleaseRV( +; CHECK: invoke "clang.arc.rv"="retain" i8* @foo0() + +define void @test1_autoreleaseRV() personality i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8*) { +entry: + %call = invoke "clang.arc.rv"="retain" "clang.arc.rv_marker" i8* @callee0_autoreleaseRV() + to label %invoke.cont unwind label %lpad + +invoke.cont: + ret void + +lpad: + %0 = landingpad { i8*, i32 } + cleanup + resume { i8*, i32 } undef +} + +; CHECK-LABEL: define void @test1_claimRV_autoreleaseRV( +; CHECK: %[[INVOKE:.*]] = invoke "clang.arc.rv"="retain" i8* @foo0() +; CHECK: call void @llvm.objc.release(i8* %[[INVOKE]]) +; CHECK-NEXT: br + +define void @test1_claimRV_autoreleaseRV() personality i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8*) { +entry: + %call = invoke "clang.arc.rv"="claim" "clang.arc.rv_marker" i8* @callee0_autoreleaseRV() + to label %invoke.cont unwind label %lpad + +invoke.cont: + ret void + +lpad: + %0 = landingpad { i8*, i32 } + cleanup + resume { i8*, i32 } undef +} + +define i8* @callee1_no_autoreleaseRV() { + %call = call i8* @foo0() + ret i8* %call +} + +; CHECK-LABEL: define void @test2_no_autoreleaseRV( +; CHECK: call "clang.arc.rv"="retain" "clang.arc.rv_marker" i8* @foo0() +; CHECK-NEXT: ret void + +define void @test2_no_autoreleaseRV() { + %call = call "clang.arc.rv"="retain" "clang.arc.rv_marker" i8* @callee1_no_autoreleaseRV() + ret void +} + +; CHECK-LABEL: define void @test2_claimRV_no_autoreleaseRV( +; CHECK: call "clang.arc.rv"="claim" "clang.arc.rv_marker" i8* @foo0() +; CHECK-NEXT: ret void + +define void @test2_claimRV_no_autoreleaseRV() { + %call = call "clang.arc.rv"="claim" "clang.arc.rv_marker" i8* @callee1_no_autoreleaseRV() + ret void +} + +; CHECK-LABEL: define void @test3_no_autoreleaseRV( +; CHECK: invoke "clang.arc.rv"="retain" "clang.arc.rv_marker" i8* @foo0() + +define void @test3_no_autoreleaseRV() personality i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8*) { +entry: + %call = invoke "clang.arc.rv"="retain" "clang.arc.rv_marker" i8* @callee1_no_autoreleaseRV() + to label %invoke.cont unwind label %lpad + +invoke.cont: + ret void + +lpad: + %0 = landingpad { i8*, i32 } + cleanup + resume { i8*, i32 } undef +} + +define i8* @callee2_nocall() { + %1 = load i8*, i8** @g0, align 8 + ret i8* %1 +} + +; Check that a call to @llvm.objc.retain is inserted if there is no matching +; autoreleaseRV call or a call. + +; CHECK-LABEL: define void @test4_nocall( +; CHECK: %[[V0:.*]] = load i8*, i8** @g0, +; CHECK-NEXT: call i8* @llvm.objc.retain(i8* %[[V0]]) +; CHECK-NEXT: ret void + +define void @test4_nocall() { + %call = call "clang.arc.rv"="retain" "clang.arc.rv_marker" i8* @callee2_nocall() + ret void +} + +; CHECK-LABEL: define void @test4_claimRV_nocall( +; CHECK: %[[V0:.*]] = load i8*, i8** @g0, +; CHECK-NEXT: ret void + +define void @test4_claimRV_nocall() { + %call = call "clang.arc.rv"="claim" "clang.arc.rv_marker" i8* @callee2_nocall() + ret void +} + +; Check that a call to @llvm.objc.retain is inserted if call to @foo already has +; the attribute. I'm not sure this will happen in practice. + +define i8* @callee3_marker() { + %1 = call "clang.arc.rv"="retain" "clang.arc.rv_marker" i8* @foo0() + ret i8* %1 +} + +; CHECK-LABEL: define void @test5( +; CHECK: %[[V0:.*]] = call "clang.arc.rv"="retain" "clang.arc.rv_marker" i8* @foo0() +; CHECK-NEXT: call i8* @llvm.objc.retain(i8* %[[V0]]) +; CHECK-NEXT: ret void + +define void @test5() { + %call = call "clang.arc.rv"="retain" "clang.arc.rv_marker" i8* @callee3_marker() + ret void +} + +; Don't pair up an autoreleaseRV in the callee and an retainRV in the caller +; if there is an instruction between the ret instruction and the call to +; autoreleaseRV that isn't a cast instruction. + +define i8* @callee0_autoreleaseRV2() { + %call = call "clang.arc.rv"="retain" "clang.arc.rv_marker" i8* @foo0() + %1 = tail call i8* @llvm.objc.autoreleaseReturnValue(i8* %call) + store i8* null, i8** @g0 + ret i8* %call +} + +; CHECK-LABEL: define void @test6( +; CHECK: %[[V0:.*]] = call "clang.arc.rv"="retain" "clang.arc.rv_marker" i8* @foo0() +; CHECK: call i8* @llvm.objc.autoreleaseReturnValue(i8* %[[V0]]) +; CHECK: store i8* null, i8** @g0, align 8 +; CHECK: call i8* @llvm.objc.retain(i8* %[[V0]]) +; CHECK-NEXT: ret void + +define void @test6() { + %call = call "clang.arc.rv"="retain" "clang.arc.rv_marker" i8* @callee0_autoreleaseRV2() + ret void +} + +declare i8* @llvm.objc.autoreleaseReturnValue(i8*) +declare i32 @__gxx_personality_v0(...) diff --git a/llvm/test/Transforms/ObjCARC/contract-marker-funclet.ll b/llvm/test/Transforms/ObjCARC/contract-marker-funclet.ll --- a/llvm/test/Transforms/ObjCARC/contract-marker-funclet.ll +++ b/llvm/test/Transforms/ObjCARC/contract-marker-funclet.ll @@ -10,6 +10,16 @@ ; } ; } +; CHECK-LABEL: define void @"\01?g@@YAXXZ"() +; CHECK-LABEL: catch +; CHECK: call void asm sideeffect "movl{{.*}}%ebp, %ebp{{.*}}", ""() [ "funclet"(token %1) ] + +; CHECK-LABEL: catch.1 +; CHECK: call void asm sideeffect "movl{{.*}}%ebp, %ebp{{.*}}", ""() [ "funclet"(token %1) ] + +; CHECK-LABEL: invoke.cont +; CHECK: call void asm sideeffect "movl{{.*}}%ebp, %ebp{{.*}}", ""(){{$}} + define void @"\01?g@@YAXXZ"() personality i8* bitcast (i32 (...)* @__CxxFrameHandler3 to i8*) { entry: %call = invoke i8* @"\01?f@@YAPAUobjc_object@@XZ"() @@ -40,23 +50,41 @@ ret void } +; CHECK-LABEL: define dso_local void @"?test_attr_claimRV@@YAXXZ"() +; CHECK: %[[CALL4:.*]] = notail call "clang.arc.rv_marker" i8* @"?noexcept_func@@YAPAUobjc_object@@XZ"() +; CHECK: call i8* @llvm.objc.unsafeClaimAutoreleasedReturnValue(i8* %[[CALL4]]) + +; CHECK: %[[V1:.*]] = cleanuppad +; CHECK: %[[CALL:.*]] = notail call "clang.arc.rv_marker" i8* @"?noexcept_func@@YAPAUobjc_object@@XZ"() [ "funclet"(token %[[V1]]) ] +; CHECK: call i8* @llvm.objc.unsafeClaimAutoreleasedReturnValue(i8* %[[CALL]]) [ "funclet"(token %[[V1]]) ] + +define dso_local void @"?test_attr_claimRV@@YAXXZ"() local_unnamed_addr #0 personality i8* bitcast (i32 (...)* @__CxxFrameHandler3 to i8*) { +entry: + invoke void @"?foo@@YAXXZ"() + to label %invoke.cont unwind label %ehcleanup + +invoke.cont: ; preds = %entry + %call.i4 = tail call "clang.arc.rv"="claim" "clang.arc.rv_marker" i8* @"?noexcept_func@@YAPAUobjc_object@@XZ"() #2 + ret void + +ehcleanup: ; preds = %entry + %0 = cleanuppad within none [] + %call.i = call "clang.arc.rv"="claim" "clang.arc.rv_marker" i8* @"?noexcept_func@@YAPAUobjc_object@@XZ"() #2 [ "funclet"(token %0) ] + cleanupret from %0 unwind to caller +} + declare i8* @"\01?f@@YAPAUobjc_object@@XZ"() declare i32 @__CxxFrameHandler3(...) +declare void @"?foo@@YAXXZ"() +declare i8* @"?noexcept_func@@YAPAUobjc_object@@XZ"() + declare dllimport i8* @llvm.objc.retainAutoreleasedReturnValue(i8*) +declare i8* @llvm.objc.unsafeClaimAutoreleasedReturnValue(i8*) declare dllimport void @llvm.objc.release(i8*) !llvm.module.flags = !{!0} !0 = !{i32 1, !"clang.arc.retainAutoreleasedReturnValueMarker", !"movl\09%ebp, %ebp\09\09// marker for objc_retainAutoreleaseReturnValue"} - -; CHECK-LABEL: catch -; CHECK: call void asm sideeffect "movl{{.*}}%ebp, %ebp{{.*}}", ""() [ "funclet"(token %1) ] - -; CHECK-LABEL: catch.1 -; CHECK: call void asm sideeffect "movl{{.*}}%ebp, %ebp{{.*}}", ""() [ "funclet"(token %1) ] - -; CHECK-LABEL: invoke.cont -; CHECK: call void asm sideeffect "movl{{.*}}%ebp, %ebp{{.*}}", ""(){{$}} diff --git a/llvm/test/Transforms/ObjCARC/contract-rv-attr.ll b/llvm/test/Transforms/ObjCARC/contract-rv-attr.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Transforms/ObjCARC/contract-rv-attr.ll @@ -0,0 +1,63 @@ +; RUN: opt -objc-arc-contract -S < %s | FileCheck %s +; RUN: opt -passes=objc-arc-contract -S < %s | FileCheck %s + +; CHECK-LABEL: define void @test0() { +; CHECK: %[[CALL:.*]] = notail call "clang.arc.rv_marker" i8* @foo() +; CHECK: call i8* @llvm.objc.retainAutoreleasedReturnValue(i8* %[[CALL]]) + +define void @test0() { + %call1 = call "clang.arc.rv"="retain" "clang.arc.rv_marker" i8* @foo() + ret void +} + +; CHECK-LABEL: define void @test1() { +; CHECK: %[[CALL:.*]] = notail call "clang.arc.rv_marker" i8* @foo() +; CHECK: call i8* @llvm.objc.unsafeClaimAutoreleasedReturnValue(i8* %[[CALL]]) + +define void @test1() { + %call1 = call "clang.arc.rv"="claim" "clang.arc.rv_marker" i8* @foo() + ret void +} + +; CHECK-LABEL:define i8* @test2( +; CHECK: %[[CALL1:.*]] = invoke "clang.arc.rv_marker" i8* @foo() + +; CHECK: %[[V0:.*]] = call i8* @llvm.objc.retainAutoreleasedReturnValue(i8* %[[CALL1]]) +; CHECK-NEXT: br + +; CHECK: %[[CALL3:.*]] = invoke "clang.arc.rv_marker" i8* @foo() + +; CHECK: %[[V2:.*]] = call i8* @llvm.objc.retainAutoreleasedReturnValue(i8* %[[CALL3]]) +; CHECK-NEXT: br + +; CHECK: %[[RETVAL:.*]] = phi i8* [ %[[V0]], {{.*}} ], [ %[[V2]], {{.*}} ] +; CHECK: ret i8* %[[RETVAL]] + +define i8* @test2(i1 zeroext %b) personality i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8*) { +entry: + br i1 %b, label %if.then, label %if.end + +if.then: + %call1 = invoke "clang.arc.rv"="retain" "clang.arc.rv_marker" i8* @foo() + to label %cleanup unwind label %lpad + +lpad: + %0 = landingpad { i8*, i32 } + cleanup + resume { i8*, i32 } undef + +if.end: + %call3 = invoke "clang.arc.rv"="retain" "clang.arc.rv_marker" i8* @foo() + to label %cleanup unwind label %lpad + +cleanup: + %retval.0 = phi i8* [ %call1, %if.then ], [ %call3, %if.end ] + ret i8* %retval.0 +} + +declare i8* @foo() +declare i32 @__gxx_personality_v0(...) + +!llvm.module.flags = !{!0} + +!0 = !{i32 1, !"clang.arc.retainAutoreleasedReturnValueMarker", !"mov\09fp, fp\09\09// marker for objc_retainAutoreleaseReturnValue"} diff --git a/llvm/test/Transforms/ObjCARC/rv.ll b/llvm/test/Transforms/ObjCARC/rv.ll --- a/llvm/test/Transforms/ObjCARC/rv.ll +++ b/llvm/test/Transforms/ObjCARC/rv.ll @@ -452,6 +452,28 @@ ret i8* %v3 } +; Remove attributes and the autoreleaseRV call if the call is a tail call. + +; CHECK-LABEL: define i8* @test31( +; CHECK: %[[CALL:.*]] = tail call i8* @returner() +; CHECK: ret i8* %[[CALL]] + +define i8* @test31() { + %call = tail call "clang.arc.rv"="retain" "clang.arc.rv_marker" i8* @returner() + %1 = call i8* @llvm.objc.autoreleaseReturnValue(i8* %call) + ret i8* %1 +} + +; CHECK-LABEL: define i8* @test32( +; CHECK: %[[CALL:.*]] = call "clang.arc.rv"="retain" "clang.arc.rv_marker" i8* @returner() +; CHECK: call i8* @llvm.objc.autoreleaseReturnValue(i8* %[[CALL]]) + +define i8* @test32() { + %call = call "clang.arc.rv"="retain" "clang.arc.rv_marker" i8* @returner() + %1 = call i8* @llvm.objc.autoreleaseReturnValue(i8* %call) + ret i8* %1 +} + !0 = !{} ; CHECK: attributes [[NUW]] = { nounwind }