diff --git a/toolchain/check/call.cpp b/toolchain/check/call.cpp index 577e2988b3e0..1dae5dc3ae29 100644 --- a/toolchain/check/call.cpp +++ b/toolchain/check/call.cpp @@ -113,42 +113,36 @@ auto PerformCall(Context& context, Parse::NodeId node_id, } } - // For functions with an implicit return type, the return type is the empty - // tuple type. - SemIR::TypeId type_id = - callable.GetDeclaredReturnType(context.sem_ir(), specific_id); - if (!type_id.is_valid()) { - type_id = context.GetTupleType({}); - } - // If there is a return slot, build storage for the result. SemIR::InstId return_storage_id = SemIR::InstId::Invalid; - SemIR::Function::ReturnSlot return_slot; - { + SemIR::ReturnInfo return_info = [&] { DiagnosticAnnotationScope annotate_diagnostics( &context.emitter(), [&](auto& builder) { CARBON_DIAGNOSTIC(IncompleteReturnTypeHere, Note, "Return type declared here."); builder.Note(callable.return_storage_id, IncompleteReturnTypeHere); }); - return_slot = - CheckFunctionReturnType(context, callee_id, callable, specific_id); - } - switch (return_slot) { - case SemIR::Function::ReturnSlot::Present: + return CheckFunctionReturnType(context, callee_id, callable, specific_id); + }(); + switch (return_info.return_slot) { + case SemIR::ReturnSlot::Present: // Tentatively put storage for a temporary in the function's return slot. // This will be replaced if necessary when we perform initialization. return_storage_id = context.AddInst( - node_id, {.type_id = type_id}); + node_id, {.type_id = return_info.type_id}); break; - case SemIR::Function::ReturnSlot::Absent: + case SemIR::ReturnSlot::Absent: + // For functions with an implicit return type, the return type is the + // empty tuple type. + if (!return_info.type_id.is_valid()) { + return_info.type_id = context.GetTupleType({}); + } break; - case SemIR::Function::ReturnSlot::Error: + case SemIR::ReturnSlot::Incomplete: // Don't form an initializing expression with an incomplete type. - type_id = SemIR::TypeId::Error; + // CheckFunctionReturnType will have diagnosed this for us if needed. + return_info.type_id = SemIR::TypeId::Error; break; - case SemIR::Function::ReturnSlot::NotComputed: - CARBON_FATAL() << "Missing return slot category in call to " << callable; } // Convert the arguments to match the parameters. @@ -156,7 +150,7 @@ auto PerformCall(Context& context, Parse::NodeId node_id, ConvertCallArgs(context, node_id, callee_function.self_id, arg_ids, return_storage_id, callable, specific_id); auto call_inst_id = - context.AddInst(node_id, {.type_id = type_id, + context.AddInst(node_id, {.type_id = return_info.type_id, .callee_id = callee_id, .args_id = converted_args_id}); diff --git a/toolchain/check/function.cpp b/toolchain/check/function.cpp index 9fbc569bb640..4518b5ab9b3d 100644 --- a/toolchain/check/function.cpp +++ b/toolchain/check/function.cpp @@ -73,49 +73,29 @@ auto CheckFunctionTypeMatches(Context& context, auto CheckFunctionReturnType(Context& context, SemIRLoc loc, SemIR::Function& function, SemIR::SpecificId specific_id) - -> SemIR::Function::ReturnSlot { - // If we have already checked the return type, we have nothing to do. - if (function.return_slot != SemIR::Function::ReturnSlot::NotComputed && - !specific_id.is_valid()) { - return function.return_slot; + -> SemIR::ReturnInfo { + auto return_info = function.GetReturnInfo(context.sem_ir(), specific_id); + + // If we couldn't determine the return information due to the return type + // being incomplete, try to complete it now. + if (return_info.return_slot == SemIR::ReturnSlot::Incomplete) { + auto diagnose_incomplete_return_type = [&] { + CARBON_DIAGNOSTIC(IncompleteTypeInFunctionReturnType, Error, + "Function returns incomplete type `{0}`.", + SemIR::TypeId); + return context.emitter().Build(loc, IncompleteTypeInFunctionReturnType, + return_info.type_id); + }; + + // TODO: Consider suppressing the diagnostic if we've already diagnosed a + // definition or call to this function. + if (context.TryToCompleteType(return_info.type_id, + diagnose_incomplete_return_type)) { + return_info = function.GetReturnInfo(context.sem_ir(), specific_id); + } } - if (!function.return_storage_id.is_valid()) { - // Implicit `-> ()` has no return slot. - function.return_slot = SemIR::Function::ReturnSlot::Absent; - return function.return_slot; - } - - auto return_type_id = - function.GetDeclaredReturnType(context.sem_ir(), specific_id); - CARBON_CHECK(return_type_id.is_valid()) - << "Have return storage but no return type."; - - // Check the return type is complete. Only diagnose incompleteness if we've - // not already done so. - auto diagnose_incomplete_return_type = [&] { - CARBON_DIAGNOSTIC(IncompleteTypeInFunctionReturnType, Error, - "Function returns incomplete type `{0}`.", SemIR::TypeId); - return context.emitter().Build(loc, IncompleteTypeInFunctionReturnType, - return_type_id); - }; - SemIR::Function::ReturnSlot result; - if (!context.TryToCompleteType( - return_type_id, - function.return_slot == SemIR::Function::ReturnSlot::Error - ? std::nullopt - : std::optional(diagnose_incomplete_return_type))) { - result = SemIR::Function::ReturnSlot::Error; - } else if (SemIR::GetInitRepr(context.sem_ir(), return_type_id) - .has_return_slot()) { - result = SemIR::Function::ReturnSlot::Present; - } else { - result = SemIR::Function::ReturnSlot::Absent; - } - if (!specific_id.is_valid()) { - function.return_slot = result; - } - return result; + return return_info; } } // namespace Carbon::Check diff --git a/toolchain/check/function.h b/toolchain/check/function.h index 455b29b8562b..bfc76ac1010a 100644 --- a/toolchain/check/function.h +++ b/toolchain/check/function.h @@ -37,11 +37,12 @@ auto CheckFunctionTypeMatches(Context& context, // Checks that the return type of the specified function is complete, issuing an // error if not. This computes the return slot usage for the function if -// necessary. +// necessary, and returns information about how the function returns its return +// value. auto CheckFunctionReturnType(Context& context, SemIRLoc loc, SemIR::Function& function, SemIR::SpecificId specific_id) - -> SemIR::Function::ReturnSlot; + -> SemIR::ReturnInfo; } // namespace Carbon::Check diff --git a/toolchain/check/global_init.cpp b/toolchain/check/global_init.cpp index 1f8457c2306f..ce13d390b4ad 100644 --- a/toolchain/check/global_init.cpp +++ b/toolchain/check/global_init.cpp @@ -45,7 +45,6 @@ auto GlobalInit::Finalize() -> void { .decl_id = SemIR::InstId::Invalid}, {.return_storage_id = SemIR::InstId::Invalid, .is_extern = false, - .return_slot = SemIR::Function::ReturnSlot::Absent, .body_block_ids = {SemIR::InstBlockId::GlobalInit}}}); } diff --git a/toolchain/check/handle_function.cpp b/toolchain/check/handle_function.cpp index 9723bcb61667..6f03bbca8de3 100644 --- a/toolchain/check/handle_function.cpp +++ b/toolchain/check/handle_function.cpp @@ -64,28 +64,6 @@ static auto DiagnoseModifiers(Context& context, DeclIntroducerState& introducer, RequireDefaultFinalOnlyInInterfaces(context, introducer, parent_scope_inst); } -// Returns the return slot usage for a function given the computed usage for two -// different declarations of the function. -static auto MergeReturnSlot(SemIR::Function::ReturnSlot a, - SemIR::Function::ReturnSlot b) - -> SemIR::Function::ReturnSlot { - if (a == SemIR::Function::ReturnSlot::NotComputed) { - return b; - } - if (b == SemIR::Function::ReturnSlot::NotComputed) { - return a; - } - if (a == SemIR::Function::ReturnSlot::Error) { - return b; - } - if (b == SemIR::Function::ReturnSlot::Error) { - return a; - } - CARBON_CHECK(a == b) - << "Different return slot usage computed for the same function."; - return a; -} - // Tries to merge new_function into prev_function_id. Since new_function won't // have a definition even if one is upcoming, set is_definition to indicate the // planned result. @@ -120,8 +98,6 @@ static auto MergeFunctionRedecl(Context& context, SemIRLoc new_loc, prev_function.return_storage_id = new_function.return_storage_id; } // The new function might have return slot information if it was imported. - prev_function.return_slot = - MergeReturnSlot(prev_function.return_slot, new_function.return_slot); if ((prev_import_ir_id.is_valid() && !new_is_import) || (prev_function.is_extern && !new_function.is_extern)) { prev_function.is_extern = new_function.is_extern; @@ -198,14 +174,10 @@ static auto BuildFunctionDecl(Context& context, auto decl_block_id = context.inst_block_stack().Pop(); auto return_storage_id = SemIR::InstId::Invalid; - auto return_slot = SemIR::Function::ReturnSlot::NotComputed; if (auto [return_node, maybe_return_storage_id] = context.node_stack().PopWithNodeIdIf(); maybe_return_storage_id) { return_storage_id = *maybe_return_storage_id; - } else { - // If there's no return type, there's no return slot. - return_slot = SemIR::Function::ReturnSlot::Absent; } auto name = PopNameComponent(context); @@ -244,11 +216,9 @@ static auto BuildFunctionDecl(Context& context, // Build the function entity. This will be merged into an existing function if // there is one, or otherwise added to the function store. - auto function_info = - SemIR::Function{{name_context.MakeEntityWithParamsBase(decl_id, name)}, - {.return_storage_id = return_storage_id, - .is_extern = is_extern, - .return_slot = return_slot}}; + auto function_info = SemIR::Function{ + {name_context.MakeEntityWithParamsBase(decl_id, name)}, + {.return_storage_id = return_storage_id, .is_extern = is_extern}}; if (is_definition) { function_info.definition_id = decl_id; } diff --git a/toolchain/check/import_ref.cpp b/toolchain/check/import_ref.cpp index 76f61bc4ed74..d7e4635fa791 100644 --- a/toolchain/check/import_ref.cpp +++ b/toolchain/check/import_ref.cpp @@ -1161,7 +1161,6 @@ class ImportRefResolver { : SemIR::InstId::Invalid}, {.return_storage_id = new_return_storage, .is_extern = function.is_extern, - .return_slot = function.return_slot, .builtin_function_kind = function.builtin_function_kind}}); // TODO: Import this or recompute it. auto specific_id = SemIR::SpecificId::Invalid; diff --git a/toolchain/check/return.cpp b/toolchain/check/return.cpp index 33333eff97aa..07e50cf6eb25 100644 --- a/toolchain/check/return.cpp +++ b/toolchain/check/return.cpp @@ -56,10 +56,17 @@ auto CheckReturnedVar(Context& context, Parse::NodeId returned_node, Parse::NodeId name_node, SemIR::NameId name_id, Parse::NodeId type_node, SemIR::TypeId type_id) -> SemIR::InstId { - // A `returned var` requires an explicit return type. auto& function = GetCurrentFunction(context); - auto return_type_id = function.GetDeclaredReturnType(context.sem_ir()); - if (!return_type_id.is_valid()) { + auto return_info = function.GetReturnInfo(context.sem_ir()); + if (!return_info.is_valid()) { + // We already diagnosed this when we started defining the function. Create a + // placeholder for error recovery. + return context.AddInst( + name_node, {.type_id = type_id, .name_id = name_id}); + } + + // A `returned var` requires an explicit return type. + if (!return_info.type_id.is_valid()) { CARBON_DIAGNOSTIC(ReturnedVarWithNoReturnType, Error, "Cannot declare a `returned var` in this function."); auto diag = @@ -70,21 +77,21 @@ auto CheckReturnedVar(Context& context, Parse::NodeId returned_node, } // The declared type of the var must match the return type of the function. - if (return_type_id != type_id) { + if (return_info.type_id != type_id) { CARBON_DIAGNOSTIC(ReturnedVarWrongType, Error, "Type `{0}` of `returned var` does not match " "return type of enclosing function.", SemIR::TypeId); auto diag = context.emitter().Build(type_node, ReturnedVarWrongType, type_id); - NoteReturnType(diag, function, return_type_id); + NoteReturnType(diag, function, return_info.type_id); diag.Emit(); return SemIR::InstId::BuiltinError; } // The variable aliases the return slot if there is one. If not, it has its // own storage. - if (function.has_return_slot()) { + if (return_info.has_return_slot()) { return function.return_storage_id; } return context.AddInst( @@ -124,9 +131,9 @@ auto BuildReturnWithExpr(Context& context, Parse::ReturnStatementId node_id, const auto& function = GetCurrentFunction(context); auto returned_var_id = GetCurrentReturnedVar(context); auto return_slot_id = SemIR::InstId::Invalid; - auto return_type_id = function.GetDeclaredReturnType(context.sem_ir()); + auto return_info = function.GetReturnInfo(context.sem_ir()); - if (!return_type_id.is_valid()) { + if (!return_info.type_id.is_valid()) { CARBON_DIAGNOSTIC( ReturnStatementDisallowExpr, Error, "No return expression should be provided in this context."); @@ -142,14 +149,16 @@ auto BuildReturnWithExpr(Context& context, Parse::ReturnStatementId node_id, NoteReturnedVar(diag, returned_var_id); diag.Emit(); expr_id = SemIR::InstId::BuiltinError; - } else if (function.has_return_slot()) { + } else if (!return_info.is_valid()) { + // We already diagnosed that the return type is invalid. Don't try to + // convert to it. + expr_id = SemIR::InstId::BuiltinError; + } else if (return_info.has_return_slot()) { expr_id = Initialize(context, node_id, function.return_storage_id, expr_id); return_slot_id = function.return_storage_id; - } else if (function.return_slot == SemIR::Function::ReturnSlot::Error) { - // Don't produce a second error complaining the return type is incomplete. - expr_id = SemIR::InstId::BuiltinError; } else { - expr_id = ConvertToValueOfType(context, node_id, expr_id, return_type_id); + expr_id = + ConvertToValueOfType(context, node_id, expr_id, return_info.type_id); } context.AddInst( @@ -169,7 +178,7 @@ auto BuildReturnVar(Context& context, Parse::ReturnStatementId node_id) } auto return_slot_id = function.return_storage_id; - if (!function.has_return_slot()) { + if (!function.GetReturnInfo(context.sem_ir()).has_return_slot()) { // If we don't have a return slot, we're returning by value. Convert to a // value expression. returned_var_id = ConvertToValueExpr(context, returned_var_id); diff --git a/toolchain/check/testdata/basics/no_prelude/raw_and_textual_ir.carbon b/toolchain/check/testdata/basics/no_prelude/raw_and_textual_ir.carbon index 813f7357598e..a919288cc82a 100644 --- a/toolchain/check/testdata/basics/no_prelude/raw_and_textual_ir.carbon +++ b/toolchain/check/testdata/basics/no_prelude/raw_and_textual_ir.carbon @@ -27,7 +27,7 @@ fn Foo(n: ()) -> ((), ()) { // CHECK:STDOUT: entity_names: // CHECK:STDOUT: entity_name0: {name: name1, parent_scope: name_scope, index: comp_time_bind} // CHECK:STDOUT: functions: -// CHECK:STDOUT: function0: {name: name0, parent_scope: name_scope0, return_storage: inst+13, return_slot: present, body: [block7]} +// CHECK:STDOUT: function0: {name: name0, parent_scope: name_scope0, return_storage: inst+13, body: [block7]} // CHECK:STDOUT: classes: {} // CHECK:STDOUT: generics: {} // CHECK:STDOUT: specifics: {} diff --git a/toolchain/check/testdata/basics/no_prelude/raw_ir.carbon b/toolchain/check/testdata/basics/no_prelude/raw_ir.carbon index 1d59fd2e516e..945b4aceb15f 100644 --- a/toolchain/check/testdata/basics/no_prelude/raw_ir.carbon +++ b/toolchain/check/testdata/basics/no_prelude/raw_ir.carbon @@ -28,7 +28,7 @@ fn Foo[T:! type](n: T) -> (T, ()) { // CHECK:STDOUT: entity_name0: {name: name1, parent_scope: name_scope, index: comp_time_bind0} // CHECK:STDOUT: entity_name1: {name: name2, parent_scope: name_scope, index: comp_time_bind} // CHECK:STDOUT: functions: -// CHECK:STDOUT: function0: {name: name0, parent_scope: name_scope0, return_storage: inst+15, return_slot: present, body: [block12]} +// CHECK:STDOUT: function0: {name: name0, parent_scope: name_scope0, return_storage: inst+15, body: [block12]} // CHECK:STDOUT: classes: {} // CHECK:STDOUT: generics: // CHECK:STDOUT: generic0: {decl: inst+16, bindings: block8} diff --git a/toolchain/check/testdata/function/declaration/no_prelude/fail_import_incomplete_return.carbon b/toolchain/check/testdata/function/declaration/no_prelude/fail_import_incomplete_return.carbon index 5e7ffe230303..0209183ce898 100644 --- a/toolchain/check/testdata/function/declaration/no_prelude/fail_import_incomplete_return.carbon +++ b/toolchain/check/testdata/function/declaration/no_prelude/fail_import_incomplete_return.carbon @@ -54,7 +54,7 @@ library "use_imported"; import library "api"; fn CallFAndGIncomplete() { - // CHECK:STDERR: fail_use_imported.carbon:[[@LINE+15]]:3: ERROR: Function returns incomplete type `C`. + // CHECK:STDERR: fail_use_imported.carbon:[[@LINE+16]]:3: ERROR: Function returns incomplete type `C`. // CHECK:STDERR: ReturnCUnused(); // CHECK:STDERR: ^~~~~~~~~~~~~ // CHECK:STDERR: fail_use_imported.carbon:[[@LINE-6]]:1: In import. @@ -69,7 +69,23 @@ fn CallFAndGIncomplete() { // CHECK:STDERR: fail_incomplete_return.carbon:7:20: Return type declared here. // CHECK:STDERR: fn ReturnCUnused() -> C; // CHECK:STDERR: ^~~~ + // CHECK:STDERR: ReturnCUnused(); + // CHECK:STDERR: fail_use_imported.carbon:[[@LINE+15]]:3: ERROR: Function returns incomplete type `C`. + // CHECK:STDERR: ReturnCUsed(); + // CHECK:STDERR: ^~~~~~~~~~~ + // CHECK:STDERR: fail_use_imported.carbon:[[@LINE-23]]:1: In import. + // CHECK:STDERR: import library "api"; + // CHECK:STDERR: ^~~~~~ + // CHECK:STDERR: fail_incomplete_return.carbon:4:1: Class was forward declared here. + // CHECK:STDERR: class C; + // CHECK:STDERR: ^~~~~~~~ + // CHECK:STDERR: fail_use_imported.carbon:[[@LINE-29]]:1: In import. + // CHECK:STDERR: import library "api"; + // CHECK:STDERR: ^~~~~~ + // CHECK:STDERR: fail_incomplete_return.carbon:8:18: Return type declared here. + // CHECK:STDERR: fn ReturnCUsed() -> C; + // CHECK:STDERR: ^~~~ ReturnCUsed(); ReturnDUnused(); ReturnDUsed(); @@ -210,11 +226,13 @@ fn CallFAndGIncomplete() { // CHECK:STDOUT: %ReturnCUsed.ref: %ReturnCUsed.type = name_ref ReturnCUsed, imports.%import_ref.4 [template = constants.%ReturnCUsed] // CHECK:STDOUT: %ReturnCUsed.call: init = call %ReturnCUsed.ref() // CHECK:STDOUT: %ReturnDUnused.ref: %ReturnDUnused.type = name_ref ReturnDUnused, imports.%import_ref.5 [template = constants.%ReturnDUnused] -// CHECK:STDOUT: %.loc24_16.1: ref %D = temporary_storage -// CHECK:STDOUT: %ReturnDUnused.call: init %D = call %ReturnDUnused.ref() to %.loc24_16.1 -// CHECK:STDOUT: %.loc24_16.2: ref %D = temporary %.loc24_16.1, %ReturnDUnused.call +// CHECK:STDOUT: %.loc40_16.1: ref %D = temporary_storage +// CHECK:STDOUT: %ReturnDUnused.call: init %D = call %ReturnDUnused.ref() to %.loc40_16.1 +// CHECK:STDOUT: %.loc40_16.2: ref %D = temporary %.loc40_16.1, %ReturnDUnused.call // CHECK:STDOUT: %ReturnDUsed.ref: %ReturnDUsed.type = name_ref ReturnDUsed, imports.%import_ref.6 [template = constants.%ReturnDUsed] -// CHECK:STDOUT: %ReturnDUsed.call: init = call %ReturnDUsed.ref() +// CHECK:STDOUT: %.loc41_14.1: ref %D = temporary_storage +// CHECK:STDOUT: %ReturnDUsed.call: init %D = call %ReturnDUsed.ref() to %.loc41_14.1 +// CHECK:STDOUT: %.loc41_14.2: ref %D = temporary %.loc41_14.1, %ReturnDUsed.call // CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/lower/file_context.cpp b/toolchain/lower/file_context.cpp index 4c3635b5aeb8..af1099663c76 100644 --- a/toolchain/lower/file_context.cpp +++ b/toolchain/lower/file_context.cpp @@ -13,6 +13,7 @@ #include "toolchain/sem_ir/entry_point.h" #include "toolchain/sem_ir/file.h" #include "toolchain/sem_ir/function.h" +#include "toolchain/sem_ir/ids.h" #include "toolchain/sem_ir/inst.h" #include "toolchain/sem_ir/typed_insts.h" @@ -136,23 +137,27 @@ auto FileContext::BuildFunctionDecl(SemIR::FunctionId function_id) return nullptr; } - // Don't lower unused functions. - if (function.return_slot == SemIR::Function::ReturnSlot::NotComputed) { - return nullptr; - } + // TODO: Consider tracking whether the function has been used, and only + // lowering it if it's needed. + + // TODO: Pass in a specific ID for generic functions. + const auto specific_id = SemIR::SpecificId::Invalid; + + const auto return_info = function.GetReturnInfo(sem_ir(), specific_id); + CARBON_CHECK(return_info.is_valid()) << "Should not lower invalid functions."; - const bool has_return_slot = function.has_return_slot(); auto implicit_param_refs = sem_ir().inst_blocks().GetOrEmpty(function.implicit_param_refs_id); // TODO: Include parameters corresponding to positional parameters. auto param_refs = sem_ir().inst_blocks().GetOrEmpty(function.param_refs_id); - auto return_type_id = function.GetDeclaredReturnType(sem_ir()); + auto* return_type = + return_info.type_id.is_valid() ? GetType(return_info.type_id) : nullptr; SemIR::InitRepr return_rep = - return_type_id.is_valid() - ? SemIR::GetInitRepr(sem_ir(), return_type_id) + return_info.type_id.is_valid() + ? SemIR::GetInitRepr(sem_ir(), return_info.type_id) : SemIR::InitRepr{.kind = SemIR::InitRepr::None}; - CARBON_CHECK(return_rep.has_return_slot() == has_return_slot); + CARBON_CHECK(return_rep.has_return_slot() == return_info.has_return_slot()); llvm::SmallVector param_types; // TODO: Consider either storing `param_inst_ids` somewhere so that we can @@ -160,12 +165,12 @@ auto FileContext::BuildFunctionDecl(SemIR::FunctionId function_id) // out a mechanism to compute the mapping between parameters and arguments on // demand. llvm::SmallVector param_inst_ids; - auto max_llvm_params = - has_return_slot + implicit_param_refs.size() + param_refs.size(); + auto max_llvm_params = (return_info.has_return_slot() ? 1 : 0) + + implicit_param_refs.size() + param_refs.size(); param_types.reserve(max_llvm_params); param_inst_ids.reserve(max_llvm_params); - if (has_return_slot) { - param_types.push_back(GetType(return_type_id)->getPointerTo()); + if (return_info.has_return_slot()) { + param_types.push_back(return_type->getPointerTo()); param_inst_ids.push_back(function.return_storage_id); } for (auto param_ref_id : @@ -189,11 +194,12 @@ auto FileContext::BuildFunctionDecl(SemIR::FunctionId function_id) } } - // If the initializing representation doesn't produce a value, set the return - // type to void. - llvm::Type* return_type = return_rep.kind == SemIR::InitRepr::ByCopy - ? GetType(return_type_id) - : llvm::Type::getVoidTy(llvm_context()); + // Compute the return type to use for the LLVM function. If the initializing + // representation doesn't produce a value, set the return type to void. + llvm::Type* function_return_type = + return_rep.kind == SemIR::InitRepr::ByCopy + ? return_type + : llvm::Type::getVoidTy(llvm_context()); std::string mangled_name; if (SemIR::IsEntryPoint(sem_ir(), function_id)) { @@ -208,8 +214,8 @@ auto FileContext::BuildFunctionDecl(SemIR::FunctionId function_id) << function.name_id; } - llvm::FunctionType* function_type = - llvm::FunctionType::get(return_type, param_types, /*isVarArg=*/false); + llvm::FunctionType* function_type = llvm::FunctionType::get( + function_return_type, param_types, /*isVarArg=*/false); auto* llvm_function = llvm::Function::Create(function_type, llvm::Function::ExternalLinkage, mangled_name, llvm_module()); @@ -220,8 +226,8 @@ auto FileContext::BuildFunctionDecl(SemIR::FunctionId function_id) auto name_id = SemIR::NameId::Invalid; if (inst_id == function.return_storage_id) { name_id = SemIR::NameId::ReturnSlot; - arg.addAttr(llvm::Attribute::getWithStructRetType( - llvm_context(), GetType(return_type_id))); + arg.addAttr( + llvm::Attribute::getWithStructRetType(llvm_context(), return_type)); } else { name_id = SemIR::Function::GetParamFromParamRefId(sem_ir(), inst_id) .second.name_id; @@ -250,7 +256,8 @@ auto FileContext::BuildFunctionDefinition(SemIR::FunctionId function_id) FunctionContext function_lowering(*this, llvm_function, vlog_stream_); - const bool has_return_slot = function.has_return_slot(); + // TODO: Pass in a specific ID for generic functions. + const auto specific_id = SemIR::SpecificId::Invalid; // Add parameters to locals. // TODO: This duplicates the mapping between sem_ir instructions and LLVM @@ -260,7 +267,7 @@ auto FileContext::BuildFunctionDefinition(SemIR::FunctionId function_id) sem_ir().inst_blocks().GetOrEmpty(function.implicit_param_refs_id); auto param_refs = sem_ir().inst_blocks().GetOrEmpty(function.param_refs_id); int param_index = 0; - if (has_return_slot) { + if (function.GetReturnInfo(sem_ir(), specific_id).has_return_slot()) { function_lowering.SetLocal(function.return_storage_id, llvm_function->getArg(param_index)); ++param_index; diff --git a/toolchain/sem_ir/formatter.cpp b/toolchain/sem_ir/formatter.cpp index a158a9a1e9df..06da042dae7b 100644 --- a/toolchain/sem_ir/formatter.cpp +++ b/toolchain/sem_ir/formatter.cpp @@ -306,7 +306,9 @@ class FormatterImpl { if (fn.return_storage_id.is_valid()) { out_ << " -> "; - if (!fn.body_block_ids.empty() && fn.has_return_slot()) { + auto return_info = fn.GetReturnInfo(sem_ir_); + if (!fn.body_block_ids.empty() && return_info.is_valid() && + return_info.has_return_slot()) { FormatName(fn.return_storage_id); out_ << ": "; } @@ -710,7 +712,8 @@ class FormatterImpl { llvm::ArrayRef args = sem_ir_.inst_blocks().Get(inst.args_id); - bool has_return_slot = GetInitRepr(sem_ir_, inst.type_id).has_return_slot(); + auto return_info = ReturnInfo::ForType(sem_ir_, inst.type_id); + bool has_return_slot = return_info.has_return_slot(); InstId return_slot_id = InstId::Invalid; if (has_return_slot) { return_slot_id = args.back(); diff --git a/toolchain/sem_ir/function.cpp b/toolchain/sem_ir/function.cpp index fae56107f13e..11aa49995759 100644 --- a/toolchain/sem_ir/function.cpp +++ b/toolchain/sem_ir/function.cpp @@ -51,4 +51,20 @@ auto Function::GetDeclaredReturnType(const File& file, file.insts().Get(return_storage_id).type_id()); } +auto ReturnInfo::ForType(const File& file, TypeId type_id) -> ReturnInfo { + if (!type_id.is_valid()) { + // Implicit `-> ()` has no return slot. + return {.type_id = type_id, .return_slot = ReturnSlot::Absent}; + } + + if (!file.types().IsComplete(type_id)) { + return {.type_id = type_id, .return_slot = ReturnSlot::Incomplete}; + } + + return {.type_id = type_id, + .return_slot = GetInitRepr(file, type_id).has_return_slot() + ? SemIR::ReturnSlot::Present + : SemIR::ReturnSlot::Absent}; +} + } // namespace Carbon::SemIR diff --git a/toolchain/sem_ir/function.h b/toolchain/sem_ir/function.h index 2eeb8be3011f..5bc1e824f489 100644 --- a/toolchain/sem_ir/function.h +++ b/toolchain/sem_ir/function.h @@ -12,22 +12,43 @@ namespace Carbon::SemIR { +// A value that describes whether the function uses a return slot. +enum class ReturnSlot : int8_t { + // The function is known to not use a return slot. + Absent, + // The function has a return slot, and a call to the function is expected to + // have an additional final argument corresponding to the return slot. + Present, + // Computing whether the function should have a return slot failed because + // the return type was incomplete. + Incomplete, +}; + +// Information about how a function returns its return value. +struct ReturnInfo { + // Builds return information for a given declared return type. + static auto ForType(const File& file, TypeId type_id) -> ReturnInfo; + + // Returns whether the return information could be fully computed. + auto is_valid() const -> bool { + return return_slot != ReturnSlot::Incomplete; + } + + // Returns whether the function has a return slot. Can only be called for + // valid return info. + auto has_return_slot() const -> bool { + CARBON_CHECK(is_valid()); + return return_slot == ReturnSlot::Present; + } + + // The return type. Invalid if no return type was specified. + TypeId type_id; + // The return slot usage for this function. + ReturnSlot return_slot; +}; + // Function-specific fields. struct FunctionFields { - // A value that describes whether the function uses a return slot. - enum class ReturnSlot : int8_t { - // Not yet known: the function has not been called or defined. - NotComputed, - // The function is known to not use a return slot. - Absent, - // The function has a return slot, and a call to the function is expected to - // have an additional final argument corresponding to the return slot. - Present, - // Computing whether the function should have a return slot failed, for - // example because the return type was incomplete. - Error - }; - // The following members always have values, and do not change throughout the // lifetime of the function. @@ -42,10 +63,6 @@ struct FunctionFields { // The following member is set on the first call to the function, or at the // point where the function is defined. - // Whether the function uses a return slot. For a generic function, this - // tracks information about the generic, not a specific. - ReturnSlot return_slot; - // The following members are set at the end of a builtin function definition. // If this is a builtin function, the corresponding builtin kind. @@ -68,21 +85,6 @@ struct Function : public EntityWithParamsBase, PrintBaseFields(out); if (return_storage_id.is_valid()) { out << ", return_storage: " << return_storage_id; - out << ", return_slot: "; - switch (return_slot) { - case ReturnSlot::NotComputed: - out << "unknown"; - break; - case ReturnSlot::Absent: - out << "absent"; - break; - case ReturnSlot::Present: - out << "present"; - break; - case ReturnSlot::Error: - out << "error"; - break; - } } if (!body_block_ids.empty()) { out << llvm::formatv( @@ -106,18 +108,11 @@ struct Function : public EntityWithParamsBase, SpecificId specific_id = SpecificId::Invalid) const -> TypeId; - // Returns whether the function has a return slot. Can only be called for a - // function that has either been called or defined, otherwise this is not - // known. - // - // For a generic function, this only returns information about the generic - // itself, not a specific. Because a generic function can't be called (only a - // specific can be), this information is only available for generic functions - // that are defined. - auto has_return_slot() const -> bool { - CARBON_CHECK(return_slot != ReturnSlot::NotComputed); - // On error, we assume no return slot is used. - return return_slot == ReturnSlot::Present; + // Returns information about how the function returns its return value. + auto GetReturnInfo(const File& file, + SpecificId specific_id = SpecificId::Invalid) const + -> ReturnInfo { + return ReturnInfo::ForType(file, GetDeclaredReturnType(file, specific_id)); } };