Skip to content

Commit 32da4f9

Browse files
author
Joe Shajrawi
committed
[Exclusivity] Sink may release release instructions out of access scopes
General case: begin_access A ... strong_release / release_value / destroy end_access The release instruction can be sunk below the end_access instruction, This extends the lifetime of the released value, but, might allow us to Mark the access scope as no nested conflict.
1 parent 22d411c commit 32da4f9

File tree

5 files changed

+349
-0
lines changed

5 files changed

+349
-0
lines changed

include/swift/SILOptimizer/PassManager/Passes.def

+2
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ PASS(AccessEnforcementDom, "access-enforcement-dom",
5858
"Remove dominated access checks with no nested conflict")
5959
PASS(AccessEnforcementOpts, "access-enforcement-opts",
6060
"Access Enforcement Optimization")
61+
PASS(AccessEnforcementReleaseSinking, "access-enforcement-release",
62+
"Access Enforcement Release Sinking")
6163
PASS(AccessEnforcementSelection, "access-enforcement-selection",
6264
"Access Enforcement Selection")
6365
PASS(AccessEnforcementWMO, "access-enforcement-wmo",

lib/SILOptimizer/PassManager/PassPipeline.cpp

+5
Original file line numberDiff line numberDiff line change
@@ -195,13 +195,15 @@ void addHighLevelLoopOptPasses(SILPassPipelinePlan &P) {
195195
P.addSimplifyCFG();
196196
// Optimize access markers for better LICM: might merge accesses
197197
// It will also set the no_nested_conflict for dynamic accesses
198+
P.addAccessEnforcementReleaseSinking();
198199
P.addAccessEnforcementOpts();
199200
P.addHighLevelLICM();
200201
// Simplify CFG after LICM that creates new exit blocks
201202
P.addSimplifyCFG();
202203
// LICM might have added new merging potential by hoisting
203204
// we don't want to restart the pipeline - ignore the
204205
// potential of merging out of two loops
206+
P.addAccessEnforcementReleaseSinking();
205207
P.addAccessEnforcementOpts();
206208
// Start of loop unrolling passes.
207209
P.addArrayCountPropagation();
@@ -465,13 +467,15 @@ static void addLateLoopOptPassPipeline(SILPassPipelinePlan &P) {
465467
P.addCodeSinking();
466468
// Optimize access markers for better LICM: might merge accesses
467469
// It will also set the no_nested_conflict for dynamic accesses
470+
P.addAccessEnforcementReleaseSinking();
468471
P.addAccessEnforcementOpts();
469472
P.addLICM();
470473
// Simplify CFG after LICM that creates new exit blocks
471474
P.addSimplifyCFG();
472475
// LICM might have added new merging potential by hoisting
473476
// we don't want to restart the pipeline - ignore the
474477
// potential of merging out of two loops
478+
P.addAccessEnforcementReleaseSinking();
475479
P.addAccessEnforcementOpts();
476480

477481
// Optimize overflow checks.
@@ -494,6 +498,7 @@ static void addLateLoopOptPassPipeline(SILPassPipelinePlan &P) {
494498
// - don't require IRGen information.
495499
static void addLastChanceOptPassPipeline(SILPassPipelinePlan &P) {
496500
// Optimize access markers for improved IRGen after all other optimizations.
501+
P.addAccessEnforcementReleaseSinking();
497502
P.addAccessEnforcementOpts();
498503
P.addAccessEnforcementWMO();
499504
P.addAccessEnforcementDom();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
//===--- AccessEnforcementReleaseSinking.cpp - release sinking opt ---===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
///
13+
/// This function pass sinks releases out of access scopes.
14+
///
15+
/// General case:
16+
/// begin_access A
17+
/// ...
18+
/// strong_release / release_value / destroy
19+
/// end_access
20+
///
21+
/// The release instruction can be sunk below the end_access instruction,
22+
/// This extends the lifetime of the released value, but, might allow us to
23+
/// Mark the access scope as no nested conflict.
24+
//===----------------------------------------------------------------------===//
25+
26+
#define DEBUG_TYPE "access-enforcement-release"
27+
28+
#include "swift/SIL/ApplySite.h"
29+
#include "swift/SIL/DebugUtils.h"
30+
#include "swift/SIL/SILFunction.h"
31+
#include "swift/SILOptimizer/PassManager/Transforms.h"
32+
33+
using namespace swift;
34+
35+
// Returns a bool: If this is a "sinkable" instruction type for this opt
36+
static bool isSinkable(SILInstruction &inst) {
37+
switch (inst.getKind()) {
38+
default:
39+
return false;
40+
case SILInstructionKind::DestroyValueInst:
41+
case SILInstructionKind::ReleaseValueInst:
42+
case SILInstructionKind::ReleaseValueAddrInst:
43+
case SILInstructionKind::StrongReleaseInst:
44+
case SILInstructionKind::UnmanagedReleaseValueInst:
45+
return true;
46+
}
47+
}
48+
49+
// Returns a bool: If this is a "barrier" instruction for this opt
50+
static bool isBarrier(SILInstruction &inst) {
51+
switch (inst.getKind()) {
52+
default:
53+
return FullApplySite::isa(&inst) != FullApplySite();
54+
case SILInstructionKind::BeginAccessInst:
55+
case SILInstructionKind::BeginUnpairedAccessInst:
56+
case SILInstructionKind::IsUniqueInst:
57+
return true;
58+
}
59+
}
60+
61+
// Processes a block bottom-up, keeping a lookout for end_access instructions
62+
// If we encounter a "barrier" we clear out the current end_access
63+
// If we encounter a "release", and we have a current end_access, we sink it
64+
static void processBlock(SILBasicBlock &block) {
65+
EndAccessInst *bottomEndAccessInst = nullptr;
66+
for (auto reverseIt = block.rbegin(); reverseIt != block.rend();
67+
++reverseIt) {
68+
SILInstruction &currIns = *reverseIt;
69+
if (auto *currEAI = dyn_cast<EndAccessInst>(&currIns)) {
70+
if (!bottomEndAccessInst) {
71+
bottomEndAccessInst = currEAI;
72+
}
73+
} else if (isBarrier(currIns)) {
74+
LLVM_DEBUG(llvm::dbgs() << "Found a barrier " << currIns
75+
<< ", clearing last seen end_access\n");
76+
bottomEndAccessInst = nullptr;
77+
} else if (isSinkable(currIns)) {
78+
LLVM_DEBUG(llvm::dbgs()
79+
<< "Found a sinkable instruction " << currIns << "\n");
80+
if (!bottomEndAccessInst) {
81+
LLVM_DEBUG(
82+
llvm::dbgs()
83+
<< "Cannot be sunk: no open barrier-less end_access found\n");
84+
continue;
85+
}
86+
LLVM_DEBUG(llvm::dbgs() << "Moving sinkable instruction below "
87+
<< *bottomEndAccessInst << "\n");
88+
// We need to avoid iterator invalidation:
89+
// We know this is not the last instruction of the block:
90+
// 1) not a TermInst
91+
// 2) bottomEndAccessInst != nil
92+
assert(reverseIt != block.rbegin() &&
93+
"Did not expect a sinkable instruction at block's end");
94+
// Go back to previous iteration
95+
auto prevIt = reverseIt;
96+
--prevIt;
97+
// Move the instruction after the end_access
98+
currIns.moveAfter(bottomEndAccessInst);
99+
// make reverseIt into a valid iterator again
100+
reverseIt = prevIt;
101+
}
102+
}
103+
}
104+
105+
namespace {
106+
struct AccessEnforcementReleaseSinking : public SILFunctionTransform {
107+
void run() override {
108+
SILFunction *F = getFunction();
109+
if (F->empty())
110+
return;
111+
112+
LLVM_DEBUG(llvm::dbgs() << "Running AccessEnforcementReleaseSinking on "
113+
<< F->getName() << "\n");
114+
115+
for (SILBasicBlock &currBB : *F) {
116+
processBlock(currBB);
117+
}
118+
}
119+
};
120+
} // namespace
121+
122+
SILTransform *swift::createAccessEnforcementReleaseSinking() {
123+
return new AccessEnforcementReleaseSinking();
124+
}

lib/SILOptimizer/Transforms/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ silopt_register_sources(
22
ARCCodeMotion.cpp
33
AccessEnforcementDom.cpp
44
AccessEnforcementOpts.cpp
5+
AccessEnforcementReleaseSinking.cpp
56
AccessEnforcementWMO.cpp
67
AllocBoxToStack.cpp
78
ArrayCountPropagation.cpp

test/SILOptimizer/access_sink.sil

+217
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
// RUN: %target-sil-opt -access-enforcement-release -assume-parsing-unqualified-ownership-sil %s -enable-sil-verify-all | %FileCheck %s
2+
//
3+
// Test the AccessEnforcementReleaseSinking pass in isolation.
4+
// This ensures that no upstream passes have removed SIL-level access markers
5+
// that are required to ensure the pass is not overly optimistic.
6+
7+
sil_stage canonical
8+
9+
import Builtin
10+
import Swift
11+
import SwiftShims
12+
13+
struct X {
14+
@sil_stored var i: Int64 { get set }
15+
init(i: Int64)
16+
init()
17+
}
18+
19+
var globalX: X
20+
21+
sil_global hidden @globalX : $X
22+
23+
sil hidden_external [global_init] @globalAddressor : $@convention(thin) () -> Builtin.RawPointer
24+
25+
// public func testSimpleRelease() {
26+
// Checks the simple case of release sinking
27+
//
28+
// CHECK-LABEL: sil @testSimpleRelease : $@convention(thin) () -> () {
29+
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
30+
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [modify] [dynamic] [[GLOBAL]] : $*X
31+
// CHECK-NEXT: [[LOADED:%.*]] = load [[BEGIN]] : $*X
32+
// CHECK-NEXT: end_access [[BEGIN]] : $*X
33+
// CHECK-NEXT: release_value [[LOADED]]
34+
// CHECK-LABEL: } // end sil function 'testSimpleRelease'
35+
sil @testSimpleRelease : $@convention(thin) () -> () {
36+
bb0:
37+
%0 = global_addr @globalX: $*X
38+
%1 = begin_access [modify] [dynamic] %0 : $*X
39+
%2 = load %1 : $*X
40+
release_value %2 : $X
41+
end_access %1 : $*X
42+
%ret = tuple ()
43+
return %ret : $()
44+
}
45+
46+
// public func testMultiBlocklSimpleRelease() {
47+
// Checks the simple case of release sinking with the begin_access in a different block
48+
//
49+
// CHECK-LABEL: sil @testMultiBlocklSimpleRelease : $@convention(thin) () -> () {
50+
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
51+
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [modify] [dynamic] [[GLOBAL]] : $*X
52+
// CHECK-NEXT: [[LOADED:%.*]] = load [[BEGIN]] : $*X
53+
// CHECK-NEXT: br bb1
54+
// CHECK: bb1
55+
// CHECK-NEXT: end_access [[BEGIN]] : $*X
56+
// CHECK-NEXT: release_value [[LOADED]]
57+
// CHECK-LABEL: } // end sil function 'testMultiBlocklSimpleRelease'
58+
sil @testMultiBlocklSimpleRelease : $@convention(thin) () -> () {
59+
bb0:
60+
%0 = global_addr @globalX: $*X
61+
%1 = begin_access [modify] [dynamic] %0 : $*X
62+
%2 = load %1 : $*X
63+
br bb1
64+
65+
bb1:
66+
release_value %2 : $X
67+
end_access %1 : $*X
68+
%ret = tuple ()
69+
return %ret : $()
70+
}
71+
72+
// public func testMultiBlocklBailOnRelease() {
73+
// Checks bailing (for now) on the simple case due to the release being in a different block
74+
//
75+
// CHECK-LABEL: sil @testMultiBlocklBailOnRelease : $@convention(thin) () -> () {
76+
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
77+
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [modify] [dynamic] [[GLOBAL]] : $*X
78+
// CHECK-NEXT: [[LOADED:%.*]] = load [[BEGIN]] : $*X
79+
// CHECK-NEXT: release_value [[LOADED]]
80+
// CHECK-NEXT: br bb1
81+
// CHECK: bb1
82+
// CHECK-NEXT: end_access [[BEGIN]] : $*X
83+
// CHECK-LABEL: } // end sil function 'testMultiBlocklBailOnRelease'
84+
sil @testMultiBlocklBailOnRelease : $@convention(thin) () -> () {
85+
bb0:
86+
%0 = global_addr @globalX: $*X
87+
%1 = begin_access [modify] [dynamic] %0 : $*X
88+
%2 = load %1 : $*X
89+
release_value %2 : $X
90+
br bb1
91+
92+
bb1:
93+
end_access %1 : $*X
94+
%ret = tuple ()
95+
return %ret : $()
96+
}
97+
98+
// public func testApplyBarrier() {
99+
// Checks we don't sink across apply-site barrier
100+
//
101+
// CHECK-LABEL: sil @testApplyBarrier : $@convention(thin) () -> () {
102+
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
103+
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [modify] [dynamic] [[GLOBAL]] : $*X
104+
// CHECK-NEXT: [[LOADED:%.*]] = load [[BEGIN]] : $*X
105+
// CHECK-NEXT: release_value [[LOADED]]
106+
// CHECK: apply
107+
// CHECK-NEXT: end_access [[BEGIN]] : $*X
108+
// CHECK-LABEL: } // end sil function 'testApplyBarrier'
109+
sil @testApplyBarrier : $@convention(thin) () -> () {
110+
bb0:
111+
%0 = global_addr @globalX: $*X
112+
%1 = begin_access [modify] [dynamic] %0 : $*X
113+
%2 = load %1 : $*X
114+
release_value %2 : $X
115+
%u0 = function_ref @globalAddressor : $@convention(thin) () -> Builtin.RawPointer
116+
%u1 = apply %u0() : $@convention(thin) () -> Builtin.RawPointer
117+
end_access %1 : $*X
118+
%ret = tuple ()
119+
return %ret : $()
120+
}
121+
122+
// public func testUniquenessBarrier() {
123+
// Checks we don't sink across a uniqueness check
124+
//
125+
// CHECK-LABEL: sil @testUniquenessBarrier : $@convention(thin) () -> () {
126+
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
127+
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [modify] [dynamic] [[GLOBAL]] : $*X
128+
// CHECK-NEXT: [[LOADED:%.*]] = load [[BEGIN]] : $*X
129+
// CHECK-NEXT: release_value [[LOADED]]
130+
// CHECK-NEXT: is_unique
131+
// CHECK-NEXT: end_access [[BEGIN]] : $*X
132+
// CHECK-LABEL: } // end sil function 'testUniquenessBarrier'
133+
sil @testUniquenessBarrier : $@convention(thin) () -> () {
134+
bb0:
135+
%0 = global_addr @globalX: $*X
136+
%1 = begin_access [modify] [dynamic] %0 : $*X
137+
%2 = load %1 : $*X
138+
release_value %2 : $X
139+
is_unique %1 : $*X
140+
end_access %1 : $*X
141+
%ret = tuple ()
142+
return %ret : $()
143+
}
144+
145+
// public func testBeginBarrier() {
146+
// Checks we don't sink across begin_access barrier
147+
//
148+
// CHECK-LABEL: sil @testBeginBarrier : $@convention(thin) () -> () {
149+
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
150+
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [modify] [dynamic] [[GLOBAL]] : $*X
151+
// CHECK-NEXT: [[LOADED:%.*]] = load [[BEGIN]] : $*X
152+
// CHECK-NEXT: release_value [[LOADED]]
153+
// CHECK-NEXT: [[BEGIN2:%.*]] = begin_access [modify] [dynamic] [[GLOBAL]] : $*X
154+
// CHECK-NEXT: end_access [[BEGIN2]] : $*X
155+
// CHECK-NEXT: end_access [[BEGIN]] : $*X
156+
// CHECK-LABEL: } // end sil function 'testBeginBarrier'
157+
sil @testBeginBarrier : $@convention(thin) () -> () {
158+
bb0:
159+
%0 = global_addr @globalX: $*X
160+
%1 = begin_access [modify] [dynamic] %0 : $*X
161+
%2 = load %1 : $*X
162+
release_value %2 : $X
163+
%b0 = begin_access [modify] [dynamic] %0 : $*X
164+
end_access %b0 : $*X
165+
end_access %1 : $*X
166+
%ret = tuple ()
167+
return %ret : $()
168+
}
169+
170+
// public func testSinkCrossMultiEnds() {
171+
// Checks that we choose the *bottom* end_access when sinking
172+
//
173+
// CHECK-LABEL: sil @testSinkCrossMultiEnds : $@convention(thin) () -> () {
174+
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
175+
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [modify] [dynamic] [[GLOBAL]] : $*X
176+
// CHECK-NEXT: [[BEGIN2:%.*]] = begin_access [modify] [dynamic] [[GLOBAL]] : $*X
177+
// CHECK-NEXT: [[LOADED:%.*]] = load [[BEGIN]] : $*X
178+
// CHECK-NEXT: end_access [[BEGIN2]] : $*X
179+
// CHECK-NEXT: end_access [[BEGIN]] : $*X
180+
// CHECK-NEXT: release_value [[LOADED]]
181+
// CHECK-LABEL: } // end sil function 'testSinkCrossMultiEnds'
182+
sil @testSinkCrossMultiEnds : $@convention(thin) () -> () {
183+
bb0:
184+
%0 = global_addr @globalX: $*X
185+
%1 = begin_access [modify] [dynamic] %0 : $*X
186+
%b0 = begin_access [modify] [dynamic] %0 : $*X
187+
%2 = load %1 : $*X
188+
release_value %2 : $X
189+
end_access %b0 : $*X
190+
end_access %1 : $*X
191+
%ret = tuple ()
192+
return %ret : $()
193+
}
194+
195+
// public func testSinkAfterBarrierEncounter() {
196+
// Checks that we sink after barrier resetting
197+
//
198+
// CHECK-LABEL: sil @testSinkAfterBarrierEncounter : $@convention(thin) () -> () {
199+
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
200+
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [modify] [dynamic] [[GLOBAL]] : $*X
201+
// CHECK-NEXT: [[LOADED:%.*]] = load [[BEGIN]] : $*X
202+
// CHECK-NEXT: end_access [[BEGIN]] : $*X
203+
// CHECK-NEXT: release_value [[LOADED]]
204+
// CHECK-NEXT: [[BEGIN2:%.*]] = begin_access [modify] [dynamic] [[GLOBAL]] : $*X
205+
// CHECK-LABEL: } // end sil function 'testSinkAfterBarrierEncounter'
206+
sil @testSinkAfterBarrierEncounter : $@convention(thin) () -> () {
207+
bb0:
208+
%0 = global_addr @globalX: $*X
209+
%1 = begin_access [modify] [dynamic] %0 : $*X
210+
%2 = load %1 : $*X
211+
release_value %2 : $X
212+
end_access %1 : $*X
213+
%b0 = begin_access [modify] [dynamic] %0 : $*X
214+
end_access %b0 : $*X
215+
%ret = tuple ()
216+
return %ret : $()
217+
}

0 commit comments

Comments
 (0)