mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
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:
@@ -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)
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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: $()
|
||||||
|
}
|
||||||
|
|||||||
80
test/SILOptimizer/access_storage_analysis_array.sil
Normal file
80
test/SILOptimizer/access_storage_analysis_array.sil
Normal 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 : $()
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user