Skip to content

Commit 8f7620f

Browse files
committed
[semantic-arc-opts] Implement a simple load [copy] -> load_borrow optimization for arguments to create optimization scaffolding.
This is the first in a sequence of patches that implement various optimizations to transform load [copy] into load_borrow. The optimization works by looking for a load [copy] that: 1. Only has destroy_value as consuming users. This implies that we do not need to pass off the in memory value at +1 and that we can use a +0 value. 2. Is loading from a memory location that is never written to or only written to after all uses of the load [copy]. and then RAUW the load [copy] with a load_borrow and convertes the destroy_value to end_borrow. NOTE: I also a .def file for AccessedStorage so we can do visitors over the kinds. The reason I want to do this is to ensure that people update these optimizations if we add new storage kinds.
1 parent 51bf7cc commit 8f7620f

File tree

5 files changed

+220
-25
lines changed

5 files changed

+220
-25
lines changed

include/swift/SIL/AccessedStorage.def

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//===--- AccessedStorage.def ----------------------------*- c++ -*---------===//
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+
/// \file
14+
///
15+
/// A file used for metaprogramming with accessed storage. Used to enable
16+
/// easily updateable visitors.
17+
///
18+
//===----------------------------------------------------------------------===//
19+
20+
#ifndef ACCESSED_STORAGE
21+
#error "Must define accesed storage before including this?!"
22+
#endif
23+
24+
#ifndef ACCESSED_STORAGE_RANGE
25+
#define ACCESSED_STORAGE_RANGE(Name, Start, End)
26+
#endif
27+
28+
ACCESSED_STORAGE(Box)
29+
ACCESSED_STORAGE(Stack)
30+
ACCESSED_STORAGE(Global)
31+
ACCESSED_STORAGE(Class)
32+
ACCESSED_STORAGE(Argument)
33+
ACCESSED_STORAGE(Yield)
34+
ACCESSED_STORAGE(Nested)
35+
ACCESSED_STORAGE(Unidentified)
36+
ACCESSED_STORAGE_RANGE(AccessedStorageKind, Box, Unidentified)
37+
38+
#undef ACCESSED_STORAGE_RANGE
39+
#undef ACCESSED_STORAGE

include/swift/SIL/MemAccessUtils.h

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -106,15 +106,11 @@ class AccessedStorage {
106106
/// Enumerate over all valid begin_access bases. Clients can use a covered
107107
/// switch to warn if findAccessedAddressBase ever adds a case.
108108
enum Kind : uint8_t {
109-
Box,
110-
Stack,
111-
Global,
112-
Class,
113-
Argument,
114-
Yield,
115-
Nested,
116-
Unidentified,
117-
NumKindBits = countBitsUsed(static_cast<unsigned>(Unidentified))
109+
#define ACCESSED_STORAGE(Name) Name,
110+
#define ACCESSED_STORAGE_RANGE(Name, Start, End) \
111+
First_##Name = Start, Last_##Name = End,
112+
#include "swift/SIL/AccessedStorage.def"
113+
NumKindBits = countBitsUsed(unsigned(Last_AccessedStorageKind))
118114
};
119115

120116
static const char *getKindName(Kind k);
@@ -339,6 +335,26 @@ inline bool accessingIdenticalLocations(AccessedStorage LHS,
339335
return LHS.getObjectProjection() == RHS.getObjectProjection();
340336
}
341337
}
338+
339+
template <class ImplTy, class ResultTy = void, typename... ArgTys>
340+
class AccessedStorageVisitor {
341+
ImplTy &asImpl() { return static_cast<ImplTy &>(*this); }
342+
343+
public:
344+
#define ACCESSED_STORAGE(Name) \
345+
ResultTy visit##Name(const AccessedStorage &storage, ArgTys &&... args);
346+
#include "swift/SIL/AccessedStorage.def"
347+
348+
ResultTy visit(const AccessedStorage &storage, ArgTys &&... args) {
349+
switch (storage.getKind()) {
350+
#define ACCESSED_STORAGE(Name) \
351+
case AccessedStorage::Name: \
352+
return asImpl().visit##Name(storage, std::forward<ArgTys>(args)...);
353+
#include "swift/SIL/AccessedStorage.def"
354+
}
355+
}
356+
};
357+
342358
} // end namespace swift
343359

344360
namespace llvm {

lib/SIL/MemAccessUtils.cpp

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -140,22 +140,10 @@ const ValueDecl *AccessedStorage::getDecl(SILFunction *F) const {
140140

141141
const char *AccessedStorage::getKindName(AccessedStorage::Kind k) {
142142
switch (k) {
143-
case Box:
144-
return "Box";
145-
case Stack:
146-
return "Stack";
147-
case Nested:
148-
return "Nested";
149-
case Unidentified:
150-
return "Unidentified";
151-
case Argument:
152-
return "Argument";
153-
case Yield:
154-
return "Yield";
155-
case Global:
156-
return "Global";
157-
case Class:
158-
return "Class";
143+
#define ACCESSED_STORAGE(NAME) \
144+
case AccessedStorage::NAME: \
145+
return #NAME;
146+
#include "swift/SIL/AccessedStorage.def"
159147
}
160148
llvm_unreachable("unhandled kind");
161149
}

lib/SILOptimizer/Mandatory/SemanticARCOpts.cpp

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313
#define DEBUG_TYPE "sil-semantic-arc-opts"
1414
#include "swift/Basic/STLExtras.h"
1515
#include "swift/SIL/BasicBlockUtils.h"
16+
#include "swift/SIL/MemAccessUtils.h"
1617
#include "swift/SIL/OwnershipUtils.h"
1718
#include "swift/SIL/SILArgument.h"
19+
#include "swift/SIL/SILBuilder.h"
1820
#include "swift/SIL/SILInstruction.h"
1921
#include "swift/SIL/SILVisitor.h"
2022
#include "swift/SILOptimizer/Analysis/PostOrderAnalysis.h"
@@ -27,6 +29,8 @@
2729
using namespace swift;
2830

2931
STATISTIC(NumEliminatedInsts, "number of removed instructions");
32+
STATISTIC(NumLoadCopyConvertedToLoadBorrow,
33+
"number of load_copy converted to load_borrow");
3034

3135
//===----------------------------------------------------------------------===//
3236
// Utility
@@ -75,6 +79,7 @@ struct SemanticARCOptVisitor
7579
bool visitSILInstruction(SILInstruction *i) { return false; }
7680
bool visitCopyValueInst(CopyValueInst *cvi);
7781
bool visitBeginBorrowInst(BeginBorrowInst *bbi);
82+
bool visitLoadInst(LoadInst *li);
7883
};
7984

8085
} // end anonymous namespace
@@ -245,6 +250,116 @@ bool SemanticARCOptVisitor::visitCopyValueInst(CopyValueInst *cvi) {
245250
return false;
246251
}
247252

253+
//===----------------------------------------------------------------------===//
254+
// load [copy] Optimizations
255+
//===----------------------------------------------------------------------===//
256+
257+
/// A flow insensitive analysis that tells the load [copy] analysis if the
258+
/// storage has 0, 1, >1 writes to it.
259+
///
260+
/// In the case of 0 writes, we return CanOptimizeLoadCopyResult::Always.
261+
///
262+
/// In the case of 1 write, we return OnlyIfStorageIsLocal. We are taking
263+
/// advantage of definite initialization implying that an alloc_stack must be
264+
/// written to once before any loads from the memory location. Thus if we are
265+
/// local and see 1 write, we can still change to load_borrow if all other uses
266+
/// check out.
267+
///
268+
/// If there is 2+ writes, we can not optimize = (.
269+
namespace {
270+
271+
struct CanOptimizeLoadCopyFromAccessVisitor
272+
: AccessedStorageVisitor<CanOptimizeLoadCopyFromAccessVisitor, bool> {
273+
SILFunction &f;
274+
275+
CanOptimizeLoadCopyFromAccessVisitor(SILFunction &f) : f(f) {}
276+
277+
// Stubs
278+
bool visitBox(const AccessedStorage &boxStorage) { return false; }
279+
bool visitStack(const AccessedStorage &stackStorage) { return false; }
280+
bool visitGlobal(const AccessedStorage &globalStorage) { return false; }
281+
bool visitClass(const AccessedStorage &classStorage) { return false; }
282+
bool visitYield(const AccessedStorage &yieldStorage) { return false; }
283+
bool visitUnidentified(const AccessedStorage &unidentifiedStorage) {
284+
return false;
285+
}
286+
bool visitNested(const AccessedStorage &nested) {
287+
llvm_unreachable("Visitor should never see nested since we lookup our "
288+
"address storage using lookup non nested");
289+
}
290+
291+
bool visitArgument(const AccessedStorage &argumentStorage);
292+
};
293+
294+
} // namespace
295+
296+
bool CanOptimizeLoadCopyFromAccessVisitor::visitArgument(
297+
const AccessedStorage &storage) {
298+
auto *arg = cast<SILFunctionArgument>(storage.getArgument(&f));
299+
300+
// Then check if we have an in_guaranteed argument. In this case, we can
301+
// always optimize load [copy] from this.
302+
if (arg->hasConvention(SILArgumentConvention::Indirect_In_Guaranteed))
303+
return true;
304+
305+
// For now just return false.
306+
return false;
307+
}
308+
309+
static bool isWrittenTo(SILFunction &f, SILValue value) {
310+
// Then find our accessed storage. If we can not find anything, be
311+
// conservative and assume that the value is written to.
312+
const auto &storage = findAccessedStorageNonNested(value);
313+
if (!storage)
314+
return false;
315+
316+
// Then see if we ever write to this address in a flow insensitive
317+
// way (ignoring stores that are obviously the only initializer to
318+
// memory). We have to do this since load_borrow assumes that the
319+
// underlying memory is never written to.
320+
return !CanOptimizeLoadCopyFromAccessVisitor(f).visit(storage);
321+
}
322+
323+
// Convert a load [copy] from unique storage [read] that has all uses that can
324+
// accept a guaranteed parameter to a load_borrow.
325+
bool SemanticARCOptVisitor::visitLoadInst(LoadInst *li) {
326+
if (li->getOwnershipQualifier() != LoadOwnershipQualifier::Copy)
327+
return false;
328+
329+
// Ok, we have our load [copy]. Make sure its value is never
330+
// consumed. If it is consumed, we need to pass off a +1 value, so
331+
// bail.
332+
//
333+
// FIXME: We should consider if it is worth promoting a load [copy]
334+
// -> load_borrow if we can put a copy_value on a cold path and thus
335+
// eliminate RR traffic on a hot path.
336+
SmallVector<DestroyValueInst *, 32> destroyValues;
337+
if (isConsumed(li, destroyValues))
338+
return false;
339+
340+
// Then check if our address is ever written to. If it is, then we
341+
// can not use the load_borrow.
342+
if (isWrittenTo(*li->getFunction(), li->getOperand()))
343+
return false;
344+
345+
// Ok, we can perform our optimization. Convert the load [copy] into a
346+
// load_borrow.
347+
auto *lbi =
348+
SILBuilderWithScope(li).createLoadBorrow(li->getLoc(), li->getOperand());
349+
while (!destroyValues.empty()) {
350+
auto *dvi = destroyValues.pop_back_val();
351+
SILBuilderWithScope(dvi).createEndBorrow(dvi->getLoc(), lbi);
352+
dvi->eraseFromParent();
353+
++NumEliminatedInsts;
354+
}
355+
356+
li->replaceAllUsesWith(lbi);
357+
li->eraseFromParent();
358+
++NumEliminatedInsts;
359+
++NumLoadCopyConvertedToLoadBorrow;
360+
return true;
361+
}
362+
248363
//===----------------------------------------------------------------------===//
249364
// Top Level Entrypoint
250365
//===----------------------------------------------------------------------===//

test/SILOptimizer/semantic-arc-opts.sil

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,3 +239,40 @@ bb3:
239239
%9999 = tuple()
240240
return %9999 : $()
241241
}
242+
243+
// Simple in_guaranteed argument load_copy.
244+
// CHECK-LABEL: sil @load_copy_from_in_guaranteed : $@convention(thin) (@in_guaranteed Builtin.NativeObject) -> () {
245+
// CHECK: bb0([[ARG:%.*]] :
246+
// CHECK: load_borrow
247+
// CHECK: load_borrow
248+
// CHECK: load [copy]
249+
// CHECK: } // end sil function 'load_copy_from_in_guaranteed'
250+
sil @load_copy_from_in_guaranteed : $@convention(thin) (@in_guaranteed Builtin.NativeObject) -> () {
251+
bb0(%0 : $*Builtin.NativeObject):
252+
%g = function_ref @guaranteed_user : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
253+
// Simple same bb.
254+
%1 = load [copy] %0 : $*Builtin.NativeObject
255+
apply %g(%1) : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
256+
destroy_value %1 : $Builtin.NativeObject
257+
258+
// Diamond.
259+
%2 = load [copy] %0 : $*Builtin.NativeObject
260+
cond_br undef, bb1, bb2
261+
262+
bb1:
263+
apply %g(%2) : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
264+
destroy_value %2 : $Builtin.NativeObject
265+
br bb3
266+
267+
bb2:
268+
destroy_value %2 : $Builtin.NativeObject
269+
br bb3
270+
271+
bb3:
272+
// Consuming use blocks.
273+
%3 = load [copy] %0 : $*Builtin.NativeObject
274+
%4 = function_ref @owned_user : $@convention(thin) (@owned Builtin.NativeObject) -> ()
275+
apply %4(%3) : $@convention(thin) (@owned Builtin.NativeObject) -> ()
276+
%9999 = tuple()
277+
return %9999 : $()
278+
}

0 commit comments

Comments
 (0)