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

Store and reuse lowered parameter order (#6593)

This resolves a longstanding TODO in `file_context.cpp`, and prepares
lowering to support compound return forms.
Geoff Romer 3 месяцев назад
Родитель
Сommit
75713908f4

+ 409 - 265
toolchain/lower/file_context.cpp

@@ -50,7 +50,7 @@ FileContext::FileContext(Context& context, const SemIR::File& sem_ir,
       inst_namer_(inst_namer),
       vlog_stream_(vlog_stream),
       functions_(LoweredFunctionStore::MakeForOverwrite(sem_ir.functions())),
-      specific_functions_(sem_ir.specifics(), nullptr),
+      specific_functions_(sem_ir.specifics(), std::nullopt),
       types_(LoweredTypeStore::MakeWithExplicitSize(
           sem_ir.constant_values().ConcreteStoreSize(),
           sem_ir.constant_values().GetTypeIdTag(), {nullptr, nullptr})),
@@ -147,12 +147,12 @@ auto FileContext::LowerDefinitions() -> void {
   // variables.
   if (auto global_ctor_id = sem_ir().global_ctor_id();
       global_ctor_id.has_value()) {
-    auto* llvm_function = BuildFunctionDecl(global_ctor_id);
+    auto llvm_function = BuildFunctionDecl(global_ctor_id);
     functions_.Set(global_ctor_id, llvm_function);
     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(), llvm_function,
+    llvm::appendToGlobalCtors(llvm_module(), llvm_function->llvm_function,
                               /*Priority=*/0);
   }
 }
@@ -245,128 +245,359 @@ auto FileContext::GetConstant(SemIR::ConstantId const_id,
   return global_variable;
 }
 
-auto FileContext::GetOrCreateFunction(SemIR::FunctionId function_id,
-                                      SemIR::SpecificId specific_id)
-    -> llvm::Function* {
+auto FileContext::GetOrCreateFunctionInfo(SemIR::FunctionId function_id,
+                                          SemIR::SpecificId specific_id)
+    -> std::optional<FunctionInfo>& {
   // 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);
+  auto& result = GetFunctionInfo(function_id, specific_id);
+  if (!result) {
+    result = BuildFunctionDecl(function_id, specific_id);
   }
-  return *result;
+  return result;
 }
 
-auto FileContext::BuildFunctionTypeInfo(const SemIR::Function& function,
-                                        SemIR::SpecificId specific_id)
-    -> FunctionTypeInfo {
-  const auto return_info =
-      SemIR::ReturnTypeInfo::ForFunction(sem_ir(), function, specific_id);
-
-  if (!return_info.is_valid()) {
-    // The return type has not been completed, create a trivial type instead.
-    return {.type =
-                llvm::FunctionType::get(llvm::Type::getVoidTy(llvm_context()),
-                                        /*isVarArg=*/false)};
+// State machine for building a FunctionTypeInfo from SemIR.
+//
+// The main difficulty this class encapsulates is that each abstraction level
+// has different expectations about how the return is reflected in the parameter
+// list.
+// - In SemIR, if the function has an initializing return form, it has a
+//   corresponding output parameter at the end of the parameter list.
+// - In LLVM IR, if the SemIR has an output parameter _and_ that parameter's
+//   type has an in-place initializing representation, we emit a corresponding
+//   `sret` output parameter (and the function's return type is void). By
+//   convention the output parameter goes at the start of the parameter list.
+// - In LLVM debug info, the list of parameter types always starts with the
+//   return type (which doubles as the type of the return parameter, if there
+//   is one).
+//
+// 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.
+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
+  // 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;
+
+ 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 {
+    lowered_param_pattern_ids_.clear();
+    param_types_.clear();
+    param_di_types_.clear();
+    return_type_ = nullptr;
+    SetReturnByCopy(SemIR::TypeId::None);
+    return false;
   }
 
-  auto get_llvm_type = [&](SemIR::TypeId type_id) -> llvm::Type* {
-    if (!type_id.has_value()) {
-      return nullptr;
-    }
-    return GetType(type_id);
-  };
+  // Handles the function's return form. The argument can be None, indicating
+  // that there was no explicitly declared 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;
+
+  // 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 {
+    CARBON_CHECK(return_type_ == nullptr);
+    CARBON_CHECK(param_di_types_.empty());
+    auto lowered_return_types = GetLoweredTypes(return_type_id);
+    return_type_ = lowered_return_types.llvm_ir_type;
+    param_di_types_.push_back(lowered_return_types.llvm_di_type);
+    return true;
+  }
 
-  // TODO: expose the `Call` parameter patterns in `Function`, and use them here
-  // instead of reconstructing them via the syntactic parameter lists.
-  auto implicit_param_patterns =
-      sem_ir().inst_blocks().GetOrEmpty(function.implicit_param_patterns_id);
-  auto param_patterns =
-      sem_ir().inst_blocks().GetOrEmpty(function.param_patterns_id);
+  // 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);
+    // TODO: replace this with a reference type.
+    param_di_types_.push_back(
+        context_.context().di_builder().createPointerType(nullptr, 8));
+    return true;
+  }
 
-  auto* return_type = get_llvm_type(return_info.type_id);
+  // 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);
+    // We don't add to param_di_types_ because that will be handled by the
+    // loop over the SemIR parameters.
+    return true;
+  }
+
+  // Handles the given `Call` parameter, which must be a *ParamPattern inst.
+  // This should be called on parameter patterns in the order that they should
+  // appear in the LLVM IR parameter list, so in particular it should be called
+  // on the `OutParamPattern` (if any) first. It should be called on all `Call`
+  // 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.
+  auto HandleParameter(SemIR::InstId param_pattern_id) -> bool;
+
+  // Records that the given parameter pattern is lowered to the given
+  // IR and DI types.
+  auto AddLoweredParam(SemIR::InstId param_pattern_id, LoweredTypes param_types)
+      -> bool {
+    lowered_param_pattern_ids_.push_back(param_pattern_id);
+    param_types_.push_back(param_types.llvm_ir_type);
+    param_di_types_.push_back(param_types.llvm_di_type);
+    return true;
+  }
 
-  llvm::SmallVector<llvm::Type*> param_types;
-  // Compute the return type to use for the LLVM function. If the initializing
-  // representation doesn't produce a value, set the return type to void.
+  // Records that the given parameter pattern is not lowered to an LLVM
+  // parameter.
+  auto IgnoreParam(SemIR::InstId param_pattern_id) -> bool {
+    unused_param_pattern_ids_.push_back(param_pattern_id);
+    return true;
+  }
+
+  // Builds and returns a FunctionTypeInfo from the accumulated information.
+  auto Finalize() -> 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;
+
+  FileContext& context_;
+  const SemIR::SpecificId specific_id_;
+
+  // 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
+  // (e.g. if they are stateless) or reordered (e.g. the return parameter, if
+  // any, always goes first).
+  llvm::SmallVector<llvm::Type*> param_types_;
+
+  // The LLLVM DI representation of the parameter list. As required by LLVM DI
+  // convention, this starts with the function's return type, and ends with the
+  // DI representations of param_types_ (in the same order). Note that those
+  // two ranges may overlap: if the first element of param_types_ represents
+  // a return parameter, the first element of param_di_types_ corresponds to it
+  // while also representing the return type.
+  llvm::SmallVector<llvm::Metadata*> param_di_types_;
+
+  // The SemIR function's `Call` param patterns that correspond to param_types_,
+  // in the same order.
+  llvm::SmallVector<SemIR::InstId> lowered_param_pattern_ids_;
+
+  // Any `Call` param patterns that aren't present in
+  // reordered_param_pattern_ids_.
+  llvm::SmallVector<SemIR::InstId> unused_param_pattern_ids_;
+
+  // 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;
+
+  // If not null, the LLVM function's first parameter should have a `sret`
+  // attribute with this type.
+  llvm::Type* sret_type_ = nullptr;
+};
+
+auto FileContext::FunctionTypeInfoBuilder::Build(
+    const SemIR::Function& function) && -> FunctionTypeInfo {
   // TODO: For the `Run` entry point, remap return type to i32 if it doesn't
   // return a value.
-  llvm::Type* function_return_type =
-      (return_info.is_valid() &&
-       return_info.init_repr.kind == SemIR::InitRepr::ByCopy)
-          ? return_type
-          : llvm::Type::getVoidTy(llvm_context());
-
-  // TODO: Consider either storing `param_inst_ids` somewhere so that we can
-  // reuse it from `BuildFunctionDefinition` and when building calls, or factor
-  // out a mechanism to compute the mapping between parameters and arguments on
-  // demand.
-  llvm::SmallVector<SemIR::InstId> param_inst_ids;
-  auto max_llvm_params = (return_info.has_return_slot() ? 1 : 0) +
-                         implicit_param_patterns.size() + param_patterns.size();
-  param_types.reserve(max_llvm_params);
-  param_inst_ids.reserve(max_llvm_params);
-  auto return_param_id = SemIR::InstId::None;
-  if (return_info.has_return_slot()) {
-    param_types.push_back(
-        llvm::PointerType::get(llvm_context(), /*AddressSpace=*/0));
-    auto return_patterns =
-        sem_ir_->inst_blocks().Get(function.return_patterns_id);
-    CARBON_CHECK(return_patterns.size() == 1,
-                 "TODO: implement support for multiple return params");
-    return_param_id = return_patterns[0];
-    param_inst_ids.push_back(return_param_id);
+
+  auto call_param_pattern_ids =
+      context_.sem_ir().inst_blocks().Get(function.call_param_patterns_id);
+  lowered_param_pattern_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 (!HandleReturnForm(function.return_form_inst_id)) {
+    return Finalize();
   }
-  for (auto param_pattern_id : llvm::concat<const SemIR::InstId>(
-           implicit_param_patterns, param_patterns)) {
-    // TODO: Handle a general pattern here, rather than assuming that each
-    // parameter pattern contains at most one binding.
-    auto param_pattern_info = SemIR::Function::GetParamPatternInfoFromPatternId(
-        sem_ir(), param_pattern_id);
-    if (!param_pattern_info) {
-      continue;
+  if (semir_return_param_index_ >= 0) {
+    CARBON_CHECK(semir_return_param_index_ ==
+                     static_cast<int>(call_param_pattern_ids.size()) - 1,
+                 "Unexpected parameter order");
+    // Handle the return parameter first, because it goes first in the LLVM
+    // convention. We remove it from call_param_pattern_ids so we don't revisit
+    // it in the subsequent loop.
+    if (!HandleParameter(call_param_pattern_ids.consume_back())) {
+      return Finalize();
     }
-    // TODO: Use a more general mechanism to determine if the binding is a
-    // reference binding.
-    if (param_pattern_info->inst.kind == SemIR::RefParamPattern::Kind ||
-        param_pattern_info->inst.kind == SemIR::VarParamPattern::Kind) {
-      param_types.push_back(
-          llvm::PointerType::get(llvm_context(), /*AddressSpace=*/0));
-      param_inst_ids.push_back(param_pattern_id);
-      continue;
+  }
+  for (auto param_pattern_id : call_param_pattern_ids) {
+    if (!HandleParameter(param_pattern_id)) {
+      return Finalize();
     }
-    auto param_type_id = ExtractScrutineeType(
-        sem_ir(), SemIR::GetTypeOfInstInSpecific(sem_ir(), specific_id,
-                                                 param_pattern_info->inst_id));
-    CARBON_CHECK(
-        !param_type_id.AsConstantId().is_symbolic(),
-        "Found symbolic type id after resolution when lowering type {0}.",
-        param_pattern_info->inst.type_id);
-    switch (auto value_rep = SemIR::ValueRepr::ForType(sem_ir(), param_type_id);
-            value_rep.kind) {
-      case SemIR::ValueRepr::Unknown:
-        // This parameter type is incomplete. Fallback to describing the
-        // function type as `void()`.
-        return {.type = llvm::FunctionType::get(
-                    llvm::Type::getVoidTy(llvm_context()),
-                    /*isVarArg=*/false)};
-      case SemIR::ValueRepr::Dependent:
-        CARBON_FATAL("Lowering function with dependent parameter type");
-      case SemIR::ValueRepr::None:
-        break;
-      case SemIR::ValueRepr::Copy:
-      case SemIR::ValueRepr::Custom:
-      case SemIR::ValueRepr::Pointer:
-        auto* param_types_to_add = get_llvm_type(value_rep.type_id);
-        param_types.push_back(param_types_to_add);
-        param_inst_ids.push_back(param_pattern_id);
-        break;
+  }
+
+  return Finalize();
+}
+
+auto FileContext::FunctionTypeInfoBuilder::HandleReturnForm(
+    SemIR::InstId return_form_inst_id) -> bool {
+  if (!return_form_inst_id.has_value()) {
+    return SetReturnByCopy(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));
+  CARBON_KIND_SWITCH(return_form_inst) {
+    case CARBON_KIND(SemIR::InitForm init_form): {
+      CARBON_CHECK(
+          std::exchange(semir_return_param_index_, init_form.index.index) == -1,
+          "TODO: Generalize this to support compound return forms");
+      auto return_type_id =
+          context_.sem_ir().types().GetTypeIdForTypeConstantId(
+              SemIR::GetConstantValueInSpecific(
+                  context_.sem_ir(), specific_id_,
+                  init_form.type_component_inst_id));
+      switch (
+          SemIR::InitRepr::ForType(context_.sem_ir(), return_type_id).kind) {
+        case SemIR::InitRepr::InPlace: {
+          return SetReturnInPlace(return_type_id);
+        }
+        case SemIR::InitRepr::ByCopy: {
+          return SetReturnByCopy(return_type_id);
+        }
+        case SemIR::InitRepr::None:
+          return SetReturnByCopy(SemIR::TypeId::None);
+        case SemIR::InitRepr::Dependent:
+        case SemIR::InitRepr::Incomplete:
+        case SemIR::InitRepr::Abstract:
+          return Abort();
+      }
+    }
+    case CARBON_KIND(SemIR::RefForm ref_form): {
+      auto return_type_id =
+          context_.sem_ir().types().GetTypeIdForTypeConstantId(
+              SemIR::GetConstantValueInSpecific(
+                  context_.sem_ir(), specific_id_,
+                  ref_form.type_component_inst_id));
+      return SetReturnByReference(return_type_id);
     }
+    default:
+      CARBON_FATAL("Unexpected inst kind: {0}", return_form_inst);
   }
-  return {.type = llvm::FunctionType::get(function_return_type, param_types,
+}
+
+auto FileContext::FunctionTypeInfoBuilder::HandleParameter(
+    SemIR::InstId param_pattern_id) -> bool {
+  auto param_pattern = context_.sem_ir().insts().Get(param_pattern_id);
+  auto param_type_id = ExtractScrutineeType(
+      context_.sem_ir(),
+      SemIR::GetTypeOfInstInSpecific(context_.sem_ir(), 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};
+  };
+
+  CARBON_CHECK(
+      !param_type_id.AsConstantId().is_symbolic(),
+      "Found symbolic type id after resolution when lowering type {0}.",
+      param_pattern.type_id());
+  CARBON_KIND_SWITCH(param_pattern) {
+    case SemIR::RefParamPattern::Kind:
+    case SemIR::VarParamPattern::Kind: {
+      return AddLoweredParam(param_pattern_id, ref_lowered_types());
+    }
+    case SemIR::OutParamPattern::Kind: {
+      switch (SemIR::InitRepr::ForType(context_.sem_ir(), param_type_id).kind) {
+        case SemIR::InitRepr::InPlace:
+          return AddLoweredParam(param_pattern_id, ref_lowered_types());
+        case SemIR::InitRepr::ByCopy:
+        case SemIR::InitRepr::None:
+          return IgnoreParam(param_pattern_id);
+        case SemIR::InitRepr::Dependent:
+        case SemIR::InitRepr::Incomplete:
+        case SemIR::InitRepr::Abstract:
+          return Abort();
+      }
+    }
+    case SemIR::ValueParamPattern::Kind: {
+      switch (auto value_rep =
+                  SemIR::ValueRepr::ForType(context_.sem_ir(), param_type_id);
+              value_rep.kind) {
+        case SemIR::ValueRepr::Unknown:
+          return Abort();
+        case SemIR::ValueRepr::Dependent:
+          CARBON_FATAL("Lowering function parameter with dependent type: {0}",
+                       param_pattern);
+        case SemIR::ValueRepr::None:
+          return IgnoreParam(param_pattern_id);
+        case SemIR::ValueRepr::Copy:
+        case SemIR::ValueRepr::Custom:
+        case SemIR::ValueRepr::Pointer: {
+          if (value_rep.type_id.has_value()) {
+            return AddLoweredParam(param_pattern_id,
+                                   GetLoweredTypes(value_rep.type_id));
+          } else {
+            return IgnoreParam(param_pattern_id);
+          }
+        }
+      }
+    }
+    default:
+      CARBON_FATAL("Unexpected inst kind: {0}", param_pattern);
+  }
+}
+
+auto FileContext::FunctionTypeInfoBuilder::Finalize() -> FunctionTypeInfo {
+  CARBON_CHECK(!param_di_types_.empty());
+  auto& di_builder = context_.context().di_builder();
+  return {.type = llvm::FunctionType::get(return_type_, param_types_,
                                           /*isVarArg=*/false),
-          .param_inst_ids = std::move(param_inst_ids),
-          .return_type = return_type,
-          .return_param_id = return_param_id};
+          .di_type = di_builder.createSubroutineType(
+              di_builder.getOrCreateTypeArray(param_di_types_),
+              llvm::DINode::FlagZero),
+          .lowered_param_pattern_ids = std::move(lowered_param_pattern_ids_),
+          .unused_param_pattern_ids = std::move(unused_param_pattern_ids_),
+          .sret_type = sret_type_};
+}
+
+auto FileContext::FunctionTypeInfoBuilder::GetLoweredTypes(
+    SemIR::TypeId type_id) -> LoweredTypes {
+  if (!type_id.has_value()) {
+    return {.llvm_ir_type = llvm::Type::getVoidTy(context_.llvm_context()),
+            .llvm_di_type = nullptr};
+  }
+  auto result = context_.GetTypeAndDIType(type_id);
+  if (result.llvm_di_type == nullptr) {
+    // TODO: figure out what type should go here, or ensure this doesn't
+    // happen.
+    result.llvm_di_type =
+        context_.context().di_builder().createPointerType(nullptr, 8);
+  }
+  return result;
 }
 
 auto FileContext::HandleReferencedCppFunction(clang::FunctionDecl* cpp_decl)
@@ -408,18 +639,18 @@ auto FileContext::HandleReferencedSpecificFunction(
 
 auto FileContext::BuildFunctionDecl(SemIR::FunctionId function_id,
                                     SemIR::SpecificId specific_id)
-    -> llvm::Function* {
+    -> std::optional<FunctionInfo> {
   const auto& function = sem_ir().functions().Get(function_id);
 
   // Don't lower generic functions. Note that associated functions in interfaces
   // have `Self` in scope, so are implicitly generic functions.
   if (function.generic_id.has_value() && !specific_id.has_value()) {
-    return nullptr;
+    return std::nullopt;
   }
 
   // Don't lower builtins.
   if (function.builtin_function_kind() != SemIR::BuiltinFunctionKind::None) {
-    return nullptr;
+    return std::nullopt;
   }
 
   // Don't lower C++ functions that use a thunk. We will never reference them
@@ -427,13 +658,14 @@ auto FileContext::BuildFunctionDecl(SemIR::FunctionId function_id,
   // corresponding C++ function anyway.
   if (function.special_function_kind ==
       SemIR::Function::SpecialFunctionKind::HasCppThunk) {
-    return nullptr;
+    return std::nullopt;
   }
 
   // TODO: Consider tracking whether the function has been used, and only
   // lowering it if it's needed.
 
-  auto function_type_info = BuildFunctionTypeInfo(function, specific_id);
+  auto function_type_info =
+      FunctionTypeInfoBuilder(this, specific_id).Build(function);
 
   // TODO: For an imported inline function, consider generating an
   // `available_externally` definition.
@@ -456,7 +688,13 @@ auto FileContext::BuildFunctionDecl(SemIR::FunctionId function_id,
     // 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;
+    return {{.type = function_type_info.type,
+             .di_type = function_type_info.di_type,
+             .lowered_param_pattern_ids =
+                 std::move(function_type_info.lowered_param_pattern_ids),
+             .unused_param_pattern_ids =
+                 std::move(function_type_info.unused_param_pattern_ids),
+             .llvm_function = existing}};
   }
 
   // If this is a C++ function, tell Clang that we referenced it.
@@ -484,20 +722,25 @@ auto FileContext::BuildFunctionDecl(SemIR::FunctionId function_id,
                "Mangled name collision: {0}", mangled_name);
 
   // Set up parameters and the return slot.
-  for (auto [inst_id, arg] : llvm::zip_equal(function_type_info.param_inst_ids,
-                                             llvm_function->args())) {
-    auto name_id = SemIR::NameId::None;
-    if (inst_id == function_type_info.return_param_id) {
-      name_id = SemIR::NameId::ReturnSlot;
-      arg.addAttr(llvm::Attribute::getWithStructRetType(
-          llvm_context(), function_type_info.return_type));
-    } else {
-      name_id = SemIR::GetPrettyNameFromPatternId(sem_ir(), inst_id);
-    }
-    arg.setName(sem_ir().names().GetIRBaseName(name_id));
+  for (auto [inst_id, arg] :
+       llvm::zip_equal(function_type_info.lowered_param_pattern_ids,
+                       llvm_function->args())) {
+    arg.setName(sem_ir().names().GetIRBaseName(
+        SemIR::GetPrettyNameFromPatternId(sem_ir(), inst_id)));
+  }
+  if (function_type_info.sret_type != nullptr) {
+    auto& return_arg = *llvm_function->args().begin();
+    return_arg.addAttr(llvm::Attribute::getWithStructRetType(
+        llvm_context(), function_type_info.sret_type));
   }
 
-  return llvm_function;
+  return {{.type = function_type_info.type,
+           .di_type = function_type_info.di_type,
+           .lowered_param_pattern_ids =
+               std::move(function_type_info.lowered_param_pattern_ids),
+           .unused_param_pattern_ids =
+               std::move(function_type_info.unused_param_pattern_ids),
+           .llvm_function = llvm_function}};
 }
 
 // Find the file and function ID describing the definition of a function.
@@ -576,8 +819,8 @@ auto FileContext::BuildFunctionBody(SemIR::FunctionId function_id,
   // instead of our context.
   const auto& definition_ir = definition_context.sem_ir();
 
-  auto* llvm_function = GetFunction(function_id, specific_id);
-  CARBON_CHECK(llvm_function,
+  auto function_info = GetFunctionInfo(function_id, specific_id);
+  CARBON_CHECK(function_info && function_info->llvm_function,
                "Attempting to define function that was not declared");
 
   const auto& body_block_ids = definition_function.body_block_ids;
@@ -619,78 +862,45 @@ auto FileContext::BuildFunctionBody(SemIR::FunctionId function_id,
       // TODO: Should we generate an InlineHint for some functions? Perhaps for
       // those defined in the API file?
     }
-    llvm_function->addFnAttrs(attr_builder);
+    function_info->llvm_function->addFnAttrs(attr_builder);
   }
 
-  auto* subprogram =
-      BuildDISubprogram(declaration_function, specific_id, llvm_function);
+  auto* subprogram = BuildDISubprogram(declaration_function, *function_info);
   FunctionContext function_lowering(
-      definition_context, llvm_function, *this, specific_id,
+      definition_context, function_info->llvm_function, *this, specific_id,
       coalescer_.InitializeFingerprintForSpecific(specific_id), subprogram,
       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 = 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
-  // the same parameter ordering.
-
-  // Lowers the given parameter. Must be called in LLVM calling convention
-  // parameter order.
-  auto lower_param = [&](SemIR::InstId param_id) {
-    // Get the value of the parameter from the function argument.
-    llvm::Value* param_value;
-
-    // The `type_id` of a parameter tracks the parameter's type.
-    CARBON_CHECK(definition_ir.insts().Is<SemIR::AnyParam>(param_id));
-    auto param_type = function_lowering.GetTypeIdOfInst(param_id);
-    if (function_lowering.GetValueRepr(param_type).repr.kind !=
-        SemIR::ValueRepr::None) {
-      param_value = llvm_function->getArg(param_index);
-      ++param_index;
-    } else {
-      param_value =
-          llvm::PoisonValue::get(function_lowering.GetType(param_type));
-    }
-    // The value of the parameter is the value of the argument.
-    function_lowering.SetLocal(param_id, param_value);
+
+  // Returns the AnyParam inst with the same index as param_pattern_id
+  // (which must be an AnyParamPattern).
+  auto param_for_param_pattern =
+      [&](SemIR::InstId param_pattern_id) -> SemIR::InstId {
+    auto sem_ir_index = sem_ir()
+                            .insts()
+                            .GetAs<SemIR::AnyParamPattern>(param_pattern_id)
+                            .index.index;
+    return call_param_ids[sem_ir_index];
   };
 
-  // Lower to the return slot parameter.
-  auto return_patterns = sem_ir_->inst_blocks().GetOrEmpty(
-      declaration_function.return_patterns_id);
-  if (!return_patterns.empty()) {
-    CARBON_CHECK(sem_ir_->inst_blocks()
-                         .Get(declaration_function.return_patterns_id)
-                         .size() == 1,
-                 "TODO: implement support for multiple return patterns");
-    auto call_param_id = call_param_ids.consume_back();
-    // 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 `return_patterns.empty()` queries whether there are any output
-    // parameters at the SemIR level.
-    if (SemIR::ReturnTypeInfo::ForFunction(sem_ir(), declaration_function,
-                                           specific_id)
-            .has_return_slot()) {
-      lower_param(call_param_id);
-    } else {
-      // The return slot might still be mentioned as a destination location, but
-      // shouldn't actually be used for anything, so we can use a poison value
-      // for it.
-      function_lowering.SetLocal(call_param_id,
-                                 llvm::PoisonValue::get(llvm::PointerType::get(
-                                     llvm_context(), /*AddressSpace=*/0)));
-    }
+  // Add local variables for the parameters.
+  for (auto [llvm_index, param_pattern_id] :
+       llvm::enumerate(function_info->lowered_param_pattern_ids)) {
+    function_lowering.SetLocal(
+        param_for_param_pattern(param_pattern_id),
+        function_info->llvm_function->getArg(llvm_index));
   }
 
-  // Lower the remaining call parameters.
-  for (auto param_id : call_param_ids) {
-    lower_param(param_id);
+  // Add local variables for the SemIR parameters that aren't LLVM parameters.
+  // These shouldn't actually be used, so they're set to poison values.
+  for (auto [llvm_index, param_pattern_id] :
+       llvm::enumerate(function_info->unused_param_pattern_ids)) {
+    auto param_id = param_for_param_pattern(param_pattern_id);
+    function_lowering.SetLocal(
+        param_id,
+        llvm::PoisonValue::get(function_lowering.GetTypeOfInst(param_id)));
   }
 
   auto decl_block_id = SemIR::InstBlockId::None;
@@ -709,7 +919,7 @@ auto FileContext::BuildFunctionBody(SemIR::FunctionId function_id,
     CARBON_VLOG("Lowering {0}\n", block_id);
     auto* llvm_block = function_lowering.GetBlock(block_id);
     // Keep the LLVM blocks in lexical order.
-    llvm_block->moveBefore(llvm_function->end());
+    llvm_block->moveBefore(function_info->llvm_function->end());
     function_lowering.builder().SetInsertPoint(llvm_block);
     function_lowering.LowerBlockContents(block_id);
   };
@@ -734,10 +944,10 @@ auto FileContext::BuildFunctionBody(SemIR::FunctionId function_id,
   }
 
   // LLVM requires that the entry block has no predecessors.
-  auto* entry_block = &llvm_function->getEntryBlock();
+  auto* entry_block = &function_info->llvm_function->getEntryBlock();
   if (entry_block->hasNPredecessorsOrMore(1)) {
     auto* new_entry_block = llvm::BasicBlock::Create(
-        llvm_context(), "entry", llvm_function, entry_block);
+        llvm_context(), "entry", function_info->llvm_function, entry_block);
     llvm::BranchInst::Create(entry_block, new_entry_block);
   }
 
@@ -746,77 +956,8 @@ auto FileContext::BuildFunctionBody(SemIR::FunctionId function_id,
   context().di_builder().finalizeSubprogram(subprogram);
 }
 
-auto FileContext::BuildDISubroutineType(const SemIR::Function& function,
-                                        SemIR::SpecificId specific_id)
-    -> llvm::DISubroutineType* {
-  auto implicit_param_patterns =
-      sem_ir().inst_blocks().GetOrEmpty(function.implicit_param_patterns_id);
-  auto param_patterns =
-      sem_ir().inst_blocks().GetOrEmpty(function.param_patterns_id);
-
-  auto* void_pointer_debug_type =
-      context().di_builder().createPointerType(nullptr, 8);
-
-  auto get_debug_type = [&](SemIR::TypeId type_id) -> llvm::DIType* {
-    CARBON_CHECK(type_id.has_value());
-    if (auto* type = GetTypeAndDIType(type_id).llvm_di_type) {
-      return type;
-    }
-    return void_pointer_debug_type;
-  };
-
-  auto return_info =
-      SemIR::ReturnTypeInfo::ForFunction(sem_ir(), function, specific_id);
-  if (function.return_type_inst_id.has_value()) {
-    // TODO: If int_repr.kind == SemIR::InitRepr::ByCopy - be sure the return
-    // type is tagged with indirect calling convention.
-  }
-
-  // TODO: Expose the `Call` parameter patterns in `Function`, and use them
-  // here.
-  llvm::SmallVector<llvm::Metadata*, 16> element_types;
-  element_types.push_back(return_info.type_id.has_value()
-                              ? get_debug_type(return_info.type_id)
-                              : nullptr);
-
-  for (auto param_pattern_id : llvm::concat<const SemIR::InstId>(
-           implicit_param_patterns, param_patterns)) {
-    auto param_pattern_info = SemIR::Function::GetParamPatternInfoFromPatternId(
-        sem_ir(), param_pattern_id);
-    if (!param_pattern_info) {
-      continue;
-    }
-    if (param_pattern_info->inst.kind == SemIR::RefParamPattern::Kind) {
-      // TODO: Maybe make the parameter type a reference type.
-    }
-    auto param_type_id = ExtractScrutineeType(
-        sem_ir(), SemIR::GetTypeOfInstInSpecific(sem_ir(), specific_id,
-                                                 param_pattern_info->inst_id));
-    switch (auto value_rep = SemIR::ValueRepr::ForType(sem_ir(), param_type_id);
-            value_rep.kind) {
-      case SemIR::ValueRepr::Unknown:
-        CARBON_FATAL("Lowering function with incomplete parameter type");
-      case SemIR::ValueRepr::Dependent:
-        CARBON_FATAL("Lowering function with dependent parameter type");
-      case SemIR::ValueRepr::None:
-        break;
-      case SemIR::ValueRepr::Copy:
-      case SemIR::ValueRepr::Custom:
-      case SemIR::ValueRepr::Pointer:
-        auto* param_type = get_debug_type(value_rep.type_id);
-        element_types.push_back(param_type);
-        break;
-    }
-  }
-
-  return context().di_builder().createSubroutineType(
-      context().di_builder().getOrCreateTypeArray(element_types),
-      llvm::DINode::FlagZero);
-}
-
 auto FileContext::BuildDISubprogram(const SemIR::Function& function,
-                                    SemIR::SpecificId specific_id,
-                                    const llvm::Function* llvm_function)
+                                    const FunctionInfo& function_info)
     -> llvm::DISubprogram* {
   if (!context().di_compile_unit()) {
     return nullptr;
@@ -825,16 +966,18 @@ auto FileContext::BuildDISubprogram(const SemIR::Function& function,
   CARBON_CHECK(name, "Unexpected special name for function: {0}",
                function.name_id);
   auto loc = GetLocForDI(function.definition_id);
-  llvm::DISubroutineType* subroutine_type =
-      BuildDISubroutineType(function, specific_id);
+  llvm::DISubroutineType* subroutine_type = function_info.di_type;
   auto* subprogram = context().di_builder().createFunction(
-      context().di_compile_unit(), *name, llvm_function->getName(),
+      context().di_compile_unit(), *name,
+      function_info.llvm_function->getName(),
       /*File=*/context().di_builder().createFile(loc.filename, ""),
       /*LineNo=*/loc.line_number, subroutine_type,
       /*ScopeLine=*/0, llvm::DINode::FlagZero,
       llvm::DISubprogram::SPFlagDefinition);
   // Add a variable for each parameter, as that is where DWARF debug information
   // comes from.
+  // TODO: this doesn't declare a variable for the output parameter. Is that
+  // what we want?
   for (auto [argument_number, type] :
        llvm::enumerate(llvm::drop_begin(subroutine_type->getTypeArray()))) {
     context().di_builder().createParameterVariable(
@@ -1106,7 +1249,8 @@ auto FileContext::BuildVtable(const SemIR::Vtable& vtable,
     vfuncs.push_back(llvm::ConstantExpr::getTrunc(
         llvm::ConstantExpr::getSub(
             llvm::ConstantExpr::getPtrToInt(
-                GetOrCreateFunction(fn_id, fn_specific_id), i64_type),
+                GetOrCreateFunctionInfo(fn_id, fn_specific_id)->llvm_function,
+                i64_type),
             vtable_const_int),
         i32_type));
   }

+ 61 - 38
toolchain/lower/file_context.h

@@ -16,6 +16,29 @@
 
 namespace Carbon::Lower {
 
+// Information about how a given function declaration is lowered.
+struct FunctionInfo {
+  // The type of the lowered function.
+  llvm::FunctionType* type;
+
+  // The debug info type of the lowered function.
+  llvm::DISubroutineType* di_type;
+
+  // The `Call` parameter patterns that correspond to parameters of the LLVM IR
+  // function, in the order of the LLVM IR parameter list. Some `Call`
+  // parameters may be omitted (e.g. if they are stateless), and the order may
+  // differ from the SemIR `Call` parameter list (e.g. the return parameter, if
+  // any, always goes first).
+  llvm::SmallVector<SemIR::InstId> lowered_param_pattern_ids;
+
+  // Any `Call` param patterns that aren't present in
+  // lowered_param_pattern_ids.
+  llvm::SmallVector<SemIR::InstId> unused_param_pattern_ids;
+
+  // The lowered function declaration.
+  llvm::Function* llvm_function;
+};
+
 // Context and shared functionality for lowering within a SemIR file.
 class FileContext {
  public:
@@ -43,12 +66,25 @@ class FileContext {
   auto GetFunction(SemIR::FunctionId function_id,
                    SemIR::SpecificId specific_id = SemIR::SpecificId::None)
       -> llvm::Function* {
-    return *GetFunctionAddr(function_id, specific_id);
+    const auto& function_info = GetFunctionInfo(function_id, specific_id);
+    return function_info ? function_info->llvm_function : nullptr;
+  }
+
+  // Returns the FunctionInfo for the given function in the given specific, if
+  // it has already been computed.
+  auto GetFunctionInfo(SemIR::FunctionId function_id,
+                       SemIR::SpecificId specific_id)
+      -> std::optional<FunctionInfo>& {
+    return specific_id.has_value() ? specific_functions_.Get(specific_id)
+                                   : functions_.Get(function_id);
   }
 
-  // Gets a or creates callable's function. Returns nullptr for a builtin.
-  auto GetOrCreateFunction(SemIR::FunctionId function_id,
-                           SemIR::SpecificId specific_id) -> llvm::Function*;
+  // 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)
+      -> std::optional<FunctionInfo>&;
 
   // Returns a lowered type for the given type_id.
   auto GetType(SemIR::TypeId type_id) -> llvm::Type* {
@@ -128,20 +164,6 @@ class FileContext {
     context().SetPrintfIntFormatString(printf_int_format_string);
   }
 
-  struct FunctionTypeInfo {
-    llvm::FunctionType* type;
-    llvm::SmallVector<SemIR::InstId> param_inst_ids;
-    llvm::Type* return_type = nullptr;
-    SemIR::InstId return_param_id = SemIR::InstId::None;
-  };
-
-  // Retrieve various features of the function's type useful for constructing
-  // the `llvm::Type` 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()`.
-  auto BuildFunctionTypeInfo(const SemIR::Function& function,
-                             SemIR::SpecificId specific_id) -> FunctionTypeInfo;
-
   // Builds the global for the given instruction, which should then be cached by
   // the caller.
   auto BuildGlobalVariableDecl(SemIR::VarStorage var_storage)
@@ -156,13 +178,6 @@ class FileContext {
       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_.Get(specific_id)
-                                   : &functions_.Get(function_id);
-  }
-
   // 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;
@@ -175,11 +190,25 @@ class FileContext {
                                         SemIR::SpecificId specific_id,
                                         llvm::Type* llvm_type) -> void;
 
+  struct FunctionTypeInfo {
+    llvm::FunctionType* type;
+    llvm::DISubroutineType* di_type;
+    llvm::SmallVector<SemIR::InstId> lowered_param_pattern_ids;
+    llvm::SmallVector<SemIR::InstId> unused_param_pattern_ids;
+
+    // When return_param_id is not `None`, the corresponding lowered parameter
+    // should be given an `sret` attribute with this type.
+    llvm::Type* sret_type = nullptr;
+  };
+
+  class FunctionTypeInfoBuilder;
+
   // 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*;
+  auto BuildFunctionDecl(
+      SemIR::FunctionId function_id,
+      SemIR::SpecificId specific_id = SemIR::SpecificId::None)
+      -> std::optional<FunctionInfo>;
 
   // Builds a function's body. Common functionality for all functions.
   //
@@ -196,16 +225,9 @@ class FileContext {
 
   // Build the DISubprogram metadata for the given function.
   auto BuildDISubprogram(const SemIR::Function& function,
-                         SemIR::SpecificId specific_id,
-                         const llvm::Function* llvm_function)
+                         const FunctionInfo& function_info)
       -> llvm::DISubprogram*;
 
-  // Build a `DISubroutineType` for the given function, including the return and
-  // parameter types.
-  auto BuildDISubroutineType(const SemIR::Function&,
-                             SemIR::SpecificId specific_id)
-      -> llvm::DISubroutineType*;
-
   // Builds the `llvm::Type` and `llvm::DIType` for the given instruction, which
   // should then be cached by the caller.
   auto BuildType(SemIR::InstId inst_id) -> LoweredTypes;
@@ -239,12 +261,13 @@ class FileContext {
   // Maps callables to lowered functions. SemIR treats callables as the
   // canonical form of a function, so lowering needs to do the same.
   using LoweredFunctionStore =
-      FixedSizeValueStore<SemIR::FunctionId, llvm::Function*,
+      FixedSizeValueStore<SemIR::FunctionId, std::optional<FunctionInfo>,
                           Tag<SemIR::CheckIRId>>;
   LoweredFunctionStore functions_;
 
   // Maps specific callables to lowered functions.
-  FixedSizeValueStore<SemIR::SpecificId, llvm::Function*, Tag<SemIR::CheckIRId>>
+  FixedSizeValueStore<SemIR::SpecificId, std::optional<FunctionInfo>,
+                      Tag<SemIR::CheckIRId>>
       specific_functions_;
 
   // Provides lowered versions of types. Entries are non-symbolic types.

+ 0 - 18
toolchain/lower/function_context.h

@@ -129,24 +129,6 @@ class FunctionContext {
                  sem_ir().insts().Get(inst_id));
   }
 
-  // Gets a callable's function.
-  auto GetFunction(SemIR::FunctionId function_id) -> llvm::Function* {
-    return file_context_->GetFunction(function_id);
-  }
-
-  // Gets or creates a callable's function.
-  auto GetOrCreateFunction(SemIR::FunctionId function_id,
-                           SemIR::SpecificId specific_id) -> llvm::Function* {
-    return file_context_->GetOrCreateFunction(function_id, specific_id);
-  }
-
-  // Builds LLVM function type information for the specified function.
-  auto BuildFunctionTypeInfo(const SemIR::Function& function,
-                             SemIR::SpecificId specific_id)
-      -> FileContext::FunctionTypeInfo {
-    return file_context_->BuildFunctionTypeInfo(function, specific_id);
-  }
-
   // Returns a lowered type for the given type_id in the given file. This adds
   // the specified type to the fingerprint.
   auto GetType(TypeInFile type) -> llvm::Type* {

+ 18 - 27
toolchain/lower/handle_call.cpp

@@ -535,10 +535,10 @@ 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);
-  auto function_type_info =
+  auto function_info =
       context.GetFileContext(callee_file)
-          .BuildFunctionTypeInfo(function,
-                                 callee_function.resolved_specific_id);
+          .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
@@ -566,9 +566,9 @@ static auto HandleVirtualCall(FunctionContext& context,
          llvm::ConstantInt::get(
              i32_type, static_cast<uint64_t>(function.virtual_index) * 4)});
   }
-  return context.builder().CreateCall(function_type_info.type, virtual_fn,
-                                      args);
+  return context.builder().CreateCall(function_info->type, virtual_fn, args);
 }
+
 auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
                 SemIR::Call inst) -> void {
   llvm::ArrayRef<SemIR::InstId> arg_ids =
@@ -612,33 +612,24 @@ auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
     return;
   }
 
-  std::vector<llvm::Value*> args;
-
-  bool call_has_return_slot =
-      SemIR::ReturnTypeInfo::ForCallee(context.sem_ir(), inst.callee_id)
-          .has_return_slot();
-  if (context.GetReturnTypeInfo(callee).info.has_return_slot()) {
-    CARBON_CHECK(call_has_return_slot);
-    args.push_back(context.GetValue(arg_ids.consume_back()));
-  } else if (call_has_return_slot) {
-    // Call instruction has a return slot but this specific callee does not.
-    // Just ignore it.
-    arg_ids.consume_back();
-  }
+  auto& function_info =
+      context.GetFileContext(callee.file)
+          .GetOrCreateFunctionInfo(callee_function.function_id,
+                                   callee_function.resolved_specific_id);
 
-  for (auto arg_id : arg_ids) {
-    auto arg_type = context.GetTypeIdOfInst(arg_id);
-    if (context.GetValueRepr(arg_type).repr.kind != SemIR::ValueRepr::None) {
-      args.push_back(context.GetValue(arg_id));
-    }
+  // Lower args in the LLVM parameter order, rather than the SemIR parameter
+  // order.
+  std::vector<llvm::Value*> args;
+  for (auto param_pattern_id : function_info->lowered_param_pattern_ids) {
+    auto sem_ir_index = callee.file->insts()
+                            .GetAs<SemIR::AnyParamPattern>(param_pattern_id)
+                            .index.index;
+    args.push_back(context.GetValue(arg_ids[sem_ir_index]));
   }
 
   llvm::CallInst* call;
   if (function.virtual_modifier == SemIR::Function::VirtualModifier::None) {
-    auto* llvm_callee =
-        context.GetFileContext(callee.file)
-            .GetOrCreateFunction(callee_function.function_id,
-                                 callee_function.resolved_specific_id);
+    auto* llvm_callee = function_info->llvm_function;
     auto describe_call = [&] {
       RawStringOstream out;
       out << "call ";

+ 5 - 4
toolchain/lower/specific_coalescer.cpp

@@ -6,6 +6,7 @@
 
 #include "common/check.h"
 #include "common/vlog.h"
+#include "toolchain/lower/file_context.h"
 
 namespace Carbon::Lower {
 
@@ -172,11 +173,11 @@ auto SpecificCoalescer::UpdateAndDeleteLLVMFunction(
     LoweredLlvmFunctionStore& lowered_llvm_functions,
     SemIR::SpecificId specific_id) -> void {
   UpdateEquivalentSpecific(specific_id);
-  auto* old_function = lowered_llvm_functions.Get(specific_id);
-  auto* new_function =
+  auto& old_function = lowered_llvm_functions.Get(specific_id);
+  auto& new_function =
       lowered_llvm_functions.Get(equivalent_specifics_.Get(specific_id));
-  old_function->replaceAllUsesWith(new_function);
-  old_function->eraseFromParent();
+  old_function->llvm_function->replaceAllUsesWith(new_function->llvm_function);
+  old_function->llvm_function->eraseFromParent();
   lowered_llvm_functions.Set(specific_id, new_function);
 }
 

+ 3 - 1
toolchain/lower/specific_coalescer.h

@@ -11,6 +11,8 @@
 
 namespace Carbon::Lower {
 
+struct FunctionInfo;
+
 // Coalescing functionality for lowering fewer specifics of the same generic.
 class SpecificCoalescer {
  public:
@@ -19,7 +21,7 @@ class SpecificCoalescer {
                           llvm::SmallVector<SemIR::SpecificId>,
                           Tag<SemIR::CheckIRId>>;
   using LoweredLlvmFunctionStore =
-      FixedSizeValueStore<SemIR::SpecificId, llvm::Function*,
+      FixedSizeValueStore<SemIR::SpecificId, std::optional<FunctionInfo>,
                           Tag<SemIR::CheckIRId>>;
 
   // Describes a specific function's body fingerprint.

+ 18 - 21
toolchain/lower/testdata/builtins/no_op.carbon

@@ -45,26 +45,26 @@ fn J() {
 // CHECK:STDOUT: ; Function Attrs: nounwind
 // CHECK:STDOUT: define void @_CG.Main() #0 !dbg !8 {
 // CHECK:STDOUT: entry:
-// CHECK:STDOUT:   %a.var = alloca {}, align 8, !dbg !12
-// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %a.var), !dbg !12
-// CHECK:STDOUT:   ret void, !dbg !13
+// CHECK:STDOUT:   %a.var = alloca {}, align 8, !dbg !9
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %a.var), !dbg !9
+// CHECK:STDOUT:   ret void, !dbg !10
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: ; Function Attrs: nounwind
-// CHECK:STDOUT: define void @_CH.Main() #0 !dbg !14 {
+// CHECK:STDOUT: define void @_CH.Main() #0 !dbg !11 {
 // CHECK:STDOUT: entry:
-// CHECK:STDOUT:   ret void, !dbg !15
+// CHECK:STDOUT:   ret void, !dbg !12
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: declare void @_CI.Main()
 // CHECK:STDOUT:
 // CHECK:STDOUT: ; Function Attrs: nounwind
-// CHECK:STDOUT: define void @_CJ.Main() #0 !dbg !16 {
+// CHECK:STDOUT: define void @_CJ.Main() #0 !dbg !13 {
 // CHECK:STDOUT: entry:
-// CHECK:STDOUT:   %.loc33_11.1.temp = alloca {}, align 8, !dbg !17
-// CHECK:STDOUT:   call void @_CI.Main(), !dbg !17
-// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %.loc33_11.1.temp), !dbg !17
-// CHECK:STDOUT:   ret void, !dbg !18
+// CHECK:STDOUT:   %.loc33_11.1.temp = alloca {}, align 8, !dbg !14
+// CHECK:STDOUT:   call void @_CI.Main(), !dbg !14
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %.loc33_11.1.temp), !dbg !14
+// CHECK:STDOUT:   ret void, !dbg !15
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
@@ -87,14 +87,11 @@ fn J() {
 // CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
 // CHECK:STDOUT: !6 = !{null}
 // CHECK:STDOUT: !7 = !DILocation(line: 17, column: 1, scope: !4)
-// CHECK:STDOUT: !8 = distinct !DISubprogram(name: "G", linkageName: "_CG.Main", scope: null, file: !3, line: 21, type: !9, spFlags: DISPFlagDefinition, unit: !2)
-// CHECK:STDOUT: !9 = !DISubroutineType(types: !10)
-// CHECK:STDOUT: !10 = !{!11}
-// CHECK:STDOUT: !11 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: null, size: 8)
-// CHECK:STDOUT: !12 = !DILocation(line: 22, column: 3, scope: !8)
-// CHECK:STDOUT: !13 = !DILocation(line: 23, column: 3, scope: !8)
-// CHECK:STDOUT: !14 = distinct !DISubprogram(name: "H", linkageName: "_CH.Main", scope: null, file: !3, line: 26, type: !9, spFlags: DISPFlagDefinition, unit: !2)
-// CHECK:STDOUT: !15 = !DILocation(line: 27, column: 3, scope: !14)
-// CHECK:STDOUT: !16 = distinct !DISubprogram(name: "J", linkageName: "_CJ.Main", scope: null, file: !3, line: 32, type: !5, spFlags: DISPFlagDefinition, unit: !2)
-// CHECK:STDOUT: !17 = !DILocation(line: 33, column: 9, scope: !16)
-// CHECK:STDOUT: !18 = !DILocation(line: 32, column: 1, scope: !16)
+// CHECK:STDOUT: !8 = distinct !DISubprogram(name: "G", linkageName: "_CG.Main", scope: null, file: !3, line: 21, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !9 = !DILocation(line: 22, column: 3, scope: !8)
+// CHECK:STDOUT: !10 = !DILocation(line: 23, column: 3, scope: !8)
+// CHECK:STDOUT: !11 = distinct !DISubprogram(name: "H", linkageName: "_CH.Main", scope: null, file: !3, line: 26, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !12 = !DILocation(line: 27, column: 3, scope: !11)
+// CHECK:STDOUT: !13 = distinct !DISubprogram(name: "J", linkageName: "_CJ.Main", scope: null, file: !3, line: 32, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !14 = !DILocation(line: 33, column: 9, scope: !13)
+// CHECK:STDOUT: !15 = !DILocation(line: 32, column: 1, scope: !13)

+ 96 - 92
toolchain/lower/testdata/class/generic.carbon

@@ -314,28 +314,28 @@ fn AccessTuple() -> (i32, i32, i32) {
 // CHECK:STDOUT: ; Function Attrs: nounwind
 // CHECK:STDOUT: define void @_CAccessEmpty.Main() #0 !dbg !20 {
 // CHECK:STDOUT: entry:
-// CHECK:STDOUT:   %c.var = alloca { i1, {} }, align 8, !dbg !21
-// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %c.var), !dbg !21
-// CHECK:STDOUT:   %.loc26_37.2.v = getelementptr inbounds nuw { i1, {} }, ptr %c.var, i32 0, i32 0, !dbg !22
-// CHECK:STDOUT:   %.loc26_37.4.w = getelementptr inbounds nuw { i1, {} }, ptr %c.var, i32 0, i32 1, !dbg !22
-// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 1 %c.var, ptr align 1 @C.val.8d5.loc26_3, i64 1, i1 false), !dbg !21
-// CHECK:STDOUT:   call void @_CGetT.C.Main.335f6bfc3bd91486(ptr %c.var), !dbg !23
-// CHECK:STDOUT:   ret void, !dbg !24
+// CHECK:STDOUT:   %c.var = alloca { i1, {} }, align 8, !dbg !23
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %c.var), !dbg !23
+// CHECK:STDOUT:   %.loc26_37.2.v = getelementptr inbounds nuw { i1, {} }, ptr %c.var, i32 0, i32 0, !dbg !24
+// CHECK:STDOUT:   %.loc26_37.4.w = getelementptr inbounds nuw { i1, {} }, ptr %c.var, i32 0, i32 1, !dbg !24
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 1 %c.var, ptr align 1 @C.val.8d5.loc26_3, i64 1, i1 false), !dbg !23
+// CHECK:STDOUT:   call void @_CGetT.C.Main.335f6bfc3bd91486(ptr %c.var), !dbg !25
+// CHECK:STDOUT:   ret void, !dbg !26
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: ; Function Attrs: nounwind
-// CHECK:STDOUT: define void @_CAccessTuple.Main(ptr sret({ i32, i32, i32 }) %return) #0 !dbg !25 {
+// CHECK:STDOUT: define void @_CAccessTuple.Main(ptr sret({ i32, i32, i32 }) %return) #0 !dbg !27 {
 // CHECK:STDOUT: entry:
-// CHECK:STDOUT:   %c.var = alloca { i1, { i32, i32, i32 } }, align 8, !dbg !26
-// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %c.var), !dbg !26
-// CHECK:STDOUT:   %.loc31_57.2.v = getelementptr inbounds nuw { i1, { i32, i32, i32 } }, ptr %c.var, i32 0, i32 0, !dbg !27
-// CHECK:STDOUT:   %.loc31_57.4.w = getelementptr inbounds nuw { i1, { i32, i32, i32 } }, ptr %c.var, i32 0, i32 1, !dbg !27
-// CHECK:STDOUT:   %tuple.elem0.tuple.elem = getelementptr inbounds nuw { i32, i32, i32 }, ptr %.loc31_57.4.w, i32 0, i32 0, !dbg !28
-// CHECK:STDOUT:   %tuple.elem1.tuple.elem = getelementptr inbounds nuw { i32, i32, i32 }, ptr %.loc31_57.4.w, i32 0, i32 1, !dbg !28
-// CHECK:STDOUT:   %tuple.elem2.tuple.elem = getelementptr inbounds nuw { i32, i32, i32 }, ptr %.loc31_57.4.w, i32 0, i32 2, !dbg !28
-// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %c.var, ptr align 4 @C.val.76f.loc31_3, i64 16, i1 false), !dbg !26
-// CHECK:STDOUT:   call void @_CGetT.C.Main.3ae0849a77989a11(ptr %return, ptr %c.var), !dbg !29
-// CHECK:STDOUT:   ret void, !dbg !30
+// CHECK:STDOUT:   %c.var = alloca { i1, { i32, i32, i32 } }, align 8, !dbg !28
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %c.var), !dbg !28
+// CHECK:STDOUT:   %.loc31_57.2.v = getelementptr inbounds nuw { i1, { i32, i32, i32 } }, ptr %c.var, i32 0, i32 0, !dbg !29
+// CHECK:STDOUT:   %.loc31_57.4.w = getelementptr inbounds nuw { i1, { i32, i32, i32 } }, ptr %c.var, i32 0, i32 1, !dbg !29
+// CHECK:STDOUT:   %tuple.elem0.tuple.elem = getelementptr inbounds nuw { i32, i32, i32 }, ptr %.loc31_57.4.w, i32 0, i32 0, !dbg !30
+// CHECK:STDOUT:   %tuple.elem1.tuple.elem = getelementptr inbounds nuw { i32, i32, i32 }, ptr %.loc31_57.4.w, i32 0, i32 1, !dbg !30
+// CHECK:STDOUT:   %tuple.elem2.tuple.elem = getelementptr inbounds nuw { i32, i32, i32 }, ptr %.loc31_57.4.w, i32 0, i32 2, !dbg !30
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %c.var, ptr align 4 @C.val.76f.loc31_3, i64 16, i1 false), !dbg !28
+// CHECK:STDOUT:   call void @_CGetT.C.Main.3ae0849a77989a11(ptr %return, ptr %c.var), !dbg !31
+// CHECK:STDOUT:   ret void, !dbg !32
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
@@ -345,52 +345,52 @@ fn AccessTuple() -> (i32, i32, i32) {
 // 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 i1 @_CGetBool.C.Main.64ccbb8e5d9a0b8e(ptr %self) #0 !dbg !31 {
+// CHECK:STDOUT: define linkonce_odr i1 @_CGetBool.C.Main.64ccbb8e5d9a0b8e(ptr %self) #0 !dbg !33 {
 // CHECK:STDOUT: entry:
-// CHECK:STDOUT:   %.loc6_16.1.v = getelementptr inbounds nuw { i1, i32 }, ptr %self, i32 0, i32 0, !dbg !36
-// CHECK:STDOUT:   %.loc6_16.2 = load i8, ptr %.loc6_16.1.v, align 1, !dbg !36
-// CHECK:STDOUT:   %.loc6_16.21 = trunc i8 %.loc6_16.2 to i1, !dbg !36
-// CHECK:STDOUT:   ret i1 %.loc6_16.21, !dbg !37
+// CHECK:STDOUT:   %.loc6_16.1.v = getelementptr inbounds nuw { i1, i32 }, ptr %self, i32 0, i32 0, !dbg !38
+// CHECK:STDOUT:   %.loc6_16.2 = load i8, ptr %.loc6_16.1.v, align 1, !dbg !38
+// CHECK:STDOUT:   %.loc6_16.21 = trunc i8 %.loc6_16.2 to i1, !dbg !38
+// CHECK:STDOUT:   ret i1 %.loc6_16.21, !dbg !39
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: ; Function Attrs: nounwind
-// CHECK:STDOUT: define linkonce_odr i32 @_CGetT.C.Main.64ccbb8e5d9a0b8e(ptr %self) #0 !dbg !38 {
+// CHECK:STDOUT: define linkonce_odr i32 @_CGetT.C.Main.64ccbb8e5d9a0b8e(ptr %self) #0 !dbg !40 {
 // CHECK:STDOUT: entry:
-// CHECK:STDOUT:   %.loc9_16.1.w = getelementptr inbounds nuw { i1, i32 }, ptr %self, i32 0, i32 1, !dbg !43
-// CHECK:STDOUT:   %.loc9_16.2 = load i32, ptr %.loc9_16.1.w, align 4, !dbg !43
-// CHECK:STDOUT:   ret i32 %.loc9_16.2, !dbg !44
+// CHECK:STDOUT:   %.loc9_16.1.w = getelementptr inbounds nuw { i1, i32 }, ptr %self, i32 0, i32 1, !dbg !45
+// CHECK:STDOUT:   %.loc9_16.2 = load i32, ptr %.loc9_16.1.w, align 4, !dbg !45
+// CHECK:STDOUT:   ret i32 %.loc9_16.2, !dbg !46
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: ; Function Attrs: nounwind
-// CHECK:STDOUT: define linkonce_odr void @_CGetT.C.Main.335f6bfc3bd91486(ptr %self) #0 !dbg !45 {
+// CHECK:STDOUT: define linkonce_odr void @_CGetT.C.Main.335f6bfc3bd91486(ptr %self) #0 !dbg !47 {
 // CHECK:STDOUT: entry:
-// CHECK:STDOUT:   %.loc9_16.1.w = getelementptr inbounds nuw { i1, {} }, ptr %self, i32 0, i32 1, !dbg !48
-// CHECK:STDOUT:   ret void, !dbg !49
+// CHECK:STDOUT:   %.loc9_16.1.w = getelementptr inbounds nuw { i1, {} }, ptr %self, i32 0, i32 1, !dbg !52
+// CHECK:STDOUT:   ret void, !dbg !53
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: ; Function Attrs: nounwind
-// CHECK:STDOUT: define linkonce_odr void @_CGetT.C.Main.3ae0849a77989a11(ptr sret({ i32, i32, i32 }) %return, ptr %self) #0 !dbg !50 {
+// CHECK:STDOUT: define linkonce_odr void @_CGetT.C.Main.3ae0849a77989a11(ptr sret({ i32, i32, i32 }) %return, ptr %self) #0 !dbg !54 {
 // CHECK:STDOUT: entry:
-// CHECK:STDOUT:   %.loc9_16.1.w = getelementptr inbounds nuw { i1, { i32, i32, i32 } }, ptr %self, i32 0, i32 1, !dbg !53
-// CHECK:STDOUT:   call void @"_COp.a02bcb1b1383357d:Copy.Core.975a30a52004fa2c"(ptr %return, ptr %.loc9_16.1.w), !dbg !53
-// CHECK:STDOUT:   ret void, !dbg !54
+// CHECK:STDOUT:   %.loc9_16.1.w = getelementptr inbounds nuw { i1, { i32, i32, i32 } }, ptr %self, i32 0, i32 1, !dbg !57
+// CHECK:STDOUT:   call void @"_COp.a02bcb1b1383357d:Copy.Core.975a30a52004fa2c"(ptr %return, ptr %.loc9_16.1.w), !dbg !57
+// CHECK:STDOUT:   ret void, !dbg !58
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: ; Function Attrs: nounwind
-// CHECK:STDOUT: define linkonce_odr void @"_COp.a02bcb1b1383357d:Copy.Core.975a30a52004fa2c"(ptr sret({ i32, i32, i32 }) %return, ptr %self) #0 !dbg !55 {
-// CHECK:STDOUT:   %tuple.elem = getelementptr inbounds nuw { i32, i32, i32 }, ptr %self, i32 0, i32 0, !dbg !59
-// CHECK:STDOUT:   %tuple.elem.load = load i32, ptr %tuple.elem, align 4, !dbg !59
-// CHECK:STDOUT:   %tuple.elem1 = getelementptr inbounds nuw { i32, i32, i32 }, ptr %return, i32 0, i32 0, !dbg !60
-// CHECK:STDOUT:   %tuple.elem2 = getelementptr inbounds nuw { i32, i32, i32 }, ptr %self, i32 0, i32 1, !dbg !61
-// CHECK:STDOUT:   %tuple.elem.load3 = load i32, ptr %tuple.elem2, align 4, !dbg !61
-// CHECK:STDOUT:   %tuple.elem4 = getelementptr inbounds nuw { i32, i32, i32 }, ptr %return, i32 0, i32 1, !dbg !60
-// CHECK:STDOUT:   %tuple.elem5 = getelementptr inbounds nuw { i32, i32, i32 }, ptr %self, i32 0, i32 2, !dbg !62
-// CHECK:STDOUT:   %tuple.elem.load6 = load i32, ptr %tuple.elem5, align 4, !dbg !62
-// CHECK:STDOUT:   %tuple.elem7 = getelementptr inbounds nuw { i32, i32, i32 }, ptr %return, i32 0, i32 2, !dbg !60
-// CHECK:STDOUT:   store i32 %tuple.elem.load, ptr %tuple.elem1, align 4, !dbg !60
-// CHECK:STDOUT:   store i32 %tuple.elem.load3, ptr %tuple.elem4, align 4, !dbg !60
-// CHECK:STDOUT:   store i32 %tuple.elem.load6, ptr %tuple.elem7, align 4, !dbg !60
-// CHECK:STDOUT:   ret void, !dbg !63
+// CHECK:STDOUT: define linkonce_odr void @"_COp.a02bcb1b1383357d:Copy.Core.975a30a52004fa2c"(ptr sret({ i32, i32, i32 }) %return, ptr %self) #0 !dbg !59 {
+// CHECK:STDOUT:   %tuple.elem = getelementptr inbounds nuw { i32, i32, i32 }, ptr %self, i32 0, i32 0, !dbg !63
+// CHECK:STDOUT:   %tuple.elem.load = load i32, ptr %tuple.elem, align 4, !dbg !63
+// CHECK:STDOUT:   %tuple.elem1 = getelementptr inbounds nuw { i32, i32, i32 }, ptr %return, i32 0, i32 0, !dbg !64
+// CHECK:STDOUT:   %tuple.elem2 = getelementptr inbounds nuw { i32, i32, i32 }, ptr %self, i32 0, i32 1, !dbg !65
+// CHECK:STDOUT:   %tuple.elem.load3 = load i32, ptr %tuple.elem2, align 4, !dbg !65
+// CHECK:STDOUT:   %tuple.elem4 = getelementptr inbounds nuw { i32, i32, i32 }, ptr %return, i32 0, i32 1, !dbg !64
+// CHECK:STDOUT:   %tuple.elem5 = getelementptr inbounds nuw { i32, i32, i32 }, ptr %self, i32 0, i32 2, !dbg !66
+// CHECK:STDOUT:   %tuple.elem.load6 = load i32, ptr %tuple.elem5, align 4, !dbg !66
+// CHECK:STDOUT:   %tuple.elem7 = getelementptr inbounds nuw { i32, i32, i32 }, ptr %return, i32 0, i32 2, !dbg !64
+// CHECK:STDOUT:   store i32 %tuple.elem.load, ptr %tuple.elem1, align 4, !dbg !64
+// CHECK:STDOUT:   store i32 %tuple.elem.load3, ptr %tuple.elem4, align 4, !dbg !64
+// CHECK:STDOUT:   store i32 %tuple.elem.load6, ptr %tuple.elem7, align 4, !dbg !64
+// CHECK:STDOUT:   ret void, !dbg !67
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: ; uselistorder directives
@@ -424,47 +424,51 @@ fn AccessTuple() -> (i32, i32, i32) {
 // CHECK:STDOUT: !17 = !DILocation(line: 21, column: 19, scope: !12)
 // CHECK:STDOUT: !18 = !DILocation(line: 22, column: 10, scope: !12)
 // CHECK:STDOUT: !19 = !DILocation(line: 22, column: 3, scope: !12)
-// CHECK:STDOUT: !20 = distinct !DISubprogram(name: "AccessEmpty", linkageName: "_CAccessEmpty.Main", scope: null, file: !3, line: 25, type: !5, spFlags: DISPFlagDefinition, unit: !2)
-// CHECK:STDOUT: !21 = !DILocation(line: 26, column: 3, scope: !20)
-// CHECK:STDOUT: !22 = !DILocation(line: 26, column: 18, scope: !20)
-// CHECK:STDOUT: !23 = !DILocation(line: 27, column: 10, scope: !20)
-// CHECK:STDOUT: !24 = !DILocation(line: 27, column: 3, scope: !20)
-// CHECK:STDOUT: !25 = distinct !DISubprogram(name: "AccessTuple", linkageName: "_CAccessTuple.Main", scope: null, file: !3, line: 30, type: !5, spFlags: DISPFlagDefinition, unit: !2)
-// CHECK:STDOUT: !26 = !DILocation(line: 31, column: 3, scope: !25)
-// CHECK:STDOUT: !27 = !DILocation(line: 31, column: 31, scope: !25)
-// CHECK:STDOUT: !28 = !DILocation(line: 31, column: 48, scope: !25)
-// CHECK:STDOUT: !29 = !DILocation(line: 32, column: 10, scope: !25)
-// CHECK:STDOUT: !30 = !DILocation(line: 32, column: 3, scope: !25)
-// CHECK:STDOUT: !31 = distinct !DISubprogram(name: "GetBool", linkageName: "_CGetBool.C.Main.64ccbb8e5d9a0b8e", scope: null, file: !3, line: 5, type: !32, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !34)
-// CHECK:STDOUT: !32 = !DISubroutineType(types: !33)
-// CHECK:STDOUT: !33 = !{!7, !7}
-// CHECK:STDOUT: !34 = !{!35}
-// CHECK:STDOUT: !35 = !DILocalVariable(arg: 1, scope: !31, type: !7)
-// CHECK:STDOUT: !36 = !DILocation(line: 6, column: 12, scope: !31)
-// CHECK:STDOUT: !37 = !DILocation(line: 6, column: 5, scope: !31)
-// CHECK:STDOUT: !38 = distinct !DISubprogram(name: "GetT", linkageName: "_CGetT.C.Main.64ccbb8e5d9a0b8e", scope: null, file: !3, line: 8, type: !39, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !41)
-// CHECK:STDOUT: !39 = !DISubroutineType(types: !40)
-// CHECK:STDOUT: !40 = !{!15, !7}
-// CHECK:STDOUT: !41 = !{!42}
-// CHECK:STDOUT: !42 = !DILocalVariable(arg: 1, scope: !38, type: !7)
-// CHECK:STDOUT: !43 = !DILocation(line: 9, column: 12, scope: !38)
-// CHECK:STDOUT: !44 = !DILocation(line: 9, column: 5, scope: !38)
-// CHECK:STDOUT: !45 = distinct !DISubprogram(name: "GetT", linkageName: "_CGetT.C.Main.335f6bfc3bd91486", scope: null, file: !3, line: 8, type: !32, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !46)
-// CHECK:STDOUT: !46 = !{!47}
-// CHECK:STDOUT: !47 = !DILocalVariable(arg: 1, scope: !45, type: !7)
-// CHECK:STDOUT: !48 = !DILocation(line: 9, column: 12, scope: !45)
-// CHECK:STDOUT: !49 = !DILocation(line: 9, column: 5, scope: !45)
-// CHECK:STDOUT: !50 = distinct !DISubprogram(name: "GetT", linkageName: "_CGetT.C.Main.3ae0849a77989a11", scope: null, file: !3, line: 8, type: !32, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !51)
-// CHECK:STDOUT: !51 = !{!52}
-// CHECK:STDOUT: !52 = !DILocalVariable(arg: 1, scope: !50, type: !7)
-// CHECK:STDOUT: !53 = !DILocation(line: 9, column: 12, scope: !50)
-// CHECK:STDOUT: !54 = !DILocation(line: 9, column: 5, scope: !50)
-// CHECK:STDOUT: !55 = distinct !DISubprogram(name: "Op", linkageName: "_COp.a02bcb1b1383357d:Copy.Core.975a30a52004fa2c", scope: null, file: !56, line: 54, type: !32, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !57)
-// CHECK:STDOUT: !56 = !DIFile(filename: "min_prelude/parts/copy.carbon", directory: "")
-// CHECK:STDOUT: !57 = !{!58}
-// CHECK:STDOUT: !58 = !DILocalVariable(arg: 1, scope: !55, type: !7)
-// CHECK:STDOUT: !59 = !DILocation(line: 55, column: 13, scope: !55)
-// CHECK:STDOUT: !60 = !DILocation(line: 55, column: 12, scope: !55)
-// CHECK:STDOUT: !61 = !DILocation(line: 55, column: 26, scope: !55)
-// CHECK:STDOUT: !62 = !DILocation(line: 55, column: 39, scope: !55)
-// CHECK:STDOUT: !63 = !DILocation(line: 55, column: 5, scope: !55)
+// CHECK:STDOUT: !20 = distinct !DISubprogram(name: "AccessEmpty", linkageName: "_CAccessEmpty.Main", scope: null, file: !3, line: 25, type: !21, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !21 = !DISubroutineType(types: !22)
+// CHECK:STDOUT: !22 = !{null}
+// CHECK:STDOUT: !23 = !DILocation(line: 26, column: 3, scope: !20)
+// CHECK:STDOUT: !24 = !DILocation(line: 26, column: 18, scope: !20)
+// CHECK:STDOUT: !25 = !DILocation(line: 27, column: 10, scope: !20)
+// CHECK:STDOUT: !26 = !DILocation(line: 27, column: 3, scope: !20)
+// CHECK:STDOUT: !27 = distinct !DISubprogram(name: "AccessTuple", linkageName: "_CAccessTuple.Main", scope: null, file: !3, line: 30, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !28 = !DILocation(line: 31, column: 3, scope: !27)
+// CHECK:STDOUT: !29 = !DILocation(line: 31, column: 31, scope: !27)
+// CHECK:STDOUT: !30 = !DILocation(line: 31, column: 48, scope: !27)
+// CHECK:STDOUT: !31 = !DILocation(line: 32, column: 10, scope: !27)
+// CHECK:STDOUT: !32 = !DILocation(line: 32, column: 3, scope: !27)
+// CHECK:STDOUT: !33 = distinct !DISubprogram(name: "GetBool", linkageName: "_CGetBool.C.Main.64ccbb8e5d9a0b8e", scope: null, file: !3, line: 5, type: !34, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !36)
+// CHECK:STDOUT: !34 = !DISubroutineType(types: !35)
+// CHECK:STDOUT: !35 = !{!7, !7}
+// CHECK:STDOUT: !36 = !{!37}
+// CHECK:STDOUT: !37 = !DILocalVariable(arg: 1, scope: !33, type: !7)
+// CHECK:STDOUT: !38 = !DILocation(line: 6, column: 12, scope: !33)
+// CHECK:STDOUT: !39 = !DILocation(line: 6, column: 5, scope: !33)
+// CHECK:STDOUT: !40 = distinct !DISubprogram(name: "GetT", linkageName: "_CGetT.C.Main.64ccbb8e5d9a0b8e", scope: null, file: !3, line: 8, type: !41, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !43)
+// CHECK:STDOUT: !41 = !DISubroutineType(types: !42)
+// CHECK:STDOUT: !42 = !{!15, !7}
+// CHECK:STDOUT: !43 = !{!44}
+// CHECK:STDOUT: !44 = !DILocalVariable(arg: 1, scope: !40, type: !7)
+// CHECK:STDOUT: !45 = !DILocation(line: 9, column: 12, scope: !40)
+// CHECK:STDOUT: !46 = !DILocation(line: 9, column: 5, scope: !40)
+// CHECK:STDOUT: !47 = distinct !DISubprogram(name: "GetT", linkageName: "_CGetT.C.Main.335f6bfc3bd91486", scope: null, file: !3, line: 8, type: !48, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !50)
+// CHECK:STDOUT: !48 = !DISubroutineType(types: !49)
+// CHECK:STDOUT: !49 = !{null, !7}
+// CHECK:STDOUT: !50 = !{!51}
+// CHECK:STDOUT: !51 = !DILocalVariable(arg: 1, scope: !47, type: !7)
+// CHECK:STDOUT: !52 = !DILocation(line: 9, column: 12, scope: !47)
+// CHECK:STDOUT: !53 = !DILocation(line: 9, column: 5, scope: !47)
+// CHECK:STDOUT: !54 = distinct !DISubprogram(name: "GetT", linkageName: "_CGetT.C.Main.3ae0849a77989a11", scope: null, file: !3, line: 8, type: !34, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !55)
+// CHECK:STDOUT: !55 = !{!56}
+// CHECK:STDOUT: !56 = !DILocalVariable(arg: 1, scope: !54, type: !7)
+// CHECK:STDOUT: !57 = !DILocation(line: 9, column: 12, scope: !54)
+// CHECK:STDOUT: !58 = !DILocation(line: 9, column: 5, scope: !54)
+// CHECK:STDOUT: !59 = distinct !DISubprogram(name: "Op", linkageName: "_COp.a02bcb1b1383357d:Copy.Core.975a30a52004fa2c", scope: null, file: !60, line: 54, type: !34, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !61)
+// CHECK:STDOUT: !60 = !DIFile(filename: "min_prelude/parts/copy.carbon", directory: "")
+// CHECK:STDOUT: !61 = !{!62}
+// CHECK:STDOUT: !62 = !DILocalVariable(arg: 1, scope: !59, type: !7)
+// CHECK:STDOUT: !63 = !DILocation(line: 55, column: 13, scope: !59)
+// CHECK:STDOUT: !64 = !DILocation(line: 55, column: 12, scope: !59)
+// CHECK:STDOUT: !65 = !DILocation(line: 55, column: 26, scope: !59)
+// CHECK:STDOUT: !66 = !DILocation(line: 55, column: 39, scope: !59)
+// CHECK:STDOUT: !67 = !DILocation(line: 55, column: 5, scope: !59)

+ 70 - 68
toolchain/lower/testdata/for/bindings.carbon

@@ -95,55 +95,55 @@ fn For() {
 // CHECK:STDOUT:
 // CHECK:STDOUT: ; Function Attrs: nounwind
 // CHECK:STDOUT: define linkonce_odr i1 @_CHasValue.Optional.Core.eeebe1ada0072aea(ptr %self) #0 !dbg !28 {
-// CHECK:STDOUT:   %1 = call i1 @"_CHas.3e8267224c5dc9c2:OptionalStorage.Core.c6c37881d2ed7d9d"(ptr %self), !dbg !32
-// CHECK:STDOUT:   ret i1 %1, !dbg !33
+// CHECK:STDOUT:   %1 = call i1 @"_CHas.3e8267224c5dc9c2:OptionalStorage.Core.c6c37881d2ed7d9d"(ptr %self), !dbg !34
+// CHECK:STDOUT:   ret i1 %1, !dbg !35
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: ; Function Attrs: nounwind
-// CHECK:STDOUT: define linkonce_odr void @_CGet.Optional.Core.eeebe1ada0072aea(ptr sret({ i32, i32 }) %return, ptr %self) #0 !dbg !34 {
-// CHECK:STDOUT:   call void @"_CGet.3e8267224c5dc9c2:OptionalStorage.Core.c6c37881d2ed7d9d"(ptr %return, ptr %self), !dbg !37
-// CHECK:STDOUT:   ret void, !dbg !38
+// CHECK:STDOUT: define linkonce_odr void @_CGet.Optional.Core.eeebe1ada0072aea(ptr sret({ i32, i32 }) %return, ptr %self) #0 !dbg !36 {
+// CHECK:STDOUT:   call void @"_CGet.3e8267224c5dc9c2:OptionalStorage.Core.c6c37881d2ed7d9d"(ptr %return, ptr %self), !dbg !39
+// CHECK:STDOUT:   ret void, !dbg !40
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: ; Function Attrs: nounwind
-// CHECK:STDOUT: define linkonce_odr void @_CNone.Optional.Core.eeebe1ada0072aea(ptr sret({ { i32, i32 }, i1 }) %return) #0 !dbg !39 {
-// CHECK:STDOUT:   call void @"_CNone.3e8267224c5dc9c2:OptionalStorage.Core.c6c37881d2ed7d9d"(ptr %return), !dbg !42
-// CHECK:STDOUT:   ret void, !dbg !43
+// CHECK:STDOUT: define linkonce_odr void @_CNone.Optional.Core.eeebe1ada0072aea(ptr sret({ { i32, i32 }, i1 }) %return) #0 !dbg !41 {
+// CHECK:STDOUT:   call void @"_CNone.3e8267224c5dc9c2:OptionalStorage.Core.c6c37881d2ed7d9d"(ptr %return), !dbg !44
+// CHECK:STDOUT:   ret void, !dbg !45
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: ; Function Attrs: nounwind
-// CHECK:STDOUT: define linkonce_odr i1 @"_CHas.3e8267224c5dc9c2:OptionalStorage.Core.c6c37881d2ed7d9d"(ptr %value) #0 !dbg !44 {
-// CHECK:STDOUT:   %has_value = getelementptr inbounds nuw { { i32, i32 }, i1 }, ptr %value, i32 0, i32 1, !dbg !47
-// CHECK:STDOUT:   %1 = load i8, ptr %has_value, align 1, !dbg !47
-// CHECK:STDOUT:   %2 = trunc i8 %1 to i1, !dbg !47
-// CHECK:STDOUT:   ret i1 %2, !dbg !48
+// CHECK:STDOUT: define linkonce_odr i1 @"_CHas.3e8267224c5dc9c2:OptionalStorage.Core.c6c37881d2ed7d9d"(ptr %value) #0 !dbg !46 {
+// CHECK:STDOUT:   %has_value = getelementptr inbounds nuw { { i32, i32 }, i1 }, ptr %value, i32 0, i32 1, !dbg !49
+// CHECK:STDOUT:   %1 = load i8, ptr %has_value, align 1, !dbg !49
+// CHECK:STDOUT:   %2 = trunc i8 %1 to i1, !dbg !49
+// CHECK:STDOUT:   ret i1 %2, !dbg !50
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: ; Function Attrs: nounwind
-// CHECK:STDOUT: define linkonce_odr void @"_CGet.3e8267224c5dc9c2:OptionalStorage.Core.c6c37881d2ed7d9d"(ptr sret({ i32, i32 }) %return, ptr %value) #0 !dbg !49 {
-// CHECK:STDOUT:   %value1 = getelementptr inbounds nuw { { i32, i32 }, i1 }, ptr %value, i32 0, i32 0, !dbg !52
-// CHECK:STDOUT:   call void @"_COp.e4daa70d026fdea2:Copy.Core.3e36085cb869f630"(ptr %return, ptr %value1), !dbg !52
-// CHECK:STDOUT:   ret void, !dbg !53
+// CHECK:STDOUT: define linkonce_odr void @"_CGet.3e8267224c5dc9c2:OptionalStorage.Core.c6c37881d2ed7d9d"(ptr sret({ i32, i32 }) %return, ptr %value) #0 !dbg !51 {
+// CHECK:STDOUT:   %value1 = getelementptr inbounds nuw { { i32, i32 }, i1 }, ptr %value, i32 0, i32 0, !dbg !54
+// CHECK:STDOUT:   call void @"_COp.e4daa70d026fdea2:Copy.Core.3e36085cb869f630"(ptr %return, ptr %value1), !dbg !54
+// CHECK:STDOUT:   ret void, !dbg !55
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: ; Function Attrs: nounwind
-// CHECK:STDOUT: define linkonce_odr void @"_CNone.3e8267224c5dc9c2:OptionalStorage.Core.c6c37881d2ed7d9d"(ptr sret({ { i32, i32 }, i1 }) %return) #0 !dbg !54 {
-// CHECK:STDOUT:   %has_value = getelementptr inbounds nuw { { i32, i32 }, i1 }, ptr %return, i32 0, i32 1, !dbg !55
-// CHECK:STDOUT:   store i8 0, ptr %has_value, align 1, !dbg !55
-// CHECK:STDOUT:   ret void, !dbg !56
+// CHECK:STDOUT: define linkonce_odr void @"_CNone.3e8267224c5dc9c2:OptionalStorage.Core.c6c37881d2ed7d9d"(ptr sret({ { i32, i32 }, i1 }) %return) #0 !dbg !56 {
+// CHECK:STDOUT:   %has_value = getelementptr inbounds nuw { { i32, i32 }, i1 }, ptr %return, i32 0, i32 1, !dbg !57
+// CHECK:STDOUT:   store i8 0, ptr %has_value, align 1, !dbg !57
+// CHECK:STDOUT:   ret void, !dbg !58
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: ; Function Attrs: nounwind
-// CHECK:STDOUT: define linkonce_odr void @"_COp.e4daa70d026fdea2:Copy.Core.3e36085cb869f630"(ptr sret({ i32, i32 }) %return, ptr %self) #0 !dbg !57 {
-// CHECK:STDOUT:   %tuple.elem = getelementptr inbounds nuw { i32, i32 }, ptr %self, i32 0, i32 0, !dbg !61
-// CHECK:STDOUT:   %tuple.elem.load = load i32, ptr %tuple.elem, align 4, !dbg !61
-// CHECK:STDOUT:   %tuple.elem1 = getelementptr inbounds nuw { i32, i32 }, ptr %return, i32 0, i32 0, !dbg !62
-// CHECK:STDOUT:   %tuple.elem2 = getelementptr inbounds nuw { i32, i32 }, ptr %self, i32 0, i32 1, !dbg !63
-// CHECK:STDOUT:   %tuple.elem.load3 = load i32, ptr %tuple.elem2, align 4, !dbg !63
-// CHECK:STDOUT:   %tuple.elem4 = getelementptr inbounds nuw { i32, i32 }, ptr %return, i32 0, i32 1, !dbg !62
-// CHECK:STDOUT:   store i32 %tuple.elem.load, ptr %tuple.elem1, align 4, !dbg !62
-// CHECK:STDOUT:   store i32 %tuple.elem.load3, ptr %tuple.elem4, align 4, !dbg !62
-// CHECK:STDOUT:   ret void, !dbg !64
+// CHECK:STDOUT: define linkonce_odr void @"_COp.e4daa70d026fdea2:Copy.Core.3e36085cb869f630"(ptr sret({ i32, i32 }) %return, ptr %self) #0 !dbg !59 {
+// CHECK:STDOUT:   %tuple.elem = getelementptr inbounds nuw { i32, i32 }, ptr %self, i32 0, i32 0, !dbg !63
+// CHECK:STDOUT:   %tuple.elem.load = load i32, ptr %tuple.elem, align 4, !dbg !63
+// CHECK:STDOUT:   %tuple.elem1 = getelementptr inbounds nuw { i32, i32 }, ptr %return, i32 0, i32 0, !dbg !64
+// CHECK:STDOUT:   %tuple.elem2 = getelementptr inbounds nuw { i32, i32 }, ptr %self, i32 0, i32 1, !dbg !65
+// CHECK:STDOUT:   %tuple.elem.load3 = load i32, ptr %tuple.elem2, align 4, !dbg !65
+// CHECK:STDOUT:   %tuple.elem4 = getelementptr inbounds nuw { i32, i32 }, ptr %return, i32 0, i32 1, !dbg !64
+// CHECK:STDOUT:   store i32 %tuple.elem.load, ptr %tuple.elem1, align 4, !dbg !64
+// CHECK:STDOUT:   store i32 %tuple.elem.load3, ptr %tuple.elem4, align 4, !dbg !64
+// CHECK:STDOUT:   ret void, !dbg !66
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: ; uselistorder directives
@@ -171,7 +171,7 @@ fn For() {
 // CHECK:STDOUT: !12 = !DILocation(line: 26, column: 1, scope: !4)
 // CHECK:STDOUT: !13 = distinct !DISubprogram(name: "NewCursor", linkageName: "_CNewCursor.EmptyRange.1ccc388edee5a786.Main:Iterate.Core.c6c37881d2ed7d9d", scope: null, file: !3, line: 15, type: !14, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !17)
 // CHECK:STDOUT: !14 = !DISubroutineType(types: !15)
-// CHECK:STDOUT: !15 = !{!16, !16}
+// CHECK:STDOUT: !15 = !{null, !16}
 // CHECK:STDOUT: !16 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: null, size: 8)
 // CHECK:STDOUT: !17 = !{!18}
 // CHECK:STDOUT: !18 = !DILocalVariable(arg: 1, scope: !13, type: !16)
@@ -184,40 +184,42 @@ fn For() {
 // CHECK:STDOUT: !25 = !DILocalVariable(arg: 2, scope: !20, type: !16)
 // CHECK:STDOUT: !26 = !DILocation(line: 19, column: 14, scope: !20)
 // CHECK:STDOUT: !27 = !DILocation(line: 19, column: 7, scope: !20)
-// CHECK:STDOUT: !28 = distinct !DISubprogram(name: "HasValue", linkageName: "_CHasValue.Optional.Core.eeebe1ada0072aea", scope: null, file: !29, line: 32, type: !14, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !30)
+// CHECK:STDOUT: !28 = distinct !DISubprogram(name: "HasValue", linkageName: "_CHasValue.Optional.Core.eeebe1ada0072aea", scope: null, file: !29, line: 32, type: !30, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !32)
 // CHECK:STDOUT: !29 = !DIFile(filename: "{{.*}}/prelude/types/optional.carbon", directory: "")
-// CHECK:STDOUT: !30 = !{!31}
-// CHECK:STDOUT: !31 = !DILocalVariable(arg: 1, scope: !28, type: !16)
-// CHECK:STDOUT: !32 = !DILocation(line: 33, column: 12, scope: !28)
-// CHECK:STDOUT: !33 = !DILocation(line: 33, column: 5, scope: !28)
-// CHECK:STDOUT: !34 = distinct !DISubprogram(name: "Get", linkageName: "_CGet.Optional.Core.eeebe1ada0072aea", scope: null, file: !29, line: 35, type: !14, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !35)
-// CHECK:STDOUT: !35 = !{!36}
-// CHECK:STDOUT: !36 = !DILocalVariable(arg: 1, scope: !34, type: !16)
-// CHECK:STDOUT: !37 = !DILocation(line: 36, column: 12, scope: !34)
-// CHECK:STDOUT: !38 = !DILocation(line: 36, column: 5, scope: !34)
-// CHECK:STDOUT: !39 = distinct !DISubprogram(name: "None", linkageName: "_CNone.Optional.Core.eeebe1ada0072aea", scope: null, file: !29, line: 26, type: !40, spFlags: DISPFlagDefinition, unit: !2)
-// CHECK:STDOUT: !40 = !DISubroutineType(types: !41)
-// CHECK:STDOUT: !41 = !{!16}
-// CHECK:STDOUT: !42 = !DILocation(line: 27, column: 12, scope: !39)
-// CHECK:STDOUT: !43 = !DILocation(line: 27, column: 5, scope: !39)
-// CHECK:STDOUT: !44 = distinct !DISubprogram(name: "Has", linkageName: "_CHas.3e8267224c5dc9c2:OptionalStorage.Core.c6c37881d2ed7d9d", scope: null, file: !29, line: 123, type: !14, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !45)
-// CHECK:STDOUT: !45 = !{!46}
-// CHECK:STDOUT: !46 = !DILocalVariable(arg: 1, scope: !44, type: !16)
-// CHECK:STDOUT: !47 = !DILocation(line: 124, column: 12, scope: !44)
-// CHECK:STDOUT: !48 = !DILocation(line: 124, column: 5, scope: !44)
-// CHECK:STDOUT: !49 = distinct !DISubprogram(name: "Get", linkageName: "_CGet.3e8267224c5dc9c2:OptionalStorage.Core.c6c37881d2ed7d9d", scope: null, file: !29, line: 126, type: !14, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !50)
-// CHECK:STDOUT: !50 = !{!51}
-// CHECK:STDOUT: !51 = !DILocalVariable(arg: 1, scope: !49, type: !16)
-// CHECK:STDOUT: !52 = !DILocation(line: 127, column: 12, scope: !49)
-// CHECK:STDOUT: !53 = !DILocation(line: 127, column: 5, scope: !49)
-// CHECK:STDOUT: !54 = distinct !DISubprogram(name: "None", linkageName: "_CNone.3e8267224c5dc9c2:OptionalStorage.Core.c6c37881d2ed7d9d", scope: null, file: !29, line: 110, type: !40, spFlags: DISPFlagDefinition, unit: !2)
-// CHECK:STDOUT: !55 = !DILocation(line: 112, column: 5, scope: !54)
-// CHECK:STDOUT: !56 = !DILocation(line: 113, column: 5, scope: !54)
-// CHECK:STDOUT: !57 = distinct !DISubprogram(name: "Op", linkageName: "_COp.e4daa70d026fdea2:Copy.Core.3e36085cb869f630", scope: null, file: !58, line: 58, type: !14, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !59)
-// CHECK:STDOUT: !58 = !DIFile(filename: "{{.*}}/prelude/copy.carbon", directory: "")
-// CHECK:STDOUT: !59 = !{!60}
-// CHECK:STDOUT: !60 = !DILocalVariable(arg: 1, scope: !57, type: !16)
-// CHECK:STDOUT: !61 = !DILocation(line: 59, column: 13, scope: !57)
-// CHECK:STDOUT: !62 = !DILocation(line: 59, column: 12, scope: !57)
-// CHECK:STDOUT: !63 = !DILocation(line: 59, column: 26, scope: !57)
-// CHECK:STDOUT: !64 = !DILocation(line: 59, column: 5, scope: !57)
+// CHECK:STDOUT: !30 = !DISubroutineType(types: !31)
+// CHECK:STDOUT: !31 = !{!16, !16}
+// CHECK:STDOUT: !32 = !{!33}
+// CHECK:STDOUT: !33 = !DILocalVariable(arg: 1, scope: !28, type: !16)
+// CHECK:STDOUT: !34 = !DILocation(line: 33, column: 12, scope: !28)
+// CHECK:STDOUT: !35 = !DILocation(line: 33, column: 5, scope: !28)
+// CHECK:STDOUT: !36 = distinct !DISubprogram(name: "Get", linkageName: "_CGet.Optional.Core.eeebe1ada0072aea", scope: null, file: !29, line: 35, type: !30, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !37)
+// CHECK:STDOUT: !37 = !{!38}
+// CHECK:STDOUT: !38 = !DILocalVariable(arg: 1, scope: !36, type: !16)
+// CHECK:STDOUT: !39 = !DILocation(line: 36, column: 12, scope: !36)
+// CHECK:STDOUT: !40 = !DILocation(line: 36, column: 5, scope: !36)
+// CHECK:STDOUT: !41 = distinct !DISubprogram(name: "None", linkageName: "_CNone.Optional.Core.eeebe1ada0072aea", scope: null, file: !29, line: 26, type: !42, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !42 = !DISubroutineType(types: !43)
+// CHECK:STDOUT: !43 = !{!16}
+// CHECK:STDOUT: !44 = !DILocation(line: 27, column: 12, scope: !41)
+// CHECK:STDOUT: !45 = !DILocation(line: 27, column: 5, scope: !41)
+// CHECK:STDOUT: !46 = distinct !DISubprogram(name: "Has", linkageName: "_CHas.3e8267224c5dc9c2:OptionalStorage.Core.c6c37881d2ed7d9d", scope: null, file: !29, line: 123, type: !30, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !47)
+// CHECK:STDOUT: !47 = !{!48}
+// CHECK:STDOUT: !48 = !DILocalVariable(arg: 1, scope: !46, type: !16)
+// CHECK:STDOUT: !49 = !DILocation(line: 124, column: 12, scope: !46)
+// CHECK:STDOUT: !50 = !DILocation(line: 124, column: 5, scope: !46)
+// CHECK:STDOUT: !51 = distinct !DISubprogram(name: "Get", linkageName: "_CGet.3e8267224c5dc9c2:OptionalStorage.Core.c6c37881d2ed7d9d", scope: null, file: !29, line: 126, type: !30, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !52)
+// CHECK:STDOUT: !52 = !{!53}
+// CHECK:STDOUT: !53 = !DILocalVariable(arg: 1, scope: !51, type: !16)
+// CHECK:STDOUT: !54 = !DILocation(line: 127, column: 12, scope: !51)
+// CHECK:STDOUT: !55 = !DILocation(line: 127, column: 5, scope: !51)
+// CHECK:STDOUT: !56 = distinct !DISubprogram(name: "None", linkageName: "_CNone.3e8267224c5dc9c2:OptionalStorage.Core.c6c37881d2ed7d9d", scope: null, file: !29, line: 110, type: !42, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !57 = !DILocation(line: 112, column: 5, scope: !56)
+// CHECK:STDOUT: !58 = !DILocation(line: 113, column: 5, scope: !56)
+// CHECK:STDOUT: !59 = distinct !DISubprogram(name: "Op", linkageName: "_COp.e4daa70d026fdea2:Copy.Core.3e36085cb869f630", scope: null, file: !60, line: 58, type: !30, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !61)
+// CHECK:STDOUT: !60 = !DIFile(filename: "{{.*}}/prelude/copy.carbon", directory: "")
+// CHECK:STDOUT: !61 = !{!62}
+// CHECK:STDOUT: !62 = !DILocalVariable(arg: 1, scope: !59, type: !16)
+// CHECK:STDOUT: !63 = !DILocation(line: 59, column: 13, scope: !59)
+// CHECK:STDOUT: !64 = !DILocation(line: 59, column: 12, scope: !59)
+// CHECK:STDOUT: !65 = !DILocation(line: 59, column: 26, scope: !59)
+// CHECK:STDOUT: !66 = !DILocation(line: 59, column: 5, scope: !59)

+ 12 - 15
toolchain/lower/testdata/function/call/empty_struct.carbon

@@ -24,16 +24,16 @@ fn Main() {
 // CHECK:STDOUT: ; Function Attrs: nounwind
 // CHECK:STDOUT: define void @_CEcho.Main() #0 !dbg !4 {
 // CHECK:STDOUT: entry:
-// CHECK:STDOUT:   ret void, !dbg !8
+// CHECK:STDOUT:   ret void, !dbg !7
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: ; Function Attrs: nounwind
-// CHECK:STDOUT: define void @_CMain.Main() #0 !dbg !9 {
+// CHECK:STDOUT: define void @_CMain.Main() #0 !dbg !8 {
 // CHECK:STDOUT: entry:
-// CHECK:STDOUT:   %_.var = alloca {}, align 8, !dbg !12
-// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %_.var), !dbg !12
-// CHECK:STDOUT:   call void @_CEcho.Main(), !dbg !13
-// CHECK:STDOUT:   ret void, !dbg !14
+// CHECK:STDOUT:   %_.var = alloca {}, align 8, !dbg !9
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %_.var), !dbg !9
+// CHECK:STDOUT:   call void @_CEcho.Main(), !dbg !10
+// CHECK:STDOUT:   ret void, !dbg !11
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
@@ -51,12 +51,9 @@ fn Main() {
 // CHECK:STDOUT: !3 = !DIFile(filename: "empty_struct.carbon", directory: "")
 // CHECK:STDOUT: !4 = distinct !DISubprogram(name: "Echo", linkageName: "_CEcho.Main", scope: null, file: !3, line: 13, type: !5, spFlags: DISPFlagDefinition, unit: !2)
 // CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
-// CHECK:STDOUT: !6 = !{!7}
-// CHECK:STDOUT: !7 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: null, size: 8)
-// CHECK:STDOUT: !8 = !DILocation(line: 14, column: 3, scope: !4)
-// CHECK:STDOUT: !9 = distinct !DISubprogram(name: "Main", linkageName: "_CMain.Main", scope: null, file: !3, line: 17, type: !10, spFlags: DISPFlagDefinition, unit: !2)
-// CHECK:STDOUT: !10 = !DISubroutineType(types: !11)
-// CHECK:STDOUT: !11 = !{null}
-// CHECK:STDOUT: !12 = !DILocation(line: 18, column: 3, scope: !9)
-// CHECK:STDOUT: !13 = !DILocation(line: 18, column: 15, scope: !9)
-// CHECK:STDOUT: !14 = !DILocation(line: 17, column: 1, scope: !9)
+// CHECK:STDOUT: !6 = !{null}
+// CHECK:STDOUT: !7 = !DILocation(line: 14, column: 3, scope: !4)
+// CHECK:STDOUT: !8 = distinct !DISubprogram(name: "Main", linkageName: "_CMain.Main", scope: null, file: !3, line: 17, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !9 = !DILocation(line: 18, column: 3, scope: !8)
+// CHECK:STDOUT: !10 = !DILocation(line: 18, column: 15, scope: !8)
+// CHECK:STDOUT: !11 = !DILocation(line: 17, column: 1, scope: !8)

+ 12 - 15
toolchain/lower/testdata/function/call/empty_tuple.carbon

@@ -24,16 +24,16 @@ fn Main() {
 // CHECK:STDOUT: ; Function Attrs: nounwind
 // CHECK:STDOUT: define void @_CEcho.Main() #0 !dbg !4 {
 // CHECK:STDOUT: entry:
-// CHECK:STDOUT:   ret void, !dbg !8
+// CHECK:STDOUT:   ret void, !dbg !7
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: ; Function Attrs: nounwind
-// CHECK:STDOUT: define void @_CMain.Main() #0 !dbg !9 {
+// CHECK:STDOUT: define void @_CMain.Main() #0 !dbg !8 {
 // CHECK:STDOUT: entry:
-// CHECK:STDOUT:   %_.var = alloca {}, align 8, !dbg !12
-// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %_.var), !dbg !12
-// CHECK:STDOUT:   call void @_CEcho.Main(), !dbg !13
-// CHECK:STDOUT:   ret void, !dbg !14
+// CHECK:STDOUT:   %_.var = alloca {}, align 8, !dbg !9
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %_.var), !dbg !9
+// CHECK:STDOUT:   call void @_CEcho.Main(), !dbg !10
+// CHECK:STDOUT:   ret void, !dbg !11
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
@@ -51,12 +51,9 @@ fn Main() {
 // CHECK:STDOUT: !3 = !DIFile(filename: "empty_tuple.carbon", directory: "")
 // CHECK:STDOUT: !4 = distinct !DISubprogram(name: "Echo", linkageName: "_CEcho.Main", scope: null, file: !3, line: 13, type: !5, spFlags: DISPFlagDefinition, unit: !2)
 // CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
-// CHECK:STDOUT: !6 = !{!7}
-// CHECK:STDOUT: !7 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: null, size: 8)
-// CHECK:STDOUT: !8 = !DILocation(line: 14, column: 3, scope: !4)
-// CHECK:STDOUT: !9 = distinct !DISubprogram(name: "Main", linkageName: "_CMain.Main", scope: null, file: !3, line: 17, type: !10, spFlags: DISPFlagDefinition, unit: !2)
-// CHECK:STDOUT: !10 = !DISubroutineType(types: !11)
-// CHECK:STDOUT: !11 = !{null}
-// CHECK:STDOUT: !12 = !DILocation(line: 18, column: 3, scope: !9)
-// CHECK:STDOUT: !13 = !DILocation(line: 18, column: 15, scope: !9)
-// CHECK:STDOUT: !14 = !DILocation(line: 17, column: 1, scope: !9)
+// CHECK:STDOUT: !6 = !{null}
+// CHECK:STDOUT: !7 = !DILocation(line: 14, column: 3, scope: !4)
+// CHECK:STDOUT: !8 = distinct !DISubprogram(name: "Main", linkageName: "_CMain.Main", scope: null, file: !3, line: 17, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !9 = !DILocation(line: 18, column: 3, scope: !8)
+// CHECK:STDOUT: !10 = !DILocation(line: 18, column: 15, scope: !8)
+// CHECK:STDOUT: !11 = !DILocation(line: 17, column: 1, scope: !8)

+ 16 - 19
toolchain/lower/testdata/function/call/implicit_empty_tuple_as_arg.carbon

@@ -31,19 +31,19 @@ fn Main() {
 // CHECK:STDOUT: ; Function Attrs: nounwind
 // CHECK:STDOUT: define void @_CBar.Main() #0 !dbg !8 {
 // CHECK:STDOUT: entry:
-// CHECK:STDOUT:   ret void, !dbg !12
+// CHECK:STDOUT:   ret void, !dbg !9
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: ; Function Attrs: nounwind
-// CHECK:STDOUT: define void @_CMain.Main() #0 !dbg !13 {
+// CHECK:STDOUT: define void @_CMain.Main() #0 !dbg !10 {
 // CHECK:STDOUT: entry:
-// CHECK:STDOUT:   %_.var = alloca {}, align 8, !dbg !14
-// CHECK:STDOUT:   %.loc19_23.1.temp = alloca {}, align 8, !dbg !15
-// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %_.var), !dbg !14
-// CHECK:STDOUT:   call void @_CFoo.Main(), !dbg !15
-// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %.loc19_23.1.temp), !dbg !15
-// CHECK:STDOUT:   call void @_CBar.Main(), !dbg !16
-// CHECK:STDOUT:   ret void, !dbg !17
+// CHECK:STDOUT:   %_.var = alloca {}, align 8, !dbg !11
+// CHECK:STDOUT:   %.loc19_23.1.temp = alloca {}, align 8, !dbg !12
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %_.var), !dbg !11
+// CHECK:STDOUT:   call void @_CFoo.Main(), !dbg !12
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %.loc19_23.1.temp), !dbg !12
+// CHECK:STDOUT:   call void @_CBar.Main(), !dbg !13
+// CHECK:STDOUT:   ret void, !dbg !14
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
@@ -66,13 +66,10 @@ fn Main() {
 // CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
 // CHECK:STDOUT: !6 = !{null}
 // CHECK:STDOUT: !7 = !DILocation(line: 14, column: 1, scope: !4)
-// CHECK:STDOUT: !8 = distinct !DISubprogram(name: "Bar", linkageName: "_CBar.Main", scope: null, file: !3, line: 16, type: !9, spFlags: DISPFlagDefinition, unit: !2)
-// CHECK:STDOUT: !9 = !DISubroutineType(types: !10)
-// CHECK:STDOUT: !10 = !{!11}
-// CHECK:STDOUT: !11 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: null, size: 8)
-// CHECK:STDOUT: !12 = !DILocation(line: 16, column: 23, scope: !8)
-// CHECK:STDOUT: !13 = distinct !DISubprogram(name: "Main", linkageName: "_CMain.Main", scope: null, file: !3, line: 18, type: !5, spFlags: DISPFlagDefinition, unit: !2)
-// CHECK:STDOUT: !14 = !DILocation(line: 19, column: 3, scope: !13)
-// CHECK:STDOUT: !15 = !DILocation(line: 19, column: 19, scope: !13)
-// CHECK:STDOUT: !16 = !DILocation(line: 19, column: 15, scope: !13)
-// CHECK:STDOUT: !17 = !DILocation(line: 18, column: 1, scope: !13)
+// CHECK:STDOUT: !8 = distinct !DISubprogram(name: "Bar", linkageName: "_CBar.Main", scope: null, file: !3, line: 16, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !9 = !DILocation(line: 16, column: 23, scope: !8)
+// CHECK:STDOUT: !10 = distinct !DISubprogram(name: "Main", linkageName: "_CMain.Main", scope: null, file: !3, line: 18, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !11 = !DILocation(line: 19, column: 3, scope: !10)
+// CHECK:STDOUT: !12 = !DILocation(line: 19, column: 19, scope: !10)
+// CHECK:STDOUT: !13 = !DILocation(line: 19, column: 15, scope: !10)
+// CHECK:STDOUT: !14 = !DILocation(line: 18, column: 1, scope: !10)

+ 68 - 0
toolchain/lower/testdata/function/call/ref_return.carbon

@@ -0,0 +1,68 @@
+// 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/function/call/ref_return.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/function/call/ref_return.carbon
+
+fn Echo(ref x: i32) -> ref i32 {
+  return x;
+}
+
+fn Main() {
+  var a: i32 = 0;
+  Echo(ref a) = 1;
+}
+
+// CHECK:STDOUT: ; ModuleID = 'ref_return.carbon'
+// CHECK:STDOUT: source_filename = "ref_return.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define ptr @_CEcho.Main(ptr %x) #0 !dbg !4 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   ret ptr %x, !dbg !11
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define void @_CMain.Main() #0 !dbg !12 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %a.var = alloca i32, align 4, !dbg !15
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %a.var), !dbg !15
+// CHECK:STDOUT:   store i32 0, ptr %a.var, align 4, !dbg !15
+// CHECK:STDOUT:   %Echo.call = call ptr @_CEcho.Main(ptr %a.var), !dbg !16
+// CHECK:STDOUT:   store i32 1, ptr %Echo.call, align 4, !dbg !16
+// CHECK:STDOUT:   ret void, !dbg !17
+// 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: 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: "ref_return.carbon", directory: "")
+// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "Echo", linkageName: "_CEcho.Main", scope: null, file: !3, line: 13, type: !5, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !9)
+// CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
+// CHECK:STDOUT: !6 = !{!7, !8}
+// CHECK:STDOUT: !7 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: null, size: 8)
+// CHECK:STDOUT: !8 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+// CHECK:STDOUT: !9 = !{!10}
+// CHECK:STDOUT: !10 = !DILocalVariable(arg: 1, scope: !4, type: !8)
+// CHECK:STDOUT: !11 = !DILocation(line: 14, column: 3, scope: !4)
+// CHECK:STDOUT: !12 = distinct !DISubprogram(name: "Main", linkageName: "_CMain.Main", scope: null, file: !3, line: 17, type: !13, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !13 = !DISubroutineType(types: !14)
+// CHECK:STDOUT: !14 = !{null}
+// CHECK:STDOUT: !15 = !DILocation(line: 18, column: 3, scope: !12)
+// CHECK:STDOUT: !16 = !DILocation(line: 19, column: 3, scope: !12)
+// CHECK:STDOUT: !17 = !DILocation(line: 17, column: 1, scope: !12)

+ 1 - 1
toolchain/lower/testdata/function/generic/type_representation.carbon

@@ -262,7 +262,7 @@ fn F_nested_tuple(a: ((i32, i32), X)) -> ((i32, i32), X) {
 // CHECK:STDOUT: !30 = !DILocation(line: 44, column: 3, scope: !26)
 // CHECK:STDOUT: !31 = distinct !DISubprogram(name: "Op", linkageName: "_COp.61ea2aba74ab3bf1:Copy.Main", scope: null, file: !3, line: 48, type: !32, spFlags: DISPFlagDefinition, unit: !2)
 // CHECK:STDOUT: !32 = !DISubroutineType(types: !33)
-// CHECK:STDOUT: !33 = !{!19}
+// CHECK:STDOUT: !33 = !{null}
 // CHECK:STDOUT: !34 = !DILocation(line: 49, column: 5, scope: !31)
 // CHECK:STDOUT: !35 = distinct !DISubprogram(name: "F_empty_tuple", linkageName: "_CF_empty_tuple.Main", scope: null, file: !3, line: 53, type: !32, spFlags: DISPFlagDefinition, unit: !2)
 // CHECK:STDOUT: !36 = !DILocation(line: 54, column: 10, scope: !35)

+ 0 - 19
toolchain/sem_ir/function.cpp

@@ -112,25 +112,6 @@ auto DecomposeVirtualFunction(const File& sem_ir, InstId fn_decl_id,
           .specific_id = specific_id};
 }
 
-auto Function::GetParamPatternInfoFromPatternId(const File& sem_ir,
-                                                InstId pattern_id)
-    -> std::optional<ParamPatternInfo> {
-  auto inst_id = pattern_id;
-  auto inst = sem_ir.insts().Get(inst_id);
-
-  sem_ir.insts().TryUnwrap(inst, inst_id, &VarPattern::subpattern_id);
-  auto [param_pattern, param_pattern_id] =
-      sem_ir.insts().TryUnwrap(inst, inst_id, &AnyParamPattern::subpattern_id);
-  if (!param_pattern) {
-    return std::nullopt;
-  }
-
-  auto binding_pattern = inst.As<AnyBindingPattern>();
-  return {{.inst_id = param_pattern_id,
-           .inst = *param_pattern,
-           .entity_name_id = binding_pattern.entity_name_id}};
-}
-
 auto Function::GetDeclaredReturnType(const File& file,
                                      SpecificId specific_id) const -> TypeId {
   if (!return_type_inst_id.has_value()) {

+ 0 - 11
toolchain/sem_ir/function.h

@@ -163,17 +163,6 @@ struct Function : public EntityWithParamsBase,
                : InstId::None;
   }
 
-  // Given the ID of an instruction from `param_patterns_id` or
-  // `implicit_param_patterns_id`, returns a `ParamPatternInfo` value with the
-  // corresponding `Call` parameter pattern, its ID, and the entity_name_id of
-  // the underlying binding pattern, or std::nullopt if there is no
-  // corresponding `Call` parameter.
-  // TODO: Remove this, by exposing `Call` parameter patterns instead of `Call`
-  // parameters in EntityWithParams.
-  static auto GetParamPatternInfoFromPatternId(const File& sem_ir,
-                                               InstId param_pattern_id)
-      -> std::optional<ParamPatternInfo>;
-
   // Gets the declared return type for a specific version of this function, or
   // the canonical return type for the original declaration no specific is
   // specified.  Returns `None` if no return type was specified, in which