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

Split a cross-file `Lower::Context` out of `Lower::FileContext`. (#5583)

In preparation for lowering information from multiple `SemIR::File`s
into a single `llvm::Module`. The primary purpose of this is to support
lowering a local specific for an imported generic function, where the
instructions for the generic function are in a different file than the
instructions for the specific. See #5475 for a draft PR implementing
that functionality on top of this.

The per-`llvm::Module` state now lives in `Lower::Context`, and
`Lower::FileContext` tracks only the per-`SemIR::File` information.
`Lower::Context` should not mention any `SemIR` IDs that are
file-specific. For now, the C++ lowering and the specific coalescing
logic are kept per-file for simplicity.
Richard Smith 11 месяцев назад
Родитель
Сommit
e91840e1b6

+ 2 - 2
toolchain/driver/compile_subcommand.cpp

@@ -713,8 +713,8 @@ auto CompilationUnit::RunLower() -> void {
       subtrees = cache_->tree_and_subtrees_getters();
     }
     module_ = Lower::LowerToLLVM(*llvm_context_, driver_env_->fs, subtrees,
-                                 input_filename_, *sem_ir_, sem_ir_->cpp_ast(),
-                                 &inst_namer, vlog_stream_);
+                                 input_filename_, *sem_ir_, &inst_namer,
+                                 vlog_stream_);
   });
   if (vlog_stream_) {
     CARBON_VLOG("*** llvm::Module ***\n");

+ 2 - 0
toolchain/lower/BUILD

@@ -30,6 +30,7 @@ cc_library(
     srcs = [
         "constant.cpp",
         "constant.h",
+        "context.cpp",
         "file_context.cpp",
         "function_context.cpp",
         "mangler.cpp",
@@ -40,6 +41,7 @@ cc_library(
         "handle*.cpp",
     ]),
     hdrs = [
+        "context.h",
         "file_context.h",
         "function_context.h",
     ],

+ 88 - 0
toolchain/lower/context.cpp

@@ -0,0 +1,88 @@
+// 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 "toolchain/lower/context.h"
+
+#include "common/check.h"
+#include "common/vlog.h"
+#include "llvm/Transforms/Utils/ModuleUtils.h"
+#include "toolchain/lower/file_context.h"
+#include "toolchain/sem_ir/inst_namer.h"
+
+namespace Carbon::Lower {
+
+Context::Context(llvm::LLVMContext& llvm_context,
+                 llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
+                 std::optional<llvm::ArrayRef<Parse::GetTreeAndSubtreesFn>>
+                     tree_and_subtrees_getters_for_debug_info,
+                 llvm::StringRef module_name, llvm::raw_ostream* vlog_stream)
+    : llvm_context_(&llvm_context),
+      llvm_module_(std::make_unique<llvm::Module>(module_name, llvm_context)),
+      file_system_(std::move(fs)),
+      di_builder_(*llvm_module_),
+      di_compile_unit_(
+          tree_and_subtrees_getters_for_debug_info
+              ? BuildDICompileUnit(module_name, *llvm_module_, di_builder_)
+              : nullptr),
+      tree_and_subtrees_getters_for_debug_info_(
+          tree_and_subtrees_getters_for_debug_info),
+      vlog_stream_(vlog_stream) {}
+
+auto Context::GetFileContext(const SemIR::File* file,
+                             const SemIR::InstNamer* inst_namer)
+    -> FileContext& {
+  auto insert_result = file_contexts_.Insert(file->check_ir_id(), [&] {
+    auto file_context =
+        std::make_unique<FileContext>(*this, *file, inst_namer, vlog_stream_);
+    file_context->PrepareToLower();
+    return file_context;
+  });
+  return *insert_result.value();
+}
+
+auto Context::Finalize() && -> std::unique_ptr<llvm::Module> {
+  file_contexts_.ForEach(
+      [](auto, auto& file_context) { file_context->Finalize(); });
+  return std::move(llvm_module_);
+}
+
+auto Context::BuildDICompileUnit(llvm::StringRef module_name,
+                                 llvm::Module& llvm_module,
+                                 llvm::DIBuilder& di_builder)
+    -> llvm::DICompileUnit* {
+  llvm_module.addModuleFlag(llvm::Module::Max, "Dwarf Version", 5);
+  llvm_module.addModuleFlag(llvm::Module::Warning, "Debug Info Version",
+                            llvm::DEBUG_METADATA_VERSION);
+  // TODO: Include directory path in the compile_unit_file.
+  llvm::DIFile* compile_unit_file = di_builder.createFile(module_name, "");
+  // TODO: Introduce a new language code for Carbon. C works well for now since
+  // it's something debuggers will already know/have support for at least.
+  // Probably have to bump to C++ at some point for virtual functions,
+  // templates, etc.
+  return di_builder.createCompileUnit(llvm::dwarf::DW_LANG_C, compile_unit_file,
+                                      "carbon",
+                                      /*isOptimized=*/false, /*Flags=*/"",
+                                      /*RV=*/0);
+}
+
+auto Context::GetLocForDI(SemIR::AbsoluteNodeId abs_node_id) -> LocForDI {
+  const auto& tree_and_subtrees =
+      (*tree_and_subtrees_getters_for_debug_info_)[abs_node_id.check_ir_id()
+                                                       .index]();
+  const auto& tokens = tree_and_subtrees.tree().tokens();
+
+  if (abs_node_id.node_id().has_value()) {
+    auto token =
+        tree_and_subtrees.GetSubtreeTokenRange(abs_node_id.node_id()).begin;
+    return {.filename = tokens.source().filename(),
+            .line_number = tokens.GetLineNumber(token),
+            .column_number = tokens.GetColumnNumber(token)};
+  } else {
+    return {.filename = tokens.source().filename(),
+            .line_number = 0,
+            .column_number = 0};
+  }
+}
+
+}  // namespace Carbon::Lower

+ 133 - 0
toolchain/lower/context.h

@@ -0,0 +1,133 @@
+// 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
+
+#ifndef CARBON_TOOLCHAIN_LOWER_CONTEXT_H_
+#define CARBON_TOOLCHAIN_LOWER_CONTEXT_H_
+
+#include <memory>
+#include <optional>
+#include <utility>
+
+#include "llvm/IR/Constants.h"
+#include "llvm/IR/DIBuilder.h"
+#include "llvm/IR/LLVMContext.h"
+#include "llvm/IR/Module.h"
+#include "toolchain/parse/tree_and_subtrees.h"
+#include "toolchain/sem_ir/absolute_node_id.h"
+#include "toolchain/sem_ir/ids.h"
+#include "toolchain/sem_ir/inst_namer.h"
+
+namespace Carbon::Lower {
+
+class FileContext;
+
+// Context for lowering to an LLVM module.
+class Context {
+ public:
+  // Location information for use with DebugInfo. The line_number and
+  // column_number are >= 0, with 0 as unknown, so that they can be passed
+  // directly to DebugInfo.
+  struct LocForDI {
+    llvm::StringRef filename;
+    int32_t line_number;
+    int32_t column_number;
+  };
+
+  explicit Context(llvm::LLVMContext& llvm_context,
+                   llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
+                   std::optional<llvm::ArrayRef<Parse::GetTreeAndSubtreesFn>>
+                       tree_and_subtrees_getters_for_debug_info,
+                   llvm::StringRef module_name, llvm::raw_ostream* vlog_stream);
+
+  // Gets or creates the `FileContext` for a given SemIR file. If an
+  // `inst_namer` is specified the first time this is called for a file, it will
+  // be used for that file. Otherwise, no instruction namer will be used.
+  // TODO: Consider building an InstNamer if we're not given one.
+  auto GetFileContext(const SemIR::File* file,
+                      const SemIR::InstNamer* inst_namer = nullptr)
+      -> FileContext&;
+
+  // Finishes lowering and takes ownership of the LLVM module. The context
+  // cannot be used further after calling this.
+  auto Finalize() && -> std::unique_ptr<llvm::Module>;
+
+  // Returns location information for use with DebugInfo.
+  auto GetLocForDI(SemIR::AbsoluteNodeId abs_node_id) -> LocForDI;
+
+  // Returns a lowered value to use for a value of type `type`.
+  auto GetTypeAsValue() -> llvm::Constant* {
+    return llvm::ConstantStruct::get(GetTypeType());
+  }
+
+  // Returns a lowered value to use for a value of int literal type.
+  auto GetIntLiteralAsValue() -> llvm::Constant* {
+    // TODO: Consider adding a named struct type for integer literals.
+    return llvm::ConstantStruct::get(llvm::StructType::get(llvm_context()));
+  }
+
+  // Returns the empty LLVM struct type used to represent the type `type`.
+  auto GetTypeType() -> llvm::StructType* {
+    if (!type_type_) {
+      // `type` is lowered to an empty LLVM StructType.
+      type_type_ = llvm::StructType::create(*llvm_context_, {}, "type");
+    }
+    return type_type_;
+  }
+
+  auto llvm_context() -> llvm::LLVMContext& { return *llvm_context_; }
+  auto llvm_module() -> llvm::Module& { return *llvm_module_; }
+  auto file_system() -> llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem>& {
+    return file_system_;
+  }
+  auto di_builder() -> llvm::DIBuilder& { return di_builder_; }
+  auto di_compile_unit() -> llvm::DICompileUnit* { return di_compile_unit_; }
+
+  auto printf_int_format_string() -> llvm::Value* {
+    return printf_int_format_string_;
+  }
+  auto SetPrintfIntFormatString(llvm::Value* printf_int_format_string) {
+    CARBON_CHECK(!printf_int_format_string_,
+                 "PrintInt formatting string already generated");
+    printf_int_format_string_ = printf_int_format_string;
+  }
+
+ private:
+  // Create the DICompileUnit metadata for this compilation.
+  auto BuildDICompileUnit(llvm::StringRef module_name,
+                          llvm::Module& llvm_module,
+                          llvm::DIBuilder& di_builder) -> llvm::DICompileUnit*;
+
+  // State for building the LLVM IR.
+  llvm::LLVMContext* llvm_context_;
+  std::unique_ptr<llvm::Module> llvm_module_;
+
+  // The filesystem for source code.
+  llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> file_system_;
+
+  // State for building the LLVM IR debug info metadata.
+  llvm::DIBuilder di_builder_;
+
+  // The DICompileUnit, if any - null implies debug info is not being emitted.
+  llvm::DICompileUnit* di_compile_unit_;
+
+  // The trees are only provided when debug info should be emitted.
+  std::optional<llvm::ArrayRef<Parse::GetTreeAndSubtreesFn>>
+      tree_and_subtrees_getters_for_debug_info_;
+
+  // The optional vlog stream.
+  llvm::raw_ostream* vlog_stream_;
+
+  // The `FileContext`s for each IR that is involved in this lowering action.
+  Map<SemIR::CheckIRId, std::unique_ptr<FileContext>> file_contexts_;
+
+  // Lowered version of the builtin type `type`.
+  llvm::StructType* type_type_ = nullptr;
+
+  // Global format string for `printf.int.format` used by the PrintInt builtin.
+  llvm::Value* printf_int_format_string_ = nullptr;
+};
+
+}  // namespace Carbon::Lower
+
+#endif  // CARBON_TOOLCHAIN_LOWER_CONTEXT_H_

+ 41 - 83
toolchain/lower/file_context.cpp

@@ -36,26 +36,11 @@
 
 namespace Carbon::Lower {
 
-FileContext::FileContext(
-    llvm::LLVMContext& llvm_context,
-    llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
-    std::optional<llvm::ArrayRef<Parse::GetTreeAndSubtreesFn>>
-        tree_and_subtrees_getters_for_debug_info,
-    llvm::StringRef module_name, const SemIR::File& sem_ir,
-    clang::ASTUnit* cpp_ast, const SemIR::InstNamer* inst_namer,
-    llvm::raw_ostream* vlog_stream)
-    : llvm_context_(&llvm_context),
-      llvm_module_(std::make_unique<llvm::Module>(module_name, llvm_context)),
-      fs_(std::move(fs)),
-      di_builder_(*llvm_module_),
-      di_compile_unit_(
-          tree_and_subtrees_getters_for_debug_info
-              ? BuildDICompileUnit(module_name, *llvm_module_, di_builder_)
-              : nullptr),
-      tree_and_subtrees_getters_for_debug_info_(
-          tree_and_subtrees_getters_for_debug_info),
+FileContext::FileContext(Context& context, const SemIR::File& sem_ir,
+                         const SemIR::InstNamer* inst_namer,
+                         llvm::raw_ostream* vlog_stream)
+    : context_(&context),
       sem_ir_(&sem_ir),
-      cpp_ast_(cpp_ast),
       inst_namer_(inst_namer),
       vlog_stream_(vlog_stream) {
   // Initialization that relies on invariants of the class.
@@ -65,11 +50,12 @@ FileContext::FileContext(
 }
 
 // TODO: Move this to lower.cpp.
-auto FileContext::Run() -> std::unique_ptr<llvm::Module> {
-  CARBON_CHECK(llvm_module_, "Run can only be called once.");
-
+auto FileContext::PrepareToLower() -> void {
   if (cpp_code_generator_) {
-    cpp_code_generator_->Initialize(cpp_ast()->getASTContext());
+    // Clang code generation should not actually modify the AST, but isn't
+    // const-correct.
+    cpp_code_generator_->Initialize(
+        const_cast<clang::ASTContext&>(cpp_ast()->getASTContext()));
   }
 
   // Lower all types that were required to be complete.
@@ -86,12 +72,6 @@ auto FileContext::Run() -> std::unique_ptr<llvm::Module> {
     functions_[id.index] = BuildFunctionDecl(id);
   }
 
-  for (const auto& class_info : sem_ir_->classes().array_ref()) {
-    if (auto* llvm_vtable = BuildVtable(class_info)) {
-      global_variables_.Insert(class_info.vtable_id, llvm_vtable);
-    }
-  }
-
   // Specific functions are lowered when we emit a reference to them.
   specific_functions_.resize(sem_ir_->specifics().size());
   // Additional data stored for specifics, for when attempting to coalesce.
@@ -106,6 +86,15 @@ auto FileContext::Run() -> std::unique_ptr<llvm::Module> {
   // Lower constants.
   constants_.resize(sem_ir_->insts().size());
   LowerConstants(*this, constants_);
+}
+
+// TODO: Move this to lower.cpp.
+auto FileContext::LowerDefinitions() -> void {
+  for (const auto& class_info : sem_ir_->classes().array_ref()) {
+    if (auto* llvm_vtable = BuildVtable(class_info)) {
+      global_variables_.Insert(class_info.vtable_id, llvm_vtable);
+    }
+  }
 
   // Lower global variable definitions.
   // TODO: Storing both a `constants_` array and a separate `global_variables_`
@@ -148,10 +137,6 @@ auto FileContext::Run() -> std::unique_ptr<llvm::Module> {
     BuildFunctionDefinition(function_id, specific_id);
   }
 
-  // Find equivalent specifics (from the same generic), replace all uses and
-  // remove duplicately lowered function definitions.
-  CoalesceEquivalentSpecifics();
-
   // Append `__global_init` to `llvm::global_ctors` to initialize global
   // variables.
   if (sem_ir().global_ctor_id().has_value()) {
@@ -161,15 +146,22 @@ auto FileContext::Run() -> std::unique_ptr<llvm::Module> {
   }
 
   if (cpp_code_generator_) {
-    cpp_code_generator_->HandleTranslationUnit(cpp_ast()->getASTContext());
+    // Clang code generation should not actually modify the AST, but isn't
+    // const-correct.
+    cpp_code_generator_->HandleTranslationUnit(
+        const_cast<clang::ASTContext&>(cpp_ast()->getASTContext()));
     bool link_error = llvm::Linker::linkModules(
-        /*Dest=*/*llvm_module_,
+        /*Dest=*/llvm_module(),
         /*Src=*/std::unique_ptr<llvm::Module>(
             cpp_code_generator_->ReleaseModule()));
     CARBON_CHECK(!link_error);
   }
+}
 
-  return std::move(llvm_module_);
+auto FileContext::Finalize() -> void {
+  // Find equivalent specifics (from the same generic), replace all uses and
+  // remove duplicately lowered function definitions.
+  CoalesceEquivalentSpecifics();
 }
 
 auto FileContext::InsertPair(
@@ -395,25 +387,6 @@ auto FileContext::AreFunctionBodiesEquivalent(
   return true;
 }
 
-auto FileContext::BuildDICompileUnit(llvm::StringRef module_name,
-                                     llvm::Module& llvm_module,
-                                     llvm::DIBuilder& di_builder)
-    -> llvm::DICompileUnit* {
-  llvm_module.addModuleFlag(llvm::Module::Max, "Dwarf Version", 5);
-  llvm_module.addModuleFlag(llvm::Module::Warning, "Debug Info Version",
-                            llvm::DEBUG_METADATA_VERSION);
-  // TODO: Include directory path in the compile_unit_file.
-  llvm::DIFile* compile_unit_file = di_builder.createFile(module_name, "");
-  // TODO: Introduce a new language code for Carbon. C works well for now since
-  // it's something debuggers will already know/have support for at least.
-  // Probably have to bump to C++ at some point for virtual functions,
-  // templates, etc.
-  return di_builder.createCompileUnit(llvm::dwarf::DW_LANG_C, compile_unit_file,
-                                      "carbon",
-                                      /*isOptimized=*/false, /*Flags=*/"",
-                                      /*RV=*/0);
-}
-
 auto FileContext::CreateCppCodeGenerator()
     -> std::unique_ptr<clang::CodeGenerator> {
   if (!cpp_ast()) {
@@ -421,15 +394,16 @@ auto FileContext::CreateCppCodeGenerator()
   }
 
   RawStringOstream clang_module_name_stream;
-  clang_module_name_stream << llvm_module_->getName() << ".clang";
+  clang_module_name_stream << llvm_module().getName() << ".clang";
 
   // Do not emit Clang's name and version as the creator of the output file.
   cpp_code_gen_options_.EmitVersionIdentMetadata = false;
 
   return std::unique_ptr<clang::CodeGenerator>(clang::CreateLLVMCodeGen(
       cpp_ast()->getASTContext().getDiagnostics(),
-      clang_module_name_stream.TakeStr(), fs_, cpp_header_search_options_,
-      cpp_preprocessor_options_, cpp_code_gen_options_, *llvm_context_));
+      clang_module_name_stream.TakeStr(), context().file_system(),
+      cpp_header_search_options_, cpp_preprocessor_options_,
+      cpp_code_gen_options_, llvm_context()));
 }
 
 auto FileContext::GetGlobal(SemIR::InstId inst_id,
@@ -858,7 +832,7 @@ auto FileContext::BuildFunctionBody(SemIR::FunctionId function_id,
 auto FileContext::BuildDISubprogram(const SemIR::Function& function,
                                     const llvm::Function* llvm_function)
     -> llvm::DISubprogram* {
-  if (!di_compile_unit_) {
+  if (!context().di_compile_unit()) {
     return nullptr;
   }
   auto name = sem_ir().names().GetAsStringIfIdentifier(function.name_id);
@@ -867,12 +841,12 @@ auto FileContext::BuildDISubprogram(const SemIR::Function& function,
   auto loc = GetLocForDI(function.definition_id);
   // TODO: Add more details here, including real subroutine type (once type
   // information is built), etc.
-  return di_builder_.createFunction(
-      di_compile_unit_, *name, llvm_function->getName(),
-      /*File=*/di_builder_.createFile(loc.filename, ""),
+  return context().di_builder().createFunction(
+      context().di_compile_unit(), *name, llvm_function->getName(),
+      /*File=*/context().di_builder().createFile(loc.filename, ""),
       /*LineNo=*/loc.line_number,
-      di_builder_.createSubroutineType(
-          di_builder_.getOrCreateTypeArray(std::nullopt)),
+      context().di_builder().createSubroutineType(
+          context().di_builder().getOrCreateTypeArray(std::nullopt)),
       /*ScopeLine=*/0, llvm::DINode::FlagZero,
       llvm::DISubprogram::SPFlagDefinition);
 }
@@ -1079,25 +1053,9 @@ auto FileContext::BuildGlobalVariableDecl(SemIR::VarStorage var_storage)
                                   /*Initializer=*/nullptr, mangled_name);
 }
 
-auto FileContext::GetLocForDI(SemIR::InstId inst_id) -> LocForDI {
-  SemIR::AbsoluteNodeId resolved =
-      GetAbsoluteNodeId(sem_ir_, SemIR::LocId(inst_id)).back();
-  const auto& tree_and_subtrees =
-      (*tree_and_subtrees_getters_for_debug_info_)[resolved.check_ir_id()
-                                                       .index]();
-  const auto& tokens = tree_and_subtrees.tree().tokens();
-
-  if (resolved.node_id().has_value()) {
-    auto token =
-        tree_and_subtrees.GetSubtreeTokenRange(resolved.node_id()).begin;
-    return {.filename = tokens.source().filename(),
-            .line_number = tokens.GetLineNumber(token),
-            .column_number = tokens.GetColumnNumber(token)};
-  } else {
-    return {.filename = tokens.source().filename(),
-            .line_number = 0,
-            .column_number = 0};
-  }
+auto FileContext::GetLocForDI(SemIR::InstId inst_id) -> Context::LocForDI {
+  return context().GetLocForDI(
+      GetAbsoluteNodeId(sem_ir_, SemIR::LocId(inst_id)).back());
 }
 
 auto FileContext::BuildVtable(const SemIR::Class& class_info)

+ 28 - 77
toolchain/lower/file_context.h

@@ -8,13 +8,8 @@
 #include "clang/Basic/CodeGenOptions.h"
 #include "clang/CodeGen/ModuleBuilder.h"
 #include "clang/Lex/PreprocessorOptions.h"
-#include "common/raw_string_ostream.h"
-#include "llvm/IR/Constants.h"
-#include "llvm/IR/DIBuilder.h"
-#include "llvm/IR/Instructions.h"
-#include "llvm/IR/LLVMContext.h"
-#include "llvm/IR/Module.h"
 #include "llvm/Support/BLAKE3.h"
+#include "toolchain/lower/context.h"
 #include "toolchain/parse/tree_and_subtrees.h"
 #include "toolchain/sem_ir/file.h"
 #include "toolchain/sem_ir/ids.h"
@@ -22,18 +17,9 @@
 
 namespace Carbon::Lower {
 
-// Context and shared functionality for lowering handlers.
+// Context and shared functionality for lowering within a SemIR file.
 class FileContext {
  public:
-  // Location information for use with DebugInfo. The line_number and
-  // column_number are >= 0, with 0 as unknown, so that they can be passed
-  // directly to DebugInfo.
-  struct LocForDI {
-    llvm::StringRef filename;
-    int32_t line_number;
-    int32_t column_number;
-  };
-
   // Describes a specific function's body fingerprint.
   struct SpecificFunctionFingerprint {
     // Fingerprint with all specific-dependent instructions, except specific
@@ -50,28 +36,25 @@ class FileContext {
     llvm::SmallVector<SemIR::SpecificId> calls;
   };
 
-  explicit FileContext(
-      llvm::LLVMContext& llvm_context,
-      llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
-      std::optional<llvm::ArrayRef<Parse::GetTreeAndSubtreesFn>>
-          tree_and_subtrees_getters_for_debug_info,
-      llvm::StringRef module_name, const SemIR::File& sem_ir,
-      clang::ASTUnit* cpp_ast, const SemIR::InstNamer* inst_namer,
-      llvm::raw_ostream* vlog_stream);
-
-  // Lowers the SemIR::File to LLVM IR. Should only be called once, and handles
-  // the main execution loop.
-  auto Run() -> std::unique_ptr<llvm::Module>;
-
-  // Create the DICompileUnit metadata for this compilation.
-  auto BuildDICompileUnit(llvm::StringRef module_name,
-                          llvm::Module& llvm_module,
-                          llvm::DIBuilder& di_builder) -> llvm::DICompileUnit*;
+  explicit FileContext(Context& context, const SemIR::File& sem_ir,
+                       const SemIR::InstNamer* inst_namer,
+                       llvm::raw_ostream* vlog_stream);
 
   // Creates the Clang `CodeGenerator` to generate LLVM module from imported C++
   // code. Returns null when not importing C++.
   auto CreateCppCodeGenerator() -> std::unique_ptr<clang::CodeGenerator>;
 
+  // Prepares to lower code in this IR, by precomputing needed LLVM types,
+  // constants, declarations, etc. Should only be called once, before we lower
+  // anything in this file.
+  auto PrepareToLower() -> void;
+
+  // Lowers all the definitions provided by the SemIR::File to LLVM IR.
+  auto LowerDefinitions() -> void;
+
+  // Perform final cleanup tasks once all lowering has been completed.
+  auto Finalize() -> void;
+
   // Gets a callable's function. Returns nullptr for a builtin.
   auto GetFunction(SemIR::FunctionId function_id) -> llvm::Function* {
     return functions_[function_id.index];
@@ -92,17 +75,16 @@ class FileContext {
   }
 
   // Returns location information for use with DebugInfo.
-  auto GetLocForDI(SemIR::InstId inst_id) -> LocForDI;
+  auto GetLocForDI(SemIR::InstId inst_id) -> Context::LocForDI;
 
   // Returns a lowered value to use for a value of type `type`.
   auto GetTypeAsValue() -> llvm::Constant* {
-    return llvm::ConstantStruct::get(GetTypeType());
+    return context().GetTypeAsValue();
   }
 
   // Returns a lowered value to use for a value of int literal type.
   auto GetIntLiteralAsValue() -> llvm::Constant* {
-    // TODO: Consider adding a named struct type for integer literals.
-    return llvm::ConstantStruct::get(llvm::StructType::get(llvm_context()));
+    return context().GetIntLiteralAsValue();
   }
 
   // Returns a global value for the given instruction.
@@ -110,29 +92,22 @@ class FileContext {
       -> llvm::Value*;
 
   // Returns the empty LLVM struct type used to represent the type `type`.
-  auto GetTypeType() -> llvm::StructType* {
-    if (!type_type_) {
-      // `type` is lowered to an empty LLVM StructType.
-      type_type_ = llvm::StructType::create(*llvm_context_, {}, "type");
-    }
-    return type_type_;
-  }
+  auto GetTypeType() -> llvm::StructType* { return context().GetTypeType(); }
 
-  auto llvm_context() -> llvm::LLVMContext& { return *llvm_context_; }
-  auto llvm_module() -> llvm::Module& { return *llvm_module_; }
+  auto context() -> Context& { return *context_; }
+  auto llvm_context() -> llvm::LLVMContext& { return context().llvm_context(); }
+  auto llvm_module() -> llvm::Module& { return context().llvm_module(); }
   auto sem_ir() -> const SemIR::File& { return *sem_ir_; }
-  auto cpp_ast() -> clang::ASTUnit* { return cpp_ast_; }
+  auto cpp_ast() -> const clang::ASTUnit* { return sem_ir().cpp_ast(); }
   auto inst_namer() -> const SemIR::InstNamer* { return inst_namer_; }
   auto global_variables() -> const Map<SemIR::InstId, llvm::GlobalVariable*>& {
     return global_variables_;
   }
   auto printf_int_format_string() -> llvm::Value* {
-    return printf_int_format_string_;
+    return context().printf_int_format_string();
   }
   auto SetPrintfIntFormatString(llvm::Value* printf_int_format_string) {
-    CARBON_CHECK(!printf_int_format_string_,
-                 "PrintInt formatting string already generated");
-    printf_int_format_string_ = printf_int_format_string;
+    context().SetPrintfIntFormatString(printf_int_format_string);
   }
 
   struct FunctionTypeInfo {
@@ -253,30 +228,12 @@ class FileContext {
       const Set<std::pair<SemIR::SpecificId, SemIR::SpecificId>>& set_of_pairs)
       -> bool;
 
-  // State for building the LLVM IR.
-  llvm::LLVMContext* llvm_context_;
-  std::unique_ptr<llvm::Module> llvm_module_;
-
-  // The filesystem for source code.
-  llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs_;
-
-  // State for building the LLVM IR debug info metadata.
-  llvm::DIBuilder di_builder_;
-
-  // The DICompileUnit, if any - null implies debug info is not being emitted.
-  llvm::DICompileUnit* di_compile_unit_;
-
-  // The trees are only provided when debug info should be emitted.
-  std::optional<llvm::ArrayRef<Parse::GetTreeAndSubtreesFn>>
-      tree_and_subtrees_getters_for_debug_info_;
+  // The overall lowering context.
+  Context* context_;
 
   // The input SemIR.
   const SemIR::File* const sem_ir_;
 
-  // A mutable Clang AST is necessary for lowering since using the AST in lower
-  // modifies it.
-  clang::ASTUnit* cpp_ast_;
-
   // The options used to create the Clang Code Generator.
   clang::HeaderSearchOptions cpp_header_search_options_;
   clang::PreprocessorOptions cpp_preprocessor_options_;
@@ -315,9 +272,6 @@ class FileContext {
   // resize this directly to the (often large) correct size.
   llvm::SmallVector<llvm::Type*, 0> types_;
 
-  // Lowered version of the builtin type `type`.
-  llvm::StructType* type_type_ = nullptr;
-
   // Maps constants to their lowered values.
   // Vector indexes correspond to `InstId` indexes for constant instructions. We
   // resize this directly to the (often large) correct size.
@@ -326,9 +280,6 @@ class FileContext {
   // Maps global variables to their lowered variant.
   Map<SemIR::InstId, llvm::GlobalVariable*> global_variables_;
 
-  // Global format string for `printf.int.format` used by the PrintInt builtin.
-  llvm::Value* printf_int_format_string_ = nullptr;
-
   // For a generic function, keep track of the specifics for which LLVM
   // function declarations were created. Those can be retrieved then from
   // `specific_functions_`. We resize this to the correct size. Vector indexes

+ 7 - 5
toolchain/lower/lower.cpp

@@ -7,6 +7,7 @@
 #include <memory>
 #include <optional>
 
+#include "toolchain/lower/context.h"
 #include "toolchain/lower/file_context.h"
 
 namespace Carbon::Lower {
@@ -16,13 +17,14 @@ auto LowerToLLVM(llvm::LLVMContext& llvm_context,
                  std::optional<llvm::ArrayRef<Parse::GetTreeAndSubtreesFn>>
                      tree_and_subtrees_getters_for_debug_info,
                  llvm::StringRef module_name, const SemIR::File& sem_ir,
-                 clang::ASTUnit* cpp_ast, const SemIR::InstNamer* inst_namer,
+                 const SemIR::InstNamer* inst_namer,
                  llvm::raw_ostream* vlog_stream)
     -> std::unique_ptr<llvm::Module> {
-  FileContext context(llvm_context, std::move(fs),
-                      tree_and_subtrees_getters_for_debug_info, module_name,
-                      sem_ir, cpp_ast, inst_namer, vlog_stream);
-  return context.Run();
+  Context context(llvm_context, std::move(fs),
+                  tree_and_subtrees_getters_for_debug_info, module_name,
+                  vlog_stream);
+  context.GetFileContext(&sem_ir, inst_namer).LowerDefinitions();
+  return std::move(context).Finalize();
 }
 
 }  // namespace Carbon::Lower

+ 1 - 1
toolchain/lower/lower.h

@@ -20,7 +20,7 @@ auto LowerToLLVM(llvm::LLVMContext& llvm_context,
                  std::optional<llvm::ArrayRef<Parse::GetTreeAndSubtreesFn>>
                      tree_and_subtrees_getters_for_debug_info,
                  llvm::StringRef module_name, const SemIR::File& sem_ir,
-                 clang::ASTUnit* cpp_ast, const SemIR::InstNamer* inst_namer,
+                 const SemIR::InstNamer* inst_namer,
                  llvm::raw_ostream* vlog_stream)
     -> std::unique_ptr<llvm::Module>;
 

+ 7 - 4
toolchain/lower/mangler.h

@@ -24,10 +24,13 @@ class Mangler {
   // specified `FileContext`.
   explicit Mangler(FileContext& file_context)
       : file_context_(file_context),
-        cpp_mangle_context_(
-            file_context.cpp_ast()
-                ? file_context.cpp_ast()->getASTContext().createMangleContext()
-                : nullptr) {}
+        cpp_mangle_context_(file_context.cpp_ast()
+                                // Clang's createMangleContext is not
+                                // const-correct, but doesn't modify the AST.
+                                ? const_cast<clang::ASTContext&>(
+                                      file_context.cpp_ast()->getASTContext())
+                                      .createMangleContext()
+                                : nullptr) {}
 
   // Produce a deterministically unique mangled name for the function specified
   // by `function_id` and `specific_id`.