Prechádzať zdrojové kódy

Use verbose formatting of instructions on crash messages. (#4125)

Changes crash messages to start printing verbose forms of instructions,
rather than just the ID. Fixes some indentation issues with stacks. Also
switches unexpected inst formatting, because now there are lots, and
it'd be helpful to know where they are.

This uses a pimpl pattern for Formatter due to the number of member
functions on Formatter. Maybe we should refactor that, but this didn't
feel like a good place to do so.

Note, I have two concerns about this change... to note them here, to
make sure others are considering them when evaluating the
implementation:

1. Some instructions are very verbose to print, as evidenced by the
fn_decl printing (which includes function params) or scope printing
(which includes scope members).
- I'm not sure whether there's a way to simply reduce this, as it seems
essential to the requested printing of instructions.
- Long-term, we may at least want to limit the number of lines printed
here. However, I've already spent a fair amount of time here and I think
it's in a good state to evaluate.
2. Increased complexity in the crash handler may result in crash
messages failing to generate.
- For example, a crash in Formatter (and its deps, such as InstNamer or
location handling) prevents a stack from being printed. I'm pretty sure
I've written crashes in Formatter before.

Here's an example crash snippet (generated by adding a crash inside
`return` handling) before:

```
2.	NodeStack:
	0.	FunctionDefinitionStart -> function2
	1.	ReturnStatementStart -> no value
	2.	IntLiteral -> inst+26
inst_block_stack_:
	0.	block<invalid>	{inst+0, inst+1, inst+2, inst+23}
	1.	block9	{inst+26}
param_and_arg_refs_stack:
args_type_info_stack_:
```

And after:

```
2.	Check::Context
          NodeStack:
            0. FunctionDefinitionStart: function2
            1. ReturnStatementStart: no value
            2. IntLiteral:
              unexpected.inst+26.loc12_10: i32 = int_literal 0 [template = constants.%.2]
          inst_block_stack_:
            0. block<invalid> {
                package: <namespace> = namespace [template] {
                  .Core = unexpected.inst+2
                  .F = unexpected.inst+23.loc11_22
                }
                unexpected.inst+1 = import Core
                unexpected.inst+2: <namespace> = namespace unexpected.inst+1, [template] {}
                unexpected.inst+23.loc11_22: %F.type = fn_decl @F [template = constants.%F] {
                  unexpected.inst+9.loc11_9: init type = call constants.%Bool() [template = bool]
                  unexpected.inst+10.loc11_9: type = value_of_initializer unexpected.inst+9.loc11_9 [template = bool]
                  unexpected.inst+11.loc11_9: type = converted unexpected.inst+9.loc11_9, unexpected.inst+10.loc11_9 [template = bool]
                  unexpected.inst+12.loc11_6: bool = param b
                  @F.%b: bool = bind_name b, unexpected.inst+12.loc11_6
                  unexpected.inst+19.loc11_18: init type = call constants.%Int32() [template = i32]
                  unexpected.inst+20.loc11_18: type = value_of_initializer unexpected.inst+19.loc11_18 [template = i32]
                  unexpected.inst+21.loc11_18: type = converted unexpected.inst+19.loc11_18, unexpected.inst+20.loc11_18 [template = i32]
                  @F.%return: ref i32 = var <return slot>
                }
              }
            1. block9 {
                unexpected.inst+26.loc12_10: i32 = int_literal 0 [template = constants.%.2]
              }
          param_and_arg_refs_stack:
          args_type_info_stack_:
```
Jon Ross-Perkins 1 rok pred
rodič
commit
65d6e3e221
35 zmenil súbory, kde vykonal 238 pridanie a 148 odobranie
  1. 1 0
      toolchain/check/BUILD
  2. 13 5
      toolchain/check/context.cpp
  3. 8 7
      toolchain/check/inst_block_stack.cpp
  4. 3 1
      toolchain/check/inst_block_stack.h
  5. 11 5
      toolchain/check/node_stack.cpp
  6. 3 1
      toolchain/check/node_stack.h
  7. 3 2
      toolchain/check/param_and_arg_refs_stack.h
  8. 1 1
      toolchain/check/testdata/class/fail_generic_method.carbon
  9. 6 6
      toolchain/check/testdata/class/generic/basic.carbon
  10. 8 8
      toolchain/check/testdata/class/generic/fail_todo_use.carbon
  11. 12 12
      toolchain/check/testdata/class/generic/field.carbon
  12. 6 6
      toolchain/check/testdata/class/generic/import.carbon
  13. 4 4
      toolchain/check/testdata/class/generic_method.carbon
  14. 1 1
      toolchain/check/testdata/function/generic/redeclare.carbon
  15. 2 2
      toolchain/check/testdata/if_expr/fail_not_in_function.carbon
  16. 2 2
      toolchain/check/testdata/impl/fail_extend_impl_forall.carbon
  17. 1 1
      toolchain/check/testdata/impl/fail_impl_bad_assoc_fn.carbon
  18. 1 1
      toolchain/check/testdata/impl/fail_redefinition.carbon
  19. 1 1
      toolchain/check/testdata/impl/no_prelude/self_in_signature.carbon
  20. 1 1
      toolchain/check/testdata/interface/no_prelude/fail_assoc_const_not_binding.carbon
  21. 1 1
      toolchain/check/testdata/interface/no_prelude/fail_assoc_const_template.carbon
  22. 1 1
      toolchain/check/testdata/interface/no_prelude/fail_duplicate.carbon
  23. 2 2
      toolchain/check/testdata/interface/no_prelude/fail_generic_redeclaration.carbon
  24. 9 9
      toolchain/check/testdata/interface/no_prelude/fail_todo_generic_default_fn.carbon
  25. 4 4
      toolchain/check/testdata/interface/no_prelude/generic.carbon
  26. 3 3
      toolchain/check/testdata/interface/no_prelude/generic_import.carbon
  27. 1 1
      toolchain/check/testdata/operators/builtin/fail_and_or_not_in_function.carbon
  28. 1 1
      toolchain/check/testdata/packages/no_prelude/fail_export_name_params.carbon
  29. 3 3
      toolchain/check/testdata/return/fail_let_in_type.carbon
  30. 3 3
      toolchain/check/testdata/struct/import.carbon
  31. 3 3
      toolchain/check/testdata/tuples/import.carbon
  32. 10 7
      toolchain/driver/driver.cpp
  33. 76 39
      toolchain/sem_ir/formatter.cpp
  34. 24 3
      toolchain/sem_ir/formatter.h
  35. 9 1
      toolchain/sem_ir/inst_namer.cpp

+ 1 - 0
toolchain/check/BUILD

@@ -241,6 +241,7 @@ cc_library(
         "//common:vlog",
         "//toolchain/parse:node_kind",
         "//toolchain/parse:tree",
+        "//toolchain/sem_ir:formatter",
         "//toolchain/sem_ir:ids",
         "@llvm-project//llvm:Support",
     ],

+ 13 - 5
toolchain/check/context.cpp

@@ -1193,14 +1193,22 @@ auto Context::GetUnqualifiedType(SemIR::TypeId type_id) -> SemIR::TypeId {
 }
 
 auto Context::PrintForStackDump(llvm::raw_ostream& output) const -> void {
-  node_stack_.PrintForStackDump(output);
-  inst_block_stack_.PrintForStackDump(output);
-  param_and_arg_refs_stack_.PrintForStackDump(output);
-  args_type_info_stack_.PrintForStackDump(output);
+  output << "Check::Context\n";
+
+  // In a stack dump, this is probably indented by a tab. We treat that as 8
+  // spaces then add a couple to indent past the Context label.
+  constexpr int Indent = 10;
+
+  SemIR::Formatter formatter(*tokens_, *parse_tree_, *sem_ir_);
+  node_stack_.PrintForStackDump(formatter, Indent, output);
+  inst_block_stack_.PrintForStackDump(formatter, Indent, output);
+  param_and_arg_refs_stack_.PrintForStackDump(formatter, Indent, output);
+  args_type_info_stack_.PrintForStackDump(formatter, Indent, output);
 }
 
 auto Context::DumpFormattedFile() const -> void {
-  FormatFile(*tokens_, *parse_tree_, *sem_ir_, llvm::errs());
+  SemIR::Formatter formatter(*tokens_, *parse_tree_, *sem_ir_);
+  formatter.Print(llvm::errs());
 }
 
 }  // namespace Carbon::Check

+ 8 - 7
toolchain/check/inst_block_stack.cpp

@@ -61,16 +61,17 @@ auto InstBlockStack::PopAndDiscard() -> void {
   CARBON_VLOG() << name_ << " PopAndDiscard " << id_stack_.size() << "\n";
 }
 
-auto InstBlockStack::PrintForStackDump(llvm::raw_ostream& output) const
+auto InstBlockStack::PrintForStackDump(SemIR::Formatter& formatter, int indent,
+                                       llvm::raw_ostream& output) const
     -> void {
+  output.indent(indent);
   output << name_ << ":\n";
   for (const auto& [i, id] : llvm::enumerate(id_stack_)) {
-    output << "\t" << i << ".\t" << id << "\t{";
-    llvm::ListSeparator sep;
-    for (auto id : insts_stack_.PeekArrayAt(i)) {
-      output << sep << id;
-    }
-    output << "}\n";
+    output.indent(indent + 2);
+    output << i << ". " << id;
+    formatter.PrintPartialTrailingCodeBlock(insts_stack_.PeekArrayAt(i),
+                                            indent + 4, output);
+    output << "\n";
   }
 }
 

+ 3 - 1
toolchain/check/inst_block_stack.h

@@ -8,6 +8,7 @@
 #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 {
 
@@ -69,7 +70,8 @@ class InstBlockStack {
   }
 
   // Prints the stack for a stack dump.
-  auto PrintForStackDump(llvm::raw_ostream& output) const -> void;
+  auto PrintForStackDump(SemIR::Formatter& formatter, int indent,
+                         llvm::raw_ostream& output) const -> void;
 
   // Runs verification that the processing cleanly finished.
   auto VerifyOnFinish() const -> void {

+ 11 - 5
toolchain/check/node_stack.cpp

@@ -8,21 +8,28 @@
 
 namespace Carbon::Check {
 
-auto NodeStack::PrintForStackDump(llvm::raw_ostream& output) const -> void {
+auto NodeStack::PrintForStackDump(SemIR::Formatter& formatter, int indent,
+                                  llvm::raw_ostream& output) const -> void {
   auto print_id = [&]<Id::Kind Kind>(Id id) {
     if constexpr (Kind == Id::Kind::None) {
-      output << " -> no value";
+      output << "no value\n";
     } else if constexpr (Kind == Id::Kind::Invalid) {
       CARBON_FATAL() << "Should not be in node stack";
+    } else if constexpr (Kind == Id::KindFor<SemIR::InstId>()) {
+      output << "\n";
+      formatter.PrintInst(id.As<Id::KindFor<SemIR::InstId>()>(), indent + 4,
+                          output);
     } else {
-      output << " -> " << id.As<Kind>();
+      output << id.As<Kind>() << "\n";
     }
   };
 
+  output.indent(indent);
   output << "NodeStack:\n";
   for (auto [i, entry] : llvm::enumerate(stack_)) {
     auto node_kind = parse_tree_->node_kind(entry.node_id);
-    output << "\t" << i << ".\t" << node_kind;
+    output.indent(indent + 2);
+    output << i << ". " << node_kind << ": ";
     switch (node_kind) {
 #define CARBON_PARSE_NODE_KIND(Kind)                                        \
   case Parse::NodeKind::Kind:                                               \
@@ -30,7 +37,6 @@ auto NodeStack::PrintForStackDump(llvm::raw_ostream& output) const -> void {
     break;
 #include "toolchain/parse/node_kind.def"
     }
-    output << "\n";
   }
 }
 

+ 3 - 1
toolchain/check/node_stack.h

@@ -11,6 +11,7 @@
 #include "toolchain/parse/node_kind.h"
 #include "toolchain/parse/tree.h"
 #include "toolchain/parse/typed_nodes.h"
+#include "toolchain/sem_ir/formatter.h"
 #include "toolchain/sem_ir/id_kind.h"
 #include "toolchain/sem_ir/ids.h"
 
@@ -325,7 +326,8 @@ class NodeStack {
   }
 
   // Prints the stack for a stack dump.
-  auto PrintForStackDump(llvm::raw_ostream& output) const -> void;
+  auto PrintForStackDump(SemIR::Formatter& formatter, int indent,
+                         llvm::raw_ostream& output) const -> void;
 
   auto empty() const -> bool { return stack_.empty(); }
   auto size() const -> size_t { return stack_.size(); }

+ 3 - 2
toolchain/check/param_and_arg_refs_stack.h

@@ -72,8 +72,9 @@ class ParamAndArgRefsStack {
   auto VerifyOnFinish() -> void { stack_.VerifyOnFinish(); }
 
   // Prints the stack for a stack dump.
-  auto PrintForStackDump(llvm::raw_ostream& output) const -> void {
-    return stack_.PrintForStackDump(output);
+  auto PrintForStackDump(SemIR::Formatter& formatter, int indent,
+                         llvm::raw_ostream& output) const -> void {
+    return stack_.PrintForStackDump(formatter, indent, output);
   }
 
  private:

+ 1 - 1
toolchain/check/testdata/class/fail_generic_method.carbon

@@ -83,7 +83,7 @@ fn Class(N:! i32).F[self: Self](n: T) {}
 // CHECK:STDOUT: class @Class
 // CHECK:STDOUT:     generic [file.%T.loc11_13.2: type] {
 // CHECK:STDOUT:   %T.ref.loc12: type = name_ref T, file.%T.loc11_13.2 [symbolic = file.%T.loc11_13.2 (constants.%T)]
-// CHECK:STDOUT:   %.loc12: <unexpected instref inst+28> (%.2) = field_decl a, element0 [template]
+// CHECK:STDOUT:   %.loc12: <unexpected>.inst+28 (%.2) = field_decl a, element0 [template]
 // CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [template = constants.%F] {
 // CHECK:STDOUT:     %.loc13: type = specific_constant constants.%Class.2, file.%Class.decl(constants.%T) [symbolic = %.loc13 (constants.%Class.2)]
 // CHECK:STDOUT:     %Self.ref: type = name_ref Self, %.loc13 [symbolic = %.loc13 (constants.%Class.2)]

+ 6 - 6
toolchain/check/testdata/class/generic/basic.carbon

@@ -76,7 +76,7 @@ class Class(T:! type) {
 // CHECK:STDOUT:     %return.var.loc17: ref %T = var <return slot>
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %T.ref.loc21: type = name_ref T, file.%T.loc11_13.2 [symbolic = file.%T.loc11_13.2 (constants.%T)]
-// CHECK:STDOUT:   %.loc21: <unexpected instref inst+41> (%.4) = field_decl k, element0 [template]
+// CHECK:STDOUT:   %.loc21: <unexpected>.inst+41 (%.4) = field_decl k, element0 [template]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%Class.2
@@ -90,7 +90,7 @@ class Class(T:! type) {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %self.ref: @Class.%.loc12_29 (%.2) = name_ref self, @Class.%self.loc12_19.3
 // CHECK:STDOUT:   %.loc13_17.1: ref @Class.%.loc12_25 (%Class.2) = deref %self.ref
-// CHECK:STDOUT:   %k.ref: <unexpected instref inst+49> (%.4) = name_ref k, @Class.%.loc21 [template = @Class.%.loc21]
+// CHECK:STDOUT:   %k.ref: <unexpected>.inst+49 (%.4) = name_ref k, @Class.%.loc21 [template = @Class.%.loc21]
 // CHECK:STDOUT:   %.loc13_17.2: ref @Class.%T.ref.loc12 (%T) = class_element_access %.loc13_17.1, element0
 // CHECK:STDOUT:   %.loc13_12: @Class.%.loc12_38 (%.3) = addr_of %.loc13_17.2
 // CHECK:STDOUT:   return %.loc13_12
@@ -100,7 +100,7 @@ class Class(T:! type) {
 // CHECK:STDOUT:     generic [file.%T.loc11_13.2: type] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %self.ref: @Class.%.loc17 (%Class.2) = name_ref self, @Class.%self.loc17_15.2
-// CHECK:STDOUT:   %k.ref: <unexpected instref inst+55> (%.4) = name_ref k, @Class.%.loc21 [template = @Class.%.loc21]
+// CHECK:STDOUT:   %k.ref: <unexpected>.inst+55 (%.4) = name_ref k, @Class.%.loc21 [template = @Class.%.loc21]
 // CHECK:STDOUT:   %.loc18_16.1: ref @Class.%T.ref.loc17 (%T) = class_element_access %self.ref, element0
 // CHECK:STDOUT:   %.loc18_16.2: @Class.%T.ref.loc17 (%T) = bind_value %.loc18_16.1
 // CHECK:STDOUT:   return %.loc18_16.2
@@ -111,9 +111,9 @@ class Class(T:! type) {
 // CHECK:STDOUT:   file.%T.loc11_13.2 => constants.%T
 // CHECK:STDOUT:
 // CHECK:STDOUT: definition:
-// CHECK:STDOUT:   <unexpected instref inst+40> => constants.%Class.2
-// CHECK:STDOUT:   <unexpected instref inst+41> => constants.%.4
-// CHECK:STDOUT:   <unexpected instref inst+37> => <unexpected instref inst+38>
+// CHECK:STDOUT:   <unexpected>.inst+40 => constants.%Class.2
+// CHECK:STDOUT:   <unexpected>.inst+41 => constants.%.4
+// CHECK:STDOUT:   <unexpected>.inst+37.loc21_8 => <unexpected>.inst+38
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: specific @Class.%GetAddr.decl(constants.%T) {

+ 8 - 8
toolchain/check/testdata/class/generic/fail_todo_use.carbon

@@ -101,7 +101,7 @@ fn Run() -> i32 {
 // CHECK:STDOUT:     %return.var: ref %.3 = var <return slot>
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %T.ref.loc16: type = name_ref T, file.%T.loc11_13.2 [symbolic = file.%T.loc11_13.2 (constants.%T)]
-// CHECK:STDOUT:   %.loc16: <unexpected instref inst+32> (%.4) = field_decl k, element0 [template]
+// CHECK:STDOUT:   %.loc16: <unexpected>.inst+32 (%.4) = field_decl k, element0 [template]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%Class.2
@@ -114,7 +114,7 @@ fn Run() -> i32 {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %self.ref: @Class.%.loc12_25 (%.2) = name_ref self, @Class.%self.loc12_15.3
 // CHECK:STDOUT:   %.loc13_17.1: ref @Class.%.loc12_21 (%Class.2) = deref %self.ref
-// CHECK:STDOUT:   %k.ref: <unexpected instref inst+40> (%.4) = name_ref k, @Class.%.loc16 [template = @Class.%.loc16]
+// CHECK:STDOUT:   %k.ref: <unexpected>.inst+40 (%.4) = name_ref k, @Class.%.loc16 [template = @Class.%.loc16]
 // CHECK:STDOUT:   %.loc13_17.2: ref @Class.%T.ref.loc12 (%T) = class_element_access %.loc13_17.1, element0
 // CHECK:STDOUT:   %.loc13_12: @Class.%.loc12_34 (%.3) = addr_of %.loc13_17.2
 // CHECK:STDOUT:   return %.loc13_12
@@ -149,9 +149,9 @@ fn Run() -> i32 {
 // CHECK:STDOUT:   file.%T.loc11_13.2 => constants.%T
 // CHECK:STDOUT:
 // CHECK:STDOUT: definition:
-// CHECK:STDOUT:   <unexpected instref inst+31> => constants.%Class.2
-// CHECK:STDOUT:   <unexpected instref inst+32> => constants.%.4
-// CHECK:STDOUT:   <unexpected instref inst+28> => <unexpected instref inst+29>
+// CHECK:STDOUT:   <unexpected>.inst+31 => constants.%Class.2
+// CHECK:STDOUT:   <unexpected>.inst+32 => constants.%.4
+// CHECK:STDOUT:   <unexpected>.inst+28.loc16_8 => <unexpected>.inst+29
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: specific @Class.%Get.decl(constants.%T) {
@@ -172,8 +172,8 @@ fn Run() -> i32 {
 // CHECK:STDOUT:   file.%T.loc11_13.2 => i32
 // CHECK:STDOUT:
 // CHECK:STDOUT: definition:
-// CHECK:STDOUT:   <unexpected instref inst+31> => constants.%Class.3
-// CHECK:STDOUT:   <unexpected instref inst+32> => constants.%.7
-// CHECK:STDOUT:   <unexpected instref inst+28> => <unexpected instref inst+29>
+// CHECK:STDOUT:   <unexpected>.inst+31 => constants.%Class.3
+// CHECK:STDOUT:   <unexpected>.inst+32 => constants.%.7
+// CHECK:STDOUT:   <unexpected>.inst+28.loc16_8 => <unexpected>.inst+29
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 12 - 12
toolchain/check/testdata/class/generic/field.carbon

@@ -115,7 +115,7 @@ fn H(U:! type, c: Class(U)) -> U {
 // CHECK:STDOUT: class @Class
 // CHECK:STDOUT:     generic [file.%T.loc11_13.2: type] {
 // CHECK:STDOUT:   %T.ref: type = name_ref T, file.%T.loc11_13.2 [symbolic = file.%T.loc11_13.2 (constants.%T)]
-// CHECK:STDOUT:   %.loc12: <unexpected instref inst+18> (%.2) = field_decl x, element0 [template]
+// CHECK:STDOUT:   %.loc12: <unexpected>.inst+18 (%.2) = field_decl x, element0 [template]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%Class.2
@@ -137,7 +137,7 @@ fn H(U:! type, c: Class(U)) -> U {
 // CHECK:STDOUT:     generic [%T: type] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %c.ref: file.%.loc19_24 (%Class.2) = name_ref c, %c
-// CHECK:STDOUT:   %x.ref: <unexpected instref inst+68> (%.2) = name_ref x, @Class.%.loc12 [template = @Class.%.loc12]
+// CHECK:STDOUT:   %x.ref: <unexpected>.inst+68 (%.2) = name_ref x, @Class.%.loc12 [template = @Class.%.loc12]
 // CHECK:STDOUT:   %.loc20_11.1: ref @G.%T (%T) = class_element_access %c.ref, element0
 // CHECK:STDOUT:   %.loc20_11.2: @G.%T (%T) = bind_value %.loc20_11.1
 // CHECK:STDOUT:   return %.loc20_11.2
@@ -147,7 +147,7 @@ fn H(U:! type, c: Class(U)) -> U {
 // CHECK:STDOUT:     generic [%U: type] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %c.ref: file.%.loc23_24 (%Class.4) = name_ref c, %c
-// CHECK:STDOUT:   %x.ref: <unexpected instref inst+91> (%.6) = name_ref x, @Class.%.loc12 [template = @Class.%.loc12]
+// CHECK:STDOUT:   %x.ref: <unexpected>.inst+91 (%.6) = name_ref x, @Class.%.loc12 [template = @Class.%.loc12]
 // CHECK:STDOUT:   %.loc24_11.1: ref @H.%U (%U) = class_element_access %c.ref, element0
 // CHECK:STDOUT:   %.loc24_11.2: @H.%U (%U) = bind_value %.loc24_11.1
 // CHECK:STDOUT:   return %.loc24_11.2
@@ -158,9 +158,9 @@ fn H(U:! type, c: Class(U)) -> U {
 // CHECK:STDOUT:   file.%T.loc11_13.2 => constants.%T
 // CHECK:STDOUT:
 // CHECK:STDOUT: definition:
-// CHECK:STDOUT:   <unexpected instref inst+17> => constants.%Class.2
-// CHECK:STDOUT:   <unexpected instref inst+18> => constants.%.2
-// CHECK:STDOUT:   <unexpected instref inst+14> => <unexpected instref inst+15>
+// CHECK:STDOUT:   <unexpected>.inst+17 => constants.%Class.2
+// CHECK:STDOUT:   <unexpected>.inst+18 => constants.%.2
+// CHECK:STDOUT:   <unexpected>.inst+14.loc12_8 => <unexpected>.inst+15
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: specific file.%Class.decl(file.%T.loc11_13.2) {
@@ -173,9 +173,9 @@ fn H(U:! type, c: Class(U)) -> U {
 // CHECK:STDOUT:   file.%T.loc11_13.2 => i32
 // CHECK:STDOUT:
 // CHECK:STDOUT: definition:
-// CHECK:STDOUT:   <unexpected instref inst+17> => constants.%Class.3
-// CHECK:STDOUT:   <unexpected instref inst+18> => constants.%.4
-// CHECK:STDOUT:   <unexpected instref inst+14> => <unexpected instref inst+15>
+// CHECK:STDOUT:   <unexpected>.inst+17 => constants.%Class.3
+// CHECK:STDOUT:   <unexpected>.inst+18 => constants.%.4
+// CHECK:STDOUT:   <unexpected>.inst+14.loc12_8 => <unexpected>.inst+15
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: specific file.%G.decl(constants.%T) {
@@ -189,9 +189,9 @@ fn H(U:! type, c: Class(U)) -> U {
 // CHECK:STDOUT:   file.%T.loc11_13.2 => constants.%U
 // CHECK:STDOUT:
 // CHECK:STDOUT: definition:
-// CHECK:STDOUT:   <unexpected instref inst+17> => constants.%Class.4
-// CHECK:STDOUT:   <unexpected instref inst+18> => constants.%.6
-// CHECK:STDOUT:   <unexpected instref inst+14> => <unexpected instref inst+15>
+// CHECK:STDOUT:   <unexpected>.inst+17 => constants.%Class.4
+// CHECK:STDOUT:   <unexpected>.inst+18 => constants.%.6
+// CHECK:STDOUT:   <unexpected>.inst+14.loc12_8 => <unexpected>.inst+15
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: specific file.%H.decl(constants.%U) {

+ 6 - 6
toolchain/check/testdata/class/generic/import.carbon

@@ -154,7 +154,7 @@ class Class(U:! type) {
 // CHECK:STDOUT:   %int.make_type_32.loc7: init type = call constants.%Int32() [template = i32]
 // CHECK:STDOUT:   %.loc7_10.1: type = value_of_initializer %int.make_type_32.loc7 [template = i32]
 // CHECK:STDOUT:   %.loc7_10.2: type = converted %int.make_type_32.loc7, %.loc7_10.1 [template = i32]
-// CHECK:STDOUT:   %.loc7_8: <unexpected instref inst+39> (%.2) = field_decl n, element0 [template]
+// CHECK:STDOUT:   %.loc7_8: <unexpected>.inst+39 (%.2) = field_decl n, element0 [template]
 // CHECK:STDOUT:   %F.decl: %F.type.1 = fn_decl @F.1 [template = constants.%F.1] {
 // CHECK:STDOUT:     %int.make_type_32.loc8: init type = call constants.%Int32() [template = i32]
 // CHECK:STDOUT:     %.loc8_13.1: type = value_of_initializer %int.make_type_32.loc8 [template = i32]
@@ -206,7 +206,7 @@ class Class(U:! type) {
 // CHECK:STDOUT: --- foo.impl.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
-// CHECK:STDOUT:   %T: type = bind_symbolic_name T 0, <unexpected instref inst+31> [symbolic]
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T 0, <unexpected>.inst+31 [symbolic]
 // CHECK:STDOUT:   %Class.type: type = generic_class_type @Class [template]
 // CHECK:STDOUT:   %.1: type = tuple_type () [template]
 // CHECK:STDOUT:   %Class.1: %Class.type = struct_value () [template]
@@ -308,7 +308,7 @@ class Class(U:! type) {
 // CHECK:STDOUT:   %CompleteClass.type: type = generic_class_type @CompleteClass [template]
 // CHECK:STDOUT:   %CompleteClass.1: %CompleteClass.type = struct_value () [template]
 // CHECK:STDOUT:   %.2: type = struct_type {.n: i32} [template]
-// CHECK:STDOUT:   %T: type = bind_symbolic_name T 0, <unexpected instref inst+28> [symbolic]
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T 0, <unexpected>.inst+28 [symbolic]
 // CHECK:STDOUT:   %CompleteClass.2: type = class_type @CompleteClass, <invalid>(%T) [symbolic]
 // CHECK:STDOUT:   %CompleteClass.3: type = class_type @CompleteClass, <invalid>(i32) [template]
 // CHECK:STDOUT:   %.3: type = ptr_type %.2 [template]
@@ -399,7 +399,7 @@ class Class(U:! type) {
 // CHECK:STDOUT:   %CompleteClass.type: type = generic_class_type @CompleteClass [template]
 // CHECK:STDOUT:   %CompleteClass.1: %CompleteClass.type = struct_value () [template]
 // CHECK:STDOUT:   %.2: type = struct_type {.n: i32} [template]
-// CHECK:STDOUT:   %T: type = bind_symbolic_name T 0, <unexpected instref inst+28> [symbolic]
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T 0, <unexpected>.inst+28 [symbolic]
 // CHECK:STDOUT:   %CompleteClass.2: type = class_type @CompleteClass, <invalid>(%T) [symbolic]
 // CHECK:STDOUT:   %CompleteClass.3: type = class_type @CompleteClass, <invalid>(i32) [template]
 // CHECK:STDOUT:   %.3: type = ptr_type %.2 [template]
@@ -483,7 +483,7 @@ class Class(U:! type) {
 // CHECK:STDOUT:   %CompleteClass.type: type = generic_class_type @CompleteClass [template]
 // CHECK:STDOUT:   %CompleteClass.1: %CompleteClass.type = struct_value () [template]
 // CHECK:STDOUT:   %.2: type = struct_type {.n: i32} [template]
-// CHECK:STDOUT:   %T: type = bind_symbolic_name T 0, <unexpected instref inst+19> [symbolic]
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T 0, <unexpected>.inst+19 [symbolic]
 // CHECK:STDOUT:   %CompleteClass.2: type = class_type @CompleteClass, <invalid>(%T) [symbolic]
 // CHECK:STDOUT:   %Int32.type: type = fn_type @Int32 [template]
 // CHECK:STDOUT:   %Int32: %Int32.type = struct_value () [template]
@@ -562,7 +562,7 @@ class Class(U:! type) {
 // CHECK:STDOUT:   %Class.type: type = generic_class_type @Class [template]
 // CHECK:STDOUT:   %.1: type = tuple_type () [template]
 // CHECK:STDOUT:   %Class.1: %Class.type = struct_value () [template]
-// CHECK:STDOUT:   %T: type = bind_symbolic_name T 0, <unexpected instref inst+17> [symbolic]
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T 0, <unexpected>.inst+17 [symbolic]
 // CHECK:STDOUT:   %Class.2: type = class_type @Class, <invalid>(%T) [symbolic]
 // CHECK:STDOUT:   %.type: type = generic_class_type @.1 [template]
 // CHECK:STDOUT:   %.2: %.type = struct_value () [template]

+ 4 - 4
toolchain/check/testdata/class/generic_method.carbon

@@ -57,7 +57,7 @@ fn Class(T:! type).F[self: Self](n: T) {}
 // CHECK:STDOUT: class @Class
 // CHECK:STDOUT:     generic [file.%T.loc11_13.2: type] {
 // CHECK:STDOUT:   %T.ref.loc12: type = name_ref T, file.%T.loc11_13.2 [symbolic = file.%T.loc11_13.2 (constants.%T)]
-// CHECK:STDOUT:   %.loc12: <unexpected instref inst+28> (%.2) = field_decl a, element0 [template]
+// CHECK:STDOUT:   %.loc12: <unexpected>.inst+28 (%.2) = field_decl a, element0 [template]
 // CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [template = constants.%F] {
 // CHECK:STDOUT:     %.loc13: type = specific_constant constants.%Class.2, file.%Class.decl(constants.%T) [symbolic = %.loc13 (constants.%Class.2)]
 // CHECK:STDOUT:     %Self.ref: type = name_ref Self, %.loc13 [symbolic = %.loc13 (constants.%Class.2)]
@@ -85,9 +85,9 @@ fn Class(T:! type).F[self: Self](n: T) {}
 // CHECK:STDOUT:   file.%T.loc11_13.2 => constants.%T
 // CHECK:STDOUT:
 // CHECK:STDOUT: definition:
-// CHECK:STDOUT:   <unexpected instref inst+27> => constants.%Class.2
-// CHECK:STDOUT:   <unexpected instref inst+28> => constants.%.2
-// CHECK:STDOUT:   <unexpected instref inst+14> => <unexpected instref inst+15>
+// CHECK:STDOUT:   <unexpected>.inst+27 => constants.%Class.2
+// CHECK:STDOUT:   <unexpected>.inst+28 => constants.%.2
+// CHECK:STDOUT:   <unexpected>.inst+14.loc12_8 => <unexpected>.inst+15
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: specific @Class.%F.decl(constants.%T) {

+ 1 - 1
toolchain/check/testdata/function/generic/redeclare.carbon

@@ -200,7 +200,7 @@ fn F(U:! type, T:! type) -> U* {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %F.ref: %F.type = name_ref F, file.%F.decl [template = constants.%F]
 // CHECK:STDOUT:   %T.ref: type = name_ref T, %T [symbolic = %T (constants.%T)]
-// CHECK:STDOUT:   %F.call: init <unexpected instref inst+32> (%.1) = call %F.ref(<invalid>) [template = <error>]
+// CHECK:STDOUT:   %F.call: init <unexpected>.inst+32 (%.1) = call %F.ref(<invalid>) [template = <error>]
 // CHECK:STDOUT:   return <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 2 - 2
toolchain/check/testdata/if_expr/fail_not_in_function.carbon

@@ -63,7 +63,7 @@ class C {
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace [template] {
 // CHECK:STDOUT:     .Core = %Core
-// CHECK:STDOUT:     .x = <unexpected instref inst+12>
+// CHECK:STDOUT:     .x = <unexpected>.inst+12.loc23_5
 // CHECK:STDOUT:     .C = %C.decl
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Core.import = import Core
@@ -80,7 +80,7 @@ class C {
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%C
-// CHECK:STDOUT:   .n = <unexpected instref inst+49>
+// CHECK:STDOUT:   .n = <unexpected>.inst+49.loc37_8
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";

+ 2 - 2
toolchain/check/testdata/impl/fail_extend_impl_forall.carbon

@@ -58,7 +58,7 @@ class C {
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @GenericInterface
 // CHECK:STDOUT:     generic [file.%T.loc11_28.2: type] {
-// CHECK:STDOUT:   %Self: <unexpected instref inst+22> (%.2) = bind_symbolic_name Self 1 [symbolic = %Self (constants.%Self)]
+// CHECK:STDOUT:   %Self: <unexpected>.inst+22 (%.2) = bind_symbolic_name Self 1 [symbolic = %Self (constants.%Self)]
 // CHECK:STDOUT:   %F.decl: %F.type.1 = fn_decl @F.1 [template = constants.%F.1] {
 // CHECK:STDOUT:     %T.ref: type = name_ref T, file.%T.loc11_28.2 [symbolic = %T.ref (constants.%T)]
 // CHECK:STDOUT:     %x.loc12_8.1: @GenericInterface.%T.ref (%T) = param x
@@ -102,7 +102,7 @@ class C {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.1(@GenericInterface.%x.loc12_8.2: @GenericInterface.%T.ref (%T))
-// CHECK:STDOUT:     generic [file.%T.loc11_28.2: type, @GenericInterface.%Self: <unexpected instref inst+22> (%.2)];
+// CHECK:STDOUT:     generic [file.%T.loc11_28.2: type, @GenericInterface.%Self: <unexpected>.inst+22 (%.2)];
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.2(@impl.%x.loc20_10.2: @impl.%T.ref (%T))
 // CHECK:STDOUT:     generic [@C.%T.loc19_23.2: type] {

+ 1 - 1
toolchain/check/testdata/impl/fail_impl_bad_assoc_fn.carbon

@@ -899,7 +899,7 @@ class SelfNestedBadReturnType {
 // CHECK:STDOUT: declaration:
 // CHECK:STDOUT:   @SelfNested.%Self.ref.loc188_12 => constants.%Self.3
 // CHECK:STDOUT:   @SelfNested.%.loc188_16.3 => constants.%.10
-// CHECK:STDOUT:   <unexpected instref inst+267> => <unexpected instref inst+268>
+// CHECK:STDOUT:   <unexpected>.inst+267.loc188_21 => <unexpected>.inst+268
 // CHECK:STDOUT:   @SelfNested.%.loc188_37 => constants.%.11
 // CHECK:STDOUT:   @SelfNested.%.loc188_38.2 => constants.%.13
 // CHECK:STDOUT:   @SelfNested.%.loc188_52 => constants.%.15

+ 1 - 1
toolchain/check/testdata/impl/fail_redefinition.carbon

@@ -68,7 +68,7 @@ impl i32 as I {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: impl @impl: i32 as %.1 {
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   witness = <unexpected instref inst+18>
+// CHECK:STDOUT:   witness = <unexpected>.inst+18
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";

+ 1 - 1
toolchain/check/testdata/impl/no_prelude/self_in_signature.carbon

@@ -284,7 +284,7 @@ impl D as SelfNested {
 // CHECK:STDOUT: declaration:
 // CHECK:STDOUT:   @SelfNested.%Self.ref.loc28_12 => constants.%Self.2
 // CHECK:STDOUT:   @SelfNested.%.loc28_16.3 => constants.%.10
-// CHECK:STDOUT:   <unexpected instref inst+86> => <unexpected instref inst+87>
+// CHECK:STDOUT:   <unexpected>.inst+86.loc28_21 => <unexpected>.inst+87
 // CHECK:STDOUT:   @SelfNested.%.loc28_36 => constants.%.11
 // CHECK:STDOUT:   @SelfNested.%.loc28_37.2 => constants.%.13
 // CHECK:STDOUT: }

+ 1 - 1
toolchain/check/testdata/interface/no_prelude/fail_assoc_const_not_binding.carbon

@@ -28,7 +28,7 @@ interface I {
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @I {
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = <unexpected instref inst+3>
+// CHECK:STDOUT:   .Self = <unexpected>.inst+3
 // CHECK:STDOUT:   witness = invalid
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 1 - 1
toolchain/check/testdata/interface/no_prelude/fail_assoc_const_template.carbon

@@ -26,7 +26,7 @@ interface I {
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @I {
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = <unexpected instref inst+3>
+// CHECK:STDOUT:   .Self = <unexpected>.inst+3
 // CHECK:STDOUT:   witness = invalid
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 1 - 1
toolchain/check/testdata/interface/no_prelude/fail_duplicate.carbon

@@ -76,7 +76,7 @@ interface Class { }
 // CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [template = constants.%F] {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = <unexpected instref inst+3>
+// CHECK:STDOUT:   .Self = <unexpected>.inst+3
 // CHECK:STDOUT:   .F = <error>
 // CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }

+ 2 - 2
toolchain/check/testdata/interface/no_prelude/fail_generic_redeclaration.carbon

@@ -92,7 +92,7 @@ interface DifferentParams(T:! ()) {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @.1
 // CHECK:STDOUT:     generic [file.%T.loc19_22.2: type] {
-// CHECK:STDOUT:   %Self: <unexpected instref inst+13> (%.4) = bind_symbolic_name Self 1 [symbolic = %Self (constants.%Self.1)]
+// CHECK:STDOUT:   %Self: <unexpected>.inst+13 (%.4) = bind_symbolic_name Self 1 [symbolic = %Self (constants.%Self.1)]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = %Self
@@ -115,7 +115,7 @@ interface DifferentParams(T:! ()) {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @.3
 // CHECK:STDOUT:     generic [file.%T.loc38_27.2: %.2] {
-// CHECK:STDOUT:   %Self: <unexpected instref inst+39> (%.7) = bind_symbolic_name Self 1 [symbolic = %Self (constants.%Self.3)]
+// CHECK:STDOUT:   %Self: <unexpected>.inst+39 (%.7) = bind_symbolic_name Self 1 [symbolic = %Self (constants.%Self.3)]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = %Self

+ 9 - 9
toolchain/check/testdata/interface/no_prelude/fail_todo_generic_default_fn.carbon

@@ -34,7 +34,7 @@ fn I(T:! type).F[self: Self]() -> Self { return self; }
 // CHECK:STDOUT:   %F: %F.type = struct_value () [template]
 // CHECK:STDOUT:   %.3: type = assoc_entity_type @I, %F.type [template]
 // CHECK:STDOUT:   %.4: %.3 = assoc_entity element0, @I.%F.decl [template]
-// CHECK:STDOUT:   %Self.2: <unexpected instref inst+28> (%.2) = bind_symbolic_name Self 1 [symbolic]
+// CHECK:STDOUT:   %Self.2: <unexpected>.inst+28 (%.2) = bind_symbolic_name Self 1 [symbolic]
 // CHECK:STDOUT:   %.type: type = fn_type @.1 [template]
 // CHECK:STDOUT:   %.5: %.type = struct_value () [template]
 // CHECK:STDOUT: }
@@ -50,14 +50,14 @@ fn I(T:! type).F[self: Self]() -> Self { return self; }
 // CHECK:STDOUT:   %.decl: %.type = fn_decl @.1 [template = constants.%.5] {
 // CHECK:STDOUT:     %T.loc22_6.1: type = param T
 // CHECK:STDOUT:     %T.loc22_6.2: type = bind_symbolic_name T 0, %T.loc22_6.1 [symbolic = %T.loc22_6.2 (constants.%T)]
-// CHECK:STDOUT:     %.loc22_24.1: <unexpected instref inst+44> (%.2) = specific_constant @I.%Self, %I.decl(constants.%T) [symbolic = %.loc22_24.1 (constants.%Self.2)]
-// CHECK:STDOUT:     %Self.ref.loc22_24: <unexpected instref inst+44> (%.2) = name_ref Self, %.loc22_24.1 [symbolic = %.loc22_24.1 (constants.%Self.2)]
+// CHECK:STDOUT:     %.loc22_24.1: <unexpected>.inst+44 (%.2) = specific_constant @I.%Self, %I.decl(constants.%T) [symbolic = %.loc22_24.1 (constants.%Self.2)]
+// CHECK:STDOUT:     %Self.ref.loc22_24: <unexpected>.inst+44 (%.2) = name_ref Self, %.loc22_24.1 [symbolic = %.loc22_24.1 (constants.%Self.2)]
 // CHECK:STDOUT:     %.loc22_24.2: type = facet_type_access %Self.ref.loc22_24 [symbolic = %.loc22_24.1 (constants.%Self.2)]
 // CHECK:STDOUT:     %.loc22_24.3: type = converted %Self.ref.loc22_24, %.loc22_24.2 [symbolic = %.loc22_24.1 (constants.%Self.2)]
 // CHECK:STDOUT:     %self.loc22_18.1: file.%.loc22_24.1 (%Self.2) = param self
 // CHECK:STDOUT:     @.1.%self: file.%.loc22_24.1 (%Self.2) = bind_name self, %self.loc22_18.1
-// CHECK:STDOUT:     %.loc22_35.1: <unexpected instref inst+44> (%.2) = specific_constant @I.%Self, %I.decl(constants.%T) [symbolic = %.loc22_24.1 (constants.%Self.2)]
-// CHECK:STDOUT:     %Self.ref.loc22_35: <unexpected instref inst+44> (%.2) = name_ref Self, %.loc22_35.1 [symbolic = %.loc22_24.1 (constants.%Self.2)]
+// CHECK:STDOUT:     %.loc22_35.1: <unexpected>.inst+44 (%.2) = specific_constant @I.%Self, %I.decl(constants.%T) [symbolic = %.loc22_24.1 (constants.%Self.2)]
+// CHECK:STDOUT:     %Self.ref.loc22_35: <unexpected>.inst+44 (%.2) = name_ref Self, %.loc22_35.1 [symbolic = %.loc22_24.1 (constants.%Self.2)]
 // CHECK:STDOUT:     %.loc22_35.2: type = facet_type_access %Self.ref.loc22_35 [symbolic = %.loc22_24.1 (constants.%Self.2)]
 // CHECK:STDOUT:     %.loc22_35.3: type = converted %Self.ref.loc22_35, %.loc22_35.2 [symbolic = %.loc22_24.1 (constants.%Self.2)]
 // CHECK:STDOUT:     @.1.%return: ref %Self.2 = var <return slot>
@@ -66,7 +66,7 @@ fn I(T:! type).F[self: Self]() -> Self { return self; }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @I
 // CHECK:STDOUT:     generic [file.%T.loc11_13.2: type] {
-// CHECK:STDOUT:   %Self: <unexpected instref inst+28> (%.2) = bind_symbolic_name Self 1 [symbolic = %Self (constants.%Self.1)]
+// CHECK:STDOUT:   %Self: <unexpected>.inst+28 (%.2) = bind_symbolic_name Self 1 [symbolic = %Self (constants.%Self.1)]
 // CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [template = constants.%F] {
 // CHECK:STDOUT:     %.loc13_14.1: %.2 = specific_constant %Self, file.%I.decl(constants.%T) [symbolic = %.loc13_14.1 (constants.%Self.1)]
 // CHECK:STDOUT:     %Self.ref.loc13_14: %.2 = name_ref Self, %.loc13_14.1 [symbolic = %.loc13_14.1 (constants.%Self.1)]
@@ -89,7 +89,7 @@ fn I(T:! type).F[self: Self]() -> Self { return self; }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F[@I.%self.loc13_8.2: @I.%.loc13_14.1 (%Self.1)]() -> %Self.1
-// CHECK:STDOUT:     generic [file.%T.loc11_13.2: type, @I.%Self: <unexpected instref inst+28> (%.2)];
+// CHECK:STDOUT:     generic [file.%T.loc11_13.2: type, @I.%Self: <unexpected>.inst+28 (%.2)];
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @.1[%self: file.%.loc22_24.1 (%Self.2)]() -> %Self.2
 // CHECK:STDOUT:     generic [file.%T.loc22_6.2: type] {
@@ -103,7 +103,7 @@ fn I(T:! type).F[self: Self]() -> Self { return self; }
 // CHECK:STDOUT:   file.%T.loc11_13.2 => constants.%T
 // CHECK:STDOUT:
 // CHECK:STDOUT: definition:
-// CHECK:STDOUT:   <unexpected instref inst+28> => constants.%.2
+// CHECK:STDOUT:   <unexpected>.inst+28 => constants.%.2
 // CHECK:STDOUT:   @I.%Self => constants.%Self.2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -125,7 +125,7 @@ fn I(T:! type).F[self: Self]() -> Self { return self; }
 // CHECK:STDOUT: specific file.%.decl(constants.%T) {
 // CHECK:STDOUT: declaration:
 // CHECK:STDOUT:   file.%T.loc22_6.2 => constants.%T
-// CHECK:STDOUT:   <unexpected instref inst+44> => constants.%.2
+// CHECK:STDOUT:   <unexpected>.inst+44 => constants.%.2
 // CHECK:STDOUT:   file.%.loc22_24.1 => constants.%Self.2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 4 - 4
toolchain/check/testdata/interface/no_prelude/generic.carbon

@@ -145,7 +145,7 @@ fn G(T:! Generic(B)) {
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Simple
 // CHECK:STDOUT:     generic [file.%T.loc4_18.2: type] {
-// CHECK:STDOUT:   %Self: <unexpected instref inst+11> (%.2) = bind_symbolic_name Self 1 [symbolic = %Self (constants.%Self.1)]
+// CHECK:STDOUT:   %Self: <unexpected>.inst+11 (%.2) = bind_symbolic_name Self 1 [symbolic = %Self (constants.%Self.1)]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = %Self
@@ -154,7 +154,7 @@ fn G(T:! Generic(B)) {
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @WithAssocFn
 // CHECK:STDOUT:     generic [file.%T.loc8_23.2: type] {
-// CHECK:STDOUT:   %Self: <unexpected instref inst+31> (%.4) = bind_symbolic_name Self 1 [symbolic = %Self (constants.%Self.2)]
+// CHECK:STDOUT:   %Self: <unexpected>.inst+31 (%.4) = bind_symbolic_name Self 1 [symbolic = %Self (constants.%Self.2)]
 // CHECK:STDOUT:   %F.decl: %F.type.1 = fn_decl @F.1 [template = constants.%F.1] {
 // CHECK:STDOUT:     %X.ref: type = name_ref X, file.%X.decl [template = constants.%X]
 // CHECK:STDOUT:     %return.var: ref %X = var <return slot>
@@ -215,7 +215,7 @@ fn G(T:! Generic(B)) {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.1() -> %X
-// CHECK:STDOUT:     generic [file.%T.loc8_23.2: type, @WithAssocFn.%Self: <unexpected instref inst+31> (%.4)];
+// CHECK:STDOUT:     generic [file.%T.loc8_23.2: type, @WithAssocFn.%Self: <unexpected>.inst+31 (%.4)];
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.2() -> @impl.2.%return.var: %X {
 // CHECK:STDOUT: !entry:
@@ -345,7 +345,7 @@ fn G(T:! Generic(B)) {
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Generic
 // CHECK:STDOUT:     generic [file.%T.loc4_19.2: type] {
-// CHECK:STDOUT:   %Self: <unexpected instref inst+11> (%.2) = bind_symbolic_name Self 1 [symbolic = %Self (constants.%Self)]
+// CHECK:STDOUT:   %Self: <unexpected>.inst+11 (%.2) = bind_symbolic_name Self 1 [symbolic = %Self (constants.%Self)]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = %Self

+ 3 - 3
toolchain/check/testdata/interface/no_prelude/generic_import.carbon

@@ -54,7 +54,7 @@ impl C as AddWith(C) {
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @AddWith
 // CHECK:STDOUT:     generic [file.%T.loc4_19.2: type] {
-// CHECK:STDOUT:   %Self: <unexpected instref inst+17> (%.2) = bind_symbolic_name Self 1 [symbolic = %Self (constants.%Self)]
+// CHECK:STDOUT:   %Self: <unexpected>.inst+17 (%.2) = bind_symbolic_name Self 1 [symbolic = %Self (constants.%Self)]
 // CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [template = constants.%F] {}
 // CHECK:STDOUT:   %.loc5: %.3 = assoc_entity element0, %F.decl [template = constants.%.4]
 // CHECK:STDOUT:
@@ -65,7 +65,7 @@ impl C as AddWith(C) {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F()
-// CHECK:STDOUT:     generic [file.%T.loc4_19.2: type, @AddWith.%Self: <unexpected instref inst+17> (%.2)];
+// CHECK:STDOUT:     generic [file.%T.loc4_19.2: type, @AddWith.%Self: <unexpected>.inst+17 (%.2)];
 // CHECK:STDOUT:
 // CHECK:STDOUT: specific file.%AddWith.decl(constants.%T) {
 // CHECK:STDOUT: declaration:
@@ -89,7 +89,7 @@ impl C as AddWith(C) {
 // CHECK:STDOUT:   %AddWith.type: type = generic_interface_type @AddWith [template]
 // CHECK:STDOUT:   %.2: type = tuple_type () [template]
 // CHECK:STDOUT:   %AddWith: %AddWith.type = struct_value () [template]
-// CHECK:STDOUT:   %T: type = bind_symbolic_name T 0, <unexpected instref inst+14> [symbolic]
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T 0, <unexpected>.inst+14 [symbolic]
 // CHECK:STDOUT:   %.3: type = interface_type @AddWith, <invalid>(%T) [symbolic]
 // CHECK:STDOUT:   %Self: %.3 = bind_symbolic_name Self 1 [symbolic]
 // CHECK:STDOUT:   %.4: type = interface_type @AddWith, <invalid>(%C) [template]

+ 1 - 1
toolchain/check/testdata/operators/builtin/fail_and_or_not_in_function.carbon

@@ -66,7 +66,7 @@ var or_: F(true or true);
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   %.loc42_17: bool = block_arg <unexpected instblockref block21> [template = constants.%.3]
-// CHECK:STDOUT:   %F.call: init type = call <unexpected instref inst+60>(%.loc42_17)
+// CHECK:STDOUT:   %F.call: init type = call <unexpected>.inst+60.loc42_10(%.loc42_17)
 // CHECK:STDOUT:   %.loc42_24.1: type = value_of_initializer %F.call
 // CHECK:STDOUT:   %.loc42_24.2: type = converted %F.call, %.loc42_24.1
 // CHECK:STDOUT:   %or_.var: ref <error> = var or_

+ 1 - 1
toolchain/check/testdata/packages/no_prelude/fail_export_name_params.carbon

@@ -78,7 +78,7 @@ export C2(T:! type);
 // CHECK:STDOUT:   %C1.type: type = generic_class_type @C1 [template]
 // CHECK:STDOUT:   %.1: type = tuple_type () [template]
 // CHECK:STDOUT:   %C1.1: %C1.type = struct_value () [template]
-// CHECK:STDOUT:   %T: type = bind_symbolic_name T 0, <unexpected instref inst+18> [symbolic]
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T 0, <unexpected>.inst+18 [symbolic]
 // CHECK:STDOUT:   %C1.2: type = class_type @C1, <invalid>(%T) [symbolic]
 // CHECK:STDOUT:   %C2.type: type = generic_class_type @C2 [template]
 // CHECK:STDOUT:   %C2.1: %C2.type = struct_value () [template]

+ 3 - 3
toolchain/check/testdata/return/fail_let_in_type.carbon

@@ -55,14 +55,14 @@ fn FirstPerfectNumber() -> z { return 6; }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @HalfDozen() -> %y
-// CHECK:STDOUT:     generic [<unexpected instref inst+21>: type] {
+// CHECK:STDOUT:     generic [<unexpected>.inst+21.loc19_5: type] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %.loc24: i32 = int_literal 6 [template = constants.%.2]
 // CHECK:STDOUT:   return <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: specific <unexpected instref inst+29>(constants.%y) {
+// CHECK:STDOUT: specific <unexpected>.inst+29.loc24_21(constants.%y) {
 // CHECK:STDOUT: declaration:
-// CHECK:STDOUT:   <unexpected instref inst+27> => constants.%y
+// CHECK:STDOUT:   <unexpected>.inst+27.loc24_19 => constants.%y
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 3 - 3
toolchain/check/testdata/struct/import.carbon

@@ -217,7 +217,7 @@ var c_bad: C({.a = 3, .b = 4}) = F();
 // CHECK:STDOUT:   %C.1: %C.type = struct_value () [template]
 // CHECK:STDOUT:   %.10: type = struct_type {} [template]
 // CHECK:STDOUT:   %.11: type = struct_type {.a: i32, .b: i32} [template]
-// CHECK:STDOUT:   %S: %.11 = bind_symbolic_name S 0, <unexpected instref inst+103> [symbolic]
+// CHECK:STDOUT:   %S: %.11 = bind_symbolic_name S 0, <unexpected>.inst+103 [symbolic]
 // CHECK:STDOUT:   %C.2: type = class_type @C, <invalid>(%S) [symbolic]
 // CHECK:STDOUT:   %.12: i32 = int_literal 1 [template]
 // CHECK:STDOUT:   %.13: i32 = int_literal 2 [template]
@@ -349,7 +349,7 @@ var c_bad: C({.a = 3, .b = 4}) = F();
 // CHECK:STDOUT:   %C.1: %C.type = struct_value () [template]
 // CHECK:STDOUT:   %.2: type = struct_type {} [template]
 // CHECK:STDOUT:   %.3: type = struct_type {.a: i32, .b: i32} [template]
-// CHECK:STDOUT:   %S: %.3 = bind_symbolic_name S 0, <unexpected instref inst+21> [symbolic]
+// CHECK:STDOUT:   %S: %.3 = bind_symbolic_name S 0, <unexpected>.inst+21 [symbolic]
 // CHECK:STDOUT:   %C.2: type = class_type @C, <invalid>(%S) [symbolic]
 // CHECK:STDOUT:   %.4: i32 = int_literal 1 [template]
 // CHECK:STDOUT:   %.5: i32 = int_literal 2 [template]
@@ -422,7 +422,7 @@ var c_bad: C({.a = 3, .b = 4}) = F();
 // CHECK:STDOUT:   %C.1: %C.type = struct_value () [template]
 // CHECK:STDOUT:   %.2: type = struct_type {} [template]
 // CHECK:STDOUT:   %.3: type = struct_type {.a: i32, .b: i32} [template]
-// CHECK:STDOUT:   %S: %.3 = bind_symbolic_name S 0, <unexpected instref inst+21> [symbolic]
+// CHECK:STDOUT:   %S: %.3 = bind_symbolic_name S 0, <unexpected>.inst+21 [symbolic]
 // CHECK:STDOUT:   %C.2: type = class_type @C, <invalid>(%S) [symbolic]
 // CHECK:STDOUT:   %.4: i32 = int_literal 3 [template]
 // CHECK:STDOUT:   %.5: i32 = int_literal 4 [template]

+ 3 - 3
toolchain/check/testdata/tuples/import.carbon

@@ -242,7 +242,7 @@ var c_bad: C((3, 4)) = F();
 // CHECK:STDOUT:   %C.type: type = generic_class_type @C [template]
 // CHECK:STDOUT:   %C.1: %C.type = struct_value () [template]
 // CHECK:STDOUT:   %.14: type = struct_type {} [template]
-// CHECK:STDOUT:   %X: %.8 = bind_symbolic_name X 0, <unexpected instref inst+105> [symbolic]
+// CHECK:STDOUT:   %X: %.8 = bind_symbolic_name X 0, <unexpected>.inst+105 [symbolic]
 // CHECK:STDOUT:   %C.2: type = class_type @C, <invalid>(%X) [symbolic]
 // CHECK:STDOUT:   %.15: i32 = int_literal 1 [template]
 // CHECK:STDOUT:   %.16: i32 = int_literal 2 [template]
@@ -390,7 +390,7 @@ var c_bad: C((3, 4)) = F();
 // CHECK:STDOUT:   %C.1: %C.type = struct_value () [template]
 // CHECK:STDOUT:   %.2: type = struct_type {} [template]
 // CHECK:STDOUT:   %.3: type = tuple_type (i32, i32) [template]
-// CHECK:STDOUT:   %X: %.3 = bind_symbolic_name X 0, <unexpected instref inst+17> [symbolic]
+// CHECK:STDOUT:   %X: %.3 = bind_symbolic_name X 0, <unexpected>.inst+17 [symbolic]
 // CHECK:STDOUT:   %C.2: type = class_type @C, <invalid>(%X) [symbolic]
 // CHECK:STDOUT:   %.4: i32 = int_literal 1 [template]
 // CHECK:STDOUT:   %.5: i32 = int_literal 2 [template]
@@ -465,7 +465,7 @@ var c_bad: C((3, 4)) = F();
 // CHECK:STDOUT:   %C.1: %C.type = struct_value () [template]
 // CHECK:STDOUT:   %.2: type = struct_type {} [template]
 // CHECK:STDOUT:   %.3: type = tuple_type (i32, i32) [template]
-// CHECK:STDOUT:   %X: %.3 = bind_symbolic_name X 0, <unexpected instref inst+17> [symbolic]
+// CHECK:STDOUT:   %X: %.3 = bind_symbolic_name X 0, <unexpected>.inst+17 [symbolic]
 // CHECK:STDOUT:   %C.2: type = class_type @C, <invalid>(%X) [symbolic]
 // CHECK:STDOUT:   %.4: i32 = int_literal 3 [template]
 // CHECK:STDOUT:   %.5: i32 = int_literal 4 [template]

+ 10 - 7
toolchain/driver/driver.cpp

@@ -641,13 +641,16 @@ class Driver::CompilationUnit {
       }
     }
 
-    if (vlog_stream_) {
-      CARBON_VLOG() << "*** SemIR::File ***\n";
-      SemIR::FormatFile(*tokens_, *parse_tree_, *sem_ir_, *vlog_stream_);
-    }
-    if (options_.dump_sem_ir && IncludeInDumps()) {
-      SemIR::FormatFile(*tokens_, *parse_tree_, *sem_ir_,
-                        driver_->output_stream_);
+    bool print = options_.dump_sem_ir && IncludeInDumps();
+    if (vlog_stream_ || print) {
+      SemIR::Formatter formatter(*tokens_, *parse_tree_, *sem_ir_);
+      if (vlog_stream_) {
+        CARBON_VLOG() << "*** SemIR::File ***\n";
+        formatter.Print(*vlog_stream_);
+      }
+      if (print) {
+        formatter.Print(driver_->output_stream_);
+      }
     }
     if (sem_ir_->has_errors()) {
       success_ = false;

+ 76 - 39
toolchain/sem_ir/formatter.cpp

@@ -22,16 +22,11 @@
 namespace Carbon::SemIR {
 
 // Formatter for printing textual Semantics IR.
-class Formatter {
+class FormatterImpl {
  public:
-  enum class AddSpace : bool { Before, After };
-
-  explicit Formatter(const Lex::TokenizedBuffer& tokenized_buffer,
-                     const Parse::Tree& parse_tree, const File& sem_ir,
-                     llvm::raw_ostream& out)
-      : sem_ir_(sem_ir),
-        out_(out),
-        inst_namer_(tokenized_buffer, parse_tree, sem_ir) {}
+  explicit FormatterImpl(const File& sem_ir, InstNamer* inst_namer,
+                         llvm::raw_ostream& out, int indent)
+      : sem_ir_(sem_ir), inst_namer_(inst_namer), out_(out), indent_(indent) {}
 
   // Prints the SemIR.
   //
@@ -44,7 +39,7 @@ class Formatter {
     FormatConstants();
     FormatImportRefs();
 
-    out_ << inst_namer_.GetScopeName(InstNamer::ScopeId::File) << " ";
+    out_ << inst_namer_->GetScopeName(InstNamer::ScopeId::File) << " ";
     OpenBrace();
 
     // TODO: Handle the case where there are multiple top-level instruction
@@ -82,6 +77,37 @@ class Formatter {
     out_ << "\n";
   }
 
+  // Prints a code block.
+  auto FormatPartialTrailingCodeBlock(llvm::ArrayRef<SemIR::InstId> block)
+      -> void {
+    out_ << ' ';
+    OpenBrace();
+    constexpr int NumPrintedOnSkip = 9;
+    // Avoid only skipping one item.
+    if (block.size() > NumPrintedOnSkip + 1) {
+      Indent();
+      out_ << "... skipping " << (block.size() - NumPrintedOnSkip)
+           << " insts ...\n";
+      block = block.take_back(NumPrintedOnSkip);
+    }
+    FormatCodeBlock(block);
+    CloseBrace();
+  }
+
+  // Prints a single instruction.
+  auto FormatInstruction(InstId inst_id) -> void {
+    if (!inst_id.is_valid()) {
+      Indent();
+      out_ << "invalid\n";
+      return;
+    }
+
+    FormatInstruction(inst_id, sem_ir_.insts().Get(inst_id));
+  }
+
+ private:
+  enum class AddSpace : bool { Before, After };
+
   // 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 {
@@ -139,7 +165,7 @@ class Formatter {
     }
 
     llvm::SaveAndRestore constants_scope(scope_, InstNamer::ScopeId::Constants);
-    out_ << inst_namer_.GetScopeName(InstNamer::ScopeId::Constants) << " ";
+    out_ << inst_namer_->GetScopeName(InstNamer::ScopeId::Constants) << " ";
     OpenBrace();
     FormatCodeBlock(sem_ir_.constants().array_ref());
     CloseBrace();
@@ -153,7 +179,7 @@ class Formatter {
     }
 
     llvm::SaveAndRestore scope(scope_, InstNamer::ScopeId::ImportRefs);
-    out_ << inst_namer_.GetScopeName(InstNamer::ScopeId::ImportRefs) << " ";
+    out_ << inst_namer_->GetScopeName(InstNamer::ScopeId::ImportRefs) << " ";
     OpenBrace();
     FormatCodeBlock(import_refs);
     CloseBrace();
@@ -170,7 +196,7 @@ class Formatter {
       FormatGeneric(class_info.generic_id);
     }
 
-    llvm::SaveAndRestore class_scope(scope_, inst_namer_.GetScopeFor(id));
+    llvm::SaveAndRestore class_scope(scope_, inst_namer_->GetScopeFor(id));
 
     if (class_info.scope_id.is_valid()) {
       out_ << ' ';
@@ -194,7 +220,7 @@ class Formatter {
       FormatGeneric(interface_info.generic_id);
     }
 
-    llvm::SaveAndRestore interface_scope(scope_, inst_namer_.GetScopeFor(id));
+    llvm::SaveAndRestore interface_scope(scope_, inst_namer_->GetScopeFor(id));
 
     if (interface_info.scope_id.is_valid()) {
       out_ << ' ';
@@ -230,7 +256,7 @@ class Formatter {
     out_ << " as ";
     FormatType(impl_info.constraint_id);
 
-    llvm::SaveAndRestore impl_scope(scope_, inst_namer_.GetScopeFor(id));
+    llvm::SaveAndRestore impl_scope(scope_, inst_namer_->GetScopeFor(id));
 
     if (impl_info.scope_id.is_valid()) {
       out_ << ' ';
@@ -267,7 +293,7 @@ class Formatter {
     out_ << "fn ";
     FormatFunctionName(id);
 
-    llvm::SaveAndRestore function_scope(scope_, inst_namer_.GetScopeFor(id));
+    llvm::SaveAndRestore function_scope(scope_, inst_namer_->GetScopeFor(id));
 
     if (fn.implicit_param_refs_id.is_valid()) {
       out_ << "[";
@@ -462,16 +488,6 @@ class Formatter {
     }
   }
 
-  auto FormatInstruction(InstId inst_id) -> void {
-    if (!inst_id.is_valid()) {
-      Indent();
-      out_ << "invalid\n";
-      return;
-    }
-
-    FormatInstruction(inst_id, sem_ir_.insts().Get(inst_id));
-  }
-
   auto FormatInstruction(InstId inst_id, Inst inst) -> void {
     CARBON_KIND_SWITCH(inst) {
 #define CARBON_SEM_IR_INST_KIND(InstT)      \
@@ -909,26 +925,28 @@ class Formatter {
   }
 
   auto FormatInstName(InstId id) -> void {
-    out_ << inst_namer_.GetNameFor(scope_, id);
+    out_ << inst_namer_->GetNameFor(scope_, id);
   }
 
   auto FormatLabel(InstBlockId id) -> void {
-    out_ << inst_namer_.GetLabelFor(scope_, id);
+    out_ << inst_namer_->GetLabelFor(scope_, id);
   }
 
   auto FormatFunctionName(FunctionId id) -> void {
-    out_ << inst_namer_.GetNameFor(id);
+    out_ << inst_namer_->GetNameFor(id);
   }
 
   auto FormatClassName(ClassId id) -> void {
-    out_ << inst_namer_.GetNameFor(id);
+    out_ << inst_namer_->GetNameFor(id);
   }
 
   auto FormatInterfaceName(InterfaceId id) -> void {
-    out_ << inst_namer_.GetNameFor(id);
+    out_ << inst_namer_->GetNameFor(id);
   }
 
-  auto FormatImplName(ImplId id) -> void { out_ << inst_namer_.GetNameFor(id); }
+  auto FormatImplName(ImplId id) -> void {
+    out_ << inst_namer_->GetNameFor(id);
+  }
 
   auto FormatSpecificName(GenericInstanceId id) -> void {
     const auto& specific = sem_ir_.generic_instances().Get(id);
@@ -982,10 +1000,11 @@ class Formatter {
     }
   }
 
- private:
   const File& sem_ir_;
+  InstNamer* const inst_namer_;
+
+  // The output stream. Set while formatting instructions.
   llvm::raw_ostream& out_;
-  InstNamer inst_namer_;
 
   // The current scope that we are formatting within. References to names in
   // this scope will not have a `@scope.` prefix added.
@@ -997,7 +1016,7 @@ class Formatter {
   bool in_terminator_sequence_ = false;
 
   // The indent depth to use for new instructions.
-  int indent_ = 0;
+  int indent_;
 
   // Whether we are currently formatting immediately after an open brace. If so,
   // a newline will be inserted before the next line indent.
@@ -1014,10 +1033,28 @@ class Formatter {
   bool pending_constant_value_is_self_ = false;
 };
 
-auto FormatFile(const Lex::TokenizedBuffer& tokenized_buffer,
-                const Parse::Tree& parse_tree, const File& sem_ir,
-                llvm::raw_ostream& out) -> void {
-  Formatter(tokenized_buffer, parse_tree, sem_ir, out).Format();
+Formatter::Formatter(const Lex::TokenizedBuffer& tokenized_buffer,
+                     const Parse::Tree& parse_tree, const File& sem_ir)
+    : sem_ir_(sem_ir), inst_namer_(tokenized_buffer, parse_tree, sem_ir) {}
+
+Formatter::~Formatter() = default;
+
+auto Formatter::Print(llvm::raw_ostream& out) -> void {
+  FormatterImpl formatter(sem_ir_, &inst_namer_, out, /*indent=*/0);
+  formatter.Format();
+}
+
+auto Formatter::PrintPartialTrailingCodeBlock(
+    llvm::ArrayRef<SemIR::InstId> block, int indent, llvm::raw_ostream& out)
+    -> void {
+  FormatterImpl formatter(sem_ir_, &inst_namer_, out, indent);
+  formatter.FormatPartialTrailingCodeBlock(block);
+}
+
+auto Formatter::PrintInst(SemIR::InstId inst_id, int indent,
+                          llvm::raw_ostream& out) -> void {
+  FormatterImpl formatter(sem_ir_, &inst_namer_, out, indent);
+  formatter.FormatInstruction(inst_id);
 }
 
 }  // namespace Carbon::SemIR

+ 24 - 3
toolchain/sem_ir/formatter.h

@@ -9,12 +9,33 @@
 #include "toolchain/lex/tokenized_buffer.h"
 #include "toolchain/parse/tree.h"
 #include "toolchain/sem_ir/file.h"
+#include "toolchain/sem_ir/inst_namer.h"
 
 namespace Carbon::SemIR {
 
-auto FormatFile(const Lex::TokenizedBuffer& tokenized_buffer,
-                const Parse::Tree& parse_tree, const File& sem_ir,
-                llvm::raw_ostream& out) -> void;
+// Formatter for printing textual Semantics IR.
+class Formatter {
+ public:
+  explicit Formatter(const Lex::TokenizedBuffer& tokenized_buffer,
+                     const Parse::Tree& parse_tree, const File& sem_ir);
+  ~Formatter();
+
+  // Prints the full IR.
+  auto Print(llvm::raw_ostream& out) -> void;
+  // Prints a single code block. Only prints the last several instructions of
+  // large blocks.
+  auto PrintPartialTrailingCodeBlock(llvm::ArrayRef<SemIR::InstId> block,
+                                     int indent, llvm::raw_ostream& out)
+      -> void;
+  // Prints a single instruction.
+  auto PrintInst(SemIR::InstId inst_id, int indent, llvm::raw_ostream& out)
+      -> void;
+
+ private:
+  const File& sem_ir_;
+  // Caches naming between Print calls.
+  InstNamer inst_namer_;
+};
 
 }  // namespace Carbon::SemIR
 

+ 9 - 1
toolchain/sem_ir/inst_namer.cpp

@@ -152,7 +152,15 @@ auto InstNamer::GetNameFor(ScopeId scope_id, InstId inst_id) const
   if (!inst_name) {
     // This should not happen in valid IR.
     std::string str;
-    llvm::raw_string_ostream(str) << "<unexpected instref " << inst_id << ">";
+    llvm::raw_string_ostream str_stream(str);
+    str_stream << "<unexpected>." << inst_id;
+    auto loc_id = sem_ir_.insts().GetLocId(inst_id);
+    // TODO: Consider handling inst_id cases.
+    if (loc_id.is_node_id()) {
+      auto token = parse_tree_.node_token(loc_id.node_id());
+      str_stream << ".loc" << tokenized_buffer_.GetLineNumber(token) << "_"
+                 << tokenized_buffer_.GetColumnNumber(token);
+    }
     return str;
   }
   if (inst_scope == scope_id) {