Skip to content

Opaque result types: dynamic replacement #24201

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

Conversation

aschwaighofer
Copy link
Contributor

No description provided.

This is to support dynamic function replacement of functions with opaque
result type.

This approach requires that all state is thrown away (that could contain the
old returned type for an opaque type) between replacements.

rdar://48887938
Clones functions containing opaque type archetypes replacing the opaque
type by a concrete type.

rdar://46140751
@aschwaighofer
Copy link
Contributor Author

@swift-ci Please test

@swift-ci
Copy link
Contributor

Build failed
Swift Test Linux Platform
Git Sha - a0387ee

Copy link
Contributor

@slavapestov slavapestov left a comment

Choose a reason for hiding this comment

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

Sorry, I don’t know why some of my comments are not rendered properly.

@@ -576,6 +592,10 @@ SILLinkage LinkEntity::getLinkage(ForDefinition_t forDefinition) const {
case Kind::ProtocolRequirementsBaseDescriptor:
case Kind::MethodLookupFunction:
case Kind::OpaqueTypeDescriptor:
case Kind::OpaqueTypeDescriptorAccessor:
Copy link
Contributor

Choose a reason for hiding this comment

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

Do these really need to be public?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yed, they are what is getting replaced.

@@ -199,6 +199,10 @@ class LinkEntity {
/// The descriptor for an opaque type.
/// The pointer is an OpaqueTypeDecl*.
OpaqueTypeDescriptor,
OpaqueTypeDescriptorAccessor,
Copy link
Contributor

Choose a reason for hiding this comment

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

These should have comments

@slavapestov
Copy link
Contributor

@aschwaighofer It looks like when dynamic replacement is off, we directly reference the opaque type descriptor and call a runtime function to get the concrete metadata. Is this correct? Then why do we need to emit an accessor function when dynamic replacement is on? Can we not dynamically replace a global variable, perhaps storing a pointer inside an indirect global variable?

Do the new symbols get emitted even when dynamic replacement is off?

Here's an example showing you can have a 'some P' type that's in parameter position, or nested inside a structural return type:

protocol P {}

protocol Q {
  associatedtype T

  func f() -> T
}

struct S : Q {
  func f() -> some P { return ... }
}

struct UsesSomePInFunnyWay {
  func blah(_ x: S.T) -> Array<S.T> { return [x] }
}

Perhaps dynamic replacement can't support this yet, but it should degrade gracefully instead of crashing in a cast.

SmallVector<OpaqueTypeArchetypeType *, 8> newFuncTypes;
SmallVector<OpaqueTypeArchetypeType *, 8> origFuncTypes;
for (auto *newFunc : DynamicReplacements) {
if (!newFunc->getLoweredFunctionType()->hasOpaqueArchetype())
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be a structural walk over the SILFunctionType using newFunc->getLoweredFunctionType().each()

auto argType = substConv.getSILArgumentType(argIdx);
if (argConv.isIndirectConvention() &&
argType.getASTType()->hasOpaqueArchetype() &&
!opd.get()->getType().getASTType()->hasOpaqueArchetype()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe just check that the types differ? What if both have opaque types but they're different, because of various inlining and specialization passes?

for (auto &inst : BB) {
auto *allocStack = dyn_cast<AllocStackInst>(&inst);
if (!allocStack ||
!allocStack->getElementType().is<OpaqueTypeArchetypeType>())
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 the element type contains an opaque type but its not at the top level?

@@ -2093,6 +2093,94 @@ static Type getDynamicComparisonType(ValueDecl *value) {
return interfaceType->removeArgumentLabels(numArgumentLabels);
}

static OpaqueTypeArchetypeType *getOpaqueResulType(const AbstractFunctionDecl *func) {
Copy link
Contributor

Choose a reason for hiding this comment

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

typo, should be "ResultType"

}

static bool
matchFunctionWithOpaqueResultType(const AbstractFunctionDecl *replacement,
Copy link
Contributor

@slavapestov slavapestov Apr 22, 2019

Choose a reason for hiding this comment

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

Maybe instead this could be a flag for TypeBase::matches(), TypeMatchFlags::CompareOpaqueTypes or something? It really doesn't need to handle parameters and results differently, the only difference is when it finds an opaque type, instead of checking for equality it checks if they have the same constraints.

We'll need that flag when we allow non-final class methods to have opaque result types for instance...

@aschwaighofer
Copy link
Contributor Author

aschwaighofer commented Apr 22, 2019

@slavapestov

It looks like when dynamic replacement is off, we directly reference the opaque type descriptor and call a runtime function to get the concrete metadata. Is this correct? Then why do we need to emit an accessor function when dynamic replacement is on? Can we not dynamically replace a global variable, perhaps storing a pointer inside an indirect global variable?

I reuse the mechanism of dynamically replacing a function which is already present. I don't want to invent a new mechanism/runtime support for a feature that will be supplanted by something else.

Do the new symbols get emitted even when dynamic replacement is off?

No it should not. (see emitOpaqueTypeDescriptorAccessor) but there is a bug that I missed the same check (getAddressOfOpaqueTypeDescriptor) that I have to fix.

@slavapestov
Copy link
Contributor

@aschwaighofer Ok, if you're going to replace this soon anyway then the current approach is fine. Do you have a radar or JIRA for the followup change? I'm curious to learn more about it.

Do you mind still tidying up the walks over the type to fish out any opaque types it contains, though, as well as the function type replacement matching in TypeCheckAttr.cpp?

@aschwaighofer
Copy link
Contributor Author

Yes I will look at fixing nested opaque types.

@aschwaighofer
Copy link
Contributor Author

@swift-ci Please test

@swift-ci
Copy link
Contributor

Build failed
Swift Test Linux Platform
Git Sha - a0387ee

@swift-ci
Copy link
Contributor

Build failed
Swift Test OS X Platform
Git Sha - a0387ee

@aschwaighofer
Copy link
Contributor Author

@swift-ci Please test

@swift-ci
Copy link
Contributor

Build failed
Swift Test OS X Platform
Git Sha - efe7bef

@swift-ci
Copy link
Contributor

Build failed
Swift Test Linux Platform
Git Sha - efe7bef

auto argConv = apply.getArgumentConvention(opd);
auto argIdx = apply.getCalleeArgIndex(opd);
auto argType = substConv.getSILArgumentType(argIdx);
if (argConv.isIndirectConvention() &&
Copy link
Contributor

Choose a reason for hiding this comment

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

Can an opaque archetype be loadable (hence the argument convention is direct) if its protocol is class-constrained? I think you want to handle that by emitting an unchecked_ref_cast

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. Fixed. Test case added.

bool foundOpaqueArchetype = false;
for (auto &BB : *getFunction()) {
for (auto &inst : BB) {
auto *allocStack = dyn_cast<AllocStackInst>(&inst);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not just look for any instruction containing an opaque type?

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.

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.

Indentation

Copy link
Contributor Author

Choose a reason for hiding this comment

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

clang-format does that.

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.

Indentation

Copy link
Contributor Author

Choose a reason for hiding this comment

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

clang-format does that.

ReplaceOpaqueTypesWithUnderlyingTypes replacer;
return SuperTy::remapSubstitutionMap(Subs).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 formatting is not consistent with the previous lines

Copy link
Contributor Author

Choose a reason for hiding this comment

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

clang-format does that.

return t
}

// CHECK-LABEL: sil @$s1A10testFooBaryyxAA1PRzlF : $@convention(thin) <T where T : P> (@in_guaranteed T) -> () {
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 add a few more test cases?

  • concrete type is address-only
  • the protocol is class-constrained
  • the opaque type is in another module (so it can't be specialized)
  • assignment to a stored property whose type is opaque (use the typealias/associated type trick I showed you in the earlier comment). I believe this will crash right now because you have to insert a cast, just like you do for function arguments


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.

If the opaque type is from a different module (and hence its concrete type is not known to us) this is going to do a lot of wasted work. Can you check for feasibility before cloning the entire function?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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

@omochi
Copy link
Contributor

omochi commented Apr 23, 2019

Why these two functionality are in one pull request?
In my understand, specialization of ORT is compile time optimization that replacement resilient representation to underlying concrete type to improve performance when caller and callee are in same module.
On the other hand, dynamic replacement is replacement function implementation in loading time which is very early timing with loading dynamic library in runtime.
Discussion is this. https://forums.swift.org/t/dynamic-method-replacement/16619

So compiler implementation of specialization is independent from dynamic replacement support for ORT.
Am I missing something?

This comment is not criticism about development process.
I am hobby compiler learner and just want to know about internal source code structure.

@aschwaighofer
Copy link
Contributor Author

No good reason.

Look at all instructions whether we have to cast. Return/YieldInst must
not change the returned type.
@aschwaighofer
Copy link
Contributor Author

This does not yet have the fix for stores ...

@aschwaighofer
Copy link
Contributor Author

I am going to remove the OpaqueArchetypeSpecializer from this PR for now.

@aschwaighofer
Copy link
Contributor Author

@swift-ci Please test

@swift-ci
Copy link
Contributor

Build failed
Swift Test OS X Platform
Git Sha - 612a87f

@swift-ci
Copy link
Contributor

Build failed
Swift Test Linux Platform
Git Sha - 612a87f

@aschwaighofer aschwaighofer changed the title Opaque result types: dynamic replacement and specialization Opaque result types: dynamic replacement Apr 23, 2019
@aschwaighofer
Copy link
Contributor Author

I have moved the specializer to a separate PR here: #24224

@aschwaighofer
Copy link
Contributor Author

@swift-ci Please test

@omochi
Copy link
Contributor

omochi commented Apr 24, 2019

Thanks to response.

@aschwaighofer aschwaighofer merged commit 43a7b44 into swiftlang:master Apr 24, 2019
@slavapestov
Copy link
Contributor

Thanks for addressing the review feedback, LGTM!

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