From 4e5dccdbf7416e6675b9c91b548d7673b4b56bc8 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 11 Jun 2025 14:34:01 -0700 Subject: [PATCH] When making a direct call to a thunk, inline the call in SemIR. (#5642) This preserves the constant values of the arguments to the thunk, which is important if the thunk requires conversion of an `IntLiteral` to some other type. This should become unnecessary once we have form support, but avoiding the indirection through a thunk function seems valuable even once that support is in place. To support this, track whether a function is a thunk on the Function object, and if so, what the callee of the thunk is. This information is also included in formatted SemIR when dumping the thunk. --- toolchain/check/call.cpp | 40 +++++-- toolchain/check/eval.cpp | 2 +- toolchain/check/handle_function.cpp | 2 +- toolchain/check/import_ref.cpp | 22 +++- .../testdata/impl/fail_call_invalid.carbon | 8 +- .../impl/fail_impl_bad_assoc_fn.carbon | 38 +++--- .../impl/fail_self_type_mismatch.carbon | 2 +- .../check/testdata/impl/impl_thunk.carbon | 18 +-- .../impl/impl_thunk_min_prelude.carbon | 81 +++++++++++++ .../check/testdata/impl/import_thunk.carbon | 23 ++-- .../testdata/impl/use_assoc_const.carbon | 14 ++- toolchain/check/thunk.cpp | 109 ++++++++++-------- toolchain/check/thunk.h | 9 ++ toolchain/lower/file_context.cpp | 2 +- toolchain/lower/handle_call.cpp | 2 +- toolchain/lower/mangler.cpp | 3 + .../lower/testdata/impl/import_thunk.carbon | 38 +++++- toolchain/lower/testdata/impl/thunk.carbon | 74 +++++++----- toolchain/sem_ir/builtin_function_kind.h | 3 + toolchain/sem_ir/formatter.cpp | 9 +- toolchain/sem_ir/function.h | 42 +++++-- toolchain/sem_ir/inst_namer.cpp | 4 +- 22 files changed, 394 insertions(+), 151 deletions(-) diff --git a/toolchain/check/call.cpp b/toolchain/check/call.cpp index b7ebb81404c6..b41549fe5701 100644 --- a/toolchain/check/call.cpp +++ b/toolchain/check/call.cpp @@ -13,7 +13,10 @@ #include "toolchain/check/deduce.h" #include "toolchain/check/facet_type.h" #include "toolchain/check/function.h" +#include "toolchain/check/import_ref.h" #include "toolchain/check/inst.h" +#include "toolchain/check/name_ref.h" +#include "toolchain/check/thunk.h" #include "toolchain/check/type.h" #include "toolchain/diagnostics/format_providers.h" #include "toolchain/sem_ir/builtin_function_kind.h" @@ -245,17 +248,34 @@ static auto PerformCallToFunction(Context& context, SemIR::LocId loc_id, break; } - // Convert the arguments to match the parameters. - auto converted_args_id = ConvertCallArgs( - context, loc_id, callee_function.self_id, arg_ids, return_slot_arg_id, - context.functions().Get(callee_function.function_id), - *callee_specific_id); - auto call_inst_id = GetOrAddInst(context, loc_id, - {.type_id = return_info.type_id, - .callee_id = callee_id, - .args_id = converted_args_id}); + auto& callee = context.functions().Get(callee_function.function_id); - return call_inst_id; + // Convert the arguments to match the parameters. + auto converted_args_id = + ConvertCallArgs(context, loc_id, callee_function.self_id, arg_ids, + return_slot_arg_id, callee, *callee_specific_id); + + // If we're about to form a direct call to a thunk, inline it. + if (callee.special_function_kind == + SemIR::Function::SpecialFunctionKind::Thunk) { + LoadImportRef(context, callee.thunk_decl_id()); + + // Name the thunk target within the enclosing scope of the thunk. + auto thunk_ref_id = + BuildNameRef(context, loc_id, callee.name_id, callee.thunk_decl_id(), + callee_function.enclosing_specific_id); + + // This recurses back into `PerformCall`. However, we never form a thunk to + // a thunk, so we only recurse once. + return PerformThunkCall(context, loc_id, callee_function.function_id, + context.inst_blocks().Get(converted_args_id), + thunk_ref_id); + } + + return GetOrAddInst(context, loc_id, + {.type_id = return_info.type_id, + .callee_id = callee_id, + .args_id = converted_args_id}); } auto PerformCall(Context& context, SemIR::LocId loc_id, SemIR::InstId callee_id, diff --git a/toolchain/check/eval.cpp b/toolchain/check/eval.cpp index 551f9e920999..3e9bf8171fd6 100644 --- a/toolchain/check/eval.cpp +++ b/toolchain/check/eval.cpp @@ -1717,7 +1717,7 @@ static auto MakeConstantForCall(EvalContext& eval_context, // Calls to builtins might be constant. builtin_kind = eval_context.functions() .Get(callee_function.function_id) - .builtin_function_kind; + .builtin_function_kind(); if (builtin_kind == SemIR::BuiltinFunctionKind::None) { // TODO: Eventually we'll want to treat some kinds of non-builtin // functions as producing constants. diff --git a/toolchain/check/handle_function.cpp b/toolchain/check/handle_function.cpp index 6ce6d47f524d..b667f824f36b 100644 --- a/toolchain/check/handle_function.cpp +++ b/toolchain/check/handle_function.cpp @@ -727,7 +727,7 @@ auto HandleParseNode(Context& context, auto& function = context.functions().Get(function_id); if (IsValidBuiltinDeclaration(context, function, builtin_kind)) { - function.builtin_function_kind = builtin_kind; + function.SetBuiltinFunction(builtin_kind); // Build an empty generic definition if this is a generic builtin. StartGenericDefinition(context, function.generic_id); FinishGenericDefinition(context, function.generic_id); diff --git a/toolchain/check/import_ref.cpp b/toolchain/check/import_ref.cpp index 01518854485a..3afb6fbc67c4 100644 --- a/toolchain/check/import_ref.cpp +++ b/toolchain/check/import_ref.cpp @@ -1841,9 +1841,7 @@ static auto MakeFunctionDecl(ImportContext& context, function_decl.function_id = context.local_functions().Add( {GetIncompleteLocalEntityBase(context, function_decl_id, import_function), {.call_params_id = SemIR::InstBlockId::None, - .return_slot_pattern_id = SemIR::InstId::None, - .special_function_kind = import_function.special_function_kind, - .builtin_function_kind = import_function.builtin_function_kind}}); + .return_slot_pattern_id = SemIR::InstId::None}}); function_decl.type_id = GetFunctionType( context.local_context(), function_decl.function_id, specific_id); @@ -1928,6 +1926,24 @@ static auto TryResolveTypedInst(ImportRefResolver& resolver, new_function.definition_id = new_function.first_owning_decl_id; } + switch (import_function.special_function_kind) { + case SemIR::Function::SpecialFunctionKind::None: { + break; + } + case SemIR::Function::SpecialFunctionKind::Builtin: { + new_function.SetBuiltinFunction(import_function.builtin_function_kind()); + break; + } + case SemIR::Function::SpecialFunctionKind::Thunk: { + auto entity_name_id = resolver.local_entity_names().AddCanonical( + {.name_id = new_function.name_id, + .parent_scope_id = new_function.parent_scope_id}); + new_function.SetThunk(AddImportRef( + resolver, import_function.thunk_decl_id(), entity_name_id)); + break; + } + } + return ResolveResult::Done(function_const_id, new_function.first_decl_id()); } diff --git a/toolchain/check/testdata/impl/fail_call_invalid.carbon b/toolchain/check/testdata/impl/fail_call_invalid.carbon index 9d946b2a697e..5ed5ad133f74 100644 --- a/toolchain/check/testdata/impl/fail_call_invalid.carbon +++ b/toolchain/check/testdata/impl/fail_call_invalid.carbon @@ -148,7 +148,7 @@ fn InstanceCall(n: i32) { // CHECK:STDOUT: // CHECK:STDOUT: fn @G.2(%self.param: ); // CHECK:STDOUT: -// CHECK:STDOUT: fn @G.3(%self.param: %i32) { +// CHECK:STDOUT: fn @G.3(%self.param: %i32) [thunk @impl.006.%G.decl.loc23_27.1] { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %G.ref: %G.type.c9825d.1 = name_ref G, @impl.006.%G.decl.loc23_27.1 [concrete = constants.%G.e73e91.1] // CHECK:STDOUT: %self.ref: %i32 = name_ref self, %self.param @@ -161,10 +161,12 @@ fn InstanceCall(n: i32) { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %n.ref: %i32 = name_ref n, %n // CHECK:STDOUT: %Simple.ref: type = name_ref Simple, file.%Simple.decl [concrete = constants.%Simple.type] -// CHECK:STDOUT: %G.ref: %Simple.assoc_type = name_ref G, @Simple.%assoc0 [concrete = constants.%assoc0.db2] +// CHECK:STDOUT: %G.ref.loc27_12: %Simple.assoc_type = name_ref G, @Simple.%assoc0 [concrete = constants.%assoc0.db2] // CHECK:STDOUT: %impl.elem0: %.8e6 = impl_witness_access constants.%Simple.impl_witness, element0 [concrete = constants.%G.e73e91.2] // CHECK:STDOUT: %bound_method: = bound_method %n.ref, %impl.elem0 -// CHECK:STDOUT: %G.call: init %empty_tuple.type = call %bound_method(%n.ref) +// CHECK:STDOUT: %G.ref.loc27_16: %G.type.c9825d.1 = name_ref G, @impl.006.%G.decl.loc23_27.1 [concrete = constants.%G.e73e91.1] +// CHECK:STDOUT: %G.bound: = bound_method %n.ref, %G.ref.loc27_16 +// CHECK:STDOUT: %G.call: init %empty_tuple.type = call %G.bound() // CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/impl/fail_impl_bad_assoc_fn.carbon b/toolchain/check/testdata/impl/fail_impl_bad_assoc_fn.carbon index df3e6eab2625..3c306454380e 100644 --- a/toolchain/check/testdata/impl/fail_impl_bad_assoc_fn.carbon +++ b/toolchain/check/testdata/impl/fail_impl_bad_assoc_fn.carbon @@ -1199,7 +1199,7 @@ class SelfNestedBadReturnType { // CHECK:STDOUT: // CHECK:STDOUT: fn @F.2(%b.param: bool); // CHECK:STDOUT: -// CHECK:STDOUT: fn @F.3() { +// CHECK:STDOUT: fn @F.3() [thunk @impl.ddd.%F.decl.loc68_18.1] { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %F.ref: %F.type.44ef8c.1 = name_ref F, @impl.ddd.%F.decl.loc68_18.1 [concrete = constants.%F.424e9e.1] // CHECK:STDOUT: return @@ -1207,7 +1207,7 @@ class SelfNestedBadReturnType { // CHECK:STDOUT: // CHECK:STDOUT: fn @F.4(%self.param: %FExtraImplicitParam); // CHECK:STDOUT: -// CHECK:STDOUT: fn @F.5() { +// CHECK:STDOUT: fn @F.5() [thunk @impl.698.%F.decl.loc84_23.1] { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %F.ref: %F.type.e1abdd.1 = name_ref F, @impl.698.%F.decl.loc84_23.1 [concrete = constants.%F.6ff574.1] // CHECK:STDOUT: %F.call: init %empty_tuple.type = call %F.ref() @@ -1222,22 +1222,24 @@ class SelfNestedBadReturnType { // CHECK:STDOUT: // CHECK:STDOUT: fn @F.8(%self.param: bool) -> bool; // CHECK:STDOUT: -// CHECK:STDOUT: fn @F.9(%self.param: bool, %b.param: bool) -> bool { +// CHECK:STDOUT: fn @F.9(%self.param: bool, %b.param: bool) -> bool [thunk @impl.5cf.%F.decl.loc116_31.1] { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %F.ref: %F.type.69596c.1 = name_ref F, @impl.5cf.%F.decl.loc116_31.1 [concrete = constants.%F.738f31.1] // CHECK:STDOUT: %self.ref: bool = name_ref self, %self.param -// CHECK:STDOUT: %F.bound: = bound_method %self.ref, %F.ref // CHECK:STDOUT: %b.ref: bool = name_ref b, %b.param +// CHECK:STDOUT: %return.ref: ref bool = name_ref , %return.param +// CHECK:STDOUT: %F.bound: = bound_method %self.ref, %F.ref // CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: fn @F.10(%b.param: bool) -> bool; // CHECK:STDOUT: -// CHECK:STDOUT: fn @F.11(%self.param: bool, %b.param: bool) -> bool { +// CHECK:STDOUT: fn @F.11(%self.param: bool, %b.param: bool) -> bool [thunk @impl.bac.%F.decl.loc129_26.1] { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %F.ref: %F.type.d97cef.1 = name_ref F, @impl.bac.%F.decl.loc129_26.1 [concrete = constants.%F.01de92.1] // CHECK:STDOUT: %self.ref: bool = name_ref self, %self.param // CHECK:STDOUT: %b.ref: bool = name_ref b, %b.param +// CHECK:STDOUT: %return.ref: ref bool = name_ref , %return.param // CHECK:STDOUT: %F.call: init bool = call %F.ref(%b.ref) // CHECK:STDOUT: %.loc129_26.1: bool = value_of_initializer %F.call // CHECK:STDOUT: %.loc129_26.2: bool = converted %F.call, %.loc129_26.1 @@ -1246,12 +1248,13 @@ class SelfNestedBadReturnType { // CHECK:STDOUT: // CHECK:STDOUT: fn @F.12(%self.param: bool, %b.param: bool); // CHECK:STDOUT: -// CHECK:STDOUT: fn @F.13(%self.param: bool, %b.param: bool) -> bool { +// CHECK:STDOUT: fn @F.13(%self.param: bool, %b.param: bool) -> bool [thunk @impl.1a7.%F.decl.loc151_30.1] { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %F.ref: %F.type.123d7a.1 = name_ref F, @impl.1a7.%F.decl.loc151_30.1 [concrete = constants.%F.c7d02d.1] // CHECK:STDOUT: %self.ref: bool = name_ref self, %self.param -// CHECK:STDOUT: %F.bound: = bound_method %self.ref, %F.ref // CHECK:STDOUT: %b.ref: bool = name_ref b, %b.param +// CHECK:STDOUT: %return.ref: ref bool = name_ref , %return.param +// CHECK:STDOUT: %F.bound: = bound_method %self.ref, %F.ref // CHECK:STDOUT: %F.call: init %empty_tuple.type = call %F.bound(%self.ref, %b.ref) // CHECK:STDOUT: %.loc151: bool = converted %F.call, [concrete = ] // CHECK:STDOUT: return @@ -1259,12 +1262,13 @@ class SelfNestedBadReturnType { // CHECK:STDOUT: // CHECK:STDOUT: fn @F.14(%self.param: bool, %b.param: %FDifferentParamType) -> bool; // CHECK:STDOUT: -// CHECK:STDOUT: fn @F.15(%self.param: bool, %b.param: bool) -> bool { +// CHECK:STDOUT: fn @F.15(%self.param: bool, %b.param: bool) -> bool [thunk @impl.f2b.%F.decl.loc170_38.1] { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %F.ref: %F.type.6b537d.1 = name_ref F, @impl.f2b.%F.decl.loc170_38.1 [concrete = constants.%F.04313a.1] // CHECK:STDOUT: %self.ref: bool = name_ref self, %self.param -// CHECK:STDOUT: %F.bound: = bound_method %self.ref, %F.ref // CHECK:STDOUT: %b.ref: bool = name_ref b, %b.param +// CHECK:STDOUT: %return.ref: ref bool = name_ref , %return.param +// CHECK:STDOUT: %F.bound: = bound_method %self.ref, %F.ref // CHECK:STDOUT: %.loc102: %FDifferentParamType = converted %b.ref, [concrete = ] // CHECK:STDOUT: %F.call: init bool = call %F.bound(%self.ref, ) // CHECK:STDOUT: %.loc170_38.1: bool = value_of_initializer %F.call @@ -1274,12 +1278,13 @@ class SelfNestedBadReturnType { // CHECK:STDOUT: // CHECK:STDOUT: fn @F.16(%self.param: %FDifferentImplicitParamType, %b.param: bool) -> bool; // CHECK:STDOUT: -// CHECK:STDOUT: fn @F.17(%self.param: bool, %b.param: bool) -> bool { +// CHECK:STDOUT: fn @F.17(%self.param: bool, %b.param: bool) -> bool [thunk @impl.db4.%F.decl.loc183_38.1] { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %F.ref: %F.type.d6232a.1 = name_ref F, @impl.db4.%F.decl.loc183_38.1 [concrete = constants.%F.886f70.1] // CHECK:STDOUT: %self.ref: bool = name_ref self, %self.param -// CHECK:STDOUT: %F.bound: = bound_method %self.ref, %F.ref // CHECK:STDOUT: %b.ref: bool = name_ref b, %b.param +// CHECK:STDOUT: %return.ref: ref bool = name_ref , %return.param +// CHECK:STDOUT: %F.bound: = bound_method %self.ref, %F.ref // CHECK:STDOUT: %.loc102: %FDifferentImplicitParamType = converted %self.ref, [concrete = ] // CHECK:STDOUT: %F.call: init bool = call %F.bound(, %b.ref) // CHECK:STDOUT: %.loc183_38.1: bool = value_of_initializer %F.call @@ -1289,12 +1294,13 @@ class SelfNestedBadReturnType { // CHECK:STDOUT: // CHECK:STDOUT: fn @F.18(%self.param: bool, %b.param: bool) -> %return.param: %FDifferentReturnType; // CHECK:STDOUT: -// CHECK:STDOUT: fn @F.19(%self.param: bool, %b.param: bool) -> bool { +// CHECK:STDOUT: fn @F.19(%self.param: bool, %b.param: bool) -> bool [thunk @impl.fcc.%F.decl.loc199_38.1] { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %F.ref: %F.type.d3b58f.1 = name_ref F, @impl.fcc.%F.decl.loc199_38.1 [concrete = constants.%F.be86c9.1] // CHECK:STDOUT: %self.ref: bool = name_ref self, %self.param -// CHECK:STDOUT: %F.bound: = bound_method %self.ref, %F.ref // CHECK:STDOUT: %b.ref: bool = name_ref b, %b.param +// CHECK:STDOUT: %return.ref: ref bool = name_ref , %return.param +// CHECK:STDOUT: %F.bound: = bound_method %self.ref, %F.ref // CHECK:STDOUT: %.loc199_38.1: ref %FDifferentReturnType = temporary_storage // CHECK:STDOUT: %F.call: init %FDifferentReturnType = call %F.bound(%self.ref, %b.ref) to %.loc199_38.1 // CHECK:STDOUT: %.loc199_38.2: bool = converted %F.call, [concrete = ] @@ -1316,10 +1322,11 @@ class SelfNestedBadReturnType { // CHECK:STDOUT: // CHECK:STDOUT: fn @F.21(%x.param: %tuple.type.a7d) -> %return.param: %array_type.a41; // CHECK:STDOUT: -// CHECK:STDOUT: fn @F.22(%x.param: %tuple.type.9c9) -> %return.param: %array_type.a41 { +// CHECK:STDOUT: fn @F.22(%x.param: %tuple.type.9c9) -> %return.param: %array_type.a41 [thunk @impl.6a5.%F.decl.loc222_87.1] { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %F.ref: %F.type.f90165.1 = name_ref F, @impl.6a5.%F.decl.loc222_87.1 [concrete = constants.%F.fa8d04.1] // CHECK:STDOUT: %x.ref: %tuple.type.9c9 = name_ref x, %x.param +// CHECK:STDOUT: %return.ref: ref %array_type.a41 = name_ref , %return.param // CHECK:STDOUT: %.loc210_41: ref %array_type.a41 = splice_block %return {} // CHECK:STDOUT: %tuple.elem0: %ptr.4cd = tuple_access %x.ref, element0 // CHECK:STDOUT: %tuple.elem1: %struct_type.x.y.a89 = tuple_access %x.ref, element1 @@ -1331,10 +1338,11 @@ class SelfNestedBadReturnType { // CHECK:STDOUT: // CHECK:STDOUT: fn @F.23(%x.param: %tuple.type.eb9) -> %return.param: %array_type.a41; // CHECK:STDOUT: -// CHECK:STDOUT: fn @F.24(%x.param: %tuple.type.eb9) -> %return.param: %array_type.126 { +// CHECK:STDOUT: fn @F.24(%x.param: %tuple.type.eb9) -> %return.param: %array_type.126 [thunk @impl.bfc.%F.decl.loc238_112.1] { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %F.ref: %F.type.0e7d1d.1 = name_ref F, @impl.bfc.%F.decl.loc238_112.1 [concrete = constants.%F.0bc78a.1] // CHECK:STDOUT: %x.ref: %tuple.type.eb9 = name_ref x, %x.param +// CHECK:STDOUT: %return.ref: ref %array_type.126 = name_ref , %return.param // CHECK:STDOUT: %.loc238_112.1: ref %array_type.a41 = temporary_storage // CHECK:STDOUT: %F.call: init %array_type.a41 = call %F.ref(%x.ref) to %.loc238_112.1 // CHECK:STDOUT: %.loc238_112.2: %array_type.126 = converted %F.call, [concrete = ] diff --git a/toolchain/check/testdata/impl/fail_self_type_mismatch.carbon b/toolchain/check/testdata/impl/fail_self_type_mismatch.carbon index d6408b09ae42..499f4c39f1c7 100644 --- a/toolchain/check/testdata/impl/fail_self_type_mismatch.carbon +++ b/toolchain/check/testdata/impl/fail_self_type_mismatch.carbon @@ -240,7 +240,7 @@ impl i32 as I { // CHECK:STDOUT: // CHECK:STDOUT: fn @F.3(%c.param: %C.6fb); // CHECK:STDOUT: -// CHECK:STDOUT: fn @F.4(%c.param: %C.d88) { +// CHECK:STDOUT: fn @F.4(%c.param: %C.d88) [thunk @impl.a9a.%F.decl.loc42_18.1] { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %F.ref: %F.type.066a53.1 = name_ref F, @impl.a9a.%F.decl.loc42_18.1 [concrete = constants.%F.9ec58f.1] // CHECK:STDOUT: %c.ref: %C.d88 = name_ref c, %c.param diff --git a/toolchain/check/testdata/impl/impl_thunk.carbon b/toolchain/check/testdata/impl/impl_thunk.carbon index 35cb833aadfb..fe5979ba5d25 100644 --- a/toolchain/check/testdata/impl/impl_thunk.carbon +++ b/toolchain/check/testdata/impl/impl_thunk.carbon @@ -359,7 +359,7 @@ impl () as I({}) { // CHECK:STDOUT: // CHECK:STDOUT: fn @F.2(%y.param: %struct_type.b.a.40c) -> %return.param: %struct_type.d.c.b36; // CHECK:STDOUT: -// CHECK:STDOUT: fn @F.3(%x.param: %struct_type.a.b.391) -> %return.param: %struct_type.c.d.15a { +// CHECK:STDOUT: fn @F.3(%x.param: %struct_type.a.b.391) -> %return.param: %struct_type.c.d.15a [thunk @impl.%F.decl.loc10_48.1] { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %F.ref: %F.type.39e918.1 = name_ref F, @impl.%F.decl.loc10_48.1 [concrete = constants.%F.c04b92.1] // CHECK:STDOUT: @@ -440,7 +440,7 @@ impl () as I({}) { // CHECK:STDOUT: // CHECK:STDOUT: fn @F.2(%self.param: %ptr.6db, %other.param: %ptr.6db) -> %ptr.019; // CHECK:STDOUT: -// CHECK:STDOUT: fn @F.3(%self.param: %ptr.e79, %other.param: %ptr.e79) -> %ptr.e79 { +// CHECK:STDOUT: fn @F.3(%self.param: %ptr.e79, %other.param: %ptr.e79) -> %ptr.e79 [thunk @impl.%F.decl.loc14_39.1] { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %F.ref: %F.type.f1b0b1.1 = name_ref F, @impl.%F.decl.loc14_39.1 [concrete = constants.%F.5161e9.1] // CHECK:STDOUT: @@ -497,7 +497,7 @@ impl () as I({}) { // CHECK:STDOUT: // CHECK:STDOUT: fn @F.2(%self.param: %A, %other.param: %A); // CHECK:STDOUT: -// CHECK:STDOUT: fn @F.3(%self.param: %B, %other.param: %B) { +// CHECK:STDOUT: fn @F.3(%self.param: %B, %other.param: %B) [thunk @impl.%F.decl.loc13_26.1] { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %F.ref: %F.type.f1b0b1.1 = name_ref F, @impl.%F.decl.loc13_26.1 [concrete = constants.%F.5161e9.1] // CHECK:STDOUT: @@ -567,7 +567,7 @@ impl () as I({}) { // CHECK:STDOUT: // CHECK:STDOUT: fn @F.2(%self.param: %ptr.6db, %other.param: %ptr.6db) -> %ptr.019; // CHECK:STDOUT: -// CHECK:STDOUT: fn @F.3(%self.param: %ptr.e79, %other.param: %ptr.e79) -> %ptr.e79 { +// CHECK:STDOUT: fn @F.3(%self.param: %ptr.e79, %other.param: %ptr.e79) -> %ptr.e79 [thunk @impl.%F.decl.loc14_39.1] { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %F.ref: %F.type.f1b0b1.1 = name_ref F, @impl.%F.decl.loc14_39.1 [concrete = constants.%F.5161e9.1] // CHECK:STDOUT: @@ -618,9 +618,10 @@ impl () as I({}) { // CHECK:STDOUT: // CHECK:STDOUT: fn @F.2() -> %return.param: %B; // CHECK:STDOUT: -// CHECK:STDOUT: fn @F.3() -> %return.param: %A { +// CHECK:STDOUT: fn @F.3() -> %return.param: %A [thunk @impl.%F.decl.loc20_14.1] { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %F.ref: %F.type.b24d6f.1 = name_ref F, @impl.%F.decl.loc20_14.1 [concrete = constants.%F.77e9d5.1] +// CHECK:STDOUT: // CHECK:STDOUT: %.loc20_14.1: ref %B = temporary_storage // CHECK:STDOUT: %F.call: init %B = call %F.ref() to %.loc20_14.1 // CHECK:STDOUT: %.loc20_14.2: ref %B = temporary %.loc20_14.1, %F.call @@ -696,7 +697,7 @@ impl () as I({}) { // CHECK:STDOUT: %F.specific_fn.loc10_29.2: = specific_function constants.%F.c04b92.1, @F.2(%ptr) [symbolic = %F.specific_fn.loc10_29.2 (constants.%F.specific_fn)] // CHECK:STDOUT: // CHECK:STDOUT: -// CHECK:STDOUT: fn(%x.param: @F.3.%ptr (%ptr.79f)) -> @F.3.%ptr (%ptr.79f) { +// CHECK:STDOUT: fn(%x.param: @F.3.%ptr (%ptr.79f)) -> @F.3.%ptr (%ptr.79f) [thunk @impl.%F.decl.loc10_29.1] { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %F.ref: %F.type.39e918.1 = name_ref F, @impl.%F.decl.loc10_29.1 [concrete = constants.%F.c04b92.1] // CHECK:STDOUT: @@ -799,7 +800,7 @@ impl () as I({}) { // CHECK:STDOUT: // CHECK:STDOUT: !definition: // CHECK:STDOUT: -// CHECK:STDOUT: fn(%self.param: %empty_tuple.type, %x.param: @F.3.%ptr (%ptr.79f)) -> @F.3.%ptr (%ptr.79f) { +// CHECK:STDOUT: fn(%self.param: %empty_tuple.type, %x.param: @F.3.%ptr (%ptr.79f)) -> @F.3.%ptr (%ptr.79f) [thunk @impl.%F.decl.loc19_39.1] { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %F.ref: %F.type.39e918.1 = name_ref F, @impl.%F.decl.loc19_39.1 [concrete = constants.%F.c04b92.1] // CHECK:STDOUT: @@ -859,9 +860,10 @@ impl () as I({}) { // CHECK:STDOUT: // CHECK:STDOUT: fn @F.2() -> %return.param: %struct_type.b.a.1b0; // CHECK:STDOUT: -// CHECK:STDOUT: fn @F.3() -> %return.param: %struct_type.a.b.f95 { +// CHECK:STDOUT: fn @F.3() -> %return.param: %struct_type.a.b.f95 [thunk @impl.%F.decl.loc10_29.1] { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %F.ref: %F.type.29ab63.1 = name_ref F, @impl.%F.decl.loc10_29.1 [concrete = constants.%F.975709.1] +// CHECK:STDOUT: // CHECK:STDOUT: %.loc10_29.1: ref %struct_type.b.a.1b0 = temporary_storage // CHECK:STDOUT: %F.call: init %struct_type.b.a.1b0 = call %F.ref() to %.loc10_29.1 // CHECK:STDOUT: %.loc10_29.2: ref %struct_type.b.a.1b0 = temporary %.loc10_29.1, %F.call diff --git a/toolchain/check/testdata/impl/impl_thunk_min_prelude.carbon b/toolchain/check/testdata/impl/impl_thunk_min_prelude.carbon index 6853f299af6d..8986a04552a4 100644 --- a/toolchain/check/testdata/impl/impl_thunk_min_prelude.carbon +++ b/toolchain/check/testdata/impl/impl_thunk_min_prelude.carbon @@ -78,3 +78,84 @@ interface OpWith(U:! type) { impl forall [T:! type, U:! Core.ImplicitAs(Wrap(T))] Wrap(T) as OpWith(U) { fn Op[self: Self](other: Self) = "no_op"; } + +// --- thunk_literal_convert.carbon + +library "[[@TEST_NAME]]"; + +fn IntLiteral() -> type = "int_literal.make_type"; +fn Int(size: IntLiteral()) -> type = "int.make_type_signed"; + +impl IntLiteral() as Core.ImplicitAs(Int(32)) { + fn Convert[self: Self]() -> Int(32) = "int.convert_checked"; +} + +interface Add(T:! type) { + fn Op(a: Self, b: T) -> Self; +} +impl forall [T:! Core.ImplicitAs(Int(32))] Int(32) as Add(T) { + fn Op(a: Self, b: Self) -> Self = "int.sadd"; +} + +fn Call() -> Int(32) { + let a: Int(32) = 1; + // The conversion from 2 to IntLiteral here relies on having the + // constant value available, so is only possible if the thunk is + // inlined. + return Int(32).(Add(IntLiteral()).Op) + //@dump-sem-ir-begin + (a, 2) + //@dump-sem-ir-end + ; +} + +// CHECK:STDOUT: --- thunk_literal_convert.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %int_32: Core.IntLiteral = int_value 32 [concrete] +// CHECK:STDOUT: %i32.builtin: type = int_type signed, %int_32 [concrete] +// CHECK:STDOUT: %ImplicitAs.type.61e: type = facet_type <@ImplicitAs, @ImplicitAs(%i32.builtin)> [concrete] +// CHECK:STDOUT: %Convert.type.059: type = fn_type @Convert.1, @ImplicitAs(%i32.builtin) [concrete] +// CHECK:STDOUT: %ImplicitAs.impl_witness: = impl_witness file.%ImplicitAs.impl_witness_table [concrete] +// CHECK:STDOUT: %Convert.type.1dd: type = fn_type @Convert.2 [concrete] +// CHECK:STDOUT: %Convert.52c: %Convert.type.1dd = struct_value () [concrete] +// CHECK:STDOUT: %ImplicitAs.facet.54f: %ImplicitAs.type.61e = facet_value Core.IntLiteral, (%ImplicitAs.impl_witness) [concrete] +// CHECK:STDOUT: %.f22: type = fn_type_with_self_type %Convert.type.059, %ImplicitAs.facet.54f [concrete] +// CHECK:STDOUT: %Op.type.049d3a.1: type = fn_type @Op.2, @impl.797(%ImplicitAs.facet.54f) [concrete] +// CHECK:STDOUT: %Op.1fb3b5.1: %Op.type.049d3a.1 = struct_value () [concrete] +// CHECK:STDOUT: %int_2.ecc: Core.IntLiteral = int_value 2 [concrete] +// CHECK:STDOUT: %Convert.bound.713: = bound_method %int_2.ecc, %Convert.52c [concrete] +// CHECK:STDOUT: %int_2.5a1: %i32.builtin = int_value 2 [concrete] +// CHECK:STDOUT: %Op.specific_fn.01379b.2: = specific_function %Op.1fb3b5.1, @Op.2(%ImplicitAs.facet.54f) [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: imports { +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Call() -> %i32.builtin { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: +// CHECK:STDOUT: %a.ref: %i32.builtin = name_ref a, %a +// CHECK:STDOUT: %int_2: Core.IntLiteral = int_value 2 [concrete = constants.%int_2.ecc] +// CHECK:STDOUT: %ImplicitAs.facet.loc25_14.1: %ImplicitAs.type.61e = facet_value Core.IntLiteral, (constants.%ImplicitAs.impl_witness) [concrete = constants.%ImplicitAs.facet.54f] +// CHECK:STDOUT: %.loc25_14.1: %ImplicitAs.type.61e = converted Core.IntLiteral, %ImplicitAs.facet.loc25_14.1 [concrete = constants.%ImplicitAs.facet.54f] +// CHECK:STDOUT: %ImplicitAs.facet.loc25_14.2: %ImplicitAs.type.61e = facet_value Core.IntLiteral, (constants.%ImplicitAs.impl_witness) [concrete = constants.%ImplicitAs.facet.54f] +// CHECK:STDOUT: %.loc25_14.2: %ImplicitAs.type.61e = converted Core.IntLiteral, %ImplicitAs.facet.loc25_14.2 [concrete = constants.%ImplicitAs.facet.54f] +// CHECK:STDOUT: +// CHECK:STDOUT: %.loc25_14.3: %Op.type.049d3a.1 = specific_constant @impl.797.%Op.decl.loc15_35.1, @impl.797(constants.%ImplicitAs.facet.54f) [concrete = constants.%Op.1fb3b5.1] +// CHECK:STDOUT: %Op.ref.loc25: %Op.type.049d3a.1 = name_ref Op, %.loc25_14.3 [concrete = constants.%Op.1fb3b5.1] +// CHECK:STDOUT: %impl.elem0.loc25_14: %.f22 = impl_witness_access constants.%ImplicitAs.impl_witness, element0 [concrete = constants.%Convert.52c] +// CHECK:STDOUT: %bound_method.loc25_14: = bound_method %int_2, %impl.elem0.loc25_14 [concrete = constants.%Convert.bound.713] +// CHECK:STDOUT: %int.convert_checked.loc25_14: init %i32.builtin = call %bound_method.loc25_14(%int_2) [concrete = constants.%int_2.5a1] +// CHECK:STDOUT: %.loc25_14.4: %i32.builtin = value_of_initializer %int.convert_checked.loc25_14 [concrete = constants.%int_2.5a1] +// CHECK:STDOUT: %.loc25_14.5: %i32.builtin = converted %int_2, %.loc25_14.4 [concrete = constants.%int_2.5a1] +// CHECK:STDOUT: %Op.specific_fn: = specific_function %Op.ref.loc25, @Op.2(constants.%ImplicitAs.facet.54f) [concrete = constants.%Op.specific_fn.01379b.2] +// CHECK:STDOUT: %impl.elem0.loc25_13: %.f22 = impl_witness_access constants.%ImplicitAs.impl_witness, element0 [concrete = constants.%Convert.52c] +// CHECK:STDOUT: %bound_method.loc25_13: = bound_method %int_2, %impl.elem0.loc25_13 [concrete = constants.%Convert.bound.713] +// CHECK:STDOUT: %int.convert_checked.loc25_13: init %i32.builtin = call %bound_method.loc25_13(%int_2) [concrete = constants.%int_2.5a1] +// CHECK:STDOUT: %.loc25_13.1: %i32.builtin = value_of_initializer %int.convert_checked.loc25_13 [concrete = constants.%int_2.5a1] +// CHECK:STDOUT: %.loc25_13.2: %i32.builtin = converted %int_2, %.loc25_13.1 [concrete = constants.%int_2.5a1] +// CHECK:STDOUT: %int.sadd: init %i32.builtin = call %Op.specific_fn(%a.ref, %.loc25_13.2) +// CHECK:STDOUT: +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/check/testdata/impl/import_thunk.carbon b/toolchain/check/testdata/impl/import_thunk.carbon index 8d21fd05ae54..719a692bae97 100644 --- a/toolchain/check/testdata/impl/import_thunk.carbon +++ b/toolchain/check/testdata/impl/import_thunk.carbon @@ -247,7 +247,7 @@ fn G() { // CHECK:STDOUT: %require_complete: = require_complete_type %C [symbolic = %require_complete (constants.%require_complete)] // CHECK:STDOUT: %C.val: @F.3.%C (%C.13320f.2) = struct_value () [symbolic = %C.val (constants.%C.val)] // CHECK:STDOUT: -// CHECK:STDOUT: fn(%x.param: %empty_struct_type) { +// CHECK:STDOUT: fn(%x.param: %empty_struct_type) [thunk @impl.%F.decl.loc8_17.1] { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %.loc8: @F.3.%F.type (%F.type.0daaa1.1) = specific_constant @impl.%F.decl.loc8_17.1, @impl(constants.%Y) [symbolic = %F (constants.%F.49c1ac.1)] // CHECK:STDOUT: %F.ref: @F.3.%F.type (%F.type.0daaa1.1) = name_ref F, %.loc8 [symbolic = %F (constants.%F.49c1ac.1)] @@ -352,7 +352,7 @@ fn G() { // CHECK:STDOUT: %Main.import_ref.572 = import_ref Main//b, inst29 [no loc], unloaded // CHECK:STDOUT: %Main.import_ref.e5d = import_ref Main//a, inst17 [no loc], unloaded // CHECK:STDOUT: %Main.import_ref.c44: %I.assoc_type = import_ref Main//a, loc5_14, loaded [concrete = constants.%assoc0] -// CHECK:STDOUT: %Main.F = import_ref Main//a, F, unloaded +// CHECK:STDOUT: %Main.F.8b9 = import_ref Main//a, F, unloaded // CHECK:STDOUT: %Main.import_ref.e03: %F.type.cf0 = import_ref Main//a, loc5_14, loaded [concrete = constants.%F.bc6] // CHECK:STDOUT: %Main.import_ref.5dd: %I.type = import_ref Main//a, inst17 [no loc], loaded [symbolic = constants.%Self] // CHECK:STDOUT: %Main.import_ref.f89: = import_ref Main//b, loc7_32, loaded [symbolic = @impl.%I.impl_witness (constants.%I.impl_witness.7d9)] @@ -363,6 +363,7 @@ fn G() { // CHECK:STDOUT: %I.impl_witness_table = impl_witness_table (%Main.import_ref.047), @impl [concrete] // CHECK:STDOUT: %Main.import_ref.eb1c17.3: %empty_tuple.type = import_ref Main//b, loc7_14, loaded [symbolic = @impl.%Y (constants.%Y)] // CHECK:STDOUT: %Main.import_ref.eb1c17.4: %empty_tuple.type = import_ref Main//b, loc7_14, loaded [symbolic = @impl.%Y (constants.%Y)] +// CHECK:STDOUT: %Main.F.5a8: @impl.%F.type.1 (%F.type.0daaa1.1) = import_ref Main//b, F, loaded [symbolic = @impl.%F.1 (constants.%F.49c1ac.1)] // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: file { @@ -379,7 +380,7 @@ fn G() { // CHECK:STDOUT: !members: // CHECK:STDOUT: .Self = imports.%Main.import_ref.e5d // CHECK:STDOUT: .F = imports.%Main.import_ref.c44 -// CHECK:STDOUT: witness = (imports.%Main.F) +// CHECK:STDOUT: witness = (imports.%Main.F.8b9) // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: generic impl @impl(imports.%Main.import_ref.eb1c17.2: %empty_tuple.type) [from "b.carbon"] { @@ -420,17 +421,25 @@ fn G() { // CHECK:STDOUT: %.loc7_7: %empty_tuple.type = converted %.loc7_6, %empty_tuple [concrete = constants.%empty_tuple] // CHECK:STDOUT: %C: type = class_type @C, @C(constants.%empty_tuple) [concrete = constants.%C.607] // CHECK:STDOUT: %I.ref: type = name_ref I, imports.%Main.I [concrete = constants.%I.type] -// CHECK:STDOUT: %F.ref: %I.assoc_type = name_ref F, imports.%Main.import_ref.c44 [concrete = constants.%assoc0] +// CHECK:STDOUT: %F.ref.loc7_11: %I.assoc_type = name_ref F, imports.%Main.import_ref.c44 [concrete = constants.%assoc0] // CHECK:STDOUT: %I.facet: %I.type = facet_value constants.%C.607, (constants.%I.impl_witness.02b) [concrete = constants.%I.facet] // CHECK:STDOUT: %.loc7_8: %I.type = converted %C, %I.facet [concrete = constants.%I.facet] // CHECK:STDOUT: %impl.elem0: %.885 = impl_witness_access constants.%I.impl_witness.02b, element0 [concrete = constants.%F.5fa954.2] // CHECK:STDOUT: %.loc7_16.1: %empty_struct_type = struct_literal () // CHECK:STDOUT: %empty_struct.loc7_16.1: %empty_struct_type = struct_value () [concrete = constants.%empty_struct] -// CHECK:STDOUT: %.loc7_17: %empty_struct_type = converted %.loc7_16.1, %empty_struct.loc7_16.1 [concrete = constants.%empty_struct] +// CHECK:STDOUT: %.loc7_17.1: %empty_struct_type = converted %.loc7_16.1, %empty_struct.loc7_16.1 [concrete = constants.%empty_struct] // CHECK:STDOUT: %specific_fn: = specific_function %impl.elem0, @F.3(constants.%empty_tuple) [concrete = constants.%F.specific_fn.4832e8.1] // CHECK:STDOUT: %empty_struct.loc7_16.2: %empty_struct_type = struct_value () [concrete = constants.%empty_struct] // CHECK:STDOUT: %.loc7_16.2: %empty_struct_type = converted %.loc7_16.1, %empty_struct.loc7_16.2 [concrete = constants.%empty_struct] -// CHECK:STDOUT: %F.call: init %empty_tuple.type = call %specific_fn(%.loc7_16.2) +// CHECK:STDOUT: %.loc7_17.2: %F.type.af4856.1 = specific_constant imports.%Main.F.5a8, @impl(constants.%empty_tuple) [concrete = constants.%F.5fa954.1] +// CHECK:STDOUT: %F.ref.loc7_17: %F.type.af4856.1 = name_ref F, %.loc7_17.2 [concrete = constants.%F.5fa954.1] +// CHECK:STDOUT: %F.specific_fn: = specific_function %F.ref.loc7_17, @F.2(constants.%empty_tuple) [concrete = constants.%F.specific_fn.4832e8.2] +// CHECK:STDOUT: %.loc7_16.3: ref %C.607 = temporary_storage +// CHECK:STDOUT: %.loc7_16.4: init %C.607 = class_init (), %.loc7_16.3 [concrete = constants.%C.val.12f] +// CHECK:STDOUT: %.loc7_16.5: ref %C.607 = temporary %.loc7_16.3, %.loc7_16.4 +// CHECK:STDOUT: %.loc7_16.6: ref %C.607 = converted %.loc7_16.2, %.loc7_16.5 +// CHECK:STDOUT: %.loc7_16.7: %C.607 = bind_value %.loc7_16.6 +// CHECK:STDOUT: %F.call: init %empty_tuple.type = call %F.specific_fn(%.loc7_16.7) // CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: @@ -459,7 +468,7 @@ fn G() { // CHECK:STDOUT: %require_complete: = require_complete_type %C [symbolic = %require_complete (constants.%require_complete)] // CHECK:STDOUT: %C.val: @F.3.%C (%C.13320f.2) = struct_value () [symbolic = %C.val (constants.%C.val.56a)] // CHECK:STDOUT: -// CHECK:STDOUT: fn; +// CHECK:STDOUT: fn [thunk imports.%Main.F.5a8]; // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: specific @C(constants.%X) { diff --git a/toolchain/check/testdata/impl/use_assoc_const.carbon b/toolchain/check/testdata/impl/use_assoc_const.carbon index 7d1f18b6278c..62962cced6c5 100644 --- a/toolchain/check/testdata/impl/use_assoc_const.carbon +++ b/toolchain/check/testdata/impl/use_assoc_const.carbon @@ -2420,10 +2420,11 @@ fn F() { // CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: -// CHECK:STDOUT: fn @F.3(%u.param: %i32) -> %i32 { +// CHECK:STDOUT: fn @F.3(%u.param: %i32) -> %i32 [thunk @impl.%F.decl.loc24_21.1] { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %F.ref: %F.type.b842fd.1 = name_ref F, @impl.%F.decl.loc24_21.1 [concrete = constants.%F.b07d12.1] // CHECK:STDOUT: %u.ref: %i32 = name_ref u, %u.param +// CHECK:STDOUT: %return.ref: ref %i32 = name_ref , %return.param // CHECK:STDOUT: %F.call: init = call %F.ref() // CHECK:STDOUT: return // CHECK:STDOUT: } @@ -2688,11 +2689,12 @@ fn F() { // CHECK:STDOUT: return %z.ref // CHECK:STDOUT: } // CHECK:STDOUT: -// CHECK:STDOUT: fn @F.3(%self.param: %empty_tuple.type, %.param: ) -> { +// CHECK:STDOUT: fn @F.3(%self.param: %empty_tuple.type, %.param: ) -> [thunk @impl.4e9.%F.decl.loc30_33.1] { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %F.ref: %F.type.f405c5.1 = name_ref F, @impl.4e9.%F.decl.loc30_33.1 [concrete = constants.%F.214a71.1] // CHECK:STDOUT: %self.ref: %empty_tuple.type = name_ref self, %self.param // CHECK:STDOUT: %.ref: = name_ref , %.param [concrete = ] +// CHECK:STDOUT: %return.ref: ref = name_ref , %return.param [concrete = ] // CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: @@ -2702,12 +2704,13 @@ fn F() { // CHECK:STDOUT: return %self.ref // CHECK:STDOUT: } // CHECK:STDOUT: -// CHECK:STDOUT: fn @F.5(%self.param: %C2, %.param: ) -> { +// CHECK:STDOUT: fn @F.5(%self.param: %C2, %.param: ) -> [thunk @impl.8b3.%F.decl.loc39_33.1] { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %F.ref: %F.type.05d6a0.1 = name_ref F, @impl.8b3.%F.decl.loc39_33.1 [concrete = constants.%F.bfa759.1] // CHECK:STDOUT: %self.ref: %C2 = name_ref self, %self.param -// CHECK:STDOUT: %F.bound: = bound_method %self.ref, %F.ref // CHECK:STDOUT: %.ref: = name_ref , %.param [concrete = ] +// CHECK:STDOUT: %return.ref: ref = name_ref , %return.param [concrete = ] +// CHECK:STDOUT: %F.bound: = bound_method %self.ref, %F.ref // CHECK:STDOUT: %F.call: init %C2 = call %F.bound(%self.ref, ) // CHECK:STDOUT: return // CHECK:STDOUT: } @@ -2913,11 +2916,12 @@ fn F() { // CHECK:STDOUT: return %v.ref // CHECK:STDOUT: } // CHECK:STDOUT: -// CHECK:STDOUT: fn @F.3(%self.param: %empty_tuple.type, %v.param: %struct_type.a) -> %struct_type.a { +// CHECK:STDOUT: fn @F.3(%self.param: %empty_tuple.type, %v.param: %struct_type.a) -> %struct_type.a [thunk @impl.%F.decl.loc16_45.1] { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %F.ref: %F.type.c5249c.1 = name_ref F, @impl.%F.decl.loc16_45.1 [concrete = constants.%F.5eb4dd.1] // CHECK:STDOUT: %self.ref: %empty_tuple.type = name_ref self, %self.param // CHECK:STDOUT: %v.ref: %struct_type.a = name_ref v, %v.param +// CHECK:STDOUT: %return.ref: ref %struct_type.a = name_ref , %return.param // CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/thunk.cpp b/toolchain/check/thunk.cpp index bb45d925acef..1ea3a3d508d7 100644 --- a/toolchain/check/thunk.cpp +++ b/toolchain/check/thunk.cpp @@ -191,26 +191,25 @@ static auto CloneFunctionDecl(Context& context, SemIR::LocId loc_id, // Create the `Function` object. auto& callee = context.functions().Get(callee_id); - function_decl.function_id = context.functions().Add(SemIR::Function{ - {.name_id = signature.name_id, - .parent_scope_id = callee.parent_scope_id, - .generic_id = generic_id, - .first_param_node_id = signature.first_param_node_id, - .last_param_node_id = signature.last_param_node_id, - .pattern_block_id = pattern_block_id, - .implicit_param_patterns_id = implicit_param_patterns_id, - .param_patterns_id = param_patterns_id, - .is_extern = false, - .extern_library_id = SemIR::LibraryNameId::None, - .non_owning_decl_id = SemIR::InstId::None, - .first_owning_decl_id = decl_id, - .definition_id = decl_id}, - {.call_params_id = call_params_id, - .return_slot_pattern_id = return_slot_pattern_id, - .special_function_kind = SemIR::Function::SpecialFunctionKind::Thunk, - .virtual_modifier = callee.virtual_modifier, - .virtual_index = callee.virtual_index, - .self_param_id = self_param_id}}); + function_decl.function_id = context.functions().Add( + SemIR::Function{{.name_id = signature.name_id, + .parent_scope_id = callee.parent_scope_id, + .generic_id = generic_id, + .first_param_node_id = signature.first_param_node_id, + .last_param_node_id = signature.last_param_node_id, + .pattern_block_id = pattern_block_id, + .implicit_param_patterns_id = implicit_param_patterns_id, + .param_patterns_id = param_patterns_id, + .is_extern = false, + .extern_library_id = SemIR::LibraryNameId::None, + .non_owning_decl_id = SemIR::InstId::None, + .first_owning_decl_id = decl_id, + .definition_id = decl_id}, + {.call_params_id = call_params_id, + .return_slot_pattern_id = return_slot_pattern_id, + .virtual_modifier = callee.virtual_modifier, + .virtual_index = callee.virtual_index, + .self_param_id = self_param_id}}); function_decl.type_id = GetFunctionType(context, function_decl.function_id, context.scope_stack().PeekSpecificId()); @@ -269,6 +268,9 @@ auto BuildThunk(Context& context, SemIR::FunctionId signature_id, CloneFunctionDecl(context, SemIR::LocId(callee_id), signature_id, signature_specific_id, callee.function_id); + // Track that this function is a thunk. + context.functions().Get(function_id).SetThunk(callee_id); + // Register the thunk to be defined when we reach the end of the enclosing // deferred definition scope, for example an `impl` or `class` definition, as // if the thunk's body were written inline in this location. @@ -284,7 +286,8 @@ auto BuildThunk(Context& context, SemIR::FunctionId signature_id, } // Build an expression that names the value matched by a pattern. -static auto BuildPatternRef(Context& context, SemIR::FunctionId function_id, +static auto BuildPatternRef(Context& context, + llvm::ArrayRef arg_ids, SemIR::InstId pattern_id) -> SemIR::InstId { auto pattern = context.insts().Get(pattern_id); @@ -294,20 +297,7 @@ static auto BuildPatternRef(Context& context, SemIR::FunctionId function_id, auto pattern_ref_id = SemIR::InstId::None; if (auto value_param = pattern.TryAs()) { - // Build a reference to this parameter. - auto call_param_id = context.inst_blocks().Get( - context.functions() - .Get(function_id) - .call_params_id)[value_param->index.index]; - // Use a pretty name for the `name_ref`. While it's suspicious to use a - // pretty name in the IR like this, the only reason we include a name at - // all here is to make the formatted SemIR more readable. - pattern_ref_id = AddInst( - context, SemIR::LocId(pattern_id), - {.type_id = context.insts().Get(call_param_id).type_id(), - .name_id = SemIR::GetPrettyNameFromPatternId( - context.sem_ir(), value_param->subpattern_id), - .value_id = call_param_id}); + pattern_ref_id = arg_ids[value_param->index.index]; } else { if (pattern_id != SemIR::ErrorInst::InstId) { context.TODO( @@ -327,24 +317,17 @@ static auto BuildPatternRef(Context& context, SemIR::FunctionId function_id, return pattern_ref_id; } -// Build a call to a function that forwards the arguments of the enclosing -// function, for use when constructing a thunk. -static auto BuildThunkCall(Context& context, SemIR::FunctionId function_id, - SemIR::InstId callee_id) -> SemIR::InstId { - auto loc_id = SemIR::LocId(callee_id); +auto PerformThunkCall(Context& context, SemIR::LocId loc_id, + SemIR::FunctionId function_id, + llvm::ArrayRef call_arg_ids, + SemIR::InstId callee_id) -> SemIR::InstId { auto& function = context.functions().Get(function_id); - // Build a `NameRef` naming the callee, and a `SpecificConstant` if needed. - auto callee_type = context.types().GetAs( - context.insts().Get(callee_id).type_id()); - callee_id = BuildNameRef(context, loc_id, function.name_id, callee_id, - callee_type.specific_id); - // If we have a self parameter, form `self.`. if (function.self_param_id.has_value()) { callee_id = PerformCompoundMemberAccess( context, loc_id, - BuildPatternRef(context, function_id, function.self_param_id), + BuildPatternRef(context, call_arg_ids, function.self_param_id), callee_id); } @@ -352,12 +335,42 @@ static auto BuildThunkCall(Context& context, SemIR::FunctionId function_id, llvm::SmallVector args; for (auto pattern_id : context.inst_blocks().Get(function.param_patterns_id)) { - args.push_back(BuildPatternRef(context, function_id, pattern_id)); + args.push_back(BuildPatternRef(context, call_arg_ids, pattern_id)); } return PerformCall(context, loc_id, callee_id, args); } +// Build a call to a function that forwards the arguments of the enclosing +// function, for use when constructing a thunk. +static auto BuildThunkCall(Context& context, SemIR::FunctionId function_id, + SemIR::InstId callee_id) -> SemIR::InstId { + auto& function = context.functions().Get(function_id); + + // Build a `NameRef` naming the callee, and a `SpecificConstant` if needed. + auto loc_id = SemIR::LocId(callee_id); + auto callee_type = context.types().GetAs( + context.insts().Get(callee_id).type_id()); + callee_id = BuildNameRef(context, loc_id, function.name_id, callee_id, + callee_type.specific_id); + + // Build a reference to each parameter for use as call arguments. + llvm::SmallVector call_args; + auto call_params = context.inst_blocks().Get(function.call_params_id); + call_args.reserve(call_params.size()); + for (auto call_param_id : call_params) { + // Use a pretty name for the `name_ref`. While it's suspicious to use a + // pretty name in the IR like this, the only reason we include a name at all + // here is to make the formatted SemIR more readable. + auto call_param = context.insts().GetAs(call_param_id); + call_args.push_back(BuildNameRef(context, SemIR::LocId(call_param_id), + call_param.pretty_name_id, call_param_id, + SemIR::SpecificId::None)); + } + + return PerformThunkCall(context, loc_id, function_id, call_args, callee_id); +} + // Given a declaration of a thunk and the function that it should call, build // the thunk body. static auto BuildThunkDefinition(Context& context, diff --git a/toolchain/check/thunk.h b/toolchain/check/thunk.h index 0f7a9c4b6479..84628e4bc430 100644 --- a/toolchain/check/thunk.h +++ b/toolchain/check/thunk.h @@ -18,6 +18,15 @@ auto BuildThunk(Context& context, SemIR::FunctionId signature_id, SemIR::SpecificId signature_specific_id, SemIR::InstId callee_id) -> SemIR::InstId; +// Builds a call to a function that forwards a call argument list built for +// `function_id` to a call to `callee_id`, for use when building a call from a +// thunk to its target. This is like `PerformCall`, except that it takes a list +// of call arguments for `function_id`, not a syntactic argument list. +auto PerformThunkCall(Context& context, SemIR::LocId loc_id, + SemIR::FunctionId function_id, + llvm::ArrayRef call_arg_ids, + SemIR::InstId callee_id) -> SemIR::InstId; + // Builds the definition for a thunk whose definition was deferred until the end // of the enclosing scope. auto BuildThunkDefinition(Context& context, diff --git a/toolchain/lower/file_context.cpp b/toolchain/lower/file_context.cpp index 1186a2d361ba..23f904542ca2 100644 --- a/toolchain/lower/file_context.cpp +++ b/toolchain/lower/file_context.cpp @@ -637,7 +637,7 @@ auto FileContext::BuildFunctionDecl(SemIR::FunctionId function_id, } // Don't lower builtins. - if (function.builtin_function_kind != SemIR::BuiltinFunctionKind::None) { + if (function.builtin_function_kind() != SemIR::BuiltinFunctionKind::None) { return nullptr; } diff --git a/toolchain/lower/handle_call.cpp b/toolchain/lower/handle_call.cpp index 559103d1e1ce..3860c03c447f 100644 --- a/toolchain/lower/handle_call.cpp +++ b/toolchain/lower/handle_call.cpp @@ -512,7 +512,7 @@ auto HandleInst(FunctionContext& context, SemIR::InstId inst_id, callee_function.function_id, callee_function.resolved_specific_id); - if (auto builtin_kind = function.builtin_function_kind; + if (auto builtin_kind = function.builtin_function_kind(); builtin_kind != SemIR::BuiltinFunctionKind::None) { HandleBuiltinCall(context, inst_id, builtin_kind, arg_ids); return; diff --git a/toolchain/lower/mangler.cpp b/toolchain/lower/mangler.cpp index cbea0180e888..be9009811500 100644 --- a/toolchain/lower/mangler.cpp +++ b/toolchain/lower/mangler.cpp @@ -163,6 +163,9 @@ auto Mangler::Mangle(SemIR::FunctionId function_id, switch (function.special_function_kind) { case SemIR::Function::SpecialFunctionKind::None: break; + case SemIR::Function::SpecialFunctionKind::Builtin: + CARBON_FATAL("Attempting to mangle declaration of builtin function {0}", + function.builtin_function_kind()); case SemIR::Function::SpecialFunctionKind::Thunk: os << ":thunk"; break; diff --git a/toolchain/lower/testdata/impl/import_thunk.carbon b/toolchain/lower/testdata/impl/import_thunk.carbon index 4cf5e6a97841..db46554b7187 100644 --- a/toolchain/lower/testdata/impl/import_thunk.carbon +++ b/toolchain/lower/testdata/impl/import_thunk.carbon @@ -134,12 +134,24 @@ fn Test(a: A) -> C { // CHECK:STDOUT: // CHECK:STDOUT: define void @_CTest.Main(ptr sret({ i32 }) %return, ptr %a) !dbg !4 { // CHECK:STDOUT: entry: -// CHECK:STDOUT: call void @"_CF:thunk.61ea2aba74ab3bf1:I.Main"(ptr %return, ptr %a), !dbg !7 -// CHECK:STDOUT: ret void, !dbg !8 +// CHECK:STDOUT: %.loc7_20.1.temp = alloca { i32 }, align 8, !dbg !7 +// CHECK:STDOUT: %.loc7_20.2.temp = alloca { i32 }, align 8, !dbg !7 +// CHECK:STDOUT: %.loc7_19.1.temp = alloca { i32 }, align 8, !dbg !8 +// CHECK:STDOUT: call void @"_CConvert.A.Main:ImplicitAs.Core"(ptr %.loc7_19.1.temp, ptr %a), !dbg !8 +// CHECK:STDOUT: call void @"_CF.61ea2aba74ab3bf1:I.Main"(ptr %.loc7_20.2.temp, ptr %.loc7_19.1.temp), !dbg !7 +// CHECK:STDOUT: %.loc7_21.1.temp = alloca { i32 }, align 8, !dbg !9 +// CHECK:STDOUT: call void @"_CConvert.B.Main:ImplicitAs.Core"(ptr %.loc7_21.1.temp, ptr %.loc7_20.2.temp), !dbg !9 +// CHECK:STDOUT: ret void, !dbg !9 // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: declare void @"_CF:thunk.61ea2aba74ab3bf1:I.Main"(ptr sret({ i32 }), ptr) // CHECK:STDOUT: +// CHECK:STDOUT: declare void @"_CF.61ea2aba74ab3bf1:I.Main"(ptr sret({ i32 }), ptr) +// CHECK:STDOUT: +// CHECK:STDOUT: declare void @"_CConvert.A.Main:ImplicitAs.Core"(ptr sret({ i32 }), ptr) +// CHECK:STDOUT: +// CHECK:STDOUT: declare void @"_CConvert.B.Main:ImplicitAs.Core"(ptr sret({ i32 }), ptr) +// CHECK:STDOUT: // CHECK:STDOUT: !llvm.module.flags = !{!0, !1} // CHECK:STDOUT: !llvm.dbg.cu = !{!2} // CHECK:STDOUT: @@ -151,7 +163,8 @@ fn Test(a: A) -> C { // CHECK:STDOUT: !5 = !DISubroutineType(types: !6) // CHECK:STDOUT: !6 = !{} // CHECK:STDOUT: !7 = !DILocation(line: 7, column: 10, scope: !4) -// CHECK:STDOUT: !8 = !DILocation(line: 7, column: 3, scope: !4) +// CHECK:STDOUT: !8 = !DILocation(line: 7, column: 19, scope: !4) +// CHECK:STDOUT: !9 = !DILocation(line: 7, column: 3, scope: !4) // CHECK:STDOUT: ; ModuleID = 'thunk_for_imported_interface.carbon' // CHECK:STDOUT: source_filename = "thunk_for_imported_interface.carbon" // CHECK:STDOUT: @@ -199,12 +212,24 @@ fn Test(a: A) -> C { // CHECK:STDOUT: // CHECK:STDOUT: define void @_CTest.Main(ptr sret({ i32 }) %return, ptr %a) !dbg !4 { // CHECK:STDOUT: entry: -// CHECK:STDOUT: call void @"_CF:thunk.X.Main:I.Main"(ptr %return, ptr %a), !dbg !7 -// CHECK:STDOUT: ret void, !dbg !8 +// CHECK:STDOUT: %.loc8_19.1.temp = alloca { i32 }, align 8, !dbg !7 +// CHECK:STDOUT: %.loc8_19.2.temp = alloca { i32 }, align 8, !dbg !7 +// CHECK:STDOUT: %.loc8_18.1.temp = alloca { i32 }, align 8, !dbg !8 +// CHECK:STDOUT: call void @"_CConvert.A.Main:ImplicitAs.Core"(ptr %.loc8_18.1.temp, ptr %a), !dbg !8 +// CHECK:STDOUT: call void @"_CF.X.Main:I.Main"(ptr %.loc8_19.2.temp, ptr %.loc8_18.1.temp), !dbg !7 +// CHECK:STDOUT: %.loc8_20.1.temp = alloca { i32 }, align 8, !dbg !9 +// CHECK:STDOUT: call void @"_CConvert.B.Main:ImplicitAs.Core"(ptr %.loc8_20.1.temp, ptr %.loc8_19.2.temp), !dbg !9 +// CHECK:STDOUT: ret void, !dbg !9 // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: declare void @"_CF:thunk.X.Main:I.Main"(ptr sret({ i32 }), ptr) // CHECK:STDOUT: +// CHECK:STDOUT: declare void @"_CF.X.Main:I.Main"(ptr sret({ i32 }), ptr) +// CHECK:STDOUT: +// CHECK:STDOUT: declare void @"_CConvert.A.Main:ImplicitAs.Core"(ptr sret({ i32 }), ptr) +// CHECK:STDOUT: +// CHECK:STDOUT: declare void @"_CConvert.B.Main:ImplicitAs.Core"(ptr sret({ i32 }), ptr) +// CHECK:STDOUT: // CHECK:STDOUT: !llvm.module.flags = !{!0, !1} // CHECK:STDOUT: !llvm.dbg.cu = !{!2} // CHECK:STDOUT: @@ -216,4 +241,5 @@ fn Test(a: A) -> C { // CHECK:STDOUT: !5 = !DISubroutineType(types: !6) // CHECK:STDOUT: !6 = !{} // CHECK:STDOUT: !7 = !DILocation(line: 8, column: 10, scope: !4) -// CHECK:STDOUT: !8 = !DILocation(line: 8, column: 3, scope: !4) +// CHECK:STDOUT: !8 = !DILocation(line: 8, column: 18, scope: !4) +// CHECK:STDOUT: !9 = !DILocation(line: 8, column: 3, scope: !4) diff --git a/toolchain/lower/testdata/impl/thunk.carbon b/toolchain/lower/testdata/impl/thunk.carbon index 91dd78a3ff42..29b8e7a9d05c 100644 --- a/toolchain/lower/testdata/impl/thunk.carbon +++ b/toolchain/lower/testdata/impl/thunk.carbon @@ -111,8 +111,14 @@ fn CallCallGeneric(c: C(()), b: B) -> A { // CHECK:STDOUT: // CHECK:STDOUT: define void @_CTest.Main(ptr sret({ i32 }) %return, ptr %a) !dbg !21 { // CHECK:STDOUT: entry: -// CHECK:STDOUT: call void @"_CF:thunk.61ea2aba74ab3bf1:I.Main"(ptr %return, ptr %a), !dbg !22 -// CHECK:STDOUT: ret void, !dbg !23 +// CHECK:STDOUT: %.loc24_20.1.temp = alloca { i32 }, align 8, !dbg !22 +// CHECK:STDOUT: %.loc24_20.2.temp = alloca { i32 }, align 8, !dbg !22 +// CHECK:STDOUT: %.loc24_19.1.temp = alloca { i32 }, align 8, !dbg !23 +// CHECK:STDOUT: call void @"_CConvert.A.Main:ImplicitAs.Core"(ptr %.loc24_19.1.temp, ptr %a), !dbg !23 +// CHECK:STDOUT: call void @"_CF.61ea2aba74ab3bf1:I.Main"(ptr %.loc24_20.2.temp, ptr %.loc24_19.1.temp), !dbg !22 +// CHECK:STDOUT: %.loc24_21.1.temp = alloca { i32 }, align 8, !dbg !24 +// CHECK:STDOUT: call void @"_CConvert.B.Main:ImplicitAs.Core"(ptr %.loc24_21.1.temp, ptr %.loc24_20.2.temp), !dbg !24 +// CHECK:STDOUT: ret void, !dbg !24 // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: !llvm.module.flags = !{!0, !1} @@ -141,7 +147,8 @@ fn CallCallGeneric(c: C(()), b: B) -> A { // CHECK:STDOUT: !20 = !DILocation(line: 16, column: 8, scope: !18) // CHECK:STDOUT: !21 = distinct !DISubprogram(name: "Test", linkageName: "_CTest.Main", scope: null, file: !3, line: 23, type: !5, spFlags: DISPFlagDefinition, unit: !2) // CHECK:STDOUT: !22 = !DILocation(line: 24, column: 10, scope: !21) -// CHECK:STDOUT: !23 = !DILocation(line: 24, column: 3, scope: !21) +// CHECK:STDOUT: !23 = !DILocation(line: 24, column: 19, scope: !21) +// CHECK:STDOUT: !24 = !DILocation(line: 24, column: 3, scope: !21) // CHECK:STDOUT: ; ModuleID = 'generic_thunk.carbon' // CHECK:STDOUT: source_filename = "generic_thunk.carbon" // CHECK:STDOUT: @@ -156,30 +163,31 @@ fn CallCallGeneric(c: C(()), b: B) -> A { // CHECK:STDOUT: // CHECK:STDOUT: define void @_CCall.Main(ptr sret({}) %return, ptr %c, ptr %b) !dbg !8 { // CHECK:STDOUT: entry: -// CHECK:STDOUT: call void @"_CF:thunk.C.Main:I.Main.e43630e9a6c38c3f"(ptr %return, ptr %c, ptr %b), !dbg !9 -// CHECK:STDOUT: ret void, !dbg !10 +// CHECK:STDOUT: %.loc22_23.1.temp = alloca {}, align 8, !dbg !9 +// CHECK:STDOUT: %.loc22_23.3.temp = alloca {}, align 8, !dbg !9 +// CHECK:STDOUT: call void @"_CConvert.B.Main:ImplicitAs.Core"(ptr %.loc22_23.3.temp, ptr %b), !dbg !9 +// CHECK:STDOUT: %.loc22_23.7.temp = alloca {}, align 8, !dbg !9 +// CHECK:STDOUT: %.loc22_22.1.temp = alloca {}, align 8, !dbg !10 +// CHECK:STDOUT: call void @"_CConvert.B.Main:ImplicitAs.Core"(ptr %.loc22_22.1.temp, ptr %b), !dbg !10 +// CHECK:STDOUT: call void @"_CF.C.Main:I.Main.e43630e9a6c38c3f"(ptr %.loc22_23.7.temp, ptr %c, ptr %.loc22_22.1.temp), !dbg !9 +// CHECK:STDOUT: %.loc22_24.1.temp = alloca {}, align 8, !dbg !11 +// CHECK:STDOUT: call void @"_CConvert.B.Main:ImplicitAs.Core"(ptr %.loc22_24.1.temp, ptr %.loc22_23.7.temp), !dbg !11 +// CHECK:STDOUT: ret void, !dbg !11 // CHECK:STDOUT: } // CHECK:STDOUT: -// CHECK:STDOUT: define void @_CCallCallGeneric.Main(ptr sret({}) %return, ptr %c, ptr %b) !dbg !11 { +// CHECK:STDOUT: define void @_CCallCallGeneric.Main(ptr sret({}) %return, ptr %c, ptr %b) !dbg !12 { // CHECK:STDOUT: entry: -// CHECK:STDOUT: call void @_CCallGeneric.Main.9ef6968dffa77413(ptr %return, ptr %c, ptr %b), !dbg !12 -// CHECK:STDOUT: ret void, !dbg !13 +// CHECK:STDOUT: call void @_CCallGeneric.Main.9ef6968dffa77413(ptr %return, ptr %c, ptr %b), !dbg !13 +// CHECK:STDOUT: ret void, !dbg !14 // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: ; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite) // CHECK:STDOUT: declare void @llvm.memcpy.p0.p0.i64(ptr noalias writeonly captures(none), ptr noalias readonly captures(none), i64, i1 immarg) #0 // CHECK:STDOUT: -// CHECK:STDOUT: define linkonce_odr void @"_CF:thunk.C.Main:I.Main.e43630e9a6c38c3f"(ptr sret({}) %return, ptr %self, ptr %x) !dbg !14 { +// CHECK:STDOUT: define linkonce_odr void @"_CF.C.Main:I.Main.e43630e9a6c38c3f"(ptr sret({}) %return, ptr %self, ptr %x) !dbg !15 { // CHECK:STDOUT: entry: -// CHECK:STDOUT: %.loc18_31.2.temp = alloca {}, align 8, !dbg !15 -// CHECK:STDOUT: call void @"_CConvert.B.Main:ImplicitAs.Core"(ptr %.loc18_31.2.temp, ptr %x), !dbg !15 -// CHECK:STDOUT: %.loc18_31.6.temp = alloca {}, align 8, !dbg !15 -// CHECK:STDOUT: %.loc12_21.1.temp = alloca {}, align 8, !dbg !16 -// CHECK:STDOUT: call void @"_CConvert.B.Main:ImplicitAs.Core"(ptr %.loc12_21.1.temp, ptr %x), !dbg !16 -// CHECK:STDOUT: call void @"_CF.C.Main:I.Main.e43630e9a6c38c3f"(ptr %.loc18_31.6.temp, ptr %self, ptr %.loc12_21.1.temp), !dbg !15 -// CHECK:STDOUT: %.loc18_31.7.temp = alloca {}, align 8, !dbg !15 -// CHECK:STDOUT: call void @"_CConvert.B.Main:ImplicitAs.Core"(ptr %.loc18_31.7.temp, ptr %.loc18_31.6.temp), !dbg !15 -// CHECK:STDOUT: ret void, !dbg !15 +// CHECK:STDOUT: call void @llvm.memcpy.p0.p0.i64(ptr align 1 %return, ptr align 1 @B.val.loc18_42, i64 0, i1 false), !dbg !16 +// CHECK:STDOUT: ret void, !dbg !16 // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: define linkonce_odr void @_CCallGeneric.Main.9ef6968dffa77413(ptr sret({}) %return, ptr %c, ptr %b) !dbg !17 { @@ -188,9 +196,16 @@ fn CallCallGeneric(c: C(()), b: B) -> A { // CHECK:STDOUT: ret void, !dbg !19 // CHECK:STDOUT: } // CHECK:STDOUT: -// CHECK:STDOUT: define linkonce_odr void @"_CF.C.Main:I.Main.e43630e9a6c38c3f"(ptr sret({}) %return, ptr %self, ptr %x) !dbg !20 { +// CHECK:STDOUT: define linkonce_odr void @"_CF:thunk.C.Main:I.Main.e43630e9a6c38c3f"(ptr sret({}) %return, ptr %self, ptr %x) !dbg !20 { // CHECK:STDOUT: entry: -// CHECK:STDOUT: call void @llvm.memcpy.p0.p0.i64(ptr align 1 %return, ptr align 1 @B.val.loc18_42, i64 0, i1 false), !dbg !21 +// CHECK:STDOUT: %.loc18_31.2.temp = alloca {}, align 8, !dbg !21 +// CHECK:STDOUT: call void @"_CConvert.B.Main:ImplicitAs.Core"(ptr %.loc18_31.2.temp, ptr %x), !dbg !21 +// CHECK:STDOUT: %.loc18_31.6.temp = alloca {}, align 8, !dbg !21 +// CHECK:STDOUT: %.loc12_21.1.temp = alloca {}, align 8, !dbg !22 +// CHECK:STDOUT: call void @"_CConvert.B.Main:ImplicitAs.Core"(ptr %.loc12_21.1.temp, ptr %x), !dbg !22 +// CHECK:STDOUT: call void @"_CF.C.Main:I.Main.e43630e9a6c38c3f"(ptr %.loc18_31.6.temp, ptr %self, ptr %.loc12_21.1.temp), !dbg !21 +// CHECK:STDOUT: %.loc18_31.7.temp = alloca {}, align 8, !dbg !21 +// CHECK:STDOUT: call void @"_CConvert.B.Main:ImplicitAs.Core"(ptr %.loc18_31.7.temp, ptr %.loc18_31.6.temp), !dbg !21 // CHECK:STDOUT: ret void, !dbg !21 // CHECK:STDOUT: } // CHECK:STDOUT: @@ -209,15 +224,16 @@ fn CallCallGeneric(c: C(()), b: B) -> A { // CHECK:STDOUT: !7 = !DILocation(line: 7, column: 37, scope: !4) // CHECK:STDOUT: !8 = distinct !DISubprogram(name: "Call", linkageName: "_CCall.Main", scope: null, file: !3, line: 21, type: !5, spFlags: DISPFlagDefinition, unit: !2) // CHECK:STDOUT: !9 = !DILocation(line: 22, column: 10, scope: !8) -// CHECK:STDOUT: !10 = !DILocation(line: 22, column: 3, scope: !8) -// CHECK:STDOUT: !11 = distinct !DISubprogram(name: "CallCallGeneric", linkageName: "_CCallCallGeneric.Main", scope: null, file: !3, line: 29, type: !5, spFlags: DISPFlagDefinition, unit: !2) -// CHECK:STDOUT: !12 = !DILocation(line: 30, column: 10, scope: !11) -// CHECK:STDOUT: !13 = !DILocation(line: 30, column: 3, scope: !11) -// CHECK:STDOUT: !14 = distinct !DISubprogram(name: "F", linkageName: "_CF:thunk.C.Main:I.Main.e43630e9a6c38c3f", scope: null, file: !3, line: 18, type: !5, spFlags: DISPFlagDefinition, unit: !2) -// CHECK:STDOUT: !15 = !DILocation(line: 18, column: 3, scope: !14) -// CHECK:STDOUT: !16 = !DILocation(line: 12, column: 20, scope: !14) +// CHECK:STDOUT: !10 = !DILocation(line: 22, column: 22, scope: !8) +// CHECK:STDOUT: !11 = !DILocation(line: 22, column: 3, scope: !8) +// CHECK:STDOUT: !12 = distinct !DISubprogram(name: "CallCallGeneric", linkageName: "_CCallCallGeneric.Main", scope: null, file: !3, line: 29, type: !5, spFlags: DISPFlagDefinition, unit: !2) +// CHECK:STDOUT: !13 = !DILocation(line: 30, column: 10, scope: !12) +// CHECK:STDOUT: !14 = !DILocation(line: 30, column: 3, scope: !12) +// CHECK:STDOUT: !15 = distinct !DISubprogram(name: "F", linkageName: "_CF.C.Main:I.Main.e43630e9a6c38c3f", scope: null, file: !3, line: 18, type: !5, spFlags: DISPFlagDefinition, unit: !2) +// CHECK:STDOUT: !16 = !DILocation(line: 18, column: 33, scope: !15) // CHECK:STDOUT: !17 = distinct !DISubprogram(name: "CallGeneric", linkageName: "_CCallGeneric.Main.9ef6968dffa77413", scope: null, file: !3, line: 25, type: !5, spFlags: DISPFlagDefinition, unit: !2) // CHECK:STDOUT: !18 = !DILocation(line: 26, column: 10, scope: !17) // CHECK:STDOUT: !19 = !DILocation(line: 26, column: 3, scope: !17) -// CHECK:STDOUT: !20 = distinct !DISubprogram(name: "F", linkageName: "_CF.C.Main:I.Main.e43630e9a6c38c3f", scope: null, file: !3, line: 18, type: !5, spFlags: DISPFlagDefinition, unit: !2) -// CHECK:STDOUT: !21 = !DILocation(line: 18, column: 33, scope: !20) +// CHECK:STDOUT: !20 = distinct !DISubprogram(name: "F", linkageName: "_CF:thunk.C.Main:I.Main.e43630e9a6c38c3f", scope: null, file: !3, line: 18, type: !5, spFlags: DISPFlagDefinition, unit: !2) +// CHECK:STDOUT: !21 = !DILocation(line: 18, column: 3, scope: !20) +// CHECK:STDOUT: !22 = !DILocation(line: 12, column: 20, scope: !20) diff --git a/toolchain/sem_ir/builtin_function_kind.h b/toolchain/sem_ir/builtin_function_kind.h index 7207a31657ea..abeeac4241eb 100644 --- a/toolchain/sem_ir/builtin_function_kind.h +++ b/toolchain/sem_ir/builtin_function_kind.h @@ -27,6 +27,9 @@ class BuiltinFunctionKind : public CARBON_ENUM_BASE(BuiltinFunctionKind) { CARBON_ENUM_CONSTANT_DECL(Name) #include "toolchain/sem_ir/builtin_function_kind.def" + using EnumBase::AsInt; + using EnumBase::FromInt; + // Returns the builtin function kind with the given name, or None if the name // is unknown. static auto ForBuiltinName(llvm::StringRef name) -> BuiltinFunctionKind; diff --git a/toolchain/sem_ir/formatter.cpp b/toolchain/sem_ir/formatter.cpp index 4378d565d272..998eb484c1ba 100644 --- a/toolchain/sem_ir/formatter.cpp +++ b/toolchain/sem_ir/formatter.cpp @@ -487,12 +487,17 @@ auto Formatter::FormatFunction(FunctionId id) -> void { FormatParamList(fn.call_params_id, return_type_info.is_valid() && return_type_info.has_return_slot()); - if (fn.builtin_function_kind != BuiltinFunctionKind::None) { + if (fn.builtin_function_kind() != BuiltinFunctionKind::None) { out_ << " = \"" - << FormatEscaped(fn.builtin_function_kind.name(), + << FormatEscaped(fn.builtin_function_kind().name(), /*use_hex_escapes=*/true) << "\""; } + if (fn.thunk_decl_id().has_value()) { + out_ << " [thunk "; + FormatArg(fn.thunk_decl_id()); + out_ << "]"; + } if (!fn.body_block_ids.empty()) { out_ << ' '; diff --git a/toolchain/sem_ir/function.h b/toolchain/sem_ir/function.h index 8fabbcbc0dcd..8cdd723439b1 100644 --- a/toolchain/sem_ir/function.h +++ b/toolchain/sem_ir/function.h @@ -16,7 +16,7 @@ namespace Carbon::SemIR { // Function-specific fields. struct FunctionFields { // Kinds of special functions. - enum class SpecialFunctionKind : uint8_t { None, Thunk }; + enum class SpecialFunctionKind : uint8_t { None, Builtin, Thunk }; // Kinds of virtual modifiers that can apply to functions. enum class VirtualModifier : uint8_t { None, Virtual, Abstract, Impl }; @@ -60,13 +60,9 @@ struct FunctionFields { // implicit_param_patterns_id from EntityWithParamsBase. InstId self_param_id = InstId::None; - // The following member is set on the first call to the function, or at the - // point where the function is defined. - - // The following members are set at the end of a builtin function definition. - - // If this is a builtin function, the corresponding builtin kind. - BuiltinFunctionKind builtin_function_kind = BuiltinFunctionKind::None; + // Data that is specific to the special function kind. Use + // `builtin_function_kind()` or `thunk_decl_id()` to access this. + AnyRawId special_function_kind_data = AnyRawId(AnyRawId::NoneIndex); // The following members are accumulated throughout the function definition. @@ -109,6 +105,22 @@ struct Function : public EntityWithParamsBase, out << "}"; } + // Returns the builtin function kind for this function, or None if this is not + // a builtin function. + auto builtin_function_kind() const -> BuiltinFunctionKind { + return special_function_kind == SpecialFunctionKind::Builtin + ? BuiltinFunctionKind::FromInt(special_function_kind_data.index) + : BuiltinFunctionKind::None; + } + + // Returns the declaration that this is a thunk for, or None if this function + // is not a thunk. + auto thunk_decl_id() const -> InstId { + return special_function_kind == SpecialFunctionKind::Thunk + ? InstId(special_function_kind_data.index) + : InstId::None; + } + // Given the ID of an instruction from `param_patterns_id` or // `implicit_param_patterns_id`, returns a `ParamPatternInfo` value with the // corresponding `Call` parameter pattern, its ID, and the entity_name_id of @@ -127,6 +139,20 @@ struct Function : public EntityWithParamsBase, auto GetDeclaredReturnType(const File& file, SpecificId specific_id = SpecificId::None) const -> TypeId; + + // Sets that this function is a builtin function. + auto SetBuiltinFunction(BuiltinFunctionKind kind) -> void { + CARBON_CHECK(special_function_kind == SpecialFunctionKind::None); + special_function_kind = SpecialFunctionKind::Builtin; + special_function_kind_data = AnyRawId(kind.AsInt()); + } + + // Sets that this function is a thunk. + auto SetThunk(InstId decl_id) -> void { + CARBON_CHECK(special_function_kind == SpecialFunctionKind::None); + special_function_kind = SpecialFunctionKind::Thunk; + special_function_kind_data = AnyRawId(decl_id.index); + } }; class File; diff --git a/toolchain/sem_ir/inst_namer.cpp b/toolchain/sem_ir/inst_namer.cpp index 47ff1086b25b..3f8a52e9346a 100644 --- a/toolchain/sem_ir/inst_namer.cpp +++ b/toolchain/sem_ir/inst_namer.cpp @@ -662,10 +662,10 @@ auto InstNamer::NamingContext::NameInst() -> void { const auto& function = sem_ir().functions().Get(callee_function.function_id); // Name the call's result based on the callee. - if (function.builtin_function_kind != BuiltinFunctionKind::None) { + if (function.builtin_function_kind() != BuiltinFunctionKind::None) { // For a builtin, use the builtin name. Otherwise, we'd typically pick // the name `Op` below, which is probably not very useful. - AddInstName(function.builtin_function_kind.name().str()); + AddInstName(function.builtin_function_kind().name().str()); return; }