Kaynağa Gözat

Instantiate C++ templates at end of file. (#6084)

Mark C++ functions as used when overload resolution selects them, and
trigger Clang's end-of-TU processing at the end of the Carbon
compilation to perform instantiation and other pending cleanup steps.
Richard Smith 7 ay önce
ebeveyn
işleme
65a7e50037

+ 1 - 0
toolchain/check/BUILD

@@ -226,6 +226,7 @@ cc_library(
         "//toolchain/sem_ir:formatter",
         "//toolchain/sem_ir:typed_insts",
         "@llvm-project//clang:frontend",
+        "@llvm-project//clang:sema",
         "@llvm-project//llvm:Support",
     ],
 )

+ 8 - 0
toolchain/check/check_unit.cpp

@@ -9,6 +9,7 @@
 #include <tuple>
 #include <utility>
 
+#include "clang/Sema/Sema.h"
 #include "common/growing_range.h"
 #include "common/pretty_stack_trace_function.h"
 #include "llvm/ADT/IntrusiveRefCntPtr.h"
@@ -580,6 +581,13 @@ auto CheckUnit::FinishRun() -> void {
   CheckPoisonedConcreteImplLookupQueries();
   CheckImpls();
 
+  if (auto* clang_ast = context_.sem_ir().clang_ast_unit()) {
+    // Ask Clang to perform any cleanups required, including instantiating used
+    // templates.
+    clang_ast->getSema().ActOnEndOfTranslationUnit();
+    context_.emitter().Flush();
+  }
+
   // Pop information for the file-level scope.
   context_.sem_ir().set_top_inst_block_id(context_.inst_block_stack().Pop());
   context_.scope_stack().Pop();

+ 2 - 0
toolchain/check/cpp_overload_resolution.cpp

@@ -171,6 +171,8 @@ auto PerformCppOverloadResolution(Context& context, SemIR::LocId loc_id,
     case clang::OverloadingResult::OR_Success: {
       // TODO: Handle the cases when Function is null.
       CARBON_CHECK(best_viable_fn->Function);
+      sema.MarkFunctionReferenced(clang::SourceLocation(),
+                                  best_viable_fn->Function);
       SemIR::InstId result =
           ImportCppFunctionDecl(context, loc_id, best_viable_fn->Function);
       return result;

+ 2 - 2
toolchain/check/testdata/interop/cpp/enum/anonymous.carbon

@@ -14,13 +14,13 @@
 
 enum { a, b, c };
 
-void F(decltype(a));
+void F(decltype(a)) {}
 
 class C {
  public:
   enum { d, e, f };
 
-  void F(decltype(d));
+  void F(decltype(d)) {}
 };
 
 // --- copy_enum.carbon

+ 9 - 4
toolchain/check/testdata/interop/cpp/function/function.carbon

@@ -134,16 +134,21 @@ fn F() {
 
 static auto foo() -> void;
 
-// --- import_static.carbon
+// --- todo_fail_import_static.carbon
 
 library "[[@TEST_NAME]]";
 
+// TODO: Promote this warning to an error by default.
+// CHECK:STDERR: todo_fail_import_static.carbon:[[@LINE+5]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./static.h:2:13: warning: function 'foo' has internal linkage but is not defined [CppInteropParseWarning]
+// CHECK:STDERR:     2 | static auto foo() -> void;
+// CHECK:STDERR:       |             ^
+// CHECK:STDERR:
 import Cpp library "static.h";
 
 fn F() {
   //@dump-sem-ir-begin
-  // TODO: We should eventually warn or error that `Cpp.foo` has internal
-  // linkage but is not defined.
+  // TODO: Produce a note pointing here.
   Cpp.foo();
   //@dump-sem-ir-end
 }
@@ -283,7 +288,7 @@ fn G() -> i32 {
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: --- import_static.carbon
+// CHECK:STDOUT: --- todo_fail_import_static.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]

+ 13 - 7
toolchain/check/testdata/interop/cpp/function/inline.carbon

@@ -42,11 +42,17 @@ inline void foo();
 
 library "[[@TEST_NAME]]";
 
+// TODO: Promote this warning to an error by default.
+// CHECK:STDERR: todo_fail_import_without_definition.carbon:[[@LINE+5]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./without_definition.h:2:13: warning: inline function 'foo' is not defined [CppInteropParseWarning]
+// CHECK:STDERR:     2 | inline void foo();
+// CHECK:STDERR:       |             ^
+// CHECK:STDERR:
 import Cpp library "without_definition.h";
 
 fn MyF() {
   //@dump-sem-ir-begin
-  // TODO: Error on using an inline function without definition.
+  // TODO: Produce a note pointing here.
   Cpp.foo();
 
   // Don't error on repeated calls.
@@ -126,12 +132,12 @@ fn MyF() {
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @MyF() {
 // CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   %Cpp.ref.loc9: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   %foo.ref.loc9: %.c5d = name_ref foo, imports.%.a21 [concrete = constants.%empty_struct]
-// CHECK:STDOUT:   %foo.call.loc9: init %empty_tuple.type = call imports.%foo.decl()
-// CHECK:STDOUT:   %Cpp.ref.loc12: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   %foo.ref.loc12: %.c5d = name_ref foo, imports.%.a21 [concrete = constants.%empty_struct]
-// CHECK:STDOUT:   %foo.call.loc12: init %empty_tuple.type = call imports.%foo.decl()
+// CHECK:STDOUT:   %Cpp.ref.loc15: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %foo.ref.loc15: %.c5d = name_ref foo, imports.%.a21 [concrete = constants.%empty_struct]
+// CHECK:STDOUT:   %foo.call.loc15: init %empty_tuple.type = call imports.%foo.decl()
+// CHECK:STDOUT:   %Cpp.ref.loc18: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %foo.ref.loc18: %.c5d = name_ref foo, imports.%.a21 [concrete = constants.%empty_struct]
+// CHECK:STDOUT:   %foo.call.loc18: init %empty_tuple.type = call imports.%foo.decl()
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 1 - 1
toolchain/check/testdata/interop/cpp/namespace.carbon

@@ -94,7 +94,7 @@ fn MyF() {
 
 // --- inline.h
 
-inline namespace { namespace N { inline namespace { void foo(); } } }
+inline namespace { namespace N { inline namespace { void foo() {} } } }
 
 // --- import_inline.carbon
 

+ 1 - 4
toolchain/lower/testdata/interop/cpp/function_in_template.carbon

@@ -16,9 +16,6 @@ template<typename T> struct X {
   static void f(T t) {}
 };
 
-// TODO: We should be able to instantiate the class and function when needed.
-template struct X<int>;
-
 using Y = X<int>;
 
 // --- use_class_template.carbon
@@ -47,7 +44,7 @@ fn F() {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: ; Function Attrs: mustprogress noinline nounwind optnone
-// CHECK:STDOUT: define weak_odr dso_local void @_ZN1XIiE1fEi(i32 %t) #0 comdat align 2 {
+// CHECK:STDOUT: define linkonce_odr dso_local void @_ZN1XIiE1fEi(i32 %t) #0 comdat align 2 {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %t.addr = alloca i32, align 4
 // CHECK:STDOUT:   store i32 %t, ptr %t.addr, align 4

+ 107 - 0
toolchain/lower/testdata/interop/cpp/template.carbon

@@ -0,0 +1,107 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/int.carbon
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/lower/testdata/interop/cpp/template.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/interop/cpp/template.carbon
+
+// --- function_template.h
+
+template<typename T> T identity(T x) { return x; }
+
+class Class {};
+
+// --- call.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "function_template.h";
+
+fn PassI32(a: i32) -> i32 {
+  return Cpp.identity(a);
+}
+
+fn PassClass(a: Cpp.Class) -> Cpp.Class {
+  return Cpp.identity(a);
+}
+
+// CHECK:STDOUT: ; ModuleID = 'call.carbon'
+// CHECK:STDOUT: source_filename = "call.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: %class.Class = type { i8 }
+// CHECK:STDOUT:
+// CHECK:STDOUT: $_Z8identityIiET_S0_ = comdat any
+// CHECK:STDOUT:
+// CHECK:STDOUT: $_Z8identityI5ClassET_S1_ = 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
+// CHECK:STDOUT:   ret i32 %identity.call, !dbg !11
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @_CPassClass.Main(ptr sret({}) %return, ptr %a) !dbg !12 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   call void @_Z8identityI5ClassET_S1_.carbon_thunk(ptr %a, ptr %return), !dbg !13
+// 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: mustprogress noinline nounwind optnone
+// CHECK:STDOUT: define linkonce_odr dso_local void @_Z8identityI5ClassET_S1_() #0 comdat {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %x = alloca %class.Class, align 1
+// CHECK:STDOUT:   ret void
+// 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: entry:
+// CHECK:STDOUT:   %x.addr = alloca ptr, align 8
+// CHECK:STDOUT:   %return.addr = alloca ptr, align 8
+// CHECK:STDOUT:   %agg.tmp = alloca %class.Class, align 1
+// CHECK:STDOUT:   %undef.agg.tmp = alloca %class.Class, align 1
+// CHECK:STDOUT:   store ptr %x, ptr %x.addr, align 8
+// CHECK:STDOUT:   store ptr %return, ptr %return.addr, align 8
+// CHECK:STDOUT:   %0 = load ptr, ptr %return.addr, align 8
+// CHECK:STDOUT:   %1 = load ptr, ptr %x.addr, align 8
+// CHECK:STDOUT:   call void @_Z8identityI5ClassET_S1_()
+// 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:
+// CHECK:STDOUT: !llvm.module.flags = !{!0, !1, !2, !3, !4}
+// CHECK:STDOUT: !llvm.dbg.cu = !{!5}
+// CHECK:STDOUT:
+// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5}
+// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
+// CHECK:STDOUT: !2 = !{i32 1, !"wchar_size", i32 4}
+// 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: "call.carbon", directory: "")
+// CHECK:STDOUT: !7 = distinct !DISubprogram(name: "PassI32", linkageName: "_CPassI32.Main", scope: null, file: !6, line: 6, type: !8, spFlags: DISPFlagDefinition, unit: !5)
+// CHECK:STDOUT: !8 = !DISubroutineType(types: !9)
+// CHECK:STDOUT: !9 = !{}
+// CHECK:STDOUT: !10 = !DILocation(line: 7, column: 10, scope: !7)
+// CHECK:STDOUT: !11 = !DILocation(line: 7, column: 3, scope: !7)
+// CHECK:STDOUT: !12 = distinct !DISubprogram(name: "PassClass", linkageName: "_CPassClass.Main", scope: null, file: !6, line: 10, type: !8, spFlags: DISPFlagDefinition, unit: !5)
+// CHECK:STDOUT: !13 = !DILocation(line: 11, column: 10, scope: !12)
+// CHECK:STDOUT: !14 = !DILocation(line: 11, column: 3, scope: !12)