From 8cf4c4d10d029cfcf5386fe0c50463702235c1e4 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Fri, 17 Oct 2025 10:10:14 -0700 Subject: [PATCH] [C++ interop] Pass top-level declarations to the code generator. (#6237) This allows us to lower indirectly-referenced C++ functions and variables. --- toolchain/check/cpp/thunk.cpp | 3 ++ toolchain/lower/file_context.cpp | 29 ++++++++++++------ .../testdata/interop/cpp/function_decl.carbon | 20 ++++++++----- .../lower/testdata/interop/cpp/globals.carbon | 4 ++- .../testdata/interop/cpp/template.carbon | 30 +++++++++---------- 5 files changed, 54 insertions(+), 32 deletions(-) diff --git a/toolchain/check/cpp/thunk.cpp b/toolchain/check/cpp/thunk.cpp index 0bf6f47d852e..251af0483a52 100644 --- a/toolchain/check/cpp/thunk.cpp +++ b/toolchain/check/cpp/thunk.cpp @@ -4,6 +4,7 @@ #include "toolchain/check/cpp/thunk.h" +#include "clang/AST/ASTConsumer.h" #include "clang/AST/GlobalDecl.h" #include "clang/AST/Mangle.h" #include "clang/Sema/Lookup.h" @@ -569,6 +570,8 @@ auto BuildCppThunk(Context& context, const SemIR::Function& callee_function) return nullptr; } + context.clang_sema().getASTConsumer().HandleTopLevelDecl( + clang::DeclGroupRef(thunk_function_decl)); return thunk_function_decl; } diff --git a/toolchain/lower/file_context.cpp b/toolchain/lower/file_context.cpp index dc1b38f38477..68818f5ab80b 100644 --- a/toolchain/lower/file_context.cpp +++ b/toolchain/lower/file_context.cpp @@ -72,6 +72,26 @@ auto FileContext::PrepareToLower() -> void { // const-correct. cpp_code_generator_->Initialize( const_cast(clang_ast_unit()->getASTContext())); + + // Work around `visitLocalTopLevelDecls` not being const. It doesn't modify + // the AST unit other than triggering deserialization. + auto* non_const_ast_unit = const_cast(clang_ast_unit()); + + // Emit any top-level declarations now. + // TODO: This may miss things that we need to emit which are handed to the + // ASTConsumer in other ways. Instead of doing this, we should create the + // CodeGenerator earlier and register it as an ASTConsumer before we parse + // the C++ inputs. + non_const_ast_unit->visitLocalTopLevelDecls( + cpp_code_generator_.get(), + [](void* codegen_ptr, const clang::Decl* decl) { + auto* codegen = static_cast(codegen_ptr); + // CodeGenerator won't modify the declaration it's given, but we can + // only call it via the ASTConsumer interface which doesn't know that. + auto* non_const_decl = const_cast(decl); + codegen->HandleTopLevelDecl(clang::DeclGroupRef(non_const_decl)); + return true; + }); } // Lower all types that were required to be complete. @@ -396,9 +416,6 @@ auto FileContext::HandleReferencedCppFunction(clang::FunctionDecl* cpp_decl) cpp_code_generator_->GetAddrOfGlobal(CreateGlobalDecl(cpp_def), /*isForDefinition=*/false); CARBON_CHECK(function_address); - - // Emit the function code. - cpp_code_generator_->HandleTopLevelDecl(clang::DeclGroupRef(cpp_def)); } auto FileContext::HandleReferencedSpecificFunction( @@ -442,12 +459,6 @@ auto FileContext::BuildFunctionDecl(SemIR::FunctionId function_id, // corresponding C++ function anyway. if (function.special_function_kind == SemIR::Function::SpecialFunctionKind::HasCppThunk) { - // Make sure Clang emits this function. - // TODO: This shouldn't be necessary: Clang should emit definitions of - // functions that it emits calls to. But this doesn't currently work. - auto clang_decl_id = sem_ir().functions().Get(function_id).clang_decl_id; - HandleReferencedCppFunction(cast( - sem_ir().clang_decls().Get(clang_decl_id).key.decl)); return nullptr; } diff --git a/toolchain/lower/testdata/interop/cpp/function_decl.carbon b/toolchain/lower/testdata/interop/cpp/function_decl.carbon index d059eb5bc07b..ca469f56631f 100644 --- a/toolchain/lower/testdata/interop/cpp/function_decl.carbon +++ b/toolchain/lower/testdata/interop/cpp/function_decl.carbon @@ -95,14 +95,14 @@ fn MyF() { inline void foo1() {} inline void foo2() { foo1(); } -// --- todo_import_inline_recursive_function_decl.carbon +// --- import_inline_recursive_function_decl.carbon library "[[@TEST_NAME]]"; import Cpp library "inline_recursive_function_decl.h"; fn MyF() { - // TODO: This should generate the definition of the inline function `foo1()`. + // This should generate the definition of the inline function `foo1()`. Cpp.foo2(); } @@ -292,13 +292,15 @@ fn MyF() { // CHECK:STDOUT: !11 = !DILocation(line: 8, column: 3, scope: !7) // CHECK:STDOUT: !12 = !DILocation(line: 9, column: 3, scope: !7) // CHECK:STDOUT: !13 = !DILocation(line: 6, column: 1, scope: !7) -// CHECK:STDOUT: ; ModuleID = 'todo_import_inline_recursive_function_decl.carbon' -// CHECK:STDOUT: source_filename = "todo_import_inline_recursive_function_decl.carbon" +// CHECK:STDOUT: ; ModuleID = 'import_inline_recursive_function_decl.carbon' +// CHECK:STDOUT: source_filename = "import_inline_recursive_function_decl.carbon" // CHECK:STDOUT: target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128" // CHECK:STDOUT: target triple = "x86_64-unknown-linux-gnu" // CHECK:STDOUT: // CHECK:STDOUT: $_Z4foo2v = comdat any // CHECK:STDOUT: +// CHECK:STDOUT: $_Z4foo1v = comdat any +// CHECK:STDOUT: // CHECK:STDOUT: define void @_CMyF.Main() !dbg !7 { // CHECK:STDOUT: entry: // CHECK:STDOUT: call void @_Z4foo2v(), !dbg !10 @@ -312,10 +314,14 @@ fn MyF() { // CHECK:STDOUT: ret void // CHECK:STDOUT: } // CHECK:STDOUT: -// CHECK:STDOUT: declare void @_Z4foo1v() #1 +// CHECK:STDOUT: ; Function Attrs: mustprogress noinline nounwind optnone +// CHECK:STDOUT: define linkonce_odr dso_local void @_Z4foo1v() #1 comdat { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: ret void +// CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: attributes #0 = { mustprogress noinline optnone "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="0" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } -// CHECK:STDOUT: attributes #1 = { "no-trapping-math"="true" "stack-protector-buffer-size"="0" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } +// CHECK:STDOUT: attributes #1 = { mustprogress noinline nounwind optnone "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="0" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } // CHECK:STDOUT: // CHECK:STDOUT: !llvm.module.flags = !{!0, !1, !2, !3, !4} // CHECK:STDOUT: !llvm.dbg.cu = !{!5} @@ -326,7 +332,7 @@ fn MyF() { // CHECK:STDOUT: !3 = !{i32 8, !"PIC Level", i32 0} // CHECK:STDOUT: !4 = !{i32 7, !"PIE Level", i32 2} // CHECK:STDOUT: !5 = distinct !DICompileUnit(language: DW_LANG_C, file: !6, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug) -// CHECK:STDOUT: !6 = !DIFile(filename: "todo_import_inline_recursive_function_decl.carbon", directory: "") +// CHECK:STDOUT: !6 = !DIFile(filename: "import_inline_recursive_function_decl.carbon", directory: "") // CHECK:STDOUT: !7 = distinct !DISubprogram(name: "MyF", linkageName: "_CMyF.Main", scope: null, file: !6, line: 6, type: !8, spFlags: DISPFlagDefinition, unit: !5) // CHECK:STDOUT: !8 = !DISubroutineType(types: !9) // CHECK:STDOUT: !9 = !{} diff --git a/toolchain/lower/testdata/interop/cpp/globals.carbon b/toolchain/lower/testdata/interop/cpp/globals.carbon index a04ead2a8afc..d6a602f0751e 100644 --- a/toolchain/lower/testdata/interop/cpp/globals.carbon +++ b/toolchain/lower/testdata/interop/cpp/globals.carbon @@ -34,7 +34,9 @@ fn MyF() { // CHECK:STDOUT: target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128" // CHECK:STDOUT: target triple = "x86_64-unknown-linux-gnu" // CHECK:STDOUT: -// CHECK:STDOUT: @global = external global {} +// CHECK:STDOUT: %class.C = type { i8 } +// CHECK:STDOUT: +// CHECK:STDOUT: @global = dso_local global %class.C zeroinitializer, align 1 // CHECK:STDOUT: // CHECK:STDOUT: define void @_CMyF.Main() !dbg !7 { // CHECK:STDOUT: entry: diff --git a/toolchain/lower/testdata/interop/cpp/template.carbon b/toolchain/lower/testdata/interop/cpp/template.carbon index 510a633a16a5..784647149902 100644 --- a/toolchain/lower/testdata/interop/cpp/template.carbon +++ b/toolchain/lower/testdata/interop/cpp/template.carbon @@ -37,10 +37,10 @@ fn PassClass(a: Cpp.Class) -> Cpp.Class { // CHECK:STDOUT: // CHECK:STDOUT: %class.Class = type { i8 } // CHECK:STDOUT: -// CHECK:STDOUT: $_Z8identityIiET_S0_ = comdat any -// CHECK:STDOUT: // CHECK:STDOUT: $_Z8identityI5ClassET_S1_ = comdat any // CHECK:STDOUT: +// CHECK:STDOUT: $_Z8identityIiET_S0_ = comdat any +// CHECK:STDOUT: // CHECK:STDOUT: define i32 @_CPassI32.Main(i32 %a) !dbg !7 { // CHECK:STDOUT: entry: // CHECK:STDOUT: %identity.call = call i32 @_Z8identityIiET_S0_(i32 %a), !dbg !10 @@ -53,17 +53,8 @@ fn PassClass(a: Cpp.Class) -> Cpp.Class { // CHECK:STDOUT: ret void, !dbg !14 // CHECK:STDOUT: } // CHECK:STDOUT: -// CHECK:STDOUT: ; Function Attrs: mustprogress noinline nounwind optnone -// CHECK:STDOUT: define linkonce_odr dso_local i32 @_Z8identityIiET_S0_(i32 %x) #0 comdat { -// CHECK:STDOUT: entry: -// CHECK:STDOUT: %x.addr = alloca i32, align 4 -// CHECK:STDOUT: store i32 %x, ptr %x.addr, align 4 -// CHECK:STDOUT: %0 = load i32, ptr %x.addr, align 4 -// CHECK:STDOUT: ret i32 %0 -// CHECK:STDOUT: } -// CHECK:STDOUT: // CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress -// CHECK:STDOUT: define dso_local void @_Z8identityI5ClassET_S1_.carbon_thunk(ptr %x, ptr %return) #1 { +// CHECK:STDOUT: define dso_local void @_Z8identityI5ClassET_S1_.carbon_thunk(ptr %x, ptr %return) #0 { // CHECK:STDOUT: entry: // CHECK:STDOUT: %x.addr = alloca ptr, align 8 // CHECK:STDOUT: %return.addr = alloca ptr, align 8 @@ -78,14 +69,23 @@ fn PassClass(a: Cpp.Class) -> Cpp.Class { // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: ; Function Attrs: mustprogress noinline nounwind optnone -// CHECK:STDOUT: define linkonce_odr dso_local void @_Z8identityI5ClassET_S1_() #0 comdat { +// CHECK:STDOUT: define linkonce_odr dso_local void @_Z8identityI5ClassET_S1_() #1 comdat { // CHECK:STDOUT: entry: // CHECK:STDOUT: %x = alloca %class.Class, align 1 // CHECK:STDOUT: ret void // CHECK:STDOUT: } // CHECK:STDOUT: -// CHECK:STDOUT: attributes #0 = { mustprogress noinline nounwind optnone "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="0" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } -// CHECK:STDOUT: attributes #1 = { alwaysinline mustprogress "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="0" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } +// CHECK:STDOUT: ; Function Attrs: mustprogress noinline nounwind optnone +// CHECK:STDOUT: define linkonce_odr dso_local i32 @_Z8identityIiET_S0_(i32 %x) #1 comdat { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %x.addr = alloca i32, align 4 +// CHECK:STDOUT: store i32 %x, ptr %x.addr, align 4 +// CHECK:STDOUT: %0 = load i32, ptr %x.addr, align 4 +// CHECK:STDOUT: ret i32 %0 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: attributes #0 = { alwaysinline mustprogress "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="0" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } +// CHECK:STDOUT: attributes #1 = { mustprogress noinline nounwind optnone "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="0" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } // CHECK:STDOUT: // CHECK:STDOUT: !llvm.module.flags = !{!0, !1, !2, !3, !4} // CHECK:STDOUT: !llvm.dbg.cu = !{!5}