From 14e4f219b17d4659bad936515ec9ab813bd9146e Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Mon, 2 Jun 2025 14:14:34 -0700 Subject: [PATCH] Support lowering specifics for an imported generic function. (#5475) When lowering a specific function whose generic was defined in a different file, switch to that other file's `FileContext` and lower the generic there. Also pass the `FileContext` corresponding to the specific into the `FunctionContext`, and use that `FileContext` for resolving requests for constants and types from the specific. --- toolchain/lower/context.cpp | 14 + toolchain/lower/context.h | 22 ++ toolchain/lower/file_context.cpp | 290 +++++++++++------- toolchain/lower/file_context.h | 71 +++-- toolchain/lower/function_context.cpp | 26 +- toolchain/lower/function_context.h | 56 +++- toolchain/lower/handle.cpp | 4 +- toolchain/lower/handle_call.cpp | 74 +++-- toolchain/lower/mangler.cpp | 6 + ...ross_library_name_collision_private.carbon | 120 ++++++++ .../testdata/function/generic/import.carbon | 171 +++++++++++ toolchain/sem_ir/generic.cpp | 62 +++- toolchain/sem_ir/generic.h | 16 + 13 files changed, 741 insertions(+), 191 deletions(-) create mode 100644 toolchain/lower/testdata/function/generic/cross_library_name_collision_private.carbon create mode 100644 toolchain/lower/testdata/function/generic/import.carbon diff --git a/toolchain/lower/context.cpp b/toolchain/lower/context.cpp index f7296bfc8b26..dba952eb7fc1 100644 --- a/toolchain/lower/context.cpp +++ b/toolchain/lower/context.cpp @@ -41,7 +41,21 @@ auto Context::GetFileContext(const SemIR::File* file, return *insert_result.value(); } +auto Context::LowerPendingDefinitions() -> void { + // Lower function definitions for generics. + // This cannot be a range-based loop, as new definitions can be added + // while building other definitions. + // NOLINTNEXTLINE(modernize-loop-convert) + for (size_t i = 0; i != specific_function_definitions_.size(); ++i) { + auto [file_context, function_id, specific_id] = + specific_function_definitions_[i]; + file_context->BuildFunctionDefinition(function_id, specific_id); + } +} + auto Context::Finalize() && -> std::unique_ptr { + LowerPendingDefinitions(); + file_contexts_.ForEach( [](auto, auto& file_context) { file_context->Finalize(); }); return std::move(llvm_module_); diff --git a/toolchain/lower/context.h b/toolchain/lower/context.h index 536b05df71a7..364d5d261090 100644 --- a/toolchain/lower/context.h +++ b/toolchain/lower/context.h @@ -34,6 +34,13 @@ class Context { int32_t column_number; }; + // A specific function whose definition needs to be lowered. + struct PendingSpecificFunctionDefinition { + FileContext* context; + SemIR::FunctionId function_id; + SemIR::SpecificId specific_id; + }; + explicit Context(llvm::LLVMContext& llvm_context, llvm::IntrusiveRefCntPtr fs, std::optional> @@ -48,6 +55,12 @@ class Context { const SemIR::InstNamer* inst_namer = nullptr) -> FileContext&; + // Registers a specific function definition to be lowered later. + auto AddPendingSpecificFunctionDefinition( + PendingSpecificFunctionDefinition pending) -> void { + specific_function_definitions_.push_back(pending); + } + // Finishes lowering and takes ownership of the LLVM module. The context // cannot be used further after calling this. auto Finalize() && -> std::unique_ptr; @@ -98,6 +111,10 @@ class Context { llvm::Module& llvm_module, llvm::DIBuilder& di_builder) -> llvm::DICompileUnit*; + // Lower any definitions that have been registered for later lowering. + // Currently, this lowers specifics for generic functions. + auto LowerPendingDefinitions() -> void; + // State for building the LLVM IR. llvm::LLVMContext* llvm_context_; std::unique_ptr llvm_module_; @@ -126,6 +143,11 @@ class Context { // Global format string for `printf.int.format` used by the PrintInt builtin. llvm::Value* printf_int_format_string_ = nullptr; + + // Tracks which specific functions need to have their definitions lowered. + // This list may grow while lowering generic definitions from this list. + llvm::SmallVector + specific_function_definitions_; }; } // namespace Carbon::Lower diff --git a/toolchain/lower/file_context.cpp b/toolchain/lower/file_context.cpp index b341204393b9..1fc54f43f1e4 100644 --- a/toolchain/lower/file_context.cpp +++ b/toolchain/lower/file_context.cpp @@ -108,9 +108,9 @@ auto FileContext::LowerDefinitions() -> void { // constant unless the variable is unnamed, in which case we need to // create it now. llvm::GlobalVariable* llvm_var = nullptr; - if (sem_ir().constant_values().Get(inst_id).is_constant()) { - llvm_var = cast( - GetGlobal(inst_id, SemIR::SpecificId::None)); + if (auto const_id = sem_ir().constant_values().Get(inst_id); + const_id.is_constant()) { + llvm_var = cast(GetConstant(const_id, inst_id)); } else { llvm_var = BuildGlobalVariableDecl(*var); } @@ -124,27 +124,29 @@ auto FileContext::LowerDefinitions() -> void { } // Lower function definitions. - for (auto [id, _] : sem_ir_->functions().enumerate()) { - BuildFunctionDefinition(id); - } - - // Lower function definitions for generics. - // This cannot be a range-based loop, as new definitions can be added - // while building other definitions. - // NOLINTNEXTLINE - for (size_t i = 0; i != specific_function_definitions_.size(); ++i) { - auto [function_id, specific_id] = specific_function_definitions_[i]; - BuildFunctionDefinition(function_id, specific_id); + for (auto [id, fn_info] : sem_ir_->functions().enumerate()) { + // If we created a declaration and the function definition is not imported, + // build a definition. + if (functions_[id.index] && fn_info.definition_id.has_value() && + !sem_ir().insts().GetImportSource(fn_info.definition_id).has_value()) { + BuildFunctionDefinition(id); + } } // Append `__global_init` to `llvm::global_ctors` to initialize global // variables. - if (sem_ir().global_ctor_id().has_value()) { + if (auto global_ctor_id = sem_ir().global_ctor_id(); + global_ctor_id.has_value()) { + const auto& global_ctor = sem_ir().functions().Get(global_ctor_id); + BuildFunctionBody(global_ctor_id, SemIR::SpecificId::None, global_ctor, + *this, global_ctor); llvm::appendToGlobalCtors(llvm_module(), GetFunction(sem_ir().global_ctor_id()), /*Priority=*/0); } +} +auto FileContext::Finalize() -> void { if (cpp_code_generator_) { // Clang code generation should not actually modify the AST, but isn't // const-correct. @@ -156,9 +158,7 @@ auto FileContext::LowerDefinitions() -> void { cpp_code_generator_->ReleaseModule())); CARBON_CHECK(!link_error); } -} -auto FileContext::Finalize() -> void { // Find equivalent specifics (from the same generic), replace all uses and // remove duplicately lowered function definitions. CoalesceEquivalentSpecifics(); @@ -406,11 +406,8 @@ auto FileContext::CreateCppCodeGenerator() cpp_code_gen_options_, llvm_context())); } -auto FileContext::GetGlobal(SemIR::InstId inst_id, - SemIR::SpecificId specific_id) -> llvm::Value* { - auto const_id = GetConstantValueInSpecific(sem_ir(), specific_id, inst_id); - CARBON_CHECK(const_id.is_concrete(), "Missing value: {0} {1} {2}", inst_id, - specific_id, sem_ir().insts().Get(inst_id)); +auto FileContext::GetConstant(SemIR::ConstantId const_id, + SemIR::InstId use_inst_id) -> llvm::Value* { auto const_inst_id = sem_ir().constant_values().GetInstId(const_id); auto* const_value = constants_[const_inst_id.index]; @@ -452,7 +449,9 @@ auto FileContext::GetGlobal(SemIR::InstId inst_id, llvm::StringRef use_name; if (inst_namer_) { const_name = inst_namer_->GetUnscopedNameFor(const_inst_id); - use_name = inst_namer_->GetUnscopedNameFor(inst_id); + if (use_inst_id.has_value()) { + use_name = inst_namer_->GetUnscopedNameFor(use_inst_id); + } } // We always need to give the global a name even if the instruction namer @@ -477,22 +476,12 @@ auto FileContext::GetGlobal(SemIR::InstId inst_id, auto FileContext::GetOrCreateFunction(SemIR::FunctionId function_id, SemIR::SpecificId specific_id) -> llvm::Function* { - // Non-generic functions are declared eagerly. - if (!specific_id.has_value()) { - return GetFunction(function_id); + // If we have already lowered a declaration of this function, just return it. + auto** result = GetFunctionAddr(function_id, specific_id); + if (!*result) { + *result = BuildFunctionDecl(function_id, specific_id); } - - if (auto* result = specific_functions_[specific_id.index]) { - return result; - } - - auto* result = BuildFunctionDecl(function_id, specific_id); - // TODO: Add this function to a list of specific functions whose definitions - // we need to emit. - specific_functions_[specific_id.index] = result; - // TODO: Use this to generate definitions for these functions. - specific_function_definitions_.push_back({function_id, specific_id}); - return result; + return *result; } auto FileContext::BuildFunctionTypeInfo(const SemIR::Function& function, @@ -591,6 +580,55 @@ auto FileContext::BuildFunctionTypeInfo(const SemIR::Function& function, .return_param_id = return_param_id}; } +auto FileContext::HandleReferencedCppFunction(clang::FunctionDecl* cpp_decl) + -> void { + // TODO: To support recursive inline functions, collect all calls to + // `HandleTopLevelDecl()` in a custom `ASTConsumer` configured in the + // `ASTUnit`, and replay them in lowering in the `CodeGenerator`. See + // https://discord.com/channels/655572317891461132/768530752592805919/1370509111585935443 + clang::FunctionDecl* cpp_def = cpp_decl->getDefinition(); + if (!cpp_def) { + return; + } + + // Create the LLVM function (`CodeGenModule::GetOrCreateLLVMFunction()`) + // so that code generation (`CodeGenModule::EmitGlobal()`) would see this + // function name (`CodeGenModule::getMangledName()`), and will generate + // its definition. + llvm::Constant* function_address = + cpp_code_generator_->GetAddrOfGlobal(clang::GlobalDecl(cpp_def), + /*isForDefinition=*/false); + CARBON_CHECK(function_address); + + // Emit the function code. + cpp_code_generator_->HandleTopLevelDecl(clang::DeclGroupRef(cpp_def)); +} + +auto FileContext::HandleReferencedSpecificFunction( + SemIR::FunctionId function_id, SemIR::SpecificId specific_id, + llvm::Type* llvm_type) -> void { + CARBON_CHECK(specific_id.has_value()); + + // Add this specific function to a list of specific functions whose + // definitions we need to emit. + // TODO: Don't do this if we know this function is emitted as a + // non-discardable symbol in the IR for some other file. + context().AddPendingSpecificFunctionDefinition({.context = this, + .function_id = function_id, + .specific_id = specific_id}); + + // Create a unique fingerprint for the function type. + // For now, we compute the function type fingerprint only for specifics, + // though we might need it for all functions in order to create a canonical + // fingerprint across translation units. + llvm::BLAKE3 function_type_fingerprint; + RawStringOstream os; + llvm_type->print(os); + function_type_fingerprint.update(os.TakeStr()); + function_type_fingerprint.final( + lowered_specifics_type_fingerprint_[specific_id.index]); +} + auto FileContext::BuildFunctionDecl(SemIR::FunctionId function_id, SemIR::SpecificId specific_id) -> llvm::Function* { @@ -612,23 +650,39 @@ auto FileContext::BuildFunctionDecl(SemIR::FunctionId function_id, auto function_type_info = BuildFunctionTypeInfo(function, specific_id); + // TODO: For an imported inline function, consider generating an + // `available_externally` definition. auto linkage = specific_id.has_value() ? llvm::Function::LinkOnceODRLinkage : llvm::Function::ExternalLinkage; Mangler m(*this); std::string mangled_name = m.Mangle(function_id, specific_id); + if (auto* existing = llvm_module().getFunction(mangled_name)) { + // We might have already lowered this function while lowering a different + // file. That's OK. + // TODO: Check-fail or maybe diagnose if the two LLVM functions are not + // produced by declarations of the same Carbon function. Name collisions + // between non-private members of the same library should have been + // diagnosed by check if detected, but it's not clear that check will always + // be able to see this problem. In theory, name collisions could also occur + // due to fingerprint collision. + return existing; + } - // Create a unique fingerprint for the function type. - // For now, compute the function type fingerprint only for specifics, though - // we might need it for all functions in order to create a canonical - // fingerprint across translation units. + // If this is a C++ function, tell Clang that we referenced it. + if (auto* cpp_decl = sem_ir().functions().Get(function_id).cpp_decl) { + CARBON_CHECK(!specific_id.has_value(), + "Specific functions cannot have C++ definitions"); + HandleReferencedCppFunction(cpp_decl); + // TODO: Check that the signature and mangling generated by Clang and the + // one we generated are the same. + } + + // If this is a specific function, we may need to do additional work to emit + // its definition. if (specific_id.has_value()) { - llvm::BLAKE3 function_type_fingerprint; - RawStringOstream os; - function_type_info.type->print(os); - function_type_fingerprint.update(os.TakeStr()); - function_type_fingerprint.final( - lowered_specifics_type_fingerprint_[specific_id.index]); + HandleReferencedSpecificFunction(function_id, specific_id, + function_type_info.type); } auto* llvm_function = llvm::Function::Create(function_type_info.type, linkage, @@ -654,84 +708,90 @@ auto FileContext::BuildFunctionDecl(SemIR::FunctionId function_id, return llvm_function; } +// Find the file and function ID describing the definition of a function. +static auto GetFunctionDefinition(const SemIR::File* decl_ir, + SemIR::FunctionId function_id) + -> std::pair { + // Find the file containing the definition. + auto decl_id = decl_ir->functions().Get(function_id).definition_id; + if (!decl_id.has_value()) { + // Function is not defined. + return {nullptr, SemIR::FunctionId::None}; + } + + // Find the function declaration this function was originally imported from. + while (true) { + auto import_inst_id = decl_ir->insts().GetImportSource(decl_id); + if (!import_inst_id.has_value()) { + break; + } + auto import_inst = decl_ir->import_ir_insts().Get(import_inst_id); + decl_ir = decl_ir->import_irs().Get(import_inst.ir_id()).sem_ir; + decl_id = import_inst.inst_id(); + } + + auto decl_ir_function_id = + decl_ir->insts().GetAs(decl_id).function_id; + return {decl_ir, decl_ir_function_id}; +} + auto FileContext::BuildFunctionDefinition(SemIR::FunctionId function_id, SemIR::SpecificId specific_id) -> void { - const auto& function = sem_ir().functions().Get(function_id); - const auto& body_block_ids = function.body_block_ids; - if (body_block_ids.empty() && - (!function.cpp_decl || !function.cpp_decl->isDefined())) { + auto [definition_ir, definition_ir_function_id] = + GetFunctionDefinition(&sem_ir(), function_id); + if (!definition_ir) { // Function is probably defined in another file; not an error. return; } - llvm::Function* llvm_function; - if (specific_id.has_value()) { - llvm_function = specific_functions_[specific_id.index]; - } else { - llvm_function = GetFunction(function_id); - if (!llvm_function) { - // We chose not to lower this function at all, for example because it's a - // generic function. - return; - } - } - - // For non-generics we do not lower. For generics, the llvm function was - // created via GetOrCreateFunction prior to this when building the - // declaration. - BuildFunctionBody(function_id, function, llvm_function, specific_id); + const auto& definition_function = + definition_ir->functions().Get(definition_ir_function_id); + BuildFunctionBody( + function_id, specific_id, sem_ir().functions().Get(function_id), + context().GetFileContext(definition_ir), definition_function); } auto FileContext::BuildFunctionBody(SemIR::FunctionId function_id, - const SemIR::Function& function, - llvm::Function* llvm_function, - SemIR::SpecificId specific_id) -> void { - const auto& body_block_ids = function.body_block_ids; - CARBON_DCHECK(llvm_function, "LLVM Function not found when lowering body."); + SemIR::SpecificId specific_id, + const SemIR::Function& declaration_function, + FileContext& definition_context, + const SemIR::Function& definition_function) + -> void { + // Note that `definition_function` is potentially from a different SemIR::File + // than the one that this file context represents. Any lowering done for + // values derived from `definition_function` should use `definition_context` + // instead of our context. + const auto& definition_ir = definition_context.sem_ir(); - if (function.cpp_decl) { - // TODO: To support recursive inline functions, collect all calls to - // `HandleTopLevelDecl()` in a custom `ASTConsumer` configured in the - // `ASTUnit`, and replay them in lowering in the `CodeGenerator`. See - // https://discord.com/channels/655572317891461132/768530752592805919/1370509111585935443 - clang::FunctionDecl* cpp_def = function.cpp_decl->getDefinition(); - CARBON_DCHECK(cpp_def, "No Clang function body found during lowering"); - - // Create the LLVM function (`CodeGenModule::GetOrCreateLLVMFunction()`) so - // that code generation (`CodeGenModule::EmitGlobal()`) would see this - // function name (`CodeGenModule::getMangledName()`), and will generate its - // definition. - llvm::Constant* function_address = - cpp_code_generator_->GetAddrOfGlobal(clang::GlobalDecl(cpp_def), - /*isForDefinition=*/false); - CARBON_DCHECK(function_address); - - // Emit the function code. - cpp_code_generator_->HandleTopLevelDecl(clang::DeclGroupRef(cpp_def)); - return; - } + auto* llvm_function = GetFunction(function_id, specific_id); + CARBON_CHECK(llvm_function, + "Attempting to define function that was not declared"); + const auto& body_block_ids = definition_function.body_block_ids; CARBON_DCHECK(!body_block_ids.empty(), "No function body blocks found during lowering."); // Store which specifics were already lowered (with definitions) for each // generic. - if (function.generic_id.has_value() && specific_id.has_value()) { - AddLoweredSpecificForGeneric(function.generic_id, specific_id); + if (declaration_function.generic_id.has_value() && specific_id.has_value()) { + // TODO: We should track this in the definition context instead so that we + // can deduplicate specifics from different files. + AddLoweredSpecificForGeneric(declaration_function.generic_id, specific_id); } FunctionContext function_lowering( - *this, llvm_function, specific_id, + definition_context, llvm_function, *this, specific_id, InitializeFingerprintForSpecific(specific_id), - BuildDISubprogram(function, llvm_function), vlog_stream_); + definition_context.BuildDISubprogram(definition_function, llvm_function), + vlog_stream_); // Add parameters to locals. // TODO: This duplicates the mapping between sem_ir instructions and LLVM // function parameters that was already computed in BuildFunctionDecl. // We should only do that once. - auto call_param_ids = - sem_ir().inst_blocks().GetOrEmpty(function.call_params_id); + auto call_param_ids = definition_ir.inst_blocks().GetOrEmpty( + definition_function.call_params_id); int param_index = 0; // TODO: Find a way to ensure this code and the function-call lowering use @@ -741,16 +801,16 @@ auto FileContext::BuildFunctionBody(SemIR::FunctionId function_id, // parameter order. auto lower_param = [&](SemIR::InstId param_id) { // Get the value of the parameter from the function argument. - auto param_inst = sem_ir().insts().GetAs(param_id); + auto param_inst = definition_ir.insts().GetAs(param_id); llvm::Value* param_value; - if (SemIR::ValueRepr::ForType(sem_ir(), param_inst.type_id).kind != + if (SemIR::ValueRepr::ForType(definition_ir, param_inst.type_id).kind != SemIR::ValueRepr::None) { param_value = llvm_function->getArg(param_index); ++param_index; } else { - param_value = llvm::PoisonValue::get(GetType( - SemIR::GetTypeOfInstInSpecific(sem_ir(), specific_id, param_id))); + param_value = llvm::PoisonValue::get( + function_lowering.GetTypeOfInstInSpecific(param_id)); } // The value of the parameter is the value of the argument. function_lowering.SetLocal(param_id, param_value); @@ -759,12 +819,13 @@ auto FileContext::BuildFunctionBody(SemIR::FunctionId function_id, // The subset of call_param_ids that is already in the order that the LLVM // calling convention expects. llvm::ArrayRef sequential_param_ids; - if (function.return_slot_pattern_id.has_value()) { + if (declaration_function.return_slot_pattern_id.has_value()) { // The LLVM calling convention has the return slot first rather than last. // Note that this queries whether there is a return slot at the LLVM level, // whereas `function.return_slot_pattern_id.has_value()` queries whether // there is a return slot at the SemIR level. - if (SemIR::ReturnTypeInfo::ForFunction(sem_ir(), function, specific_id) + if (SemIR::ReturnTypeInfo::ForFunction(sem_ir(), declaration_function, + specific_id) .has_return_slot()) { lower_param(call_param_ids.back()); } @@ -781,13 +842,13 @@ auto FileContext::BuildFunctionBody(SemIR::FunctionId function_id, if (function_id == sem_ir().global_ctor_id()) { decl_block_id = SemIR::InstBlockId::Empty; } else { - decl_block_id = sem_ir() - .insts() - .GetAs(function.latest_decl_id()) - .decl_block_id; + decl_block_id = + definition_ir.insts() + .GetAs(definition_function.latest_decl_id()) + .decl_block_id; } - // Lowers the contents of block_id into the corresponding LLVM block, + // Lowers the contents of decl_block_id into the corresponding LLVM block, // creating it if it doesn't already exist. auto lower_block = [&](SemIR::InstBlockId block_id) { CARBON_VLOG("Lowering {0}\n", block_id); @@ -1075,9 +1136,10 @@ auto FileContext::BuildVtable(const SemIR::Class& class_info) Mangler m(*this); std::string mangled_name = m.MangleVTable(class_info); - auto first_owning_decl_loc = - sem_ir().insts().GetCanonicalLocId(class_info.first_owning_decl_id); - if (first_owning_decl_loc.kind() == SemIR::LocId::Kind::ImportIRInstId) { + if (sem_ir() + .insts() + .GetImportSource(class_info.first_owning_decl_id) + .has_value()) { // Emit a declaration of an imported vtable using a(n opaque) pointer type. // This doesn't have to match the definition that appears elsewhere, it'll // still get merged correctly. diff --git a/toolchain/lower/file_context.h b/toolchain/lower/file_context.h index f2efb24674a9..fc7c8df8e00a 100644 --- a/toolchain/lower/file_context.h +++ b/toolchain/lower/file_context.h @@ -55,9 +55,12 @@ class FileContext { // Perform final cleanup tasks once all lowering has been completed. auto Finalize() -> void; - // Gets a callable's function. Returns nullptr for a builtin. - auto GetFunction(SemIR::FunctionId function_id) -> llvm::Function* { - return functions_[function_id.index]; + // Gets a callable's function. Returns nullptr for a builtin or a function we + // have not lowered. + auto GetFunction(SemIR::FunctionId function_id, + SemIR::SpecificId specific_id = SemIR::SpecificId::None) + -> llvm::Function* { + return *GetFunctionAddr(function_id, specific_id); } // Gets a or creates callable's function. Returns nullptr for a builtin. @@ -87,8 +90,9 @@ class FileContext { return context().GetIntLiteralAsValue(); } - // Returns a global value for the given instruction. - auto GetGlobal(SemIR::InstId inst_id, SemIR::SpecificId specific_id) + // Returns a value for the given constant. If specified, `use_inst_id` is the + // instruction that is using this constant. + auto GetConstant(SemIR::ConstantId const_id, SemIR::InstId use_inst_id) -> llvm::Value*; // Returns the empty LLVM struct type used to represent the type `type`. @@ -129,13 +133,6 @@ class FileContext { auto BuildGlobalVariableDecl(SemIR::VarStorage var_storage) -> llvm::GlobalVariable*; - private: - // Builds the declaration for the given function, which should then be cached - // by the caller. - auto BuildFunctionDecl(SemIR::FunctionId function_id, - SemIR::SpecificId specific_id = - SemIR::SpecificId::None) -> llvm::Function*; - // Builds the definition for the given function. If the function is only a // declaration with no definition, does nothing. If this is a generic it'll // only be lowered if the specific_id is specified. During this lowering of @@ -144,11 +141,44 @@ class FileContext { SemIR::FunctionId function_id, SemIR::SpecificId specific_id = SemIR::SpecificId::None) -> void; - // Builds a functions body. Common functionality for all functions. - auto BuildFunctionBody( - SemIR::FunctionId function_id, const SemIR::Function& function, - llvm::Function* llvm_function, - SemIR::SpecificId specific_id = SemIR::SpecificId::None) -> void; + private: + // Gets the location in which a callable's function is stored. + auto GetFunctionAddr(SemIR::FunctionId function_id, + SemIR::SpecificId specific_id) -> llvm::Function** { + return specific_id.has_value() ? &specific_functions_[specific_id.index] + : &functions_[function_id.index]; + } + + // Notes that a C++ function has been referenced for the first time, so we + // should ask Clang to generate a definition for it if possible. + auto HandleReferencedCppFunction(clang::FunctionDecl* cpp_decl) -> void; + + // Notes that a specific function has been referenced for the first time. + // Updates the fingerprint to include the function's type, and adds the + // function to the list of specific functions whose definitions should be + // lowered. + auto HandleReferencedSpecificFunction(SemIR::FunctionId function_id, + SemIR::SpecificId specific_id, + llvm::Type* llvm_type) -> void; + + // Builds the declaration for the given function, which should then be cached + // by the caller. + auto BuildFunctionDecl(SemIR::FunctionId function_id, + SemIR::SpecificId specific_id = + SemIR::SpecificId::None) -> llvm::Function*; + + // Builds a function's body. Common functionality for all functions. + // + // The `function_id` and `specific_id` identify the function within this + // context's file. If the function was defined in a different file, + // `definition_context` is a `FileContext` for that other file. + // `definition_function` is the `Function` object within the file that owns + // the definition. + auto BuildFunctionBody(SemIR::FunctionId function_id, + SemIR::SpecificId specific_id, + const SemIR::Function& declaration_function, + FileContext& definition_context, + const SemIR::Function& definition_function) -> void; // Build the DISubprogram metadata for the given function. auto BuildDISubprogram(const SemIR::Function& function, @@ -260,13 +290,6 @@ class FileContext { // `SpecificId` indexes. We resize this directly to the correct size. llvm::SmallVector specific_functions_; - // Maps which specific functions are generics that need to have their - // definitions lowered after the lowering of other definitions. - // This list may grow while lowering generic definitions from this list. - // The list uses the `SpecificId` to index into specific_functions_. - llvm::SmallVector, 10> - specific_function_definitions_; - // Provides lowered versions of types. // Vector indexes correspond to `TypeId` indexes for non-symbolic types. We // resize this directly to the (often large) correct size. diff --git a/toolchain/lower/function_context.cpp b/toolchain/lower/function_context.cpp index 1cfaaa181b19..0efed382e316 100644 --- a/toolchain/lower/function_context.cpp +++ b/toolchain/lower/function_context.cpp @@ -13,11 +13,12 @@ namespace Carbon::Lower { FunctionContext::FunctionContext( FileContext& file_context, llvm::Function* function, - SemIR::SpecificId specific_id, + FileContext& specific_file_context, SemIR::SpecificId specific_id, FileContext::SpecificFunctionFingerprint* function_fingerprint, llvm::DISubprogram* di_subprogram, llvm::raw_ostream* vlog_stream) : file_context_(&file_context), function_(function), + specific_file_context_(&specific_file_context), specific_id_(specific_id), builder_(file_context.llvm_context(), llvm::ConstantFolder(), Inserter(file_context.inst_namer())), @@ -158,7 +159,16 @@ auto FunctionContext::GetValue(SemIR::InstId inst_id) -> llvm::Value* { return result.value(); } - auto* global = file_context_->GetGlobal(inst_id, specific_id_); + auto [const_ir, const_id] = GetConstantValueInSpecific( + specific_sem_ir(), specific_id_, sem_ir(), inst_id); + CARBON_CHECK(const_ir == &sem_ir() || const_ir == &specific_sem_ir()); + CARBON_CHECK(const_id.is_concrete(), + "Missing value: {0} {1} in {2} has non-concrete value {3}", + inst_id, sem_ir().insts().Get(inst_id), specific_id_, const_id); + // We can only pass on the InstId if it refers to the file in which the + // constant value was provided. + auto* global = GetFileContext(const_ir).GetConstant( + const_id, const_ir == &sem_ir() ? inst_id : SemIR::InstId::None); AddGlobalToCurrentFingerprint(global); return global; } @@ -208,8 +218,10 @@ auto FunctionContext::FinishInit(SemIR::TypeId type_id, SemIR::InstId dest_id, } } -auto FunctionContext::GetTypeOfInst(SemIR::InstId inst_id) -> SemIR::TypeId { - return SemIR::GetTypeOfInstInSpecific(sem_ir(), specific_id(), inst_id); +auto FunctionContext::GetTypeIdOfInstInSpecific(SemIR::InstId inst_id) + -> std::pair { + return SemIR::GetTypeOfInstInSpecific(specific_sem_ir(), specific_id(), + sem_ir(), inst_id); } auto FunctionContext::CopyValue(SemIR::TypeId type_id, SemIR::InstId source_id, @@ -260,7 +272,8 @@ auto FunctionContext::Inserter::InsertHelper( insert_pt); } -auto FunctionContext::AddCallToCurrentFingerprint(SemIR::FunctionId function_id, +auto FunctionContext::AddCallToCurrentFingerprint(SemIR::CheckIRId file_id, + SemIR::FunctionId function_id, SemIR::SpecificId specific_id) -> void { if (!function_fingerprint_) { @@ -268,9 +281,10 @@ auto FunctionContext::AddCallToCurrentFingerprint(SemIR::FunctionId function_id, } RawStringOstream os; - // TODO: Replace index with info that is translation unit independent. + // TODO: Replace indexes with info that is translation unit independent. // Using a string that includes the `FunctionId` string and the index to // avoid possible collisions. This needs revisiting. + os << "file_id" << file_id.index << "\n"; os << "function_id" << function_id.index << "\n"; current_fingerprint_.common_fingerprint.update(os.TakeStr()); // TODO: Replace index with info that is translation unit independent. diff --git a/toolchain/lower/function_context.h b/toolchain/lower/function_context.h index 95fff31fab07..f6e9b057deb8 100644 --- a/toolchain/lower/function_context.h +++ b/toolchain/lower/function_context.h @@ -12,6 +12,7 @@ #include "llvm/IR/Module.h" #include "toolchain/lower/file_context.h" #include "toolchain/sem_ir/file.h" +#include "toolchain/sem_ir/ids.h" namespace Carbon::Lower { @@ -23,7 +24,7 @@ class FunctionContext { // be null (see members). explicit FunctionContext( FileContext& file_context, llvm::Function* function, - SemIR::SpecificId specific_id, + FileContext& specific_file_context, SemIR::SpecificId specific_id, FileContext::SpecificFunctionFingerprint* function_fingerprint, llvm::DISubprogram* di_subprogram, llvm::raw_ostream* vlog_stream); @@ -106,7 +107,18 @@ class FunctionContext { } // Returns the type of the given instruction in the current specific. - auto GetTypeOfInst(SemIR::InstId inst_id) -> SemIR::TypeId; + auto GetTypeOfInstInSpecific(SemIR::InstId inst_id) -> llvm::Type* { + auto [type_file, type_id] = GetTypeIdOfInstInSpecific(inst_id); + auto* type = GetFileContext(type_file).GetType(type_id); + AddTypeToCurrentFingerprint(type); + return type; + } + + // Returns the type of the given instruction in the current specific. + // TODO: Each caller of this should add information to the fingerprint + // indicating what information they used from the type. + auto GetTypeIdOfInstInSpecific(SemIR::InstId inst_id) + -> std::pair; // Returns a lowered value to use for a value of type `type`. auto GetTypeAsValue() -> llvm::Value* { @@ -153,8 +165,10 @@ class FunctionContext { SemIR::InstId source_id) -> void; // When fingerprinting for a specific, adds the call, found in the function - // body, to . - auto AddCallToCurrentFingerprint(SemIR::FunctionId function_id, + // body, to . `function_id` and `specific_id` are + // IDs within the file identified by `function_file_id`. + auto AddCallToCurrentFingerprint(SemIR::CheckIRId file_id, + SemIR::FunctionId function_id, SemIR::SpecificId specific_id) -> void; // When fingerprinting for a specific, adds the type. @@ -164,15 +178,38 @@ class FunctionContext { // is complete. auto EmitFinalFingerprint() -> void; + // Returns the FileContext to use for lowering in the given file. + auto GetFileContext(const SemIR::File* file) -> FileContext& { + // Avoid hash table lookup for the expected files. + if (file == &sem_ir()) { + return *file_context_; + } + if (file == &specific_sem_ir()) { + return *specific_file_context_; + } + return file_context_->context().GetFileContext(file); + } + auto llvm_context() -> llvm::LLVMContext& { return file_context_->llvm_context(); } auto llvm_module() -> llvm::Module& { return file_context_->llvm_module(); } auto llvm_function() -> llvm::Function& { return *function_; } - auto specific_id() -> SemIR::SpecificId { return specific_id_; } auto builder() -> llvm::IRBuilderBase& { return builder_; } auto sem_ir() -> const SemIR::File& { return file_context_->sem_ir(); } + // The file context for the file that `specific_id()` is within. + auto specific_file_context() -> FileContext& { + return *specific_file_context_; + } + // The file that `specific_id()` is within. + auto specific_sem_ir() -> const SemIR::File& { + return specific_file_context_->sem_ir(); + } + // The specific ID for the function that is being lowered. Note that this is + // an ID from `specific_sem_ir()`, not from `sem_ir()`. + auto specific_id() -> SemIR::SpecificId { return specific_id_; } + // TODO: could template on BuiltinFunctionKind if more format // globals are eventually needed. auto printf_int_format_string() -> llvm::Value* { @@ -222,12 +259,19 @@ class FunctionContext { // When fingerprinting for a specific, adds the global. auto AddGlobalToCurrentFingerprint(llvm::Value* global) -> void; - // Context for the overall lowering process. + // Context for lowering in the file that contains this function's + // instructions. FileContext* file_context_; // The IR function we're generating. llvm::Function* function_; + // Context for lowering in the file that contains our `specific_id_`. Note + // that this is a different file than the one referred to by `file_context_` + // if we are lowering a specific that was generated for a generic function + // defined in a different file. + FileContext* specific_file_context_; + // The specific id, if the function is a specific. SemIR::SpecificId specific_id_; diff --git a/toolchain/lower/handle.cpp b/toolchain/lower/handle.cpp index e9bdae118aef..89a41e27e60b 100644 --- a/toolchain/lower/handle.cpp +++ b/toolchain/lower/handle.cpp @@ -286,9 +286,7 @@ auto HandleInst(FunctionContext& context, SemIR::InstId inst_id, auto HandleInst(FunctionContext& context, SemIR::InstId inst_id, SemIR::VarStorage /* inst */) -> void { - auto* type = context.GetType(SemIR::GetTypeOfInstInSpecific( - context.sem_ir(), context.specific_id(), inst_id)); - context.AddTypeToCurrentFingerprint(type); + auto* type = context.GetTypeOfInstInSpecific(inst_id); // Position the first alloca right before the start of the executable code in // the function. diff --git a/toolchain/lower/handle_call.cpp b/toolchain/lower/handle_call.cpp index 5052b14ea37e..559103d1e1ce 100644 --- a/toolchain/lower/handle_call.cpp +++ b/toolchain/lower/handle_call.cpp @@ -64,7 +64,8 @@ static auto GetBuiltinFCmpPredicate(SemIR::BuiltinFunctionKind builtin_kind) // Returns whether the specified instruction has a signed integer type. static auto IsSignedInt(FunctionContext& context, SemIR::InstId int_id) -> bool { - return context.sem_ir().types().IsSignedInt(context.GetTypeOfInst(int_id)); + auto [file, type_id] = context.GetTypeIdOfInstInSpecific(int_id); + return file->types().IsSignedInt(type_id); } // Creates a zext or sext instruction depending on the signedness of the @@ -225,12 +226,13 @@ static auto CreateBinaryOperatorForBuiltin( // arithmetic or logical shift. auto lhs_id = context.sem_ir().inst_blocks().Get( context.sem_ir().insts().GetAs(inst_id).args_id)[0]; - auto lhs_type_id = context.GetTypeOfInst(lhs_id); + auto [lhs_type_file, lhs_type_id] = + context.GetTypeIdOfInstInSpecific(lhs_id); if (builtin_kind == SemIR::BuiltinFunctionKind::IntRightShiftAssign) { - lhs_type_id = context.sem_ir().GetPointeeType(lhs_type_id); + lhs_type_id = lhs_type_file->GetPointeeType(lhs_type_id); } return CreateIntShift(context, - context.sem_ir().types().IsSignedInt(lhs_type_id) + lhs_type_file->types().IsSignedInt(lhs_type_id) ? llvm::Instruction::AShr : llvm::Instruction::LShr, lhs, rhs); @@ -390,19 +392,21 @@ static auto HandleBuiltinCall(FunctionContext& context, SemIR::InstId inst_id, case SemIR::BuiltinFunctionKind::IntLeftShiftAssign: case SemIR::BuiltinFunctionKind::IntRightShiftAssign: { auto* lhs_ptr = context.GetValue(arg_ids[0]); - auto lhs_type_id = context.GetTypeOfInst(arg_ids[0]); - auto pointee_type_id = context.sem_ir().GetPointeeType(lhs_type_id); + auto [lhs_type_file, lhs_type_id] = + context.GetTypeIdOfInstInSpecific(arg_ids[0]); + auto pointee_type_id = lhs_type_file->GetPointeeType(lhs_type_id); // TODO: Factor out the code to create loads and stores, and include alias // and alignment information. auto* lhs_value = context.builder().CreateLoad( - context.GetType(pointee_type_id), lhs_ptr); + context.GetFileContext(lhs_type_file).GetType(pointee_type_id), + lhs_ptr); auto* result = CreateBinaryOperatorForBuiltin( context, inst_id, builtin_kind, lhs_value, context.GetValue(arg_ids[1])); context.builder().CreateStore(result, lhs_ptr); // TODO: Add a helper to get a "no value representation" value. - context.SetLocal(inst_id, llvm::PoisonValue::get(context.GetType( - context.GetTypeOfInst(inst_id)))); + context.SetLocal(inst_id, llvm::PoisonValue::get( + context.GetTypeOfInstInSpecific(inst_id))); return; } case SemIR::BuiltinFunctionKind::IntEq: @@ -476,13 +480,36 @@ auto HandleInst(FunctionContext& context, SemIR::InstId inst_id, llvm::ArrayRef arg_ids = context.sem_ir().inst_blocks().Get(inst.args_id); - auto callee_function = SemIR::GetCalleeFunction( - context.sem_ir(), inst.callee_id, context.specific_id()); + // TODO: This duplicates the SpecificId handling in `GetCalleeFunction`. + + // TODO: Should the `bound_method` be removed when forming the `call` + // instruction? The `self` parameter is transferred into the call argument + // list. + auto callee_id = inst.callee_id; + if (auto bound_method = + context.sem_ir().insts().TryGetAs(callee_id)) { + callee_id = bound_method->function_decl_id; + } + + // Map to the callee in the specific. This might be in a different file than + // the one we're currently lowering. + const auto* callee_file = &context.sem_ir(); + if (context.specific_id().has_value()) { + auto [const_file, const_id] = GetConstantValueInSpecific( + context.specific_sem_ir(), context.specific_id(), context.sem_ir(), + callee_id); + callee_file = const_file; + callee_id = const_file->constant_values().GetInstIdIfValid(const_id); + CARBON_CHECK(callee_id.has_value()); + } + + auto callee_function = SemIR::GetCalleeFunction(*callee_file, callee_id); CARBON_CHECK(callee_function.function_id.has_value()); const SemIR::Function& function = - context.sem_ir().functions().Get(callee_function.function_id); - context.AddCallToCurrentFingerprint(callee_function.function_id, + callee_file->functions().Get(callee_function.function_id); + context.AddCallToCurrentFingerprint(callee_file->check_ir_id(), + callee_function.function_id, callee_function.resolved_specific_id); if (auto builtin_kind = function.builtin_function_kind; @@ -493,17 +520,19 @@ auto HandleInst(FunctionContext& context, SemIR::InstId inst_id, std::vector args; - auto inst_type_id = context.GetTypeOfInst(inst_id); + auto [inst_type_file, inst_type_id] = + context.GetTypeIdOfInstInSpecific(inst_id); - if (SemIR::ReturnTypeInfo::ForType(context.sem_ir(), inst_type_id) + if (SemIR::ReturnTypeInfo::ForType(*inst_type_file, inst_type_id) .has_return_slot()) { args.push_back(context.GetValue(arg_ids.back())); arg_ids = arg_ids.drop_back(); } for (auto arg_id : arg_ids) { - auto arg_type_id = context.GetTypeOfInst(arg_id); - if (SemIR::ValueRepr::ForType(context.sem_ir(), arg_type_id).kind != + auto [arg_type_file, arg_type_id] = + context.GetTypeIdOfInstInSpecific(arg_id); + if (SemIR::ValueRepr::ForType(*arg_type_file, arg_type_id).kind != SemIR::ValueRepr::None) { args.push_back(context.GetValue(arg_id)); } @@ -521,8 +550,10 @@ auto HandleInst(FunctionContext& context, SemIR::InstId inst_id, auto* vtable = context.builder().CreateLoad(ptr_type, args.front(), "vtable"); auto* i32_type = llvm::IntegerType::getInt32Ty(context.llvm_context()); - auto function_type_info = context.BuildFunctionTypeInfo( - function, callee_function.resolved_specific_id); + auto function_type_info = + context.GetFileContext(callee_file) + .BuildFunctionTypeInfo(function, + callee_function.resolved_specific_id); call = context.builder().CreateCall( function_type_info.type, context.builder().CreateCall( @@ -535,8 +566,9 @@ auto HandleInst(FunctionContext& context, SemIR::InstId inst_id, args); } else { call = context.builder().CreateCall( - context.GetOrCreateFunction(callee_function.function_id, - callee_function.resolved_specific_id), + context.GetFileContext(callee_file) + .GetOrCreateFunction(callee_function.function_id, + callee_function.resolved_specific_id), args); } diff --git a/toolchain/lower/mangler.cpp b/toolchain/lower/mangler.cpp index cd4c54b5404d..f23510acf466 100644 --- a/toolchain/lower/mangler.cpp +++ b/toolchain/lower/mangler.cpp @@ -167,6 +167,8 @@ auto Mangler::Mangle(SemIR::FunctionId function_id, break; } + // TODO: If the function is private, also include the library name as part of + // the mangling. MangleInverseQualifiedNameScope(os, function.parent_scope_id); // TODO: Add proper support for mangling generic entities. For now we use a @@ -197,6 +199,8 @@ auto Mangler::MangleGlobalVariable(SemIR::InstId pattern_id) -> std::string { auto var_name = sem_ir().entity_names().Get(var_name_id); MangleNameId(os, var_name.name_id); + // TODO: If the variable is private, also include the library name as part of + // the mangling. MangleInverseQualifiedNameScope(os, var_name.parent_scope_id); return os.TakeStr(); } @@ -217,6 +221,8 @@ auto Mangler::MangleVTable(const SemIR::Class& class_info) -> std::string { os << "_C"; MangleNameId(os, class_info.name_id); + // TODO: If the class is private, also include the library name as part of the + // mangling. MangleInverseQualifiedNameScope(os, class_info.parent_scope_id); os << ".$vtable"; diff --git a/toolchain/lower/testdata/function/generic/cross_library_name_collision_private.carbon b/toolchain/lower/testdata/function/generic/cross_library_name_collision_private.carbon new file mode 100644 index 000000000000..b6499963e664 --- /dev/null +++ b/toolchain/lower/testdata/function/generic/cross_library_name_collision_private.carbon @@ -0,0 +1,120 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/lower/testdata/function/generic/cross_library_name_collision_private.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/function/generic/cross_library_name_collision_private.carbon + +// --- lib1.carbon + +library "[[@TEST_NAME]]"; + +private fn F[T:! type](a: T, b: T) -> T { + return a; +} + +fn Lib1CallF[T:! type](a: T, b: T) -> T { + return F(a, b); +} + +// --- lib2.carbon + +library "[[@TEST_NAME]]"; + +// Duplicate function name in different files. Shouldn't be a name conflict +// because of `private`. However, we currently use the same mangling for both +// functions. +private fn F[T:! type](a: T, b: T) -> T { + return b; +} + +fn Lib2CallF[T:! type](a: T, b: T) -> T { + return F(a, b); +} + +// --- todo_use.carbon + +// TODO: This should call the `F` from lib1 and the `F` from lib2, and return 3. +// Currently it calls the same function twice. + +import library "lib1"; +import library "lib2"; + +fn Run() -> i32 { + return Lib1CallF(1 as i32, 2 as i32) + Lib2CallF(1 as i32, 2 as i32); +} + +// CHECK:STDOUT: ; ModuleID = 'lib1.carbon' +// CHECK:STDOUT: source_filename = "lib1.carbon" +// CHECK:STDOUT: +// CHECK:STDOUT: !llvm.module.flags = !{!0, !1} +// CHECK:STDOUT: !llvm.dbg.cu = !{!2} +// CHECK:STDOUT: +// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5} +// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3} +// CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug) +// CHECK:STDOUT: !3 = !DIFile(filename: "lib1.carbon", directory: "") +// CHECK:STDOUT: ; ModuleID = 'lib2.carbon' +// CHECK:STDOUT: source_filename = "lib2.carbon" +// CHECK:STDOUT: +// CHECK:STDOUT: !llvm.module.flags = !{!0, !1} +// CHECK:STDOUT: !llvm.dbg.cu = !{!2} +// CHECK:STDOUT: +// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5} +// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3} +// CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug) +// CHECK:STDOUT: !3 = !DIFile(filename: "lib2.carbon", directory: "") +// CHECK:STDOUT: ; ModuleID = 'todo_use.carbon' +// CHECK:STDOUT: source_filename = "todo_use.carbon" +// CHECK:STDOUT: +// CHECK:STDOUT: define i32 @main() !dbg !4 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %Lib1CallF.call = call i32 @_CLib1CallF.Main.b88d1103f417c6d4(i32 1, i32 2), !dbg !7 +// CHECK:STDOUT: %Lib2CallF.call = call i32 @_CLib2CallF.Main.b88d1103f417c6d4(i32 1, i32 2), !dbg !8 +// CHECK:STDOUT: %int.sadd = add i32 %Lib1CallF.call, %Lib2CallF.call, !dbg !7 +// CHECK:STDOUT: ret i32 %int.sadd, !dbg !9 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: define linkonce_odr i32 @_CLib1CallF.Main.b88d1103f417c6d4(i32 %a, i32 %b) !dbg !10 { +// CHECK:STDOUT: %1 = call i32 @_CF.Main.b88d1103f417c6d4(i32 %a, i32 %b), !dbg !12 +// CHECK:STDOUT: ret i32 %1, !dbg !13 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: define linkonce_odr i32 @_CLib2CallF.Main.b88d1103f417c6d4(i32 %a, i32 %b) !dbg !14 { +// CHECK:STDOUT: %1 = call i32 @_CF.Main.b88d1103f417c6d4(i32 %a, i32 %b), !dbg !16 +// CHECK:STDOUT: ret i32 %1, !dbg !17 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: define linkonce_odr i32 @_CF.Main.b88d1103f417c6d4(i32 %a, i32 %b) !dbg !18 { +// CHECK:STDOUT: ret i32 %a, !dbg !19 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: ; uselistorder directives +// CHECK:STDOUT: uselistorder ptr @_CF.Main.b88d1103f417c6d4, { 1, 0 } +// CHECK:STDOUT: +// CHECK:STDOUT: !llvm.module.flags = !{!0, !1} +// CHECK:STDOUT: !llvm.dbg.cu = !{!2} +// CHECK:STDOUT: +// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5} +// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3} +// CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug) +// CHECK:STDOUT: !3 = !DIFile(filename: "todo_use.carbon", directory: "") +// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "Run", linkageName: "main", scope: null, file: !3, line: 8, type: !5, spFlags: DISPFlagDefinition, unit: !2) +// CHECK:STDOUT: !5 = !DISubroutineType(types: !6) +// CHECK:STDOUT: !6 = !{} +// CHECK:STDOUT: !7 = !DILocation(line: 9, column: 10, scope: !4) +// CHECK:STDOUT: !8 = !DILocation(line: 9, column: 42, scope: !4) +// CHECK:STDOUT: !9 = !DILocation(line: 9, column: 3, scope: !4) +// CHECK:STDOUT: !10 = distinct !DISubprogram(name: "Lib1CallF", linkageName: "_CLib1CallF.Main.b88d1103f417c6d4", scope: null, file: !11, line: 8, type: !5, spFlags: DISPFlagDefinition, unit: !2) +// CHECK:STDOUT: !11 = !DIFile(filename: "lib1.carbon", directory: "") +// CHECK:STDOUT: !12 = !DILocation(line: 9, column: 10, scope: !10) +// CHECK:STDOUT: !13 = !DILocation(line: 9, column: 3, scope: !10) +// CHECK:STDOUT: !14 = distinct !DISubprogram(name: "Lib2CallF", linkageName: "_CLib2CallF.Main.b88d1103f417c6d4", scope: null, file: !15, line: 11, type: !5, spFlags: DISPFlagDefinition, unit: !2) +// CHECK:STDOUT: !15 = !DIFile(filename: "lib2.carbon", directory: "") +// CHECK:STDOUT: !16 = !DILocation(line: 12, column: 10, scope: !14) +// CHECK:STDOUT: !17 = !DILocation(line: 12, column: 3, scope: !14) +// CHECK:STDOUT: !18 = distinct !DISubprogram(name: "F", linkageName: "_CF.Main.b88d1103f417c6d4", scope: null, file: !11, line: 4, type: !5, spFlags: DISPFlagDefinition, unit: !2) +// CHECK:STDOUT: !19 = !DILocation(line: 5, column: 3, scope: !18) diff --git a/toolchain/lower/testdata/function/generic/import.carbon b/toolchain/lower/testdata/function/generic/import.carbon new file mode 100644 index 000000000000..52a6066f43c1 --- /dev/null +++ b/toolchain/lower/testdata/function/generic/import.carbon @@ -0,0 +1,171 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/lower/testdata/function/generic/import.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/function/generic/import.carbon + +// Test that we can import and call generic functions across libraries, both +// when the generic is defined in a directly imported library and when it's +// defined in an indirectly imported library. + +// --- indirectly_imported.carbon + +library "[[@TEST_NAME]]"; + +fn IndirectDeclared(); + +fn IndirectDefined() {} + +fn IndirectGeneric(T:! type, x: T*) -> T* { + IndirectDeclared(); + IndirectDefined(); + return x; +} + +// --- directly_imported.carbon + +library "[[@TEST_NAME]]"; + +import library "indirectly_imported"; + +fn DirectDeclared(); + +fn DirectDefined() {} + +fn DirectGeneric(T:! type, y: T*) -> T* { + DirectDeclared(); + DirectDefined(); + return IndirectGeneric(T, y); +} + +// --- use.carbon + +library "[[@TEST_NAME]]"; + +import library "directly_imported"; + +fn Call() -> i32 { + var n: i32 = 0; + var p: i32* = DirectGeneric(i32, &n); + return *p; +} + +// CHECK:STDOUT: ; ModuleID = 'indirectly_imported.carbon' +// CHECK:STDOUT: source_filename = "indirectly_imported.carbon" +// CHECK:STDOUT: +// CHECK:STDOUT: declare void @_CIndirectDeclared.Main() +// CHECK:STDOUT: +// CHECK:STDOUT: define void @_CIndirectDefined.Main() !dbg !4 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: ret void, !dbg !7 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: !llvm.module.flags = !{!0, !1} +// CHECK:STDOUT: !llvm.dbg.cu = !{!2} +// CHECK:STDOUT: +// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5} +// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3} +// CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug) +// CHECK:STDOUT: !3 = !DIFile(filename: "indirectly_imported.carbon", directory: "") +// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "IndirectDefined", linkageName: "_CIndirectDefined.Main", scope: null, file: !3, line: 6, type: !5, spFlags: DISPFlagDefinition, unit: !2) +// CHECK:STDOUT: !5 = !DISubroutineType(types: !6) +// CHECK:STDOUT: !6 = !{} +// CHECK:STDOUT: !7 = !DILocation(line: 6, column: 1, scope: !4) +// CHECK:STDOUT: ; ModuleID = 'directly_imported.carbon' +// CHECK:STDOUT: source_filename = "directly_imported.carbon" +// CHECK:STDOUT: +// CHECK:STDOUT: declare void @_CDirectDeclared.Main() +// CHECK:STDOUT: +// CHECK:STDOUT: define void @_CDirectDefined.Main() !dbg !4 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: ret void, !dbg !7 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: !llvm.module.flags = !{!0, !1} +// CHECK:STDOUT: !llvm.dbg.cu = !{!2} +// CHECK:STDOUT: +// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5} +// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3} +// CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug) +// CHECK:STDOUT: !3 = !DIFile(filename: "directly_imported.carbon", directory: "") +// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "DirectDefined", linkageName: "_CDirectDefined.Main", scope: null, file: !3, line: 8, type: !5, spFlags: DISPFlagDefinition, unit: !2) +// CHECK:STDOUT: !5 = !DISubroutineType(types: !6) +// CHECK:STDOUT: !6 = !{} +// CHECK:STDOUT: !7 = !DILocation(line: 8, column: 1, scope: !4) +// CHECK:STDOUT: ; ModuleID = 'use.carbon' +// CHECK:STDOUT: source_filename = "use.carbon" +// CHECK:STDOUT: +// CHECK:STDOUT: define i32 @_CCall.Main() !dbg !4 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %n.var = alloca i32, align 4, !dbg !7 +// CHECK:STDOUT: %p.var = alloca ptr, align 8, !dbg !8 +// CHECK:STDOUT: call void @llvm.lifetime.start.p0(i64 4, ptr %n.var), !dbg !7 +// CHECK:STDOUT: store i32 0, ptr %n.var, align 4, !dbg !7 +// CHECK:STDOUT: call void @llvm.lifetime.start.p0(i64 8, ptr %p.var), !dbg !8 +// CHECK:STDOUT: %DirectGeneric.call = call ptr @_CDirectGeneric.Main.b88d1103f417c6d4(ptr %n.var), !dbg !9 +// CHECK:STDOUT: store ptr %DirectGeneric.call, ptr %p.var, align 8, !dbg !8 +// CHECK:STDOUT: %.loc9_11 = load ptr, ptr %p.var, align 8, !dbg !10 +// CHECK:STDOUT: %.loc9_10.2 = load i32, ptr %.loc9_11, align 4, !dbg !11 +// CHECK:STDOUT: ret i32 %.loc9_10.2, !dbg !12 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) +// CHECK:STDOUT: declare void @llvm.lifetime.start.p0(i64 immarg, ptr captures(none)) #0 +// CHECK:STDOUT: +// CHECK:STDOUT: define linkonce_odr ptr @_CDirectGeneric.Main.b88d1103f417c6d4(ptr %y) !dbg !13 { +// CHECK:STDOUT: call void @_CDirectDeclared.Main(), !dbg !15 +// CHECK:STDOUT: call void @_CDirectDefined.Main(), !dbg !16 +// CHECK:STDOUT: %1 = call ptr @_CIndirectGeneric.Main.b88d1103f417c6d4(ptr %y), !dbg !17 +// CHECK:STDOUT: ret ptr %1, !dbg !18 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: declare void @_CDirectDeclared.Main() +// CHECK:STDOUT: +// CHECK:STDOUT: declare void @_CDirectDefined.Main() +// CHECK:STDOUT: +// CHECK:STDOUT: define linkonce_odr ptr @_CIndirectGeneric.Main.b88d1103f417c6d4(ptr %x) !dbg !19 { +// CHECK:STDOUT: call void @_CIndirectDeclared.Main(), !dbg !21 +// CHECK:STDOUT: call void @_CIndirectDefined.Main(), !dbg !22 +// CHECK:STDOUT: ret ptr %x, !dbg !23 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: declare void @_CIndirectDeclared.Main() +// CHECK:STDOUT: +// CHECK:STDOUT: declare void @_CIndirectDefined.Main() +// CHECK:STDOUT: +// CHECK:STDOUT: ; uselistorder directives +// CHECK:STDOUT: uselistorder ptr @llvm.lifetime.start.p0, { 1, 0 } +// CHECK:STDOUT: +// CHECK:STDOUT: attributes #0 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) } +// CHECK:STDOUT: +// CHECK:STDOUT: !llvm.module.flags = !{!0, !1} +// CHECK:STDOUT: !llvm.dbg.cu = !{!2} +// CHECK:STDOUT: +// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5} +// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3} +// CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug) +// CHECK:STDOUT: !3 = !DIFile(filename: "use.carbon", directory: "") +// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "Call", linkageName: "_CCall.Main", scope: null, file: !3, line: 6, type: !5, spFlags: DISPFlagDefinition, unit: !2) +// CHECK:STDOUT: !5 = !DISubroutineType(types: !6) +// CHECK:STDOUT: !6 = !{} +// CHECK:STDOUT: !7 = !DILocation(line: 7, column: 3, scope: !4) +// CHECK:STDOUT: !8 = !DILocation(line: 8, column: 3, scope: !4) +// CHECK:STDOUT: !9 = !DILocation(line: 8, column: 17, scope: !4) +// CHECK:STDOUT: !10 = !DILocation(line: 9, column: 11, scope: !4) +// CHECK:STDOUT: !11 = !DILocation(line: 9, column: 10, scope: !4) +// CHECK:STDOUT: !12 = !DILocation(line: 9, column: 3, scope: !4) +// CHECK:STDOUT: !13 = distinct !DISubprogram(name: "DirectGeneric", linkageName: "_CDirectGeneric.Main.b88d1103f417c6d4", scope: null, file: !14, line: 10, type: !5, spFlags: DISPFlagDefinition, unit: !2) +// CHECK:STDOUT: !14 = !DIFile(filename: "directly_imported.carbon", directory: "") +// CHECK:STDOUT: !15 = !DILocation(line: 11, column: 3, scope: !13) +// CHECK:STDOUT: !16 = !DILocation(line: 12, column: 3, scope: !13) +// CHECK:STDOUT: !17 = !DILocation(line: 13, column: 10, scope: !13) +// CHECK:STDOUT: !18 = !DILocation(line: 13, column: 3, scope: !13) +// CHECK:STDOUT: !19 = distinct !DISubprogram(name: "IndirectGeneric", linkageName: "_CIndirectGeneric.Main.b88d1103f417c6d4", scope: null, file: !20, line: 8, type: !5, spFlags: DISPFlagDefinition, unit: !2) +// CHECK:STDOUT: !20 = !DIFile(filename: "indirectly_imported.carbon", directory: "") +// CHECK:STDOUT: !21 = !DILocation(line: 9, column: 3, scope: !19) +// CHECK:STDOUT: !22 = !DILocation(line: 10, column: 3, scope: !19) +// CHECK:STDOUT: !23 = !DILocation(line: 11, column: 3, scope: !19) diff --git a/toolchain/sem_ir/generic.cpp b/toolchain/sem_ir/generic.cpp index c918eadefed3..723fe052eddb 100644 --- a/toolchain/sem_ir/generic.cpp +++ b/toolchain/sem_ir/generic.cpp @@ -52,61 +52,89 @@ auto SpecificStore::CollectMemUsage(MemUsage& mem_usage, lookup_table_, KeyContext(&specifics_)); } -static auto GetConstantInSpecific(const File& sem_ir, SpecificId specific_id, - ConstantId const_id) -> ConstantId { +static auto GetConstantInSpecific(const File& specific_ir, + SpecificId specific_id, const File& const_ir, + ConstantId const_id) + -> std::pair { if (!const_id.is_symbolic()) { // The constant does not depend on a generic parameter. - return const_id; + return {&const_ir, const_id}; } - const auto& symbolic = sem_ir.constant_values().GetSymbolicConstant(const_id); + const auto& symbolic = + const_ir.constant_values().GetSymbolicConstant(const_id); if (!symbolic.generic_id.has_value()) { // The constant is an unattached symbolic constant, not associated with some // particular generic. - return const_id; + return {&const_ir, const_id}; } if (!specific_id.has_value()) { // We have a generic constant but no specific. We treat this as a request // for the value that should be used within the generic itself, which is the // unattached constant. - return sem_ir.constant_values().Get(symbolic.inst_id); + return {&const_ir, const_ir.constant_values().Get(symbolic.inst_id)}; } - const auto& specific = sem_ir.specifics().Get(specific_id); - CARBON_CHECK(specific.generic_id == symbolic.generic_id, - "Given a specific for the wrong generic"); + const auto& specific = specific_ir.specifics().Get(specific_id); + // TODO: Enforce this check even if the generic and specific are in different + // IRs. + CARBON_CHECK( + &specific_ir != &const_ir || specific.generic_id == symbolic.generic_id, + "Given a specific for the wrong generic"); auto value_block_id = specific.GetValueBlock(symbolic.index.region()); if (!value_block_id.has_value()) { // For the self specific, we can see queries before the definition is // resolved. Return the unattached constant value. CARBON_CHECK( - sem_ir.generics().GetSelfSpecific(symbolic.generic_id) == specific_id, + specific_ir.generics().GetSelfSpecific( + specific_ir.specifics().Get(specific_id).generic_id) == specific_id, "Queried {0} in {1} for {2} before it was resolved.", symbolic.index, specific_id, - sem_ir.insts().Get(sem_ir.generics().Get(specific.generic_id).decl_id)); + specific_ir.insts().Get( + specific_ir.generics().Get(specific.generic_id).decl_id)); // TODO: Make sure this is the same value that we put in the self specific // when it's resolved. Consider not building value blocks for a self // specific. - return sem_ir.constant_values().Get(symbolic.inst_id); + return {&const_ir, const_ir.constant_values().Get(symbolic.inst_id)}; } - return sem_ir.constant_values().Get( - sem_ir.inst_blocks().Get(value_block_id)[symbolic.index.index()]); + return {&specific_ir, + specific_ir.constant_values().Get(specific_ir.inst_blocks().Get( + value_block_id)[symbolic.index.index()])}; } auto GetConstantValueInSpecific(const File& sem_ir, SpecificId specific_id, InstId inst_id) -> ConstantId { - return GetConstantInSpecific(sem_ir, specific_id, - sem_ir.constant_values().GetAttached(inst_id)); + return GetConstantInSpecific(sem_ir, specific_id, sem_ir, + sem_ir.constant_values().GetAttached(inst_id)) + .second; +} + +auto GetConstantValueInSpecific(const File& specific_ir, SpecificId specific_id, + const File& inst_ir, InstId inst_id) + -> std::pair { + return GetConstantInSpecific(specific_ir, specific_id, inst_ir, + inst_ir.constant_values().GetAttached(inst_id)); } auto GetTypeOfInstInSpecific(const File& sem_ir, SpecificId specific_id, InstId inst_id) -> TypeId { auto type_id = sem_ir.insts().GetAttachedType(inst_id); auto const_id = sem_ir.types().GetConstantId(type_id); - auto specific_const_id = GetConstantInSpecific(sem_ir, specific_id, const_id); + auto [_, specific_const_id] = + GetConstantInSpecific(sem_ir, specific_id, sem_ir, const_id); return TypeId::ForTypeConstant(specific_const_id); } +auto GetTypeOfInstInSpecific(const File& specific_ir, SpecificId specific_id, + const File& inst_ir, InstId inst_id) + -> std::pair { + auto type_id = inst_ir.insts().GetAttachedType(inst_id); + auto const_id = inst_ir.types().GetConstantId(type_id); + auto [result_ir, result_const_id] = + GetConstantInSpecific(specific_ir, specific_id, inst_ir, const_id); + return {result_ir, TypeId::ForTypeConstant(result_const_id)}; +} + } // namespace Carbon::SemIR diff --git a/toolchain/sem_ir/generic.h b/toolchain/sem_ir/generic.h index 387bd1079b10..747bb92b5c74 100644 --- a/toolchain/sem_ir/generic.h +++ b/toolchain/sem_ir/generic.h @@ -158,12 +158,28 @@ class SpecificStore : public Yaml::Printable { auto GetConstantValueInSpecific(const File& sem_ir, SpecificId specific_id, InstId inst_id) -> ConstantId; +// Gets the substituted constant value of a potentially generic instruction +// within a specific, where the generic instruction and the specific may be in +// different files. Returns the file in which the constant value was found and +// the constant ID in that file. +auto GetConstantValueInSpecific(const File& specific_ir, SpecificId specific_id, + const File& inst_ir, InstId inst_id) + -> std::pair; + // Gets the substituted type of an instruction within a specific. Note that this // does not perform substitution, and will return `None` if the substituted type // is not yet known. auto GetTypeOfInstInSpecific(const File& sem_ir, SpecificId specific_id, InstId inst_id) -> TypeId; +// Gets the substituted type of a potentially generic instruction within a +// specific, where the generic instruction and the specific may be in different +// files. Returns the file in which the type was found and the type ID in that +// file. +auto GetTypeOfInstInSpecific(const File& specific_ir, SpecificId specific_id, + const File& inst_ir, InstId inst_id) + -> std::pair; + } // namespace Carbon::SemIR #endif // CARBON_TOOLCHAIN_SEM_IR_GENERIC_H_