Skip to content

Support generating loadable types in serialized function when package-cmo is enabled. #73478

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 1 commit into from
May 8, 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
6 changes: 1 addition & 5 deletions include/swift/SIL/SILFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -779,11 +779,7 @@ class SILFunction
return getLoweredFunctionType()->getRepresentation();
}

ResilienceExpansion getResilienceExpansion() const {
return (isSerialized()
? ResilienceExpansion::Minimal
: ResilienceExpansion::Maximal);
}
ResilienceExpansion getResilienceExpansion() const;

// Returns the type expansion context to be used inside this function.
TypeExpansionContext getTypeExpansionContext() const {
Expand Down
17 changes: 17 additions & 0 deletions lib/SIL/IR/SILFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,23 @@ bool SILFunction::isNoReturnFunction(TypeExpansionContext context) const {
.isNoReturnFunction(getModule(), context);
}

ResilienceExpansion SILFunction::getResilienceExpansion() const {
// If package serialization is enabled, we can safely
// assume that the defining .swiftmodule is built from
// 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.
if (getModule().getSwiftModule()->serializePackageEnabled() &&
getModule().getSwiftModule()->isResilient())
return ResilienceExpansion::Maximal;

return (isSerialized()
? ResilienceExpansion::Minimal
: ResilienceExpansion::Maximal);
}

const TypeLowering &
SILFunction::getTypeLowering(AbstractionPattern orig, Type subst) {
return getModule().Types.getTypeLowering(orig, subst,
Expand Down
12 changes: 10 additions & 2 deletions lib/SIL/IR/TypeLowering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2324,17 +2324,25 @@ 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.
bool sameModule = (D->getModuleContext() == &TC.M);
auto declModule = D->getModuleContext();
bool sameModule = (declModule == &TC.M);
if (sameModule)
properties.addSubobject(RecursiveProperties::forResilient());

// If the type is in a different module, 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.
if (!sameModule || Expansion.getResilienceExpansion() ==
bool serializedPackage = declModule->inSamePackage(&TC.M) &&
declModule->isResilient() &&
declModule->serializePackageEnabled();
if ((!sameModule && !serializedPackage) ||
Expansion.getResilienceExpansion() ==
ResilienceExpansion::Minimal) {
properties.addSubobject(RecursiveProperties::forOpaque());
return true;
Expand Down
15 changes: 1 addition & 14 deletions lib/SIL/Verifier/SILVerifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -158,22 +158,9 @@ namespace {
template <typename DeclType>
bool checkResilience(DeclType *D, ModuleDecl *M,
ResilienceExpansion expansion) {
auto refDeclModule = D->getModuleContext();
// Explicitly bypassed for debugging with `bypass-resilience-checks`
if (refDeclModule->getBypassResilience())
if (D->getModuleContext()->getBypassResilience())
return false;

// If package serialization is enabled with `experimental-package-cmo`,
// decls can be serialized in a resiliently built module. In such case,
// a direct access should be allowed.
auto packageSerialized = expansion == ResilienceExpansion::Minimal &&
refDeclModule->isResilient() &&
refDeclModule->allowNonResilientAccess() &&
refDeclModule->serializePackageEnabled() &&
refDeclModule->inSamePackage(M);
if (packageSerialized)
return false;

return D->isResilient(M, expansion);
}

Expand Down
22 changes: 4 additions & 18 deletions lib/SILOptimizer/IPO/CrossModuleOptimization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ class CrossModuleOptimization {

bool canSerializeGlobal(SILGlobalVariable *global);

bool canSerializeType(SILType type, TypeExpansionContext typeExpCtx);
bool canSerializeType(SILType type);

bool canUseFromInline(DeclContext *declCtxt);

Expand Down Expand Up @@ -331,14 +331,12 @@ bool CrossModuleOptimization::canSerializeFunction(
bool CrossModuleOptimization::canSerializeInstruction(
SILInstruction *inst, FunctionFlags &canSerializeFlags, int maxDepth) {
// First check if any result or operand types prevent serialization.
auto typeExpCtx = inst->getFunction()->getTypeExpansionContext();

for (SILValue result : inst->getResults()) {
if (!canSerializeType(result->getType(), typeExpCtx))
if (!canSerializeType(result->getType()))
return false;
}
for (Operand &op : inst->getAllOperands()) {
if (!canSerializeType(op.get()->getType(), typeExpCtx))
if (!canSerializeType(op.get()->getType()))
return false;
}

Expand Down Expand Up @@ -437,23 +435,11 @@ bool CrossModuleOptimization::canSerializeGlobal(SILGlobalVariable *global) {
return true;
}

bool CrossModuleOptimization::canSerializeType(SILType type,
TypeExpansionContext typeExpCtx) {
bool CrossModuleOptimization::canSerializeType(SILType type) {
auto iter = typesChecked.find(type);
if (iter != typesChecked.end())
return iter->getSecond();

if (M.getSwiftModule()->isResilient()) {
auto minResilientCtx = TypeExpansionContext(ResilienceExpansion::Minimal,
typeExpCtx.getContext(),
typeExpCtx.isWholeModuleContext());
auto loadableInMinResilientCtx = M.Types.getTypeLowering(type, minResilientCtx).isLoadable();
if (!loadableInMinResilientCtx) {
typesChecked[type] = false;
return false;
}
}

bool success = !type.getASTType().findIf(
[this](Type rawSubType) {
CanType subType = rawSubType->getCanonicalType();
Expand Down
77 changes: 63 additions & 14 deletions test/SILOptimizer/package-cmo-resilient-mode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -172,43 +172,73 @@ print(prevPkgData)

//--- Lib.swift

// FIXME: handle struct_element_addr %field in resilient mode; requires non-resilience in SIL verify.
// CHECK-RES-NOT: s3Lib9PubStructV6fooVarSivg
// CHECK-RES-NOT: s3Lib9PkgStructV6fooVarSivg

// FIXME: handle `struct $PubStruct` in resilient mode; PubStruct is by-address, so fails in IsLodableOrOpaque check.
// CHECK-RES-NOT: s3Lib9PubStructV6fooVarSivs
// CHECK-RES-NOT: s3Lib9PkgStructV6fooVarSivs

public struct PubStruct {
// PubStruct.foovar.getter
// CHECK-RES-DAG: sil [serialized] [canonical] @$s3Lib9PubStructV6fooVarSivg : $@convention(method) (@in_guaranteed PubStruct) -> Int {
// CHECK-NONRES-DAG: sil [transparent] [serialized] [canonical] [ossa] @$s3Lib9PubStructV6fooVarSivg : $@convention(method) (PubStruct) -> Int
// CHECK-NONRES-DAG: sil [transparent] [serialized] [canonical] [ossa] @$s3Lib9PubStructV6fooVarSivM : $@yield_once @convention(method) (@inout PubStruct) -> @yields @inout Int {
// CHECK-RES-DAG: [[FIELD:%.*]] = struct_element_addr %0 : $*PubStruct, #PubStruct.fooVar
// CHECK-RES-DAG: load [[FIELD]] : $*Int
// CHECK-NONRES-DAG = struct_extract %0 : $PubStruct, #PubStruct.fooVar

// PubStruct.foovar.setter
// CHECK-RES-DAG: sil [serialized] [canonical] @$s3Lib9PubStructV6fooVarSivs : $@convention(method) (Int, @inout PubStruct) -> () {
// CHECK-NONRES-DAG: sil [transparent] [serialized] [canonical] [ossa] @$s3Lib9PubStructV6fooVarSivs : $@convention(method) (Int, @inout PubStruct) -> () {

/// NOTE: `struct $PubStruct` in [serialized] function is legal only if package serialization is enabled.
// CHECK-COMMON-DAG: [[FIELD:%.*]] = struct $PubStruct
// CHECK-RES-DAG: store [[FIELD]] to {{.*}} : $*PubStruct
// CHECK-NONRES-DAG: store [[FIELD]] to [trivial] {{.*}} : $*PubStruct

// PubStruct.foovar.modify
// CHECK-RES-DAG: sil [serialized] [canonical] @$s3Lib9PubStructV6fooVarSivM : $@yield_once @convention(method) (@inout PubStruct) -> @yields @inout Int {
// CHECK-NONRES-DAG: sil [transparent] [serialized] [canonical] [ossa] @$s3Lib9PubStructV6fooVarSivM : $@yield_once @convention(method) (@inout PubStruct) -> @yields @inout Int {
// CHECK-COMMON-DAG: [[FIELD:%.*]] = struct_element_addr %0 : $*PubStruct, #PubStruct.fooVar
// CHECK-COMMON-DAG: yield [[FIELD]]
public var fooVar: Int

public init(_ arg: Int) {
// CHECK-RES-DAG: sil [serialized] [canonical] @$s3Lib9PubStructVyACSicfC : $@convention(method) (Int, @thin PubStruct.Type) -> @out PubStruct {
// CHECK-NONRES-DAG: sil [serialized] [canonical] @$s3Lib9PubStructVyACSicfC : $@convention(method) (Int, @thin PubStruct.Type) -> PubStruct {
// CHECK-COMMON-DAG: [[FIELD:%.*]] = struct $PubStruct
// CHECK-RES-DAG: store [[FIELD]] to %0 : $*PubStruct
// CHECK-NONRES-DAG: return [[FIELD]] : $PubStruct
fooVar = arg
}
public func f() {
// CHECK-RES-DAG: sil [serialized] [canonical] @$s3Lib9PubStructV1fyyF : $@convention(method) (@in_guaranteed PubStruct) -> () {
// CHECK-NONRES-DAG: sil [serialized] [canonical] @$s3Lib9PubStructV1fyyF : $@convention(method) (PubStruct) -> () {
print(fooVar)
}
}

public func runPub(_ arg: PubStruct) {
// CHECK-RES-DAG: sil [serialized] [canonical] @$s3Lib6runPubyyAA0C6StructVF : $@convention(thin) (@in_guaranteed PubStruct) -> () {
// CHECK-NONRES-DAG: sil [serialized] [canonical] @$s3Lib6runPubyyAA0C6StructVF : $@convention(thin) (PubStruct) -> () {
print(arg)
}

@frozen
public struct FrPubStruct {
// CHECK-COMMON-DAG: sil [transparent] [serialized] [canonical] [ossa] @$s3Lib11FrPubStructV6fooVarSivM : $@yield_once @convention(method) (@inout FrPubStruct) -> @yields @inout Int {
// FrPubStruct.fooVar.getter
// CHECK-COMMON-DAG: sil [transparent] [serialized] [canonical] [ossa] @$s3Lib11FrPubStructV6fooVarSivg : $@convention(method) (FrPubStruct) -> Int {
// CHECK-COMMON-DAG: [[FIELD:%.*]] = struct_extract %0 : $FrPubStruct, #FrPubStruct.fooVar
// CHECK-COMMON-DAG: return [[FIELD]] : $Int

// FrPubStruct.fooVar.setter
// CHECK-COMMON-DAG: sil [transparent] [serialized] [canonical] [ossa] @$s3Lib11FrPubStructV6fooVarSivs : $@convention(method) (Int, @inout FrPubStruct) -> () {
// CHECK-COMMON-DAG: [[FIELD:%.*]] = struct $FrPubStruct
// CHECK-COMMON-DAG: store [[FIELD]] to [trivial] {{.*}} : $*FrPubStruct

// FrPubStruct.fooVar.modify
// CHECK-COMMON-DAG: sil [transparent] [serialized] [canonical] [ossa] @$s3Lib11FrPubStructV6fooVarSivM : $@yield_once @convention(method) (@inout FrPubStruct) -> @yields @inout Int {
// CHECK-COMMON-DAG: [[FIELD:%.*]] = struct_element_addr %0 : $*FrPubStruct, #FrPubStruct.fooVar
// CHECK-COMMON-DAG: yield [[FIELD]]
public var fooVar: Int

public init(_ arg: Int) {
// CHECK-COMMON-DAG: sil [serialized] [canonical] @$s3Lib11FrPubStructVyACSicfC : $@convention(method) (Int, @thin FrPubStruct.Type) -> FrPubStruct {
// CHECK-COMMON-DAG: [[FIELD:%.*]] = struct $FrPubStruct
// CHECK-COMMON-DAG: return [[FIELD]] : $FrPubStruct
fooVar = arg
}
public func f() {
Expand All @@ -222,25 +252,44 @@ public func runFrPub(_ arg: FrPubStruct) {
}

package struct PkgStruct {
// fooVar.getter
// PkgStruct.fooVar.getter
// CHECK-RES-DAG: sil package [serialized] [canonical] @$s3Lib9PkgStructV6fooVarSivg : $@convention(method) (@in_guaranteed PkgStruct) -> Int {
// CHECK-NONRES-DAG: sil package [transparent] [serialized] [canonical] [ossa] @$s3Lib9PkgStructV6fooVarSivg : $@convention(method) (PkgStruct) -> Int {
// fooVar.modify
// CHECK-NONRES-DAG: sil package [transparent] [serialized] [canonical] [ossa] @$s3Lib9PkgStructV6fooVarSivM : $@yield_once @convention(method) (@inout PkgStruct) -> @yields @inout Int {
// fooVar.setter
// CHECK-RES-DAG: [[FIELD:%.*]] = struct_element_addr %0 : $*PkgStruct, #PkgStruct.fooVar
// CHECK-RES-DAG: load [[FIELD]] : $*Int
// CHECK-NONRES-DAG = struct_extract %0 : $PkgStruct, #PkgStruct.fooVar

// PkgStruct.fooVar.setter
// CHECK-RES-DAG: sil package [serialized] [canonical] @$s3Lib9PkgStructV6fooVarSivs : $@convention(method) (Int, @inout PkgStruct) -> () {
// CHECK-NONRES-DAG: sil package [transparent] [serialized] [canonical] [ossa] @$s3Lib9PkgStructV6fooVarSivs : $@convention(method) (Int, @inout PkgStruct) -> () {
// CHECK-COMMON-DAG: [[FIELD:%.*]] = struct $PkgStruct
// CHECK-RES-DAG: store [[FIELD]] to {{.*}} : $*PkgStruct
// CHECK-NONRES-DAG: store [[FIELD]] to [trivial] {{.*}} : $*PkgStruct

// PkgStruct.fooVar.modify
// CHECK-RES-DAG: sil package [serialized] [canonical] @$s3Lib9PkgStructV6fooVarSivM : $@yield_once @convention(method) (@inout PkgStruct) -> @yields @inout Int {
// CHECK-NONRES-DAG: sil package [transparent] [serialized] [canonical] [ossa] @$s3Lib9PkgStructV6fooVarSivM : $@yield_once @convention(method) (@inout PkgStruct) -> @yields @inout Int {
// CHECK-COMMON-DAG: [[FIELD:%.*]] = struct_element_addr %0 : $*PkgStruct, #PkgStruct.fooVar
// CHECK-COMMON-DAG: yield [[FIELD]]
package var fooVar: Int

package init(_ arg: Int) {
// CHECK-RES-DAG: sil package [serialized] [canonical] @$s3Lib9PkgStructVyACSicfC : $@convention(method) (Int, @thin PkgStruct.Type) -> @out PkgStruct {
// CHECK-NONRES-DAG: sil package [serialized] [canonical] @$s3Lib9PkgStructVyACSicfC : $@convention(method) (Int, @thin PkgStruct.Type) -> PkgStruct {
// CHECK-COMMON-DAG: [[FIELD:%.*]] = struct $PkgStruct
// CHECK-RES-DAG: store [[FIELD]] to %0 : $*PkgStruct
// CHECK-NONRES-DAG: return [[FIELD]] : $PkgStruct
fooVar = arg
}
package func f() {
// CHECK-RES-DAG: sil package [serialized] [canonical] @$s3Lib9PkgStructV1fyyF : $@convention(method) (@in_guaranteed PkgStruct) -> () {
// CHECK-NONRES-DAG: sil package [serialized] [canonical] @$s3Lib9PkgStructV1fyyF : $@convention(method) (PkgStruct) -> () {
print(fooVar)
}
}

package func runPkg(_ arg: PkgStruct) {
// CHECK-RES-DAG: sil package [serialized] [canonical] @$s3Lib6runPkgyyAA0C6StructVF : $@convention(thin) (@in_guaranteed PkgStruct) -> () {
// CHECK-NONRES-DAG: sil package [serialized] [canonical] @$s3Lib6runPkgyyAA0C6StructVF : $@convention(thin) (PkgStruct) -> () {
print(arg)
}
Expand Down