Skip to content

Teach findAccessedStorage about global addressors. #16877

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions include/swift/Parse/ParseSILSupport.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#ifndef SWIFT_PARSER_PARSESILSUPPORT_H
#define SWIFT_PARSER_PARSESILSUPPORT_H

#include "swift/Parse/Parser.h"
#include "llvm/Support/PrettyStackTrace.h"

namespace swift {
Expand Down
19 changes: 12 additions & 7 deletions include/swift/SIL/MemAccessUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ class AccessedStorage {
}
}

/// Return true if the storage is guaranteed local.
bool isLocal() const {
switch (getKind()) {
case Box:
Expand Down Expand Up @@ -349,15 +350,19 @@ namespace swift {
/// If `sourceAddr` is produced by a begin_access, this returns a Nested
/// AccessedStorage kind. This is useful for exclusivity checking to distinguish
/// between a nested access vs. a conflict.
AccessedStorage findAccessedStorage(SILValue sourceAddr);
AccessedStorage findUnknownAccessedStorage(SILValue sourceAddr);

/// Given an address accessed by an instruction that reads or modifies
/// memory, return an AccessedStorage that identifies the formal access, looking
/// through any Nested access to find the original storage.
/// Given an address that is accessed with dynamic enforcement, return
/// an AccessedStorage object that identifies the formal access.
///
/// This has the same behavior findUnknownAccessedStorage except that dynamic
/// access has stricter requirements. In particular, Unidentified access is only
/// allowed for patterns that are recognized as local access. This should be
/// used by any optimization pass that analyzes dynamic access and assumes that
/// Global or Class access does not alias with Unidentified access.
///
/// This is identical to findAccessedStorage(), but never returns Nested
/// storage.
AccessedStorage findAccessedStorageNonNested(SILValue sourceAddr);
/// The returned AccessedStorage will never be Nested.
AccessedStorage findDynamicAccessedStorage(SILValue sourceAddr);

/// Return true if the given address operand is used by a memory operation that
/// initializes the memory at that address, implying that the previous value is
Expand Down
37 changes: 36 additions & 1 deletion include/swift/SIL/SILGlobalVariable.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ class SILGlobalVariable
/// once (either in its declaration, or once later), making it immutable.
unsigned IsLet : 1;

/// The VarDecl associated with this SILGlobalVariable. For debugger purpose.
/// The VarDecl associated with this SILGlobalVariable. Must by nonnull for
/// language-level global variables.
VarDecl *VDecl;

/// Whether or not this is a declaration.
Expand Down Expand Up @@ -225,4 +226,38 @@ public ilist_default_traits<::swift::SILGlobalVariable> {

} // end llvm namespace

//===----------------------------------------------------------------------===//
// Utilities for verification and optimization.
//===----------------------------------------------------------------------===//

namespace swift {

/// Given an addressor, AddrF, return the global variable being addressed, or
/// return nullptr if the addressor isn't a recognized pattern.
SILGlobalVariable *getVariableOfGlobalInit(SILFunction *AddrF);

/// Return the callee of a once call.
SILFunction *getCalleeOfOnceCall(BuiltinInst *BI);

/// Helper for getVariableOfGlobalInit(), so GlobalOpts can deeply inspect and
/// rewrite the initialization pattern.
///
/// Given an addressor, AddrF, find the call to the global initializer if
/// present, otherwise return null. If an initializer is returned, then
/// `CallsToOnce` is initialized to the corresponding builtin "once" call.
SILFunction *findInitializer(SILModule *Module, SILFunction *AddrF,
BuiltinInst *&CallToOnce);

/// Helper for getVariableOfGlobalInit(), so GlobalOpts can deeply inspect and
/// rewrite the initialization pattern.
///
/// Given a global initializer, InitFunc, return the GlobalVariable that it
/// statically initializes or return nullptr if it isn't an obvious static
/// initializer. If a global variable is returned, InitVal is initialized to the
/// the instruction producing the global's initial value.
SILGlobalVariable *getVariableOfStaticInitializer(
SILFunction *InitFunc, SingleValueInstruction *&InitVal);

} // namespace swift

#endif
4 changes: 4 additions & 0 deletions include/swift/SIL/SILLinkage.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

namespace swift {

class ValueDecl;

/// Linkage for a SIL object. This concept combines the notions
/// of symbol linkage and visibility.
///
Expand Down Expand Up @@ -176,6 +178,8 @@ inline bool isPossiblyUsedExternally(SILLinkage linkage, bool wholeModule) {
return linkage <= SILLinkage::Hidden;
}

SILLinkage getDeclSILLinkage(const ValueDecl *decl);

inline bool hasPublicVisibility(SILLinkage linkage) {
switch (linkage) {
case SILLinkage::Public:
Expand Down
8 changes: 8 additions & 0 deletions include/swift/SIL/SILModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,14 @@ class SILModule {
void setOptRecordStream(std::unique_ptr<llvm::yaml::Output> &&Stream,
std::unique_ptr<llvm::raw_ostream> &&RawStream);

// This is currently limited to VarDecl because the visibility of global
// variables and class properties is straighforward, while the visibility of
// class methods (ValueDecls) depends on the subclass scope. "Visiblity" has
// a different meaning when vtable layout is at stake.
bool isVisibleExternally(const VarDecl *decl) {
return isPossiblyUsedExternally(getDeclSILLinkage(decl), isWholeModule());
}

PropertyListType &getPropertyList() { return properties; }
const PropertyListType &getPropertyList() const { return properties; }

Expand Down
31 changes: 24 additions & 7 deletions lib/ParseSIL/ParseSIL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@
#include "swift/AST/ProtocolConformance.h"
#include "swift/Basic/Defer.h"
#include "swift/Basic/Timer.h"
#include "swift/Demangling/Demangle.h"
#include "swift/Parse/Lexer.h"
#include "swift/Parse/Parser.h"
#include "swift/Parse/ParseSILSupport.h"
#include "swift/Parse/Parser.h"
#include "swift/SIL/AbstractionPattern.h"
#include "swift/SIL/InstructionUtils.h"
#include "swift/SIL/SILArgument.h"
Expand All @@ -28,8 +29,8 @@
#include "swift/SIL/SILModule.h"
#include "swift/SIL/SILUndef.h"
#include "swift/SIL/TypeLowering.h"
#include "swift/Syntax/SyntaxKind.h"
#include "swift/Subsystems.h"
#include "swift/Syntax/SyntaxKind.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/Support/SaveAndRestore.h"

Expand Down Expand Up @@ -5404,11 +5405,27 @@ bool SILParserTUState::parseSILGlobal(Parser &P) {
if (!GlobalLinkage.hasValue())
GlobalLinkage = SILLinkage::DefaultForDefinition;

// FIXME: check for existing global variable?
auto *GV = SILGlobalVariable::create(M, GlobalLinkage.getValue(),
isSerialized,
GlobalName.str(),GlobalType,
RegularLocation(NameLoc));
// Lookup the global variable declaration for this sil_global.
std::string GlobalDeclName = Demangle::demangleSymbolAsString(
GlobalName.str(), Demangle::DemangleOptions::SimplifiedUIDemangleOptions());
SmallVector<ValueDecl *, 4> CurModuleResults;
P.SF.getParentModule()->lookupValue(
{}, P.Context.getIdentifier(GlobalDeclName), NLKind::UnqualifiedLookup,
CurModuleResults);
// A variable declaration exists for all sil_global variables defined in
// Swift. If a Swift global is defined outside this module, it will be exposed
// via an addressor rather than as a sil_global. However, globals imported
// from clang *will* result in a sil_global *without* any corresponding
// variable declaration.
VarDecl *VD = nullptr;
if (!CurModuleResults.empty()) {
assert(CurModuleResults.size() == 1);
VD = cast<VarDecl>(CurModuleResults[0]);
assert(getDeclSILLinkage(VD) == GlobalLinkage);
}
auto *GV = SILGlobalVariable::create(
M, GlobalLinkage.getValue(), isSerialized, GlobalName.str(), GlobalType,
RegularLocation(NameLoc), VD);

GV->setLet(isLet);
// Parse static initializer if exists.
Expand Down
134 changes: 115 additions & 19 deletions lib/SIL/MemAccessUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#define DEBUG_TYPE "sil-access-utils"

#include "swift/SIL/MemAccessUtils.h"
#include "swift/SIL/SILGlobalVariable.h"
#include "swift/SIL/SILUndef.h"

using namespace swift;
Expand All @@ -28,6 +29,14 @@ AccessedStorage::Kind AccessedStorage::classify(SILValue base) {
return Stack;
case ValueKind::GlobalAddrInst:
return Global;
case ValueKind::ApplyInst: {
FullApplySite apply(cast<ApplyInst>(base));
if (auto *funcRef = apply.getReferencedFunction()) {
if (getVariableOfGlobalInit(funcRef))
return Global;
}
return Unidentified;
}
case ValueKind::RefElementAddrInst:
return Class;
// A function argument is effectively a nested access, enforced
Expand Down Expand Up @@ -67,7 +76,19 @@ AccessedStorage::AccessedStorage(SILValue base, Kind kind) {
paramIndex = cast<SILFunctionArgument>(base)->getIndex();
break;
case Global:
global = cast<GlobalAddrInst>(base)->getReferencedGlobal();
if (auto *GAI = dyn_cast<GlobalAddrInst>(base))
global = GAI->getReferencedGlobal();
else {
FullApplySite apply(cast<ApplyInst>(base));
auto *funcRef = apply.getReferencedFunction();
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
Expand Down Expand Up @@ -149,10 +170,31 @@ void AccessedStorage::print(raw_ostream &os) const {

void AccessedStorage::dump() const { print(llvm::dbgs()); }

// Given an address base is a block argument, verify that 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.
/// Return true if the given apply invokes a global addressor defined in another
/// module.
static bool isExternalGlobalAddressor(ApplyInst *AI) {
FullApplySite apply(AI);
auto *funcRef = apply.getReferencedFunction();
if (!funcRef)
return false;

return funcRef->isGlobalInit() && funcRef->isExternalDeclaration();
}

/// Return true of the given SILValue produces an UnsafePointer.
static bool isUnsafePointerProducer(SILValue ptrVal) {
SILType ptrTy = ptrVal->getType();
if (ptrTy.isAddress())
return false;

PointerTypeKind PTK;
return bool(ptrTy.getASTType()->getAnyPointerElementType(PTK));
}

/// Given an address base is a block argument, verify that 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.
static void checkSwitchEnumBlockArg(SILPHIArgument *arg) {
assert(!arg->getType().isAddress());
SILBasicBlock *Pred = arg->getParent()->getSinglePredecessorBlock();
Expand Down Expand Up @@ -186,23 +228,64 @@ static bool isAddressForLocalInitOnly(SILValue sourceAddr) {
}
}

AccessedStorage swift::findAccessedStorage(SILValue sourceAddr) {
// AccessEnforcementWMO makes a strong assumption that all accesses are either
// identified or are *not* accessing a global variable or class property defined
// in this module. Consequently, we cannot simply bail out on
// PointerToAddressInst as an Unidentified access.
static AccessedStorage findAccessedStorage(SILValue sourceAddr,
bool isDynamic) {
SILValue address = sourceAddr;
while (true) {
AccessedStorage::Kind kind = AccessedStorage::classify(address);
// First handle identified cases: these are always valid as the base of a
// formal access.
// First handle identified cases: these are always valid as the base of
// a formal access.
if (kind != AccessedStorage::Unidentified)
return AccessedStorage(address, kind);

// UnsafePointers may be indirectly accessed via inout arguments.
// UnsafePointer access only occurs after inlining inout arguments, which
// must be statically enforced. Note: since the address originates from
// UnsafePointer, no enforcement is actually needed, but that can't be
// known until after inlining.
if (!isDynamic && isUnsafePointerProducer(address)) {
return AccessedStorage(address, AccessedStorage::Unidentified);
}

// Check for Builtin.RawPointer producers before looking through
// struct/tuple_extract.
//
// e.g. MaterializeForSet produces a tuple contianing a RawPointer. It may
// only be accessed directly after inlining an inout argument.
//
// For static access, any RawPointer is a valid address. For dynamic access,
// we must look through the source of the pointer and verify that it is from
// a known, recognizable addressor.
if (!isDynamic && address->getType().is<BuiltinRawPointerType>())
return AccessedStorage(address, AccessedStorage::Unidentified);

// Handle other unidentified address sources.
switch (address->getKind()) {
default:
if (isAddressForLocalInitOnly(address))
if (!isDynamic && isAddressForLocalInitOnly(address))
return AccessedStorage(address, AccessedStorage::Unidentified);

return AccessedStorage();

// Addressors.
//
// Since apply does not produce addresses, this must be the source of a
// pointer_to_address. UnsafePointer producers are checked above. Here we
// check for a recognized call producing Builtin.RawPointer.
case ValueKind::ApplyInst:
// Global addressors produce the address for formal accessed.
if (isExternalGlobalAddressor(cast<ApplyInst>(address)))
return AccessedStorage(address, AccessedStorage::Unidentified);

// Since struct_extract does not produce addresses, this must be the
// source of a pointer_to_address. Don't allow any other pointers to be
// accessed.
return AccessedStorage();

case ValueKind::PointerToAddressInst:
case ValueKind::SILUndef:
return AccessedStorage(address, AccessedStorage::Unidentified);

Expand Down Expand Up @@ -233,6 +316,7 @@ AccessedStorage swift::findAccessedStorage(SILValue sourceAddr) {
// Look through address casts to find the source address.
case ValueKind::MarkUninitializedInst:
case ValueKind::OpenExistentialAddrInst:
case ValueKind::PointerToAddressInst:
case ValueKind::UncheckedAddrCastInst:
// Inductive cases that apply to any type.
case ValueKind::CopyValueInst:
Expand All @@ -250,8 +334,11 @@ AccessedStorage swift::findAccessedStorage(SILValue sourceAddr) {
continue;

// Subobject projections.
// Includes non-address extracts to handle pointer_to_address source.
case ValueKind::StructElementAddrInst:
case ValueKind::TupleElementAddrInst:
case ValueKind::StructExtractInst:
case ValueKind::TupleExtractInst:
case ValueKind::UncheckedTakeEnumDataAddrInst:
case ValueKind::RefTailAddrInst:
case ValueKind::TailAddrInst:
Expand All @@ -262,14 +349,22 @@ AccessedStorage swift::findAccessedStorage(SILValue sourceAddr) {
}
}

AccessedStorage swift::findAccessedStorageNonNested(SILValue sourceAddr) {
while (true) {
const AccessedStorage &storage = findAccessedStorage(sourceAddr);
if (!storage || storage.getKind() != AccessedStorage::Nested)
return storage;

sourceAddr = cast<BeginAccessInst>(storage.getValue())->getSource();
}
AccessedStorage swift::findUnknownAccessedStorage(SILValue sourceAddr) {
return findAccessedStorage(sourceAddr, /*isDynamic=*/false);
}

AccessedStorage swift::findDynamicAccessedStorage(SILValue sourceAddr) {
const AccessedStorage &storage =
findAccessedStorage(sourceAddr, /*isDynamic=*/true);

// Nested access occurs after inlining a function in which inout arguments are
// passed inout to a callee:
// e.g. foo(x: inout Int) { bar(&x) }
// Exclusivity rules require static enforcement of inout arguments.
assert(storage.getKind() != AccessedStorage::Nested
&& "Nested dynamic access is not allowed");

return storage;
}

// Return true if the given access is on a 'let' lvalue.
Expand Down Expand Up @@ -340,8 +435,9 @@ bool swift::isPossibleFormalAccessBase(const AccessedStorage &storage,
// checking. However, for the purpose of inserting unenforced markers and
// performaing verification, it needs to be ignored.
auto *BAI = cast<BeginAccessInst>(storage.getValue());
assert(BAI->getEnforcement() != SILAccessEnforcement::Dynamic);
const AccessedStorage &nestedStorage =
findAccessedStorage(BAI->getSource());
findUnknownAccessedStorage(BAI->getSource());
if (!nestedStorage)
return false;

Expand Down
Loading