Skip to content

[SIL] Add [serialized_for_package] to control package-wide resilience domain in Package-CMO. #73566

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

Merged
merged 3 commits into from
May 16, 2024
Merged
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
20 changes: 20 additions & 0 deletions include/swift/SIL/SILFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,10 @@ class SILFunction
/// The function's serialized attribute.
bool Serialized : 1;

/// [serialized_for_package] attribute if package serialization
/// is enabled.
bool SerializedForPackage : 1;

/// Specifies if this function is a thunk or a reabstraction thunk.
///
/// The inliner uses this information to avoid inlining (non-trivial)
Expand Down Expand Up @@ -1138,6 +1142,22 @@ class SILFunction
"too few bits for Serialized storage");
}

/// A [serialized_for_package] attribute is used to indicate that a function
/// is [serialized] because of package-cmo optimization.
/// Package-cmo allows serializing a function containing a loadable type in
/// a resiliently built module, which is normally illegal. During SIL deserialization,
/// this attribute can be used to check whether a loaded function that was serialized
/// can be allowed to have loadable types. This attribute is also used to determine
/// if a callee can be inlined into a caller that's serialized without package-cmo, for
/// example, by explicitly annotating the caller decl with `@inlinable`.
IsSerializedForPackage_t isSerializedForPackage() const {
return IsSerializedForPackage_t(SerializedForPackage);
}
void
setSerializedForPackage(IsSerializedForPackage_t isSerializedForPackage) {
SerializedForPackage = isSerializedForPackage;
}

/// Get this function's thunk attribute.
IsThunk_t isThunk() const { return IsThunk_t(Thunk); }
void setThunk(IsThunk_t isThunk) {
Expand Down
5 changes: 5 additions & 0 deletions include/swift/SIL/SILLinkage.h
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,11 @@ enum IsSerialized_t : unsigned char {
IsSerialized
};

enum IsSerializedForPackage_t : unsigned char {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with Slava. You should combine both enums, e.g.

enum IsSerialized_t : unsigned char {
  IsNotSerialized,
  IsSerialized,
  IsSerializedForPackage
};

IsNotSerializedForPackage,
IsSerializedForPackage
};

/// The scope in which a subclassable class can be subclassed.
enum class SubclassScope : uint8_t {
/// This class can be subclassed in other modules.
Expand Down
28 changes: 19 additions & 9 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3060,7 +3060,7 @@ bool AbstractStorageDecl::isResilient(ModuleDecl *M,
case ResilienceExpansion::Maximal:
if (M == getModuleContext())
return false;
// Non-resilient if bypass optimization in package is enabled
// Access non-resiliently if package optimization is enabled
if (bypassResilienceInPackage(M))
return false;
return isResilient();
Expand Down Expand Up @@ -4271,13 +4271,23 @@ bool ValueDecl::hasOpenAccess(const DeclContext *useDC) const {
}

bool ValueDecl::bypassResilienceInPackage(ModuleDecl *accessingModule) const {
// Client needs to opt in to bypass resilience checks at the use site.
// Client and the loaded module both need to be in the same package.
// The loaded module needs to be built from source and opt in to allow
// non-resilient access.
return getASTContext().LangOpts.EnableBypassResilienceInPackage &&
getModuleContext()->inSamePackage(accessingModule) &&
getModuleContext()->allowNonResilientAccess();
auto declModule = getModuleContext();
if (declModule->inSamePackage(accessingModule) &&
declModule->allowNonResilientAccess()) {
// If the defining module is built with package-cmo,
// allow direct access from the use site that belongs
// to accessingModule (client module).
if (declModule->isResilient() &&
declModule->serializePackageEnabled())
return true;

// If not, check if the client can still opt in to
// have a direct access to this decl from the use
// site with a flag.
// FIXME: serialize this flag to Module and get it via accessingModule.
return getASTContext().LangOpts.EnableBypassResilienceInPackage;
}
return false;
}

/// Given the formal access level for using \p VD, compute the scope where
Expand Down Expand Up @@ -5134,7 +5144,7 @@ bool NominalTypeDecl::isResilient(ModuleDecl *M,
// non-resiliently in a maximal context.
if (M == getModuleContext())
return false;
// Non-resilient if bypass optimization in package is enabled
// Access non-resiliently if package optimization is enabled
if (bypassResilienceInPackage(M))
return false;

Expand Down
8 changes: 6 additions & 2 deletions lib/SIL/IR/SILDeclRef.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -460,8 +460,12 @@ static LinkageLimit getLinkageLimit(SILDeclRef constant) {
case Kind::EnumElement:
return Limit::OnDemand;

case Kind::GlobalAccessor:
return cast<VarDecl>(d)->isResilient() ? Limit::NeverPublic : Limit::None;
case Kind::GlobalAccessor: {
auto varDecl = cast<VarDecl>(d);
return varDecl->isResilient() &&
!varDecl->getModuleContext()->allowNonResilientAccess() ?
Limit::NeverPublic : Limit::None;
}

case Kind::DefaultArgGenerator:
// If the default argument is to be serialized, only use non-ABI public
Expand Down
15 changes: 13 additions & 2 deletions lib/SIL/IR/SILFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ void SILFunction::init(
this->Bare = isBareSILFunction;
this->Transparent = isTrans;
this->Serialized = isSerialized;
this->SerializedForPackage = false;
this->Thunk = isThunk;
this->ClassSubclassScope = unsigned(classSubclassScope);
this->GlobalInitFlag = false;
Expand Down Expand Up @@ -523,12 +524,22 @@ ResilienceExpansion SILFunction::getResilienceExpansion() const {
// source and is never used outside of its package;
// Even if the module is built resiliently, return
// maximal expansion here so aggregate types can be
// loadable in the same resilient domain (from a client
// module in the same package.
// treated as loadable in the same resilient domain
// (across modules in the same package).
if (getModule().getSwiftModule()->serializePackageEnabled() &&
getModule().getSwiftModule()->isResilient())
return ResilienceExpansion::Maximal;

// If a function definition is in another module, and
// it was serialized due to package serialization opt,
// a new attribute [serialized_for_package] is added
// to the definition site. During deserialization, this
// attribute is preserved if the current module is in
// the same package, thus should be in the same resilience
// domain.
if (isSerializedForPackage() == IsSerializedForPackage)
return ResilienceExpansion::Maximal;

return (isSerialized()
? ResilienceExpansion::Minimal
: ResilienceExpansion::Maximal);
Expand Down
13 changes: 11 additions & 2 deletions lib/SIL/IR/SILPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3341,6 +3341,14 @@ void SILFunction::print(SILPrintContext &PrintCtx) const {
case IsSerialized: OS << "[serialized] "; break;
}

switch (isSerializedForPackage()) {
case IsNotSerializedForPackage:
break;
case IsSerializedForPackage:
OS << "[serialized_for_package] ";
break;
}

switch (isThunk()) {
case IsNotThunk: break;
case IsThunk: OS << "[thunk] "; break;
Expand Down Expand Up @@ -3528,7 +3536,7 @@ void SILGlobalVariable::print(llvm::raw_ostream &OS, bool Verbose) const {

if (isSerialized())
OS << "[serialized] ";

if (isLet())
OS << "[let] ";

Expand Down Expand Up @@ -3840,7 +3848,7 @@ void SILProperty::print(SILPrintContext &Ctx) const {
OS << "sil_property ";
if (isSerialized())
OS << "[serialized] ";

OS << '#';
printValueDecl(getDecl(), OS);
if (auto sig = getDecl()->getInnermostDeclContext()
Expand Down Expand Up @@ -4019,6 +4027,7 @@ void SILVTable::print(llvm::raw_ostream &OS, bool Verbose) const {
OS << "sil_vtable ";
if (isSerialized())
OS << "[serialized] ";

if (SILType classTy = getClassType()) {
OS << classTy;
} else {
Expand Down
29 changes: 16 additions & 13 deletions lib/SIL/IR/TypeLowering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2324,31 +2324,34 @@ namespace {
if (D->isResilient()) {
// If the type is resilient and defined in our module, make a note of
// that, since our lowering now depends on the resilience expansion.
// The same should happen if the type was resilient and serialized in
// another module in the same package with package-cmo enabled, which
// treats those modules to be in the same resilience domain.
auto declModule = D->getModuleContext();
bool sameModule = (declModule == &TC.M);
if (sameModule)
bool serializedPackage = declModule != &TC.M &&
declModule->inSamePackage(&TC.M) &&
declModule->isResilient() &&
declModule->serializePackageEnabled();
auto inSameResilienceDomain = sameModule || serializedPackage;
if (inSameResilienceDomain)
properties.addSubobject(RecursiveProperties::forResilient());

// If the type is in a different module, or if we're using a minimal
// If the type is in a different module and not in the same package
// resilience domain (with package-cmo), or if we're using a minimal
// expansion, the type is address only and completely opaque to us.
// However, this is not true if the different module is in the same
// package and package serialization is enabled (resilience expansion
// is maximal), e.g. in case of package-cmo.
//
// Note: if the type is in a different module, the lowering does
// not depend on the resilience expansion, so we do not need to set
// the isResilient() flag above.
bool serializedPackage = declModule->inSamePackage(&TC.M) &&
declModule->isResilient() &&
declModule->serializePackageEnabled();
if ((!sameModule && !serializedPackage) ||
// Note: if the type is in a different module and not in the same
// package resilience domain, the lowering does not depend on the
// resilience expansion, so we do not need to set the isResilient()
// flag above.
if (!inSameResilienceDomain ||
Expansion.getResilienceExpansion() ==
ResilienceExpansion::Minimal) {
properties.addSubobject(RecursiveProperties::forOpaque());
return true;
}
}

return false;
}

Expand Down
44 changes: 25 additions & 19 deletions lib/SIL/Parser/ParseSIL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -665,7 +665,8 @@ void SILParser::convertRequirements(ArrayRef<RequirementRepr> From,
}

static bool parseDeclSILOptional(
bool *isTransparent, IsSerialized_t *isSerialized, bool *isCanonical,
bool *isTransparent, IsSerialized_t *isSerialized,
IsSerializedForPackage_t *isSerializedForPackage, bool *isCanonical,
bool *hasOwnershipSSA, bool *hasResultDependsOnSelf, IsThunk_t *isThunk,
IsDynamicallyReplaceable_t *isDynamic, IsDistributed_t *isDistributed,
IsRuntimeAccessible_t *isRuntimeAccessible,
Expand All @@ -676,10 +677,9 @@ static bool parseDeclSILOptional(
SILFunction **usedAdHocRequirementWitness, Identifier *objCReplacementFor,
SILFunction::Purpose *specialPurpose, Inline_t *inlineStrategy,
OptimizationMode *optimizationMode, PerformanceConstraints *perfConstraints,
bool *isPerformanceConstraint,
bool *markedAsUsed, StringRef *section, bool *isLet, bool *isWeakImported,
bool *needStackProtection, AvailabilityContext *availability,
bool *isWithoutActuallyEscapingThunk,
bool *isPerformanceConstraint, bool *markedAsUsed, StringRef *section,
bool *isLet, bool *isWeakImported, bool *needStackProtection,
AvailabilityContext *availability, bool *isWithoutActuallyEscapingThunk,
SmallVectorImpl<std::string> *Semantics,
SmallVectorImpl<ParsedSpecAttr> *SpecAttrs, ValueDecl **ClangDecl,
EffectsKind *MRK, SILParser &SP, SILModule &M) {
Expand All @@ -697,6 +697,9 @@ static bool parseDeclSILOptional(
*isTransparent = true;
else if (isSerialized && SP.P.Tok.getText() == "serialized")
*isSerialized = IsSerialized;
else if (isSerializedForPackage &&
SP.P.Tok.getText() == "serialized_for_package")
*isSerializedForPackage = IsSerializedForPackage;
else if (isDynamic && SP.P.Tok.getText() == "dynamically_replacable")
*isDynamic = IsDynamic;
else if (isDistributed && SP.P.Tok.getText() == "distributed")
Expand Down Expand Up @@ -7106,6 +7109,7 @@ bool SILParserState::parseDeclSIL(Parser &P) {

bool isTransparent = false;
IsSerialized_t isSerialized = IsNotSerialized;
IsSerializedForPackage_t isSerializedForPackage = IsNotSerializedForPackage;
bool isCanonical = false;
IsDynamicallyReplaceable_t isDynamic = IsNotDynamic;
IsDistributed_t isDistributed = IsNotDistributed;
Expand Down Expand Up @@ -7138,17 +7142,17 @@ bool SILParserState::parseDeclSIL(Parser &P) {
Identifier objCReplacementFor;
if (parseSILLinkage(FnLinkage, P) ||
parseDeclSILOptional(
&isTransparent, &isSerialized, &isCanonical, &hasOwnershipSSA,
&hasResultDependsOnSelf, &isThunk, &isDynamic, &isDistributed,
&isRuntimeAccessible, &forceEnableLexicalLifetimes,
&isTransparent, &isSerialized, &isSerializedForPackage, &isCanonical,
&hasOwnershipSSA, &hasResultDependsOnSelf, &isThunk, &isDynamic,
&isDistributed, &isRuntimeAccessible, &forceEnableLexicalLifetimes,
&useStackForPackMetadata, &hasUnsafeNonEscapableResult,
&isExactSelfClass, &DynamicallyReplacedFunction,
&AdHocWitnessFunction, &objCReplacementFor, &specialPurpose,
&inlineStrategy, &optimizationMode, &perfConstr,
&isPerformanceConstraint, &markedAsUsed,
&section, nullptr, &isWeakImported, &needStackProtection,
&availability, &isWithoutActuallyEscapingThunk, &Semantics,
&SpecAttrs, &ClangDecl, &MRK, FunctionState, M) ||
&isPerformanceConstraint, &markedAsUsed, &section, nullptr,
&isWeakImported, &needStackProtection, &availability,
&isWithoutActuallyEscapingThunk, &Semantics, &SpecAttrs, &ClangDecl,
&MRK, FunctionState, M) ||
P.parseToken(tok::at_sign, diag::expected_sil_function_name) ||
P.parseIdentifier(FnName, FnNameLoc, /*diagnoseDollarPrefix=*/false,
diag::expected_sil_function_name) ||
Expand All @@ -7173,6 +7177,8 @@ bool SILParserState::parseDeclSIL(Parser &P) {
FunctionState.F->setBare(IsBare);
FunctionState.F->setTransparent(IsTransparent_t(isTransparent));
FunctionState.F->setSerialized(IsSerialized_t(isSerialized));
FunctionState.F->setSerializedForPackage(
IsSerializedForPackage_t(isSerializedForPackage));
FunctionState.F->setWasDeserializedCanonical(isCanonical);
if (!hasOwnershipSSA)
FunctionState.F->setOwnershipEliminated();
Expand Down Expand Up @@ -7399,10 +7405,9 @@ bool SILParserState::parseSILGlobal(Parser &P) {
parseDeclSILOptional(nullptr, &isSerialized, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr,
nullptr, nullptr, nullptr, nullptr, nullptr, &isLet,
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, State, M) ||
nullptr, &isLet, nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr, State, M) ||
P.parseToken(tok::at_sign, diag::expected_sil_value_name) ||
P.parseIdentifier(GlobalName, NameLoc, /*diagnoseDollarPrefix=*/false,
diag::expected_sil_value_name) ||
Expand Down Expand Up @@ -7454,7 +7459,7 @@ bool SILParserState::parseSILProperty(Parser &P) {
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, SP, M))
nullptr, nullptr, nullptr, nullptr, SP, M))
return true;

ValueDecl *VD;
Expand Down Expand Up @@ -7524,7 +7529,7 @@ bool SILParserState::parseSILVTable(Parser &P) {
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, VTableState, M))
nullptr, nullptr, nullptr, nullptr, VTableState, M))
return true;


Expand Down Expand Up @@ -7647,7 +7652,8 @@ bool SILParserState::parseSILMoveOnlyDeinit(Parser &parser) {
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, moveOnlyDeinitTableState, M))
nullptr, nullptr, nullptr, nullptr,
moveOnlyDeinitTableState, M))
return true;

// Parse the class name.
Expand Down Expand Up @@ -8134,7 +8140,7 @@ bool SILParserState::parseSILWitnessTable(Parser &P) {
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, WitnessState, M))
nullptr, nullptr, nullptr, nullptr, WitnessState, M))
return true;

// Parse the protocol conformance.
Expand Down
Loading