Skip to content

[cxx-interop] Fix exporting cdecl Swift functions to Obj-C++ #74516

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 1 commit into from
Jun 21, 2024

Conversation

Xazax-hun
Copy link
Contributor

We export cdecl function declarations twice: for Objective-C and for C++. When the code is compiled in Objective-C++ both of the declarations are visible to the compiler. The generated header did not compile, because only one of the declarations were noexcept. There are multiple possible ways to fix this issue, one of them would make only C++ declarations visible in Objective-C++ mode. However, for this particular problem I decided to also make the Objective-C functions SWIFT_NOEXCEPT. This approach resolves the inconsistency that broke the code when compiled in Objective-C++ mode. Moreover, Swift guarantees that those cdecl declarations cannot raise errors, so in case we only generate the C declarations and consume them from C++ or Objective-C++, those are the correct declarations.

rdar://129550313

@Xazax-hun Xazax-hun added the c++ interop Feature: Interoperability with C++ label Jun 18, 2024
@Xazax-hun
Copy link
Contributor Author

@swift-ci please smoke test

@egorzhdan egorzhdan requested a review from beccadax June 18, 2024 13:26
Copy link
Contributor

@egorzhdan egorzhdan left a comment

Choose a reason for hiding this comment

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

Agreed on the approach chosen, but let's wait for a review from @beccadax to be certain – I'm not sure if there can be any ABI concerns with making this change to Obj-C interop.

We export cdecl function declarations twice: for Objective-C and for
C++. When the code is compiled in Objective-C++ both of the declarations
are visible to the compiler. The generated header did not compile,
because only one of the declarations were noexcept. There are multiple
possible ways to fix this issue, one of them would make only C++
declarations visible in Objective-C++ mode. However, for this particular
problem I decided to also make the Objective-C functions SWIFT_NOEXCEPT.
This approach resolves the inconsistency that broke the code when
compiled in Objective-C++ mode. Moreover, Swift guarantees that those
cdecl declarations cannot raise errors, so in case we only generate the
C declarations and consume them from C++ or Objective-C++, those are the
correct declarations.

rdar://129550313
@Xazax-hun Xazax-hun force-pushed the gaborh/objcpp-fix branch from 4f03fa1 to 5bb9ccf Compare June 18, 2024 15:40
@Xazax-hun
Copy link
Contributor Author

Looks like there were a couple of tests that I did not run locally but needed an update.

@swift-ci please smoke test

@Xazax-hun Xazax-hun changed the title [cxx-interop] Fix exporing cdecl Swift functions to Obj-C++ [cxx-interop] Fix exporting cdecl Swift functions to Obj-C++ Jun 19, 2024
Copy link
Contributor

@beccadax beccadax left a comment

Choose a reason for hiding this comment

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

swift/include/swift/PrintAsClang/ClangMacros.def says:

CLANG_MACRO_CXX("SWIFT_NOEXCEPT", , "noexcept", )

If I understand this correctly, that means SWIFT_NOEXCEPT exists in both C++ and ObjC modes, evaluating to noexcept in C++ mode and nothing in ObjC mode.

If that's true, then this change LGTM.

@Xazax-hun Xazax-hun merged commit 5fbf42a into main Jun 21, 2024
3 checks passed
@Xazax-hun Xazax-hun deleted the gaborh/objcpp-fix branch June 21, 2024 05:58
Xazax-hun added a commit that referenced this pull request Jun 21, 2024
[cxx-interop] Fix exporting cdecl Swift functions to Obj-C++
@@ -1440,6 +1440,7 @@ class DeclAndTypePrinter::Implementation
os << "SWIFT_EXTERN ";
printFunctionDeclAsCFunctionDecl(FD, FD->getCDeclName(), resultTy);
printFunctionClangAttributes(FD, funcTy);
os << " SWIFT_NOEXCEPT";
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't noexcept be the first thing after the function declaration and before any possible attributes? Does the test below try to parse the header or just checks the output?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, the RUN: %check-interop-cxx-header-in-clang(%t/functions.h) runline will try to parse the header. I used this order for consistency, because other parts of the codebase was also emitting those attributes first and noexcept later. I think there is no strict place to put these non-standard attributes on the declaration, Clang seems to be forgiving on that front.

Copy link
Contributor

Choose a reason for hiding this comment

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

The parsing is not as forgiving on my side. In Xcode 16b2 if I add

extern int _Nonnull foo(void) __attribute__((warn_unused_result)) noexcept;

to a C++ file, I get:

error: expected function body after function declarator
   10 | extern int _Nonnull foo(void) __attribute__((warn_unused_result)) noexcept;
      |                                                                   ^

Where does the noexcept is added in other parts of the codebase? I just sent #74780, but if there's more places it might not be bad to look at them too. They might need fixing.

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 just checked and it looks like the order was an oversight on my part. Too bad the -fsyntax-only wasn't good enough to catch this :/ Thanks a lot for the PR, I just approved it. We also need to cherry-pick this to the release branch.

drodriguez added a commit to drodriguez/swift that referenced this pull request Jun 27, 2024
In swiftlang#74516 the `SWIFT_NOEXCEPT` specifier is added to the imported Swift
functions in C++ mode, but it is added after the function attributes. It seems
that the tests only do `-fsyntax-only`, which seems not to catch an error like
"expected function body after function declarator" when the header is
used without that flag.

Flip the attributes and the specifier around in the printer, and flip
them in all the tests.

(cherry picked from commit 1ea7c5a)
drodriguez added a commit that referenced this pull request Jun 28, 2024
In #74516 the `SWIFT_NOEXCEPT` specifier is added to the imported Swift
functions in C++ mode, but it is added after the function attributes. It seems
that the tests only do `-fsyntax-only`, which seems not to catch an error like
"expected function body after function declarator" when the header is
used without that flag.

Flip the attributes and the specifier around in the printer, and flip
them in all the tests.

The tests were using `%check-in-clang`, but it only checks importing as
an objective-c-header. Add a parallel `%check-in-clang-cxx` to test also
in C++. It uses C++17 because of some details in the imported headers and
disables a warning about variadic macros that was promoted to an error
and was blocking passing the tests. The clang-importer-sdk gets the
minimal set of files to compile the two modified tests as C++. The files
are mostly empty, except `cstddef` that imports the equivalent C header.
Some modifications were needed in `ctypes.h` because the header was
using features only available in C and not C++.
drodriguez added a commit that referenced this pull request Jun 28, 2024
Explanation:
In #74516 the `SWIFT_NOEXCEPT` specifier is added to the imported Swift
functions in C++ mode, but it is added after the function attributes. It seems
that the tests only do `-fsyntax-only`, which seems not to catch an error like
"expected function body after function declarator" when the header is
used without that flag.

Flip the attributes and the specifier around in the printer, and flip
them in all the tests.

The tests were using `%check-in-clang`, but it only checks importing as
an objective-c-header. Add a parallel `%check-in-clang-cxx` to test also
in C++. It uses C++17 because of some details in the imported headers and
disables a warning about variadic macros that was promoted to an error
and was blocking passing the tests. The clang-importer-sdk gets the
minimal set of files to compile the two modified tests as C++. The files
are mostly empty, except `cstddef` that imports the equivalent C header.
Some modifications were needed in `ctypes.h` because the header was
using features only available in C and not C++.

Scope: C++ Interop
Risk: Low besides the risks pointed out in #74615.
Testing: The tests have been modified. Locally using the header without -fsyntax-only also avoids the error.
Reviewer: @Xazax-hun
Original PR: #74780
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.

4 participants