Просмотр исходного кода

Fix crash lowering call to generic function with concrete type in signature (#7009)

When a function call appears in a generic, and calls another generic
that has a concrete type in its call-site signature, that concrete type
will be completed only in the file that contains the call. The generic
containing the call won't require completeness to be checked again when
forming a specific call, because the type was concrete. This means that
when lowering the call instruction, there is no single file that is
guaranteed to contain complete types for all of the callee's parameters
-- the file containing the specific callee won't necessarily have
completed the concrete parts of the signature, and the files containing
the definition and call won't necessarily have completed the symbolic
parts of the signature.

To handle this, look at both versions of the function when building its
lowered signature -- the version that we saw when forming the `call`
instruction and the version corresponding to the concrete, specific
callee, and combine information from both to form the LLVM function
type.

---------

Co-authored-by: Geoff Romer <gromer@google.com>
Richard Smith 4 недель назад
Родитель
Сommit
ea409f7cbf

+ 2 - 2
toolchain/check/import_ref.cpp

@@ -2448,12 +2448,12 @@ static auto TryResolveTypedInst(ImportRefResolver& resolver,
       resolver, resolver.import_classes()
                     .Get(resolver.import_vtables().Get(inst.vtable_id).class_id)
                     .vtable_decl_id);
-  auto vtable_const_inst = resolver.local_insts().Get(
-      resolver.local_constant_values().GetInstId(vtable_const_id));
   if (resolver.HasNewWork()) {
     return ResolveResult::Retry();
   }
 
+  auto vtable_const_inst = resolver.local_insts().Get(
+      resolver.local_constant_values().GetInstId(vtable_const_id));
   return ResolveResult::Deduplicated<SemIR::VtablePtr>(
       resolver,
       {.type_id = GetPointerType(resolver.local_context(),

+ 251 - 136
toolchain/lower/file_context.cpp

@@ -247,13 +247,17 @@ auto FileContext::GetConstant(SemIR::ConstantId const_id,
   return global_variable;
 }
 
-auto FileContext::GetOrCreateFunctionInfo(SemIR::FunctionId function_id,
-                                          SemIR::SpecificId specific_id)
-    -> std::optional<FunctionInfo>& {
+auto FileContext::GetOrCreateFunctionInfo(
+    SemIR::FunctionId function_id, SemIR::SpecificId specific_id,
+    FileContext* fallback_file, SemIR::FunctionId fallback_function_id,
+    SemIR::SpecificId fallback_specific_id) -> std::optional<FunctionInfo>& {
   // If we have already lowered a declaration of this function, just return it.
+  // TODO: If the existing declaration is inexact, and we now have a fallback,
+  // we should try again.
   auto& result = GetFunctionInfo(function_id, specific_id);
   if (!result) {
-    result = BuildFunctionDecl(function_id, specific_id);
+    result = BuildFunctionDecl(function_id, specific_id, fallback_file,
+                               fallback_function_id, fallback_specific_id);
   }
   return result;
 }
@@ -276,57 +280,103 @@ auto FileContext::GetOrCreateFunctionInfo(SemIR::FunctionId function_id,
 // Furthermore, SemIR is designed to eventually support compound return forms,
 // in which case there can be multiple output parameters for different pieces of
 // the return form, but it's not yet clear how we will lower such functions.
+//
+// We also deal with the case where the function signature involves incomplete
+// types. This can happen if the function is declared but never defined nor
+// called in this file. Declarations of such functions can still need to be
+// emitted; currently this happens if they are part of a class's vtable. Such
+// uses do not need an exact signature, so we emit them with the LLVM type
+// `void()` and set `inexact` on the result to indicate the type is not known.
+// LLVM can handle merging inexact and exact signatures, and this matches how
+// Clang handles the corresponding situation in C++.
+//
+// One additional complexity is that we may need to fetch information about the
+// same function from multiple different files. For a call to a generic
+// function, there may be no single file in which all the relevant types are
+// complete, so we will look at both the specific function definition that is
+// the resolved callee, as well as the partially-specific function from the call
+// site.
+//
+// In general, we support being given a list of variants of the function, in
+// which the first function in the list is the primary declaration and should be
+// the most specific function, and the others are used as fallbacks if an
+// incomplete type is encountered.
 class FileContext::FunctionTypeInfoBuilder {
  public:
-  // Creates a FunctionTypeInfoBuilder that uses the given FileContext, and
-  // the given specific of the function.
-  FunctionTypeInfoBuilder(FileContext* context, SemIR::SpecificId specific_id)
-      : context_(*context), specific_id_(specific_id) {}
-
-  // Retrieves various features of `function`'s type useful for constructing the
-  // `llvm::Type` and `llvm::DISubroutineType` for the `llvm::Function`. If any
-  // part of the type can't be manifest (eg: incomplete return or parameter
+  struct FunctionInContext {
+    FileContext* context;
+    SemIR::FunctionId function_id;
+    SemIR::SpecificId specific_id;
+  };
+
+  // Creates a FunctionTypeInfoBuilder that uses the given functions.
+  explicit FunctionTypeInfoBuilder(llvm::ArrayRef<FunctionInContext> functions)
+      : context_(&functions.front().context->context()), functions_(functions) {
+    CARBON_CHECK(!functions_.empty());
+  }
+
+  // Retrieves various features of the function's type useful for constructing
+  // the `llvm::Type` and `llvm::DISubroutineType` for the `llvm::Function`. If
+  // any part of the type can't be manifest (eg: incomplete return or parameter
   // types), then the result is as if the type was `void()`. Should only be
   // called once on a given builder.
-  auto Build(const SemIR::Function& function) && -> FunctionTypeInfo;
+  auto Build() && -> FunctionTypeInfo;
 
  private:
-  // By convention, state transition methods return false to indicate that
-  // `Abort` was called. As a convenience, that applies even to methods that
-  // never call `Abort`, and to `Abort` itself, so that their callers can easily
-  // propagate the failure.
-
-  // Resets the builder to the fallback state `void()`. This puts the builder in
-  // a state where Finalize can be called, and no other operation should be
-  // called.
-  auto Abort() -> bool {
-    call_param_pattern_ids_ = {};
-    lowered_param_indices_.clear();
-    unused_param_indices_.clear();
-    param_name_ids_.clear();
-    param_types_.clear();
-    param_di_types_.clear();
-    return_type_ = nullptr;
-    inexact_ = true;
-    SetReturnByCopy(SemIR::TypeId::None);
-    return false;
-  }
-
-  // Handles the function's return form. The argument can be None, indicating
-  // that there was no explicitly declared return form.
+  // By convention, state transition methods return false (without changing the
+  // accumulated information about the function) to indicate that we could not
+  // manifest the complete function type successfully in this context.
+
+  // Information about how a function is called in SemIR.
+  struct SemIRIndexInfo {
+    // The number of parameters in the SemIR call signature.
+    int num_params;
+    // The index of the first return parameter in the SemIR call signature.
+    int return_param_index;
+
+    friend auto operator==(const SemIRIndexInfo& lhs, const SemIRIndexInfo& rhs)
+        -> bool = default;
+  };
+
+  // Get information about the SemIR function signature.
+  auto GetSemIRIndexInfo(const FunctionInContext& fn_in_context)
+      -> SemIRIndexInfo {
+    const auto& sem_ir = fn_in_context.context->sem_ir();
+    const auto& function = sem_ir.functions().Get(fn_in_context.function_id);
+
+    int num_params =
+        sem_ir.inst_blocks().Get(function.call_param_patterns_id).size();
+
+    int return_param_index = -1;
+    if (function.call_param_ranges.return_size() > 0) {
+      CARBON_CHECK(function.call_param_ranges.return_size() == 1,
+                   "TODO: support multiple return forms");
+      return_param_index = function.call_param_ranges.return_begin().index;
+    }
+
+    return {.num_params = num_params, .return_param_index = return_param_index};
+  }
+
+  // Handles the function's return form.
   //
-  // This should be called before HandleParameter. It delegates to exactly one
-  // of SetReturnByCopy, SetReturnByReference, SetReturnInPlace, or Abort, and
-  // returns false if Abort was called.
-  auto HandleReturnForm(SemIR::InstId return_form_inst_id) -> bool;
+  // This should be called before `HandleParameter`. It handles the return form
+  // by trying each `FunctionInContext` until one succeeds, and returns false if
+  // all attempts failed.
+  auto HandleReturnForm() -> bool;
+
+  // Tries to handle the return form using the given context. Delegates to
+  // exactly one of `SetReturnByCopy`, `SetReturnByReference`, or
+  // `SetReturnInPlace`, or returns false if the return type is incomplete.
+  auto TryHandleReturnForm(const FunctionInContext& func_ctx) -> bool;
 
   // Records that the LLVM function returns by copy, with type `return_type_id`.
   // `return_type_id` can be `None`, which is treated as equivalent to the
   // default return type `()`.
-  auto SetReturnByCopy(SemIR::TypeId return_type_id) -> bool {
+  auto SetReturnByCopy(const FunctionInContext& func_ctx,
+                       SemIR::TypeId return_type_id) -> bool {
     CARBON_CHECK(return_type_ == nullptr);
     CARBON_CHECK(param_di_types_.empty());
-    auto lowered_return_types = GetLoweredTypes(return_type_id);
+    auto lowered_return_types = GetLoweredTypes(func_ctx, return_type_id);
     return_type_ = lowered_return_types.llvm_ir_type;
     param_di_types_.push_back(lowered_return_types.llvm_di_type);
     return true;
@@ -334,9 +384,10 @@ class FileContext::FunctionTypeInfoBuilder {
 
   // Records that the LLVM function returns by reference, with type
   // `return_type_id`.
-  auto SetReturnByReference(SemIR::TypeId /*return_type_id*/) -> bool {
-    return_type_ =
-        llvm::PointerType::get(context_.llvm_context(), /*AddressSpace=*/0);
+  auto SetReturnByReference(const FunctionInContext& func_ctx,
+                            SemIR::TypeId /*return_type_id*/) -> bool {
+    return_type_ = llvm::PointerType::get(func_ctx.context->llvm_context(),
+                                          /*AddressSpace=*/0);
     // TODO: replace this with a reference type.
     param_di_types_.push_back(GetPointerDIType(nullptr));
     return true;
@@ -344,9 +395,10 @@ class FileContext::FunctionTypeInfoBuilder {
 
   // Records that the LLVM function returns in place, with type
   // `return_type_id`.
-  auto SetReturnInPlace(SemIR::TypeId return_type_id) -> bool {
-    return_type_ = llvm::Type::getVoidTy(context_.llvm_context());
-    sret_type_ = context_.GetType(return_type_id);
+  auto SetReturnInPlace(const FunctionInContext& func_ctx,
+                        SemIR::TypeId return_type_id) -> bool {
+    return_type_ = llvm::Type::getVoidTy(func_ctx.context->llvm_context());
+    sret_type_ = func_ctx.context->GetType(return_type_id);
     // We don't add to param_di_types_ because that will be handled by the
     // loop over the SemIR parameters.
     return true;
@@ -359,18 +411,25 @@ class FileContext::FunctionTypeInfoBuilder {
   // parameters; it will determine which parameters belong in the LLVM IR
   // parameter list.
   //
-  // This delegates to exactly one of AddLoweredParam, IgnoreParam, or Abort,
-  // and returns false if Abort was called.
+  // This tries each `FunctionInContext` until one succeeds, and returns false
+  // if all attempts failed.
   auto HandleParameter(SemIR::CallParamIndex index) -> bool;
 
-  // Records that the parameter pattern at the given index jas the given ID, and
+  // Tries to handle the parameter pattern at the given index using the given
+  // context. Delegates to either `AddLoweredParam` or `IgnoreParam`, or returns
+  // false if the parameter type is incomplete.
+  auto TryHandleParameter(const FunctionInContext& func_ctx,
+                          SemIR::CallParamIndex index) -> bool;
+
+  // Records that the parameter pattern at the given index has the given ID, and
   // lowers to the given IR and DI types.
-  auto AddLoweredParam(SemIR::CallParamIndex index,
+  auto AddLoweredParam(const FunctionInContext& func_ctx,
+                       SemIR::CallParamIndex index,
                        SemIR::InstId param_pattern_id, LoweredTypes param_types)
       -> bool {
     lowered_param_indices_.push_back(index);
-    param_name_ids_.push_back(
-        SemIR::GetPrettyNameFromPatternId(context_.sem_ir(), param_pattern_id));
+    param_name_ids_.push_back(SemIR::GetPrettyNameFromPatternId(
+        func_ctx.context->sem_ir(), param_pattern_id));
     param_types_.push_back(param_types.llvm_ir_type);
     param_di_types_.push_back(param_types.llvm_di_type);
     return true;
@@ -386,26 +445,32 @@ class FileContext::FunctionTypeInfoBuilder {
   // Builds and returns a FunctionTypeInfo from the accumulated information.
   auto Finalize() -> FunctionTypeInfo;
 
+  // Clears out accumulated state and returns a FunctionTypeInfo with the
+  // fallback state `void()`.
+  auto Abort() -> FunctionTypeInfo;
+
   // Returns LLVM IR and DI types for the given SemIR type. This is not a state
   // transition. It mostly delegates to context_.GetTypeAndDIType, but treats
   // TypeId::None as equivalent to the unit type, and uses an untyped pointer as
   // a placeholder DI type if context_ doesn't provide one.
-  auto GetLoweredTypes(SemIR::TypeId type_id) -> LoweredTypes;
+  auto GetLoweredTypes(const FunctionInContext& func_ctx, SemIR::TypeId type_id)
+      -> LoweredTypes;
 
   // Returns a DI type for a pointer to the given pointee. The pointee type may
   // be null.
   auto GetPointerDIType(llvm::DIType* pointee_type, unsigned address_space = 0)
       -> llvm::DIDerivedType* {
-    const auto& data_layout = context_.llvm_module().getDataLayout();
-    return context_.context().di_builder().createPointerType(
+    const auto& data_layout = context_->llvm_module().getDataLayout();
+    return context_->di_builder().createPointerType(
         pointee_type, data_layout.getPointerSizeInBits(address_space));
   }
 
-  FileContext& context_;
-  const SemIR::SpecificId specific_id_;
+  Context* context_;
+
+  llvm::ArrayRef<FunctionInContext> functions_;
 
-  // The input `Call` parameter patterns.
-  llvm::ArrayRef<SemIR::InstId> call_param_pattern_ids_;
+  // The number of input `Call` parameter patterns.
+  int num_params_ = 0;
 
   // The types of the parameters in the LLVM IR function. Each one corresponds
   // to a SemIR `Call` parameter, but some `Call` parameters may be omitted
@@ -433,11 +498,6 @@ class FileContext::FunctionTypeInfoBuilder {
   // lowered_param_indices_.
   llvm::SmallVector<SemIR::CallParamIndex> unused_param_indices_;
 
-  // The `index` member of the SemIR function's return parameter, or -1 if it
-  // has no return parameter. Note that even if the SemIR function has a return
-  // parameter, the LLVM IR function might not.
-  int semir_return_param_index_ = -1;
-
   // The LLVM function's return type.
   llvm::Type* return_type_ = nullptr;
 
@@ -452,110 +512,127 @@ class FileContext::FunctionTypeInfoBuilder {
   bool inexact_ = false;
 };
 
-auto FileContext::FunctionTypeInfoBuilder::Build(
-    const SemIR::Function& function) && -> FunctionTypeInfo {
+auto FileContext::FunctionTypeInfoBuilder::Build() && -> FunctionTypeInfo {
   // TODO: For the `Run` entry point, remap return type to i32 if it doesn't
   // return a value.
 
-  call_param_pattern_ids_ =
-      context_.sem_ir().inst_blocks().Get(function.call_param_patterns_id);
-  lowered_param_indices_.reserve(call_param_pattern_ids_.size());
-  param_name_ids_.reserve(call_param_pattern_ids_.size());
-  param_types_.reserve(call_param_pattern_ids_.size());
-  param_di_types_.reserve(call_param_pattern_ids_.size());
-
-  if (function.call_param_ranges.return_size() > 0) {
-    CARBON_CHECK(function.call_param_ranges.return_size() == 1,
-                 "TODO: support multiple return forms");
-    semir_return_param_index_ = function.call_param_ranges.return_begin().index;
-  }
-  if (!HandleReturnForm(function.return_form_inst_id)) {
-    return Finalize();
-  }
-  int params_end = call_param_pattern_ids_.size();
-  if (semir_return_param_index_ >= 0) {
-    CARBON_CHECK(semir_return_param_index_ ==
-                     static_cast<int>(call_param_pattern_ids_.size()) - 1,
+  // Determine how the parameters are numbered in SemIR, and make sure it's the
+  // same for all versions of the function.
+  auto semir_info = GetSemIRIndexInfo(functions_.front());
+  CARBON_CHECK(
+      llvm::all_of(functions_.drop_front(), [&](const auto& fn_in_context) {
+        return GetSemIRIndexInfo(fn_in_context) == semir_info;
+      }));
+
+  num_params_ = semir_info.num_params;
+  lowered_param_indices_.reserve(num_params_);
+  param_name_ids_.reserve(num_params_);
+  param_types_.reserve(num_params_);
+  param_di_types_.reserve(num_params_);
+
+  if (!HandleReturnForm()) {
+    return Abort();
+  }
+  int params_end = num_params_;
+  if (semir_info.return_param_index >= 0) {
+    CARBON_CHECK(semir_info.return_param_index == semir_info.num_params - 1,
                  "Unexpected parameter order");
-    params_end = semir_return_param_index_;
+    params_end = semir_info.return_param_index;
     // Handle the return parameter first, because it goes first in the LLVM
     // convention.
-    if (!HandleParameter(SemIR::CallParamIndex(semir_return_param_index_))) {
-      return Finalize();
+    if (!HandleParameter(
+            SemIR::CallParamIndex(semir_info.return_param_index))) {
+      return Abort();
     }
   }
   for (int i : llvm::seq(params_end)) {
     if (!HandleParameter(SemIR::CallParamIndex(i))) {
-      return Finalize();
+      return Abort();
     }
   }
 
   return Finalize();
 }
 
-auto FileContext::FunctionTypeInfoBuilder::HandleReturnForm(
-    SemIR::InstId return_form_inst_id) -> bool {
+auto FileContext::FunctionTypeInfoBuilder::HandleReturnForm() -> bool {
+  for (const auto& func_ctx : functions_) {
+    if (TryHandleReturnForm(func_ctx)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+auto FileContext::FunctionTypeInfoBuilder::TryHandleReturnForm(
+    const FunctionInContext& func_ctx) -> bool {
+  const auto& function =
+      func_ctx.context->sem_ir().functions().Get(func_ctx.function_id);
+  auto return_form_inst_id = function.return_form_inst_id;
   if (!return_form_inst_id.has_value()) {
-    return SetReturnByCopy(SemIR::TypeId::None);
+    return SetReturnByCopy(func_ctx, SemIR::TypeId::None);
   }
 
   auto return_form_const_id = SemIR::GetConstantValueInSpecific(
-      context_.sem_ir(), specific_id_, return_form_inst_id);
-  auto return_form_inst = context_.sem_ir().insts().Get(
-      context_.sem_ir().constant_values().GetInstId(return_form_const_id));
+      func_ctx.context->sem_ir(), func_ctx.specific_id, return_form_inst_id);
+  auto return_form_inst = func_ctx.context->sem_ir().insts().Get(
+      func_ctx.context->sem_ir().constant_values().GetInstId(
+          return_form_const_id));
   CARBON_KIND_SWITCH(return_form_inst) {
     case CARBON_KIND(SemIR::InitForm init_form): {
       auto return_type_id =
-          context_.sem_ir().types().GetTypeIdForTypeConstantId(
+          func_ctx.context->sem_ir().types().GetTypeIdForTypeConstantId(
               SemIR::GetConstantValueInSpecific(
-                  context_.sem_ir(), specific_id_,
+                  func_ctx.context->sem_ir(), func_ctx.specific_id,
                   init_form.type_component_inst_id));
       switch (
-          SemIR::InitRepr::ForType(context_.sem_ir(), return_type_id).kind) {
+          SemIR::InitRepr::ForType(func_ctx.context->sem_ir(), return_type_id)
+              .kind) {
         case SemIR::InitRepr::InPlace: {
-          return SetReturnInPlace(return_type_id);
+          return SetReturnInPlace(func_ctx, return_type_id);
         }
         case SemIR::InitRepr::ByCopy: {
-          return SetReturnByCopy(return_type_id);
+          return SetReturnByCopy(func_ctx, return_type_id);
         }
         case SemIR::InitRepr::None:
-          return SetReturnByCopy(SemIR::TypeId::None);
+          return SetReturnByCopy(func_ctx, SemIR::TypeId::None);
         case SemIR::InitRepr::Dependent:
           CARBON_FATAL("Lowering function return with dependent type: {0}",
                        return_form_inst);
         case SemIR::InitRepr::Incomplete:
         case SemIR::InitRepr::Abstract:
-          return Abort();
+          return false;
       }
     }
     case CARBON_KIND(SemIR::RefForm ref_form): {
       auto return_type_id =
-          context_.sem_ir().types().GetTypeIdForTypeConstantId(
+          func_ctx.context->sem_ir().types().GetTypeIdForTypeConstantId(
               SemIR::GetConstantValueInSpecific(
-                  context_.sem_ir(), specific_id_,
+                  func_ctx.context->sem_ir(), func_ctx.specific_id,
                   ref_form.type_component_inst_id));
-      return SetReturnByReference(return_type_id);
+      return SetReturnByReference(func_ctx, return_type_id);
     }
     case CARBON_KIND(SemIR::ValueForm val_form): {
       auto return_type_id =
-          context_.sem_ir().types().GetTypeIdForTypeConstantId(
+          func_ctx.context->sem_ir().types().GetTypeIdForTypeConstantId(
               SemIR::GetConstantValueInSpecific(
-                  context_.sem_ir(), specific_id_,
+                  func_ctx.context->sem_ir(), func_ctx.specific_id,
                   val_form.type_component_inst_id));
       switch (
-          SemIR::ValueRepr::ForType(context_.sem_ir(), return_type_id).kind) {
+          SemIR::ValueRepr::ForType(func_ctx.context->sem_ir(), return_type_id)
+              .kind) {
         case SemIR::ValueRepr::Unknown:
-          return Abort();
+          return false;
+
         case SemIR::ValueRepr::Dependent:
           CARBON_FATAL("Lowering function return with dependent type: {0}",
                        return_form_inst);
         case SemIR::ValueRepr::None:
-          return SetReturnByCopy(SemIR::TypeId::None);
+          return SetReturnByCopy(func_ctx, SemIR::TypeId::None);
         case SemIR::ValueRepr::Copy:
-          return SetReturnByCopy(return_type_id);
+          return SetReturnByCopy(func_ctx, return_type_id);
         case SemIR::ValueRepr::Pointer:
         case SemIR::ValueRepr::Custom:
-          return SetReturnByReference(return_type_id);
+          return SetReturnByReference(func_ctx, return_type_id);
       }
     }
     default:
@@ -565,19 +642,33 @@ auto FileContext::FunctionTypeInfoBuilder::HandleReturnForm(
 
 auto FileContext::FunctionTypeInfoBuilder::HandleParameter(
     SemIR::CallParamIndex index) -> bool {
-  const auto& sem_ir = context_.sem_ir();
-  auto param_pattern_id = call_param_pattern_ids_[index.index];
+  for (const auto& func_ctx : functions_) {
+    if (TryHandleParameter(func_ctx, index)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+auto FileContext::FunctionTypeInfoBuilder::TryHandleParameter(
+    const FunctionInContext& func_ctx, SemIR::CallParamIndex index) -> bool {
+  const auto& sem_ir = func_ctx.context->sem_ir();
+  auto param_pattern_id =
+      sem_ir.inst_blocks().Get(sem_ir.functions()
+                                   .Get(func_ctx.function_id)
+                                   .call_param_patterns_id)[index.index];
   auto param_pattern = sem_ir.insts().Get(param_pattern_id);
   auto param_type_id = ExtractScrutineeType(
-      sem_ir,
-      SemIR::GetTypeOfInstInSpecific(sem_ir, specific_id_, param_pattern_id));
+      sem_ir, SemIR::GetTypeOfInstInSpecific(sem_ir, func_ctx.specific_id,
+                                             param_pattern_id));
 
   // Returns the appropriate LoweredTypes for reference-like parameters.
   auto ref_lowered_types = [&]() -> LoweredTypes {
-    return {.llvm_ir_type = llvm::PointerType::get(context_.llvm_context(),
-                                                   /*AddressSpace=*/0),
-            // TODO: replace this with a reference type.
-            .llvm_di_type = GetLoweredTypes(param_type_id).llvm_di_type};
+    return {
+        .llvm_ir_type = llvm::PointerType::get(func_ctx.context->llvm_context(),
+                                               /*AddressSpace=*/0),
+        // TODO: replace this with a reference type.
+        .llvm_di_type = GetLoweredTypes(func_ctx, param_type_id).llvm_di_type};
   };
 
   CARBON_CHECK(
@@ -613,12 +704,14 @@ auto FileContext::FunctionTypeInfoBuilder::HandleParameter(
   switch (param_kind) {
     case SemIR::RefParamPattern::Kind:
     case SemIR::VarParamPattern::Kind: {
-      return AddLoweredParam(index, param_pattern_id, ref_lowered_types());
+      return AddLoweredParam(func_ctx, index, param_pattern_id,
+                             ref_lowered_types());
     }
     case SemIR::OutParamPattern::Kind: {
       switch (SemIR::InitRepr::ForType(sem_ir, param_type_id).kind) {
         case SemIR::InitRepr::InPlace:
-          return AddLoweredParam(index, param_pattern_id, ref_lowered_types());
+          return AddLoweredParam(func_ctx, index, param_pattern_id,
+                                 ref_lowered_types());
         case SemIR::InitRepr::ByCopy:
         case SemIR::InitRepr::None:
           return IgnoreParam(index);
@@ -627,14 +720,14 @@ auto FileContext::FunctionTypeInfoBuilder::HandleParameter(
                        param_pattern);
         case SemIR::InitRepr::Incomplete:
         case SemIR::InitRepr::Abstract:
-          return Abort();
+          return false;
       }
     }
     case SemIR::ValueParamPattern::Kind: {
       switch (auto value_rep = SemIR::ValueRepr::ForType(sem_ir, param_type_id);
               value_rep.kind) {
         case SemIR::ValueRepr::Unknown:
-          return Abort();
+          return false;
         case SemIR::ValueRepr::Dependent:
           CARBON_FATAL("Lowering function parameter with dependent type: {0}",
                        param_pattern);
@@ -644,8 +737,9 @@ auto FileContext::FunctionTypeInfoBuilder::HandleParameter(
         case SemIR::ValueRepr::Custom:
         case SemIR::ValueRepr::Pointer: {
           if (value_rep.type_id.has_value()) {
-            return AddLoweredParam(index, param_pattern_id,
-                                   GetLoweredTypes(value_rep.type_id));
+            return AddLoweredParam(
+                func_ctx, index, param_pattern_id,
+                GetLoweredTypes(func_ctx, value_rep.type_id));
           } else {
             return IgnoreParam(index);
           }
@@ -659,9 +753,9 @@ auto FileContext::FunctionTypeInfoBuilder::HandleParameter(
 
 auto FileContext::FunctionTypeInfoBuilder::Finalize() -> FunctionTypeInfo {
   CARBON_CHECK(lowered_param_indices_.size() + unused_param_indices_.size() ==
-               call_param_pattern_ids_.size());
+               static_cast<size_t>(num_params_));
   CARBON_CHECK(!param_di_types_.empty());
-  auto& di_builder = context_.context().di_builder();
+  auto& di_builder = context_->di_builder();
   return {.type = llvm::FunctionType::get(return_type_, param_types_,
                                           /*isVarArg=*/false),
           .di_type = di_builder.createSubroutineType(
@@ -674,13 +768,27 @@ auto FileContext::FunctionTypeInfoBuilder::Finalize() -> FunctionTypeInfo {
           .inexact = inexact_};
 }
 
+auto FileContext::FunctionTypeInfoBuilder::Abort() -> FunctionTypeInfo {
+  num_params_ = 0;
+  lowered_param_indices_.clear();
+  unused_param_indices_.clear();
+  param_name_ids_.clear();
+  param_types_.clear();
+  param_di_types_.clear();
+  return_type_ = llvm::Type::getVoidTy(context_->llvm_context());
+  param_di_types_.push_back(nullptr);
+  inexact_ = true;
+  return Finalize();
+}
+
 auto FileContext::FunctionTypeInfoBuilder::GetLoweredTypes(
-    SemIR::TypeId type_id) -> LoweredTypes {
+    const FunctionInContext& func_ctx, SemIR::TypeId type_id) -> LoweredTypes {
   if (!type_id.has_value()) {
-    return {.llvm_ir_type = llvm::Type::getVoidTy(context_.llvm_context()),
-            .llvm_di_type = nullptr};
+    return {
+        .llvm_ir_type = llvm::Type::getVoidTy(func_ctx.context->llvm_context()),
+        .llvm_di_type = nullptr};
   }
-  auto result = context_.GetTypeAndDIType(type_id);
+  auto result = func_ctx.context->GetTypeAndDIType(type_id);
   if (result.llvm_di_type == nullptr) {
     // TODO: figure out what type should go here, or ensure this doesn't
     // happen.
@@ -792,7 +900,10 @@ auto FileContext::GetOrCreateLLVMFunction(
 }
 
 auto FileContext::BuildFunctionDecl(SemIR::FunctionId function_id,
-                                    SemIR::SpecificId specific_id)
+                                    SemIR::SpecificId specific_id,
+                                    FileContext* fallback_file,
+                                    SemIR::FunctionId fallback_function_id,
+                                    SemIR::SpecificId fallback_specific_id)
     -> std::optional<FunctionInfo> {
   const auto& function = sem_ir().functions().Get(function_id);
 
@@ -818,8 +929,12 @@ auto FileContext::BuildFunctionDecl(SemIR::FunctionId function_id,
   // TODO: Consider tracking whether the function has been used, and only
   // lowering it if it's needed.
 
+  FunctionTypeInfoBuilder::FunctionInContext func_infos[] = {
+      {this, function_id, specific_id},
+      {fallback_file, fallback_function_id, fallback_specific_id}};
   auto function_type_info =
-      FunctionTypeInfoBuilder(this, specific_id).Build(function);
+      FunctionTypeInfoBuilder(llvm::ArrayRef(func_infos, fallback_file ? 2 : 1))
+          .Build();
   auto* llvm_function =
       GetOrCreateLLVMFunction(function_type_info, function_id, specific_id);
 

+ 12 - 3
toolchain/lower/file_context.h

@@ -87,8 +87,14 @@ class FileContext {
   // Returns the FunctionInfo for the given function in the given specific. If
   // it's not already available, this function will compute it, including
   // creating the `llvm::Function` for it. Returns nullopt for a builtin.
-  auto GetOrCreateFunctionInfo(SemIR::FunctionId function_id,
-                               SemIR::SpecificId specific_id)
+  //
+  // The fallback information is used if the specific function has incomplete
+  // types.
+  auto GetOrCreateFunctionInfo(
+      SemIR::FunctionId function_id, SemIR::SpecificId specific_id,
+      FileContext* fallback_file = nullptr,
+      SemIR::FunctionId fallback_function_id = SemIR::FunctionId::None,
+      SemIR::SpecificId fallback_specific_id = SemIR::SpecificId::None)
       -> std::optional<FunctionInfo>&;
 
   // Returns a lowered type for the given type_id.
@@ -238,7 +244,10 @@ class FileContext {
   // by the caller.
   auto BuildFunctionDecl(
       SemIR::FunctionId function_id,
-      SemIR::SpecificId specific_id = SemIR::SpecificId::None)
+      SemIR::SpecificId specific_id = SemIR::SpecificId::None,
+      FileContext* fallback_file = nullptr,
+      SemIR::FunctionId fallback_function_id = SemIR::FunctionId::None,
+      SemIR::SpecificId fallback_specific_id = SemIR::SpecificId::None)
       -> std::optional<FunctionInfo>;
 
   // Builds a function's body. Common functionality for all functions.

+ 14 - 10
toolchain/lower/handle_call.cpp

@@ -591,9 +591,8 @@ static auto HandleBuiltinCall(FunctionContext& context, SemIR::InstId inst_id,
 
 static auto HandleVirtualCall(FunctionContext& context,
                               llvm::ArrayRef<llvm::Value*> args,
-                              const SemIR::File* callee_file,
                               const SemIR::Function& function,
-                              const SemIR::CalleeFunction& callee_function)
+                              const FunctionInfo& function_info)
     -> llvm::CallInst* {
   CARBON_CHECK(!args.empty(),
                "Virtual functions must have at least one parameter");
@@ -609,10 +608,6 @@ static auto HandleVirtualCall(FunctionContext& context,
   auto* i32_type = llvm::IntegerType::getInt32Ty(context.llvm_context());
   auto* pointer_type =
       llvm::PointerType::get(context.llvm_context(), /* address space */ 0);
-  const auto& function_info =
-      context.GetFileContext(callee_file)
-          .GetOrCreateFunctionInfo(callee_function.function_id,
-                                   callee_function.resolved_specific_id);
   llvm::Value* virtual_fn;
   if (function.clang_decl_id.has_value()) {
     // Use absolute vtables for clang interop - the itanium vtable contains
@@ -640,7 +635,7 @@ static auto HandleVirtualCall(FunctionContext& context,
          llvm::ConstantInt::get(
              i32_type, static_cast<uint64_t>(function.virtual_index) * 4)});
   }
-  return context.builder().CreateCall(function_info->type, virtual_fn, args);
+  return context.builder().CreateCall(function_info.type, virtual_fn, args);
 }
 
 auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
@@ -660,6 +655,11 @@ auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
     callee.inst_id = bound_method->function_decl_id;
   }
 
+  // Find the callee that the call instruction was type-checked against. This
+  // determines the meaning of the `arg_ids`.
+  auto inst_callee_function =
+      SemIR::GetCalleeAsFunction(*callee.file, callee.inst_id);
+
   // Map to the callee in the specific. This might be in a different file than
   // the one we're currently lowering.
   if (context.specific_id().has_value()) {
@@ -686,10 +686,15 @@ auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
     return;
   }
 
+  // Get the function info for the callee. If the callee has incomplete types,
+  // fall back to using the information from the call instruction.
   const auto& function_info =
       context.GetFileContext(callee.file)
           .GetOrCreateFunctionInfo(callee_function.function_id,
-                                   callee_function.resolved_specific_id);
+                                   callee_function.resolved_specific_id,
+                                   &context.GetFileContext(&context.sem_ir()),
+                                   inst_callee_function.function_id,
+                                   inst_callee_function.resolved_specific_id);
   CARBON_CHECK(!function_info->inexact,
                "Attempting to emit call to inexact function: {0}",
                *function_info->llvm_function);
@@ -722,8 +727,7 @@ auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
                  "Argument count mismatch: {0}", describe_call());
     call = context.builder().CreateCall(llvm_callee, args);
   } else {
-    call = HandleVirtualCall(context, args, callee.file, function,
-                             callee_function);
+    call = HandleVirtualCall(context, args, function, *function_info);
   }
 
   context.SetLocal(inst_id, call);

+ 390 - 0
toolchain/lower/testdata/packages/imported_type_completeness.carbon

@@ -0,0 +1,390 @@
+// 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
+//
+// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/int.carbon
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/lower/testdata/packages/imported_type_completeness.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/packages/imported_type_completeness.carbon
+
+// --- require_completion_in_call.carbon
+
+library "[[@TEST_NAME]]";
+
+class C(T:! type) {
+  // Core.Int(123) is required to be complete here.
+  fn F() -> Core.Int(123) {
+    return 123;
+  }
+
+  // Core.Int(456) is required to be complete here.
+  fn G(unused t: T) -> Core.Int(456) {
+    return 456;
+  }
+}
+
+fn G(T:! type, t: T) {
+  // Core.Int(123) is also required to be complete here. But because it's not
+  // dependent, we don't require it to be complete when forming a specific for G.
+  ({} as C(T)).F();
+
+  // Same for Core.Int(456).
+  ({} as C(T)).G(t);
+}
+
+// --- use_without_completion_in_call.carbon
+
+library "[[@TEST_NAME]]";
+
+import library "require_completion_in_call";
+
+fn I() {
+  // OK, even though nothing in the compilation of this file requires i123 or
+  // i456 to be complete.
+  G((), ());
+}
+
+// --- require_completion_in_vtable.carbon
+
+library "[[@TEST_NAME]]";
+
+base class C(T:! type) {
+  // Require Core.Int(11) and Core.Int(12) to be complete.
+  virtual fn F[unused self: Self]() -> Core.Int(11) { return 11; }
+  virtual fn G[unused self: Self](unused t: T) -> Core.Int(12) { return 12; }
+}
+
+// --- use_without_completion_in_vtable.carbon
+
+library "[[@TEST_NAME]]";
+
+import library "require_completion_in_vtable";
+
+// Trigger emission of C({})'s vtable without requiring Core.Int(N) to be
+// complete in this file.
+// TODO: This currently emits an undefined external reference to the vtable!
+var v: C({}) = {};
+
+// --- require_completion_in_virtual_call.carbon
+
+library "[[@TEST_NAME]]";
+
+import library "require_completion_in_vtable";
+
+fn F(T:! type, t: T) {
+  var c: C(T) = {};
+  c.F();
+  c.G(t);
+}
+
+// --- use_without_completion_in_virtual_call.carbon
+
+library "[[@TEST_NAME]]";
+
+import library "require_completion_in_virtual_call";
+
+class C {}
+
+fn CallF() {
+  F(C, {} as C);
+}
+
+// CHECK:STDOUT: ; ModuleID = 'require_completion_in_call.carbon'
+// CHECK:STDOUT: source_filename = "require_completion_in_call.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_plus_plus, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+// CHECK:STDOUT: !3 = !DIFile(filename: "require_completion_in_call.carbon", directory: "")
+// CHECK:STDOUT: ; ModuleID = 'use_without_completion_in_call.carbon'
+// CHECK:STDOUT: source_filename = "use_without_completion_in_call.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: @C.val.7d0 = internal constant {} zeroinitializer
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define void @_CI.Main() #0 !dbg !4 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   call void @_CG.Main.e43630e9a6c38c3f(), !dbg !7
+// CHECK:STDOUT:   ret void, !dbg !8
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define weak_odr void @"_COp.30c7a4d8cf1d8b9f:core.Destroy.Core"(ptr %self) #0 !dbg !9 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   ret void, !dbg !16
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define linkonce_odr void @_CG.Main.e43630e9a6c38c3f() #0 !dbg !17 {
+// CHECK:STDOUT:   %temp = alloca {}, align 8, !dbg !18
+// CHECK:STDOUT:   %temp1 = alloca {}, align 8, !dbg !19
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %temp), !dbg !18
+// CHECK:STDOUT:   %1 = call i123 @_CF.C.Main.e43630e9a6c38c3f(), !dbg !20
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %temp1), !dbg !19
+// CHECK:STDOUT:   %2 = call i456 @_CG.C.Main.e43630e9a6c38c3f(), !dbg !21
+// CHECK:STDOUT:   call void @"_COp.30c7a4d8cf1d8b9f:core.Destroy.Core"(ptr @C.val.7d0), !dbg !19
+// CHECK:STDOUT:   call void @"_COp.30c7a4d8cf1d8b9f:core.Destroy.Core"(ptr @C.val.7d0), !dbg !18
+// CHECK:STDOUT:   ret void, !dbg !22
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.lifetime.start.p0(ptr captures(none)) #1
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define linkonce_odr i123 @_CF.C.Main.e43630e9a6c38c3f() #0 !dbg !23 {
+// CHECK:STDOUT:   ret i123 123, !dbg !27
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define linkonce_odr i456 @_CG.C.Main.e43630e9a6c38c3f() #0 !dbg !28 {
+// CHECK:STDOUT:   ret i456 456, !dbg !32
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; uselistorder directives
+// CHECK:STDOUT: uselistorder ptr @llvm.lifetime.start.p0, { 1, 0 }
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { nounwind }
+// CHECK:STDOUT: attributes #1 = { 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_plus_plus, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+// CHECK:STDOUT: !3 = !DIFile(filename: "use_without_completion_in_call.carbon", directory: "")
+// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "I", linkageName: "_CI.Main", scope: null, file: !3, line: 6, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
+// CHECK:STDOUT: !6 = !{null}
+// CHECK:STDOUT: !7 = !DILocation(line: 9, column: 3, scope: !4)
+// CHECK:STDOUT: !8 = !DILocation(line: 6, column: 1, scope: !4)
+// CHECK:STDOUT: !9 = distinct !DISubprogram(name: "Op", linkageName: "_COp.30c7a4d8cf1d8b9f:core.Destroy.Core", scope: null, file: !10, line: 22, type: !11, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !14)
+// CHECK:STDOUT: !10 = !DIFile(filename: "require_completion_in_call.carbon", directory: "")
+// CHECK:STDOUT: !11 = !DISubroutineType(types: !12)
+// CHECK:STDOUT: !12 = !{null, !13}
+// CHECK:STDOUT: !13 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: null, size: 64)
+// CHECK:STDOUT: !14 = !{!15}
+// CHECK:STDOUT: !15 = !DILocalVariable(arg: 1, scope: !9, type: !13)
+// CHECK:STDOUT: !16 = !DILocation(line: 22, column: 4, scope: !9)
+// CHECK:STDOUT: !17 = distinct !DISubprogram(name: "G", linkageName: "_CG.Main.e43630e9a6c38c3f", scope: null, file: !10, line: 16, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !18 = !DILocation(line: 19, column: 4, scope: !17)
+// CHECK:STDOUT: !19 = !DILocation(line: 22, column: 4, scope: !17)
+// CHECK:STDOUT: !20 = !DILocation(line: 19, column: 3, scope: !17)
+// CHECK:STDOUT: !21 = !DILocation(line: 22, column: 3, scope: !17)
+// CHECK:STDOUT: !22 = !DILocation(line: 16, column: 1, scope: !17)
+// CHECK:STDOUT: !23 = distinct !DISubprogram(name: "F", linkageName: "_CF.C.Main.e43630e9a6c38c3f", scope: null, file: !10, line: 6, type: !24, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !24 = !DISubroutineType(types: !25)
+// CHECK:STDOUT: !25 = !{!26}
+// CHECK:STDOUT: !26 = !DIBasicType(name: "int", size: 123, encoding: DW_ATE_signed)
+// CHECK:STDOUT: !27 = !DILocation(line: 7, column: 5, scope: !23)
+// CHECK:STDOUT: !28 = distinct !DISubprogram(name: "G", linkageName: "_CG.C.Main.e43630e9a6c38c3f", scope: null, file: !10, line: 11, type: !29, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !29 = !DISubroutineType(types: !30)
+// CHECK:STDOUT: !30 = !{!31}
+// CHECK:STDOUT: !31 = !DIBasicType(name: "int", size: 456, encoding: DW_ATE_signed)
+// CHECK:STDOUT: !32 = !DILocation(line: 12, column: 5, scope: !28)
+// CHECK:STDOUT: ; ModuleID = 'require_completion_in_vtable.carbon'
+// CHECK:STDOUT: source_filename = "require_completion_in_vtable.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_plus_plus, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+// CHECK:STDOUT: !3 = !DIFile(filename: "require_completion_in_vtable.carbon", directory: "")
+// CHECK:STDOUT: ; ModuleID = 'use_without_completion_in_vtable.carbon'
+// CHECK:STDOUT: source_filename = "use_without_completion_in_vtable.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: @_Cv.Main = global { ptr } zeroinitializer
+// CHECK:STDOUT: @"_CC.Main.$vtable.6944efcfbb6a8360" = external unnamed_addr constant ptr
+// CHECK:STDOUT: @C.val.loc9_1 = internal constant { ptr } { ptr @"_CC.Main.$vtable.6944efcfbb6a8360" }
+// CHECK:STDOUT: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 0, ptr @_C__global_init.Main, ptr null }]
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define internal void @_C__global_init.Main() #0 !dbg !4 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 8 @_Cv.Main, ptr align 8 @C.val.loc9_1, i64 8, i1 false), !dbg !7
+// CHECK:STDOUT:   ret void, !dbg !8
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.memcpy.p0.p0.i64(ptr noalias writeonly captures(none), ptr noalias readonly captures(none), i64, i1 immarg) #1
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { nounwind }
+// CHECK:STDOUT: attributes #1 = { nocallback nofree 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_plus_plus, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+// CHECK:STDOUT: !3 = !DIFile(filename: "use_without_completion_in_vtable.carbon", directory: "")
+// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "__global_init", linkageName: "_C__global_init.Main", scope: null, file: !3, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
+// CHECK:STDOUT: !6 = !{null}
+// CHECK:STDOUT: !7 = !DILocation(line: 9, column: 1, scope: !4)
+// CHECK:STDOUT: !8 = !DILocation(line: 0, scope: !4)
+// CHECK:STDOUT: ; ModuleID = 'require_completion_in_virtual_call.carbon'
+// CHECK:STDOUT: source_filename = "require_completion_in_virtual_call.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define weak_odr void @"_COp.a8f528cd70a29ff8:core.Destroy.Core"(ptr %self) #0 !dbg !4 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   ret void, !dbg !10
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { nounwind }
+// 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_plus_plus, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+// CHECK:STDOUT: !3 = !DIFile(filename: "require_completion_in_virtual_call.carbon", directory: "")
+// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "Op", linkageName: "_COp.a8f528cd70a29ff8:core.Destroy.Core", scope: null, file: !3, line: 7, type: !5, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !8)
+// CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
+// CHECK:STDOUT: !6 = !{null, !7}
+// CHECK:STDOUT: !7 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: null, size: 64)
+// CHECK:STDOUT: !8 = !{!9}
+// CHECK:STDOUT: !9 = !DILocalVariable(arg: 1, scope: !4, type: !7)
+// CHECK:STDOUT: !10 = !DILocation(line: 7, column: 3, scope: !4)
+// CHECK:STDOUT: ; ModuleID = 'use_without_completion_in_virtual_call.carbon'
+// CHECK:STDOUT: source_filename = "use_without_completion_in_virtual_call.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: @C.val.4a3 = internal constant {} zeroinitializer
+// CHECK:STDOUT: @"_CC.Main.$vtable.0c4ab795cec6cb27" = external unnamed_addr constant ptr
+// CHECK:STDOUT: @C.val.4a3.loc9_11.3 = internal constant {} zeroinitializer
+// CHECK:STDOUT: @C.val.011.anon = internal constant { ptr } { ptr @"_CC.Main.$vtable.0c4ab795cec6cb27" }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define void @_CCallF.Main() #0 !dbg !4 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %.loc9_9.2.temp = alloca {}, align 8, !dbg !7
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %.loc9_9.2.temp), !dbg !7
+// CHECK:STDOUT:   call void @_CF.Main.0c4ab795cec6cb27(ptr @C.val.4a3.loc9_11.3), !dbg !8
+// CHECK:STDOUT:   call void @"_COp.71e25083d63c4d6c:core.Destroy.Core"(ptr @C.val.4a3), !dbg !7
+// CHECK:STDOUT:   ret void, !dbg !9
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define weak_odr void @"_COp.71e25083d63c4d6c:core.Destroy.Core"(ptr %self) #0 !dbg !10 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   ret void, !dbg !16
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define weak_odr void @"_COp.a8f528cd70a29ff8:core.Destroy.Core"(ptr %self) #0 !dbg !17 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   ret void, !dbg !21
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define weak_odr void @"_COp.e4f158e1c31bad9e:core.Destroy.Core"(ptr %self) #0 !dbg !22 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   ret void, !dbg !25
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.lifetime.start.p0(ptr captures(none)) #1
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define linkonce_odr void @_CF.Main.0c4ab795cec6cb27(ptr %t) #0 !dbg !26 {
+// CHECK:STDOUT:   %1 = alloca { ptr }, align 8, !dbg !29
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %1), !dbg !29
+// CHECK:STDOUT:   %vptr = getelementptr inbounds nuw { ptr }, ptr %1, i32 0, i32 0, !dbg !30
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 8 %1, ptr align 8 @C.val.011.anon, i64 8, i1 false), !dbg !29
+// CHECK:STDOUT:   %vtable = load ptr, ptr %1, align 8, !dbg !31
+// CHECK:STDOUT:   %2 = call ptr @llvm.load.relative.i32(ptr %vtable, i32 0), !dbg !31
+// CHECK:STDOUT:   %3 = call i11 %2(ptr %1), !dbg !31
+// CHECK:STDOUT:   %vtable1 = load ptr, ptr %1, align 8, !dbg !32
+// CHECK:STDOUT:   %4 = call ptr @llvm.load.relative.i32(ptr %vtable1, i32 4), !dbg !32
+// CHECK:STDOUT:   %5 = call i12 %4(ptr %1, ptr %t), !dbg !32
+// CHECK:STDOUT:   call void @"_COp.e4f158e1c31bad9e:core.Destroy.Core"(ptr %1), !dbg !29
+// CHECK:STDOUT:   ret void, !dbg !33
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.memcpy.p0.p0.i64(ptr noalias writeonly captures(none), ptr noalias readonly captures(none), i64, i1 immarg) #2
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define linkonce_odr i11 @_CF.C.Main.0c4ab795cec6cb27(ptr %self) #0 !dbg !34 {
+// CHECK:STDOUT:   ret i11 11, !dbg !41
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: read)
+// CHECK:STDOUT: declare ptr @llvm.load.relative.i32(ptr, i32) #3
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define linkonce_odr i12 @_CG.C.Main.0c4ab795cec6cb27(ptr %self, ptr %t) #0 !dbg !42 {
+// CHECK:STDOUT:   ret i12 12, !dbg !49
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; uselistorder directives
+// CHECK:STDOUT: uselistorder ptr @llvm.load.relative.i32, { 1, 0 }
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { nounwind }
+// CHECK:STDOUT: attributes #1 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) }
+// CHECK:STDOUT: attributes #2 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }
+// CHECK:STDOUT: attributes #3 = { nocallback nofree nosync nounwind willreturn memory(argmem: read) }
+// 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_plus_plus, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+// CHECK:STDOUT: !3 = !DIFile(filename: "use_without_completion_in_virtual_call.carbon", directory: "")
+// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "CallF", linkageName: "_CCallF.Main", scope: null, file: !3, line: 8, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
+// CHECK:STDOUT: !6 = !{null}
+// CHECK:STDOUT: !7 = !DILocation(line: 9, column: 8, scope: !4)
+// CHECK:STDOUT: !8 = !DILocation(line: 9, column: 3, scope: !4)
+// CHECK:STDOUT: !9 = !DILocation(line: 8, column: 1, scope: !4)
+// CHECK:STDOUT: !10 = distinct !DISubprogram(name: "Op", linkageName: "_COp.71e25083d63c4d6c:core.Destroy.Core", scope: null, file: !3, line: 9, type: !11, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !14)
+// CHECK:STDOUT: !11 = !DISubroutineType(types: !12)
+// CHECK:STDOUT: !12 = !{null, !13}
+// CHECK:STDOUT: !13 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: null, size: 64)
+// CHECK:STDOUT: !14 = !{!15}
+// CHECK:STDOUT: !15 = !DILocalVariable(arg: 1, scope: !10, type: !13)
+// CHECK:STDOUT: !16 = !DILocation(line: 9, column: 8, scope: !10)
+// CHECK:STDOUT: !17 = distinct !DISubprogram(name: "Op", linkageName: "_COp.a8f528cd70a29ff8:core.Destroy.Core", scope: null, file: !18, line: 7, type: !11, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !19)
+// CHECK:STDOUT: !18 = !DIFile(filename: "require_completion_in_virtual_call.carbon", directory: "")
+// CHECK:STDOUT: !19 = !{!20}
+// CHECK:STDOUT: !20 = !DILocalVariable(arg: 1, scope: !17, type: !13)
+// CHECK:STDOUT: !21 = !DILocation(line: 7, column: 3, scope: !17)
+// CHECK:STDOUT: !22 = distinct !DISubprogram(name: "Op", linkageName: "_COp.e4f158e1c31bad9e:core.Destroy.Core", scope: null, file: !18, line: 7, type: !11, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !23)
+// CHECK:STDOUT: !23 = !{!24}
+// CHECK:STDOUT: !24 = !DILocalVariable(arg: 1, scope: !22, type: !13)
+// CHECK:STDOUT: !25 = !DILocation(line: 7, column: 3, scope: !22)
+// CHECK:STDOUT: !26 = distinct !DISubprogram(name: "F", linkageName: "_CF.Main.0c4ab795cec6cb27", scope: null, file: !18, line: 6, type: !11, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !27)
+// CHECK:STDOUT: !27 = !{!28}
+// CHECK:STDOUT: !28 = !DILocalVariable(arg: 1, scope: !26, type: !13)
+// CHECK:STDOUT: !29 = !DILocation(line: 7, column: 3, scope: !26)
+// CHECK:STDOUT: !30 = !DILocation(line: 7, column: 17, scope: !26)
+// CHECK:STDOUT: !31 = !DILocation(line: 8, column: 3, scope: !26)
+// CHECK:STDOUT: !32 = !DILocation(line: 9, column: 3, scope: !26)
+// CHECK:STDOUT: !33 = !DILocation(line: 6, column: 1, scope: !26)
+// CHECK:STDOUT: !34 = distinct !DISubprogram(name: "F", linkageName: "_CF.C.Main.0c4ab795cec6cb27", scope: null, file: !35, line: 6, type: !36, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !39)
+// CHECK:STDOUT: !35 = !DIFile(filename: "require_completion_in_vtable.carbon", directory: "")
+// CHECK:STDOUT: !36 = !DISubroutineType(types: !37)
+// CHECK:STDOUT: !37 = !{!38, !13}
+// CHECK:STDOUT: !38 = !DIBasicType(name: "int", size: 11, encoding: DW_ATE_signed)
+// CHECK:STDOUT: !39 = !{!40}
+// CHECK:STDOUT: !40 = !DILocalVariable(arg: 1, scope: !34, type: !13)
+// CHECK:STDOUT: !41 = !DILocation(line: 6, column: 55, scope: !34)
+// CHECK:STDOUT: !42 = distinct !DISubprogram(name: "G", linkageName: "_CG.C.Main.0c4ab795cec6cb27", scope: null, file: !35, line: 7, type: !43, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !46)
+// CHECK:STDOUT: !43 = !DISubroutineType(types: !44)
+// CHECK:STDOUT: !44 = !{!45, !13, !13}
+// CHECK:STDOUT: !45 = !DIBasicType(name: "int", size: 12, encoding: DW_ATE_signed)
+// CHECK:STDOUT: !46 = !{!47, !48}
+// CHECK:STDOUT: !47 = !DILocalVariable(arg: 1, scope: !42, type: !13)
+// CHECK:STDOUT: !48 = !DILocalVariable(arg: 2, scope: !42, type: !13)
+// CHECK:STDOUT: !49 = !DILocation(line: 7, column: 66, scope: !42)