Skip to content

[SILGen] Fix the type of closure thunks that are passed const reference structs #75491

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
merged 5 commits into from
Sep 27, 2024

Conversation

ahatanaka
Copy link
Contributor

The thunk's parameter needs the @in_guaranteed convention if it's a const reference parameter. However, that convention wasn't being used because clang importer was removing the const reference from the type and SILGen was computing the type of the parameter based on the type without const reference.

This commit fixes the bug by passing the clang function type to SILDeclRef so that it can be used to compute the correct thunk type.

This fixes a crash when a closure is passed to a C function taking a pointer to a function that has a const reference struct parameter.

rdar://131321096

@ahatanaka
Copy link
Contributor Author

@swift-ci please test

@ahatanaka ahatanaka marked this pull request as ready for review July 26, 2024 13:54
@ahatanaka ahatanaka requested review from rjmccall, egorzhdan, hyp, zoecarver and jckarter and removed request for jckarter, hyp, egorzhdan and zoecarver July 26, 2024 13:54
@egorzhdan egorzhdan added the c++ interop Feature: Interoperability with C++ label Jul 26, 2024
Copy link
Contributor

@rjmccall rjmccall left a comment

Choose a reason for hiding this comment

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

The basic approach here seems wrong to me. When lowering a foreign function type, or the type of a foreign SILDeclRef, we should be able to recover its foreign type without expecting it to be passed down separately.

@@ -1797,7 +1807,9 @@ ManagedValue emitCFunctionPointer(SILGenFunction &SGF,
#endif
semanticExpr = conv->getSubExpr()->getSemanticsProvidingExpr();
}


const clang::Type *destFnType = nullptr;
Copy link
Contributor

Choose a reason for hiding this comment

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

This ought to be named something that makes it clear that this is a Clang type.

else
loweredDestTy = SGF.getLoweredType(
AbstractionPattern(destTy->getCanonicalType(), clangInfo.getType()),
destTy);
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 not be necessary. If we're lowering a foreign function type, we should be honoring the Clang type stored in it automatically without requiring that we pass down separately as an abstraction pattern.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm passing the type in the abstraction pattern because the stored clang type gets dropped in TypeConverter::getTypeLowering if I just pass the type without the abstraction pattern to getLoweredType.

  const TypeLowering &
  getTypeLowering(Type t, TypeExpansionContext forExpansion) {
    AbstractionPattern pattern(t->getCanonicalType());
    return getTypeLowering(pattern, t, forExpansion);
  }

The call to t->getCanonicalType() drops the clang 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.

Actually, t still has the clang type, but it's dropped somewhere else later.

Copy link
Contributor

Choose a reason for hiding this comment

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

It'd be good to understand that. I don't think there's any good reason for us to drop clang types, especially during type lowering.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Setting LangOptions::UseClangFunctionTypes or passing -use-clang-function-types prevents clang types from getting dropped, but it also causes other regression tests to crash/fail.

It looks like it hasn't been turned on by default because of unresolved issues.

    /// Use Clang function types for computing canonical types.
    /// If this option is false, the clang function types will still be computed
    /// but will not be used for checking type equality.
    /// [TODO: Clang-type-plumbing] Turn on for feature rollout.
    bool UseClangFunctionTypes = false;

Copy link
Contributor Author

@ahatanaka ahatanaka Aug 30, 2024

Choose a reason for hiding this comment

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

Do you mean the SILDeclRef that's declared later in the function?

// Produce a reference to the C-compatible entry point for the function.
SILDeclRef constant(loc, /*foreign*/ true);

loc passed to the constructor is an AbstractClosureExpr, whose type doesn't have a clang type. The type of parameter FunctionConversionExpr *e passed to this function has the clang type.

Copy link
Contributor

@rjmccall rjmccall Sep 20, 2024

Choose a reason for hiding this comment

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

I still think this shouldn't be necessary — lowering a @convention(c) function type that has a Clang type attached should definitely be producing a type that uses the conventions of that Clang type. Can you identify what exactly drops this? It's one thing if it's just not being propagated into the SILFunctionType, but it seems bizarre that we'd not use this information when it's available in type lowering.

Maybe the query about whether we have a Clang type on an AbstractionPattern just needs to consider the possibility that we have a @convention(c) function type that's carrying such a 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.

The clang type is dropped in TypeConverter::getTypeLowering.

  getTypeLowering(Type t, TypeExpansionContext forExpansion) {
    AbstractionPattern pattern(t->getCanonicalType());
    return getTypeLowering(pattern, t, forExpansion);
  }

The call to t->getCanonicalType() drops the clang type because UseClangFunctionTypes isn't set.

I experimented with creating an AbstractionPattern with a clang type in that function if it exists and it seemed to work although I didn't test it extensively.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

And the type t passed to the overload of getTypeLowering loses its clang type when getCanonicalType is called again.

const TypeLowering &
TypeConverter::getTypeLowering(AbstractionPattern origType,
                               Type origSubstType,
                               TypeExpansionContext forExpansion) {
  CanType substType = origSubstType->getCanonicalType();

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, okay. You'd said up-thread that it wasn't dropped by canonicalization, but if it is, I agree that we can't do much here. Please at least add a comment explaining that this won't be necessary when we switch to Clang types, because the Clang function type should survive canonicalization.

@ahatanaka
Copy link
Contributor Author

@swift-ci please test

Copy link
Contributor

@rjmccall rjmccall left a comment

Choose a reason for hiding this comment

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

Alright, thanks, LGTM.

@ahatanaka
Copy link
Contributor Author

@swift-ci please smoke test

structs

The thunk's parameter needs the @in_guaranteed convention if it's a
const reference parameter. However, that convention wasn't being used
because clang importer was removing the const reference from the
type and SILGen was computing the type of the parameter based on the
type without const reference.

This commit fixes the bug by passing the clang function type to
SILDeclRef so that it can be used to compute the correct thunk type.

This fixes a crash when a closure is passed to a C function taking a
pointer to a function that has a const reference struct parameter.

rdar://131321096
The precondition checks that a clang type must be passed when creating
the foreign entry point for a closure.
This is needed to fix the static assertions in the file that were
failing because a new member was added to SILDeclRef.
@ahatanaka
Copy link
Contributor Author

@swift-ci please test

@ahatanaka ahatanaka merged commit e074426 into main Sep 27, 2024
5 checks passed
@ahatanaka ahatanaka deleted the cxx-const-ref branch September 27, 2024 14:04
@ahatanaka
Copy link
Contributor Author

@swift-ci Please Build Toolchain macOS Platform

ahatanaka added a commit that referenced this pull request Oct 3, 2024
…ce structs (#75491)

The thunk's parameter needs the @in_guaranteed convention if it's a
const reference parameter. However, that convention wasn't being used
because clang importer was removing the const reference from the
type and SILGen was computing the type of the parameter based on the
type without const reference.

This commit fixes the bug by passing the clang function type to
SILDeclRef so that it can be used to compute the correct thunk type.

This fixes a crash when a closure is passed to a C function taking a
pointer to a function that has a const reference struct parameter.

rdar://131321096
ahatanaka added a commit that referenced this pull request Oct 8, 2024
…ce structs (#75491)

The thunk's parameter needs the @in_guaranteed convention if it's a
const reference parameter. However, that convention wasn't being used
because clang importer was removing the const reference from the
type and SILGen was computing the type of the parameter based on the
type without const reference.

This commit fixes the bug by passing the clang function type to
SILDeclRef so that it can be used to compute the correct thunk type.

This fixes a crash when a closure is passed to a C function taking a
pointer to a function that has a const reference struct parameter.

rdar://131321096
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c++ interop Feature: Interoperability with C++
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants