-
Notifications
You must be signed in to change notification settings - Fork 11.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[ObjC][ARC] Use operand bundle 'clang.arc.rv' instead of explicitly
emitting retainRV or claimRV calls in the IR Background: This patch makes changes to the front-end and middle-end that are needed to fix a longstanding problem where llvm breaks ARC's autorelease optimization (see the link below) by separating calls from the marker instructions or retainRV/claimRV calls. The backend changes are in https://reviews.llvm.org/D92569. https://clang.llvm.org/docs/AutomaticReferenceCounting.html#arc-runtime-objc-autoreleasereturnvalue What this patch does to fix the problem: - The front-end adds operand bundle "clang.arc.rv" to calls, which indicates the call is implicitly followed by a marker instruction and an implicit retainRV/claimRV call that consumes the call result. In addition, it emits a call to @llvm.objc.clang.arc.noop.use, which consumes the call result, to prevent the middle-end passes from changing the return type of the called function. This is currently done only when the target is arm64 and the optimization level is higher than -O0. - ARC optimizer temporarily emits retainRV/claimRV calls after the calls with the operand bundle in the IR and removes the inserted calls after processing the function. - ARC contract pass emits retainRV/claimRV calls after the call with the operand bundle. It doesn't remove the operand bundle on the call since the backend needs it to emit the marker instruction. The retainRV and claimRV calls are emitted late in the pipeline to prevent optimization passes from transforming the IR in a way that makes it harder for the ARC middle-end passes to figure out the def-use relationship between the call and the retainRV/claimRV calls (which is the cause of PR31925). - The function inliner removes an autoreleaseRV call in the callee if nothing in the callee prevents it from being paired up with the retainRV/claimRV call in the caller. It then inserts a release call if the call is annotated with claimRV since autoreleaseRV+claimRV is equivalent to a release. If it cannot find an autoreleaseRV call, it tries to transfer the operand bundle to a function call in the callee. This is important since ARC optimizer can remove the autoreleaseRV returning the callee result, which makes it impossible to pair it up with the retainRV/claimRV call in the caller. If that fails, it simply emits a retain call in the IR if the implicit call is a call to retainRV and does nothing if it's a call to claimRV. Future work: - Use the operand bundle on x86-64. - Fix the auto upgrader to convert call+retainRV/claimRV pairs into calls annotated with the operand bundles. rdar://71443534 Differential Revision: https://reviews.llvm.org/D92808
- Loading branch information
Showing
35 changed files
with
1,084 additions
and
123 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
// 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 [[A:.*]]* @makeA() [ "clang.arc.rv"(i64 1) ] | ||
// CHECK-NEXT: call void (...) @llvm.objc.clang.arc.noop.use({{.*}} [[T0]]) | ||
// 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 [[A]]* @makeA() [ "clang.arc.rv"(i64 1) ] | ||
// CHECK-NEXT: call void (...) @llvm.objc.clang.arc.noop.use({{.*}} [[T0]]) | ||
// 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 [[A]]* @makeA() [ "clang.arc.rv"(i64 0) ] | ||
// CHECK-NEXT: call void (...) @llvm.objc.clang.arc.noop.use({{.*}} [[T0]]) | ||
// 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 [[A]]* @makeA() [ "clang.arc.rv"(i64 0) ] | ||
// CHECK-NEXT: call void (...) @llvm.objc.clang.arc.noop.use({{.*}} [[T0]]) | ||
// 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 [[A]]* @makeA() [ "clang.arc.rv"(i64 1) ] | ||
// CHECK-NEXT: call void (...) @llvm.objc.clang.arc.noop.use({{.*}} [[T0]]) | ||
// 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 [[A]]* @makeA() [ "clang.arc.rv"(i64 1) ] | ||
// CHECK-NEXT: call void (...) @llvm.objc.clang.arc.noop.use({{.*}} [[T0]]) | ||
// 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 [[A]]* @makeA() [ "clang.arc.rv"(i64 0) ] | ||
// CHECK-NEXT: call void (...) @llvm.objc.clang.arc.noop.use({{.*}} [[T0]]) | ||
// 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 [[A]]* @makeA() [ "clang.arc.rv"(i64 0) ] | ||
// CHECK-NEXT: call void (...) @llvm.objc.clang.arc.noop.use({{.*}} [[T0]]) | ||
// 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 [[A]]* @makeA() [ "clang.arc.rv"(i64 1) ] | ||
// CHECK-NEXT: call void (...) @llvm.objc.clang.arc.noop.use({{.*}} [[T0]]) | ||
// CHECK-NEXT: ret void | ||
|
||
void test_cast_to_void() { | ||
(void) makeA(); | ||
} | ||
// CHECK-LABEL: define{{.*}} void @test_cast_to_void() | ||
// CHECK: [[T0:%.*]] = call [[A]]* @makeA() [ "clang.arc.rv"(i64 1) ] | ||
// CHECK-NEXT: call void (...) @llvm.objc.clang.arc.noop.use({{.*}} [[T0]]) | ||
// 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"} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
//===- ObjCARCUtil.h - ObjC ARC Utility Functions ---------------*- 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 ARC utility functions which are used by various parts of | ||
/// the compiler. | ||
/// | ||
//===----------------------------------------------------------------------===// | ||
|
||
#ifndef LLVM_LIB_ANALYSIS_OBJCARCUTIL_H | ||
#define LLVM_LIB_ANALYSIS_OBJCARCUTIL_H | ||
|
||
#include "llvm/IR/InstrTypes.h" | ||
#include "llvm/IR/LLVMContext.h" | ||
|
||
namespace llvm { | ||
namespace objcarc { | ||
|
||
static inline const char *getRVMarkerModuleFlagStr() { | ||
return "clang.arc.retainAutoreleasedReturnValueMarker"; | ||
} | ||
|
||
enum RVOperandBundle : unsigned { RVOB_Retain, RVOB_Claim }; | ||
|
||
static RVOperandBundle getRVOperandBundleEnum(bool IsRetain) { | ||
return IsRetain ? RVOB_Retain : RVOB_Claim; | ||
} | ||
|
||
static inline bool hasRVOpBundle(const CallBase *CB, bool IsRetain) { | ||
auto B = CB->getOperandBundle(LLVMContext::OB_clang_arc_rv); | ||
if (!B.hasValue()) | ||
return false; | ||
return cast<ConstantInt>(B->Inputs[0])->getZExtValue() == | ||
getRVOperandBundleEnum(IsRetain); | ||
} | ||
|
||
static inline bool hasRVOpBundle(const CallBase *CB) { | ||
return CB->getOperandBundle(LLVMContext::OB_clang_arc_rv).hasValue(); | ||
} | ||
|
||
} // end namespace objcarc | ||
} // end namespace llvm | ||
|
||
#endif |
Oops, something went wrong.