Skip to content

Commit ed32270

Browse files
Merge pull request swiftlang#79191 from aschwaighofer/access_enforcement_fixes
AccessEnforcement: Fix analysis to include mayReleases as potentially executing unknown code
2 parents 9e218fc + 7a251af commit ed32270

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 *)
@@ -621,6 +622,9 @@ bool findUnreferenceableStorage(StructDecl *decl, SILType structType,
621622

622623
SILValue getInitOfTemporaryAllocStack(AllocStackInst *asi);
623624

625+
bool isDestructorSideEffectFree(SILInstruction *mayRelease,
626+
DestructorAnalysis *DA);
627+
624628
} // end namespace swift
625629

626630
#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)