-
Notifications
You must be signed in to change notification settings - Fork 10.2k
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
base: main
Are you sure you want to change the base?
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.
b9db498
to
4c4cc4f
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.
I'm not completely confident about the SILGen changes. Could you please make sure that any tests specific to this feature also work with -enable-sil-opaque-values
. We're trying to enable that by default now and can't keep adding new problems to debug.
@@ -325,6 +325,10 @@ static ManagedValue emitManagedParameter(SILGenFunction &SGF, SILLocation loc, | |||
return ManagedValue::forBorrowedAddressRValue(value); | |||
} | |||
|
|||
case ParameterConvention::Indirect_In_CXX: | |||
// Don't emit a cleanup if the parameter is @in_cxx. | |||
return ManagedValue::forOwnedRValue(value, CleanupHandle::invalid()); |
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.
@nate-chandler could you take a quick peek at just the SILGen changes to see if the make sense to you?
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.
As far as SIL opaque values goes, an easy thing to check would be whether the tests [added in this PR] crash out of SILGen with -enable-sil-opaque-values
. Treating such a parameter as owned while suppressing its destruction seems to make sense given the problem being solved.
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 see crashes when I add -enable-sil-opaque-values
.
It crashes in SILValueOwnershipChecker::checkFunctionArgWithoutLifetimeEndingUses
with the error message Owned function parameter without life ending uses!
when I compile closure-thunk-executable-macosx.swift
.
This is the SIL that's causing the crash:
// thunk for @escaping @callee_guaranteed (@in_guaranteed NonTrivial) -> ()
sil shared [transparent] [serialized] [reabstraction_thunk] [ossa] @$sSo10NonTrivialVIegn_ABIeyBC_TR : $@convention(c) (@inout_aliasable @block_storage @callee_guaranteed (@in_guaranteed NonTrivial) -> (), @in_cxx NonTrivial) -> () {
// %0 // user: %2
// %1 // user: %5
bb0(%0 : $*@block_storage @callee_guaranteed (@in_guaranteed NonTrivial) -> (), %1 : @owned $NonTrivial):
%2 = project_block_storage %0 : $*@block_storage @callee_guaranteed (@in_guaranteed NonTrivial) -> () // user: %3
%3 = load [copy] %2 : $*@callee_guaranteed (@in_guaranteed NonTrivial) -> () // users: %8, %4
%4 = begin_borrow %3 : $@callee_guaranteed (@in_guaranteed NonTrivial) -> () // users: %6, %5
%5 = apply %4(%1) : $@callee_guaranteed (@in_guaranteed NonTrivial) -> ()
end_borrow %4 : $@callee_guaranteed (@in_guaranteed NonTrivial) -> () // id: %6
%7 = tuple () // user: %9
destroy_value %3 : $@callee_guaranteed (@in_guaranteed NonTrivial) -> () // id: %8
return %7 : $() // id: %9
} // end sil function '$sSo10NonTrivialVIegn_ABIeyBC_TR'
This is a thunk function for the block.
It seems that the checker is complaining that the second argument of the thunk (@in_cxx NonTrivial
) doesn't have lifetime-ending uses.
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 wonder changing the thunk's parameter to @in_guaranteed NonTrivial
would fix the crash. Alternatively, maybe emitObjCThunkArguments
should emit the cleanup instead of suppressing it and instead have destroy_addr
removed before IRGen.
4c4cc4f
to
0b6e7b6
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).
45a5dc2
to
dab4b05
Compare
@swift-ci please test |
…ion if it's used by a function taking a @in_cxx parameter
@swift-ci please test |
@swift-ci please test |
@swift-ci please test |
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