Skip to content

Commit 8d41d6e

Browse files
authored
Enable strict verification of begin_access patterns in all SIL passes. (#17534)
* Teach findAccessedStorage about global addressors. AccessedStorage now properly represents access to global variables, even if they haven't been fully optimized down to global_addr instructions. This is essential for optimizing dynamic exclusivity checks. As a verified SIL property, all access to globals and class properties needs to be identifiable. * Add stronger SILVerifier support for formal access. Ensure that all formal access follows recognizable patterns at all points in the SIL pipeline. This is important to run acccess enforcement optimization late in the pipeline.
1 parent b34c087 commit 8d41d6e

File tree

8 files changed

+381
-108
lines changed

8 files changed

+381
-108
lines changed

include/swift/SIL/SILGlobalVariable.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ class SILGlobalVariable
6868
/// once (either in its declaration, or once later), making it immutable.
6969
unsigned IsLet : 1;
7070

71-
/// The VarDecl associated with this SILGlobalVariable. For debugger purpose.
71+
/// The VarDecl associated with this SILGlobalVariable. Must by nonnull for
72+
/// language-level global variables.
7273
VarDecl *VDecl;
7374

7475
/// Whether or not this is a declaration.

lib/SIL/MemAccessUtils.cpp

Lines changed: 80 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#define DEBUG_TYPE "sil-access-utils"
1414

1515
#include "swift/SIL/MemAccessUtils.h"
16+
#include "swift/SIL/SILGlobalVariable.h"
1617
#include "swift/SIL/SILUndef.h"
1718

1819
using namespace swift;
@@ -28,6 +29,14 @@ AccessedStorage::Kind AccessedStorage::classify(SILValue base) {
2829
return Stack;
2930
case ValueKind::GlobalAddrInst:
3031
return Global;
32+
case ValueKind::ApplyInst: {
33+
FullApplySite apply(cast<ApplyInst>(base));
34+
if (auto *funcRef = apply.getReferencedFunction()) {
35+
if (getVariableOfGlobalInit(funcRef))
36+
return Global;
37+
}
38+
return Unidentified;
39+
}
3140
case ValueKind::RefElementAddrInst:
3241
return Class;
3342
// A function argument is effectively a nested access, enforced
@@ -67,7 +76,19 @@ AccessedStorage::AccessedStorage(SILValue base, Kind kind) {
6776
paramIndex = cast<SILFunctionArgument>(base)->getIndex();
6877
break;
6978
case Global:
70-
global = cast<GlobalAddrInst>(base)->getReferencedGlobal();
79+
if (auto *GAI = dyn_cast<GlobalAddrInst>(base))
80+
global = GAI->getReferencedGlobal();
81+
else {
82+
FullApplySite apply(cast<ApplyInst>(base));
83+
auto *funcRef = apply.getReferencedFunction();
84+
assert(funcRef);
85+
global = getVariableOfGlobalInit(funcRef);
86+
assert(global);
87+
// Require a decl for all formally accessed globals defined in this
88+
// module. (Access of globals defined elsewhere has Unidentified storage).
89+
// AccessEnforcementWMO requires this.
90+
assert(global->getDecl());
91+
}
7192
break;
7293
case Class: {
7394
// Do a best-effort to find the identity of the object being projected
@@ -149,6 +170,17 @@ void AccessedStorage::print(raw_ostream &os) const {
149170

150171
void AccessedStorage::dump() const { print(llvm::dbgs()); }
151172

173+
// Return true if the given apply invokes a global addressor defined in another
174+
// module.
175+
static bool isExternalGlobalAddressor(ApplyInst *AI) {
176+
FullApplySite apply(AI);
177+
auto *funcRef = apply.getReferencedFunction();
178+
if (!funcRef)
179+
return false;
180+
181+
return funcRef->isGlobalInit() && funcRef->isExternalDeclaration();
182+
}
183+
152184
// Given an address base is a block argument, verify that it is actually a box
153185
// projected from a switch_enum. This is a valid pattern at any SIL stage
154186
// resulting in a block-type phi. In later SIL stages, the optimizer may form
@@ -186,12 +218,16 @@ static bool isAddressForLocalInitOnly(SILValue sourceAddr) {
186218
}
187219
}
188220

221+
// AccessEnforcementWMO makes a strong assumption that all accesses are either
222+
// identified or are *not* accessing a global variable or class property defined
223+
// in this module. Consequently, we cannot simply bail out on
224+
// PointerToAddressInst as an Unidentified access.
189225
AccessedStorage swift::findAccessedStorage(SILValue sourceAddr) {
190226
SILValue address = sourceAddr;
191227
while (true) {
192228
AccessedStorage::Kind kind = AccessedStorage::classify(address);
193-
// First handle identified cases: these are always valid as the base of a
194-
// formal access.
229+
// First handle identified cases: these are always valid as the base of
230+
// a formal access.
195231
if (kind != AccessedStorage::Unidentified)
196232
return AccessedStorage(address, kind);
197233

@@ -202,10 +238,16 @@ AccessedStorage swift::findAccessedStorage(SILValue sourceAddr) {
202238
return AccessedStorage(address, AccessedStorage::Unidentified);
203239
return AccessedStorage();
204240

205-
case ValueKind::PointerToAddressInst:
206241
case ValueKind::SILUndef:
207242
return AccessedStorage(address, AccessedStorage::Unidentified);
208243

244+
case ValueKind::ApplyInst:
245+
if (isExternalGlobalAddressor(cast<ApplyInst>(address)))
246+
return AccessedStorage(address, AccessedStorage::Unidentified);
247+
248+
// Don't currently allow any other calls to return an accessed address.
249+
return AccessedStorage();
250+
209251
// A block argument may be a box value projected out of
210252
// switch_enum. Address-type block arguments are not allowed.
211253
case ValueKind::SILPHIArgument:
@@ -218,17 +260,22 @@ AccessedStorage swift::findAccessedStorage(SILValue sourceAddr) {
218260
// Load a box from an indirect payload of an opaque enum.
219261
// We must have peeked past the project_box earlier in this loop.
220262
// (the indirectness makes it a box, the load is for address-only).
221-
//
263+
//
222264
// %payload_adr = unchecked_take_enum_data_addr %enum : $*Enum, #Enum.case
223265
// %box = load [take] %payload_adr : $*{ var Enum }
224266
//
225267
// FIXME: this case should go away with opaque values.
268+
//
269+
// Otherwise return invalid AccessedStorage.
226270
case ValueKind::LoadInst: {
227-
assert(address->getType().is<SILBoxType>());
228-
address = cast<LoadInst>(address)->getOperand();
229-
assert(isa<UncheckedTakeEnumDataAddrInst>(address));
230-
continue;
271+
if (address->getType().is<SILBoxType>()) {
272+
address = cast<LoadInst>(address)->getOperand();
273+
assert(isa<UncheckedTakeEnumDataAddrInst>(address));
274+
continue;
275+
}
276+
return AccessedStorage();
231277
}
278+
232279
// Inductive cases:
233280
// Look through address casts to find the source address.
234281
case ValueKind::MarkUninitializedInst:
@@ -249,6 +296,30 @@ AccessedStorage swift::findAccessedStorage(SILValue sourceAddr) {
249296
address = cast<SingleValueInstruction>(address)->getOperand(0);
250297
continue;
251298

299+
// Access to a Builtin.RawPointer. Treat this like the inductive cases
300+
// above because some RawPointer's originate from identified locations. See
301+
// the special case for global addressors, which return RawPointer above.
302+
//
303+
// If the inductive search does not find a valid addressor, it will
304+
// eventually reach the default case that returns in invalid location. This
305+
// is correct for RawPointer because, although accessing a RawPointer is
306+
// legal SIL, there is no way to guarantee that it doesn't access class or
307+
// global storage, so returning a valid unidentified storage object would be
308+
// incorrect. It is the caller's responsibility to know that formal access
309+
// to such a location can be safely ignored.
310+
//
311+
// For example:
312+
//
313+
// - KeyPath Builtins access RawPointer. However, the caller can check
314+
// that the access `isFromBuilin` and ignore the storage.
315+
//
316+
// - lldb generates RawPointer access for debugger variables, but SILGen
317+
// marks debug VarDecl access as 'Unsafe' and SIL passes don't need the
318+
// AccessedStorage for 'Unsafe' access.
319+
case ValueKind::PointerToAddressInst:
320+
address = cast<SingleValueInstruction>(address)->getOperand(0);
321+
continue;
322+
252323
// Subobject projections.
253324
case ValueKind::StructElementAddrInst:
254325
case ValueKind::TupleElementAddrInst:

lib/SIL/SILVerifier.cpp

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1581,17 +1581,21 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
15811581
"category");
15821582
}
15831583

1584+
template <class AI>
1585+
void checkAccessEnforcement(AI *AccessInst) {
1586+
if (AccessInst->getModule().getStage() != SILStage::Raw) {
1587+
require(AccessInst->getEnforcement() != SILAccessEnforcement::Unknown,
1588+
"access must have known enforcement outside raw stage");
1589+
}
1590+
}
1591+
15841592
void checkBeginAccessInst(BeginAccessInst *BAI) {
1585-
auto sourceOper = BAI->getOperand();
1586-
requireSameType(BAI->getType(), sourceOper->getType(),
1593+
requireSameType(BAI->getType(), BAI->getSource()->getType(),
15871594
"result must be same type as operand");
15881595
require(BAI->getType().isAddress(),
15891596
"begin_access operand must have address type");
15901597

1591-
if (BAI->getModule().getStage() != SILStage::Raw) {
1592-
require(BAI->getEnforcement() != SILAccessEnforcement::Unknown,
1593-
"access must have known enforcement outside raw stage");
1594-
}
1598+
checkAccessEnforcement(BAI);
15951599

15961600
switch (BAI->getAccessKind()) {
15971601
case SILAccessKind::Init:
@@ -1605,10 +1609,25 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
16051609
break;
16061610
}
16071611

1608-
// For dynamic Read/Modify access, AccessEnforcementWMO assumes a valid
1609-
// AccessedStorage and runs very late in the optimizer pipeline.
1610-
// TODO: eventually, make this true for any kind of access.
1611-
findAccessedStorage(sourceOper);
1612+
// Verify that all formal accesses patterns are recognized as part of a
1613+
// whitelist. The presence of an unknown pattern means that analysis will
1614+
// silently fail, and the compiler may be introducing undefined behavior
1615+
// with no other way to detect it.
1616+
//
1617+
// For example, AccessEnforcementWMO runs very late in the
1618+
// pipeline and assumes valid storage for all dynamic Read/Modify access. It
1619+
// also requires that Unidentified access fit a whitelist on known
1620+
// non-internal globals or class properties.
1621+
//
1622+
// First check that findAccessedStorage returns without asserting. For
1623+
// Unsafe enforcement, that is sufficient. For any other enforcement
1624+
// level also require that it returns a valid AccessedStorage object.
1625+
// Unsafe enforcement is used for some unrecognizable access patterns,
1626+
// like debugger variables. The compiler never cares about the source of
1627+
// those accesses.
1628+
AccessedStorage storage = findAccessedStorage(BAI->getSource());
1629+
if (BAI->getEnforcement() != SILAccessEnforcement::Unsafe)
1630+
require(storage, "Unknown formal access pattern");
16121631
}
16131632

16141633
void checkEndAccessInst(EndAccessInst *EAI) {
@@ -1623,20 +1642,43 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
16231642
}
16241643
}
16251644

1626-
void checkBeginUnpairedAccessInst(BeginUnpairedAccessInst *I) {
1627-
require(I->getEnforcement() != SILAccessEnforcement::Unknown,
1645+
void checkBeginUnpairedAccessInst(BeginUnpairedAccessInst *BUAI) {
1646+
require(BUAI->getEnforcement() != SILAccessEnforcement::Unknown,
16281647
"unpaired access can never use unknown enforcement");
1629-
require(I->getSource()->getType().isAddress(),
1648+
require(BUAI->getSource()->getType().isAddress(),
16301649
"address operand must have address type");
1631-
requireAddressType(BuiltinUnsafeValueBufferType, I->getBuffer(),
1650+
requireAddressType(BuiltinUnsafeValueBufferType, BUAI->getBuffer(),
16321651
"scratch buffer operand");
1652+
1653+
checkAccessEnforcement(BUAI);
1654+
1655+
switch (BUAI->getAccessKind()) {
1656+
case SILAccessKind::Init:
1657+
case SILAccessKind::Deinit:
1658+
require(BUAI->getEnforcement() == SILAccessEnforcement::Static,
1659+
"init/deinit accesses cannot use non-static enforcement");
1660+
break;
1661+
case SILAccessKind::Read:
1662+
case SILAccessKind::Modify:
1663+
break;
1664+
}
1665+
1666+
// First check that findAccessedStorage never asserts.
1667+
AccessedStorage storage = findAccessedStorage(BUAI->getSource());
1668+
// Only allow Unsafe and Builtin access to have invalid storage.
1669+
if (BUAI->getEnforcement() != SILAccessEnforcement::Unsafe
1670+
&& !BUAI->isFromBuiltin()) {
1671+
require(storage, "Unknown formal access pattern");
1672+
}
16331673
}
16341674

16351675
void checkEndUnpairedAccessInst(EndUnpairedAccessInst *I) {
16361676
require(I->getEnforcement() != SILAccessEnforcement::Unknown,
16371677
"unpaired access can never use unknown enforcement");
16381678
requireAddressType(BuiltinUnsafeValueBufferType, I->getBuffer(),
16391679
"scratch buffer operand");
1680+
1681+
checkAccessEnforcement(I);
16401682
}
16411683

16421684
void checkStoreInst(StoreInst *SI) {

lib/SILOptimizer/Analysis/AccessSummaryAnalysis.cpp

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,15 @@ void AccessSummaryAnalysis::processArgument(FunctionInfo *info,
6868
switch (user->getKind()) {
6969
case SILInstructionKind::BeginAccessInst: {
7070
auto *BAI = cast<BeginAccessInst>(user);
71-
const IndexTrieNode *subPath = findSubPathAccessed(BAI);
72-
summary.mergeWith(BAI->getAccessKind(), BAI->getLoc(), subPath);
73-
// We don't add the users of the begin_access to the worklist because
74-
// even if these users eventually begin an access to the address
75-
// or a projection from it, that access can't begin more exclusive
76-
// access than this access -- otherwise it will be diagnosed
77-
// elsewhere.
71+
if (BAI->getEnforcement() != SILAccessEnforcement::Unsafe) {
72+
const IndexTrieNode *subPath = findSubPathAccessed(BAI);
73+
summary.mergeWith(BAI->getAccessKind(), BAI->getLoc(), subPath);
74+
// We don't add the users of the begin_access to the worklist because
75+
// even if these users eventually begin an access to the address
76+
// or a projection from it, that access can't begin more exclusive
77+
// access than this access -- otherwise it will be diagnosed
78+
// elsewhere.
79+
}
7880
break;
7981
}
8082
case SILInstructionKind::EndUnpairedAccessInst:

lib/SILOptimizer/Mandatory/AccessMarkerElimination.cpp

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,6 @@ using namespace swift;
3232
// This temporary option allows markers during optimization passes. Enabling
3333
// this flag causes this pass to preserve only dynamic checks when dynamic
3434
// checking is enabled. Otherwise, this pass removes all checks.
35-
//
36-
// This is currently unsupported because tail duplication results in
37-
// address-type block arguments.
3835
llvm::cl::opt<bool> EnableOptimizedAccessMarkers(
3936
"sil-optimized-access-markers", llvm::cl::init(true),
4037
llvm::cl::desc("Enable memory access markers during optimization passes."));

lib/SILOptimizer/Mandatory/DiagnoseStaticExclusivity.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -811,6 +811,9 @@ static void checkForViolationAtApply(ApplySite Apply, AccessState &State) {
811811
static void checkForViolationsAtInstruction(SILInstruction &I,
812812
AccessState &State) {
813813
if (auto *BAI = dyn_cast<BeginAccessInst>(&I)) {
814+
if (BAI->getEnforcement() == SILAccessEnforcement::Unsafe)
815+
return;
816+
814817
SILAccessKind Kind = BAI->getAccessKind();
815818
const AccessedStorage &Storage =
816819
findValidAccessedStorage(BAI->getSource());
@@ -826,6 +829,9 @@ static void checkForViolationsAtInstruction(SILInstruction &I,
826829
}
827830

828831
if (auto *EAI = dyn_cast<EndAccessInst>(&I)) {
832+
if (EAI->getBeginAccess()->getEnforcement() == SILAccessEnforcement::Unsafe)
833+
return;
834+
829835
auto It =
830836
State.Accesses->find(findValidAccessedStorage(EAI->getSource()));
831837
AccessInfo &Info = It->getSecond();
@@ -1080,6 +1086,9 @@ static void checkAccessedAddress(Operand *memOper, StorageMap &Accesses) {
10801086
// Otherwise, the address base should be an in-scope begin_access.
10811087
if (storage.getKind() == AccessedStorage::Nested) {
10821088
auto *BAI = cast<BeginAccessInst>(storage.getValue());
1089+
if (BAI->getEnforcement() == SILAccessEnforcement::Unsafe)
1090+
return;
1091+
10831092
const AccessedStorage &Storage = findValidAccessedStorage(BAI->getSource());
10841093
AccessInfo &Info = Accesses[Storage];
10851094
if (!Info.hasAccessesInProgress())

0 commit comments

Comments
 (0)