From 3445b86ab836b78a8f6c4b76ca11c1df15c384e2 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 16 Oct 2025 19:05:09 -0700 Subject: [PATCH 1/2] [SILGen] Switch `@_extern(c)` over to a foreign entrypoint Rather than treating `@_extern(c)` functions like a Swift function with a C calling convention, treat them as foreign entrypoints, which better matches how we handle imported C functions. --- lib/SIL/IR/SILDeclRef.cpp | 3 +++ lib/SIL/IR/SILFunctionType.cpp | 2 -- test/SILGen/extern_c.swift | 16 ++++++++-------- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/lib/SIL/IR/SILDeclRef.cpp b/lib/SIL/IR/SILDeclRef.cpp index 2130c8cbb1e2e..2fb6b27fdb023 100644 --- a/lib/SIL/IR/SILDeclRef.cpp +++ b/lib/SIL/IR/SILDeclRef.cpp @@ -111,6 +111,9 @@ bool swift::requiresForeignEntryPoint(ValueDecl *vd) { if (vd->hasClangNode()) return true; + if (ExternAttr::find(vd->getAttrs(), ExternKind::C)) + return true; + if (auto *accessor = dyn_cast(vd)) { // Property accessors should be generated alongside the property. if (accessor->isGetterOrSetter()) { diff --git a/lib/SIL/IR/SILFunctionType.cpp b/lib/SIL/IR/SILFunctionType.cpp index d7981cc106d0e..f945630fd9c49 100644 --- a/lib/SIL/IR/SILFunctionType.cpp +++ b/lib/SIL/IR/SILFunctionType.cpp @@ -4474,8 +4474,6 @@ TypeConverter::getDeclRefRepresentation(SILDeclRef c) { case SILDeclRef::Kind::Func: if (c.getDecl()->getDeclContext()->isTypeContext()) return SILFunctionTypeRepresentation::Method; - if (ExternAttr::find(c.getDecl()->getAttrs(), ExternKind::C)) - return SILFunctionTypeRepresentation::CFunctionPointer; return SILFunctionTypeRepresentation::Thin; case SILDeclRef::Kind::Destroyer: diff --git a/test/SILGen/extern_c.swift b/test/SILGen/extern_c.swift index c95a49288beb9..16cd7010bfa78 100644 --- a/test/SILGen/extern_c.swift +++ b/test/SILGen/extern_c.swift @@ -6,11 +6,11 @@ @_extern(c, "my_c_name") func withCName(_ x: Int) -> Int -// CHECK-DAG: sil hidden_external [asmname "take_c_func_ptr"] @$s8extern_c12takeCFuncPtryyS2iXCF : $@convention(c) (@convention(c) (Int) -> Int) -> () +// CHECK-DAG: sil hidden_external [asmname "take_c_func_ptr"] @$s8extern_c12takeCFuncPtryyS2iXCFTo : $@convention(c) (@convention(c) (Int) -> Int) -> () @_extern(c, "take_c_func_ptr") func takeCFuncPtr(_ f: @convention(c) (Int) -> Int) -// CHECK-DAG: sil [asmname "public_visible"] @$s8extern_c16publicVisibilityyS2iF : $@convention(c) (Int) -> Int +// CHECK-DAG: sil [serialized] [asmname "public_visible"] @$s8extern_c16publicVisibilityyS2iFTo : $@convention(c) (Int) -> Int @_extern(c, "public_visible") public func publicVisibility(_ x: Int) -> Int @@ -18,32 +18,32 @@ public func publicVisibility(_ x: Int) -> Int @_extern(c, "private_visible") private func privateVisibility(_ x: Int) -> Int -// CHECK-DAG: sil hidden_external [asmname "withoutCName"] @$s8extern_c12withoutCNameSiyF : $@convention(c) () -> Int +// CHECK-DAG: sil hidden_external [asmname "withoutCName"] @$s8extern_c12withoutCNameSiyFTo : $@convention(c) () -> Int @_extern(c) func withoutCName() -> Int // CHECK-DAG: sil hidden [ossa] @$s8extern_c10defaultArgyySiFfA_ : $@convention(thin) () -> Int { -// CHECK-DAG: sil hidden_external [asmname "default_arg"] @$s8extern_c10defaultArgyySiF : $@convention(c) (Int) -> () +// CHECK-DAG: sil hidden_external [asmname "default_arg"] @$s8extern_c10defaultArgyySiFTo : $@convention(c) (Int) -> () @_extern(c, "default_arg") func defaultArg(_ x: Int = 42) func main() { // CHECK-DAG: [[F1:%.+]] = function_ref @$s8extern_c9withCNameyS2iFTo : $@convention(c) (Int) -> Int - // CHECK-DAG: [[F2:%.+]] = function_ref @$s8extern_c12takeCFuncPtryyS2iXCF : $@convention(c) (@convention(c) (Int) -> Int) -> () + // CHECK-DAG: [[F2:%.+]] = function_ref @$s8extern_c12takeCFuncPtryyS2iXCFTo : $@convention(c) (@convention(c) (Int) -> Int) -> () // CHECK-DAG: apply [[F2]]([[F1]]) : $@convention(c) (@convention(c) (Int) -> Int) -> () takeCFuncPtr(withCName) - // CHECK-DAG: [[F3:%.+]] = function_ref @$s8extern_c16publicVisibilityyS2iF : $@convention(c) (Int) -> Int + // CHECK-DAG: [[F3:%.+]] = function_ref @$s8extern_c16publicVisibilityyS2iFTo : $@convention(c) (Int) -> Int // CHECK-DAG: apply [[F3]]({{.*}}) : $@convention(c) (Int) -> Int _ = publicVisibility(42) // CHECK-DAG: [[F4:%.+]] = function_ref @$s8extern_c17privateVisibility{{.*}} : $@convention(c) (Int) -> Int // CHECK-DAG: apply [[F4]]({{.*}}) : $@convention(c) (Int) -> Int _ = privateVisibility(24) - // CHECK-DAG: [[F5:%.+]] = function_ref @$s8extern_c12withoutCNameSiyF : $@convention(c) () -> Int + // CHECK-DAG: [[F5:%.+]] = function_ref @$s8extern_c12withoutCNameSiyFTo : $@convention(c) () -> Int // CHECK-DAG: apply [[F5]]() : $@convention(c) () -> Int _ = withoutCName() // CHECK-DAG: [[F6:%.+]] = function_ref @$s8extern_c10defaultArgyySiFfA_ : $@convention(thin) () -> Int // CHECK-DAG: [[DEFAULT_V:%.+]] = apply [[F6]]() : $@convention(thin) () -> Int - // CHECK-DAG: [[F7:%.+]] = function_ref @$s8extern_c10defaultArgyySiF : $@convention(c) (Int) -> () + // CHECK-DAG: [[F7:%.+]] = function_ref @$s8extern_c10defaultArgyySiFTo : $@convention(c) (Int) -> () // CHECK-DAG: apply [[F7]]([[DEFAULT_V]]) : $@convention(c) (Int) -> () defaultArg() } From 79fc0f1b2edc0ab5f71abace198d051a2e7d42e9 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 16 Oct 2025 20:04:34 -0700 Subject: [PATCH 2/2] [extern] Give `@_extern(c)` functions `@convention(c)` type This ensures that when we reference them, we get an appropriately-typed function value. SILGen can then thunk to whatever function type is needed. --- lib/SILGen/SILGenExpr.cpp | 3 +++ lib/Sema/TypeCheckDecl.cpp | 5 +++++ test/SILGen/extern_c.swift | 12 ++++++++++++ 3 files changed, 20 insertions(+) diff --git a/lib/SILGen/SILGenExpr.cpp b/lib/SILGen/SILGenExpr.cpp index e7d07dd2cffeb..b5a287f5f497b 100644 --- a/lib/SILGen/SILGenExpr.cpp +++ b/lib/SILGen/SILGenExpr.cpp @@ -996,6 +996,9 @@ emitRValueForDecl(SILLocation loc, ConcreteDeclRef declRef, Type ncRefType, // If the referenced decl isn't a VarDecl, it should be a constant of some // sort. SILDeclRef silDeclRef(decl); + if (ExternAttr::find(decl->getAttrs(), ExternKind::C)) + silDeclRef = silDeclRef.asForeign(); + assert(silDeclRef.getParameterListCount() == 1); auto substType = cast(refType); auto typeContext = getFunctionTypeInfo(substType); diff --git a/lib/Sema/TypeCheckDecl.cpp b/lib/Sema/TypeCheckDecl.cpp index b4de9d0e0c106..aa3482315b2a4 100644 --- a/lib/Sema/TypeCheckDecl.cpp +++ b/lib/Sema/TypeCheckDecl.cpp @@ -2565,6 +2565,11 @@ InterfaceTypeRequest::evaluate(Evaluator &eval, ValueDecl *D) const { infoBuilder = infoBuilder.withSendingResult(); } + if (ExternAttr::find(D->getAttrs(), ExternKind::C)) { + infoBuilder = infoBuilder.withRepresentation( + FunctionTypeRepresentation::CFunctionPointer); + } + // Lifetime dependencies only apply to the outer function type. if (!hasSelf && lifetimeDependenceInfo.has_value()) { infoBuilder = diff --git a/test/SILGen/extern_c.swift b/test/SILGen/extern_c.swift index 16cd7010bfa78..054d063492014 100644 --- a/test/SILGen/extern_c.swift +++ b/test/SILGen/extern_c.swift @@ -27,6 +27,10 @@ func withoutCName() -> Int @_extern(c, "default_arg") func defaultArg(_ x: Int = 42) +func callMe(body: (Int) -> Int) -> Int { + return body(17) +} + func main() { // CHECK-DAG: [[F1:%.+]] = function_ref @$s8extern_c9withCNameyS2iFTo : $@convention(c) (Int) -> Int // CHECK-DAG: [[F2:%.+]] = function_ref @$s8extern_c12takeCFuncPtryyS2iXCFTo : $@convention(c) (@convention(c) (Int) -> Int) -> () @@ -46,6 +50,14 @@ func main() { // CHECK-DAG: [[F7:%.+]] = function_ref @$s8extern_c10defaultArgyySiFTo : $@convention(c) (Int) -> () // CHECK-DAG: apply [[F7]]([[DEFAULT_V]]) : $@convention(c) (Int) -> () defaultArg() + + // CHECK-DAG: [[CREF:%[0-9]+]] = function_ref @$s8extern_c16publicVisibilityyS2iFTo : $@convention(c) (Int) -> Int + // CHECK-DAG: [[THUNK:%[0-9]+]] = function_ref @$sS2iIetCyd_S2iIegyd_TR : $@convention(thin) (Int, @convention(c) (Int) -> Int) -> Int + // CHECK-DAG: [[APPLIED:%[0-9]+]] = partial_apply [callee_guaranteed] [[THUNK]]([[CREF]]) : $@convention(thin) (Int, @convention(c) (Int) -> Int) -> Int + // CHECK-DAG: [[NOESCAPE_APPLIED:%[0-9]+]] = convert_escape_to_noescape [not_guaranteed] [[APPLIED]] to $@noescape @callee_guaranteed (Int) -> Int + // CHECK-DAG: [[CALL_ME:%[0-9]+]] = function_ref @$s8extern_c6callMe4bodyS3iXE_tF + // CHECK-DAG: apply [[CALL_ME]]([[NOESCAPE_APPLIED]]) : $@convention(thin) (@guaranteed @noescape @callee_guaranteed (Int) -> Int) -> Int + _ = callMe(body: publicVisibility) } main()