Procházet zdrojové kódy

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.
Richard Smith před 11 měsíci
rodič
revize
14e4f219b1

+ 14 - 0
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<llvm::Module> {
+  LowerPendingDefinitions();
+
   file_contexts_.ForEach(
       [](auto, auto& file_context) { file_context->Finalize(); });
   return std::move(llvm_module_);

+ 22 - 0
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<llvm::vfs::FileSystem> fs,
                    std::optional<llvm::ArrayRef<Parse::GetTreeAndSubtreesFn>>
@@ -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<llvm::Module>;
@@ -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> 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<PendingSpecificFunctionDefinition>
+      specific_function_definitions_;
 };
 
 }  // namespace Carbon::Lower

+ 177 - 115
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<llvm::GlobalVariable>(
-            GetGlobal(inst_id, SemIR::SpecificId::None));
+      if (auto const_id = sem_ir().constant_values().Get(inst_id);
+          const_id.is_constant()) {
+        llvm_var = cast<llvm::GlobalVariable>(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<const SemIR::File*, SemIR::FunctionId> {
+  // 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<SemIR::FunctionDecl>(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.");
-
-  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;
-  }
+                                    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();
+
+  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<SemIR::AnyParam>(param_id);
+    auto param_inst = definition_ir.insts().GetAs<SemIR::AnyParam>(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<SemIR::InstId> 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<SemIR::FunctionDecl>(function.latest_decl_id())
-                        .decl_block_id;
+    decl_block_id =
+        definition_ir.insts()
+            .GetAs<SemIR::FunctionDecl>(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.

+ 47 - 24
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<llvm::Function*, 0> 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<std::pair<SemIR::FunctionId, SemIR::SpecificId>, 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.

+ 20 - 6
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<const SemIR::File*, SemIR::TypeId> {
+  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.

+ 50 - 6
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<const SemIR::File*, SemIR::TypeId>;
 
   // 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 <function_id, specific_id>.
-  auto AddCallToCurrentFingerprint(SemIR::FunctionId function_id,
+  // body, to <function_id, specific_id>. `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_;
 

+ 1 - 3
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.

+ 53 - 21
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<SemIR::Call>(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<SemIR::InstId> 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<SemIR::BoundMethod>(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<llvm::Value*> 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);
   }
 

+ 6 - 0
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";

+ 120 - 0
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)

+ 171 - 0
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)

+ 45 - 17
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<const File*, ConstantId> {
   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<const File*, ConstantId> {
+  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<const File*, TypeId> {
+  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

+ 16 - 0
toolchain/sem_ir/generic.h

@@ -158,12 +158,28 @@ class SpecificStore : public Yaml::Printable<SpecificStore> {
 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<const File*, ConstantId>;
+
 // 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<const File*, TypeId>;
+
 }  // namespace Carbon::SemIR
 
 #endif  // CARBON_TOOLCHAIN_SEM_IR_GENERIC_H_