From 5da87f43da15bda0286a3cbedc27db9688e34fc5 Mon Sep 17 00:00:00 2001 From: Jon Ross-Perkins Date: Tue, 29 Apr 2025 11:00:07 -0700 Subject: [PATCH] Split SemIR's formatter class into a more typical h+cpp (#5372) Trying to make it easier to see the API at a glance. The class has become really long, and this doesn't fundamentally change that, but hopefully makes it easier to navigate. The entry structure also had some cruft that I'm removing. I'm trying to keep functions in the same order as they currently are. The delta still looks unhappy because of the churn, but hopefully this at least explains the ordering in formatter.h. You can try using the "Add indent" commit on the PR to see a better before-after delta. --------- Co-authored-by: Dana Jansens --- toolchain/check/BUILD | 1 - toolchain/check/inst_block_stack.h | 1 - toolchain/driver/compile_subcommand.cpp | 5 +- toolchain/sem_ir/formatter.cpp | 2602 ++++++++++------------- toolchain/sem_ir/formatter.h | 429 +++- 5 files changed, 1580 insertions(+), 1458 deletions(-) diff --git a/toolchain/check/BUILD b/toolchain/check/BUILD index ad6e3a56a992..0cbd84d11b50 100644 --- a/toolchain/check/BUILD +++ b/toolchain/check/BUILD @@ -118,7 +118,6 @@ cc_library( "//toolchain/parse:tree", "//toolchain/sem_ir:expr_info", "//toolchain/sem_ir:file", - "//toolchain/sem_ir:formatter", "//toolchain/sem_ir:inst", "//toolchain/sem_ir:typed_insts", "@llvm-project//clang:frontend", diff --git a/toolchain/check/inst_block_stack.h b/toolchain/check/inst_block_stack.h index adf8b9429be8..ffc3cd250fe0 100644 --- a/toolchain/check/inst_block_stack.h +++ b/toolchain/check/inst_block_stack.h @@ -8,7 +8,6 @@ #include "common/array_stack.h" #include "llvm/ADT/SmallVector.h" #include "toolchain/sem_ir/file.h" -#include "toolchain/sem_ir/formatter.h" namespace Carbon::Check { diff --git a/toolchain/driver/compile_subcommand.cpp b/toolchain/driver/compile_subcommand.cpp index 1186106f6658..1a54f6e722b9 100644 --- a/toolchain/driver/compile_subcommand.cpp +++ b/toolchain/driver/compile_subcommand.cpp @@ -569,12 +569,13 @@ auto CompilationUnit::PostCheck() -> void { }; SemIR::Formatter formatter(&*sem_ir_, should_format_entity); + formatter.Format(); if (vlog_stream_) { CARBON_VLOG("*** SemIR::File ***\n"); - formatter.Print(*vlog_stream_); + formatter.Write(*vlog_stream_); } if (print) { - formatter.Print(*driver_env_->output_stream); + formatter.Write(*driver_env_->output_stream); } } if (sem_ir_->has_errors()) { diff --git a/toolchain/sem_ir/formatter.cpp b/toolchain/sem_ir/formatter.cpp index 5fa75cec9d57..05f53d290778 100644 --- a/toolchain/sem_ir/formatter.cpp +++ b/toolchain/sem_ir/formatter.cpp @@ -21,7 +21,6 @@ #include "toolchain/sem_ir/expr_info.h" #include "toolchain/sem_ir/function.h" #include "toolchain/sem_ir/ids.h" -#include "toolchain/sem_ir/inst_namer.h" #include "toolchain/sem_ir/name_scope.h" #include "toolchain/sem_ir/typed_insts.h" @@ -32,1249 +31,1080 @@ namespace Carbon::SemIR { -// Formatter for printing textual Semantics IR. -class FormatterImpl { - public: - explicit FormatterImpl(const File* sem_ir, InstNamer* inst_namer, - Formatter::ShouldFormatEntityFn should_format_entity, - int indent) - : sem_ir_(sem_ir), - inst_namer_(inst_namer), - should_format_entity_(should_format_entity), - indent_(indent) { - // Create the first chunk and assign it to all instructions that don't have - // a chunk of their own. - auto first_chunk = AddChunkNoFlush(true); - tentative_inst_chunks_.resize(sem_ir_->insts().size(), first_chunk); +Formatter::Formatter(const File* sem_ir, + ShouldFormatEntityFn should_format_entity) + : sem_ir_(sem_ir), + inst_namer_(sem_ir_), + should_format_entity_(should_format_entity) { + // Create the first chunk and assign it to all instructions that don't have + // a chunk of their own. + auto first_chunk = AddChunkNoFlush(true); + tentative_inst_chunks_.resize(sem_ir_->insts().size(), first_chunk); +} + +auto Formatter::Format() -> void { + out_ << "--- " << sem_ir_->filename() << "\n\n"; + + FormatScopeIfUsed(InstNamer::ScopeId::Constants, + sem_ir_->constants().array_ref()); + FormatScopeIfUsed(InstNamer::ScopeId::ImportRefs, + sem_ir_->inst_blocks().Get(InstBlockId::ImportRefs)); + + out_ << inst_namer_.GetScopeName(InstNamer::ScopeId::File) << " "; + OpenBrace(); + + // TODO: Handle the case where there are multiple top-level instruction + // blocks. For example, there may be branching in the initializer of a + // global or a type expression. + if (auto block_id = sem_ir_->top_inst_block_id(); block_id.has_value()) { + llvm::SaveAndRestore file_scope(scope_, InstNamer::ScopeId::File); + FormatCodeBlock(block_id); } - // Prints the SemIR. - // - // Constants are printed first and may be referenced by later sections, - // including file-scoped instructions. The file scope may contain entity - // declarations which are defined later, such as classes. - auto Format() -> void { - out_ << "--- " << sem_ir_->filename() << "\n\n"; + CloseBrace(); + out_ << '\n'; - FormatScopeIfUsed(InstNamer::ScopeId::Constants, - sem_ir_->constants().array_ref()); - FormatScopeIfUsed(InstNamer::ScopeId::ImportRefs, - sem_ir_->inst_blocks().Get(InstBlockId::ImportRefs)); + for (auto [id, _] : sem_ir_->interfaces().enumerate()) { + FormatInterface(id); + } - out_ << inst_namer_->GetScopeName(InstNamer::ScopeId::File) << " "; + for (auto [id, _] : sem_ir_->associated_constants().enumerate()) { + FormatAssociatedConstant(id); + } + + for (auto [id, _] : sem_ir_->impls().enumerate()) { + FormatImpl(id); + } + + for (auto [id, _] : sem_ir_->classes().enumerate()) { + FormatClass(id); + } + + for (auto [id, _] : sem_ir_->functions().enumerate()) { + FormatFunction(id); + } + + for (auto [id, _] : sem_ir_->specifics().enumerate()) { + FormatSpecific(id); + } + + // End-of-file newline. + out_ << "\n"; +} + +auto Formatter::Write(llvm::raw_ostream& out) -> void { + FlushChunk(); + for (const auto& chunk : output_chunks_) { + if (chunk.include_in_output) { + out << chunk.chunk; + } + } +} + +auto Formatter::FlushChunk() -> void { + CARBON_CHECK(output_chunks_.back().chunk.empty()); + output_chunks_.back().chunk = std::move(buffer_); + buffer_.clear(); +} + +auto Formatter::AddChunkNoFlush(bool include_in_output) -> size_t { + CARBON_CHECK(buffer_.empty()); + output_chunks_.push_back({.include_in_output = include_in_output}); + return output_chunks_.size() - 1; +} + +auto Formatter::AddChunk(bool include_in_output) -> size_t { + FlushChunk(); + return AddChunkNoFlush(include_in_output); +} + +auto Formatter::IncludeChunkInOutput(size_t chunk) -> void { + if (chunk == output_chunks_.size() - 1) { + return; + } + + if (auto& current_chunk = output_chunks_.back(); + !current_chunk.include_in_output) { + current_chunk.dependencies.push_back(chunk); + return; + } + + llvm::SmallVector to_add = {chunk}; + while (!to_add.empty()) { + auto& chunk = output_chunks_[to_add.pop_back_val()]; + if (chunk.include_in_output) { + continue; + } + chunk.include_in_output = true; + to_add.append(chunk.dependencies); + chunk.dependencies.clear(); + } +} + +auto Formatter::ShouldFormatEntity(InstId decl_id) -> bool { + if (!decl_id.has_value()) { + return true; + } + return should_format_entity_(decl_id); +} + +auto Formatter::ShouldFormatEntity(const EntityWithParamsBase& entity) -> bool { + return ShouldFormatEntity(entity.latest_decl_id()); +} + +auto Formatter::OpenBrace() -> void { + // Put the constant value of an instruction before any braced block, rather + // than at the end. + FormatPendingConstantValue(AddSpace::After); + + // Put the imported-from library name before the definition of the entity. + FormatPendingImportedFrom(AddSpace::After); + + out_ << '{'; + indent_ += 2; + after_open_brace_ = true; +} + +auto Formatter::CloseBrace() -> void { + indent_ -= 2; + if (!after_open_brace_) { + Indent(); + } + out_ << '}'; + after_open_brace_ = false; +} + +auto Formatter::Semicolon() -> void { + FormatPendingImportedFrom(AddSpace::Before); + out_ << ';'; +} + +auto Formatter::Indent(int offset) -> void { + if (after_open_brace_) { + out_ << '\n'; + after_open_brace_ = false; + } + out_.indent(indent_ + offset); +} + +auto Formatter::IndentLabel() -> void { + CARBON_CHECK(indent_ >= 2); + if (!after_open_brace_) { + out_ << '\n'; + } + Indent(-2); +} + +auto Formatter::FormatScopeIfUsed(InstNamer::ScopeId scope_id, + llvm::ArrayRef block) -> void { + if (block.empty()) { + return; + } + + llvm::SaveAndRestore scope(scope_, scope_id); + // Note, we don't use OpenBrace() / CloseBrace() here because we always want + // a newline to avoid misformatting if the first instruction is omitted. + out_ << inst_namer_.GetScopeName(scope_id) << " {\n"; + indent_ += 2; + for (const InstId inst_id : block) { + TentativeOutputScope scope(*this); + tentative_inst_chunks_[inst_id.index] = scope.index; + FormatInst(inst_id); + } + out_ << "}\n\n"; + indent_ -= 2; +} + +auto Formatter::FormatClass(ClassId id) -> void { + const Class& class_info = sem_ir_->classes().Get(id); + if (!ShouldFormatEntity(class_info)) { + return; + } + + FormatEntityStart("class", class_info, id); + + llvm::SaveAndRestore class_scope(scope_, inst_namer_.GetScopeFor(id)); + + if (class_info.scope_id.has_value()) { + out_ << ' '; + OpenBrace(); + FormatCodeBlock(class_info.body_block_id); + Indent(); + out_ << "complete_type_witness = "; + FormatName(class_info.complete_type_witness_id); + out_ << "\n"; + + FormatNameScope(class_info.scope_id, "!members:\n"); + CloseBrace(); + } else { + Semicolon(); + } + out_ << '\n'; + + FormatEntityEnd(class_info.generic_id); +} + +auto Formatter::FormatInterface(InterfaceId id) -> void { + const Interface& interface_info = sem_ir_->interfaces().Get(id); + if (!ShouldFormatEntity(interface_info)) { + return; + } + + FormatEntityStart("interface", interface_info, id); + + llvm::SaveAndRestore interface_scope(scope_, inst_namer_.GetScopeFor(id)); + + if (interface_info.scope_id.has_value()) { + out_ << ' '; + OpenBrace(); + FormatCodeBlock(interface_info.body_block_id); + + // Always include the !members label because we always list the witness in + // this section. + IndentLabel(); + out_ << "!members:\n"; + FormatNameScope(interface_info.scope_id); + + Indent(); + out_ << "witness = "; + FormatArg(interface_info.associated_entities_id); + out_ << "\n"; + + CloseBrace(); + } else { + Semicolon(); + } + out_ << '\n'; + + FormatEntityEnd(interface_info.generic_id); +} + +auto Formatter::FormatAssociatedConstant(AssociatedConstantId id) -> void { + const AssociatedConstant& assoc_const = + sem_ir_->associated_constants().Get(id); + if (!ShouldFormatEntity(assoc_const.decl_id)) { + return; + } + + FormatEntityStart("assoc_const", assoc_const.decl_id, assoc_const.generic_id, + id); + + llvm::SaveAndRestore assoc_const_scope(scope_, inst_namer_.GetScopeFor(id)); + + out_ << " "; + FormatName(assoc_const.name_id); + out_ << ":! "; + FormatTypeOfInst(assoc_const.decl_id); + if (assoc_const.default_value_id.has_value()) { + out_ << " = "; + FormatArg(assoc_const.default_value_id); + } + out_ << ";\n"; + + FormatEntityEnd(assoc_const.generic_id); +} + +auto Formatter::FormatImpl(ImplId id) -> void { + const Impl& impl_info = sem_ir_->impls().Get(id); + if (!ShouldFormatEntity(impl_info)) { + return; + } + + FormatEntityStart("impl", impl_info, id); + + llvm::SaveAndRestore impl_scope(scope_, inst_namer_.GetScopeFor(id)); + + out_ << ": "; + FormatName(impl_info.self_id); + out_ << " as "; + FormatName(impl_info.constraint_id); + + if (impl_info.is_complete()) { + out_ << ' '; + OpenBrace(); + FormatCodeBlock(impl_info.body_block_id); + + // Print the !members label even if the name scope is empty because we + // always list the witness in this section. + IndentLabel(); + out_ << "!members:\n"; + if (impl_info.scope_id.has_value()) { + FormatNameScope(impl_info.scope_id); + } + + Indent(); + out_ << "witness = "; + FormatArg(impl_info.witness_id); + out_ << "\n"; + + CloseBrace(); + } else { + Semicolon(); + } + out_ << '\n'; + + FormatEntityEnd(impl_info.generic_id); +} + +auto Formatter::FormatFunction(FunctionId id) -> void { + const Function& fn = sem_ir_->functions().Get(id); + if (!ShouldFormatEntity(fn)) { + return; + } + + std::string function_start; + switch (fn.virtual_modifier) { + case FunctionFields::VirtualModifier::Virtual: + function_start += "virtual "; + break; + case FunctionFields::VirtualModifier::Abstract: + function_start += "abstract "; + break; + case FunctionFields::VirtualModifier::Impl: + function_start += "impl "; + break; + case FunctionFields::VirtualModifier::None: + break; + } + if (fn.is_extern) { + function_start += "extern "; + } + function_start += "fn"; + FormatEntityStart(function_start, fn, id); + + llvm::SaveAndRestore function_scope(scope_, inst_namer_.GetScopeFor(id)); + + auto return_type_info = ReturnTypeInfo::ForFunction(*sem_ir_, fn); + FormatParamList(fn.call_params_id, return_type_info.is_valid() && + return_type_info.has_return_slot()); + + if (fn.builtin_function_kind != BuiltinFunctionKind::None) { + out_ << " = \"" + << FormatEscaped(fn.builtin_function_kind.name(), + /*use_hex_escapes=*/true) + << "\""; + } + + if (!fn.body_block_ids.empty()) { + out_ << ' '; OpenBrace(); - // TODO: Handle the case where there are multiple top-level instruction - // blocks. For example, there may be branching in the initializer of a - // global or a type expression. - if (auto block_id = sem_ir_->top_inst_block_id(); block_id.has_value()) { - llvm::SaveAndRestore file_scope(scope_, InstNamer::ScopeId::File); + for (auto block_id : fn.body_block_ids) { + IndentLabel(); + FormatLabel(block_id); + out_ << ":\n"; + FormatCodeBlock(block_id); } CloseBrace(); - out_ << '\n'; + } else { + Semicolon(); + } + out_ << '\n'; - for (auto [id, _] : sem_ir_->interfaces().enumerate()) { - FormatInterface(id); - } + FormatEntityEnd(fn.generic_id); +} - for (auto [id, _] : sem_ir_->associated_constants().enumerate()) { - FormatAssociatedConstant(id); - } - - for (auto [id, _] : sem_ir_->impls().enumerate()) { - FormatImpl(id); - } - - for (auto [id, _] : sem_ir_->classes().enumerate()) { - FormatClass(id); - } - - for (auto [id, _] : sem_ir_->functions().enumerate()) { - FormatFunction(id); - } - - for (auto [id, _] : sem_ir_->specifics().enumerate()) { - FormatSpecific(id); - } - - // End-of-file newline. - out_ << "\n"; +auto Formatter::FormatSpecificRegion(const Generic& generic, + const Specific& specific, + GenericInstIndex::Region region, + llvm::StringRef region_name) -> void { + if (!specific.GetValueBlock(region).has_value()) { + return; } - // Write buffered output to the given stream. - auto Write(llvm::raw_ostream& out) -> void { - FlushChunk(); - for (const auto& chunk : output_chunks_) { - if (chunk.include_in_output) { - out << chunk.chunk; - } - } + if (!region_name.empty()) { + IndentLabel(); + out_ << "!" << region_name << ":\n"; } - - private: - enum class AddSpace : bool { Before, After }; - - // A chunk of the buffered output. Chunks of the output, such as constant - // values, are buffered until we reach the end of formatting so that we can - // decide whether to include them based on whether they are referenced. - struct OutputChunk { - // Whether this chunk is known to be included in the output. - bool include_in_output; - // The textual contents of this chunk. - std::string chunk = std::string(); - // Chunks that should be included in the output if this one is. - llvm::SmallVector dependencies = {}; - }; - - // A scope in which output should be buffered because we don't yet know - // whether to include it in the final formatted SemIR. - struct TentativeOutputScope { - explicit TentativeOutputScope(FormatterImpl& f) : formatter(f) { - index = formatter.AddChunk(false); - } - ~TentativeOutputScope() { - auto next_index = formatter.AddChunk(true); - CARBON_CHECK(next_index == index + 1, "Nested TentativeOutputScope"); - } - FormatterImpl& formatter; - size_t index; - }; - - // Flushes the buffered output to the current chunk. - auto FlushChunk() -> void { - CARBON_CHECK(output_chunks_.back().chunk.empty()); - output_chunks_.back().chunk = std::move(buffer_); - buffer_.clear(); - } - - // Adds a new chunk to the output. Does not flush existing output, so should - // only be called if there is no buffered output. - auto AddChunkNoFlush(bool include_in_output) -> size_t { - CARBON_CHECK(buffer_.empty()); - output_chunks_.push_back({.include_in_output = include_in_output}); - return output_chunks_.size() - 1; - } - - // Flushes the current chunk and add a new chunk to the output. - auto AddChunk(bool include_in_output) -> size_t { - FlushChunk(); - return AddChunkNoFlush(include_in_output); - } - - // Marks the given chunk as being included in the output if the current chunk - // is. - auto IncludeChunkInOutput(size_t chunk) -> void { - if (chunk == output_chunks_.size() - 1) { - return; - } - - if (auto& current_chunk = output_chunks_.back(); - !current_chunk.include_in_output) { - current_chunk.dependencies.push_back(chunk); - return; - } - - llvm::SmallVector to_add = {chunk}; - while (!to_add.empty()) { - auto& chunk = output_chunks_[to_add.pop_back_val()]; - if (chunk.include_in_output) { - continue; - } - chunk.include_in_output = true; - to_add.append(chunk.dependencies); - chunk.dependencies.clear(); - } - } - - // Determines whether the specified entity should be included in the formatted - // output. - auto ShouldFormatEntity(InstId decl_id) -> bool { - if (!decl_id.has_value()) { - return true; - } - return should_format_entity_(decl_id); - } - - auto ShouldFormatEntity(const EntityWithParamsBase& entity) -> bool { - return ShouldFormatEntity(entity.latest_decl_id()); - } - - // Begins a braced block. Writes an open brace, and prepares to insert a - // newline after it if the braced block is non-empty. - auto OpenBrace() -> void { - // Put the constant value of an instruction before any braced block, rather - // than at the end. - FormatPendingConstantValue(AddSpace::After); - - // Put the imported-from library name before the definition of the entity. - FormatPendingImportedFrom(AddSpace::After); - - out_ << '{'; - indent_ += 2; - after_open_brace_ = true; - } - - // Ends a braced block by writing a close brace. - auto CloseBrace() -> void { - indent_ -= 2; - if (!after_open_brace_) { - Indent(); - } - out_ << '}'; - after_open_brace_ = false; - } - - auto Semicolon() -> void { - FormatPendingImportedFrom(AddSpace::Before); - out_ << ';'; - } - - // Adds beginning-of-line indentation. If we're at the start of a braced - // block, first starts a new line. - auto Indent(int offset = 0) -> void { - if (after_open_brace_) { - out_ << '\n'; - after_open_brace_ = false; - } - out_.indent(indent_ + offset); - } - - // Adds beginning-of-label indentation. This is one level less than normal - // indentation. Labels also get a preceding blank line unless they're at the - // start of a block. - auto IndentLabel() -> void { - CARBON_CHECK(indent_ >= 2); - if (!after_open_brace_) { - out_ << '\n'; - } - Indent(-2); - } - - // Formats a top-level scope, and any of the instructions in that scope that - // are used. - auto FormatScopeIfUsed(InstNamer::ScopeId scope_id, - llvm::ArrayRef block) -> void { - if (block.empty()) { - return; - } - - llvm::SaveAndRestore scope(scope_, scope_id); - // Note, we don't use OpenBrace() / CloseBrace() here because we always want - // a newline to avoid misformatting if the first instruction is omitted. - out_ << inst_namer_->GetScopeName(scope_id) << " {\n"; - indent_ += 2; - for (const InstId inst_id : block) { - TentativeOutputScope scope(*this); - tentative_inst_chunks_[inst_id.index] = scope.index; - FormatInst(inst_id); - } - out_ << "}\n\n"; - indent_ -= 2; - } - - // Formats a full class. - auto FormatClass(ClassId id) -> void { - const Class& class_info = sem_ir_->classes().Get(id); - if (!ShouldFormatEntity(class_info)) { - return; - } - - FormatEntityStart("class", class_info, id); - - llvm::SaveAndRestore class_scope(scope_, inst_namer_->GetScopeFor(id)); - - if (class_info.scope_id.has_value()) { - out_ << ' '; - OpenBrace(); - FormatCodeBlock(class_info.body_block_id); - Indent(); - out_ << "complete_type_witness = "; - FormatName(class_info.complete_type_witness_id); - out_ << "\n"; - - FormatNameScope(class_info.scope_id, "!members:\n"); - CloseBrace(); - } else { - Semicolon(); - } - out_ << '\n'; - - FormatEntityEnd(class_info.generic_id); - } - - // Formats a full interface. - auto FormatInterface(InterfaceId id) -> void { - const Interface& interface_info = sem_ir_->interfaces().Get(id); - if (!ShouldFormatEntity(interface_info)) { - return; - } - - FormatEntityStart("interface", interface_info, id); - - llvm::SaveAndRestore interface_scope(scope_, inst_namer_->GetScopeFor(id)); - - if (interface_info.scope_id.has_value()) { - out_ << ' '; - OpenBrace(); - FormatCodeBlock(interface_info.body_block_id); - - // Always include the !members label because we always list the witness in - // this section. - IndentLabel(); - out_ << "!members:\n"; - FormatNameScope(interface_info.scope_id); - - Indent(); - out_ << "witness = "; - FormatArg(interface_info.associated_entities_id); - out_ << "\n"; - - CloseBrace(); - } else { - Semicolon(); - } - out_ << '\n'; - - FormatEntityEnd(interface_info.generic_id); - } - - // Formats an associated constant entity. - auto FormatAssociatedConstant(AssociatedConstantId id) -> void { - const AssociatedConstant& assoc_const = - sem_ir_->associated_constants().Get(id); - if (!ShouldFormatEntity(assoc_const.decl_id)) { - return; - } - - FormatEntityStart("assoc_const", assoc_const.decl_id, - assoc_const.generic_id, id); - - llvm::SaveAndRestore assoc_const_scope(scope_, - inst_namer_->GetScopeFor(id)); - - out_ << " "; - FormatName(assoc_const.name_id); - out_ << ":! "; - FormatTypeOfInst(assoc_const.decl_id); - if (assoc_const.default_value_id.has_value()) { - out_ << " = "; - FormatArg(assoc_const.default_value_id); - } - out_ << ";\n"; - - FormatEntityEnd(assoc_const.generic_id); - } - - // Formats a full impl. - auto FormatImpl(ImplId id) -> void { - const Impl& impl_info = sem_ir_->impls().Get(id); - if (!ShouldFormatEntity(impl_info)) { - return; - } - - FormatEntityStart("impl", impl_info, id); - - llvm::SaveAndRestore impl_scope(scope_, inst_namer_->GetScopeFor(id)); - - out_ << ": "; - FormatName(impl_info.self_id); - out_ << " as "; - FormatName(impl_info.constraint_id); - - if (impl_info.is_complete()) { - out_ << ' '; - OpenBrace(); - FormatCodeBlock(impl_info.body_block_id); - - // Print the !members label even if the name scope is empty because we - // always list the witness in this section. - IndentLabel(); - out_ << "!members:\n"; - if (impl_info.scope_id.has_value()) { - FormatNameScope(impl_info.scope_id); - } - - Indent(); - out_ << "witness = "; - FormatArg(impl_info.witness_id); - out_ << "\n"; - - CloseBrace(); - } else { - Semicolon(); - } - out_ << '\n'; - - FormatEntityEnd(impl_info.generic_id); - } - - // Formats a full function. - auto FormatFunction(FunctionId id) -> void { - const Function& fn = sem_ir_->functions().Get(id); - if (!ShouldFormatEntity(fn)) { - return; - } - - std::string function_start; - switch (fn.virtual_modifier) { - case FunctionFields::VirtualModifier::Virtual: - function_start += "virtual "; - break; - case FunctionFields::VirtualModifier::Abstract: - function_start += "abstract "; - break; - case FunctionFields::VirtualModifier::Impl: - function_start += "impl "; - break; - case FunctionFields::VirtualModifier::None: - break; - } - if (fn.is_extern) { - function_start += "extern "; - } - function_start += "fn"; - FormatEntityStart(function_start, fn, id); - - llvm::SaveAndRestore function_scope(scope_, inst_namer_->GetScopeFor(id)); - - auto return_type_info = ReturnTypeInfo::ForFunction(*sem_ir_, fn); - FormatParamList(fn.call_params_id, return_type_info.is_valid() && - return_type_info.has_return_slot()); - - if (fn.builtin_function_kind != BuiltinFunctionKind::None) { - out_ << " = \"" - << FormatEscaped(fn.builtin_function_kind.name(), - /*use_hex_escapes=*/true) - << "\""; - } - - if (!fn.body_block_ids.empty()) { - out_ << ' '; - OpenBrace(); - - for (auto block_id : fn.body_block_ids) { - IndentLabel(); - FormatLabel(block_id); - out_ << ":\n"; - - FormatCodeBlock(block_id); - } - - CloseBrace(); - } else { - Semicolon(); - } - out_ << '\n'; - - FormatEntityEnd(fn.generic_id); - } - - // Helper for FormatSpecific to print regions. - auto FormatSpecificRegion(const Generic& generic, const Specific& specific, - GenericInstIndex::Region region, - llvm::StringRef region_name) -> void { - if (!specific.GetValueBlock(region).has_value()) { - return; - } - - if (!region_name.empty()) { - IndentLabel(); - out_ << "!" << region_name << ":\n"; - } - for (auto [generic_inst_id, specific_inst_id] : llvm::zip_longest( - sem_ir_->inst_blocks().GetOrEmpty(generic.GetEvalBlock(region)), - sem_ir_->inst_blocks().GetOrEmpty( - specific.GetValueBlock(region)))) { - Indent(); - if (generic_inst_id) { - FormatName(*generic_inst_id); - } else { - out_ << ""; - } - out_ << " => "; - if (specific_inst_id) { - FormatName(*specific_inst_id); - } else { - out_ << ""; - } - out_ << "\n"; - } - } - - // Formats a full specific. - auto FormatSpecific(SpecificId id) -> void { - const auto& specific = sem_ir_->specifics().Get(id); - const auto& generic = sem_ir_->generics().Get(specific.generic_id); - if (!should_format_entity_(generic.decl_id)) { - // Omit specifics if we also omitted the generic. - return; - } - - llvm::SaveAndRestore generic_scope( - scope_, inst_namer_->GetScopeFor(specific.generic_id)); - - out_ << "\n"; - - out_ << "specific "; - FormatName(id); - out_ << " "; - - OpenBrace(); - FormatSpecificRegion(generic, specific, - GenericInstIndex::Region::Declaration, ""); - FormatSpecificRegion(generic, specific, - GenericInstIndex::Region::Definition, "definition"); - CloseBrace(); - - out_ << "\n"; - } - - // Handles generic-specific setup for FormatEntityStart. - auto FormatGenericStart(llvm::StringRef entity_kind, GenericId generic_id) - -> void { - const auto& generic = sem_ir_->generics().Get(generic_id); - out_ << "\n"; + for (auto [generic_inst_id, specific_inst_id] : llvm::zip_longest( + sem_ir_->inst_blocks().GetOrEmpty(generic.GetEvalBlock(region)), + sem_ir_->inst_blocks().GetOrEmpty(specific.GetValueBlock(region)))) { Indent(); - out_ << "generic " << entity_kind << " "; - FormatName(generic_id); - - llvm::SaveAndRestore generic_scope(scope_, - inst_namer_->GetScopeFor(generic_id)); - - FormatParamList(generic.bindings_id); - - out_ << " "; - OpenBrace(); - FormatCodeBlock(generic.decl_block_id); - if (generic.definition_block_id.has_value()) { - IndentLabel(); - out_ << "!definition:\n"; - FormatCodeBlock(generic.definition_block_id); + if (generic_inst_id) { + FormatName(*generic_inst_id); + } else { + out_ << ""; } - } - - // Provides common formatting for entities, paired with FormatEntityEnd. - template - auto FormatEntityStart(llvm::StringRef entity_kind, - InstId first_owning_decl_id, GenericId generic_id, - IdT entity_id) -> void { - // If this entity was imported from a different IR, annotate the name of - // that IR in the output before the `{` or `;`. - if (first_owning_decl_id.has_value()) { - auto loc_id = sem_ir_->insts().GetCanonicalLocId(first_owning_decl_id); - if (loc_id.kind() == LocId::Kind::ImportIRInstId) { - auto import_ir_id = - sem_ir_->import_ir_insts().Get(loc_id.import_ir_inst_id()).ir_id(); - const auto* import_file = - sem_ir_->import_irs().Get(import_ir_id).sem_ir; - pending_imported_from_ = import_file->filename(); - } + out_ << " => "; + if (specific_inst_id) { + FormatName(*specific_inst_id); + } else { + out_ << ""; } - - if (generic_id.has_value()) { - FormatGenericStart(entity_kind, generic_id); - } - out_ << "\n"; - after_open_brace_ = false; - Indent(); - out_ << entity_kind; + } +} - // If there's a generic, it will have attached the name. Otherwise, add the - // name here. - if (!generic_id.has_value()) { - out_ << " "; - FormatName(entity_id); - } +auto Formatter::FormatSpecific(SpecificId id) -> void { + const auto& specific = sem_ir_->specifics().Get(id); + const auto& generic = sem_ir_->generics().Get(specific.generic_id); + if (!should_format_entity_(generic.decl_id)) { + // Omit specifics if we also omitted the generic. + return; } - template - auto FormatEntityStart(llvm::StringRef entity_kind, - const EntityWithParamsBase& entity, IdT entity_id) - -> void { - FormatEntityStart(entity_kind, entity.first_owning_decl_id, - entity.generic_id, entity_id); + llvm::SaveAndRestore generic_scope( + scope_, inst_namer_.GetScopeFor(specific.generic_id)); + + out_ << "\n"; + + out_ << "specific "; + FormatName(id); + out_ << " "; + + OpenBrace(); + FormatSpecificRegion(generic, specific, GenericInstIndex::Region::Declaration, + ""); + FormatSpecificRegion(generic, specific, GenericInstIndex::Region::Definition, + "definition"); + CloseBrace(); + + out_ << "\n"; +} + +auto Formatter::FormatGenericStart(llvm::StringRef entity_kind, + GenericId generic_id) -> void { + const auto& generic = sem_ir_->generics().Get(generic_id); + out_ << "\n"; + Indent(); + out_ << "generic " << entity_kind << " "; + FormatName(generic_id); + + llvm::SaveAndRestore generic_scope(scope_, + inst_namer_.GetScopeFor(generic_id)); + + FormatParamList(generic.bindings_id); + + out_ << " "; + OpenBrace(); + FormatCodeBlock(generic.decl_block_id); + if (generic.definition_block_id.has_value()) { + IndentLabel(); + out_ << "!definition:\n"; + FormatCodeBlock(generic.definition_block_id); } +} - // Provides common formatting for entities, paired with FormatEntityStart. - auto FormatEntityEnd(GenericId generic_id) -> void { - if (generic_id.has_value()) { - CloseBrace(); - out_ << '\n'; - } - } - - // Formats parameters, eliding them completely if they're empty. Wraps input - // parameters in parentheses. Formats output parameter as a return type. - auto FormatParamList(InstBlockId params_id, bool has_return_slot = false) - -> void { - if (!params_id.has_value()) { - // TODO: This happens for imported functions, for which we don't currently - // import the call parameters list. - return; - } - - llvm::StringLiteral close = ")"; - out_ << "("; - - llvm::ListSeparator sep; - for (InstId param_id : sem_ir_->inst_blocks().Get(params_id)) { - auto is_out_param = sem_ir_->insts().Is(param_id); - if (is_out_param) { - // TODO: An input parameter following an output parameter is formatted a - // bit strangely. For example, alternating input and output parameters - // produces: - // - // fn @F(%in1: %t) -> %out1: %t, %in2: %t -> %out2: %t - // - // This doesn't actually happen right now, though. - out_ << std::exchange(close, llvm::StringLiteral("")); - out_ << " -> "; - } else { - out_ << sep; - } - if (!param_id.has_value()) { - out_ << "invalid"; - continue; - } - // Don't include the name of the return slot parameter if the function - // doesn't have a return slot; the name won't be used for anything in that - // case. - // TODO: Should the call parameter even exist in that case? There isn't a - // corresponding argument in a `call` instruction. - if (!is_out_param || has_return_slot) { - FormatName(param_id); - out_ << ": "; - } - FormatTypeOfInst(param_id); - } - - out_ << close; - } - - // Prints instructions for a code block. - auto FormatCodeBlock(InstBlockId block_id) -> void { - for (const InstId inst_id : sem_ir_->inst_blocks().GetOrEmpty(block_id)) { - FormatInst(inst_id); - } - } - - // Prints a code block with braces, intended to be used trailing after other - // content on the same line. If non-empty, instructions are on separate lines. - auto FormatTrailingBlock(InstBlockId block_id) -> void { - out_ << ' '; - OpenBrace(); - FormatCodeBlock(block_id); +auto Formatter::FormatEntityEnd(GenericId generic_id) -> void { + if (generic_id.has_value()) { CloseBrace(); + out_ << '\n'; + } +} + +auto Formatter::FormatParamList(InstBlockId params_id, bool has_return_slot) + -> void { + if (!params_id.has_value()) { + // TODO: This happens for imported functions, for which we don't currently + // import the call parameters list. + return; } - // Prints the contents of a name scope, with an optional label. - auto FormatNameScope(NameScopeId id, llvm::StringRef label = "") -> void { - const auto& scope = sem_ir_->name_scopes().Get(id); + llvm::StringLiteral close = ")"; + out_ << "("; - if (scope.entries().empty() && scope.extended_scopes().empty() && - scope.import_ir_scopes().empty() && !scope.is_cpp_scope() && - !scope.has_error()) { - // Name scope is empty. - return; + llvm::ListSeparator sep; + for (InstId param_id : sem_ir_->inst_blocks().Get(params_id)) { + auto is_out_param = sem_ir_->insts().Is(param_id); + if (is_out_param) { + // TODO: An input parameter following an output parameter is formatted a + // bit strangely. For example, alternating input and output parameters + // produces: + // + // fn @F(%in1: %t) -> %out1: %t, %in2: %t -> %out2: %t + // + // This doesn't actually happen right now, though. + out_ << std::exchange(close, llvm::StringLiteral("")); + out_ << " -> "; + } else { + out_ << sep; } - - if (!label.empty()) { - IndentLabel(); - out_ << label; + if (!param_id.has_value()) { + out_ << "invalid"; + continue; } + // Don't include the name of the return slot parameter if the function + // doesn't have a return slot; the name won't be used for anything in that + // case. + // TODO: Should the call parameter even exist in that case? There isn't a + // corresponding argument in a `call` instruction. + if (!is_out_param || has_return_slot) { + FormatName(param_id); + out_ << ": "; + } + FormatTypeOfInst(param_id); + } - for (auto [name_id, result] : scope.entries()) { - Indent(); - out_ << "."; - FormatName(name_id); - switch (result.access_kind()) { - case AccessKind::Public: - break; - case AccessKind::Protected: - out_ << " [protected]"; - break; - case AccessKind::Private: - out_ << " [private]"; - break; - } - out_ << " = "; - if (result.is_poisoned()) { - out_ << ""; + out_ << close; +} + +auto Formatter::FormatCodeBlock(InstBlockId block_id) -> void { + for (const InstId inst_id : sem_ir_->inst_blocks().GetOrEmpty(block_id)) { + FormatInst(inst_id); + } +} + +auto Formatter::FormatTrailingBlock(InstBlockId block_id) -> void { + out_ << ' '; + OpenBrace(); + FormatCodeBlock(block_id); + CloseBrace(); +} + +auto Formatter::FormatNameScope(NameScopeId id, llvm::StringRef label) -> void { + const auto& scope = sem_ir_->name_scopes().Get(id); + + if (scope.entries().empty() && scope.extended_scopes().empty() && + scope.import_ir_scopes().empty() && !scope.is_cpp_scope() && + !scope.has_error()) { + // Name scope is empty. + return; + } + + if (!label.empty()) { + IndentLabel(); + out_ << label; + } + + for (auto [name_id, result] : scope.entries()) { + Indent(); + out_ << "."; + FormatName(name_id); + switch (result.access_kind()) { + case AccessKind::Public: + break; + case AccessKind::Protected: + out_ << " [protected]"; + break; + case AccessKind::Private: + out_ << " [private]"; + break; + } + out_ << " = "; + if (result.is_poisoned()) { + out_ << ""; + } else { + FormatName(result.is_found() ? result.target_inst_id() : InstId::None); + } + out_ << "\n"; + } + + for (auto extended_scope_id : scope.extended_scopes()) { + Indent(); + out_ << "extend "; + FormatName(extended_scope_id); + out_ << "\n"; + } + + // This is used to cluster all "Core//prelude/..." imports, but not + // "Core//prelude" itself. This avoids unrelated churn in test files when we + // add or remove an unused prelude file, but is intended to still show the + // existence of indirect imports. + bool has_prelude_components = false; + for (auto [import_ir_id, unused] : scope.import_ir_scopes()) { + auto label = GetImportIRLabel(import_ir_id); + if (label.starts_with("Core//prelude/")) { + if (has_prelude_components) { + // Only print the existence once. + continue; } else { - FormatName(result.is_found() ? result.target_inst_id() : InstId::None); + has_prelude_components = true; + label = "Core//prelude/..."; } - out_ << "\n"; - } - - for (auto extended_scope_id : scope.extended_scopes()) { - Indent(); - out_ << "extend "; - FormatName(extended_scope_id); - out_ << "\n"; - } - - // This is used to cluster all "Core//prelude/..." imports, but not - // "Core//prelude" itself. This avoids unrelated churn in test files when we - // add or remove an unused prelude file, but is intended to still show the - // existence of indirect imports. - bool has_prelude_components = false; - for (auto [import_ir_id, unused] : scope.import_ir_scopes()) { - auto label = GetImportIRLabel(import_ir_id); - if (label.starts_with("Core//prelude/")) { - if (has_prelude_components) { - // Only print the existence once. - continue; - } else { - has_prelude_components = true; - label = "Core//prelude/..."; - } - } - Indent(); - out_ << "import " << label << "\n"; - } - - if (scope.is_cpp_scope()) { - Indent(); - out_ << "import Cpp//...\n"; - } - - if (scope.has_error()) { - Indent(); - out_ << "has_error\n"; } + Indent(); + out_ << "import " << label << "\n"; } - // Prints a single instruction. - auto FormatInst(InstId inst_id) -> void { - if (!inst_id.has_value()) { - Indent(); - out_ << "none\n"; - return; - } - - FormatInst(inst_id, sem_ir_->insts().Get(inst_id)); + if (scope.is_cpp_scope()) { + Indent(); + out_ << "import Cpp//...\n"; } - auto FormatInst(InstId inst_id, Inst inst) -> void { - CARBON_KIND_SWITCH(inst) { + if (scope.has_error()) { + Indent(); + out_ << "has_error\n"; + } +} + +auto Formatter::FormatInst(InstId inst_id, Inst inst) -> void { + CARBON_KIND_SWITCH(inst) { #define CARBON_SEM_IR_INST_KIND(InstT) \ case CARBON_KIND(InstT typed_inst): { \ FormatInst(inst_id, typed_inst); \ break; \ } #include "toolchain/sem_ir/inst_kind.def" - } } +} - template - auto FormatInst(InstId inst_id, InstT inst) -> void { +auto Formatter::FormatInst(InstId inst_id, ImportRefUnloaded inst) -> void { + Indent(); + FormatInstLhs(inst_id, inst); + out_ << ImportRefUnloaded::Kind.ir_name(); + FormatInstRhs(inst); + out_ << "\n"; +} + +auto Formatter::FormatInst(InstId inst_id) -> void { + if (!inst_id.has_value()) { Indent(); - FormatInstLhs(inst_id, inst); - out_ << InstT::Kind.ir_name(); - pending_constant_value_ = sem_ir_->constant_values().Get(inst_id); - pending_constant_value_is_self_ = - sem_ir_->constant_values().GetInstIdIfValid(pending_constant_value_) == - inst_id; - FormatInstRhs(inst); - FormatPendingConstantValue(AddSpace::Before); - out_ << "\n"; + out_ << "none\n"; + return; } - // Don't print a constant for ImportRefUnloaded. - auto FormatInst(InstId inst_id, ImportRefUnloaded inst) -> void { - Indent(); - FormatInstLhs(inst_id, inst); - out_ << ImportRefUnloaded::Kind.ir_name(); - FormatInstRhs(inst); - out_ << "\n"; + FormatInst(inst_id, sem_ir_->insts().Get(inst_id)); +} + +auto Formatter::FormatPendingImportedFrom(AddSpace space_where) -> void { + if (pending_imported_from_.empty()) { + return; } - // If there is a pending library name that the current instruction was - // imported from, print it now and clear it out. - auto FormatPendingImportedFrom(AddSpace space_where) -> void { - if (pending_imported_from_.empty()) { - return; - } - - if (space_where == AddSpace::Before) { - out_ << ' '; - } - out_ << "[from \"" << FormatEscaped(pending_imported_from_) << "\"]"; - if (space_where == AddSpace::After) { - out_ << ' '; - } - pending_imported_from_ = llvm::StringRef(); - } - - // If there is a pending constant value attached to the current instruction, - // print it now and clear it out. The constant value gets printed before the - // first braced block argument, or at the end of the instruction if there are - // no such arguments. - auto FormatPendingConstantValue(AddSpace space_where) -> void { - if (pending_constant_value_ == ConstantId::NotConstant) { - return; - } - - if (space_where == AddSpace::Before) { - out_ << ' '; - } - out_ << '['; - if (pending_constant_value_.has_value()) { - switch ( - sem_ir_->constant_values().GetDependence(pending_constant_value_)) { - case ConstantDependence::None: - out_ << "concrete"; - break; - case ConstantDependence::PeriodSelf: - out_ << "symbolic_self"; - break; - // TODO: Consider renaming this. This will cause a lot of SemIR churn. - case ConstantDependence::Checked: - out_ << "symbolic"; - break; - case ConstantDependence::Template: - out_ << "template"; - break; - } - if (!pending_constant_value_is_self_) { - out_ << " = "; - FormatConstant(pending_constant_value_); - } - } else { - out_ << pending_constant_value_; - } - out_ << ']'; - if (space_where == AddSpace::After) { - out_ << ' '; - } - pending_constant_value_ = ConstantId::NotConstant; - } - - auto FormatInstLhs(InstId inst_id, Inst inst) -> void { - switch (inst.kind().value_kind()) { - case InstValueKind::Typed: - FormatName(inst_id); - out_ << ": "; - switch (GetExprCategory(*sem_ir_, inst_id)) { - case ExprCategory::NotExpr: - case ExprCategory::Error: - case ExprCategory::Value: - case ExprCategory::Mixed: - break; - case ExprCategory::DurableRef: - case ExprCategory::EphemeralRef: - out_ << "ref "; - break; - case ExprCategory::Initializing: - out_ << "init "; - break; - } - FormatTypeOfInst(inst_id); - out_ << " = "; - break; - case InstValueKind::None: - break; - } - } - - // Format ImportCppDecl name. - auto FormatInstLhs(InstId inst_id, ImportCppDecl /*inst*/) -> void { - FormatName(inst_id); - out_ << " = "; - } - - // Format ImportDecl with its name. - auto FormatInstLhs(InstId inst_id, ImportDecl /*inst*/) -> void { - FormatName(inst_id); - out_ << " = "; - } - - // Print ImportRefUnloaded with type-like semantics even though it lacks a - // type_id. - auto FormatInstLhs(InstId inst_id, ImportRefUnloaded /*inst*/) -> void { - FormatName(inst_id); - out_ << " = "; - } - - // Format ImplWitnessTable with its name even though it lacks a type_id. - auto FormatInstLhs(InstId inst_id, ImplWitnessTable /*inst*/) -> void { - FormatName(inst_id); - out_ << " = "; - } - - template - auto FormatInstRhs(InstT inst) -> void { - // By default, an instruction has a comma-separated argument list. - using Info = Internal::InstLikeTypeInfo; - if constexpr (Info::NumArgs == 2) { - // Several instructions have a second operand that's a specific ID. We - // don't include it in the argument list if there is no corresponding - // specific, that is, when we're not in a generic context. - if constexpr (std::is_same_v, - SpecificId>) { - if (!Info::template Get<1>(inst).has_value()) { - FormatArgs(Info::template Get<0>(inst)); - return; - } - } - FormatArgs(Info::template Get<0>(inst), Info::template Get<1>(inst)); - } else if constexpr (Info::NumArgs == 1) { - FormatArgs(Info::template Get<0>(inst)); - } else { - FormatArgs(); - } - } - - auto FormatInstRhs(BindSymbolicName inst) -> void { - // A BindSymbolicName with no value is a purely symbolic binding, such as - // the `Self` in an interface. Don't print out `none` for the value. - if (inst.value_id.has_value()) { - FormatArgs(inst.entity_name_id, inst.value_id); - } else { - FormatArgs(inst.entity_name_id); - } - } - - auto FormatInstRhs(BlockArg inst) -> void { - out_ << " "; - FormatLabel(inst.block_id); - } - - auto FormatInstRhs(Namespace inst) -> void { - if (inst.import_id.has_value()) { - FormatArgs(inst.import_id, inst.name_scope_id); - } else { - FormatArgs(inst.name_scope_id); - } - } - - auto FormatInst(InstId /*inst_id*/, BranchIf inst) -> void { - if (!in_terminator_sequence_) { - Indent(); - } - out_ << "if "; - FormatName(inst.cond_id); - out_ << " " << Branch::Kind.ir_name() << " "; - FormatLabel(inst.target_id); - out_ << " else "; - in_terminator_sequence_ = true; - } - - auto FormatInst(InstId /*inst_id*/, BranchWithArg inst) -> void { - if (!in_terminator_sequence_) { - Indent(); - } - out_ << BranchWithArg::Kind.ir_name() << " "; - FormatLabel(inst.target_id); - out_ << "("; - FormatName(inst.arg_id); - out_ << ")\n"; - in_terminator_sequence_ = false; - } - - auto FormatInst(InstId /*inst_id*/, Branch inst) -> void { - if (!in_terminator_sequence_) { - Indent(); - } - out_ << Branch::Kind.ir_name() << " "; - FormatLabel(inst.target_id); - out_ << "\n"; - in_terminator_sequence_ = false; - } - - auto FormatInstRhs(Call inst) -> void { - out_ << " "; - FormatArg(inst.callee_id); - - if (!inst.args_id.has_value()) { - out_ << "()"; - return; - } - - llvm::ArrayRef args = sem_ir_->inst_blocks().Get(inst.args_id); - - auto return_info = ReturnTypeInfo::ForType(*sem_ir_, inst.type_id); - if (!return_info.is_valid()) { - out_ << "()"; - return; - } - bool has_return_slot = return_info.has_return_slot(); - InstId return_slot_arg_id = InstId::None; - if (has_return_slot) { - return_slot_arg_id = args.back(); - args = args.drop_back(); - } - - llvm::ListSeparator sep; - out_ << '('; - for (auto inst_id : args) { - out_ << sep; - FormatArg(inst_id); - } - out_ << ')'; - - if (has_return_slot) { - FormatReturnSlotArg(return_slot_arg_id); - } - } - - auto FormatInstRhs(ArrayInit inst) -> void { - FormatArgs(inst.inits_id); - FormatReturnSlotArg(inst.dest_id); - } - - auto FormatInstRhs(InitializeFrom inst) -> void { - FormatArgs(inst.src_id); - FormatReturnSlotArg(inst.dest_id); - } - - auto FormatInstRhs(ValueParam inst) -> void { - FormatArgs(inst.index); - // Omit pretty_name because it's an implementation detail of - // pretty-printing. - } - - auto FormatInstRhs(RefParam inst) -> void { - FormatArgs(inst.index); - // Omit pretty_name because it's an implementation detail of - // pretty-printing. - } - - auto FormatInstRhs(OutParam inst) -> void { - FormatArgs(inst.index); - // Omit pretty_name because it's an implementation detail of - // pretty-printing. - } - - auto FormatInstRhs(ReturnExpr ret) -> void { - FormatArgs(ret.expr_id); - if (ret.dest_id.has_value()) { - FormatReturnSlotArg(ret.dest_id); - } - } - - auto FormatInstRhs(ReturnSlot inst) -> void { - // Omit inst.type_inst_id because it's not semantically significant. - FormatArgs(inst.storage_id); - } - - auto FormatInstRhs(ReturnSlotPattern /*inst*/) -> void { - // No-op because type_id is the only semantically significant field, - // and it's handled separately. - } - - auto FormatInstRhs(StructInit init) -> void { - FormatArgs(init.elements_id); - FormatReturnSlotArg(init.dest_id); - } - - auto FormatInstRhs(TupleInit init) -> void { - FormatArgs(init.elements_id); - FormatReturnSlotArg(init.dest_id); - } - - auto FormatInstRhs(FunctionDecl inst) -> void { - FormatArgs(inst.function_id); - llvm::SaveAndRestore class_scope( - scope_, inst_namer_->GetScopeFor(inst.function_id)); - FormatTrailingBlock( - sem_ir_->functions().Get(inst.function_id).pattern_block_id); - FormatTrailingBlock(inst.decl_block_id); - } - - auto FormatInstRhs(ClassDecl inst) -> void { - FormatArgs(inst.class_id); - llvm::SaveAndRestore class_scope(scope_, - inst_namer_->GetScopeFor(inst.class_id)); - FormatTrailingBlock(sem_ir_->classes().Get(inst.class_id).pattern_block_id); - FormatTrailingBlock(inst.decl_block_id); - } - - auto FormatInstRhs(ImplDecl inst) -> void { - FormatArgs(inst.impl_id); - llvm::SaveAndRestore class_scope(scope_, - inst_namer_->GetScopeFor(inst.impl_id)); - FormatTrailingBlock(sem_ir_->impls().Get(inst.impl_id).pattern_block_id); - FormatTrailingBlock(inst.decl_block_id); - } - - auto FormatInstRhs(InterfaceDecl inst) -> void { - FormatArgs(inst.interface_id); - llvm::SaveAndRestore class_scope( - scope_, inst_namer_->GetScopeFor(inst.interface_id)); - FormatTrailingBlock( - sem_ir_->interfaces().Get(inst.interface_id).pattern_block_id); - FormatTrailingBlock(inst.decl_block_id); - } - - auto FormatInstRhs(AssociatedConstantDecl inst) -> void { - FormatArgs(inst.assoc_const_id); - llvm::SaveAndRestore assoc_const_scope( - scope_, inst_namer_->GetScopeFor(inst.assoc_const_id)); - FormatTrailingBlock(inst.decl_block_id); - } - - auto FormatInstRhs(IntValue inst) -> void { - out_ << " "; - sem_ir_->ints() - .Get(inst.int_id) - .print(out_, sem_ir_->types().IsSignedInt(inst.type_id)); - } - - auto FormatInstRhs(FloatLiteral inst) -> void { - llvm::SmallVector buffer; - sem_ir_->floats().Get(inst.float_id).toString(buffer); - out_ << " " << buffer; - } - - // Format the metadata in File for `import Cpp`. - auto FormatInstRhs(ImportCppDecl /*inst*/) -> void { - out_ << " "; - OpenBrace(); - for (ImportCpp import_cpp : sem_ir_->import_cpps().array_ref()) { - Indent(); - out_ << "import Cpp \"" - << FormatEscaped( - sem_ir_->string_literal_values().Get(import_cpp.library_id)) - << "\"\n"; - } - CloseBrace(); - } - - auto FormatImportRefRhs(ImportIRInstId import_ir_inst_id, - EntityNameId entity_name_id, - llvm::StringLiteral loaded_label) -> void { - out_ << " "; - auto import_ir_inst = sem_ir_->import_ir_insts().Get(import_ir_inst_id); - FormatArg(import_ir_inst.ir_id()); - out_ << ", "; - if (entity_name_id.has_value()) { - // Prefer to show the entity name when possible. - FormatArg(entity_name_id); - } else { - // Show a name based on the location when possible, or the numeric - // instruction as a last resort. - const auto& import_ir = sem_ir_->import_irs().Get(import_ir_inst.ir_id()); - auto loc_id = - import_ir.sem_ir->insts().GetCanonicalLocId(import_ir_inst.inst_id()); - switch (loc_id.kind()) { - case LocId::Kind::None: { - out_ << import_ir_inst.inst_id() << " [no loc]"; - break; - } - case LocId::Kind::ImportIRInstId: { - // TODO: Probably don't want to format each indirection, but maybe - // reuse GetCanonicalImportIRInst? - out_ << import_ir_inst.inst_id() << " [indirect]"; - break; - } - case LocId::Kind::NodeId: { - // Formats a NodeId from the import. - const auto& tree = import_ir.sem_ir->parse_tree(); - auto token = tree.node_token(loc_id.node_id()); - out_ << "loc" << tree.tokens().GetLineNumber(token) << "_" - << tree.tokens().GetColumnNumber(token); - break; - } - case LocId::Kind::InstId: - CARBON_FATAL("Unexpected LocId: {0}", loc_id); - } - } - out_ << ", " << loaded_label; - } - - auto FormatInstRhs(ImportRefLoaded inst) -> void { - FormatImportRefRhs(inst.import_ir_inst_id, inst.entity_name_id, "loaded"); - } - - auto FormatInstRhs(ImportRefUnloaded inst) -> void { - FormatImportRefRhs(inst.import_ir_inst_id, inst.entity_name_id, "unloaded"); - } - - auto FormatInstRhs(InstValue inst) -> void { + if (space_where == AddSpace::Before) { out_ << ' '; - OpenBrace(); - // TODO: Should we use a more compact representation in the case where the - // inst is a SpliceBlock? - FormatInst(inst.inst_id); - CloseBrace(); + } + out_ << "[from \"" << FormatEscaped(pending_imported_from_) << "\"]"; + if (space_where == AddSpace::After) { + out_ << ' '; + } + pending_imported_from_ = llvm::StringRef(); +} + +auto Formatter::FormatPendingConstantValue(AddSpace space_where) -> void { + if (pending_constant_value_ == ConstantId::NotConstant) { + return; } - auto FormatInstRhs(NameBindingDecl inst) -> void { - FormatTrailingBlock(inst.pattern_block_id); + if (space_where == AddSpace::Before) { + out_ << ' '; } - - auto FormatInstRhs(SpliceBlock inst) -> void { - FormatArgs(inst.result_id); - FormatTrailingBlock(inst.block_id); + out_ << '['; + if (pending_constant_value_.has_value()) { + switch (sem_ir_->constant_values().GetDependence(pending_constant_value_)) { + case ConstantDependence::None: + out_ << "concrete"; + break; + case ConstantDependence::PeriodSelf: + out_ << "symbolic_self"; + break; + // TODO: Consider renaming this. This will cause a lot of SemIR churn. + case ConstantDependence::Checked: + out_ << "symbolic"; + break; + case ConstantDependence::Template: + out_ << "template"; + break; + } + if (!pending_constant_value_is_self_) { + out_ << " = "; + FormatConstant(pending_constant_value_); + } + } else { + out_ << pending_constant_value_; } - - auto FormatInstRhs(WhereExpr inst) -> void { - FormatArgs(inst.period_self_id); - FormatTrailingBlock(inst.requirements_id); + out_ << ']'; + if (space_where == AddSpace::After) { + out_ << ' '; } + pending_constant_value_ = ConstantId::NotConstant; +} - auto FormatInstRhs(StructType inst) -> void { - out_ << " {"; - llvm::ListSeparator sep; - for (auto field : sem_ir_->struct_type_fields().Get(inst.fields_id)) { - out_ << sep << "."; - FormatName(field.name_id); +auto Formatter::FormatInstLhs(InstId inst_id, Inst inst) -> void { + switch (inst.kind().value_kind()) { + case InstValueKind::Typed: + FormatName(inst_id); out_ << ": "; - FormatInstAsType(field.type_inst_id); - } - out_ << "}"; + switch (GetExprCategory(*sem_ir_, inst_id)) { + case ExprCategory::NotExpr: + case ExprCategory::Error: + case ExprCategory::Value: + case ExprCategory::Mixed: + break; + case ExprCategory::DurableRef: + case ExprCategory::EphemeralRef: + out_ << "ref "; + break; + case ExprCategory::Initializing: + out_ << "init "; + break; + } + FormatTypeOfInst(inst_id); + out_ << " = "; + break; + case InstValueKind::None: + break; + } +} + +auto Formatter::FormatInstLhs(InstId inst_id, ImportCppDecl /*inst*/) -> void { + FormatName(inst_id); + out_ << " = "; +} + +auto Formatter::FormatInstLhs(InstId inst_id, ImportDecl /*inst*/) -> void { + FormatName(inst_id); + out_ << " = "; +} + +auto Formatter::FormatInstLhs(InstId inst_id, ImportRefUnloaded /*inst*/) + -> void { + FormatName(inst_id); + out_ << " = "; +} + +auto Formatter::FormatInstLhs(InstId inst_id, ImplWitnessTable /*inst*/) + -> void { + FormatName(inst_id); + out_ << " = "; +} + +auto Formatter::FormatInstRhs(BindSymbolicName inst) -> void { + // A BindSymbolicName with no value is a purely symbolic binding, such as + // the `Self` in an interface. Don't print out `none` for the value. + if (inst.value_id.has_value()) { + FormatArgs(inst.entity_name_id, inst.value_id); + } else { + FormatArgs(inst.entity_name_id); + } +} + +auto Formatter::FormatInstRhs(BlockArg inst) -> void { + out_ << " "; + FormatLabel(inst.block_id); +} + +auto Formatter::FormatInstRhs(Namespace inst) -> void { + if (inst.import_id.has_value()) { + FormatArgs(inst.import_id, inst.name_scope_id); + } else { + FormatArgs(inst.name_scope_id); + } +} + +auto Formatter::FormatInst(InstId /*inst_id*/, BranchIf inst) -> void { + if (!in_terminator_sequence_) { + Indent(); + } + out_ << "if "; + FormatName(inst.cond_id); + out_ << " " << Branch::Kind.ir_name() << " "; + FormatLabel(inst.target_id); + out_ << " else "; + in_terminator_sequence_ = true; +} + +auto Formatter::FormatInst(InstId /*inst_id*/, BranchWithArg inst) -> void { + if (!in_terminator_sequence_) { + Indent(); + } + out_ << BranchWithArg::Kind.ir_name() << " "; + FormatLabel(inst.target_id); + out_ << "("; + FormatName(inst.arg_id); + out_ << ")\n"; + in_terminator_sequence_ = false; +} + +auto Formatter::FormatInst(InstId /*inst_id*/, Branch inst) -> void { + if (!in_terminator_sequence_) { + Indent(); + } + out_ << Branch::Kind.ir_name() << " "; + FormatLabel(inst.target_id); + out_ << "\n"; + in_terminator_sequence_ = false; +} + +auto Formatter::FormatInstRhs(Call inst) -> void { + out_ << " "; + FormatArg(inst.callee_id); + + if (!inst.args_id.has_value()) { + out_ << "()"; + return; } - auto FormatArgs() -> void {} + llvm::ArrayRef args = sem_ir_->inst_blocks().Get(inst.args_id); - template - auto FormatArgs(Args... args) -> void { - out_ << ' '; - llvm::ListSeparator sep; - ((out_ << sep, FormatArg(args)), ...); + auto return_info = ReturnTypeInfo::ForType(*sem_ir_, inst.type_id); + if (!return_info.is_valid()) { + out_ << "()"; + return; + } + bool has_return_slot = return_info.has_return_slot(); + InstId return_slot_arg_id = InstId::None; + if (has_return_slot) { + return_slot_arg_id = args.back(); + args = args.drop_back(); } - // FormatArg variants handling printing instruction arguments. Several things - // provide equivalent behavior with `FormatName`, so we provide that as the - // default. - template - auto FormatArg(IdT id) -> void { - FormatName(id); + llvm::ListSeparator sep; + out_ << '('; + for (auto inst_id : args) { + out_ << sep; + FormatArg(inst_id); } + out_ << ')'; - auto FormatArg(BoolValue v) -> void { out_ << v; } + if (has_return_slot) { + FormatReturnSlotArg(return_slot_arg_id); + } +} - auto FormatArg(EntityNameId id) -> void { - if (!id.has_value()) { - out_ << "_"; - return; +auto Formatter::FormatInstRhs(ArrayInit inst) -> void { + FormatArgs(inst.inits_id); + FormatReturnSlotArg(inst.dest_id); +} + +auto Formatter::Formatter::FormatInstRhs(InitializeFrom inst) -> void { + FormatArgs(inst.src_id); + FormatReturnSlotArg(inst.dest_id); +} + +auto Formatter::FormatInstRhs(ValueParam inst) -> void { + FormatArgs(inst.index); + // Omit pretty_name because it's an implementation detail of + // pretty-printing. +} + +auto Formatter::FormatInstRhs(RefParam inst) -> void { + FormatArgs(inst.index); + // Omit pretty_name because it's an implementation detail of + // pretty-printing. +} + +auto Formatter::FormatInstRhs(OutParam inst) -> void { + FormatArgs(inst.index); + // Omit pretty_name because it's an implementation detail of + // pretty-printing. +} + +auto Formatter::FormatInstRhs(ReturnExpr ret) -> void { + FormatArgs(ret.expr_id); + if (ret.dest_id.has_value()) { + FormatReturnSlotArg(ret.dest_id); + } +} + +auto Formatter::FormatInstRhs(ReturnSlot inst) -> void { + // Omit inst.type_inst_id because it's not semantically significant. + FormatArgs(inst.storage_id); +} + +auto Formatter::FormatInstRhs(ReturnSlotPattern /*inst*/) -> void { + // No-op because type_id is the only semantically significant field, + // and it's handled separately. +} + +auto Formatter::FormatInstRhs(StructInit init) -> void { + FormatArgs(init.elements_id); + FormatReturnSlotArg(init.dest_id); +} + +auto Formatter::FormatInstRhs(TupleInit init) -> void { + FormatArgs(init.elements_id); + FormatReturnSlotArg(init.dest_id); +} + +auto Formatter::FormatInstRhs(FunctionDecl inst) -> void { + FormatArgs(inst.function_id); + llvm::SaveAndRestore class_scope(scope_, + inst_namer_.GetScopeFor(inst.function_id)); + FormatTrailingBlock( + sem_ir_->functions().Get(inst.function_id).pattern_block_id); + FormatTrailingBlock(inst.decl_block_id); +} + +auto Formatter::FormatInstRhs(ClassDecl inst) -> void { + FormatArgs(inst.class_id); + llvm::SaveAndRestore class_scope(scope_, + inst_namer_.GetScopeFor(inst.class_id)); + FormatTrailingBlock(sem_ir_->classes().Get(inst.class_id).pattern_block_id); + FormatTrailingBlock(inst.decl_block_id); +} + +auto Formatter::FormatInstRhs(ImplDecl inst) -> void { + FormatArgs(inst.impl_id); + llvm::SaveAndRestore class_scope(scope_, + inst_namer_.GetScopeFor(inst.impl_id)); + FormatTrailingBlock(sem_ir_->impls().Get(inst.impl_id).pattern_block_id); + FormatTrailingBlock(inst.decl_block_id); +} + +auto Formatter::FormatInstRhs(InterfaceDecl inst) -> void { + FormatArgs(inst.interface_id); + llvm::SaveAndRestore class_scope(scope_, + inst_namer_.GetScopeFor(inst.interface_id)); + FormatTrailingBlock( + sem_ir_->interfaces().Get(inst.interface_id).pattern_block_id); + FormatTrailingBlock(inst.decl_block_id); +} + +auto Formatter::FormatInstRhs(AssociatedConstantDecl inst) -> void { + FormatArgs(inst.assoc_const_id); + llvm::SaveAndRestore assoc_const_scope( + scope_, inst_namer_.GetScopeFor(inst.assoc_const_id)); + FormatTrailingBlock(inst.decl_block_id); +} + +auto Formatter::FormatInstRhs(IntValue inst) -> void { + out_ << " "; + sem_ir_->ints() + .Get(inst.int_id) + .print(out_, sem_ir_->types().IsSignedInt(inst.type_id)); +} + +auto Formatter::FormatInstRhs(FloatLiteral inst) -> void { + llvm::SmallVector buffer; + sem_ir_->floats().Get(inst.float_id).toString(buffer); + out_ << " " << buffer; +} + +auto Formatter::FormatInstRhs(ImportCppDecl /*inst*/) -> void { + out_ << " "; + OpenBrace(); + for (ImportCpp import_cpp : sem_ir_->import_cpps().array_ref()) { + Indent(); + out_ << "import Cpp \"" + << FormatEscaped( + sem_ir_->string_literal_values().Get(import_cpp.library_id)) + << "\"\n"; + } + CloseBrace(); +} + +auto Formatter::FormatImportRefRhs(ImportIRInstId import_ir_inst_id, + EntityNameId entity_name_id, + llvm::StringLiteral loaded_label) -> void { + out_ << " "; + auto import_ir_inst = sem_ir_->import_ir_insts().Get(import_ir_inst_id); + FormatArg(import_ir_inst.ir_id()); + out_ << ", "; + if (entity_name_id.has_value()) { + // Prefer to show the entity name when possible. + FormatArg(entity_name_id); + } else { + // Show a name based on the location when possible, or the numeric + // instruction as a last resort. + const auto& import_ir = sem_ir_->import_irs().Get(import_ir_inst.ir_id()); + auto loc_id = + import_ir.sem_ir->insts().GetCanonicalLocId(import_ir_inst.inst_id()); + switch (loc_id.kind()) { + case LocId::Kind::None: { + out_ << import_ir_inst.inst_id() << " [no loc]"; + break; + } + case LocId::Kind::ImportIRInstId: { + // TODO: Probably don't want to format each indirection, but maybe + // reuse GetCanonicalImportIRInst? + out_ << import_ir_inst.inst_id() << " [indirect]"; + break; + } + case LocId::Kind::NodeId: { + // Formats a NodeId from the import. + const auto& tree = import_ir.sem_ir->parse_tree(); + auto token = tree.node_token(loc_id.node_id()); + out_ << "loc" << tree.tokens().GetLineNumber(token) << "_" + << tree.tokens().GetColumnNumber(token); + break; + } + case LocId::Kind::InstId: + CARBON_FATAL("Unexpected LocId: {0}", loc_id); } - const auto& info = sem_ir_->entity_names().Get(id); - FormatName(info.name_id); - if (info.bind_index().has_value()) { - out_ << ", " << info.bind_index().index; - } - if (info.is_template) { - out_ << ", template"; + } + out_ << ", " << loaded_label; +} + +auto Formatter::FormatInstRhs(ImportRefLoaded inst) -> void { + FormatImportRefRhs(inst.import_ir_inst_id, inst.entity_name_id, "loaded"); +} + +auto Formatter::FormatInstRhs(ImportRefUnloaded inst) -> void { + FormatImportRefRhs(inst.import_ir_inst_id, inst.entity_name_id, "unloaded"); +} + +auto Formatter::FormatInstRhs(InstValue inst) -> void { + out_ << ' '; + OpenBrace(); + // TODO: Should we use a more compact representation in the case where the + // inst is a SpliceBlock? + FormatInst(inst.inst_id); + CloseBrace(); +} + +auto Formatter::FormatInstRhs(NameBindingDecl inst) -> void { + FormatTrailingBlock(inst.pattern_block_id); +} + +auto Formatter::FormatInstRhs(SpliceBlock inst) -> void { + FormatArgs(inst.result_id); + FormatTrailingBlock(inst.block_id); +} + +auto Formatter::FormatInstRhs(WhereExpr inst) -> void { + FormatArgs(inst.period_self_id); + FormatTrailingBlock(inst.requirements_id); +} + +auto Formatter::FormatInstRhs(StructType inst) -> void { + out_ << " {"; + llvm::ListSeparator sep; + for (auto field : sem_ir_->struct_type_fields().Get(inst.fields_id)) { + out_ << sep << "."; + FormatName(field.name_id); + out_ << ": "; + FormatInstAsType(field.type_inst_id); + } + out_ << "}"; +} + +auto Formatter::FormatArg(EntityNameId id) -> void { + if (!id.has_value()) { + out_ << "_"; + return; + } + const auto& info = sem_ir_->entity_names().Get(id); + FormatName(info.name_id); + if (info.bind_index().has_value()) { + out_ << ", " << info.bind_index().index; + } + if (info.is_template) { + out_ << ", template"; + } +} + +auto Formatter::FormatArg(FacetTypeId id) -> void { + const auto& info = sem_ir_->facet_types().Get(id); + // Nothing output to indicate that this is a facet type since this is only + // used as the argument to a `facet_type` instruction. + out_ << "<"; + + llvm::ListSeparator sep(" & "); + if (info.extend_constraints.empty()) { + out_ << "type"; + } else { + for (auto interface : info.extend_constraints) { + out_ << sep; + FormatName(interface.interface_id); + if (interface.specific_id.has_value()) { + out_ << ", "; + FormatName(interface.specific_id); + } } } - auto FormatArg(FacetTypeId id) -> void { - const auto& info = sem_ir_->facet_types().Get(id); - // Nothing output to indicate that this is a facet type since this is only - // used as the argument to a `facet_type` instruction. - out_ << "<"; - - llvm::ListSeparator sep(" & "); - if (info.extend_constraints.empty()) { - out_ << "type"; - } else { - for (auto interface : info.extend_constraints) { - out_ << sep; + if (info.other_requirements || !info.self_impls_constraints.empty() || + !info.rewrite_constraints.empty()) { + out_ << " where "; + llvm::ListSeparator and_sep(" and "); + if (!info.self_impls_constraints.empty()) { + out_ << and_sep << ".Self impls "; + llvm::ListSeparator amp_sep(" & "); + for (auto interface : info.self_impls_constraints) { + out_ << amp_sep; FormatName(interface.interface_id); if (interface.specific_id.has_value()) { out_ << ", "; @@ -1282,297 +1112,181 @@ class FormatterImpl { } } } - - if (info.other_requirements || !info.self_impls_constraints.empty() || - !info.rewrite_constraints.empty()) { - out_ << " where "; - llvm::ListSeparator and_sep(" and "); - if (!info.self_impls_constraints.empty()) { - out_ << and_sep << ".Self impls "; - llvm::ListSeparator amp_sep(" & "); - for (auto interface : info.self_impls_constraints) { - out_ << amp_sep; - FormatName(interface.interface_id); - if (interface.specific_id.has_value()) { - out_ << ", "; - FormatName(interface.specific_id); - } - } - } - for (auto rewrite : info.rewrite_constraints) { - out_ << and_sep; - FormatArg(rewrite.lhs_id); - out_ << " = "; - FormatArg(rewrite.rhs_id); - } - if (info.other_requirements) { - out_ << and_sep << "TODO"; - } + for (auto rewrite : info.rewrite_constraints) { + out_ << and_sep; + FormatArg(rewrite.lhs_id); + out_ << " = "; + FormatArg(rewrite.rhs_id); } - out_ << ">"; - } - - auto FormatArg(IntKind k) -> void { k.Print(out_); } - - auto FormatArg(FloatKind k) -> void { k.Print(out_); } - - auto FormatArg(ImportIRId id) -> void { - if (id.has_value()) { - out_ << GetImportIRLabel(id); - } else { - out_ << id; + if (info.other_requirements) { + out_ << and_sep << "TODO"; } } + out_ << ">"; +} - auto FormatArg(IntId id) -> void { - // We don't know the signedness to use here. Default to unsigned. - sem_ir_->ints().Get(id).print(out_, /*isSigned=*/false); +auto Formatter::FormatArg(ImportIRId id) -> void { + if (id.has_value()) { + out_ << GetImportIRLabel(id); + } else { + out_ << id; + } +} + +auto Formatter::FormatArg(IntId id) -> void { + // We don't know the signedness to use here. Default to unsigned. + sem_ir_->ints().Get(id).print(out_, /*isSigned=*/false); +} + +auto Formatter::FormatArg(NameScopeId id) -> void { + OpenBrace(); + FormatNameScope(id); + CloseBrace(); +} + +auto Formatter::FormatArg(InstBlockId id) -> void { + if (!id.has_value()) { + out_ << "invalid"; + return; } - auto FormatArg(ElementIndex index) -> void { out_ << index; } + out_ << '('; + llvm::ListSeparator sep; + for (auto inst_id : sem_ir_->inst_blocks().Get(id)) { + out_ << sep; + FormatArg(inst_id); + } + out_ << ')'; +} - auto FormatArg(CallParamIndex index) -> void { out_ << index; } +auto Formatter::FormatArg(AbsoluteInstBlockId id) -> void { + FormatArg(static_cast(id)); +} - auto FormatArg(NameScopeId id) -> void { - OpenBrace(); - FormatNameScope(id); - CloseBrace(); +auto Formatter::FormatArg(RealId id) -> void { + // TODO: Format with a `.` when the exponent is near zero. + const auto& real = sem_ir_->reals().Get(id); + real.mantissa.print(out_, /*isSigned=*/false); + out_ << (real.is_decimal ? 'e' : 'p') << real.exponent; +} + +auto Formatter::FormatArg(StringLiteralValueId id) -> void { + out_ << '"' + << FormatEscaped(sem_ir_->string_literal_values().Get(id), + /*use_hex_escapes=*/true) + << '"'; +} + +auto Formatter::FormatReturnSlotArg(InstId dest_id) -> void { + out_ << " to "; + FormatArg(dest_id); +} + +auto Formatter::FormatName(NameId id) -> void { + out_ << sem_ir_->names().GetFormatted(id); +} + +auto Formatter::FormatName(InstId id) -> void { + if (id.has_value()) { + IncludeChunkInOutput(tentative_inst_chunks_[id.index]); + } + out_ << inst_namer_.GetNameFor(scope_, id); +} + +auto Formatter::FormatName(SpecificId id) -> void { + const auto& specific = sem_ir_->specifics().Get(id); + FormatName(specific.generic_id); + FormatArg(specific.args_id); +} + +auto Formatter::FormatName(SpecificInterfaceId id) -> void { + const auto& interface = sem_ir_->specific_interfaces().Get(id); + FormatName(interface.interface_id); + if (interface.specific_id.has_value()) { + out_ << ", "; + FormatArg(interface.specific_id); + } +} + +auto Formatter::FormatLabel(InstBlockId id) -> void { + out_ << inst_namer_.GetLabelFor(scope_, id); +} + +auto Formatter::FormatConstant(ConstantId id) -> void { + if (!id.has_value()) { + out_ << ""; + return; } - auto FormatArg(InstBlockId id) -> void { - if (!id.has_value()) { - out_ << "invalid"; + // For a symbolic constant in a generic, list the constant value in the + // generic first, and the canonical constant second. + if (id.is_symbolic()) { + const auto& symbolic_constant = + sem_ir_->constant_values().GetSymbolicConstant(id); + if (symbolic_constant.generic_id.has_value()) { + const auto& generic = + sem_ir_->generics().Get(symbolic_constant.generic_id); + FormatName(sem_ir_->inst_blocks().Get(generic.GetEvalBlock( + symbolic_constant.index.region()))[symbolic_constant.index.index()]); + out_ << " ("; + FormatName(sem_ir_->constant_values().GetInstId(id)); + out_ << ")"; return; } - - out_ << '('; - llvm::ListSeparator sep; - for (auto inst_id : sem_ir_->inst_blocks().Get(id)) { - out_ << sep; - FormatArg(inst_id); - } - out_ << ')'; } - auto FormatArg(AbsoluteInstBlockId id) -> void { - FormatArg(static_cast(id)); + FormatName(sem_ir_->constant_values().GetInstId(id)); +} + +auto Formatter::FormatInstAsType(InstId id) -> void { + if (!id.has_value()) { + out_ << "invalid"; + return; } - auto FormatArg(RealId id) -> void { - // TODO: Format with a `.` when the exponent is near zero. - const auto& real = sem_ir_->reals().Get(id); - real.mantissa.print(out_, /*isSigned=*/false); - out_ << (real.is_decimal ? 'e' : 'p') << real.exponent; + // Types are formatted in the `constants` scope because they typically refer + // to constants. + llvm::SaveAndRestore file_scope(scope_, InstNamer::ScopeId::Constants); + if (auto const_id = sem_ir_->constant_values().Get(id); + const_id.has_value()) { + FormatConstant(const_id); + } else { + // Type instruction didn't have a constant value. Fall back to printing + // the instruction name. + FormatArg(id); + } +} + +auto Formatter::FormatTypeOfInst(InstId id) -> void { + auto type_id = sem_ir_->insts().Get(id).type_id(); + if (!type_id.has_value()) { + out_ << "invalid"; + return; } - auto FormatArg(StringLiteralValueId id) -> void { - out_ << '"' - << FormatEscaped(sem_ir_->string_literal_values().Get(id), - /*use_hex_escapes=*/true) - << '"'; - } + // Types are formatted in the `constants` scope because they typically refer + // to constants. + llvm::SaveAndRestore file_scope(scope_, InstNamer::ScopeId::Constants); + FormatConstant(sem_ir_->types().GetConstantId(type_id)); +} - auto FormatReturnSlotArg(InstId dest_id) -> void { - out_ << " to "; - FormatArg(dest_id); - } +auto Formatter::GetImportIRLabel(ImportIRId id) -> std::string { + CARBON_CHECK(id.has_value(), + "Callers are responsible for checking `id.has_value`"); + const auto& import_ir = *sem_ir_->import_irs().Get(id).sem_ir; + CARBON_CHECK(import_ir.library_id().has_value()); - // `FormatName` is used when we need the name from an id. Most id types use - // equivalent name formatting from InstNamer, although there are a few special - // formats below. - template - auto FormatName(IdT id) -> void { - out_ << inst_namer_->GetNameFor(id); - } - - auto FormatName(NameId id) -> void { - out_ << sem_ir_->names().GetFormatted(id); - } - - auto FormatName(InstId id) -> void { - if (id.has_value()) { - IncludeChunkInOutput(tentative_inst_chunks_[id.index]); - } - out_ << inst_namer_->GetNameFor(scope_, id); - } - - auto FormatName(AbsoluteInstId id) -> void { - FormatName(static_cast(id)); - } - - auto FormatName(DestInstId id) -> void { - FormatName(static_cast(id)); - } - - auto FormatName(MetaInstId id) -> void { - FormatName(static_cast(id)); - } - - auto FormatName(SpecificId id) -> void { - const auto& specific = sem_ir_->specifics().Get(id); - FormatName(specific.generic_id); - FormatArg(specific.args_id); - } - - auto FormatName(SpecificInterfaceId id) -> void { - const auto& interface = sem_ir_->specific_interfaces().Get(id); - FormatName(interface.interface_id); - if (interface.specific_id.has_value()) { - out_ << ", "; - FormatArg(interface.specific_id); - } - } - - auto FormatName(TypeInstId id) -> void { - FormatName(static_cast(id)); - } - - auto FormatLabel(InstBlockId id) -> void { - out_ << inst_namer_->GetLabelFor(scope_, id); - } - - auto FormatConstant(ConstantId id) -> void { - if (!id.has_value()) { - out_ << ""; - return; - } - - // For a symbolic constant in a generic, list the constant value in the - // generic first, and the canonical constant second. - if (id.is_symbolic()) { - const auto& symbolic_constant = - sem_ir_->constant_values().GetSymbolicConstant(id); - if (symbolic_constant.generic_id.has_value()) { - const auto& generic = - sem_ir_->generics().Get(symbolic_constant.generic_id); - FormatName(sem_ir_->inst_blocks().Get(generic.GetEvalBlock( - symbolic_constant.index - .region()))[symbolic_constant.index.index()]); - out_ << " ("; - FormatName(sem_ir_->constant_values().GetInstId(id)); - out_ << ")"; - return; - } - } - - FormatName(sem_ir_->constant_values().GetInstId(id)); - } - - auto FormatInstAsType(InstId id) -> void { - if (!id.has_value()) { - out_ << "invalid"; - return; - } - - // Types are formatted in the `constants` scope because they typically refer - // to constants. - llvm::SaveAndRestore file_scope(scope_, InstNamer::ScopeId::Constants); - if (auto const_id = sem_ir_->constant_values().Get(id); - const_id.has_value()) { - FormatConstant(const_id); - } else { - // Type instruction didn't have a constant value. Fall back to printing - // the instruction name. - FormatArg(id); - } - } - - auto FormatTypeOfInst(InstId id) -> void { - auto type_id = sem_ir_->insts().Get(id).type_id(); - if (!type_id.has_value()) { - out_ << "invalid"; - return; - } - - // Types are formatted in the `constants` scope because they typically refer - // to constants. - llvm::SaveAndRestore file_scope(scope_, InstNamer::ScopeId::Constants); - FormatConstant(sem_ir_->types().GetConstantId(type_id)); - } - - // Returns the label for the indicated IR. - auto GetImportIRLabel(ImportIRId id) -> std::string { - CARBON_CHECK(id.has_value(), - "Callers are responsible for checking `id.has_value`"); - const auto& import_ir = *sem_ir_->import_irs().Get(id).sem_ir; - CARBON_CHECK(import_ir.library_id().has_value()); - - auto package_id = import_ir.package_id(); - llvm::StringRef package_name = - package_id.AsIdentifierId().has_value() - ? import_ir.identifiers().Get(package_id.AsIdentifierId()) - : package_id.AsSpecialName(); - llvm::StringRef library_name = - (import_ir.library_id() != LibraryNameId::Default) - ? import_ir.string_literal_values().Get( - import_ir.library_id().AsStringLiteralValueId()) - : "default"; - return llvm::formatv("{0}//{1}", package_name, library_name); - } - - const File* sem_ir_; - InstNamer* const inst_namer_; - Formatter::ShouldFormatEntityFn should_format_entity_; - - // The output stream buffer. - std::string buffer_; - - // The output stream. - llvm::raw_string_ostream out_ = llvm::raw_string_ostream(buffer_); - - // Chunks of output text that we have created so far. - llvm::SmallVector output_chunks_; - - // The current scope that we are formatting within. References to names in - // this scope will not have a `@scope.` prefix added. - InstNamer::ScopeId scope_ = InstNamer::ScopeId::None; - - // Whether we are formatting in a terminator sequence, that is, a sequence of - // branches at the end of a block. The entirety of a terminator sequence is - // formatted on a single line, despite being multiple instructions. - bool in_terminator_sequence_ = false; - - // The indent depth to use for new instructions. - int indent_; - - // Whether we are currently formatting immediately after an open brace. If so, - // a newline will be inserted before the next line indent. - bool after_open_brace_ = false; - - // The constant value of the current instruction, if it has one that has not - // yet been printed. The value `NotConstant` is used as a sentinel to indicate - // there is nothing to print. - ConstantId pending_constant_value_ = ConstantId::NotConstant; - - // Whether `pending_constant_value_`'s instruction is the same as the - // instruction currently being printed. If true, only the phase of the - // constant is printed, and the value is omitted. - bool pending_constant_value_is_self_ = false; - - // The name of the IR file from which the current entity was imported, if it - // was imported and no file has been printed yet. This is printed before the - // first open brace or the semicolon in the entity declaration. - llvm::StringRef pending_imported_from_; - - // Indexes of chunks of output that should be included when an instruction is - // referenced, indexed by the instruction's index. This is resized in advance - // to the correct size. - llvm::SmallVector tentative_inst_chunks_; -}; - -Formatter::Formatter(const File* sem_ir, - ShouldFormatEntityFn should_format_entity) - : sem_ir_(sem_ir), - should_format_entity_(should_format_entity), - inst_namer_(sem_ir) {} - -Formatter::~Formatter() = default; - -auto Formatter::Print(llvm::raw_ostream& out) -> void { - FormatterImpl formatter(sem_ir_, &inst_namer_, should_format_entity_, - /*indent=*/0); - formatter.Format(); - formatter.Write(out); + auto package_id = import_ir.package_id(); + llvm::StringRef package_name = + package_id.AsIdentifierId().has_value() + ? import_ir.identifiers().Get(package_id.AsIdentifierId()) + : package_id.AsSpecialName(); + llvm::StringRef library_name = + (import_ir.library_id() != LibraryNameId::Default) + ? import_ir.string_literal_values().Get( + import_ir.library_id().AsStringLiteralValueId()) + : "default"; + return llvm::formatv("{0}//{1}", package_name, library_name); } } // namespace Carbon::SemIR diff --git a/toolchain/sem_ir/formatter.h b/toolchain/sem_ir/formatter.h index 765e758ef716..91bacdb35189 100644 --- a/toolchain/sem_ir/formatter.h +++ b/toolchain/sem_ir/formatter.h @@ -6,8 +6,6 @@ #define CARBON_TOOLCHAIN_SEM_IR_FORMATTER_H_ #include "llvm/Support/raw_ostream.h" -#include "toolchain/lex/tokenized_buffer.h" -#include "toolchain/parse/tree.h" #include "toolchain/sem_ir/file.h" #include "toolchain/sem_ir/inst_namer.h" @@ -21,21 +19,432 @@ class Formatter { using ShouldFormatEntityFn = llvm::function_refbool>; - explicit Formatter( - const File* sem_ir, - ShouldFormatEntityFn should_format_entity = [](InstId) { return true; }); - ~Formatter(); + explicit Formatter(const File* sem_ir, + ShouldFormatEntityFn should_format_entity); - // Prints the full IR. - auto Print(llvm::raw_ostream& out) -> void; + // Prints the SemIR into an internal buffer. + // + // Constants are printed first and may be referenced by later sections, + // including file-scoped instructions. The file scope may contain entity + // declarations which are defined later, such as classes. + auto Format() -> void; + + // Write buffered output to the given stream. + auto Write(llvm::raw_ostream& out) -> void; private: + enum class AddSpace : bool { Before, After }; + + // A chunk of the buffered output. Chunks of the output, such as constant + // values, are buffered until we reach the end of formatting so that we can + // decide whether to include them based on whether they are referenced. + struct OutputChunk { + // Whether this chunk is known to be included in the output. + bool include_in_output; + // The textual contents of this chunk. + std::string chunk = std::string(); + // Chunks that should be included in the output if this one is. + llvm::SmallVector dependencies = {}; + }; + + // A scope in which output should be buffered because we don't yet know + // whether to include it in the final formatted SemIR. + struct TentativeOutputScope { + explicit TentativeOutputScope(Formatter& f) : formatter(f) { + index = formatter.AddChunk(false); + } + ~TentativeOutputScope() { + auto next_index = formatter.AddChunk(true); + CARBON_CHECK(next_index == index + 1, "Nested TentativeOutputScope"); + } + Formatter& formatter; + size_t index; + }; + + // Flushes the buffered output to the current chunk. + auto FlushChunk() -> void; + + // Adds a new chunk to the output. Does not flush existing output, so should + // only be called if there is no buffered output. + auto AddChunkNoFlush(bool include_in_output) -> size_t; + + // Flushes the current chunk and add a new chunk to the output. + auto AddChunk(bool include_in_output) -> size_t; + + // Marks the given chunk as being included in the output if the current chunk + // is. + auto IncludeChunkInOutput(size_t chunk) -> void; + + // Determines whether the specified entity should be included in the formatted + // output. + auto ShouldFormatEntity(InstId decl_id) -> bool; + + auto ShouldFormatEntity(const EntityWithParamsBase& entity) -> bool; + + // Begins a braced block. Writes an open brace, and prepares to insert a + // newline after it if the braced block is non-empty. + auto OpenBrace() -> void; + + // Ends a braced block by writing a close brace. + auto CloseBrace() -> void; + + auto Semicolon() -> void; + + // Adds beginning-of-line indentation. If we're at the start of a braced + // block, first starts a new line. + auto Indent(int offset = 0) -> void; + + // Adds beginning-of-label indentation. This is one level less than normal + // indentation. Labels also get a preceding blank line unless they're at the + // start of a block. + auto IndentLabel() -> void; + + // Formats a top-level scope, and any of the instructions in that scope that + // are used. + auto FormatScopeIfUsed(InstNamer::ScopeId scope_id, + llvm::ArrayRef block) -> void; + + // Formats a full class. + auto FormatClass(ClassId id) -> void; + + // Formats a full interface. + auto FormatInterface(InterfaceId id) -> void; + + // Formats an associated constant entity. + auto FormatAssociatedConstant(AssociatedConstantId id) -> void; + + // Formats a full impl. + auto FormatImpl(ImplId id) -> void; + + // Formats a full function. + auto FormatFunction(FunctionId id) -> void; + + // Helper for FormatSpecific to print regions. + auto FormatSpecificRegion(const Generic& generic, const Specific& specific, + GenericInstIndex::Region region, + llvm::StringRef region_name) -> void; + + // Formats a full specific. + auto FormatSpecific(SpecificId id) -> void; + + // Handles generic-specific setup for FormatEntityStart. + auto FormatGenericStart(llvm::StringRef entity_kind, GenericId generic_id) + -> void; + + // Provides common formatting for entities, paired with FormatEntityEnd. + template + auto FormatEntityStart(llvm::StringRef entity_kind, + InstId first_owning_decl_id, GenericId generic_id, + IdT entity_id) -> void; + + template + auto FormatEntityStart(llvm::StringRef entity_kind, + const EntityWithParamsBase& entity, IdT entity_id) + -> void; + + // Provides common formatting for entities, paired with FormatEntityStart. + auto FormatEntityEnd(GenericId generic_id) -> void; + + // Formats parameters, eliding them completely if they're empty. Wraps input + // parameters in parentheses. Formats output parameter as a return type. + auto FormatParamList(InstBlockId params_id, bool has_return_slot = false) + -> void; + + // Prints instructions for a code block. + auto FormatCodeBlock(InstBlockId block_id) -> void; + + // Prints a code block with braces, intended to be used trailing after other + // content on the same line. If non-empty, instructions are on separate lines. + auto FormatTrailingBlock(InstBlockId block_id) -> void; + + // Prints the contents of a name scope, with an optional label. + auto FormatNameScope(NameScopeId id, llvm::StringRef label = "") -> void; + + auto FormatInst(InstId inst_id, Inst inst) -> void; + + // Don't print a constant for ImportRefUnloaded. + auto FormatInst(InstId inst_id, ImportRefUnloaded inst) -> void; + + // Prints a single instruction. + auto FormatInst(InstId inst_id) -> void; + + template + auto FormatInst(InstId inst_id, InstT inst) -> void; + + // If there is a pending library name that the current instruction was + // imported from, print it now and clear it out. + auto FormatPendingImportedFrom(AddSpace space_where) -> void; + + // If there is a pending constant value attached to the current instruction, + // print it now and clear it out. The constant value gets printed before the + // first braced block argument, or at the end of the instruction if there are + // no such arguments. + auto FormatPendingConstantValue(AddSpace space_where) -> void; + + auto FormatInstLhs(InstId inst_id, Inst inst) -> void; + + // Format ImportCppDecl name. + auto FormatInstLhs(InstId inst_id, ImportCppDecl inst) -> void; + + // Format ImportDecl with its name. + auto FormatInstLhs(InstId inst_id, ImportDecl inst) -> void; + + // Print ImportRefUnloaded with type-like semantics even though it lacks a + // type_id. + auto FormatInstLhs(InstId inst_id, ImportRefUnloaded inst) -> void; + + // Format ImplWitnessTable with its name even though it lacks a type_id. + auto FormatInstLhs(InstId inst_id, ImplWitnessTable inst) -> void; + + template + auto FormatInstRhs(InstT inst) -> void; + + auto FormatInstRhs(BindSymbolicName inst) -> void; + + auto FormatInstRhs(BlockArg inst) -> void; + auto FormatInstRhs(Namespace inst) -> void; + + auto FormatInst(InstId inst_id, BranchIf inst) -> void; + auto FormatInst(InstId inst_id, BranchWithArg inst) -> void; + auto FormatInst(InstId inst_id, Branch inst) -> void; + + auto FormatInstRhs(Call inst) -> void; + auto FormatInstRhs(ArrayInit inst) -> void; + auto FormatInstRhs(InitializeFrom inst) -> void; + auto FormatInstRhs(ValueParam inst) -> void; + auto FormatInstRhs(RefParam inst) -> void; + auto FormatInstRhs(OutParam inst) -> void; + auto FormatInstRhs(ReturnExpr ret) -> void; + auto FormatInstRhs(ReturnSlot inst) -> void; + auto FormatInstRhs(ReturnSlotPattern inst) -> void; + auto FormatInstRhs(StructInit init) -> void; + auto FormatInstRhs(TupleInit init) -> void; + auto FormatInstRhs(FunctionDecl inst) -> void; + auto FormatInstRhs(ClassDecl inst) -> void; + auto FormatInstRhs(ImplDecl inst) -> void; + auto FormatInstRhs(InterfaceDecl inst) -> void; + auto FormatInstRhs(AssociatedConstantDecl inst) -> void; + auto FormatInstRhs(IntValue inst) -> void; + auto FormatInstRhs(FloatLiteral inst) -> void; + + // Format the metadata in File for `import Cpp`. + auto FormatInstRhs(ImportCppDecl inst) -> void; + + auto FormatImportRefRhs(ImportIRInstId import_ir_inst_id, + EntityNameId entity_name_id, + llvm::StringLiteral loaded_label) -> void; + + auto FormatInstRhs(ImportRefLoaded inst) -> void; + auto FormatInstRhs(ImportRefUnloaded inst) -> void; + auto FormatInstRhs(InstValue inst) -> void; + auto FormatInstRhs(NameBindingDecl inst) -> void; + auto FormatInstRhs(SpliceBlock inst) -> void; + auto FormatInstRhs(WhereExpr inst) -> void; + auto FormatInstRhs(StructType inst) -> void; + + auto FormatArgs() -> void {} + + template + auto FormatArgs(Args... args) -> void { + out_ << ' '; + llvm::ListSeparator sep; + ((out_ << sep, FormatArg(args)), ...); + } + + // FormatArg variants handling printing instruction arguments. Several things + // provide equivalent behavior with `FormatName`, so we provide that as the + // default. + template + auto FormatArg(IdT id) -> void { + FormatName(id); + } + + auto FormatArg(BoolValue v) -> void { out_ << v; } + auto FormatArg(EntityNameId id) -> void; + auto FormatArg(FacetTypeId id) -> void; + auto FormatArg(IntKind k) -> void { k.Print(out_); } + auto FormatArg(FloatKind k) -> void { k.Print(out_); } + auto FormatArg(ImportIRId id) -> void; + auto FormatArg(IntId id) -> void; + auto FormatArg(ElementIndex index) -> void { out_ << index; } + auto FormatArg(CallParamIndex index) -> void { out_ << index; } + auto FormatArg(NameScopeId id) -> void; + auto FormatArg(InstBlockId id) -> void; + auto FormatArg(AbsoluteInstBlockId id) -> void; + auto FormatArg(RealId id) -> void; + auto FormatArg(StringLiteralValueId id) -> void; + + auto FormatReturnSlotArg(InstId dest_id) -> void; + + // `FormatName` is used when we need the name from an id. Most id types use + // equivalent name formatting from InstNamer, although there are a few special + // formats below. + template + auto FormatName(IdT id) -> void { + out_ << inst_namer_.GetNameFor(id); + } + + // TODO: Consider generic handling for children of `InstId`. + auto FormatName(AbsoluteInstId id) -> void { + FormatName(static_cast(id)); + } + + auto FormatName(DestInstId id) -> void { + FormatName(static_cast(id)); + } + + auto FormatName(MetaInstId id) -> void { + FormatName(static_cast(id)); + } + + auto FormatName(NameId id) -> void; + auto FormatName(InstId id) -> void; + + auto FormatName(SpecificId id) -> void; + auto FormatName(SpecificInterfaceId id) -> void; + + auto FormatName(TypeInstId id) -> void { + FormatName(static_cast(id)); + } + + auto FormatLabel(InstBlockId id) -> void; + + auto FormatConstant(ConstantId id) -> void; + + auto FormatInstAsType(InstId id) -> void; + + auto FormatTypeOfInst(InstId id) -> void; + + // Returns the label for the indicated IR. + auto GetImportIRLabel(ImportIRId id) -> std::string; + const File* sem_ir_; - ShouldFormatEntityFn should_format_entity_; - // Caches naming between Print calls. InstNamer inst_namer_; + ShouldFormatEntityFn should_format_entity_; + + // The output stream buffer. + std::string buffer_; + + // The output stream. + llvm::raw_string_ostream out_ = llvm::raw_string_ostream(buffer_); + + // Chunks of output text that we have created so far. + llvm::SmallVector output_chunks_; + + // The current scope that we are formatting within. References to names in + // this scope will not have a `@scope.` prefix added. + InstNamer::ScopeId scope_ = InstNamer::ScopeId::None; + + // Whether we are formatting in a terminator sequence, that is, a sequence of + // branches at the end of a block. The entirety of a terminator sequence is + // formatted on a single line, despite being multiple instructions. + bool in_terminator_sequence_ = false; + + // The indent depth to use for new instructions. + int indent_ = 0; + + // Whether we are currently formatting immediately after an open brace. If so, + // a newline will be inserted before the next line indent. + bool after_open_brace_ = false; + + // The constant value of the current instruction, if it has one that has not + // yet been printed. The value `NotConstant` is used as a sentinel to indicate + // there is nothing to print. + ConstantId pending_constant_value_ = ConstantId::NotConstant; + + // Whether `pending_constant_value_`'s instruction is the same as the + // instruction currently being printed. If true, only the phase of the + // constant is printed, and the value is omitted. + bool pending_constant_value_is_self_ = false; + + // The name of the IR file from which the current entity was imported, if it + // was imported and no file has been printed yet. This is printed before the + // first open brace or the semicolon in the entity declaration. + llvm::StringRef pending_imported_from_; + + // Indexes of chunks of output that should be included when an instruction is + // referenced, indexed by the instruction's index. This is resized in advance + // to the correct size. + llvm::SmallVector tentative_inst_chunks_; }; +template +auto Formatter::FormatEntityStart(llvm::StringRef entity_kind, + InstId first_owning_decl_id, + GenericId generic_id, IdT entity_id) -> void { + // If this entity was imported from a different IR, annotate the name of + // that IR in the output before the `{` or `;`. + if (first_owning_decl_id.has_value()) { + auto loc_id = sem_ir_->insts().GetCanonicalLocId(first_owning_decl_id); + if (loc_id.kind() == LocId::Kind::ImportIRInstId) { + auto import_ir_id = + sem_ir_->import_ir_insts().Get(loc_id.import_ir_inst_id()).ir_id(); + const auto* import_file = sem_ir_->import_irs().Get(import_ir_id).sem_ir; + pending_imported_from_ = import_file->filename(); + } + } + + if (generic_id.has_value()) { + FormatGenericStart(entity_kind, generic_id); + } + + out_ << "\n"; + after_open_brace_ = false; + Indent(); + out_ << entity_kind; + + // If there's a generic, it will have attached the name. Otherwise, add the + // name here. + if (!generic_id.has_value()) { + out_ << " "; + FormatName(entity_id); + } +} + +template +auto Formatter::FormatEntityStart(llvm::StringRef entity_kind, + const EntityWithParamsBase& entity, + IdT entity_id) -> void { + FormatEntityStart(entity_kind, entity.first_owning_decl_id, entity.generic_id, + entity_id); +} + +template +auto Formatter::FormatInst(InstId inst_id, InstT inst) -> void { + Indent(); + FormatInstLhs(inst_id, inst); + out_ << InstT::Kind.ir_name(); + pending_constant_value_ = sem_ir_->constant_values().Get(inst_id); + pending_constant_value_is_self_ = sem_ir_->constant_values().GetInstIdIfValid( + pending_constant_value_) == inst_id; + FormatInstRhs(inst); + FormatPendingConstantValue(AddSpace::Before); + out_ << "\n"; +} + +template +auto Formatter::FormatInstRhs(InstT inst) -> void { + // By default, an instruction has a comma-separated argument list. + using Info = Internal::InstLikeTypeInfo; + if constexpr (Info::NumArgs == 2) { + // Several instructions have a second operand that's a specific ID. We + // don't include it in the argument list if there is no corresponding + // specific, that is, when we're not in a generic context. + if constexpr (std::is_same_v, + SpecificId>) { + if (!Info::template Get<1>(inst).has_value()) { + FormatArgs(Info::template Get<0>(inst)); + return; + } + } + FormatArgs(Info::template Get<0>(inst), Info::template Get<1>(inst)); + } else if constexpr (Info::NumArgs == 1) { + FormatArgs(Info::template Get<0>(inst)); + } else { + FormatArgs(); + } +} + } // namespace Carbon::SemIR #endif // CARBON_TOOLCHAIN_SEM_IR_FORMATTER_H_