-
Notifications
You must be signed in to change notification settings - Fork 10.4k
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
Emit and call thunks for back deployed functions #41416
Conversation
3c576fc
to
7895516
Compare
… The thunk calls the original function if it is available at runtime and otherwise falls back to calling a copy that is emitted into the client (not yet implemented).
9c4b5a7
to
af76a8d
Compare
@swift-ci please test |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work! I think it's mostly complete, I just have a few suggestions.
include/swift/AST/Decl.h
Outdated
@@ -6268,6 +6268,9 @@ class AbstractFunctionDecl : public GenericContext, public ValueDecl { | |||
/// Returns 'true' if the function is distributed. | |||
bool isDistributed() const; | |||
|
|||
/// Returns 'true' if the function can be back deployed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment doesn't really add any value in my opinion -- either it should be removed, or clarified with an explanation of the actual behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated
lib/SIL/IR/SILDeclRef.cpp
Outdated
|
||
|
||
// Back deployment support functions are not in the vtable. | ||
if (backDeploymentKind != SILDeclRef::BackDeploymentKind::None) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this be instead enforced with a restriction preventing @_backDeploy from being applied to non-final methods?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I suppose that the eventual type checking restrictions that will be in place should prevent this from ever happening. Would it make sense to assert even then, though? One thing I don't have a good feel for yet is how much to count on type checking and how much to check its work later :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, if something in SILGen is impossible and should have been caught by the type checker, asserting is the right approach. Trust but verify :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So it turns out that this is called unconditionally and the early return is needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So it turns out that this is called unconditionally and the early return is needed.
Ah, this needs more thought then. When it is being called? I see that we call this for final methods that override something else. So the question is, is this supported:
class Base {
func foo() {}
}
class Derived {
@_backDeploy final override func foo() {}
}
If so, this should do something more useful since back-deployed methods can appear in the vtable, they just cannot be overriden further.
If not, you might want to prohibit non-final and override methods from being @_backDeploy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I goofed here and put the assert before of the if (auto overridden = getOverridden())
branch which caused it to fail. It's fine to assert inside the branch so I've done that.
@@ -1030,6 +1030,11 @@ class SILGenApply : public Lowering::ExprVisitor<SILGenApply> { | |||
} | |||
|
|||
void processClassMethod(DeclRefExpr *e, AbstractFunctionDecl *afd) { | |||
// FIXME(backDeploy): Investigate support for back deployed class method |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be fine for final methods
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense. I'm planning to come back through with a PR specifically aimed at making sure class methods with static dispatch are supported and that the attribute is rejected on methods with dynamic dispatch.
// CHECK: %4 = function_ref @$s21back_deploy_attribute14TopLevelStructV0a8DeployedF4FuncyyF : $@convention(method) (TopLevelStruct) -> () | ||
// CHECK: %5 = apply %4(%0) : $@convention(method) (TopLevelStruct) -> () | ||
// CHECK: [[FNREF:%.*]] = function_ref @$s21back_deploy_attribute14TopLevelStructV0a8DeployedF4FuncyyFTwb : $@convention(method) (TopLevelStruct) -> () | ||
// CHECK: [[FNRES:%.*]] = apply [[FNREF]]([[STRUCT_ARG]]) : $@convention(method) (TopLevelStruct) -> () | ||
s.backDeployedStructFunc() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestions for more tests:
- Complex parameter lists (indirectly-passed values like existentials, inout parameters, tuples)
- Complex returns (indirectly-returned values like existentials, tuples)
- Generic functions
- Async and throws functions
- Anything with a non-trivial body
Also please add tests in test/attr/ or test/Sema/ to ensure that inlinable restrictions are enforced in the body of a @_backDeploy just like they are for @_aeic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, I should cover many more types of functions. One thing I wonder about is the scalability of testing all those different function types as SILGen tests. Do you think it's important that we verify the SIL for each of those configurations, or would it be satisfactory to have a validation test that calls many different types of back deployed functions and verifies their behaviors? I was planning on writing the latter kind of test in upcoming PRs after a bit more of the type checking is in place.
I do have an existing test in /test/attr
verifying the type checking diagnostics for this attribute. However, much of the type checking is still coming in later PRs. I wanted to get this working end to end to make sure I understood how it would work before adding all the polish.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it makes sense to cover every possible case, I was just thinking of the high-value ones that might identify bugs in how you're forwarding arguments and results.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good, I'll cover what you listed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Testing generics revealed that the thunk is not quite forwarding arguments perfectly for all functions correctly yet so I'm continuing to investigate that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I now have tests covering back deployment of the following function archetypes:
- A trivial top level function with no arguments or results.
- A top level function that takes an argument and returns a value.
- A trivial top level function that throws.
- A trivial top level function on a struct.
- A generic function that takes and returns a
T
.
I also experimented with async functions, functions taking inout
params, and functions taking/returning an existential but did not end up writing any SILGen test for those specifically because they didn't appear to me to exercise distinct logic in my SILGen code. I think there may be diminishing returns on more SILGen tests. However, I do intend to write executable validation tests that exercise a wider variety of function archetypes in an upcoming PR.
include/swift/SIL/SILDeclRef.h
Outdated
@@ -162,6 +162,17 @@ struct SILDeclRef { | |||
AsyncEntryPoint, | |||
}; | |||
|
|||
/// Represents the variants of a back deployable function. | |||
enum class BackDeploymentKind : unsigned { | |||
/// The original function. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the one that is part of the ABI, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's right, I'll clarify that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated comments
include/swift/SIL/SILDeclRef.h
Outdated
/// The original function. | ||
None, | ||
/// The thunk variant of a function that calls either the original function | ||
/// or the fallback variant if the original is unavailable. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mention this one is @_aeic / PublicNonABI
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated comments
include/swift/SIL/SILDeclRef.h
Outdated
/// The thunk variant of a function that calls either the original function | ||
/// or the fallback variant if the original is unavailable. | ||
Thunk, | ||
/// The fallback variant of the function. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mention this one is @_aeic / PublicNonABI
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated comments
lib/Demangling/NodePrinter.cpp
Outdated
@@ -1414,6 +1416,7 @@ NodePointer NodePrinter::print(NodePointer Node, unsigned depth, | |||
case Node::Kind::CFunctionPointer: | |||
case Node::Kind::ObjCBlock: | |||
case Node::Kind::EscapingObjCBlock: | |||
case Node::Kind::BackDeploymentFallback: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mangling output should differentiate the fallback from the original function I think
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense, I'll figure out how to accomplish that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed and added tests for the mangling classification. This revealed a number of issues with my existing mangling implementation which are now fixed.
…pdate the back deployment thunk mangling to use the same prefix.
af76a8d
to
81edf79
Compare
Thanks for the review. I have addressed all the feedback except for adding more SILGen tests, which I'll pick up Monday. |
81edf79
to
201381a
Compare
…l it when the original function is not available at run time.
201381a
to
5339dae
Compare
@swift-ci please test |
🎉 |
Emit a thunk that wraps the call to a function with the
@_backDeploy
attribute. The thunk calls the original function if it is available at runtime and otherwise falls back to calling a copy that is emitted into the client:Resolves rdar://89122645