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
This commit is contained in:
Arnold Schwaighofer
2025-02-02 10:05:18 -08:00
parent c95c45201e
commit 7a251af60c
12 changed files with 371 additions and 23 deletions

View File

@@ -50,6 +50,8 @@ KNOWN_STDLIB_TYPE_DECL(StaticString, NominalTypeDecl, 0)
KNOWN_STDLIB_TYPE_DECL(Substring, NominalTypeDecl, 0) KNOWN_STDLIB_TYPE_DECL(Substring, NominalTypeDecl, 0)
KNOWN_STDLIB_TYPE_DECL(Array, NominalTypeDecl, 1) KNOWN_STDLIB_TYPE_DECL(Array, NominalTypeDecl, 1)
KNOWN_STDLIB_TYPE_DECL(ArraySlice, NominalTypeDecl, 1) KNOWN_STDLIB_TYPE_DECL(ArraySlice, NominalTypeDecl, 1)
KNOWN_STDLIB_TYPE_DECL(_ArrayBuffer, NominalTypeDecl, 1)
KNOWN_STDLIB_TYPE_DECL(_ContiguousArrayBuffer, NominalTypeDecl, 1)
KNOWN_STDLIB_TYPE_DECL(_ContiguousArrayStorage, ClassDecl, 1) KNOWN_STDLIB_TYPE_DECL(_ContiguousArrayStorage, ClassDecl, 1)
KNOWN_STDLIB_TYPE_DECL(Set, NominalTypeDecl, 1) KNOWN_STDLIB_TYPE_DECL(Set, NominalTypeDecl, 1)
KNOWN_STDLIB_TYPE_DECL(Sequence, NominalTypeDecl, 1) KNOWN_STDLIB_TYPE_DECL(Sequence, NominalTypeDecl, 1)

View File

@@ -31,6 +31,7 @@
namespace swift { namespace swift {
class BasicCalleeAnalysis; class BasicCalleeAnalysis;
class DestructorAnalysis;
/// Information about a formal access within a function pertaining to a /// Information about a formal access within a function pertaining to a
/// particular AccessStorage location. /// particular AccessStorage location.
@@ -202,7 +203,7 @@ public:
/// Record any access scopes entered by the given single SIL instruction. 'I' /// Record any access scopes entered by the given single SIL instruction. 'I'
/// must not be a FullApply; use mergeFromApply instead. /// must not be a FullApply; use mergeFromApply instead.
void analyzeInstruction(SILInstruction *I); void analyzeInstruction(SILInstruction *I, DestructorAnalysis *DA);
void print(raw_ostream &os) const; void print(raw_ostream &os) const;
void dump() const; void dump() const;
@@ -317,8 +318,8 @@ public:
/// Analyze the side-effects of a single SIL instruction \p I. /// Analyze the side-effects of a single SIL instruction \p I.
/// Visited callees are added to \p BottomUpOrder until \p RecursionDepth /// Visited callees are added to \p BottomUpOrder until \p RecursionDepth
/// reaches MaxRecursionDepth. /// reaches MaxRecursionDepth.
void analyzeInstruction(SILInstruction *I) { void analyzeInstruction(SILInstruction *I, DestructorAnalysis *DA) {
accessResult.analyzeInstruction(I); accessResult.analyzeInstruction(I, DA);
} }
void print(raw_ostream &os) const { accessResult.print(os); } void print(raw_ostream &os) const { accessResult.print(os); }
@@ -380,6 +381,10 @@ class AccessStorageAnalysis : public BottomUpIPAnalysis {
/// Callee analysis, used for determining the callees at call sites. /// Callee analysis, used for determining the callees at call sites.
BasicCalleeAnalysis *BCA; BasicCalleeAnalysis *BCA;
/// Destructor analysis, used for determined which releases are harmless wrt
/// to their side-effects.
DestructorAnalysis *DA;
public: public:
AccessStorageAnalysis() AccessStorageAnalysis()
: BottomUpIPAnalysis(SILAnalysisKind::AccessStorage) {} : BottomUpIPAnalysis(SILAnalysisKind::AccessStorage) {}
@@ -412,6 +417,8 @@ public:
BasicCalleeAnalysis *getBasicCalleeAnalysis() { return BCA; } BasicCalleeAnalysis *getBasicCalleeAnalysis() { return BCA; }
DestructorAnalysis *getDestructorAnalysis() { return DA; }
virtual void initialize(SILPassManager *PM) override; virtual void initialize(SILPassManager *PM) override;
/// Invalidate all information in this analysis. /// Invalidate all information in this analysis.

View File

@@ -35,6 +35,7 @@ namespace swift {
class DominanceInfo; class DominanceInfo;
class DeadEndBlocks; class DeadEndBlocks;
class BasicCalleeAnalysis; class BasicCalleeAnalysis;
class DestructorAnalysis;
template <class T> class NullablePtr; template <class T> class NullablePtr;
/// Transform a Use Range (Operand*) into a User Range (SILInstruction *) /// Transform a Use Range (Operand*) into a User Range (SILInstruction *)
@@ -626,6 +627,9 @@ bool findUnreferenceableStorage(StructDecl *decl, SILType structType,
SILValue getInitOfTemporaryAllocStack(AllocStackInst *asi); SILValue getInitOfTemporaryAllocStack(AllocStackInst *asi);
bool isDestructorSideEffectFree(SILInstruction *mayRelease,
DestructorAnalysis *DA);
} // end namespace swift } // end namespace swift
#endif // SWIFT_SILOPTIMIZER_UTILS_INSTOPTUTILS_H #endif // SWIFT_SILOPTIMIZER_UTILS_INSTOPTUTILS_H

View File

@@ -15,7 +15,9 @@
#include "swift/Basic/Assertions.h" #include "swift/Basic/Assertions.h"
#include "swift/SILOptimizer/Analysis/AccessStorageAnalysis.h" #include "swift/SILOptimizer/Analysis/AccessStorageAnalysis.h"
#include "swift/SILOptimizer/Analysis/BasicCalleeAnalysis.h" #include "swift/SILOptimizer/Analysis/BasicCalleeAnalysis.h"
#include "swift/SILOptimizer/Analysis/DestructorAnalysis.h"
#include "swift/SILOptimizer/Analysis/FunctionOrder.h" #include "swift/SILOptimizer/Analysis/FunctionOrder.h"
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
#include "swift/SILOptimizer/PassManager/PassManager.h" #include "swift/SILOptimizer/PassManager/PassManager.h"
using namespace swift; using namespace swift;
@@ -313,13 +315,16 @@ void AccessStorageResult::visitBeginAccess(B *beginAccess) {
result.first->mergeFrom(storageAccess); result.first->mergeFrom(storageAccess);
} }
void AccessStorageResult::analyzeInstruction(SILInstruction *I) { void AccessStorageResult::analyzeInstruction(SILInstruction *I,
DestructorAnalysis *DA) {
assert(!FullApplySite::isa(I) && "caller should merge"); assert(!FullApplySite::isa(I) && "caller should merge");
if (auto *BAI = dyn_cast<BeginAccessInst>(I)) if (auto *BAI = dyn_cast<BeginAccessInst>(I))
visitBeginAccess(BAI); visitBeginAccess(BAI);
else if (auto *BUAI = dyn_cast<BeginUnpairedAccessInst>(I)) else if (auto *BUAI = dyn_cast<BeginUnpairedAccessInst>(I))
visitBeginAccess(BUAI); visitBeginAccess(BUAI);
else if (I->mayRelease() && !isDestructorSideEffectFree(I, DA))
setWorstEffects();
} }
void StorageAccessInfo::print(raw_ostream &os) const { void StorageAccessInfo::print(raw_ostream &os) const {
@@ -393,6 +398,7 @@ bool FunctionAccessStorage::summarizeCall(FullApplySite fullApply) {
void AccessStorageAnalysis::initialize( void AccessStorageAnalysis::initialize(
SILPassManager *PM) { SILPassManager *PM) {
BCA = PM->getAnalysis<BasicCalleeAnalysis>(); BCA = PM->getAnalysis<BasicCalleeAnalysis>();
DA = PM->getAnalysis<DestructorAnalysis>();
} }
void AccessStorageAnalysis::invalidate() { void AccessStorageAnalysis::invalidate() {
@@ -449,7 +455,7 @@ void AccessStorageAnalysis::analyzeFunction(
if (auto fullApply = FullApplySite::isa(&I)) if (auto fullApply = FullApplySite::isa(&I))
analyzeCall(functionInfo, fullApply, bottomUpOrder, recursionDepth); analyzeCall(functionInfo, fullApply, bottomUpOrder, recursionDepth);
else else
functionInfo->functionEffects.analyzeInstruction(&I); functionInfo->functionEffects.analyzeInstruction(&I, DA);
} }
} }
LLVM_DEBUG(llvm::dbgs() << " << finished " << F->getName() << '\n'); LLVM_DEBUG(llvm::dbgs() << " << finished " << F->getName() << '\n');

View File

@@ -43,6 +43,13 @@ bool DestructorAnalysis::cacheResult(CanType Type, bool Result) {
return Result; return Result;
} }
static bool isKnownSafeStdlibContainerType(CanType Ty) {
return Ty->isArray() ||
Ty->is_ArrayBuffer() ||
Ty->is_ContiguousArrayBuffer() ||
Ty->isDictionary();
}
bool DestructorAnalysis::isSafeType(CanType Ty) { bool DestructorAnalysis::isSafeType(CanType Ty) {
// Don't visit types twice. // Don't visit types twice.
auto CachedRes = Cached.find(Ty); auto CachedRes = Cached.find(Ty);
@@ -68,7 +75,8 @@ bool DestructorAnalysis::isSafeType(CanType Ty) {
// * or all stored properties are safe types. // * or all stored properties are safe types.
if (auto *Struct = Ty->getStructOrBoundGenericStruct()) { if (auto *Struct = Ty->getStructOrBoundGenericStruct()) {
if (implementsDestructorSafeContainerProtocol(Struct) && if ((implementsDestructorSafeContainerProtocol(Struct) ||
isKnownSafeStdlibContainerType(Ty)) &&
areTypeParametersSafe(Ty)) areTypeParametersSafe(Ty))
return cacheResult(Ty, true); return cacheResult(Ty, true);

View File

@@ -88,6 +88,7 @@
#include "swift/SILOptimizer/Analysis/DominanceAnalysis.h" #include "swift/SILOptimizer/Analysis/DominanceAnalysis.h"
#include "swift/SILOptimizer/Analysis/LoopRegionAnalysis.h" #include "swift/SILOptimizer/Analysis/LoopRegionAnalysis.h"
#include "swift/SILOptimizer/PassManager/Transforms.h" #include "swift/SILOptimizer/PassManager/Transforms.h"
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
#include "swift/SILOptimizer/Utils/InstructionDeleter.h" #include "swift/SILOptimizer/Utils/InstructionDeleter.h"
#include "swift/SILOptimizer/Utils/OwnershipOptUtils.h" #include "swift/SILOptimizer/Utils/OwnershipOptUtils.h"
#include "llvm/ADT/MapVector.h" #include "llvm/ADT/MapVector.h"
@@ -633,7 +634,7 @@ void AccessConflictAndMergeAnalysis::propagateAccessSetsBottomUp(
} }
// FIXME: Treat may-release conservatively in the analysis itself by // FIXME: Treat may-release conservatively in the analysis itself by
// adding a mayRelease flag, in addition to the unidentified flag. // adding a mayRelease flag, in addition to the unidentified flag.
accessResult.analyzeInstruction(&instr); accessResult.analyzeInstruction(&instr, ASA->getDestructorAnalysis());
} }
} }
} }
@@ -838,7 +839,8 @@ void AccessConflictAndMergeAnalysis::localDataFlowInBlock(RegionState &state,
visitFullApply(fullApply, state); visitFullApply(fullApply, state);
continue; continue;
} }
if (instr.mayRelease()) { if (instr.mayRelease() &&
!isDestructorSideEffectFree(&instr, ASA->getDestructorAnalysis())) {
visitMayRelease(&instr, state); visitMayRelease(&instr, state);
} }
} }

View File

@@ -35,6 +35,7 @@
#include "swift/SILOptimizer/Analysis/ArraySemantic.h" #include "swift/SILOptimizer/Analysis/ArraySemantic.h"
#include "swift/SILOptimizer/Analysis/BasicCalleeAnalysis.h" #include "swift/SILOptimizer/Analysis/BasicCalleeAnalysis.h"
#include "swift/SILOptimizer/Analysis/DominanceAnalysis.h" #include "swift/SILOptimizer/Analysis/DominanceAnalysis.h"
#include "swift/SILOptimizer/Analysis/DestructorAnalysis.h"
#include "swift/SILOptimizer/OptimizerBridging.h" #include "swift/SILOptimizer/OptimizerBridging.h"
#include "swift/SILOptimizer/Utils/CFGOptUtils.h" #include "swift/SILOptimizer/Utils/CFGOptUtils.h"
#include "swift/SILOptimizer/Utils/DebugOptUtils.h" #include "swift/SILOptimizer/Utils/DebugOptUtils.h"
@@ -2470,3 +2471,130 @@ SILValue swift::getInitOfTemporaryAllocStack(AllocStackInst *asi) {
return findRootValueForTupleTempAllocation(asi, state); return findRootValueForTupleTempAllocation(asi, state);
return findRootValueForNonTupleTempAllocation(asi, state); return findRootValueForNonTupleTempAllocation(asi, state);
} }
SILType getTypeOfLoadOfArrayOperandStorage(SILValue val) {
// The projection should look something like this:
// %29 = struct_element_addr %28 : $*Array<UInt8>, #Array._buffer
// %30 = struct_element_addr %29 : $*_ArrayBuffer<UInt8>, #_ArrayBuffer._storage
// %31 = struct_element_addr %30 : $*_BridgeStorage<__ContiguousArrayStorageBase>, #_BridgeStorage.rawValue
// %32 = load %31 : $*Builtin.BridgeObject
// We can strip casts and init_existential_ref leading to a load.
if (auto initExistRef = dyn_cast<InitExistentialRefInst>(val))
val = initExistRef->getOperand();
auto ld = dyn_cast<LoadInst>(stripCasts(val));
if (!ld)
return SILType();
auto opd = ld->getOperand();
auto opdTy = opd->getType();
if (opdTy.getObjectType() !=
SILType::getBridgeObjectType(opdTy.getASTContext()))
return SILType();
auto bridgedStoragePrj = dyn_cast<StructElementAddrInst>(opd);
if (!bridgedStoragePrj)
return SILType();
auto arrayBufferStoragePrj =
dyn_cast<StructElementAddrInst>(bridgedStoragePrj->getOperand());
if (!arrayBufferStoragePrj)
return SILType();
// If successfull return _ArrayBuffer<UInt8>.
return arrayBufferStoragePrj->getOperand()->getType().getObjectType();
}
static bool isBoxTypeWithoutSideEffectsOnRelease(SILFunction *f,
DestructorAnalysis *DA,
SILType ty) {
auto silBoxedTy = ty.getSILBoxFieldType(f);
if (silBoxedTy && !DA->mayStoreToMemoryOnDestruction(silBoxedTy))
return true;
return false;
}
static bool isReleaseOfClosureWithoutSideffects(SILFunction *f,
DestructorAnalysis *DA,
SILValue opd) {
auto fnTy = dyn_cast<SILFunctionType>(opd->getType().getASTType());
if (!fnTy)
return false;
if (fnTy->isNoEscape() &&
fnTy->getRepresentation() == SILFunctionType::Representation::Thick)
return true;
auto pa = dyn_cast<PartialApplyInst>(lookThroughOwnershipInsts(opd));
if (!pa)
return false;
// Check that all captured argument types are "trivial".
for (auto &opd: pa->getArgumentOperands()) {
auto OpdTy = opd.get()->getType().getObjectType();
if (!DA->mayStoreToMemoryOnDestruction(OpdTy))
continue;
if (isBoxTypeWithoutSideEffectsOnRelease(f, DA, OpdTy))
continue;
return false;
}
return true;
}
bool swift::isDestructorSideEffectFree(SILInstruction *mayRelease,
DestructorAnalysis *DA) {
switch (mayRelease->getKind()) {
case SILInstructionKind::DestroyValueInst:
case SILInstructionKind::StrongReleaseInst:
case SILInstructionKind::ReleaseValueInst: {
auto opd = mayRelease->getOperand(0);
auto opdTy = opd->getType();
if (!DA->mayStoreToMemoryOnDestruction(opdTy))
return true;
auto arrayTy = getTypeOfLoadOfArrayOperandStorage(opd);
if (arrayTy && !DA->mayStoreToMemoryOnDestruction(arrayTy))
return true;
if (isReleaseOfClosureWithoutSideffects(mayRelease->getFunction(), DA, opd))
return true;
if (isBoxTypeWithoutSideEffectsOnRelease(mayRelease->getFunction(), DA,
opdTy))
return true;
return false;
}
case SILInstructionKind::BuiltinInst: {
auto *builtin = cast<BuiltinInst>(mayRelease);
switch (builtin->getBuiltinInfo().ID) {
case BuiltinValueKind::CopyArray:
case BuiltinValueKind::TakeArrayNoAlias:
case BuiltinValueKind::TakeArrayFrontToBack:
case BuiltinValueKind::TakeArrayBackToFront:
return true; // nothing is released, harmless regardless of type
case BuiltinValueKind::AssignCopyArrayNoAlias:
case BuiltinValueKind::AssignCopyArrayFrontToBack:
case BuiltinValueKind::AssignCopyArrayBackToFront:
case BuiltinValueKind::AssignTakeArray:
case BuiltinValueKind::DestroyArray: {
SubstitutionMap substitutions = builtin->getSubstitutions();
auto eltTy = substitutions.getReplacementTypes()[0];
return !DA->mayStoreToMemoryOnDestruction(
builtin->getFunction()->getLoweredType(eltTy));
// Only harmless if the array element type destruction is harmless.
}
default:
break;
}
return false;
}
// Unhandled instruction.
default:
return false;
}
return false;
}

View File

@@ -976,17 +976,17 @@ bb0:
// public func testOldToNewMapReadMayRelease() { // public func testOldToNewMapReadMayRelease() {
// Checks merging 2 out of 3 scopes resulting in a larger read scope // Checks merging 3 of 3 scopes resulting in a larger read scope
// Due to a MayRelease instruction before the 3rd scope // There is a MayRelease instruction but we recognize it to be harmless
// //
// CHECK-LABEL: sil @testOldToNewMapReadMayRelease : $@convention(thin) () -> () { // CHECK-LABEL: sil @testOldToNewMapReadMayRelease : $@convention(thin) () -> () {
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X // CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [read] [dynamic] [no_nested_conflict] [[GLOBAL]] : $*X // CHECK-NEXT: [[BEGIN:%.*]] = begin_access [read] [dynamic] [no_nested_conflict] [[GLOBAL]] : $*X
// CHECK-NEXT: load [[BEGIN]] : $*X // CHECK-NEXT: load [[BEGIN]] : $*X
// CHECK-NEXT: load [[BEGIN]] : $*X // CHECK-NEXT: load [[BEGIN]] : $*X
// CHECK-NEXT: end_access [[BEGIN]] : $*X
// CHECK-NEXT: strong_release // CHECK-NEXT: strong_release
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [read] [dynamic] [no_nested_conflict] [[GLOBAL]] : $*X // CHECK-NEXT: load [[BEGIN]] : $*X
// CHECK-NEXT: end_access [[BEGIN]] : $*X
// CHECK-LABEL: } // end sil function 'testOldToNewMapReadMayRelease' // CHECK-LABEL: } // end sil function 'testOldToNewMapReadMayRelease'
sil @testOldToNewMapReadMayRelease : $@convention(thin) () -> () { sil @testOldToNewMapReadMayRelease : $@convention(thin) () -> () {
bb0: bb0:
@@ -1711,3 +1711,32 @@ bb0(%0 : $RefElemNoConflictClass, %1 : $any Error):
%10 = tuple () %10 = tuple ()
return %10 : $() return %10 : $()
} }
public class MayHaveDeinit {
deinit
}
sil @releaseArg : $@convention(thin) (@owned MayHaveDeinit) -> () {
bb0(%0 : $MayHaveDeinit):
release_value %0 : $MayHaveDeinit
%2 = tuple ()
return %2 : $()
}
sil @testSummarizeFunction : $@convention(method) (@guaranteed TestClass, @owned MayHaveDeinit) -> () {
bb0(%0 : $TestClass, %1: $MayHaveDeinit):
%2 = ref_element_addr %0 : $TestClass, #TestClass.flags
%3 = begin_access [modify] [dynamic] %2 : $*Int64
%4 = integer_literal $Builtin.Int64, 3
%5 = struct $Int64 (%4 : $Builtin.Int64)
store %5 to %3 : $*Int64
%6 = function_ref @releaseArg : $@convention(thin) (@owned MayHaveDeinit) -> ()
%7 = apply %6(%1) : $@convention(thin) (@owned MayHaveDeinit) -> ()
end_access %3 : $*Int64
%12 = tuple ()
return %12 : $()
}
// CHECK-LABEL: sil @testSummarizeFunction
// CHECK-NOT: begin_access {{.*}}no_nested_conflict
// CHECK: } // end sil function 'testSummarizeFunction'

View File

@@ -1006,9 +1006,9 @@ bb0:
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [read] [dynamic] [no_nested_conflict] [[GLOBAL]] : $*X // CHECK-NEXT: [[BEGIN:%.*]] = begin_access [read] [dynamic] [no_nested_conflict] [[GLOBAL]] : $*X
// CHECK-NEXT: load [trivial] [[BEGIN]] : $*X // CHECK-NEXT: load [trivial] [[BEGIN]] : $*X
// CHECK-NEXT: load [trivial] [[BEGIN]] : $*X // CHECK-NEXT: load [trivial] [[BEGIN]] : $*X
// CHECK-NEXT: end_access [[BEGIN]] : $*X
// CHECK-NEXT: destroy_value // CHECK-NEXT: destroy_value
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [read] [dynamic] [no_nested_conflict] [[GLOBAL]] : $*X // CHECK-NEXT: load [trivial] [[BEGIN]] : $*X
// CHECK-NEXT: end_access [[BEGIN]] : $*X
// CHECK-LABEL: } // end sil function 'testOldToNewMapReadMayRelease' // CHECK-LABEL: } // end sil function 'testOldToNewMapReadMayRelease'
sil [ossa] @testOldToNewMapReadMayRelease : $@convention(thin) () -> () { sil [ossa] @testOldToNewMapReadMayRelease : $@convention(thin) () -> () {
bb0: bb0:

View File

@@ -711,3 +711,88 @@ bb0:
end_access %5 : $*Builtin.Int64 end_access %5 : $*Builtin.Int64
return %6 : $Builtin.Int64 return %6 : $Builtin.Int64
} }
// CHECK: @test_harmless_regardless_of_type_pod
// CHECK-NEXT: @test_harmless_regardless_of_type_ref
// CHECK-NEXT: @test_harmless_typedependent
// CHECK-NEXT: @test_not_harmless_typedependent
// CHECK-NEXT: unidentified accesses: modify
// CHECK-NEXT: @test_not_harmless_typedependent_2
// CHECK-NEXT: unidentified accesses: modify
// CHECK-NEXT: @test_not_harmless_typedependent_3
// CHECK-NEXT: unidentified accesses: modify
// CHECK-NEXT: @test_not_harmless_typedependent_4
// CHECK-NEXT: unidentified accesses: modify
// CHECK-NEXT: @test_not_harmless_typedependent_5
// CHECK-NEXT: unidentified accesses: modify
sil hidden [transparent] @test_harmless_regardless_of_type_pod : $@convention(thin) (Builtin.RawPointer, Builtin.RawPointer, Builtin.Word) -> () {
bb0(%0 : $Builtin.RawPointer, %1: $Builtin.RawPointer, %2: $Builtin.Word):
%3 = metatype $@thin UInt8.Type
%4 = builtin "copyArray"<UInt8>(%3 : $@thin UInt8.Type, %0 : $Builtin.RawPointer, %1 : $Builtin.RawPointer, %2 : $Builtin.Word) : $()
%5 = builtin "takeArrayFrontToBack"<UInt8>(%3 : $@thin UInt8.Type, %0 : $Builtin.RawPointer, %1 : $Builtin.RawPointer, %2 : $Builtin.Word) : $()
%6 = builtin "takeArrayBackToFront"<UInt8>(%3 : $@thin UInt8.Type, %0 : $Builtin.RawPointer, %1 : $Builtin.RawPointer, %2 : $Builtin.Word) : $()
%7 = builtin "takeArrayNoAlias"<UInt8>(%3 : $@thin UInt8.Type, %0 : $Builtin.RawPointer, %1 : $Builtin.RawPointer, %2 : $Builtin.Word) : $()
%t = tuple()
return %t: $()
}
sil hidden [transparent] @test_harmless_regardless_of_type_ref : $@convention(thin) (Builtin.RawPointer, Builtin.RawPointer, Builtin.Word) -> () {
bb0(%0 : $Builtin.RawPointer, %1: $Builtin.RawPointer, %2: $Builtin.Word):
%3 = metatype $@thin C.Type
%4 = builtin "copyArray"<C>(%3 : $@thin C.Type, %0 : $Builtin.RawPointer, %1 : $Builtin.RawPointer, %2 : $Builtin.Word) : $()
%5 = builtin "takeArrayFrontToBack"<C>(%3 : $@thin C.Type, %0 : $Builtin.RawPointer, %1 : $Builtin.RawPointer, %2 : $Builtin.Word) : $()
%6 = builtin "takeArrayBackToFront"<C>(%3 : $@thin C.Type, %0 : $Builtin.RawPointer, %1 : $Builtin.RawPointer, %2 : $Builtin.Word) : $()
%7 = builtin "takeArrayNoAlias"<C>(%3 : $@thin C.Type, %0 : $Builtin.RawPointer, %1 : $Builtin.RawPointer, %2 : $Builtin.Word) : $()
%t = tuple()
return %t: $()
}
sil hidden [transparent] @test_harmless_typedependent : $@convention(thin) (Builtin.RawPointer, Builtin.RawPointer, Builtin.Word) -> () {
bb0(%0 : $Builtin.RawPointer, %1: $Builtin.RawPointer, %2: $Builtin.Word):
%3 = metatype $@thin UInt8.Type
%8 = builtin "assignCopyArrayNoAlias"<UInt8>(%3 : $@thin UInt8.Type, %0 : $Builtin.RawPointer, %1 : $Builtin.RawPointer, %2 : $Builtin.Word) : $()
%9 = builtin "assignCopyArrayFrontToBack"<UInt8>(%3 : $@thin UInt8.Type, %0 : $Builtin.RawPointer, %1 : $Builtin.RawPointer, %2 : $Builtin.Word) : $()
%10 = builtin "assignCopyArrayBackToFront"<UInt8>(%3 : $@thin UInt8.Type, %0 : $Builtin.RawPointer, %1 : $Builtin.RawPointer, %2 : $Builtin.Word) : $()
%11 = builtin "assignTakeArray"<UInt8>(%3 : $@thin UInt8.Type, %0 : $Builtin.RawPointer, %1 : $Builtin.RawPointer, %2 : $Builtin.Word) : $()
%12 = builtin "destroyArray"<UInt8>(%3 : $@thin UInt8.Type, %0 : $Builtin.RawPointer, %2 : $Builtin.Word) : $()
%t = tuple()
return %t: $()
}
sil hidden [transparent] @test_not_harmless_typedependent : $@convention(thin) (Builtin.RawPointer, Builtin.RawPointer, Builtin.Word) -> () {
bb0(%0 : $Builtin.RawPointer, %1: $Builtin.RawPointer, %2: $Builtin.Word):
%3 = metatype $@thin C.Type
%8 = builtin "assignCopyArrayNoAlias"<C>(%3 : $@thin C.Type, %0 : $Builtin.RawPointer, %1 : $Builtin.RawPointer, %2 : $Builtin.Word) : $()
%t = tuple()
return %t: $()
}
sil hidden [transparent] @test_not_harmless_typedependent_2 : $@convention(thin) (Builtin.RawPointer, Builtin.RawPointer, Builtin.Word) -> () {
bb0(%0 : $Builtin.RawPointer, %1: $Builtin.RawPointer, %2: $Builtin.Word):
%3 = metatype $@thin C.Type
%9 = builtin "assignCopyArrayFrontToBack"<C>(%3 : $@thin C.Type, %0 : $Builtin.RawPointer, %1 : $Builtin.RawPointer, %2 : $Builtin.Word) : $()
%t = tuple()
return %t: $()
}
sil hidden [transparent] @test_not_harmless_typedependent_3 : $@convention(thin) (Builtin.RawPointer, Builtin.RawPointer, Builtin.Word) -> () {
bb0(%0 : $Builtin.RawPointer, %1: $Builtin.RawPointer, %2: $Builtin.Word):
%3 = metatype $@thin C.Type
%10 = builtin "assignCopyArrayBackToFront"<C>(%3 : $@thin C.Type, %0 : $Builtin.RawPointer, %1 : $Builtin.RawPointer, %2 : $Builtin.Word) : $()
%t = tuple()
return %t: $()
}
sil hidden [transparent] @test_not_harmless_typedependent_4 : $@convention(thin) (Builtin.RawPointer, Builtin.RawPointer, Builtin.Word) -> () {
bb0(%0 : $Builtin.RawPointer, %1: $Builtin.RawPointer, %2: $Builtin.Word):
%3 = metatype $@thin C.Type
%11 = builtin "assignTakeArray"<C>(%3 : $@thin C.Type, %0 : $Builtin.RawPointer, %1 : $Builtin.RawPointer, %2 : $Builtin.Word) : $()
%t = tuple()
return %t: $()
}
sil hidden [transparent] @test_not_harmless_typedependent_5 : $@convention(thin) (Builtin.RawPointer, Builtin.RawPointer, Builtin.Word) -> () {
bb0(%0 : $Builtin.RawPointer, %1: $Builtin.RawPointer, %2: $Builtin.Word):
%3 = metatype $@thin C.Type
%12 = builtin "destroyArray"<C>(%3 : $@thin C.Type, %0 : $Builtin.RawPointer, %2 : $Builtin.Word) : $()
%t = tuple()
return %t: $()
}

View File

@@ -0,0 +1,80 @@
// RUN: %target-sil-opt %s -access-storage-analysis-dump -enable-sil-verify-all -o /dev/null | %FileCheck %s
// This test depends on the _ArrayBuffer type.
// REQUIRES: OS=macosx || OS=iphoneos
sil_stage canonical
import Builtin
import Swift
import SwiftShims
class UnsafeThing {
deinit
}
class Container {
var fld : [UInt8]
}
sil_vtable Container {}
class Container2 {
var fld : [UnsafeThing]
}
sil_vtable Container2 {}
// CHECK-LABEL: @release_safe_array
// CHECK-NOT: unidentified accesses: modify
sil @release_safe_array : $@convention(thin) (@guaranteed Container) -> () {
bb0(%0 : $Container):
%1 = ref_element_addr %0 : $Container, #Container.fld
%2 = struct_element_addr %1 : $*Array<UInt8>, #Array._buffer
%3 = struct_element_addr %2 : $*_ArrayBuffer<UInt8>, #_ArrayBuffer._storage
%4 = struct_element_addr %3 : $*_BridgeStorage<__ContiguousArrayStorageBase>, #_BridgeStorage.rawValue
%5 = load %4 : $*Builtin.BridgeObject
strong_release %5: $Builtin.BridgeObject
%7 = unchecked_ref_cast %5 : $Builtin.BridgeObject to $__ContiguousArrayStorageBase
strong_release %7: $__ContiguousArrayStorageBase
%8 = init_existential_ref %7 : $__ContiguousArrayStorageBase : $__ContiguousArrayStorageBase, $AnyObject
strong_retain %8 : $AnyObject
%t = tuple ()
return %t : $()
}
// CHECK-LABEL: @release_unsafe_array
// CHECK: unidentified accesses: modify
sil @release_unsafe_array : $@convention(thin) (@guaranteed Container2) -> () {
bb0(%0 : $Container2):
%1 = ref_element_addr %0 : $Container2, #Container2.fld
%2 = struct_element_addr %1 : $*Array<UnsafeThing>, #Array._buffer
%3 = struct_element_addr %2 : $*_ArrayBuffer<UnsafeThing>, #_ArrayBuffer._storage
%4 = struct_element_addr %3 : $*_BridgeStorage<__ContiguousArrayStorageBase>, #_BridgeStorage.rawValue
%5 = load %4 : $*Builtin.BridgeObject
strong_release %5: $Builtin.BridgeObject
%t = tuple ()
return %t : $()
}
sil @someClosure : $@convention(thin) (@guaranteed UnsafeThing) -> ()
// CHECK-LABEL: @testCaptureDeinit
// CHECK: unidentified accesses: modify
// Releasing the closure will trigger the deinit of the captured class.
sil @testCaptureDeinit : $@convention(thin) (Int, @guaranteed UnsafeThing) -> () {
bb0(%0 : $Int, %u : $UnsafeThing):
%s1 = alloc_stack $Int
store %0 to %s1 : $*Int
%s2 = alloc_stack $Int
store %0 to %s2 : $*Int
%f = function_ref @someClosure : $@convention(thin) (@guaranteed UnsafeThing) -> ()
%pa = partial_apply [callee_guaranteed] %f(%u) : $@convention(thin) (@guaranteed UnsafeThing) -> ()
%access = begin_access [modify] [dynamic] %s1 : $*Int
strong_release %pa : $@callee_guaranteed () -> ()
end_access %access : $*Int
dealloc_stack %s2 : $*Int
dealloc_stack %s1 : $*Int
%v = tuple ()
return %v : $()
}

View File

@@ -372,7 +372,7 @@ bb0(%0 : @guaranteed $C, %1 : $Int):
// CHECK-LABEL: @readWriteIdentifiedClass // CHECK-LABEL: @readWriteIdentifiedClass
// CHECK: [modify] Class %1 = alloc_ref $C // CHECK: [modify] Class %1 = alloc_ref $C
// CHECK: Field: var property: Int // CHECK: Field: var property: Int
sil [ossa] @readWriteIdentifiedClass : $@convention(thin) (Int) -> (Int) { sil [ossa] @readWriteIdentifiedClass : $@convention(thin) (Int) -> @owned C {
bb0(%0 : $Int): bb0(%0 : $Int):
%1 = alloc_ref $C %1 = alloc_ref $C
%2 = function_ref @writeIdentifiedClass : $@convention(thin) (@guaranteed C, Int) -> () %2 = function_ref @writeIdentifiedClass : $@convention(thin) (@guaranteed C, Int) -> ()
@@ -383,8 +383,7 @@ bb0(%0 : $Int):
%6 = load [trivial] %5 : $*Int %6 = load [trivial] %5 : $*Int
end_access %5 : $*Int end_access %5 : $*Int
end_borrow %borrow : $C end_borrow %borrow : $C
destroy_value %1 : $C return %1 : $C
return %6 : $Int
} }
// CHECK-LABEL: @readIdentifiedNestedClass // CHECK-LABEL: @readIdentifiedNestedClass
@@ -423,7 +422,7 @@ bb0(%0 : @guaranteed $C, %1 : $Int):
// CHECK-LABEL: @readWriteIdentifiedNestedClass // CHECK-LABEL: @readWriteIdentifiedNestedClass
// CHECK: [modify] Class %1 = alloc_ref $C // CHECK: [modify] Class %1 = alloc_ref $C
// CHECK: Field: var property: Int // CHECK: Field: var property: Int
sil [ossa] @readWriteIdentifiedNestedClass : $@convention(thin) (Int) -> (Int) { sil [ossa] @readWriteIdentifiedNestedClass : $@convention(thin) (Int) -> @owned C {
bb0(%0 : $Int): bb0(%0 : $Int):
%1 = alloc_ref $C %1 = alloc_ref $C
%2 = function_ref @writeIdentifiedNestedClass : $@convention(thin) (@guaranteed C, Int) -> () %2 = function_ref @writeIdentifiedNestedClass : $@convention(thin) (@guaranteed C, Int) -> ()
@@ -436,8 +435,7 @@ bb0(%0 : $Int):
end_access %5 : $*Int end_access %5 : $*Int
end_access %6 : $*Int end_access %6 : $*Int
end_borrow %borrow : $C end_borrow %borrow : $C
destroy_value %1 : $C return %1 : $C
return %7 : $Int
} }
enum TreeB<T> { enum TreeB<T> {
@@ -447,7 +445,7 @@ enum TreeB<T> {
} }
// CHECK-LABEL: @readIndirectEnum // CHECK-LABEL: @readIndirectEnum
// CHECK: [read] Argument %1 = argument of bb0 : $*TreeB<T> // CHECK: unidentified accesses: modify
sil [ossa] @readIndirectEnum : $@convention(thin) <T> (@in TreeB<T>) -> (@out TreeB<T>) { sil [ossa] @readIndirectEnum : $@convention(thin) <T> (@in TreeB<T>) -> (@out TreeB<T>) {
bb0(%0 : $*TreeB<T>, %1 : $*TreeB<T>): bb0(%0 : $*TreeB<T>, %1 : $*TreeB<T>):
%enumAddr = unchecked_take_enum_data_addr %1 : $*TreeB<T>, #TreeB.Branch!enumelt %enumAddr = unchecked_take_enum_data_addr %1 : $*TreeB<T>, #TreeB.Branch!enumelt
@@ -465,8 +463,7 @@ bb0(%0 : $*TreeB<T>, %1 : $*TreeB<T>):
struct SomeError: Error {} struct SomeError: Error {}
// CHECK-LABEL: @writeIdentifiedArgReadUnidentifiedVarious // CHECK-LABEL: @writeIdentifiedArgReadUnidentifiedVarious
// CHECK: [modify] Argument %0 = argument of bb0 : $*Int // CHECK: unidentified accesses: modify
// CHECK: unidentified accesses: read
sil [ossa] @writeIdentifiedArgReadUnidentifiedVarious : $@convention(thin) (Int, @owned Error) -> (@out Int) { sil [ossa] @writeIdentifiedArgReadUnidentifiedVarious : $@convention(thin) (Int, @owned Error) -> (@out Int) {
bb0(%out : $*Int, %i : $Int, %e : @owned $Error): bb0(%out : $*Int, %i : $Int, %e : @owned $Error):
%argAccess = begin_access [modify] [dynamic] %out : $*Int %argAccess = begin_access [modify] [dynamic] %out : $*Int