Fix mangling collision for C++ class template specializations (#7269)

Carbon-side thunks (for example the `Copy`/`Destroy` witness thunks
generated for imported C++ types) are mangled by Carbon, and their names
incorporate a fingerprint of the involved types. The instruction
fingerprinter identifies a class only by its name and parent scope,
which is sufficient for Carbon classes but not for imported C++ classes:
different specializations of one class template (and other cases such as
types in anonymous namespaces) share a Carbon name and parent scope. As
a result, the thunks for two distinct specializations could mangle to
the same name, producing a single LLVM function with two definitions and
failing `verifyModule` during lowering.

When fingerprinting a class imported from C++, also include the Clang
mangled name of its type.

Test: toolchain/lower/testdata/interop/cpp/thunks.carbon gains a split
with two specializations of one class template, each requiring a thunk;
their thunks now get distinct mangled names instead of colliding.

Assisted-by: Claude Code

---------

Co-authored-by: Christopher Di Bella <cjdb.ns@gmail.com>
This commit is contained in:
Chandler Carruth
2026-05-30 08:13:00 +00:00
committed by GitHub
co-authored by Christopher Di Bella
parent 3ef128ac91
commit 8a59f2a76b
64 changed files with 4762 additions and 4330 deletions
+9 -9
View File
@@ -2103,12 +2103,12 @@ fn F() {
// CHECK:STDOUT: %int_32: Core.IntLiteral = int_value 32 [concrete]
// CHECK:STDOUT: %i32: type = class_type @Int, @Int(%int_32) [concrete]
// CHECK:STDOUT: %A: type = class_type @A [concrete]
// CHECK:STDOUT: %pattern_type.b54: type = pattern_type %A [concrete]
// CHECK:STDOUT: %pattern_type.aa6: type = pattern_type %A [concrete]
// CHECK:STDOUT: %B: type = class_type @B [concrete]
// CHECK:STDOUT: %A.elem: type = unbound_element_type %A, %B [concrete]
// CHECK:STDOUT: %B.elem: type = unbound_element_type %B, %i32 [concrete]
// CHECK:STDOUT: %.b0d: ref %B = class_element_access imports.%a.var, element0 [concrete]
// CHECK:STDOUT: %.94c: ref %i32 = class_element_access %.b0d, element0 [concrete]
// CHECK:STDOUT: %.e8d: ref %B = class_element_access imports.%a.var, element0 [concrete]
// CHECK:STDOUT: %.6b6: ref %i32 = class_element_access %.e8d, element0 [concrete]
// CHECK:STDOUT: %int_2.ecc: Core.IntLiteral = int_value 2 [concrete]
// CHECK:STDOUT: %ImplicitAs.type.544: type = facet_type <@ImplicitAs, @ImplicitAs(%i32)> [concrete]
// CHECK:STDOUT: %To: Core.IntLiteral = symbolic_binding To, 0 [symbolic]
@@ -2128,11 +2128,11 @@ fn F() {
// CHECK:STDOUT:
// CHECK:STDOUT: imports {
// CHECK:STDOUT: %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
// CHECK:STDOUT: .m = constants.%.94c
// CHECK:STDOUT: .m = constants.%.6b6
// CHECK:STDOUT: import Cpp//...
// CHECK:STDOUT: }
// CHECK:STDOUT: %a.patt: %pattern_type.b54 = ref_binding_pattern a [concrete]
// CHECK:STDOUT: %a.var_patt: %pattern_type.b54 = var_pattern %a.patt [concrete]
// CHECK:STDOUT: %a.patt: %pattern_type.aa6 = ref_binding_pattern a [concrete]
// CHECK:STDOUT: %a.var_patt: %pattern_type.aa6 = var_pattern %a.patt [concrete]
// CHECK:STDOUT: %a.var: ref %A = var %a.var_patt [concrete]
// CHECK:STDOUT: %Core.import_ref.7f3: @Core.IntLiteral.as.ImplicitAs.impl.%Core.IntLiteral.as.ImplicitAs.impl.Convert.type (%Core.IntLiteral.as.ImplicitAs.impl.Convert.type.766) = import_ref Core//prelude/types/int, loc{{\d+_\d+}}, loaded [symbolic = @Core.IntLiteral.as.ImplicitAs.impl.%Core.IntLiteral.as.ImplicitAs.impl.Convert (constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.ec3)]
// CHECK:STDOUT: %ImplicitAs.impl_witness_table.1be = impl_witness_table (%Core.import_ref.7f3), @Core.IntLiteral.as.ImplicitAs.impl [concrete]
@@ -2143,10 +2143,10 @@ fn F() {
// CHECK:STDOUT: %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
// CHECK:STDOUT: <elided>
// CHECK:STDOUT: %b.ref: %A.elem = name_ref b, @A.%.1 [concrete = @A.%.1]
// CHECK:STDOUT: %.loc17_6.1: ref %B = class_element_access imports.%a.var, element0 [concrete = constants.%.b0d]
// CHECK:STDOUT: %.loc17_6.1: ref %B = class_element_access imports.%a.var, element0 [concrete = constants.%.e8d]
// CHECK:STDOUT: %c.ref: %B.elem = name_ref c, @B.%.1 [concrete = @B.%.1]
// CHECK:STDOUT: %.loc17_6.2: ref %i32 = class_element_access %.loc17_6.1, element0 [concrete = constants.%.94c]
// CHECK:STDOUT: %m.ref: ref %i32 = name_ref m, constants.%.94c [concrete = constants.%.94c]
// CHECK:STDOUT: %.loc17_6.2: ref %i32 = class_element_access %.loc17_6.1, element0 [concrete = constants.%.6b6]
// CHECK:STDOUT: %m.ref: ref %i32 = name_ref m, constants.%.6b6 [concrete = constants.%.6b6]
// CHECK:STDOUT: %int_2: Core.IntLiteral = int_value 2 [concrete = constants.%int_2.ecc]
// CHECK:STDOUT: %impl.elem0: %.fa3 = impl_witness_access constants.%ImplicitAs.impl_witness.ddb, element0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.e6b]
// CHECK:STDOUT: %bound_method.loc17_9.1: <bound method> = bound_method %int_2, %impl.elem0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.bound]