Skip to content

Add a pass to specialize opaque type archetypes. #24224

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

Conversation

aschwaighofer
Copy link
Contributor

Clones functions containing opaque type archetypes replacing the opaque
type by a concrete type.

rdar://46140751

@aschwaighofer
Copy link
Contributor Author

This does not check for resilience.

Resilience is currently not respected by type replacement: https://github.com/apple/swift/blob/master/lib/AST/Type.cpp#L2459

@aschwaighofer
Copy link
Contributor Author

@swift-ci Please test

@slavapestov
Copy link
Contributor

Ah, so basically all opaque types are eliminated by this pass today?

@aschwaighofer
Copy link
Contributor Author

@aschwaighofer
Copy link
Contributor Author

I don't know the right answer yet how to do resilience properly: Maybe don't specialize if the namingDeclaration is resilient.

@aschwaighofer
Copy link
Contributor Author

@swift-ci Please test

@swift-ci
Copy link
Contributor

Build failed
Swift Test Linux Platform
Git Sha - dbf7f64

@swift-ci
Copy link
Contributor

Build failed
Swift Test OS X Platform
Git Sha - dbf7f64

@slavapestov
Copy link
Contributor

I think we should just say the opaque type is resilient if the module it’s in is resilient.

Making it depend on the resilience of the underlying nominal seems like an unnecessary complication. Also the underlying nominal could be something like Array which is fragile, but you might be returning a ‘some Sequence’ still expecting it to be resilient.

Finally inlinable functions would be an exception because obviously if you can see the body of the function the opaque type is not resilient.

What do you think @jckarter?

@slavapestov
Copy link
Contributor

We should also add a test to validation-test/Evolution which changes an opaque type resiliently, once this is implemented.

@aschwaighofer
Copy link
Contributor Author

I meant we would call ‘’opaquedecl->getNamingDecl()’’ which returns either a abstractstoragedecl or a func decl. From there we would figure out if that decl is resilient or not in our context: here is where I am more fuzzy and would need to look at how our APIs work. But we could apply the rules you mentioned if the decl is in a different resilient module it is resilient except if the funcdecl is inline able. Or maybe I can get from the naming decl to the sil function and use SIL apis. I will look into this.

@aschwaighofer
Copy link
Contributor Author

@swift-ci Please test linux

@swift-ci
Copy link
Contributor

Build failed
Swift Test Linux Platform
Git Sha - 70f5fd0

@aschwaighofer
Copy link
Contributor Author

@swift-ci Please test

@swift-ci
Copy link
Contributor

Build failed
Swift Test Linux Platform
Git Sha - 70f5fd0

@swift-ci
Copy link
Contributor

Build failed
Swift Test OS X Platform
Git Sha - 70f5fd0

@aschwaighofer
Copy link
Contributor Author

@swift-ci Please test

@swift-ci
Copy link
Contributor

Build failed
Swift Test OS X Platform
Git Sha - 3beb095

@swift-ci
Copy link
Contributor

Build failed
Swift Test Linux Platform
Git Sha - 3beb095

@aschwaighofer
Copy link
Contributor Author

@swift-ci Please test linux

@aschwaighofer
Copy link
Contributor Author

@swift-ci Please smoke test

1 similar comment
@aschwaighofer
Copy link
Contributor Author

@swift-ci Please smoke test

@jckarter
Copy link
Contributor

@slavapestov I had hoped to get some feedback from you on how best to deal with resilience. It looks like Arnold has the basic criteria implemented—the underlying type is resilient if the module is resilient, or if the body of the NamingDecl is inlinable. However, should we pass down a ResilienceExpansion so that we do the right thing when specializing in the bodies of methods that are themselves inlinable and things like that?

@jckarter
Copy link
Contributor

For instance, if you had:

public func foo() -> some P { ... }

@inlinable public func bar() { _ = foo() }

you wouldn't necessarily want to expose foo()'s underlying return type to other modules that inline bar.

ProtocolConformanceRef substOpaqueTypesWithUnderlyingTypes(Type origType) const;

ProtocolConformanceRef substOpaqueTypesWithUnderlyingTypes(
Type origType, ModuleDecl *modulePerformingSubstitution) const;
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you move this method, SubstitutionMap::substOpaqueTypesWithUnderlyingTypes() and ReplaceOpaqueTypesWithUnderlyingTypes to your SIL pass since that's the only place where they're used now?

@@ -109,7 +109,11 @@ class TypeSubstCloner : public SILClonerWithScopes<ImplClass> {

assert(Subs.empty() ||
SubstCalleeSILType ==
Callee->getType().substGenericArgs(AI.getModule(), Subs));
Copy link
Contributor

Choose a reason for hiding this comment

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

This assertion is a bit silly now, how about just removing it instead of teaching TypeSubstCloner about opaque types

lib/AST/Type.cpp Outdated
auto namingDecl = opaqueRoot->getDecl()->getNamingDecl();
auto module = namingDecl->getModuleContext();
if (module->isResilient() && module != modulePerformingSubstitution &&
!namingDecl->getAttrs().hasAttribute<InlinableAttr>()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Please us getResilienceExpansion() instead of directly checking for an inlinable attribute; you're missing a number of cases this way


// Apply the opaque types substitution.
ReplaceOpaqueTypesWithUnderlyingTypes replacer(currentModule);
Sty = Ty.subst(Original.getModule(), SubsMap)
Copy link
Contributor

Choose a reason for hiding this comment

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

since SubsMap is just the forwarding substitutions I don't think you need the first call to subst() at all, it will do nothing

return SuperTy::remapConformance(type, conf)
.subst(type, replacer, replacer,
SubstFlags::SubstituteOpaqueArchetypes |
SubstFlags::AllowLoweredTypes);
Copy link
Contributor

Choose a reason for hiding this comment

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

AllowLoweredTypes isn't needed here

return SuperTy::remapASTType(ty)
.subst(replacer, replacer,
SubstFlags::SubstituteOpaqueArchetypes |
SubstFlags::AllowLoweredTypes)
Copy link
Contributor

Choose a reason for hiding this comment

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

AllowLoweredTypes isn't needed here

ReplaceOpaqueTypesWithUnderlyingTypes replacer(currentModule);
return ty.subst(replacer, replacer,
SubstFlags::SubstituteOpaqueArchetypes |
SubstFlags::AllowLoweredTypes)
Copy link
Contributor

Choose a reason for hiding this comment

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

This is pretty heavy weight. How about walking the type and seeing if it contains an opaque type from the current module?

if (foundOpaqueArchetype)
break;
auto *allocStack = dyn_cast<AllocStackInst>(&inst);
if (!allocStack || !opaqueArchetypeWouldChange(
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't understand why AllocStackInsts are special. You should specialize a function even if it doesn't contain any AllocStackInsts


if (foundOpaqueArchetype) {
SubstitutionMap subsMap = getFunction()->getForwardingSubstitutionMap();
OpaqueSpecializerCloner s(subsMap, *getFunction());
Copy link
Contributor

Choose a reason for hiding this comment

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

the subsMap should not be used at all, I think you can try removing it. It's just burning CPU cycles to substitute using forwarding substitutions. Maybe the opaque cloner should directly inherit from SILCloner and not TypeSubstCloner?

@slavapestov
Copy link
Contributor

@jckarter @aschwaighofer Yeah, I think you should look at SILFunction::getResilienceExpansion(). If a function's resilience expansion is Minimal, you should not specialize any opaque types except those from other inlinable functions.

lib/AST/Type.cpp Outdated
@@ -2491,6 +2501,15 @@ ReplaceOpaqueTypesWithUnderlyingTypes::operator()(CanType maybeOpaqueType,

auto archetype = archetypeAndRoot->first;
auto opaqueRoot = archetypeAndRoot->second;

// Don't replace opaque types from resilient modules.
Copy link
Contributor

Choose a reason for hiding this comment

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

This bit of code is copy and pasted twice, please turn it into a method on ReplaceOpaqueTypesWithUnderlyingTypes. Also it should take the resilience expansion into account:

  • If the opaque type is from an inlinable function, replace opaque type with the underlying type.
  • If expansion is Maximal and we're in the same module, replace the opaque type with the underlying type.
  • If expansion is Minimal and we didn't hit the first case, do nothing.

@@ -2465,7 +2476,7 @@ SILFunctionType::substGenericArgs(SILModule &silModule,
LookupConformanceFn conformances) {
if (!isPolymorphic()) return CanSILFunctionType(this);
SILTypeSubstituter substituter(silModule, subs, conformances,
getGenericSignature());
getGenericSignature(), false);
Copy link
Contributor

Choose a reason for hiding this comment

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

Add a comment here, /*shouldSubstituteOpaqueTypes*/ false

return shouldPerformSubstitution(opaque, context);
}

bool ReplaceOpaqueTypesWithUnderlyingTypes::shouldPerformSubstitution(
Copy link
Contributor Author

@aschwaighofer aschwaighofer Apr 29, 2019

Choose a reason for hiding this comment

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

@slavapestov Can you check whether you agree with these rules:

  • if the function with the opaque result type is inline-able allow substitution
  • if the context function in which we perform substitution has maximal resilience expansion and the context function and the opaque type's function are in the same module allow substitution
  • if the opaque types's module is not resilient allow substitution
  • otherwise disallow substitution

@aschwaighofer
Copy link
Contributor Author

@swift-ci Please test

@swift-ci
Copy link
Contributor

swift-ci commented May 1, 2019

Build failed
Swift Test OS X Platform
Git Sha - cfc34b16b6f5df19349d64374973d0327c22bda7

@swift-ci
Copy link
Contributor

swift-ci commented May 1, 2019

Build failed
Swift Test Linux Platform
Git Sha - cfc34b16b6f5df19349d64374973d0327c22bda7

Clones functions containing opaque type archetypes replacing the opaque
type by a concrete type.

rdar://46140751
We might have to replace opaque archetypes to satisfy equality of types
* Opaque types are abi compatible with their substituted types
* Insert casts in more places
* Respect no optimization attribute
@aschwaighofer aschwaighofer force-pushed the specialize_opaque_result_types branch from cfc34b1 to 0e313c7 Compare May 1, 2019 16:31
@aschwaighofer
Copy link
Contributor Author

@swift-ci Please test

@swift-ci
Copy link
Contributor

swift-ci commented May 1, 2019

Build failed
Swift Test Linux Platform
Git Sha - cfc34b16b6f5df19349d64374973d0327c22bda7

@swift-ci
Copy link
Contributor

swift-ci commented May 1, 2019

Build failed
Swift Test OS X Platform
Git Sha - cfc34b16b6f5df19349d64374973d0327c22bda7

@aschwaighofer aschwaighofer merged commit aa376a4 into swiftlang:master May 2, 2019
@@ -0,0 +1,466 @@
#define DEBUG_TYPE "opaque-archetype-specializer"
Copy link
Contributor

Choose a reason for hiding this comment

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

This needs a copyright notice, etc

Copy link
Contributor Author

Choose a reason for hiding this comment

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


static Type substOpaqueTypesWithUnderlyingTypes(
Type ty, SILFunction *context) {
ReplaceOpaqueTypesWithUnderlyingTypes replacer(context);
Copy link
Contributor

Choose a reason for hiding this comment

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

Please move ReplaceOpaqueTypesWithUnderlyingTypes into this file

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is now also used by lib/SIL/SILFunctionType.cpp because substituted and unsubstituted are ABI compatible.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, ok

if (!getBuilder().hasOwnership()) {
switch (Inst->getOwnershipQualifier()) {
case StoreOwnershipQualifier::Assign: {
auto *li = getBuilder().createLoad(getOpLocation(Inst->getLoc()), dst,
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do you lower assigns to load/store here instead of letting them be?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same code as SILCloner.

Copy link
Contributor

Choose a reason for hiding this comment

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

Why does it need to appear here?

auto cast = getBuilder().createUncheckedAddrCast(apply.getLoc(),
opd.get(), argType);
opd.set(cast);
} else if (argType.is<SILFunctionType>()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

What if its an optional of a function type, or a tuple type, etc? You need to recursively transform types instead

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point! I will go through memory for now in those cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@compnerd
Copy link
Member

compnerd commented May 2, 2019

@aschwaighofer
Copy link
Contributor Author

I will fix.

@aschwaighofer
Copy link
Contributor Author

@compnerd I hope that is the circular dependency issue I introduced. Should be fixed by #24443

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants