Skip to content

Commit 7a251af

Browse files
committed
AccessEnforcement: Fix analysis to include mayReleases as potentially
executing unknown code This means we have to claw back some performance by recognizing harmless releases. Such as releases on types we known don't call a deinit with unknown side-effects. rdar://143497196 rdar://143141695
1 parent c95c452 commit 7a251af

12 files changed

+371
-23
lines changed

include/swift/AST/KnownStdlibTypes.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ KNOWN_STDLIB_TYPE_DECL(StaticString, NominalTypeDecl, 0)
5050
KNOWN_STDLIB_TYPE_DECL(Substring, NominalTypeDecl, 0)
5151
KNOWN_STDLIB_TYPE_DECL(Array, NominalTypeDecl, 1)
5252
KNOWN_STDLIB_TYPE_DECL(ArraySlice, NominalTypeDecl, 1)
53+
KNOWN_STDLIB_TYPE_DECL(_ArrayBuffer, NominalTypeDecl, 1)
54+
KNOWN_STDLIB_TYPE_DECL(_ContiguousArrayBuffer, NominalTypeDecl, 1)
5355
KNOWN_STDLIB_TYPE_DECL(_ContiguousArrayStorage, ClassDecl, 1)
5456
KNOWN_STDLIB_TYPE_DECL(Set, NominalTypeDecl, 1)
5557
KNOWN_STDLIB_TYPE_DECL(Sequence, NominalTypeDecl, 1)

include/swift/SILOptimizer/Analysis/AccessStorageAnalysis.h

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
namespace swift {
3232

3333
class BasicCalleeAnalysis;
34+
class DestructorAnalysis;
3435

3536
/// Information about a formal access within a function pertaining to a
3637
/// particular AccessStorage location.
@@ -202,7 +203,7 @@ class AccessStorageResult {
202203

203204
/// Record any access scopes entered by the given single SIL instruction. 'I'
204205
/// must not be a FullApply; use mergeFromApply instead.
205-
void analyzeInstruction(SILInstruction *I);
206+
void analyzeInstruction(SILInstruction *I, DestructorAnalysis *DA);
206207

207208
void print(raw_ostream &os) const;
208209
void dump() const;
@@ -317,8 +318,8 @@ class FunctionAccessStorage {
317318
/// Analyze the side-effects of a single SIL instruction \p I.
318319
/// Visited callees are added to \p BottomUpOrder until \p RecursionDepth
319320
/// reaches MaxRecursionDepth.
320-
void analyzeInstruction(SILInstruction *I) {
321-
accessResult.analyzeInstruction(I);
321+
void analyzeInstruction(SILInstruction *I, DestructorAnalysis *DA) {
322+
accessResult.analyzeInstruction(I, DA);
322323
}
323324

324325
void print(raw_ostream &os) const { accessResult.print(os); }
@@ -380,6 +381,10 @@ class AccessStorageAnalysis : public BottomUpIPAnalysis {
380381
/// Callee analysis, used for determining the callees at call sites.
381382
BasicCalleeAnalysis *BCA;
382383

384+
/// Destructor analysis, used for determined which releases are harmless wrt
385+
/// to their side-effects.
386+
DestructorAnalysis *DA;
387+
383388
public:
384389
AccessStorageAnalysis()
385390
: BottomUpIPAnalysis(SILAnalysisKind::AccessStorage) {}
@@ -412,6 +417,8 @@ class AccessStorageAnalysis : public BottomUpIPAnalysis {
412417

413418
BasicCalleeAnalysis *getBasicCalleeAnalysis() { return BCA; }
414419

420+
DestructorAnalysis *getDestructorAnalysis() { return DA; }
421+
415422
virtual void initialize(SILPassManager *PM) override;
416423

417424
/// Invalidate all information in this analysis.

include/swift/SILOptimizer/Utils/InstOptUtils.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ namespace swift {
3535
class DominanceInfo;
3636
class DeadEndBlocks;
3737
class BasicCalleeAnalysis;
38+
class DestructorAnalysis;
3839
template <class T> class NullablePtr;
3940

4041
/// Transform a Use Range (Operand*) into a User Range (SILInstruction *)
@@ -626,6 +627,9 @@ bool findUnreferenceableStorage(StructDecl *decl, SILType structType,
626627

627628
SILValue getInitOfTemporaryAllocStack(AllocStackInst *asi);
628629

630+
bool isDestructorSideEffectFree(SILInstruction *mayRelease,
631+
DestructorAnalysis *DA);
632+
629633
} // end namespace swift
630634

631635
#endif // SWIFT_SILOPTIMIZER_UTILS_INSTOPTUTILS_H

lib/SILOptimizer/Analysis/AccessStorageAnalysis.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
#include "swift/Basic/Assertions.h"
1616
#include "swift/SILOptimizer/Analysis/AccessStorageAnalysis.h"
1717
#include "swift/SILOptimizer/Analysis/BasicCalleeAnalysis.h"
18+
#include "swift/SILOptimizer/Analysis/DestructorAnalysis.h"
1819
#include "swift/SILOptimizer/Analysis/FunctionOrder.h"
20+
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
1921
#include "swift/SILOptimizer/PassManager/PassManager.h"
2022

2123
using namespace swift;
@@ -313,13 +315,16 @@ void AccessStorageResult::visitBeginAccess(B *beginAccess) {
313315
result.first->mergeFrom(storageAccess);
314316
}
315317

316-
void AccessStorageResult::analyzeInstruction(SILInstruction *I) {
318+
void AccessStorageResult::analyzeInstruction(SILInstruction *I,
319+
DestructorAnalysis *DA) {
317320
assert(!FullApplySite::isa(I) && "caller should merge");
318321

319322
if (auto *BAI = dyn_cast<BeginAccessInst>(I))
320323
visitBeginAccess(BAI);
321324
else if (auto *BUAI = dyn_cast<BeginUnpairedAccessInst>(I))
322325
visitBeginAccess(BUAI);
326+
else if (I->mayRelease() && !isDestructorSideEffectFree(I, DA))
327+
setWorstEffects();
323328
}
324329

325330
void StorageAccessInfo::print(raw_ostream &os) const {
@@ -393,6 +398,7 @@ bool FunctionAccessStorage::summarizeCall(FullApplySite fullApply) {
393398
void AccessStorageAnalysis::initialize(
394399
SILPassManager *PM) {
395400
BCA = PM->getAnalysis<BasicCalleeAnalysis>();
401+
DA = PM->getAnalysis<DestructorAnalysis>();
396402
}
397403

398404
void AccessStorageAnalysis::invalidate() {
@@ -449,7 +455,7 @@ void AccessStorageAnalysis::analyzeFunction(
449455
if (auto fullApply = FullApplySite::isa(&I))
450456
analyzeCall(functionInfo, fullApply, bottomUpOrder, recursionDepth);
451457
else
452-
functionInfo->functionEffects.analyzeInstruction(&I);
458+
functionInfo->functionEffects.analyzeInstruction(&I, DA);
453459
}
454460
}
455461
LLVM_DEBUG(llvm::dbgs() << " << finished " << F->getName() << '\n');

lib/SILOptimizer/Analysis/DestructorAnalysis.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,13 @@ bool DestructorAnalysis::cacheResult(CanType Type, bool Result) {
4343
return Result;
4444
}
4545

46+
static bool isKnownSafeStdlibContainerType(CanType Ty) {
47+
return Ty->isArray() ||
48+
Ty->is_ArrayBuffer() ||
49+
Ty->is_ContiguousArrayBuffer() ||
50+
Ty->isDictionary();
51+
}
52+
4653
bool DestructorAnalysis::isSafeType(CanType Ty) {
4754
// Don't visit types twice.
4855
auto CachedRes = Cached.find(Ty);
@@ -68,7 +75,8 @@ bool DestructorAnalysis::isSafeType(CanType Ty) {
6875
// * or all stored properties are safe types.
6976
if (auto *Struct = Ty->getStructOrBoundGenericStruct()) {
7077

71-
if (implementsDestructorSafeContainerProtocol(Struct) &&
78+
if ((implementsDestructorSafeContainerProtocol(Struct) ||
79+
isKnownSafeStdlibContainerType(Ty)) &&
7280
areTypeParametersSafe(Ty))
7381
return cacheResult(Ty, true);
7482

lib/SILOptimizer/Transforms/AccessEnforcementOpts.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
#include "swift/SILOptimizer/Analysis/DominanceAnalysis.h"
8989
#include "swift/SILOptimizer/Analysis/LoopRegionAnalysis.h"
9090
#include "swift/SILOptimizer/PassManager/Transforms.h"
91+
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
9192
#include "swift/SILOptimizer/Utils/InstructionDeleter.h"
9293
#include "swift/SILOptimizer/Utils/OwnershipOptUtils.h"
9394
#include "llvm/ADT/MapVector.h"
@@ -633,7 +634,7 @@ void AccessConflictAndMergeAnalysis::propagateAccessSetsBottomUp(
633634
}
634635
// FIXME: Treat may-release conservatively in the analysis itself by
635636
// adding a mayRelease flag, in addition to the unidentified flag.
636-
accessResult.analyzeInstruction(&instr);
637+
accessResult.analyzeInstruction(&instr, ASA->getDestructorAnalysis());
637638
}
638639
}
639640
}
@@ -838,7 +839,8 @@ void AccessConflictAndMergeAnalysis::localDataFlowInBlock(RegionState &state,
838839
visitFullApply(fullApply, state);
839840
continue;
840841
}
841-
if (instr.mayRelease()) {
842+
if (instr.mayRelease() &&
843+
!isDestructorSideEffectFree(&instr, ASA->getDestructorAnalysis())) {
842844
visitMayRelease(&instr, state);
843845
}
844846
}

lib/SILOptimizer/Utils/InstOptUtils.cpp

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
#include "swift/SILOptimizer/Analysis/ArraySemantic.h"
3636
#include "swift/SILOptimizer/Analysis/BasicCalleeAnalysis.h"
3737
#include "swift/SILOptimizer/Analysis/DominanceAnalysis.h"
38+
#include "swift/SILOptimizer/Analysis/DestructorAnalysis.h"
3839
#include "swift/SILOptimizer/OptimizerBridging.h"
3940
#include "swift/SILOptimizer/Utils/CFGOptUtils.h"
4041
#include "swift/SILOptimizer/Utils/DebugOptUtils.h"
@@ -2470,3 +2471,130 @@ SILValue swift::getInitOfTemporaryAllocStack(AllocStackInst *asi) {
24702471
return findRootValueForTupleTempAllocation(asi, state);
24712472
return findRootValueForNonTupleTempAllocation(asi, state);
24722473
}
2474+
2475+
SILType getTypeOfLoadOfArrayOperandStorage(SILValue val) {
2476+
// The projection should look something like this:
2477+
// %29 = struct_element_addr %28 : $*Array<UInt8>, #Array._buffer
2478+
// %30 = struct_element_addr %29 : $*_ArrayBuffer<UInt8>, #_ArrayBuffer._storage
2479+
// %31 = struct_element_addr %30 : $*_BridgeStorage<__ContiguousArrayStorageBase>, #_BridgeStorage.rawValue
2480+
// %32 = load %31 : $*Builtin.BridgeObject
2481+
2482+
// We can strip casts and init_existential_ref leading to a load.
2483+
if (auto initExistRef = dyn_cast<InitExistentialRefInst>(val))
2484+
val = initExistRef->getOperand();
2485+
auto ld = dyn_cast<LoadInst>(stripCasts(val));
2486+
if (!ld)
2487+
return SILType();
2488+
2489+
auto opd = ld->getOperand();
2490+
auto opdTy = opd->getType();
2491+
if (opdTy.getObjectType() !=
2492+
SILType::getBridgeObjectType(opdTy.getASTContext()))
2493+
return SILType();
2494+
2495+
auto bridgedStoragePrj = dyn_cast<StructElementAddrInst>(opd);
2496+
if (!bridgedStoragePrj)
2497+
return SILType();
2498+
2499+
auto arrayBufferStoragePrj =
2500+
dyn_cast<StructElementAddrInst>(bridgedStoragePrj->getOperand());
2501+
if (!arrayBufferStoragePrj)
2502+
return SILType();
2503+
2504+
// If successfull return _ArrayBuffer<UInt8>.
2505+
return arrayBufferStoragePrj->getOperand()->getType().getObjectType();
2506+
}
2507+
2508+
static bool isBoxTypeWithoutSideEffectsOnRelease(SILFunction *f,
2509+
DestructorAnalysis *DA,
2510+
SILType ty) {
2511+
auto silBoxedTy = ty.getSILBoxFieldType(f);
2512+
if (silBoxedTy && !DA->mayStoreToMemoryOnDestruction(silBoxedTy))
2513+
return true;
2514+
return false;
2515+
}
2516+
2517+
2518+
static bool isReleaseOfClosureWithoutSideffects(SILFunction *f,
2519+
DestructorAnalysis *DA,
2520+
SILValue opd) {
2521+
auto fnTy = dyn_cast<SILFunctionType>(opd->getType().getASTType());
2522+
if (!fnTy)
2523+
return false;
2524+
2525+
if (fnTy->isNoEscape() &&
2526+
fnTy->getRepresentation() == SILFunctionType::Representation::Thick)
2527+
return true;
2528+
2529+
auto pa = dyn_cast<PartialApplyInst>(lookThroughOwnershipInsts(opd));
2530+
if (!pa)
2531+
return false;
2532+
2533+
// Check that all captured argument types are "trivial".
2534+
for (auto &opd: pa->getArgumentOperands()) {
2535+
auto OpdTy = opd.get()->getType().getObjectType();
2536+
if (!DA->mayStoreToMemoryOnDestruction(OpdTy))
2537+
continue;
2538+
if (isBoxTypeWithoutSideEffectsOnRelease(f, DA, OpdTy))
2539+
continue;
2540+
return false;
2541+
}
2542+
2543+
return true;
2544+
}
2545+
2546+
bool swift::isDestructorSideEffectFree(SILInstruction *mayRelease,
2547+
DestructorAnalysis *DA) {
2548+
switch (mayRelease->getKind()) {
2549+
case SILInstructionKind::DestroyValueInst:
2550+
case SILInstructionKind::StrongReleaseInst:
2551+
case SILInstructionKind::ReleaseValueInst: {
2552+
auto opd = mayRelease->getOperand(0);
2553+
auto opdTy = opd->getType();
2554+
if (!DA->mayStoreToMemoryOnDestruction(opdTy))
2555+
return true;
2556+
2557+
auto arrayTy = getTypeOfLoadOfArrayOperandStorage(opd);
2558+
if (arrayTy && !DA->mayStoreToMemoryOnDestruction(arrayTy))
2559+
return true;
2560+
2561+
if (isReleaseOfClosureWithoutSideffects(mayRelease->getFunction(), DA, opd))
2562+
return true;
2563+
2564+
if (isBoxTypeWithoutSideEffectsOnRelease(mayRelease->getFunction(), DA,
2565+
opdTy))
2566+
return true;
2567+
2568+
return false;
2569+
}
2570+
case SILInstructionKind::BuiltinInst: {
2571+
auto *builtin = cast<BuiltinInst>(mayRelease);
2572+
switch (builtin->getBuiltinInfo().ID) {
2573+
case BuiltinValueKind::CopyArray:
2574+
case BuiltinValueKind::TakeArrayNoAlias:
2575+
case BuiltinValueKind::TakeArrayFrontToBack:
2576+
case BuiltinValueKind::TakeArrayBackToFront:
2577+
return true; // nothing is released, harmless regardless of type
2578+
case BuiltinValueKind::AssignCopyArrayNoAlias:
2579+
case BuiltinValueKind::AssignCopyArrayFrontToBack:
2580+
case BuiltinValueKind::AssignCopyArrayBackToFront:
2581+
case BuiltinValueKind::AssignTakeArray:
2582+
case BuiltinValueKind::DestroyArray: {
2583+
SubstitutionMap substitutions = builtin->getSubstitutions();
2584+
auto eltTy = substitutions.getReplacementTypes()[0];
2585+
return !DA->mayStoreToMemoryOnDestruction(
2586+
builtin->getFunction()->getLoweredType(eltTy));
2587+
// Only harmless if the array element type destruction is harmless.
2588+
}
2589+
default:
2590+
break;
2591+
}
2592+
return false;
2593+
}
2594+
// Unhandled instruction.
2595+
default:
2596+
return false;
2597+
}
2598+
2599+
return false;
2600+
}

test/SILOptimizer/access_enforcement_opts.sil

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -976,17 +976,17 @@ bb0:
976976

977977

978978
// public func testOldToNewMapReadMayRelease() {
979-
// Checks merging 2 out of 3 scopes resulting in a larger read scope
980-
// Due to a MayRelease instruction before the 3rd scope
979+
// Checks merging 3 of 3 scopes resulting in a larger read scope
980+
// There is a MayRelease instruction but we recognize it to be harmless
981981
//
982982
// CHECK-LABEL: sil @testOldToNewMapReadMayRelease : $@convention(thin) () -> () {
983983
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
984984
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [read] [dynamic] [no_nested_conflict] [[GLOBAL]] : $*X
985985
// CHECK-NEXT: load [[BEGIN]] : $*X
986986
// CHECK-NEXT: load [[BEGIN]] : $*X
987-
// CHECK-NEXT: end_access [[BEGIN]] : $*X
988987
// CHECK-NEXT: strong_release
989-
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [read] [dynamic] [no_nested_conflict] [[GLOBAL]] : $*X
988+
// CHECK-NEXT: load [[BEGIN]] : $*X
989+
// CHECK-NEXT: end_access [[BEGIN]] : $*X
990990
// CHECK-LABEL: } // end sil function 'testOldToNewMapReadMayRelease'
991991
sil @testOldToNewMapReadMayRelease : $@convention(thin) () -> () {
992992
bb0:
@@ -1711,3 +1711,32 @@ bb0(%0 : $RefElemNoConflictClass, %1 : $any Error):
17111711
%10 = tuple ()
17121712
return %10 : $()
17131713
}
1714+
1715+
public class MayHaveDeinit {
1716+
deinit
1717+
}
1718+
1719+
sil @releaseArg : $@convention(thin) (@owned MayHaveDeinit) -> () {
1720+
bb0(%0 : $MayHaveDeinit):
1721+
release_value %0 : $MayHaveDeinit
1722+
%2 = tuple ()
1723+
return %2 : $()
1724+
}
1725+
1726+
sil @testSummarizeFunction : $@convention(method) (@guaranteed TestClass, @owned MayHaveDeinit) -> () {
1727+
bb0(%0 : $TestClass, %1: $MayHaveDeinit):
1728+
%2 = ref_element_addr %0 : $TestClass, #TestClass.flags
1729+
%3 = begin_access [modify] [dynamic] %2 : $*Int64
1730+
%4 = integer_literal $Builtin.Int64, 3
1731+
%5 = struct $Int64 (%4 : $Builtin.Int64)
1732+
store %5 to %3 : $*Int64
1733+
%6 = function_ref @releaseArg : $@convention(thin) (@owned MayHaveDeinit) -> ()
1734+
%7 = apply %6(%1) : $@convention(thin) (@owned MayHaveDeinit) -> ()
1735+
end_access %3 : $*Int64
1736+
%12 = tuple ()
1737+
return %12 : $()
1738+
}
1739+
1740+
// CHECK-LABEL: sil @testSummarizeFunction
1741+
// CHECK-NOT: begin_access {{.*}}no_nested_conflict
1742+
// CHECK: } // end sil function 'testSummarizeFunction'

test/SILOptimizer/access_enforcement_opts_ossa.sil

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1006,9 +1006,9 @@ bb0:
10061006
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [read] [dynamic] [no_nested_conflict] [[GLOBAL]] : $*X
10071007
// CHECK-NEXT: load [trivial] [[BEGIN]] : $*X
10081008
// CHECK-NEXT: load [trivial] [[BEGIN]] : $*X
1009-
// CHECK-NEXT: end_access [[BEGIN]] : $*X
10101009
// CHECK-NEXT: destroy_value
1011-
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [read] [dynamic] [no_nested_conflict] [[GLOBAL]] : $*X
1010+
// CHECK-NEXT: load [trivial] [[BEGIN]] : $*X
1011+
// CHECK-NEXT: end_access [[BEGIN]] : $*X
10121012
// CHECK-LABEL: } // end sil function 'testOldToNewMapReadMayRelease'
10131013
sil [ossa] @testOldToNewMapReadMayRelease : $@convention(thin) () -> () {
10141014
bb0:

0 commit comments

Comments
 (0)