فهرست منبع

Refactor YAML handling to use the llvm::yaml API. (#3337)

Provides an adapter for the llvm::yaml API because it otherwise needs a
bunch of const/non-const definitions, and the traits are difficult to
diagnose issues with. The current approach is pretty simple to use, even
if it's not super efficient (which, yaml output is more of a debugging
thing so I'm not really expecting it to be an issue).

Changes the format of yaml output to provide more index information,
just as reminders when seeing something like `node+0`. Note this would
create more churn in deltas if we were reliant on the output yaml in
tests, but we aren't so it should be okay.
Jon Ross-Perkins 2 سال پیش
والد
کامیت
3af7eb2672

+ 10 - 0
common/ostream.h

@@ -6,6 +6,7 @@
 #define CARBON_COMMON_OSTREAM_H_
 #define CARBON_COMMON_OSTREAM_H_
 
 
 #include <ostream>
 #include <ostream>
+#include <type_traits>
 
 
 #include "llvm/Support/raw_os_ostream.h"
 #include "llvm/Support/raw_os_ostream.h"
 // Libraries should include this header instead of raw_ostream.
 // Libraries should include this header instead of raw_ostream.
@@ -50,6 +51,15 @@ class Printable {
   }
   }
 };
 };
 
 
+// Returns the result of printing the value.
+template <typename T>
+inline auto PrintToString(const T& val) -> std::string {
+  std::string str;
+  llvm::raw_string_ostream stream(str);
+  stream << val;
+  return str;
+}
+
 }  // namespace Carbon
 }  // namespace Carbon
 
 
 namespace llvm {
 namespace llvm {

+ 11 - 0
toolchain/base/BUILD

@@ -28,6 +28,7 @@ cc_library(
     hdrs = ["value_store.h"],
     hdrs = ["value_store.h"],
     deps = [
     deps = [
         ":index_base",
         ":index_base",
+        ":yaml",
         "//common:check",
         "//common:check",
         "//common:ostream",
         "//common:ostream",
         "@llvm-project//llvm:Support",
         "@llvm-project//llvm:Support",
@@ -46,3 +47,13 @@ cc_test(
         "@com_google_googletest//:gtest",
         "@com_google_googletest//:gtest",
     ],
     ],
 )
 )
+
+cc_library(
+    name = "yaml",
+    hdrs = ["yaml.h"],
+    deps = [
+        "//common:check",
+        "//common:ostream",
+        "@llvm-project//llvm:Support",
+    ],
+)

+ 27 - 88
toolchain/base/value_store.h

@@ -12,10 +12,12 @@
 #include "llvm/ADT/APInt.h"
 #include "llvm/ADT/APInt.h"
 #include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/STLFunctionalExtras.h"
 #include "llvm/ADT/STLFunctionalExtras.h"
+#include "llvm/ADT/Sequence.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/Support/YAMLParser.h"
 #include "llvm/Support/YAMLParser.h"
 #include "toolchain/base/index_base.h"
 #include "toolchain/base/index_base.h"
+#include "toolchain/base/yaml.h"
 
 
 namespace Carbon {
 namespace Carbon {
 
 
@@ -86,73 +88,14 @@ namespace Internal {
 // Used as a parent class for non-printable types. This is just for
 // Used as a parent class for non-printable types. This is just for
 // std::conditional, not as an API.
 // std::conditional, not as an API.
 class ValueStoreNotPrintable {};
 class ValueStoreNotPrintable {};
-
-// Provides YAML printing of a value.
-template <typename ValueT>
-inline auto PrintValue(llvm::raw_ostream& out, const ValueT& val) {
-  out << val;
-};
-inline auto PrintValue(llvm::raw_ostream& out, const llvm::APInt& val) {
-  val.print(out, /*isSigned=*/false);
-};
-inline auto PrintValue(llvm::raw_ostream& out, const llvm::StringRef& val) {
-  out << "\"" << llvm::yaml::escape(val) << "\"";
-};
-
-struct DefaultPrinter {
-  template <typename ValueT>
-  void operator()(llvm::raw_ostream& out, const ValueT& value) {
-    PrintValue(out, value);
-  }
-};
-
 }  // namespace Internal
 }  // namespace Internal
 
 
-// Provides YAML printing of a list. The first line indent applies even if there
-// is no label, in which case it applies to the first element.
-template <typename ValueT>
-inline auto PrintValueRange(
-    llvm::raw_ostream& out, llvm::iterator_range<const ValueT*> range,
-    std::optional<llvm::StringRef> label, int first_line_indent,
-    int later_indent, bool trailing_newline,
-    llvm::function_ref<void(llvm::raw_ostream&, const ValueT& val)> print =
-        Internal::DefaultPrinter()) {
-  out.indent(first_line_indent);
-  if (label) {
-    out << *label << ":";
-    if (range.empty()) {
-      // Add a space between the `:` and the `[]` printed below.
-      out << " ";
-    } else {
-      out << "\n";
-      out.indent(later_indent);
-    }
-  }
-  if (range.empty()) {
-    out << "[]";
-    if (trailing_newline) {
-      out << "\n";
-    }
-    return;
-  }
-  std::string sep_str = "\n";
-  sep_str.append(later_indent, ' ');
-  llvm::ListSeparator sep(sep_str);
-  for (const auto& val : range) {
-    out << sep << "- ";
-    print(out, val);
-  }
-  if (trailing_newline) {
-    out << "\n";
-  }
-}
-
 // A simple wrapper for accumulating values, providing IDs to later retrieve the
 // A simple wrapper for accumulating values, providing IDs to later retrieve the
 // value. This does not do deduplication.
 // value. This does not do deduplication.
 template <typename IdT, typename ValueT = typename IdT::IndexedType>
 template <typename IdT, typename ValueT = typename IdT::IndexedType>
 class ValueStore
 class ValueStore
     : public std::conditional<std::is_base_of_v<Printable<ValueT>, ValueT>,
     : public std::conditional<std::is_base_of_v<Printable<ValueT>, ValueT>,
-                              Printable<ValueStore<IdT, ValueT>>,
+                              Yaml::Printable<ValueStore<IdT, ValueT>>,
                               Internal::ValueStoreNotPrintable> {
                               Internal::ValueStoreNotPrintable> {
  public:
  public:
   using PrintFn =
   using PrintFn =
@@ -189,17 +132,13 @@ class ValueStore
   auto Reserve(size_t size) -> void { values_.reserve(size); }
   auto Reserve(size_t size) -> void { values_.reserve(size); }
 
 
   // These are to support printable structures, and are not guaranteed.
   // These are to support printable structures, and are not guaranteed.
-  auto Print(llvm::raw_ostream& out) const -> void {
-    Print(out, std::nullopt, 0, 0);
-  }
-  auto Print(llvm::raw_ostream& out, std::optional<llvm::StringRef> label,
-             int first_line_indent, int later_indent,
-             // This decays so that `const llvm::APInt` printing catches the
-             // specialization.
-             PrintFn print = Internal::DefaultPrinter()) const -> void {
-    PrintValueRange(out, llvm::iterator_range(values_), label,
-                    first_line_indent, later_indent, /*trailing_newline=*/true,
-                    print);
+  auto OutputYaml() const -> Yaml::OutputMapping {
+    return Yaml::OutputMapping([&](Yaml::OutputMapping::Map map) {
+      for (auto i : llvm::seq(values_.size())) {
+        auto id = IdT(i);
+        map.Add(PrintToString(id), Yaml::OutputScalar(Get(id)));
+      }
+    });
   }
   }
 
 
   auto array_ref() const -> llvm::ArrayRef<ValueT> { return values_; }
   auto array_ref() const -> llvm::ArrayRef<ValueT> { return values_; }
@@ -212,7 +151,7 @@ class ValueStore
 // Storage for StringRefs. The caller is responsible for ensuring storage is
 // Storage for StringRefs. The caller is responsible for ensuring storage is
 // allocated.
 // allocated.
 template <>
 template <>
-class ValueStore<StringId> : public Printable<ValueStore<StringId>> {
+class ValueStore<StringId> : public Yaml::Printable<ValueStore<StringId>> {
  public:
  public:
   using PrintFn =
   using PrintFn =
       llvm::function_ref<void(llvm::raw_ostream&, const llvm::StringRef& val)>;
       llvm::function_ref<void(llvm::raw_ostream&, const llvm::StringRef& val)>;
@@ -234,15 +173,12 @@ class ValueStore<StringId> : public Printable<ValueStore<StringId>> {
     return values_[id.index];
     return values_[id.index];
   }
   }
 
 
-  auto Print(llvm::raw_ostream& out) const -> void {
-    Print(out, std::nullopt, 0, 0);
-  }
-  auto Print(llvm::raw_ostream& out, std::optional<llvm::StringRef> label,
-             int first_line_indent, int later_indent,
-             PrintFn print = Internal::DefaultPrinter()) const -> void {
-    PrintValueRange(out, llvm::iterator_range(values_), label,
-                    first_line_indent, later_indent, /*trailing_newline=*/true,
-                    print);
+  auto OutputYaml() const -> Yaml::OutputMapping {
+    return Yaml::OutputMapping([&](Yaml::OutputMapping::Map map) {
+      for (auto [i, val] : llvm::enumerate(values_)) {
+        map.Add(PrintToString(StringId(i)), val);
+      }
+    });
   }
   }
 
 
  private:
  private:
@@ -252,7 +188,7 @@ class ValueStore<StringId> : public Printable<ValueStore<StringId>> {
 
 
 // Stores that will be used across compiler steps. This is provided mainly so
 // Stores that will be used across compiler steps. This is provided mainly so
 // that they don't need to be passed separately.
 // that they don't need to be passed separately.
-class SharedValueStores : public Printable<SharedValueStores> {
+class SharedValueStores : public Yaml::Printable<SharedValueStores> {
  public:
  public:
   auto integers() -> ValueStore<IntegerId>& { return integers_; }
   auto integers() -> ValueStore<IntegerId>& { return integers_; }
   auto integers() const -> const ValueStore<IntegerId>& { return integers_; }
   auto integers() const -> const ValueStore<IntegerId>& { return integers_; }
@@ -261,12 +197,15 @@ class SharedValueStores : public Printable<SharedValueStores> {
   auto strings() -> ValueStore<StringId>& { return strings_; }
   auto strings() -> ValueStore<StringId>& { return strings_; }
   auto strings() const -> const ValueStore<StringId>& { return strings_; }
   auto strings() const -> const ValueStore<StringId>& { return strings_; }
 
 
-  auto Print(llvm::raw_ostream& out) const -> void {
-    out << "shared_values:\n"
-        << "  - ";
-    integers_.Print(out, "integers", 0, 6);
-    reals_.Print(out, "reals", 4, 6);
-    strings_.Print(out, "strings", 4, 6);
+  auto OutputYaml() const -> Yaml::OutputMapping {
+    return Yaml::OutputMapping([&](Yaml::OutputMapping::Map map) {
+      map.Add("shared_values",
+              Yaml::OutputMapping([&](Yaml::OutputMapping::Map map) {
+                map.Add("integers", integers_.OutputYaml());
+                map.Add("reals", reals_.OutputYaml());
+                map.Add("strings", strings_.OutputYaml());
+              }));
+    });
   }
   }
 
 
  private:
  private:

+ 12 - 11
toolchain/base/value_store_test.cpp

@@ -78,14 +78,14 @@ TEST(ValueStore, String) {
   EXPECT_THAT(value_stores.strings().Add(b), Eq(b_id));
   EXPECT_THAT(value_stores.strings().Add(b), Eq(b_id));
 }
 }
 
 
-auto MatchSharedValues(testing::Matcher<Yaml::SequenceValue> integers,
-                       testing::Matcher<Yaml::SequenceValue> reals,
-                       testing::Matcher<Yaml::SequenceValue> strings) -> auto {
+auto MatchSharedValues(testing::Matcher<Yaml::MappingValue> integers,
+                       testing::Matcher<Yaml::MappingValue> reals,
+                       testing::Matcher<Yaml::MappingValue> strings) -> auto {
   return Yaml::IsYaml(Yaml::Sequence(ElementsAre(Yaml::Mapping(ElementsAre(Pair(
   return Yaml::IsYaml(Yaml::Sequence(ElementsAre(Yaml::Mapping(ElementsAre(Pair(
-      "shared_values", Yaml::Sequence(ElementsAre(Yaml::Mapping(ElementsAre(
-                           Pair("integers", Yaml::Sequence(integers)),
-                           Pair("reals", Yaml::Sequence(reals)),
-                           Pair("strings", Yaml::Sequence(strings))))))))))));
+      "shared_values",
+      Yaml::Mapping(ElementsAre(Pair("integers", Yaml::Mapping(integers)),
+                                Pair("reals", Yaml::Mapping(reals)),
+                                Pair("strings", Yaml::Mapping(strings))))))))));
 }
 }
 
 
 TEST(ValueStore, PrintEmpty) {
 TEST(ValueStore, PrintEmpty) {
@@ -106,10 +106,11 @@ TEST(ValueStore, PrintVals) {
   TestRawOstream out;
   TestRawOstream out;
   value_stores.Print(out);
   value_stores.Print(out);
 
 
-  EXPECT_THAT(Yaml::Value::FromText(out.TakeStr()),
-              MatchSharedValues(ElementsAre(Yaml::Scalar("8")),
-                                ElementsAre(Yaml::Scalar("8*10^8")),
-                                ElementsAre(Yaml::Scalar("foo'\"baz"))));
+  EXPECT_THAT(
+      Yaml::Value::FromText(out.TakeStr()),
+      MatchSharedValues(ElementsAre(Pair("int0", Yaml::Scalar("8"))),
+                        ElementsAre(Pair("real0", Yaml::Scalar("8*10^8"))),
+                        ElementsAre(Pair("str0", Yaml::Scalar("foo'\"baz")))));
 }
 }
 
 
 }  // namespace
 }  // namespace

+ 115 - 0
toolchain/base/yaml.h

@@ -0,0 +1,115 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef CARBON_TOOLCHAIN_BASE_YAML_H_
+#define CARBON_TOOLCHAIN_BASE_YAML_H_
+
+#include "common/check.h"
+#include "common/ostream.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/YAMLTraits.h"
+
+// This file provides adapters for outputting YAML using llvm::yaml's APIs. It
+// only supports output, not input. However, it addresses the mix of const and
+// non-const expectations of the llvm::yaml that make it difficult to otherwise
+// use the trait-based approach.
+
+namespace Carbon::Yaml {
+
+// Helper for printing YAML, to maintain a consistent configuration.
+template <typename T>
+inline auto Print(llvm::raw_ostream& out, T yaml) -> void {
+  llvm::yaml::Output yout(out, /*Ctxt=*/nullptr, /*WrapColumn=*/80);
+  yout << yaml;
+}
+
+// Similar to the standard Printable<T>, but relies on OutputYaml for printing.
+template <typename T>
+class Printable : public Carbon::Printable<T> {
+ public:
+  auto Print(llvm::raw_ostream& out) const -> void {
+    Carbon::Yaml::Print(out, static_cast<const T*>(this)->OutputYaml());
+  }
+};
+
+// Adapts a function for outputting YAML as a scalar. This currently assumes no
+// scalars passed through this should be quoted.
+class OutputScalar {
+ public:
+  template <typename T>
+  explicit OutputScalar(const T& val)
+      : output_([&](llvm::raw_ostream& out) -> void { out << val; }) {}
+
+  explicit OutputScalar(const llvm::APInt& val)
+      : output_([&](llvm::raw_ostream& out) -> void {
+          // Carbon's plain APInt storage is typically unsigned.
+          val.print(out, /*isSigned=*/false);
+        }) {}
+
+  explicit OutputScalar(std::function<void(llvm::raw_ostream&)> output)
+      : output_(std::move(output)) {}
+
+  auto Output(llvm::raw_ostream& out) const -> void { output_(out); }
+
+ private:
+  std::function<void(llvm::raw_ostream&)> output_;
+};
+
+// Adapts a function for outputting YAML as a mapping.
+class OutputMapping {
+ public:
+  class Map {
+   public:
+    explicit Map(llvm::yaml::IO& io) : io_(io) {}
+
+    // Maps a value. This mainly takes responsibility for copying the value,
+    // letting mapRequired take `&value`.
+    template <typename T>
+    auto Add(llvm::StringRef key, T value) -> void {
+      io_.mapRequired(key.data(), value);
+    }
+
+   private:
+    llvm::yaml::IO& io_;
+  };
+
+  explicit OutputMapping(std::function<void(OutputMapping::Map)> output)
+      : output_(std::move(output)) {}
+
+  auto Output(llvm::yaml::IO& io) -> void { output_(Map(io)); }
+
+ private:
+  std::function<void(OutputMapping::Map)> output_;
+};
+
+}  // namespace Carbon::Yaml
+
+// Link OutputScalar to the llvm::yaml::IO API.
+template <>
+struct llvm::yaml::ScalarTraits<Carbon::Yaml::OutputScalar> {
+  static auto output(const Carbon::Yaml::OutputScalar& value, void* /*ctxt*/,
+                     llvm::raw_ostream& out) -> void {
+    value.Output(out);
+  }
+  static auto input(StringRef /*scalar*/, void* /*ctxt*/,
+                    Carbon::Yaml::OutputScalar& /*value*/) -> StringRef {
+    CARBON_FATAL() << "Input is unsupported.";
+  }
+  static auto mustQuote(StringRef /*value*/) -> QuotingType {
+    return QuotingType::None;
+  }
+};
+static_assert(llvm::yaml::has_ScalarTraits<Carbon::Yaml::OutputScalar>::value);
+
+// Link OutputMapping to the llvm::yaml::IO API.
+template <>
+struct llvm::yaml::MappingTraits<Carbon::Yaml::OutputMapping> {
+  static auto mapping(IO& io, Carbon::Yaml::OutputMapping& mapping) -> void {
+    mapping.Output(io);
+  }
+};
+static_assert(llvm::yaml::has_MappingTraits<Carbon::Yaml::OutputMapping,
+                                            llvm::yaml::EmptyContext>::value);
+
+#endif  // CARBON_TOOLCHAIN_BASE_YAML_H_

+ 20 - 18
toolchain/check/testdata/basics/builtin_nodes.carbon

@@ -6,21 +6,23 @@
 //
 //
 // AUTOUPDATE
 // AUTOUPDATE
 
 
-// CHECK:STDOUT: - filename: builtin_nodes.carbon
-// CHECK:STDOUT:   sem_ir:
-// CHECK:STDOUT:   - cross_reference_irs_size: 1
-// CHECK:STDOUT:     functions: []
-// CHECK:STDOUT:     classes: []
-// CHECK:STDOUT:     types: []
-// CHECK:STDOUT:     type_blocks: []
-// CHECK:STDOUT:     nodes:
-// CHECK:STDOUT:       - {kind: CrossReference, arg0: ir0, arg1: nodeTypeType, type: typeTypeType}
-// CHECK:STDOUT:       - {kind: CrossReference, arg0: ir0, arg1: nodeError, type: typeError}
-// CHECK:STDOUT:       - {kind: CrossReference, arg0: ir0, arg1: nodeBoolType, type: typeTypeType}
-// CHECK:STDOUT:       - {kind: CrossReference, arg0: ir0, arg1: nodeIntegerType, type: typeTypeType}
-// CHECK:STDOUT:       - {kind: CrossReference, arg0: ir0, arg1: nodeFloatingPointType, type: typeTypeType}
-// CHECK:STDOUT:       - {kind: CrossReference, arg0: ir0, arg1: nodeStringType, type: typeTypeType}
-// CHECK:STDOUT:       - {kind: CrossReference, arg0: ir0, arg1: nodeFunctionType, type: typeTypeType}
-// CHECK:STDOUT:       - {kind: CrossReference, arg0: ir0, arg1: nodeNamespaceType, type: typeTypeType}
-// CHECK:STDOUT:     node_blocks:
-// CHECK:STDOUT:       - []
+// CHECK:STDOUT: ---
+// CHECK:STDOUT: filename:        builtin_nodes.carbon
+// CHECK:STDOUT: sem_ir:
+// CHECK:STDOUT:   cross_reference_irs_size: 1
+// CHECK:STDOUT:   functions:       {}
+// CHECK:STDOUT:   classes:         {}
+// CHECK:STDOUT:   types:           {}
+// CHECK:STDOUT:   type_blocks:     {}
+// CHECK:STDOUT:   nodes:
+// CHECK:STDOUT:     nodeTypeType:    {kind: CrossReference, arg0: ir0, arg1: nodeTypeType, type: typeTypeType}
+// CHECK:STDOUT:     nodeError:       {kind: CrossReference, arg0: ir0, arg1: nodeError, type: typeError}
+// CHECK:STDOUT:     nodeBoolType:    {kind: CrossReference, arg0: ir0, arg1: nodeBoolType, type: typeTypeType}
+// CHECK:STDOUT:     nodeIntegerType: {kind: CrossReference, arg0: ir0, arg1: nodeIntegerType, type: typeTypeType}
+// CHECK:STDOUT:     nodeFloatingPointType: {kind: CrossReference, arg0: ir0, arg1: nodeFloatingPointType, type: typeTypeType}
+// CHECK:STDOUT:     nodeStringType:  {kind: CrossReference, arg0: ir0, arg1: nodeStringType, type: typeTypeType}
+// CHECK:STDOUT:     nodeFunctionType: {kind: CrossReference, arg0: ir0, arg1: nodeFunctionType, type: typeTypeType}
+// CHECK:STDOUT:     nodeNamespaceType: {kind: CrossReference, arg0: ir0, arg1: nodeNamespaceType, type: typeTypeType}
+// CHECK:STDOUT:   node_blocks:
+// CHECK:STDOUT:     block0:          {}
+// CHECK:STDOUT: ...

+ 40 - 32
toolchain/check/testdata/basics/multifile_raw_and_textual_ir.carbon

@@ -14,22 +14,26 @@ fn A() {}
 // --- b.carbon
 // --- b.carbon
 fn B() {}
 fn B() {}
 
 
-// CHECK:STDOUT: - filename: a.carbon
-// CHECK:STDOUT:   sem_ir:
-// CHECK:STDOUT:   - cross_reference_irs_size: 1
-// CHECK:STDOUT:     functions:
-// CHECK:STDOUT:       - {name: str0, param_refs: block0, body: [block1]}
-// CHECK:STDOUT:     classes: []
-// CHECK:STDOUT:     types:
-// CHECK:STDOUT:       - {node: nodeFunctionType, value_rep: {kind: copy, type: type0}}
-// CHECK:STDOUT:     type_blocks: []
-// CHECK:STDOUT:     nodes:
-// CHECK:STDOUT:       - {kind: FunctionDeclaration, arg0: function0, type: type0}
-// CHECK:STDOUT:       - {kind: Return}
-// CHECK:STDOUT:     node_blocks:
-// CHECK:STDOUT:       - []
-// CHECK:STDOUT:       - - node+1
-// CHECK:STDOUT:       - - node+0
+// CHECK:STDOUT: ---
+// CHECK:STDOUT: filename:        a.carbon
+// CHECK:STDOUT: sem_ir:
+// CHECK:STDOUT:   cross_reference_irs_size: 1
+// CHECK:STDOUT:   functions:
+// CHECK:STDOUT:     function0:       {name: str0, param_refs: block0, body: [block1]}
+// CHECK:STDOUT:   classes:         {}
+// CHECK:STDOUT:   types:
+// CHECK:STDOUT:     type0:           {node: nodeFunctionType, value_rep: {kind: copy, type: type0}}
+// CHECK:STDOUT:   type_blocks:     {}
+// CHECK:STDOUT:   nodes:
+// CHECK:STDOUT:     node+0:          {kind: FunctionDeclaration, arg0: function0, type: type0}
+// CHECK:STDOUT:     node+1:          {kind: Return}
+// CHECK:STDOUT:   node_blocks:
+// CHECK:STDOUT:     block0:          {}
+// CHECK:STDOUT:     block1:
+// CHECK:STDOUT:       0:               node+1
+// CHECK:STDOUT:     block2:
+// CHECK:STDOUT:       0:               node+0
+// CHECK:STDOUT: ...
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: file "a.carbon" {
 // CHECK:STDOUT: file "a.carbon" {
 // CHECK:STDOUT:   %A: <function> = fn_decl @A
 // CHECK:STDOUT:   %A: <function> = fn_decl @A
@@ -39,22 +43,26 @@ fn B() {}
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   return
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
-// CHECK:STDOUT: - filename: b.carbon
-// CHECK:STDOUT:   sem_ir:
-// CHECK:STDOUT:   - cross_reference_irs_size: 1
-// CHECK:STDOUT:     functions:
-// CHECK:STDOUT:       - {name: str1, param_refs: block0, body: [block1]}
-// CHECK:STDOUT:     classes: []
-// CHECK:STDOUT:     types:
-// CHECK:STDOUT:       - {node: nodeFunctionType, value_rep: {kind: copy, type: type0}}
-// CHECK:STDOUT:     type_blocks: []
-// CHECK:STDOUT:     nodes:
-// CHECK:STDOUT:       - {kind: FunctionDeclaration, arg0: function0, type: type0}
-// CHECK:STDOUT:       - {kind: Return}
-// CHECK:STDOUT:     node_blocks:
-// CHECK:STDOUT:       - []
-// CHECK:STDOUT:       - - node+1
-// CHECK:STDOUT:       - - node+0
+// CHECK:STDOUT: ---
+// CHECK:STDOUT: filename:        b.carbon
+// CHECK:STDOUT: sem_ir:
+// CHECK:STDOUT:   cross_reference_irs_size: 1
+// CHECK:STDOUT:   functions:
+// CHECK:STDOUT:     function0:       {name: str1, param_refs: block0, body: [block1]}
+// CHECK:STDOUT:   classes:         {}
+// CHECK:STDOUT:   types:
+// CHECK:STDOUT:     type0:           {node: nodeFunctionType, value_rep: {kind: copy, type: type0}}
+// CHECK:STDOUT:   type_blocks:     {}
+// CHECK:STDOUT:   nodes:
+// CHECK:STDOUT:     node+0:          {kind: FunctionDeclaration, arg0: function0, type: type0}
+// CHECK:STDOUT:     node+1:          {kind: Return}
+// CHECK:STDOUT:   node_blocks:
+// CHECK:STDOUT:     block0:          {}
+// CHECK:STDOUT:     block1:
+// CHECK:STDOUT:       0:               node+1
+// CHECK:STDOUT:     block2:
+// CHECK:STDOUT:       0:               node+0
+// CHECK:STDOUT: ...
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: file "b.carbon" {
 // CHECK:STDOUT: file "b.carbon" {
 // CHECK:STDOUT:   %B: <function> = fn_decl @B
 // CHECK:STDOUT:   %B: <function> = fn_decl @B

+ 40 - 32
toolchain/check/testdata/basics/multifile_raw_ir.carbon

@@ -14,35 +14,43 @@ fn A() {}
 // --- b.carbon
 // --- b.carbon
 fn B() {}
 fn B() {}
 
 
-// CHECK:STDOUT: - filename: a.carbon
-// CHECK:STDOUT:   sem_ir:
-// CHECK:STDOUT:   - cross_reference_irs_size: 1
-// CHECK:STDOUT:     functions:
-// CHECK:STDOUT:       - {name: str0, param_refs: block0, body: [block1]}
-// CHECK:STDOUT:     classes: []
-// CHECK:STDOUT:     types:
-// CHECK:STDOUT:       - {node: nodeFunctionType, value_rep: {kind: copy, type: type0}}
-// CHECK:STDOUT:     type_blocks: []
-// CHECK:STDOUT:     nodes:
-// CHECK:STDOUT:       - {kind: FunctionDeclaration, arg0: function0, type: type0}
-// CHECK:STDOUT:       - {kind: Return}
-// CHECK:STDOUT:     node_blocks:
-// CHECK:STDOUT:       - []
-// CHECK:STDOUT:       - - node+1
-// CHECK:STDOUT:       - - node+0
-// CHECK:STDOUT: - filename: b.carbon
-// CHECK:STDOUT:   sem_ir:
-// CHECK:STDOUT:   - cross_reference_irs_size: 1
-// CHECK:STDOUT:     functions:
-// CHECK:STDOUT:       - {name: str1, param_refs: block0, body: [block1]}
-// CHECK:STDOUT:     classes: []
-// CHECK:STDOUT:     types:
-// CHECK:STDOUT:       - {node: nodeFunctionType, value_rep: {kind: copy, type: type0}}
-// CHECK:STDOUT:     type_blocks: []
-// CHECK:STDOUT:     nodes:
-// CHECK:STDOUT:       - {kind: FunctionDeclaration, arg0: function0, type: type0}
-// CHECK:STDOUT:       - {kind: Return}
-// CHECK:STDOUT:     node_blocks:
-// CHECK:STDOUT:       - []
-// CHECK:STDOUT:       - - node+1
-// CHECK:STDOUT:       - - node+0
+// CHECK:STDOUT: ---
+// CHECK:STDOUT: filename:        a.carbon
+// CHECK:STDOUT: sem_ir:
+// CHECK:STDOUT:   cross_reference_irs_size: 1
+// CHECK:STDOUT:   functions:
+// CHECK:STDOUT:     function0:       {name: str0, param_refs: block0, body: [block1]}
+// CHECK:STDOUT:   classes:         {}
+// CHECK:STDOUT:   types:
+// CHECK:STDOUT:     type0:           {node: nodeFunctionType, value_rep: {kind: copy, type: type0}}
+// CHECK:STDOUT:   type_blocks:     {}
+// CHECK:STDOUT:   nodes:
+// CHECK:STDOUT:     node+0:          {kind: FunctionDeclaration, arg0: function0, type: type0}
+// CHECK:STDOUT:     node+1:          {kind: Return}
+// CHECK:STDOUT:   node_blocks:
+// CHECK:STDOUT:     block0:          {}
+// CHECK:STDOUT:     block1:
+// CHECK:STDOUT:       0:               node+1
+// CHECK:STDOUT:     block2:
+// CHECK:STDOUT:       0:               node+0
+// CHECK:STDOUT: ...
+// CHECK:STDOUT: ---
+// CHECK:STDOUT: filename:        b.carbon
+// CHECK:STDOUT: sem_ir:
+// CHECK:STDOUT:   cross_reference_irs_size: 1
+// CHECK:STDOUT:   functions:
+// CHECK:STDOUT:     function0:       {name: str1, param_refs: block0, body: [block1]}
+// CHECK:STDOUT:   classes:         {}
+// CHECK:STDOUT:   types:
+// CHECK:STDOUT:     type0:           {node: nodeFunctionType, value_rep: {kind: copy, type: type0}}
+// CHECK:STDOUT:   type_blocks:     {}
+// CHECK:STDOUT:   nodes:
+// CHECK:STDOUT:     node+0:          {kind: FunctionDeclaration, arg0: function0, type: type0}
+// CHECK:STDOUT:     node+1:          {kind: Return}
+// CHECK:STDOUT:   node_blocks:
+// CHECK:STDOUT:     block0:          {}
+// CHECK:STDOUT:     block1:
+// CHECK:STDOUT:       0:               node+1
+// CHECK:STDOUT:     block2:
+// CHECK:STDOUT:       0:               node+0
+// CHECK:STDOUT: ...

+ 75 - 64
toolchain/check/testdata/basics/raw_and_textual_ir.carbon

@@ -12,70 +12,81 @@ fn Foo(n: i32) -> (i32, f64) {
   return (n + 2, 3.4);
   return (n + 2, 3.4);
 }
 }
 
 
-// CHECK:STDOUT: - filename: raw_and_textual_ir.carbon
-// CHECK:STDOUT:   sem_ir:
-// CHECK:STDOUT:   - cross_reference_irs_size: 1
-// CHECK:STDOUT:     functions:
-// CHECK:STDOUT:       - {name: str0, param_refs: block1, return_type: type3, return_slot: node+4, body: [block4]}
-// CHECK:STDOUT:     classes: []
-// CHECK:STDOUT:     types:
-// CHECK:STDOUT:       - {node: nodeIntegerType, value_rep: {kind: copy, type: type0}}
-// CHECK:STDOUT:       - {node: node+1, value_rep: {kind: unknown, type: type<invalid>}}
-// CHECK:STDOUT:       - {node: nodeFloatingPointType, value_rep: {kind: copy, type: type2}}
-// CHECK:STDOUT:       - {node: node+3, value_rep: {kind: pointer, type: type4}}
-// CHECK:STDOUT:       - {node: node+5, value_rep: {kind: copy, type: type4}}
-// CHECK:STDOUT:       - {node: nodeFunctionType, value_rep: {kind: copy, type: type5}}
-// CHECK:STDOUT:     type_blocks:
-// CHECK:STDOUT:       - - typeTypeType
-// CHECK:STDOUT:         - typeTypeType
-// CHECK:STDOUT:       - - type0
-// CHECK:STDOUT:         - type2
-// CHECK:STDOUT:     nodes:
-// CHECK:STDOUT:       - {kind: Parameter, arg0: str1, type: type0}
-// CHECK:STDOUT:       - {kind: TupleType, arg0: typeBlock0, type: typeTypeType}
-// CHECK:STDOUT:       - {kind: TupleLiteral, arg0: block2, type: type1}
-// CHECK:STDOUT:       - {kind: TupleType, arg0: typeBlock1, type: typeTypeType}
-// CHECK:STDOUT:       - {kind: VarStorage, arg0: str2, type: type3}
-// CHECK:STDOUT:       - {kind: PointerType, arg0: type3, type: typeTypeType}
-// CHECK:STDOUT:       - {kind: FunctionDeclaration, arg0: function0, type: type5}
-// CHECK:STDOUT:       - {kind: NameReference, arg0: str1, arg1: node+0, type: type0}
-// CHECK:STDOUT:       - {kind: IntegerLiteral, arg0: int3, type: type0}
-// CHECK:STDOUT:       - {kind: BinaryOperatorAdd, arg0: node+7, arg1: node+8, type: type0}
-// CHECK:STDOUT:       - {kind: RealLiteral, arg0: real0, type: type2}
-// CHECK:STDOUT:       - {kind: TupleLiteral, arg0: block5, type: type3}
-// CHECK:STDOUT:       - {kind: TupleAccess, arg0: node+4, arg1: member0, type: type0}
-// CHECK:STDOUT:       - {kind: InitializeFrom, arg0: node+9, arg1: node+12, type: type0}
-// CHECK:STDOUT:       - {kind: TupleAccess, arg0: node+4, arg1: member1, type: type2}
-// CHECK:STDOUT:       - {kind: InitializeFrom, arg0: node+10, arg1: node+14, type: type2}
-// CHECK:STDOUT:       - {kind: TupleInit, arg0: node+11, arg1: block6, type: type3}
-// CHECK:STDOUT:       - {kind: ReturnExpression, arg0: node+16}
-// CHECK:STDOUT:     node_blocks:
-// CHECK:STDOUT:       - []
-// CHECK:STDOUT:       - - node+0
-// CHECK:STDOUT:       - - nodeIntegerType
-// CHECK:STDOUT:         - nodeFloatingPointType
-// CHECK:STDOUT:       - - node+0
-// CHECK:STDOUT:         - node+1
-// CHECK:STDOUT:         - node+2
-// CHECK:STDOUT:         - node+3
-// CHECK:STDOUT:         - node+4
-// CHECK:STDOUT:       - - node+7
-// CHECK:STDOUT:         - node+8
-// CHECK:STDOUT:         - node+9
-// CHECK:STDOUT:         - node+10
-// CHECK:STDOUT:         - node+11
-// CHECK:STDOUT:         - node+12
-// CHECK:STDOUT:         - node+13
-// CHECK:STDOUT:         - node+14
-// CHECK:STDOUT:         - node+15
-// CHECK:STDOUT:         - node+16
-// CHECK:STDOUT:         - node+17
-// CHECK:STDOUT:       - - node+9
-// CHECK:STDOUT:         - node+10
-// CHECK:STDOUT:       - - node+13
-// CHECK:STDOUT:         - node+15
-// CHECK:STDOUT:       - - node+5
-// CHECK:STDOUT:         - node+6
+// CHECK:STDOUT: ---
+// CHECK:STDOUT: filename:        raw_and_textual_ir.carbon
+// CHECK:STDOUT: sem_ir:
+// CHECK:STDOUT:   cross_reference_irs_size: 1
+// CHECK:STDOUT:   functions:
+// CHECK:STDOUT:     function0:       {name: str0, param_refs: block1, return_type: type3, return_slot: node+4, body: [block4]}
+// CHECK:STDOUT:   classes:         {}
+// CHECK:STDOUT:   types:
+// CHECK:STDOUT:     type0:           {node: nodeIntegerType, value_rep: {kind: copy, type: type0}}
+// CHECK:STDOUT:     type1:           {node: node+1, value_rep: {kind: unknown, type: type<invalid>}}
+// CHECK:STDOUT:     type2:           {node: nodeFloatingPointType, value_rep: {kind: copy, type: type2}}
+// CHECK:STDOUT:     type3:           {node: node+3, value_rep: {kind: pointer, type: type4}}
+// CHECK:STDOUT:     type4:           {node: node+5, value_rep: {kind: copy, type: type4}}
+// CHECK:STDOUT:     type5:           {node: nodeFunctionType, value_rep: {kind: copy, type: type5}}
+// CHECK:STDOUT:   type_blocks:
+// CHECK:STDOUT:     typeBlock0:
+// CHECK:STDOUT:       0:               typeTypeType
+// CHECK:STDOUT:       1:               typeTypeType
+// CHECK:STDOUT:     typeBlock1:
+// CHECK:STDOUT:       0:               type0
+// CHECK:STDOUT:       1:               type2
+// CHECK:STDOUT:   nodes:
+// CHECK:STDOUT:     node+0:          {kind: Parameter, arg0: str1, type: type0}
+// CHECK:STDOUT:     node+1:          {kind: TupleType, arg0: typeBlock0, type: typeTypeType}
+// CHECK:STDOUT:     node+2:          {kind: TupleLiteral, arg0: block2, type: type1}
+// CHECK:STDOUT:     node+3:          {kind: TupleType, arg0: typeBlock1, type: typeTypeType}
+// CHECK:STDOUT:     node+4:          {kind: VarStorage, arg0: str2, type: type3}
+// CHECK:STDOUT:     node+5:          {kind: PointerType, arg0: type3, type: typeTypeType}
+// CHECK:STDOUT:     node+6:          {kind: FunctionDeclaration, arg0: function0, type: type5}
+// CHECK:STDOUT:     node+7:          {kind: NameReference, arg0: str1, arg1: node+0, type: type0}
+// CHECK:STDOUT:     node+8:          {kind: IntegerLiteral, arg0: int3, type: type0}
+// CHECK:STDOUT:     node+9:          {kind: BinaryOperatorAdd, arg0: node+7, arg1: node+8, type: type0}
+// CHECK:STDOUT:     node+10:         {kind: RealLiteral, arg0: real0, type: type2}
+// CHECK:STDOUT:     node+11:         {kind: TupleLiteral, arg0: block5, type: type3}
+// CHECK:STDOUT:     node+12:         {kind: TupleAccess, arg0: node+4, arg1: member0, type: type0}
+// CHECK:STDOUT:     node+13:         {kind: InitializeFrom, arg0: node+9, arg1: node+12, type: type0}
+// CHECK:STDOUT:     node+14:         {kind: TupleAccess, arg0: node+4, arg1: member1, type: type2}
+// CHECK:STDOUT:     node+15:         {kind: InitializeFrom, arg0: node+10, arg1: node+14, type: type2}
+// CHECK:STDOUT:     node+16:         {kind: TupleInit, arg0: node+11, arg1: block6, type: type3}
+// CHECK:STDOUT:     node+17:         {kind: ReturnExpression, arg0: node+16}
+// CHECK:STDOUT:   node_blocks:
+// CHECK:STDOUT:     block0:          {}
+// CHECK:STDOUT:     block1:
+// CHECK:STDOUT:       0:               node+0
+// CHECK:STDOUT:     block2:
+// CHECK:STDOUT:       0:               nodeIntegerType
+// CHECK:STDOUT:       1:               nodeFloatingPointType
+// CHECK:STDOUT:     block3:
+// CHECK:STDOUT:       0:               node+0
+// CHECK:STDOUT:       1:               node+1
+// CHECK:STDOUT:       2:               node+2
+// CHECK:STDOUT:       3:               node+3
+// CHECK:STDOUT:       4:               node+4
+// CHECK:STDOUT:     block4:
+// CHECK:STDOUT:       0:               node+7
+// CHECK:STDOUT:       1:               node+8
+// CHECK:STDOUT:       2:               node+9
+// CHECK:STDOUT:       3:               node+10
+// CHECK:STDOUT:       4:               node+11
+// CHECK:STDOUT:       5:               node+12
+// CHECK:STDOUT:       6:               node+13
+// CHECK:STDOUT:       7:               node+14
+// CHECK:STDOUT:       8:               node+15
+// CHECK:STDOUT:       9:               node+16
+// CHECK:STDOUT:       10:              node+17
+// CHECK:STDOUT:     block5:
+// CHECK:STDOUT:       0:               node+9
+// CHECK:STDOUT:       1:               node+10
+// CHECK:STDOUT:     block6:
+// CHECK:STDOUT:       0:               node+13
+// CHECK:STDOUT:       1:               node+15
+// CHECK:STDOUT:     block7:
+// CHECK:STDOUT:       0:               node+5
+// CHECK:STDOUT:       1:               node+6
+// CHECK:STDOUT: ...
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: file "raw_and_textual_ir.carbon" {
 // CHECK:STDOUT: file "raw_and_textual_ir.carbon" {
 // CHECK:STDOUT:   %.loc11: type = ptr_type (i32, f64)
 // CHECK:STDOUT:   %.loc11: type = ptr_type (i32, f64)

+ 75 - 64
toolchain/check/testdata/basics/raw_ir.carbon

@@ -12,67 +12,78 @@ fn Foo(n: i32) -> (i32, f64) {
   return (n + 2, 3.4);
   return (n + 2, 3.4);
 }
 }
 
 
-// CHECK:STDOUT: - filename: raw_ir.carbon
-// CHECK:STDOUT:   sem_ir:
-// CHECK:STDOUT:   - cross_reference_irs_size: 1
-// CHECK:STDOUT:     functions:
-// CHECK:STDOUT:       - {name: str0, param_refs: block1, return_type: type3, return_slot: node+4, body: [block4]}
-// CHECK:STDOUT:     classes: []
-// CHECK:STDOUT:     types:
-// CHECK:STDOUT:       - {node: nodeIntegerType, value_rep: {kind: copy, type: type0}}
-// CHECK:STDOUT:       - {node: node+1, value_rep: {kind: unknown, type: type<invalid>}}
-// CHECK:STDOUT:       - {node: nodeFloatingPointType, value_rep: {kind: copy, type: type2}}
-// CHECK:STDOUT:       - {node: node+3, value_rep: {kind: pointer, type: type4}}
-// CHECK:STDOUT:       - {node: node+5, value_rep: {kind: copy, type: type4}}
-// CHECK:STDOUT:       - {node: nodeFunctionType, value_rep: {kind: copy, type: type5}}
-// CHECK:STDOUT:     type_blocks:
-// CHECK:STDOUT:       - - typeTypeType
-// CHECK:STDOUT:         - typeTypeType
-// CHECK:STDOUT:       - - type0
-// CHECK:STDOUT:         - type2
-// CHECK:STDOUT:     nodes:
-// CHECK:STDOUT:       - {kind: Parameter, arg0: str1, type: type0}
-// CHECK:STDOUT:       - {kind: TupleType, arg0: typeBlock0, type: typeTypeType}
-// CHECK:STDOUT:       - {kind: TupleLiteral, arg0: block2, type: type1}
-// CHECK:STDOUT:       - {kind: TupleType, arg0: typeBlock1, type: typeTypeType}
-// CHECK:STDOUT:       - {kind: VarStorage, arg0: str2, type: type3}
-// CHECK:STDOUT:       - {kind: PointerType, arg0: type3, type: typeTypeType}
-// CHECK:STDOUT:       - {kind: FunctionDeclaration, arg0: function0, type: type5}
-// CHECK:STDOUT:       - {kind: NameReference, arg0: str1, arg1: node+0, type: type0}
-// CHECK:STDOUT:       - {kind: IntegerLiteral, arg0: int3, type: type0}
-// CHECK:STDOUT:       - {kind: BinaryOperatorAdd, arg0: node+7, arg1: node+8, type: type0}
-// CHECK:STDOUT:       - {kind: RealLiteral, arg0: real0, type: type2}
-// CHECK:STDOUT:       - {kind: TupleLiteral, arg0: block5, type: type3}
-// CHECK:STDOUT:       - {kind: TupleAccess, arg0: node+4, arg1: member0, type: type0}
-// CHECK:STDOUT:       - {kind: InitializeFrom, arg0: node+9, arg1: node+12, type: type0}
-// CHECK:STDOUT:       - {kind: TupleAccess, arg0: node+4, arg1: member1, type: type2}
-// CHECK:STDOUT:       - {kind: InitializeFrom, arg0: node+10, arg1: node+14, type: type2}
-// CHECK:STDOUT:       - {kind: TupleInit, arg0: node+11, arg1: block6, type: type3}
-// CHECK:STDOUT:       - {kind: ReturnExpression, arg0: node+16}
-// CHECK:STDOUT:     node_blocks:
-// CHECK:STDOUT:       - []
-// CHECK:STDOUT:       - - node+0
-// CHECK:STDOUT:       - - nodeIntegerType
-// CHECK:STDOUT:         - nodeFloatingPointType
-// CHECK:STDOUT:       - - node+0
-// CHECK:STDOUT:         - node+1
-// CHECK:STDOUT:         - node+2
-// CHECK:STDOUT:         - node+3
-// CHECK:STDOUT:         - node+4
-// CHECK:STDOUT:       - - node+7
-// CHECK:STDOUT:         - node+8
-// CHECK:STDOUT:         - node+9
-// CHECK:STDOUT:         - node+10
-// CHECK:STDOUT:         - node+11
-// CHECK:STDOUT:         - node+12
-// CHECK:STDOUT:         - node+13
-// CHECK:STDOUT:         - node+14
-// CHECK:STDOUT:         - node+15
-// CHECK:STDOUT:         - node+16
-// CHECK:STDOUT:         - node+17
-// CHECK:STDOUT:       - - node+9
-// CHECK:STDOUT:         - node+10
-// CHECK:STDOUT:       - - node+13
-// CHECK:STDOUT:         - node+15
-// CHECK:STDOUT:       - - node+5
-// CHECK:STDOUT:         - node+6
+// CHECK:STDOUT: ---
+// CHECK:STDOUT: filename:        raw_ir.carbon
+// CHECK:STDOUT: sem_ir:
+// CHECK:STDOUT:   cross_reference_irs_size: 1
+// CHECK:STDOUT:   functions:
+// CHECK:STDOUT:     function0:       {name: str0, param_refs: block1, return_type: type3, return_slot: node+4, body: [block4]}
+// CHECK:STDOUT:   classes:         {}
+// CHECK:STDOUT:   types:
+// CHECK:STDOUT:     type0:           {node: nodeIntegerType, value_rep: {kind: copy, type: type0}}
+// CHECK:STDOUT:     type1:           {node: node+1, value_rep: {kind: unknown, type: type<invalid>}}
+// CHECK:STDOUT:     type2:           {node: nodeFloatingPointType, value_rep: {kind: copy, type: type2}}
+// CHECK:STDOUT:     type3:           {node: node+3, value_rep: {kind: pointer, type: type4}}
+// CHECK:STDOUT:     type4:           {node: node+5, value_rep: {kind: copy, type: type4}}
+// CHECK:STDOUT:     type5:           {node: nodeFunctionType, value_rep: {kind: copy, type: type5}}
+// CHECK:STDOUT:   type_blocks:
+// CHECK:STDOUT:     typeBlock0:
+// CHECK:STDOUT:       0:               typeTypeType
+// CHECK:STDOUT:       1:               typeTypeType
+// CHECK:STDOUT:     typeBlock1:
+// CHECK:STDOUT:       0:               type0
+// CHECK:STDOUT:       1:               type2
+// CHECK:STDOUT:   nodes:
+// CHECK:STDOUT:     node+0:          {kind: Parameter, arg0: str1, type: type0}
+// CHECK:STDOUT:     node+1:          {kind: TupleType, arg0: typeBlock0, type: typeTypeType}
+// CHECK:STDOUT:     node+2:          {kind: TupleLiteral, arg0: block2, type: type1}
+// CHECK:STDOUT:     node+3:          {kind: TupleType, arg0: typeBlock1, type: typeTypeType}
+// CHECK:STDOUT:     node+4:          {kind: VarStorage, arg0: str2, type: type3}
+// CHECK:STDOUT:     node+5:          {kind: PointerType, arg0: type3, type: typeTypeType}
+// CHECK:STDOUT:     node+6:          {kind: FunctionDeclaration, arg0: function0, type: type5}
+// CHECK:STDOUT:     node+7:          {kind: NameReference, arg0: str1, arg1: node+0, type: type0}
+// CHECK:STDOUT:     node+8:          {kind: IntegerLiteral, arg0: int3, type: type0}
+// CHECK:STDOUT:     node+9:          {kind: BinaryOperatorAdd, arg0: node+7, arg1: node+8, type: type0}
+// CHECK:STDOUT:     node+10:         {kind: RealLiteral, arg0: real0, type: type2}
+// CHECK:STDOUT:     node+11:         {kind: TupleLiteral, arg0: block5, type: type3}
+// CHECK:STDOUT:     node+12:         {kind: TupleAccess, arg0: node+4, arg1: member0, type: type0}
+// CHECK:STDOUT:     node+13:         {kind: InitializeFrom, arg0: node+9, arg1: node+12, type: type0}
+// CHECK:STDOUT:     node+14:         {kind: TupleAccess, arg0: node+4, arg1: member1, type: type2}
+// CHECK:STDOUT:     node+15:         {kind: InitializeFrom, arg0: node+10, arg1: node+14, type: type2}
+// CHECK:STDOUT:     node+16:         {kind: TupleInit, arg0: node+11, arg1: block6, type: type3}
+// CHECK:STDOUT:     node+17:         {kind: ReturnExpression, arg0: node+16}
+// CHECK:STDOUT:   node_blocks:
+// CHECK:STDOUT:     block0:          {}
+// CHECK:STDOUT:     block1:
+// CHECK:STDOUT:       0:               node+0
+// CHECK:STDOUT:     block2:
+// CHECK:STDOUT:       0:               nodeIntegerType
+// CHECK:STDOUT:       1:               nodeFloatingPointType
+// CHECK:STDOUT:     block3:
+// CHECK:STDOUT:       0:               node+0
+// CHECK:STDOUT:       1:               node+1
+// CHECK:STDOUT:       2:               node+2
+// CHECK:STDOUT:       3:               node+3
+// CHECK:STDOUT:       4:               node+4
+// CHECK:STDOUT:     block4:
+// CHECK:STDOUT:       0:               node+7
+// CHECK:STDOUT:       1:               node+8
+// CHECK:STDOUT:       2:               node+9
+// CHECK:STDOUT:       3:               node+10
+// CHECK:STDOUT:       4:               node+11
+// CHECK:STDOUT:       5:               node+12
+// CHECK:STDOUT:       6:               node+13
+// CHECK:STDOUT:       7:               node+14
+// CHECK:STDOUT:       8:               node+15
+// CHECK:STDOUT:       9:               node+16
+// CHECK:STDOUT:       10:              node+17
+// CHECK:STDOUT:     block5:
+// CHECK:STDOUT:       0:               node+9
+// CHECK:STDOUT:       1:               node+10
+// CHECK:STDOUT:     block6:
+// CHECK:STDOUT:       0:               node+13
+// CHECK:STDOUT:       1:               node+15
+// CHECK:STDOUT:     block7:
+// CHECK:STDOUT:       0:               node+5
+// CHECK:STDOUT:       1:               node+6
+// CHECK:STDOUT: ...

+ 24 - 22
toolchain/driver/testdata/dump_shared_values.carbon

@@ -15,26 +15,28 @@ var real3: f64 = 0.8e9;
 var str1: String = "abc";
 var str1: String = "abc";
 var str2: String = "ab'\"c";
 var str2: String = "ab'\"c";
 
 
+// CHECK:STDOUT: ---
 // CHECK:STDOUT: shared_values:
 // CHECK:STDOUT: shared_values:
-// CHECK:STDOUT:   - integers:
-// CHECK:STDOUT:       - 32
-// CHECK:STDOUT:       - 1
-// CHECK:STDOUT:       - 32
-// CHECK:STDOUT:       - 8
-// CHECK:STDOUT:       - 64
-// CHECK:STDOUT:       - 64
-// CHECK:STDOUT:       - 64
-// CHECK:STDOUT:     reals:
-// CHECK:STDOUT:       - 10*10^-1
-// CHECK:STDOUT:       - 8*10^7
-// CHECK:STDOUT:       - 8*10^8
-// CHECK:STDOUT:     strings:
-// CHECK:STDOUT:       - "int1"
-// CHECK:STDOUT:       - "int2"
-// CHECK:STDOUT:       - "real1"
-// CHECK:STDOUT:       - "real2"
-// CHECK:STDOUT:       - "real3"
-// CHECK:STDOUT:       - "str1"
-// CHECK:STDOUT:       - "abc"
-// CHECK:STDOUT:       - "str2"
-// CHECK:STDOUT:       - "ab'\"c"
+// CHECK:STDOUT:   integers:
+// CHECK:STDOUT:     int0:            32
+// CHECK:STDOUT:     int1:            1
+// CHECK:STDOUT:     int2:            32
+// CHECK:STDOUT:     int3:            8
+// CHECK:STDOUT:     int4:            64
+// CHECK:STDOUT:     int5:            64
+// CHECK:STDOUT:     int6:            64
+// CHECK:STDOUT:   reals:
+// CHECK:STDOUT:     real0:           10*10^-1
+// CHECK:STDOUT:     real1:           8*10^7
+// CHECK:STDOUT:     real2:           8*10^8
+// CHECK:STDOUT:   strings:
+// CHECK:STDOUT:     str0:            int1
+// CHECK:STDOUT:     str1:            int2
+// CHECK:STDOUT:     str2:            real1
+// CHECK:STDOUT:     str3:            real2
+// CHECK:STDOUT:     str4:            real3
+// CHECK:STDOUT:     str5:            str1
+// CHECK:STDOUT:     str6:            abc
+// CHECK:STDOUT:     str7:            str2
+// CHECK:STDOUT:     str8:            'ab''"c'
+// CHECK:STDOUT: ...

+ 1 - 1
toolchain/lex/tokenized_buffer_test.cpp

@@ -1057,7 +1057,7 @@ TEST_F(LexerTest, DiagnosticUnrecognizedChar) {
   Lex("\b", consumer);
   Lex("\b", consumer);
 }
 }
 
 
-TEST_F(LexerTest, PrintingAsYaml) {
+TEST_F(LexerTest, PrintingOutputYaml) {
   // Test that we can parse this into YAML and verify line and indent data.
   // Test that we can parse this into YAML and verify line and indent data.
   auto buffer = Lex("\n ;\n\n\n; ;\n\n\n\n\n\n\n\n\n\n\n");
   auto buffer = Lex("\n ;\n\n\n; ;\n\n\n\n\n\n\n\n\n\n\n");
   ASSERT_FALSE(buffer.has_errors());
   ASSERT_FALSE(buffer.has_errors());

+ 2 - 0
toolchain/sem_ir/BUILD

@@ -69,6 +69,7 @@ cc_library(
         ":value_stores",
         ":value_stores",
         "//common:check",
         "//common:check",
         "//toolchain/base:value_store",
         "//toolchain/base:value_store",
+        "//toolchain/base:yaml",
         "@llvm-project//llvm:Support",
         "@llvm-project//llvm:Support",
     ],
     ],
 )
 )
@@ -102,6 +103,7 @@ cc_library(
     deps = [
     deps = [
         ":node",
         ":node",
         "//toolchain/base:value_store",
         "//toolchain/base:value_store",
+        "//toolchain/base:yaml",
         "@llvm-project//llvm:Support",
         "@llvm-project//llvm:Support",
     ],
     ],
 )
 )

+ 25 - 21
toolchain/sem_ir/file.cpp

@@ -8,6 +8,7 @@
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/SmallVector.h"
 #include "toolchain/base/value_store.h"
 #include "toolchain/base/value_store.h"
+#include "toolchain/base/yaml.h"
 #include "toolchain/sem_ir/builtin_kind.h"
 #include "toolchain/sem_ir/builtin_kind.h"
 #include "toolchain/sem_ir/node.h"
 #include "toolchain/sem_ir/node.h"
 #include "toolchain/sem_ir/node_kind.h"
 #include "toolchain/sem_ir/node_kind.h"
@@ -129,27 +130,30 @@ auto File::Verify() const -> ErrorOr<Success> {
   return Success();
   return Success();
 }
 }
 
 
-auto File::Print(llvm::raw_ostream& out, bool include_builtins) const -> void {
-  out << "- filename: " << filename_ << "\n"
-      << "  sem_ir:\n"
-      << "  - cross_reference_irs_size: " << cross_reference_irs_.size()
-      << "\n";
-
-  static constexpr int FirstLineIndent = 4;
-  static constexpr int LaterIndent = 6;
-  functions_.Print(out, "functions", FirstLineIndent, LaterIndent);
-  classes_.Print(out, "classes", FirstLineIndent, LaterIndent);
-  types_.Print(out, "types", FirstLineIndent, LaterIndent);
-  type_blocks_.Print(out, "type_blocks", FirstLineIndent, LaterIndent);
-
-  auto nodes = nodes_.array_ref();
-  if (!include_builtins) {
-    nodes = nodes.drop_front(BuiltinKind::ValidCount);
-  }
-  PrintValueRange(out, llvm::iterator_range(nodes), "nodes", FirstLineIndent,
-                  LaterIndent, /*trailing_newline=*/true);
-
-  node_blocks_.Print(out, "node_blocks", FirstLineIndent, LaterIndent);
+auto File::OutputYaml(bool include_builtins) const -> Yaml::OutputMapping {
+  return Yaml::OutputMapping([this,
+                              include_builtins](Yaml::OutputMapping::Map map) {
+    map.Add("filename", filename_);
+    map.Add("sem_ir", Yaml::OutputMapping([&](Yaml::OutputMapping::Map map) {
+              map.Add("cross_reference_irs_size",
+                      Yaml::OutputScalar(cross_reference_irs_.size()));
+              map.Add("functions", functions_.OutputYaml());
+              map.Add("classes", classes_.OutputYaml());
+              map.Add("types", types_.OutputYaml());
+              map.Add("type_blocks", type_blocks_.OutputYaml());
+              map.Add("nodes",
+                      Yaml::OutputMapping([&](Yaml::OutputMapping::Map map) {
+                        int start =
+                            include_builtins ? 0 : BuiltinKind::ValidCount;
+                        for (int i : llvm::seq(start, nodes_.size())) {
+                          auto id = NodeId(i);
+                          map.Add(PrintToString(id),
+                                  Yaml::OutputScalar(nodes_.Get(id)));
+                        }
+                      }));
+              map.Add("node_blocks", node_blocks_.OutputYaml());
+            }));
+  });
 }
 }
 
 
 // Map a node kind representing a type into an integer describing the
 // Map a node kind representing a type into an integer describing the

+ 5 - 4
toolchain/sem_ir/file.h

@@ -10,6 +10,7 @@
 #include "llvm/Support/Allocator.h"
 #include "llvm/Support/Allocator.h"
 #include "llvm/Support/FormatVariadic.h"
 #include "llvm/Support/FormatVariadic.h"
 #include "toolchain/base/value_store.h"
 #include "toolchain/base/value_store.h"
+#include "toolchain/base/yaml.h"
 #include "toolchain/sem_ir/node.h"
 #include "toolchain/sem_ir/node.h"
 #include "toolchain/sem_ir/value_stores.h"
 #include "toolchain/sem_ir/value_stores.h"
 
 
@@ -174,11 +175,11 @@ class File : public Printable<File> {
   // TODO: In the future, the things to print may change, for example by adding
   // TODO: In the future, the things to print may change, for example by adding
   // preludes. We may then want the ability to omit other things similar to
   // preludes. We may then want the ability to omit other things similar to
   // builtins.
   // builtins.
-  auto Print(llvm::raw_ostream& out, bool include_builtins) const -> void;
-
-  auto Print(llvm::raw_ostream& out) const -> void {
-    Print(out, /*include_builtins=*/false);
+  auto Print(llvm::raw_ostream& out, bool include_builtins = false) const
+      -> void {
+    Yaml::Print(out, OutputYaml(include_builtins));
   }
   }
+  auto OutputYaml(bool include_builtins) const -> Yaml::OutputMapping;
 
 
   // Returns array bound value from the bound node.
   // Returns array bound value from the bound node.
   auto GetArrayBoundValue(NodeId bound_id) const -> uint64_t {
   auto GetArrayBoundValue(NodeId bound_id) const -> uint64_t {

+ 15 - 13
toolchain/sem_ir/value_stores.h

@@ -7,6 +7,7 @@
 
 
 #include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/DenseMap.h"
 #include "toolchain/base/value_store.h"
 #include "toolchain/base/value_store.h"
+#include "toolchain/base/yaml.h"
 #include "toolchain/sem_ir/node.h"
 #include "toolchain/sem_ir/node.h"
 
 
 namespace Carbon::SemIR {
 namespace Carbon::SemIR {
@@ -72,7 +73,7 @@ class NameScopeStore {
 // BlockValueStore is used as-is, but there are also children that expose the
 // BlockValueStore is used as-is, but there are also children that expose the
 // protected members for type-specific functionality.
 // protected members for type-specific functionality.
 template <typename IdT, typename ValueT>
 template <typename IdT, typename ValueT>
-class BlockValueStore {
+class BlockValueStore : public Yaml::Printable<BlockValueStore<IdT, ValueT>> {
  public:
  public:
   explicit BlockValueStore(llvm::BumpPtrAllocator& allocator)
   explicit BlockValueStore(llvm::BumpPtrAllocator& allocator)
       : allocator_(&allocator) {}
       : allocator_(&allocator) {}
@@ -88,18 +89,19 @@ class BlockValueStore {
   // Returns the requested block.
   // Returns the requested block.
   auto Get(IdT id) -> llvm::MutableArrayRef<ValueT> { return values_.Get(id); }
   auto Get(IdT id) -> llvm::MutableArrayRef<ValueT> { return values_.Get(id); }
 
 
-  auto Print(llvm::raw_ostream& out) const -> void {
-    Print(out, std::nullopt, 0, 0);
-  }
-  auto Print(llvm::raw_ostream& out, std::optional<llvm::StringRef> label,
-             int first_line_indent, int later_indent) const -> void {
-    values_.Print(out, label, first_line_indent, later_indent,
-                  [&](llvm::raw_ostream& out,
-                      const llvm::MutableArrayRef<ValueT>& value) {
-                    PrintValueRange<ValueT>(out, llvm::iterator_range(value),
-                                            std::nullopt, 0, later_indent + 2,
-                                            /*trailing_newline=*/false);
-                  });
+  auto OutputYaml() const -> Yaml::OutputMapping {
+    return Yaml::OutputMapping([&](Yaml::OutputMapping::Map map) {
+      for (auto block_index : llvm::seq(values_.size())) {
+        auto block_id = IdT(block_index);
+        map.Add(PrintToString(block_id),
+                Yaml::OutputMapping([&](Yaml::OutputMapping::Map map) {
+                  auto block = Get(block_id);
+                  for (auto i : llvm::seq(block.size())) {
+                    map.Add(llvm::itostr(i), Yaml::OutputScalar(block[i]));
+                  }
+                }));
+      }
+    });
   }
   }
 
 
   auto size() const -> int { return values_.size(); }
   auto size() const -> int { return values_.size(); }

+ 26 - 21
toolchain/sem_ir/yaml_test.cpp

@@ -44,40 +44,45 @@ TEST(SemIRTest, YAML) {
   auto node_id = Yaml::Scalar(MatchesRegex(R"(node\+\d+)"));
   auto node_id = Yaml::Scalar(MatchesRegex(R"(node\+\d+)"));
   auto node_builtin = Yaml::Scalar(MatchesRegex(R"(node\w+)"));
   auto node_builtin = Yaml::Scalar(MatchesRegex(R"(node\w+)"));
   auto type_id = Yaml::Scalar(MatchesRegex(R"(type\d+)"));
   auto type_id = Yaml::Scalar(MatchesRegex(R"(type\d+)"));
-  auto type_builtin = Yaml::Mapping(ElementsAre(
-      Pair("node", node_builtin), Pair("value_rep", Yaml::Mapping(_))));
+  auto type_builtin = Pair(
+      type_id, Yaml::Mapping(ElementsAre(Pair("node", node_builtin),
+                                         Pair("value_rep", Yaml::Mapping(_)))));
 
 
-  auto file = Yaml::Sequence(ElementsAre(Yaml::Mapping(ElementsAre(
+  auto file = Yaml::Mapping(ElementsAre(
       Pair("cross_reference_irs_size", "1"),
       Pair("cross_reference_irs_size", "1"),
-      Pair("functions", Yaml::Sequence(SizeIs(1))),
-      Pair("classes", Yaml::Sequence(SizeIs(0))),
-      Pair("types", Yaml::Sequence(Each(type_builtin))),
-      Pair("type_blocks", Yaml::Sequence(IsEmpty())),
+      Pair("functions", Yaml::Mapping(SizeIs(1))),
+      Pair("classes", Yaml::Mapping(SizeIs(0))),
+      Pair("types", Yaml::Mapping(Each(type_builtin))),
+      Pair("type_blocks", Yaml::Mapping(IsEmpty())),
       Pair("nodes",
       Pair("nodes",
-           Yaml::Sequence(AllOf(
+           Yaml::Mapping(AllOf(
+               Each(Key(node_id)),
                // kind is required, other parts are optional.
                // kind is required, other parts are optional.
-               Each(Yaml::Mapping(Contains(Pair("kind", _)))),
+               Each(Pair(_, Yaml::Mapping(Contains(Pair("kind", _))))),
                // A 0-arg node.
                // A 0-arg node.
-               Contains(Yaml::Mapping(ElementsAre(Pair("kind", "Return")))),
+               Contains(
+                   Pair(_, Yaml::Mapping(ElementsAre(Pair("kind", "Return"))))),
                // A 1-arg node.
                // A 1-arg node.
-               Contains(Yaml::Mapping(ElementsAre(
-                   Pair("kind", "IntegerLiteral"), Pair("arg0", integer_id),
-                   Pair("type", type_id)))),
+               Contains(Pair(
+                   _, Yaml::Mapping(ElementsAre(Pair("kind", "IntegerLiteral"),
+                                                Pair("arg0", integer_id),
+                                                Pair("type", type_id))))),
                // A 2-arg node.
                // A 2-arg node.
-               Contains(Yaml::Mapping(ElementsAre(Pair("kind", "Assign"),
-                                                  Pair("arg0", node_id),
-                                                  Pair("arg1", node_id))))))),
+               Contains(Pair(
+                   _, Yaml::Mapping(ElementsAre(Pair("kind", "Assign"),
+                                                Pair("arg0", node_id),
+                                                Pair("arg1", node_id)))))))),
       // This production has only two node blocks.
       // This production has only two node blocks.
       Pair("node_blocks",
       Pair("node_blocks",
-           Yaml::Sequence(ElementsAre(Yaml::Sequence(IsEmpty()),
-                                      Yaml::Sequence(Each(node_id)),
-                                      Yaml::Sequence(Each(node_id)))))))));
+           Yaml::Mapping(ElementsAre(
+               Pair("block0", Yaml::Mapping(IsEmpty())),
+               Pair("block1", Yaml::Mapping(Each(Pair(_, node_id)))),
+               Pair("block2", Yaml::Mapping(Each(Pair(_, node_id)))))))));
 
 
   auto root = Yaml::Sequence(ElementsAre(Yaml::Mapping(
   auto root = Yaml::Sequence(ElementsAre(Yaml::Mapping(
       ElementsAre(Pair("filename", "test.carbon"), Pair("sem_ir", file)))));
       ElementsAre(Pair("filename", "test.carbon"), Pair("sem_ir", file)))));
 
 
-  EXPECT_THAT(Yaml::Value::FromText(print_stream.TakeStr()),
-              IsYaml(ElementsAre(root)));
+  EXPECT_THAT(Yaml::Value::FromText(print_stream.TakeStr()), IsYaml(root));
 }
 }
 
 
 }  // namespace
 }  // namespace