//===--- MemAccessUtils.cpp - Utilities for SIL memory access. ------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// #define DEBUG_TYPE "sil-access-utils" #include "swift/SIL/MemAccessUtils.h" #include "swift/SIL/ApplySite.h" #include "swift/SIL/Projection.h" #include "swift/SIL/SILGlobalVariable.h" #include "swift/SIL/SILModule.h" #include "swift/SIL/SILUndef.h" using namespace swift; SILValue swift::stripAccessMarkers(SILValue v) { while (auto *bai = dyn_cast(v)) { v = bai->getOperand(); } return v; } // The resulting projection must have an address-type operand at index zero // representing the projected address. SingleValueInstruction *swift::isAccessProjection(SILValue v) { switch (v->getKind()) { default: return nullptr; case ValueKind::StructElementAddrInst: case ValueKind::TupleElementAddrInst: case ValueKind::UncheckedTakeEnumDataAddrInst: case ValueKind::TailAddrInst: case ValueKind::IndexAddrInst: return cast(v); }; } // TODO: When the optimizer stops stripping begin_access markers, then we should // be able to assert that the result is a BeginAccessInst and the default case // is unreachable. SILValue swift::getAddressAccess(SILValue v) { while (true) { assert(v->getType().isAddress()); auto *projection = isAccessProjection(v); if (!projection) return v; v = projection->getOperand(0); } } SILValue swift::getAccessedAddress(SILValue v) { while (true) { SILValue v2 = stripAccessMarkers(getAddressAccess(v)); if (v2 == v) return v; v = v2; } } bool swift::isLetAddress(SILValue accessedAddress) { assert(accessedAddress == getAccessedAddress(accessedAddress) && "caller must find the address root"); // Is this an address of a "let" class member? if (auto *rea = dyn_cast(accessedAddress)) return rea->getField()->isLet(); // Is this an address of a global "let"? if (auto *gai = dyn_cast(accessedAddress)) { auto *globalDecl = gai->getReferencedGlobal()->getDecl(); return globalDecl && globalDecl->isLet(); } return false; } AccessedStorage::AccessedStorage(SILValue base, Kind kind) { assert(base && "invalid storage base"); initKind(kind); switch (kind) { case Box: assert(isa(base)); value = base; break; case Stack: assert(isa(base)); value = base; break; case Nested: assert(isa(base)); value = base; break; case Yield: assert(isa(base)); value = base; break; case Unidentified: value = base; break; case Argument: value = base; setElementIndex(cast(base)->getIndex()); break; case Global: if (auto *GAI = dyn_cast(base)) global = GAI->getReferencedGlobal(); else { FullApplySite apply(cast(base)); auto *funcRef = apply.getReferencedFunctionOrNull(); assert(funcRef); global = getVariableOfGlobalInit(funcRef); assert(global); // Require a decl for all formally accessed globals defined in this // module. (Access of globals defined elsewhere has Unidentified storage). // AccessEnforcementWMO requires this. assert(global->getDecl()); } break; case Class: { // Do a best-effort to find the identity of the object being projected // from. It is OK to be unsound here (i.e. miss when two ref_element_addrs // actually refer the same address) because these addresses will be // dynamically checked, and static analysis will be sufficiently // conservative given that classes are not "uniquely identified". auto *REA = cast(base); value = stripBorrow(REA->getOperand()); setElementIndex(REA->getFieldNo()); } } } const ValueDecl *AccessedStorage::getDecl() const { switch (getKind()) { case Box: return cast(value)->getLoc().getAsASTNode(); case Stack: return cast(value)->getDecl(); case Global: return global->getDecl(); case Class: { auto *decl = getObject()->getType().getNominalOrBoundGenericNominal(); return decl->getStoredProperties()[getPropertyIndex()]; } case Argument: return getArgument()->getDecl(); case Yield: return nullptr; case Nested: return nullptr; case Unidentified: return nullptr; } llvm_unreachable("unhandled kind"); } const char *AccessedStorage::getKindName(AccessedStorage::Kind k) { switch (k) { case Box: return "Box"; case Stack: return "Stack"; case Nested: return "Nested"; case Unidentified: return "Unidentified"; case Argument: return "Argument"; case Yield: return "Yield"; case Global: return "Global"; case Class: return "Class"; } llvm_unreachable("unhandled kind"); } void AccessedStorage::print(raw_ostream &os) const { os << getKindName(getKind()) << " "; switch (getKind()) { case Box: case Stack: case Nested: case Yield: case Unidentified: os << value; break; case Argument: os << value; break; case Global: os << *global; break; case Class: os << getObject(); os << " Field: "; getDecl()->print(os); os << " Index: " << getPropertyIndex() << "\n"; break; } } void AccessedStorage::dump() const { print(llvm::dbgs()); } // Return true if the given apply invokes a global addressor defined in another // module. bool swift::isExternalGlobalAddressor(ApplyInst *AI) { FullApplySite apply(AI); auto *funcRef = apply.getReferencedFunctionOrNull(); if (!funcRef) return false; return funcRef->isGlobalInit() && funcRef->isExternalDeclaration(); } // Return true if the given StructExtractInst extracts the RawPointer from // Unsafe[Mutable]Pointer. bool swift::isUnsafePointerExtraction(StructExtractInst *SEI) { assert(isa(SEI->getType().getASTType())); auto &C = SEI->getModule().getASTContext(); auto *decl = SEI->getStructDecl(); return decl == C.getUnsafeMutablePointerDecl() || decl == C.getUnsafePointerDecl(); } // Given a block argument address base, check if it is actually a box projected // from a switch_enum. This is a valid pattern at any SIL stage resulting in a // block-type phi. In later SIL stages, the optimizer may form address-type // phis, causing this assert if called on those cases. void swift::checkSwitchEnumBlockArg(SILPhiArgument *arg) { assert(!arg->getType().isAddress()); SILBasicBlock *Pred = arg->getParent()->getSinglePredecessorBlock(); if (!Pred || !isa(Pred->getTerminator())) { arg->dump(); llvm_unreachable("unexpected box source."); } } /// Return true if the given address value is produced by a special address /// producer that is only used for local initialization, not formal access. bool swift::isAddressForLocalInitOnly(SILValue sourceAddr) { switch (sourceAddr->getKind()) { default: return false; // Value to address conversions: the operand is the non-address source // value. These allow local mutation of the value but should never be used // for formal access of an lvalue. case ValueKind::OpenExistentialBoxInst: case ValueKind::ProjectExistentialBoxInst: return true; // Self-evident local initialization. case ValueKind::InitEnumDataAddrInst: case ValueKind::InitExistentialAddrInst: case ValueKind::AllocExistentialBoxInst: case ValueKind::AllocValueBufferInst: case ValueKind::ProjectValueBufferInst: return true; } } namespace { // The result of an accessed storage query. A complete result produces an // AccessedStorage object, which may or may not be valid. An incomplete result // produces a SILValue representing the source address for use with deeper // queries. The source address is not necessarily a SIL address type because // the query can extend past pointer-to-address casts. class AccessedStorageResult { AccessedStorage storage; SILValue address; bool complete; explicit AccessedStorageResult(SILValue address) : address(address), complete(false) {} public: AccessedStorageResult(const AccessedStorage &storage) : storage(storage), complete(true) {} static AccessedStorageResult incomplete(SILValue address) { return AccessedStorageResult(address); } bool isComplete() const { return complete; } AccessedStorage getStorage() const { assert(complete); return storage; } SILValue getAddress() const { assert(!complete); return address; } }; struct FindAccessedStorageVisitor : public AccessUseDefChainVisitor { SmallVector addressWorklist; SmallPtrSet visitedPhis; Optional storage; public: FindAccessedStorageVisitor(SILValue firstSourceAddr) : addressWorklist({firstSourceAddr}) {} AccessedStorage doIt() { while (!addressWorklist.empty()) { visit(addressWorklist.pop_back_val()); } return storage.getValueOr(AccessedStorage()); } void setStorage(AccessedStorage foundStorage) { if (!storage) { storage = foundStorage; } else { // `storage` may still be invalid. If both `storage` and `foundStorage` // are invalid, this check passes, but we return an invalid storage // below. if (!storage->hasIdenticalBase(foundStorage)) { storage = AccessedStorage(); addressWorklist.clear(); } } } void visitBase(SILValue base, AccessedStorage::Kind kind) { setStorage(AccessedStorage(base, kind)); } void visitNonAccess(SILValue value) { setStorage(AccessedStorage()); } void visitPhi(SILPhiArgument *phiArg) { if (visitedPhis.insert(phiArg).second) { phiArg->getIncomingPhiValues(addressWorklist); } } void visitIncomplete(SILValue projectedAddr, SILValue parentAddr) { addressWorklist.push_back(parentAddr); } }; } // namespace AccessedStorage swift::findAccessedStorage(SILValue sourceAddr) { return FindAccessedStorageVisitor(sourceAddr).doIt(); } AccessedStorage swift::findAccessedStorageNonNested(SILValue sourceAddr) { while (true) { const AccessedStorage &storage = findAccessedStorage(sourceAddr); if (!storage || storage.getKind() != AccessedStorage::Nested) return storage; sourceAddr = cast(storage.getValue())->getSource(); } } // Return true if the given access is on a 'let' lvalue. bool AccessedStorage::isLetAccess(SILFunction *F) const { if (auto *decl = dyn_cast_or_null(getDecl())) return decl->isLet(); // It's unclear whether a global will ever be missing it's varDecl, but // technically we only preserve it for debug info. So if we don't have a decl, // check the flag on SILGlobalVariable, which is guaranteed valid, if (getKind() == AccessedStorage::Global) return getGlobal()->isLet(); return false; } static bool isScratchBuffer(SILValue value) { // Special case unsafe value buffer access. return value->getType().is(); } bool swift::memInstMustInitialize(Operand *memOper) { SILValue address = memOper->get(); SILInstruction *memInst = memOper->getUser(); switch (memInst->getKind()) { default: return false; case SILInstructionKind::CopyAddrInst: { auto *CAI = cast(memInst); return CAI->getDest() == address && CAI->isInitializationOfDest(); } case SILInstructionKind::InitExistentialAddrInst: case SILInstructionKind::InitEnumDataAddrInst: case SILInstructionKind::InjectEnumAddrInst: return true; case SILInstructionKind::StoreInst: return cast(memInst)->getOwnershipQualifier() == StoreOwnershipQualifier::Init; #define NEVER_OR_SOMETIMES_LOADABLE_CHECKED_REF_STORAGE(Name, ...) \ case SILInstructionKind::Store##Name##Inst: \ return cast(memInst)->isInitializationOfDest(); #include "swift/AST/ReferenceStorage.def" } } bool swift::isPossibleFormalAccessBase(const AccessedStorage &storage, SILFunction *F) { switch (storage.getKind()) { case AccessedStorage::Box: case AccessedStorage::Stack: if (isScratchBuffer(storage.getValue())) return false; break; case AccessedStorage::Global: break; case AccessedStorage::Class: break; case AccessedStorage::Yield: // Yields are accessed by the caller. return false; case AccessedStorage::Argument: // Function arguments are accessed by the caller. return false; case AccessedStorage::Nested: { // A begin_access is considered a separate base for the purpose of conflict // checking. However, for the purpose of inserting unenforced markers and // performaing verification, it needs to be ignored. auto *BAI = cast(storage.getValue()); const AccessedStorage &nestedStorage = findAccessedStorage(BAI->getSource()); if (!nestedStorage) return false; return isPossibleFormalAccessBase(nestedStorage, F); } case AccessedStorage::Unidentified: if (isAddressForLocalInitOnly(storage.getValue())) return false; if (isa(storage.getValue())) { checkSwitchEnumBlockArg(cast(storage.getValue())); return false; } // Pointer-to-address exclusivity cannot be enforced. `baseAddress` may be // pointing anywhere within an object. if (isa(storage.getValue())) return false; if (isa(storage.getValue())) return false; if (isScratchBuffer(storage.getValue())) return false; } // Additional checks that apply to anything that may fall through. // Immutable values are only accessed for initialization. if (storage.isLetAccess(F)) return false; return true; } /// Helper for visitApplyAccesses that visits address-type call arguments, /// including arguments to @noescape functions that are passed as closures to /// the current call. static void visitApplyAccesses(ApplySite apply, llvm::function_ref visitor) { for (Operand &oper : apply.getArgumentOperands()) { // Consider any address-type operand an access. Whether it is read or modify // depends on the argument convention. if (oper.get()->getType().isAddress()) { visitor(&oper); continue; } auto fnType = oper.get()->getType().getAs(); if (!fnType || !fnType->isNoEscape()) continue; // When @noescape function closures are passed as arguments, their // arguments are considered accessed at the call site. TinyPtrVector partialApplies; findClosuresForFunctionValue(oper.get(), partialApplies); // Recursively visit @noescape function closure arguments. for (auto *PAI : partialApplies) visitApplyAccesses(PAI, visitor); } } static void visitBuiltinAddress(BuiltinInst *builtin, llvm::function_ref visitor) { if (auto kind = builtin->getBuiltinKind()) { switch (kind.getValue()) { default: builtin->dump(); llvm_unreachable("unexpected builtin memory access."); // WillThrow exists for the debugger, does nothing. case BuiltinValueKind::WillThrow: return; // Buitins that affect memory but can't be formal accesses. case BuiltinValueKind::UnexpectedError: case BuiltinValueKind::ErrorInMain: case BuiltinValueKind::IsOptionalType: case BuiltinValueKind::AllocRaw: case BuiltinValueKind::DeallocRaw: case BuiltinValueKind::Fence: case BuiltinValueKind::StaticReport: case BuiltinValueKind::Once: case BuiltinValueKind::OnceWithContext: case BuiltinValueKind::Unreachable: case BuiltinValueKind::CondUnreachable: case BuiltinValueKind::DestroyArray: case BuiltinValueKind::UnsafeGuaranteed: case BuiltinValueKind::UnsafeGuaranteedEnd: case BuiltinValueKind::Swift3ImplicitObjCEntrypoint: case BuiltinValueKind::TSanInoutAccess: return; // General memory access to a pointer in first operand position. case BuiltinValueKind::CmpXChg: case BuiltinValueKind::AtomicLoad: case BuiltinValueKind::AtomicStore: case BuiltinValueKind::AtomicRMW: // Currently ignored because the access is on a RawPointer, not a // SIL address. // visitor(&builtin->getAllOperands()[0]); return; // Arrays: (T.Type, Builtin.RawPointer, Builtin.RawPointer, // Builtin.Word) case BuiltinValueKind::CopyArray: case BuiltinValueKind::TakeArrayNoAlias: case BuiltinValueKind::TakeArrayFrontToBack: case BuiltinValueKind::TakeArrayBackToFront: case BuiltinValueKind::AssignCopyArrayNoAlias: case BuiltinValueKind::AssignCopyArrayFrontToBack: case BuiltinValueKind::AssignCopyArrayBackToFront: case BuiltinValueKind::AssignTakeArray: // Currently ignored because the access is on a RawPointer. // visitor(&builtin->getAllOperands()[1]); // visitor(&builtin->getAllOperands()[2]); return; } } if (auto ID = builtin->getIntrinsicID()) { switch (ID.getValue()) { // Exhaustively verifying all LLVM instrinsics that access memory is // impractical. Instead, we call out the few common cases and return in // the default case. default: return; case llvm::Intrinsic::memcpy: case llvm::Intrinsic::memmove: // Currently ignored because the access is on a RawPointer. // visitor(&builtin->getAllOperands()[0]); // visitor(&builtin->getAllOperands()[1]); return; case llvm::Intrinsic::memset: // Currently ignored because the access is on a RawPointer. // visitor(&builtin->getAllOperands()[0]); return; } } llvm_unreachable("Must be either a builtin or intrinsic."); } void swift::visitAccessedAddress(SILInstruction *I, llvm::function_ref visitor) { assert(I->mayReadOrWriteMemory()); // Reference counting instructions do not access user visible memory. if (isa(I)) return; if (isa(I)) return; if (auto apply = FullApplySite::isa(I)) { visitApplyAccesses(apply, visitor); return; } if (auto builtin = dyn_cast(I)) { visitBuiltinAddress(builtin, visitor); return; } switch (I->getKind()) { default: I->dump(); llvm_unreachable("unexpected memory access."); case SILInstructionKind::AssignInst: case SILInstructionKind::AssignByWrapperInst: visitor(&I->getAllOperands()[AssignInst::Dest]); return; case SILInstructionKind::CheckedCastAddrBranchInst: visitor(&I->getAllOperands()[CheckedCastAddrBranchInst::Src]); visitor(&I->getAllOperands()[CheckedCastAddrBranchInst::Dest]); return; case SILInstructionKind::CopyAddrInst: visitor(&I->getAllOperands()[CopyAddrInst::Src]); visitor(&I->getAllOperands()[CopyAddrInst::Dest]); return; #define NEVER_OR_SOMETIMES_LOADABLE_CHECKED_REF_STORAGE(Name, ...) \ case SILInstructionKind::Store##Name##Inst: #include "swift/AST/ReferenceStorage.def" case SILInstructionKind::StoreInst: case SILInstructionKind::StoreBorrowInst: visitor(&I->getAllOperands()[StoreInst::Dest]); return; case SILInstructionKind::SelectEnumAddrInst: visitor(&I->getAllOperands()[0]); return; #define NEVER_OR_SOMETIMES_LOADABLE_CHECKED_REF_STORAGE(Name, ...) \ case SILInstructionKind::Load##Name##Inst: #include "swift/AST/ReferenceStorage.def" case SILInstructionKind::InitExistentialAddrInst: case SILInstructionKind::InjectEnumAddrInst: case SILInstructionKind::LoadInst: case SILInstructionKind::LoadBorrowInst: case SILInstructionKind::OpenExistentialAddrInst: case SILInstructionKind::SwitchEnumAddrInst: case SILInstructionKind::UncheckedTakeEnumDataAddrInst: case SILInstructionKind::UnconditionalCheckedCastInst: { // Assuming all the above have only a single address operand. assert(I->getNumOperands() - I->getNumTypeDependentOperands() == 1); Operand *singleOperand = &I->getAllOperands()[0]; // Check the operand type because UnconditionalCheckedCastInst may operate // on a non-address. if (singleOperand->get()->getType().isAddress()) visitor(singleOperand); return; } // Non-access cases: these are marked with memory side effects, but, by // themselves, do not access formal memory. #define UNCHECKED_REF_STORAGE(Name, ...) \ case SILInstructionKind::StrongCopy##Name##ValueInst: #define ALWAYS_OR_SOMETIMES_LOADABLE_CHECKED_REF_STORAGE(Name, ...) \ case SILInstructionKind::StrongCopy##Name##ValueInst: #include "swift/AST/ReferenceStorage.def" case SILInstructionKind::AbortApplyInst: case SILInstructionKind::AllocBoxInst: case SILInstructionKind::AllocExistentialBoxInst: case SILInstructionKind::AllocGlobalInst: case SILInstructionKind::BeginAccessInst: case SILInstructionKind::BeginApplyInst: case SILInstructionKind::BeginBorrowInst: case SILInstructionKind::BeginUnpairedAccessInst: case SILInstructionKind::BindMemoryInst: case SILInstructionKind::CheckedCastValueBranchInst: case SILInstructionKind::CondFailInst: case SILInstructionKind::CopyBlockInst: case SILInstructionKind::CopyBlockWithoutEscapingInst: case SILInstructionKind::CopyValueInst: case SILInstructionKind::DeinitExistentialAddrInst: case SILInstructionKind::DeinitExistentialValueInst: case SILInstructionKind::DestroyAddrInst: case SILInstructionKind::DestroyValueInst: case SILInstructionKind::EndAccessInst: case SILInstructionKind::EndApplyInst: case SILInstructionKind::EndBorrowInst: case SILInstructionKind::EndUnpairedAccessInst: case SILInstructionKind::EndLifetimeInst: case SILInstructionKind::ExistentialMetatypeInst: case SILInstructionKind::FixLifetimeInst: case SILInstructionKind::InitExistentialValueInst: case SILInstructionKind::IsUniqueInst: case SILInstructionKind::IsEscapingClosureInst: case SILInstructionKind::KeyPathInst: case SILInstructionKind::OpenExistentialBoxInst: case SILInstructionKind::OpenExistentialBoxValueInst: case SILInstructionKind::OpenExistentialValueInst: case SILInstructionKind::PartialApplyInst: case SILInstructionKind::ProjectValueBufferInst: case SILInstructionKind::YieldInst: case SILInstructionKind::UnwindInst: case SILInstructionKind::UncheckedOwnershipConversionInst: case SILInstructionKind::UncheckedRefCastAddrInst: case SILInstructionKind::UnconditionalCheckedCastAddrInst: case SILInstructionKind::UnconditionalCheckedCastValueInst: case SILInstructionKind::ValueMetatypeInst: return; } } SILBasicBlock::iterator swift::removeBeginAccess(BeginAccessInst *beginAccess) { while (!beginAccess->use_empty()) { Operand *op = *beginAccess->use_begin(); // Delete any associated end_access instructions. if (auto endAccess = dyn_cast(op->getUser())) { endAccess->eraseFromParent(); // Forward all other uses to the original address. } else { op->set(beginAccess->getSource()); } } return beginAccess->getParent()->erase(beginAccess); } bool swift::isSingleInitAllocStack(AllocStackInst *asi, SmallVectorImpl &destroyingUses) { // For now, we just look through projections and rely on memInstMustInitialize // to classify all other uses as init or not. SmallVector worklist(asi->getUses()); bool foundInit = false; while (!worklist.empty()) { auto *use = worklist.pop_back_val(); auto *user = use->getUser(); if (Projection::isAddressProjection(user) || isa(user)) { // Look through address projections. for (SILValue r : user->getResults()) { llvm::copy(r->getUses(), std::back_inserter(worklist)); } continue; } if (auto *li = dyn_cast(user)) { // If we are not taking, if (li->getOwnershipQualifier() != LoadOwnershipQualifier::Take) { continue; } // Treat load [take] as a write. return false; } switch (user->getKind()) { default: break; case SILInstructionKind::DestroyAddrInst: destroyingUses.push_back(use); continue; case SILInstructionKind::DeallocStackInst: case SILInstructionKind::LoadBorrowInst: case SILInstructionKind::DebugValueAddrInst: continue; } // See if we have an initializer and that such initializer is in the same // block. if (memInstMustInitialize(use)) { if (user->getParent() != asi->getParent() || foundInit) { return false; } foundInit = true; continue; } // Otherwise, if we have found something not in our whitelist, return false. return false; } // We did not find any users that we did not understand. So we can // conservatively return true here. return true; }