-
Notifications
You must be signed in to change notification settings - Fork 10.3k
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
Add a new parameter convention @in_cxx
for non-trivial C++ classes that are passed indirectly and destructed by the caller
#73019
Conversation
@swift-ci please test |
b1a8d3b
to
c6c9427
Compare
@swift-ci please test |
c6c9427
to
4c26cbb
Compare
@swift-ci please test |
@@ -347,6 +347,11 @@ public enum ArgumentConvention : CustomStringConvertible { | |||
/// convention used by mutable captures in @noescape closures. | |||
case indirectInoutAliasable | |||
|
|||
/// This argument is passed indirectly, i.e. by directly passing the address | |||
/// of an object in memory. The callee may modify, but does not destroy the | |||
/// object. |
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.
/// object. | |
/// object. This corresponds to the parameter-passing convention of the | |
/// Itanium C++ ABI, which is used ubiquitously on non-Windows targets. |
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.
The description sounds like either inout
or inout_aliasable
to me. What are the SIL-level semantics of the new convention?
@@ -416,7 +422,8 @@ public enum ArgumentConvention : CustomStringConvertible { | |||
.packOwned, .packGuaranteed: | |||
return true | |||
case .directOwned, .directUnowned, .directGuaranteed, | |||
.indirectInout, .indirectInoutAliasable, .indirectOut, | |||
.indirectInout, .indirectInoutAliasable, .indirectInCXX, | |||
.indirectOut, |
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'm not sure what this property is used for, but if it includes both @in
and @in_guaranteed
, it probably ought to include @in_cxx
.
@@ -164,7 +164,7 @@ public struct ParameterInfo : CustomStringConvertible { | |||
switch convention { | |||
case .indirectIn, .indirectInGuaranteed: | |||
return hasLoweredAddresses || type.isOpenedExistentialWithError() | |||
case .indirectInout, .indirectInoutAliasable: | |||
case .indirectInout, .indirectInoutAliasable, .indirectInCXX: |
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'd like @gottesmm to weigh in here. Naively, I'd say it'd be nice to model these as direct arguments in non-lowered-addresses mode, but the fact that we have to destroy the value in the caller (and observe any mutations made by the callee) might make that impractical.
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.
cc @atrick
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.
It's potentially useful to have a SIL convention that says: the caller and callee must observe the same address of an addressable value. But that isn't generally implementable for all T
given reabstraction of function types and tuples.
I'm still baffled by what this convention means. The comment says it's about observing mutation. If that's true, then we should pass a SIL address in non-lowered-addresses mode. But why, then aren't you using inout
?
If it is not the case that the caller and callee need to observe the same address, then we should pass SIL values instead.
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 saw an assertion failure when I tried using inout
by making getIndirectCParameterConvention
return Indirect_Inout
instead of Indirect_In_CXX
.
The failed assertion is in ArgEmitter::emit
:
assert(arg.hasLValueType() == param.isIndirectInOut());
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 Itanium C++ ABI convention in which non-trivial C++ values are passed indirectly but the caller is responsible for destroying them. The caller and callee do need to observe the same address. It is essentially the same as inout
at the SIL level except that it's not inout
in the source language, and I didn't want us to have to weaken all the SIL lowering assertions that e.g. an inout
parameter always corresponds to an l-value argument expression, because those are quite useful in catching type-checking bugs. That is why I suggested to Akira that he add a new convention that was mostly treated the same way as inout
throughout the SIL pipeline.
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.
We should probably pass SIL arguments to in-cxx
by-address at all stages, and make it very clear that the caller can observe mutation.
Can the callee observe aliases?
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.
The parameter object can't be directly named except through the parameter; it's never the same object as e.g. a local variable in the caller. However, it can be aliased in the same way that almost any C++ object technically can, e.g. by escaping this
during construction. We've generally been assuming that that's not something we need to worry about, at least from the perspective of "well, we're obviously not going to be enforce exclusivity on types that don't follow basic local-reasoning principles".
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.
It occurs to me that we probably need special logic when consuming parameters like this. They probably should be consumable as long as the type is movable; we should compile consumption as a C++ move and then enforce use restrictions on the moved-from value just like we would for a Swift value, even though technically the object is still there.
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 think I'm starting to understand. At the source level, this is pass-by-value into the callee, and the caller has no visibility of that value. But, since the convention requires the caller to destroy the value, the caller's implementation must also observe any mutation.
Just like @in
, In ownership SIL (OSSA) the argument should be consumed by the call (not by a separate destroy after the call).
Just like @in
, non-address-lowered SIL should pass the value, not an address.
OwnershipModelEliminator could insert a destroy_addr
after the call for these arguments. By then, they will be addresses. And IRGen doesn't need to do anything special.
include/swift/AST/Types.h
Outdated
@@ -4059,6 +4059,11 @@ enum class ParameterConvention : uint8_t { | |||
/// convention used by mutable captures in @noescape closures. | |||
Indirect_InoutAliasable, | |||
|
|||
/// This argument is passed indirectly, i.e. by directly passing the address | |||
/// of an object in memory. The callee may modify, but does not destroy the | |||
/// object. |
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.
/// object. | |
/// object. This corresponds to the parameter-passing convention of the | |
/// Itanium C++ ABI, which is used ubiquitously on non-Windows targets. |
include/swift/SIL/SILDeclRef.h
Outdated
@@ -200,6 +200,8 @@ struct SILDeclRef { | |||
/// Set if this is for an async let closure. | |||
unsigned isAsyncLetClosure : 1; | |||
|
|||
unsigned CFunctionPointer : 1; |
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.
Can you explain why this is necessary?
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'm trying to get getAbstractionPatternForConstant
to return an AbstractionPattern
with a clang type.
Without this change, checkForABIDifferences
in convertCFunctionSignature
returns TypeConverter::ABIDifference::NeedsThunk
because loweredResultTy
and loweredDestTy
don't match, which causes the compiler to reject the code. loweredDestTy
's parameter type is @in_cxx
and I think loweredResultTy
's parameter type would be @in_guaranteed
without the change.
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 ran into this problem when I was testing testClosureToFuncPtr
in closure-thunk-macosx.swift, which passes a swift closure to a C++ function taking a function pointer.
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.
The Conventions
object for a foreign parameter of one of these types should always return @in_cxx
, whether it appears as part of a C function pointer, a block, an ObjC method, or whatever. You should be able to just do this in getIndirectCParameterConvention
.
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 was needed to fix a preexisting bug, but I think it should be fixed in a separate patch and it can be fixed without making any changes to SILDeclRef
.
lib/Demangling/Demangler.cpp
Outdated
@@ -2160,6 +2160,7 @@ NodePointer Demangler::demangleImplParamConvention(Node::Kind ConvKind) { | |||
case 'l': attr = "@inout"; break; | |||
case 'b': attr = "@inout_aliasable"; break; | |||
case 'n': attr = "@in_guaranteed"; break; | |||
case 'C': attr = "@in_cxx"; break; |
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 should be documented in docs/ABI/Mangling.rst.
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.
Also, please add a test in https://github.com/apple/swift/blob/main/test/Demangle/Inputs/manglings.txt
4c26cbb
to
198dd06
Compare
@swift-ci please test |
The updated patch treats |
// CHECK: call void @llvm.lifetime.start.p0(i64 8, ptr %[[V0]]) | ||
// CHECK: call ptr @__swift_cxx_ctor_ZN10NonTrivialC1Ev(ptr %[[V0]]) | ||
// CHECK: invoke void %[[V1]](ptr %[[V0]]) | ||
// CHECK: to label %[[INVOKE_CONT:.*]] unwind label %{{.*}} |
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 think we can turn invoke
into call
and simplify the IR if we fix this bug: #73396
lib/SILGen/SILGenBridging.cpp
Outdated
@@ -1416,7 +1421,11 @@ emitObjCThunkArguments(SILGenFunction &SGF, SILLocation loc, SILDeclRef thunk, | |||
arg = emitObjCUnconsumedArgument(SGF, loc, arg); | |||
} | |||
|
|||
auto managedArg = SGF.emitManagedRValueWithCleanup(arg); | |||
// Don't emit a cleanup if the argument is @in_cxx. |
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 tried to use @in_guaranteed
instead of @in_cxx
for thunk parameters so that we don't have to make this change, but ran into a few issues.
@swift-ci please test |
45a5dc2
to
dab4b05
Compare
@swift-ci please test |
64044f1
to
e890ce6
Compare
@swift-ci please test |
that are passed indirectly and destructed by the caller Fix a bug where `@in`, which indicates the callee is responsible for destroying the passed object, was being used to pass such classes. rdar://122707697
- @in_cxx is handled the same way as @in in callers and @in_guaranteed in callees (except for mutation). Fix checks in various places accordingly. - Emit copies of @in_cxx parameters and destroy them in thunks.
'C' is already taken by @convention(c).
e890ce6
to
4d6a47f
Compare
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.
LGTM
Fix a bug where
@in
, which indicates the callee is responsible for destroying the passed object, was being used to pass such classes.rdar://122707697