Skip to content

Commit 9c7f0c7

Browse files
authored
Merge pull request #80917 from xymus/cdecl-print
PrintAsClang: Print a C block in the compatibility header for `@cdecl` functions
2 parents 69c5a02 + f6dd432 commit 9c7f0c7

13 files changed

+245
-4
lines changed

lib/PrintAsClang/DeclAndTypePrinter.cpp

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2289,6 +2289,14 @@ class DeclAndTypePrinter::Implementation
22892289
return false;
22902290
}
22912291

2292+
std::optional<PrimitiveTypeMapping::ClangTypeInfo>
2293+
getKnownType(const TypeDecl *typeDecl) {
2294+
if (outputLang == OutputLanguageMode::C)
2295+
return owningPrinter.typeMapping.getKnownCTypeInfo(typeDecl);
2296+
2297+
return owningPrinter.typeMapping.getKnownObjCTypeInfo(typeDecl);
2298+
}
2299+
22922300
/// If \p typeDecl is one of the standard library types used to map in Clang
22932301
/// primitives and basic types, print out the appropriate spelling and
22942302
/// return true.
@@ -2297,8 +2305,7 @@ class DeclAndTypePrinter::Implementation
22972305
/// for interfacing with C and Objective-C.
22982306
bool printIfKnownSimpleType(const TypeDecl *typeDecl,
22992307
std::optional<OptionalTypeKind> optionalKind) {
2300-
auto knownTypeInfo =
2301-
owningPrinter.typeMapping.getKnownObjCTypeInfo(typeDecl);
2308+
auto knownTypeInfo = getKnownType(typeDecl);
23022309
if (!knownTypeInfo)
23032310
return false;
23042311
os << knownTypeInfo->name;
@@ -2999,6 +3006,22 @@ bool DeclAndTypePrinter::shouldInclude(const ValueDecl *VD) {
29993006
return false;
30003007
}
30013008

3009+
// In C output mode print only the C variant `@cdecl` (no `@_cdecl`),
3010+
// while in other modes print only `@_cdecl`.
3011+
std::optional<ForeignLanguage> cdeclKind = std::nullopt;
3012+
if (auto *FD = dyn_cast<AbstractFunctionDecl>(VD))
3013+
cdeclKind = FD->getCDeclKind();
3014+
if (cdeclKind &&
3015+
(*cdeclKind == ForeignLanguage::C) !=
3016+
(outputLang == OutputLanguageMode::C))
3017+
return false;
3018+
3019+
// C output mode only accepts @cdecl functions.
3020+
if (outputLang == OutputLanguageMode::C &&
3021+
!cdeclKind) {
3022+
return false;
3023+
}
3024+
30023025
if (VD->getAttrs().hasAttribute<ImplementationOnlyAttr>())
30033026
return false;
30043027

lib/PrintAsClang/ModuleContentsWriter.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1118,6 +1118,17 @@ void swift::printModuleContentsAsObjC(
11181118
.write();
11191119
}
11201120

1121+
void swift::printModuleContentsAsC(
1122+
raw_ostream &os, llvm::SmallPtrSetImpl<ImportModuleTy> &imports,
1123+
ModuleDecl &M, SwiftToClangInteropContext &interopContext) {
1124+
llvm::raw_null_ostream prologueOS;
1125+
llvm::StringSet<> exposedModules;
1126+
ModuleWriter(os, prologueOS, imports, M, interopContext, getRequiredAccess(M),
1127+
/*requiresExposedAttribute=*/false, exposedModules,
1128+
OutputLanguageMode::C)
1129+
.write();
1130+
}
1131+
11211132
EmittedClangHeaderDependencyInfo swift::printModuleContentsAsCxx(
11221133
raw_ostream &os, ModuleDecl &M, SwiftToClangInteropContext &interopContext,
11231134
bool requiresExposedAttribute, llvm::StringSet<> &exposedModules) {

lib/PrintAsClang/ModuleContentsWriter.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ void printModuleContentsAsObjC(raw_ostream &os,
3737
ModuleDecl &M,
3838
SwiftToClangInteropContext &interopContext);
3939

40+
void printModuleContentsAsC(raw_ostream &os,
41+
llvm::SmallPtrSetImpl<ImportModuleTy> &imports,
42+
ModuleDecl &M,
43+
SwiftToClangInteropContext &interopContext);
44+
4045
struct EmittedClangHeaderDependencyInfo {
4146
/// The set of imported modules used by this module.
4247
SmallPtrSet<ImportModuleTy, 8> imports;

lib/PrintAsClang/OutputLanguageMode.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
namespace swift {
1717

18-
enum class OutputLanguageMode { ObjC, Cxx };
18+
enum class OutputLanguageMode { ObjC, Cxx, C };
1919

2020
} // end namespace swift
2121

lib/PrintAsClang/PrintAsClang.cpp

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,17 @@ static void emitObjCConditional(raw_ostream &out,
5858
out << "#endif\n";
5959
}
6060

61+
static void emitExternC(raw_ostream &out,
62+
llvm::function_ref<void()> cCase) {
63+
emitCxxConditional(out, [&] {
64+
out << "extern \"C\" {\n";
65+
});
66+
cCase();
67+
emitCxxConditional(out, [&] {
68+
out << "} // extern \"C\"\n";
69+
});
70+
}
71+
6172
static void writePtrauthPrologue(raw_ostream &os, ASTContext &ctx) {
6273
emitCxxConditional(os, [&]() {
6374
ClangSyntaxPrinter(ctx, os).printIgnoredDiagnosticBlock(
@@ -207,6 +218,21 @@ static void writePrologue(raw_ostream &out, ASTContext &ctx,
207218

208219
static_assert(SWIFT_MAX_IMPORTED_SIMD_ELEMENTS == 4,
209220
"need to add SIMD typedefs here if max elements is increased");
221+
222+
if (ctx.LangOpts.hasFeature(Feature::CDecl)) {
223+
// For C compilers which don’t support nullability attributes, ignore them;
224+
// for ones which do, suppress warnings about them being an extension.
225+
out << "#if !__has_feature(nullability)\n"
226+
"# define _Nonnull\n"
227+
"# define _Nullable\n"
228+
"# define _Null_unspecified\n"
229+
"#elif !defined(__OBJC__)\n"
230+
"# pragma clang diagnostic ignored \"-Wnullability-extension\"\n"
231+
"#endif\n"
232+
"#if !__has_feature(nullability_nullable_result)\n"
233+
"# define _Nullable_result _Nullable\n"
234+
"#endif\n";
235+
}
210236
}
211237

212238
static int compareImportModulesByName(const ImportModuleTy *left,
@@ -577,12 +603,21 @@ bool swift::printAsClangHeader(raw_ostream &os, ModuleDecl *M,
577603
llvm::PrettyStackTraceString trace("While generating Clang header");
578604

579605
SwiftToClangInteropContext interopContext(*M, irGenOpts);
606+
writePrologue(os, M->getASTContext(), computeMacroGuard(M));
607+
608+
// C content (@cdecl)
609+
if (M->getASTContext().LangOpts.hasFeature(Feature::CDecl)) {
610+
SmallPtrSet<ImportModuleTy, 8> imports;
611+
emitExternC(os, [&] {
612+
printModuleContentsAsC(os, imports, *M, interopContext);
613+
});
614+
}
580615

616+
// Objective-C content
581617
SmallPtrSet<ImportModuleTy, 8> imports;
582618
std::string objcModuleContentsBuf;
583619
llvm::raw_string_ostream objcModuleContents{objcModuleContentsBuf};
584620
printModuleContentsAsObjC(objcModuleContents, imports, *M, interopContext);
585-
writePrologue(os, M->getASTContext(), computeMacroGuard(M));
586621
emitObjCConditional(os, [&] {
587622
llvm::StringMap<StringRef> exposedModuleHeaderNames;
588623
writeImports(os, imports, *M, bridgingHeader, frontendOpts,
@@ -591,6 +626,8 @@ bool swift::printAsClangHeader(raw_ostream &os, ModuleDecl *M,
591626
writePostImportPrologue(os, *M);
592627
emitObjCConditional(os, [&] { os << "\n" << objcModuleContents.str(); });
593628
writeObjCEpilogue(os);
629+
630+
// C++ content
594631
emitCxxConditional(os, [&] {
595632
// FIXME: Expose Swift with @expose by default.
596633
bool enableCxx = frontendOpts.ClangHeaderExposedDecls.has_value() ||

test/PrintAsObjC/cdecl-imports.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) %s -parse-as-library -typecheck -verify -emit-objc-header-path %t/swift.h
33
// RUN: %FileCheck %s < %t/swift.h
44
// RUN: %check-in-clang %t/swift.h
5+
// RUN: %check-in-clang-c %t/swift.h
56
// RUN: %check-in-clang-cxx %t/swift.h
67

78
// REQUIRES: objc_interop
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/// Similar test to cdecl-official but gated to objc-interop compatibility
2+
3+
// RUN: %empty-directory(%t)
4+
// RUN: split-file %S/cdecl-official.swift %t --leading-lines
5+
6+
/// Generate cdecl.h
7+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) \
8+
// RUN: %t/Lib.swift -emit-module -verify -o %t -emit-module-doc \
9+
// RUN: -emit-clang-header-path %t/cdecl.h \
10+
// RUN: -enable-experimental-feature CDecl
11+
12+
/// Check cdecl.h directly
13+
// RUN: %check-in-clang %t/cdecl.h
14+
// RUN: %check-in-clang-cxx %t/cdecl.h
15+
16+
/// Build an Objective-C client against cdecl.h
17+
// RUN: %clang -c %t/Client.c -fmodules -I %t \
18+
// RUN: -F %S/../Inputs/clang-importer-sdk-path/frameworks \
19+
// RUN: -I %clang-include-dir -Werror \
20+
// RUN: -isysroot %S/../Inputs/clang-importer-sdk
21+
22+
// REQUIRES: swift_feature_CDecl
23+
// REQUIRES: objc_interop

test/PrintAsObjC/cdecl-official.swift

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: split-file %s %t --leading-lines
3+
4+
/// Generate cdecl.h
5+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) \
6+
// RUN: %t/Lib.swift -emit-module -verify -o %t -emit-module-doc \
7+
// RUN: -emit-clang-header-path %t/cdecl.h \
8+
// RUN: -enable-experimental-feature CDecl
9+
10+
/// Check cdecl.h directly
11+
// RUN: %FileCheck %s --input-file %t/cdecl.h
12+
// RUN: %check-in-clang-c %t/cdecl.h -Wnullable-to-nonnull-conversion
13+
14+
/// Build a client against cdecl.h
15+
// RUN: %clang-no-modules -c %t/Client.c -I %t \
16+
// RUN: -F %S/../Inputs/clang-importer-sdk-path/frameworks \
17+
// RUN: -I %clang-include-dir -Werror \
18+
// RUN: -isysroot %S/../Inputs/clang-importer-sdk
19+
20+
// REQUIRES: swift_feature_CDecl
21+
22+
//--- Lib.swift
23+
24+
// CHECK-NOT: assume_nonnull
25+
26+
// CHECK: #if defined(__cplusplus)
27+
// CHECK: extern "C" {
28+
// CHECK: #endif
29+
30+
/// My documentation
31+
@cdecl("simple")
32+
func a_simple(x: Int, bar y: Int) -> Int { return x }
33+
// CHECK-LABEL: // My documentation
34+
// CHECK-LABEL: SWIFT_EXTERN ptrdiff_t simple(ptrdiff_t x, ptrdiff_t y) SWIFT_NOEXCEPT SWIFT_WARN_UNUSED_RESULT;
35+
36+
@cdecl("primitiveTypes")
37+
public func b_primitiveTypes(i: Int, ci: CInt, l: CLong, c: CChar, f: Float, d: Double, b: Bool) {}
38+
// CHECK-LABEL: SWIFT_EXTERN void primitiveTypes(ptrdiff_t i, int ci, long l, char c, float f, double d, bool b) SWIFT_NOEXCEPT;
39+
40+
@cdecl("has_keyword_arg_names")
41+
func c_keywordArgNames(auto: Int, union: Int) {}
42+
// CHECK-LABEL: SWIFT_EXTERN void has_keyword_arg_names(ptrdiff_t auto_, ptrdiff_t union_) SWIFT_NOEXCEPT;
43+
44+
@cdecl("return_never")
45+
func d_returnNever() -> Never { fatalError() }
46+
// CHECK-LABEL: SWIFT_EXTERN void return_never(void) SWIFT_NOEXCEPT SWIFT_NORETURN;
47+
48+
/// Pointer types
49+
// CHECK: /// Pointer types
50+
51+
@cdecl("pointers")
52+
func f_pointers(_ x: UnsafeMutablePointer<Int>,
53+
y: UnsafePointer<Int>,
54+
z: UnsafeMutableRawPointer,
55+
w: UnsafeRawPointer,
56+
u: OpaquePointer) {}
57+
// CHECK: SWIFT_EXTERN void pointers(ptrdiff_t * _Nonnull x, ptrdiff_t const * _Nonnull y, void * _Nonnull z, void const * _Nonnull w, void * _Nonnull u) SWIFT_NOEXCEPT;
58+
59+
@cdecl("nullable_pointers")
60+
func g_nullablePointers(_ x: UnsafeMutableRawPointer,
61+
y: UnsafeMutableRawPointer?,
62+
z: UnsafeMutableRawPointer!) {}
63+
// CHECK: SWIFT_EXTERN void nullable_pointers(void * _Nonnull x, void * _Nullable y, void * _Null_unspecified z) SWIFT_NOEXCEPT;
64+
65+
// CHECK: #if defined(__cplusplus)
66+
// CHECK-NEXT: }
67+
// CHECK-NEXT: #endif
68+
69+
//--- Client.c
70+
71+
#include "cdecl.h"
72+
73+
int main() {
74+
ptrdiff_t x = simple(42, 43);
75+
primitiveTypes(1, 2, 3, 'a', 1.0f, 2.0, true);
76+
has_keyword_arg_names(1, 2);
77+
return_never();
78+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/// Ensure we print @cdecl and @_cdecl only once.
2+
3+
// RUN: %empty-directory(%t)
4+
5+
/// Generate cdecl.h
6+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) \
7+
// RUN: %s -emit-module -verify -o %t -emit-module-doc \
8+
// RUN: -emit-objc-header-path %t/cdecl.h \
9+
// RUN: -disable-objc-attr-requires-foundation-module \
10+
// RUN: -enable-experimental-feature CDecl
11+
12+
/// Check cdecl.h directly
13+
// RUN: %FileCheck %s --input-file %t/cdecl.h
14+
// RUN: %check-in-clang %t/cdecl.h
15+
// RUN: %check-in-clang-c %t/cdecl.h -Wnullable-to-nonnull-conversion
16+
// RUN: %check-in-clang-cxx %t/cdecl.h
17+
18+
// REQUIRES: swift_feature_CDecl
19+
// REQUIRES: objc_interop
20+
21+
@cdecl("cFunc")
22+
func cFunc() { }
23+
// CHECK: cFunc
24+
// CHECK-NOT: cFunc
25+
26+
/// The class would break C parsing if printed in wrong block
27+
@objc
28+
class ObjCClass {}
29+
30+
@_cdecl("objcFunc")
31+
func objcFunc() -> ObjCClass! { return ObjCClass() }
32+
// CHECK: objcFunc
33+
// CHECK-NOT: objcFunc

test/PrintAsObjC/cdecl.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// RUN: %FileCheck %s < %t/cdecl.h
55
// RUN: %check-in-clang %t/cdecl.h
66
// RUN: %check-in-clang -fno-modules -Qunused-arguments %t/cdecl.h -include ctypes.h -include CoreFoundation.h
7+
// RUN: %check-in-clang-c -fno-modules -Qunused-arguments %t/cdecl.h -include ctypes.h -include CoreFoundation.h
78
// RUN: %check-in-clang-cxx -fno-modules -Qunused-arguments %t/cdecl.h -include ctypes.h -include CoreFoundation.h
89

910
// REQUIRES: objc_interop

test/PrintAsObjC/lit.local.cfg

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ config.substitutions.insert(0, ('%check-in-clang',
1010
'-I %%clang-include-dir '
1111
'-isysroot %r/Inputs/clang-importer-sdk' % config.test_source_root) )
1212

13+
config.substitutions.insert(0, ('%check-in-clang-c',
14+
'%%clang-no-modules -fsyntax-only -x c-header -std=c99 '
15+
'-Weverything -Werror -Wno-unused-macros -Wno-incomplete-module '
16+
'-Wno-auto-import -Wno-poison-system-directories '
17+
'-F %%clang-importer-sdk-path/frameworks '
18+
'-I %%clang-include-dir '
19+
'-isysroot %r/Inputs/clang-importer-sdk' % config.test_source_root) )
20+
1321
config.substitutions.insert(0, ('%check-in-clang-cxx',
1422
'%%clang -fsyntax-only -x objective-c++-header -std=c++17 '
1523
'-fobjc-arc -fmodules -fmodules-validate-system-headers '

test/attr/attr_cdecl_official.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,19 @@ class Foo {
5555

5656
@cdecl("throwing") // expected-error{{raising errors from @cdecl functions is not supported}}
5757
func throwing() throws { }
58+
59+
@cdecl("acceptedPointers")
60+
func acceptedPointers(_ x: UnsafeMutablePointer<Int>,
61+
y: UnsafePointer<Int>,
62+
z: UnsafeMutableRawPointer,
63+
w: UnsafeRawPointer,
64+
u: OpaquePointer) {}
65+
66+
@cdecl("rejectedPointers")
67+
func rejectedPointers( // expected-error 6 {{global function cannot be marked '@cdecl' because the type of the parameter}}
68+
x: UnsafePointer<String>, // expected-note {{Swift structs cannot be represented in Objective-C}} // FIXME: Should reference C.
69+
y: CVaListPointer, // expected-note {{Swift structs cannot be represented in Objective-C}}
70+
z: UnsafeBufferPointer<Int>, // expected-note {{Swift structs cannot be represented in Objective-C}}
71+
u: UnsafeMutableBufferPointer<Int>, // expected-note {{Swift structs cannot be represented in Objective-C}}
72+
v: UnsafeRawBufferPointer, // expected-note {{Swift structs cannot be represented in Objective-C}}
73+
t: UnsafeMutableRawBufferPointer) {} // expected-note {{Swift structs cannot be represented in Objective-C}}

test/lit.cfg

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,11 @@ config.substitutions.append( ('%clangxx',
789789
"%r %s" %
790790
(config.clangxx, clang_mcp_opt)) )
791791

792+
# Alternative to %clang that doesn't require -fmodules.
793+
config.substitutions.append( ('%clang-no-modules',
794+
"%r" %
795+
(config.clang)) )
796+
792797
# This must come after all substitutions containing "%clang".
793798
# Note: %clang is the locally-built clang.
794799
# To get Xcode's clang, use %target-clang.

0 commit comments

Comments
 (0)