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

Add tracking of function parameters (#2552)

For parameters (and in the future, arguments too; generally comma-separated lists) track two node blocks:

1. param_ir: The complete IR.
2. param_refs: Nodes within the IR that are the "root" parameter.

param_refs should allow quick counting of the # of parameters, and more efficient comparison of call args with function parameters. param_ir should be necessary to generate the actual signature.

In order to construct this, this refactors the node_block_stack into its own class, which is reused in params_stack. These carry references to the underlying SmallVector for lazy modification in order to avoid a dependency cycle with SemanticsIR (also see notes on empty node blocks below).

When finalized, the block pair is pushed onto finished_params_stack. That's because node_stack only has space for one thing, and this is two things -- so I'm essentially choosing a trade-off of adding another stack in order to avoid consuming more space in the expectation that most parse nodes have 0 or 1 things to return, and 2 will be very rare.

As factored, this currently consolidates most empty node blocks into a single canonical empty node block. This is because I think empty blocks, i.e. `()`, will be very common. In order to achieve this, SemanticsNodeBlockStack does lazy creation.

An alternative approach would have been to use 1 node block per parameter. We decided against this in order to reduce the number of vectors being created.
Jon Ross-Perkins 3 лет назад
Родитель
Сommit
6feed2ae33
37 измененных файлов с 748 добавлено и 149 удалено
  1. 14 0
      toolchain/semantics/BUILD
  2. 9 2
      toolchain/semantics/semantics_ir.cpp
  3. 38 8
      toolchain/semantics/semantics_ir.h
  4. 4 1
      toolchain/semantics/semantics_ir_test.cpp
  5. 26 4
      toolchain/semantics/semantics_node.h
  6. 60 0
      toolchain/semantics/semantics_node_block_stack.cpp
  7. 65 0
      toolchain/semantics/semantics_node_block_stack.h
  8. 47 22
      toolchain/semantics/semantics_node_stack.cpp
  9. 32 15
      toolchain/semantics/semantics_node_stack.h
  10. 101 51
      toolchain/semantics/semantics_parse_tree_handler.cpp
  11. 16 8
      toolchain/semantics/semantics_parse_tree_handler.h
  12. 2 0
      toolchain/semantics/testdata/basics/empty.carbon
  13. 2 0
      toolchain/semantics/testdata/basics/empty_decl.carbon
  14. 7 2
      toolchain/semantics/testdata/basics/fail_name_lookup.carbon
  15. 2 2
      toolchain/semantics/testdata/basics/verbose.carbon
  16. 7 2
      toolchain/semantics/testdata/function/basic.carbon
  17. 13 6
      toolchain/semantics/testdata/function/order.carbon
  18. 47 0
      toolchain/semantics/testdata/function/params_one.carbon
  19. 47 0
      toolchain/semantics/testdata/function/params_one_comma.carbon
  20. 53 0
      toolchain/semantics/testdata/function/params_two.carbon
  21. 53 0
      toolchain/semantics/testdata/function/params_two_comma.carbon
  22. 7 2
      toolchain/semantics/testdata/operators/binary_op.carbon
  23. 7 2
      toolchain/semantics/testdata/operators/fail_type_mismatch.carbon
  24. 7 2
      toolchain/semantics/testdata/operators/fail_type_mismatch_once.carbon
  25. 7 2
      toolchain/semantics/testdata/return/literal.carbon
  26. 7 2
      toolchain/semantics/testdata/return/trivial.carbon
  27. 7 2
      toolchain/semantics/testdata/var/decl.carbon
  28. 7 2
      toolchain/semantics/testdata/var/decl_with_init.carbon
  29. 7 2
      toolchain/semantics/testdata/var/fail_duplicate_decl.carbon
  30. 7 2
      toolchain/semantics/testdata/var/fail_init_type_mismatch.carbon
  31. 7 2
      toolchain/semantics/testdata/var/fail_init_with_self.carbon
  32. 7 2
      toolchain/semantics/testdata/var/fail_lookup_outside_scope.carbon
  33. 4 0
      toolchain/semantics/testdata/var/global_decl.carbon
  34. 4 0
      toolchain/semantics/testdata/var/global_decl_with_init.carbon
  35. 4 0
      toolchain/semantics/testdata/var/global_lookup.carbon
  36. 7 2
      toolchain/semantics/testdata/var/global_lookup_in_scope.carbon
  37. 7 2
      toolchain/semantics/testdata/var/lookup.carbon

+ 14 - 0
toolchain/semantics/BUILD

@@ -34,6 +34,19 @@ cc_library(
     ],
 )
 
+cc_library(
+    name = "semantics_node_block_stack",
+    srcs = ["semantics_node_block_stack.cpp"],
+    hdrs = ["semantics_node_block_stack.h"],
+    deps = [
+        ":semantics_node",
+        "//common:check",
+        "//common:ostream",
+        "//common:vlog",
+        "@llvm-project//llvm:Support",
+    ],
+)
+
 cc_library(
     name = "semantics_node_stack",
     srcs = ["semantics_node_stack.cpp"],
@@ -62,6 +75,7 @@ cc_library(
     deps = [
         ":semantics_builtin_kind",
         ":semantics_node",
+        ":semantics_node_block_stack",
         ":semantics_node_stack",
         "//common:check",
         "//common:ostream",

+ 9 - 2
toolchain/semantics/semantics_ir.cpp

@@ -38,8 +38,8 @@ auto SemanticsIR::MakeBuiltinIR() -> SemanticsIR {
   semantics.AddNode(block_id, SemanticsNode::MakeBuiltin(
                                   SemanticsBuiltinKind::RealType, type_type));
 
-  CARBON_CHECK(semantics.node_blocks_.size() == 1)
-      << "BuildBuiltins should only produce 1 block, actual: "
+  CARBON_CHECK(semantics.node_blocks_.size() == 2)
+      << "BuildBuiltins should produce 2 blocks, actual: "
       << semantics.node_blocks_.size();
   return semantics;
 }
@@ -77,6 +77,13 @@ auto SemanticsIR::Print(llvm::raw_ostream& out) const -> void {
 
   out << "cross_reference_irs_size: " << cross_reference_irs_.size() << "\n";
 
+  out << "callables: [\n";
+  for (auto callable : callables_) {
+    out.indent(Indent);
+    out << callable << "\n";
+  }
+  out << "]\n";
+
   out << "integer_literals: [\n";
   for (const auto& integer_literal : integer_literals_) {
     out.indent(Indent);

+ 38 - 8
toolchain/semantics/semantics_ir.h

@@ -16,6 +16,19 @@ class SemanticsIRForTest;
 
 namespace Carbon {
 
+// A callable object.
+struct SemanticsCallable {
+  auto Print(llvm::raw_ostream& out) const -> void {
+    out << "{param_ir: " << param_ir_id << ", param_refs: " << param_refs_id
+        << "}";
+  }
+
+  // The full IR for parameters.
+  SemanticsNodeBlockId param_ir_id;
+  // A block containing a single reference node per parameter.
+  SemanticsNodeBlockId param_refs_id;
+};
+
 // Provides semantic analysis on a ParseTree.
 class SemanticsIR {
  public:
@@ -39,7 +52,10 @@ class SemanticsIR {
   friend class SemanticsParseTreeHandler;
 
   explicit SemanticsIR(const SemanticsIR* builtin_ir)
-      : cross_reference_irs_({builtin_ir == nullptr ? this : builtin_ir}) {}
+      : cross_reference_irs_({builtin_ir == nullptr ? this : builtin_ir}) {
+    // For SemanticsNodeBlockId::Empty.
+    node_blocks_.resize(1);
+  }
 
   // Returns the requested node.
   auto GetNode(SemanticsNodeId node_id) const -> const SemanticsNode& {
@@ -51,6 +67,12 @@ class SemanticsIR {
     return GetNode(node_id).type();
   }
 
+  auto AddCallable(SemanticsCallable callable) -> SemanticsCallableId {
+    SemanticsCallableId id(callables_.size());
+    callables_.push_back(callable);
+    return id;
+  }
+
   // Adds an integer literal, returning an ID to reference it.
   auto AddIntegerLiteral(llvm::APInt integer_literal)
       -> SemanticsIntegerLiteralId {
@@ -59,6 +81,15 @@ class SemanticsIR {
     return id;
   }
 
+  // Adds a node to a specified block, returning an ID to reference the node.
+  auto AddNode(SemanticsNodeBlockId block_id, SemanticsNode node)
+      -> SemanticsNodeId {
+    SemanticsNodeId node_id(nodes_.size());
+    nodes_.push_back(node);
+    node_blocks_[block_id.index].push_back(node_id);
+    return node_id;
+  }
+
   // Adds an empty new node block, returning an ID to reference it and add
   // items.
   auto AddNodeBlock() -> SemanticsNodeBlockId {
@@ -67,13 +98,9 @@ class SemanticsIR {
     return id;
   }
 
-  // Adds a node to a specified block, returning an ID to reference the node.
-  auto AddNode(SemanticsNodeBlockId block_id, SemanticsNode node)
-      -> SemanticsNodeId {
-    SemanticsNodeId node_id(nodes_.size());
-    nodes_.push_back(node);
-    node_blocks_[block_id.index].push_back(node_id);
-    return node_id;
+  auto GetNodeBlock(SemanticsNodeBlockId block_id)
+      -> llvm::SmallVector<SemanticsNodeId>& {
+    return node_blocks_[block_id.index];
   }
 
   // Adds an string, returning an ID to reference it.
@@ -101,6 +128,9 @@ class SemanticsIR {
 
   bool has_errors_ = false;
 
+  // Storage for callable objects.
+  llvm::SmallVector<SemanticsCallable> callables_;
+
   // Related IRs. There will always be at least 2 entries, the builtin IR (used
   // for references of builtins) followed by the current IR (used for references
   // crossing node blocks).

+ 4 - 1
toolchain/semantics/semantics_ir_test.cpp

@@ -23,6 +23,7 @@ using ::testing::AllOf;
 using ::testing::Contains;
 using ::testing::Each;
 using ::testing::ElementsAre;
+using ::testing::IsEmpty;
 using ::testing::MatchesRegex;
 using ::testing::Pair;
 
@@ -50,6 +51,7 @@ TEST(SemanticsIRTest, YAML) {
       Yaml::Value::FromText(print_output),
       ElementsAre(Yaml::Mapping(ElementsAre(
           Pair("cross_reference_irs_size", "1"),
+          Pair("callables", Yaml::Sequence(IsEmpty())),
           Pair("integer_literals", Yaml::Sequence(ElementsAre("0"))),
           Pair("strings", Yaml::Sequence(ElementsAre("x"))),
           Pair("nodes",
@@ -69,7 +71,8 @@ TEST(SemanticsIRTest, YAML) {
                        Pair("arg1", node_id), Pair("type", node_id))))))),
           // This production has only one node block.
           Pair("node_blocks",
-               Yaml::Sequence(ElementsAre(Yaml::Sequence(Each(node_id)))))))));
+               Yaml::Sequence(ElementsAre(Yaml::Sequence(IsEmpty()),
+                                          Yaml::Sequence(Each(node_id)))))))));
 }
 
 }  // namespace

+ 26 - 4
toolchain/semantics/semantics_node.h

@@ -34,6 +34,14 @@ struct SemanticsNodeId : public IndexBase {
   }
 };
 
+// The ID of a callable, such as a function.
+struct SemanticsCallableId : public IndexBase {
+  using IndexBase::IndexBase;
+  auto Print(llvm::raw_ostream& out) const -> void {
+    out << "callable" << index;
+  }
+};
+
 // The ID of a cross-referenced IR.
 struct SemanticsCrossReferenceIRId : public IndexBase {
   using IndexBase::IndexBase;
@@ -54,6 +62,14 @@ struct SemanticsIntegerLiteralId : public IndexBase {
 
 // Type-safe storage of node blocks.
 struct SemanticsNodeBlockId : public IndexBase {
+  // All SemanticsIR instances must provide the 0th node block as empty.
+  // NOLINTNEXTLINE(readability-identifier-naming)
+  static const SemanticsNodeBlockId Empty;
+
+  // An explicitly invalid ID.
+  // NOLINTNEXTLINE(readability-identifier-naming)
+  static const SemanticsNodeBlockId Invalid;
+
   using IndexBase::IndexBase;
   auto Print(llvm::raw_ostream& out) const -> void {
     out << "block";
@@ -61,6 +77,11 @@ struct SemanticsNodeBlockId : public IndexBase {
   }
 };
 
+constexpr SemanticsNodeBlockId SemanticsNodeBlockId::Empty =
+    SemanticsNodeBlockId(0);
+constexpr SemanticsNodeBlockId SemanticsNodeBlockId::Invalid =
+    SemanticsNodeBlockId();
+
 // Type-safe storage of strings.
 struct SemanticsStringId : public IndexBase {
   using IndexBase::IndexBase;
@@ -147,14 +168,15 @@ class SemanticsNode {
   }
 
   // TODO: The signature should be added as a parameter.
-  static auto MakeFunctionDeclaration(ParseTree::Node parse_node)
+  static auto MakeFunctionDeclaration(ParseTree::Node parse_node,
+                                      SemanticsCallableId signature)
       -> SemanticsNode {
     return SemanticsNode(parse_node, SemanticsNodeKind::FunctionDeclaration,
-                         SemanticsNodeId());
+                         SemanticsNodeId(), signature.index);
   }
-  auto GetAsFunctionDeclaration() const -> NoArgs {
+  auto GetAsFunctionDeclaration() const -> SemanticsCallableId {
     CARBON_CHECK(kind_ == SemanticsNodeKind::FunctionDeclaration);
-    return {};
+    return {SemanticsCallableId(arg0_)};
   }
 
   static auto MakeFunctionDefinition(ParseTree::Node parse_node,

+ 60 - 0
toolchain/semantics/semantics_node_block_stack.cpp

@@ -0,0 +1,60 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "toolchain/semantics/semantics_node_block_stack.h"
+
+#include "common/vlog.h"
+#include "toolchain/semantics/semantics_node.h"
+
+namespace Carbon {
+
+auto SemanticsNodeBlockStack::Push() -> void {
+  CARBON_VLOG() << "NodeBlock Push " << stack_.size() << "\n";
+  CARBON_CHECK(stack_.size() < (1 << 20))
+      << "Excessive stack size: likely infinite loop";
+  stack_.push_back(SemanticsNodeBlockId::Invalid);
+}
+
+auto SemanticsNodeBlockStack::PushWithUnconditionalAlloc()
+    -> SemanticsNodeBlockId {
+  SemanticsNodeBlockId block_id(node_blocks_->size());
+  CARBON_VLOG() << "NodeBlock Push " << stack_.size() << ": " << block_id
+                << "\n";
+  CARBON_CHECK(stack_.size() < (1 << 20))
+      << "Excessive stack size: likely infinite loop";
+  node_blocks_->resize(block_id.index + 1);
+  stack_.push_back(block_id);
+  return block_id;
+}
+
+auto SemanticsNodeBlockStack::PeekForAdd() -> SemanticsNodeBlockId {
+  auto& back = stack_.back();
+  if (!back.is_valid()) {
+    SemanticsNodeBlockId block_id(node_blocks_->size());
+    node_blocks_->resize(block_id.index + 1);
+    back = block_id;
+    CARBON_VLOG() << "NodeBlock Add " << stack_.size() - 1 << ": " << back
+                  << "\n";
+  }
+  return back;
+}
+
+auto SemanticsNodeBlockStack::Pop() -> SemanticsNodeBlockId {
+  auto back = stack_.pop_back_val();
+  CARBON_VLOG() << "NodeBlock Pop " << stack_.size() << ": " << back << "\n";
+  if (!back.is_valid()) {
+    return SemanticsNodeBlockId::Empty;
+  }
+  return back;
+}
+
+auto SemanticsNodeBlockStack::PrintForStackDump(llvm::raw_ostream& output) const
+    -> void {
+  output << "SemanticsNodeBlockStack:\n";
+  for (int i = 0; i < static_cast<int>(stack_.size()); ++i) {
+    output << "\t" << i << ".\t" << stack_[i] << "\n";
+  }
+}
+
+}  // namespace Carbon

+ 65 - 0
toolchain/semantics/semantics_node_block_stack.h

@@ -0,0 +1,65 @@
+// 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_SEMANTICS_SEMANTICS_NODE_BLOCK_STACK_H_
+#define CARBON_TOOLCHAIN_SEMANTICS_SEMANTICS_NODE_BLOCK_STACK_H_
+
+#include <type_traits>
+
+#include "common/check.h"
+#include "llvm/ADT/SmallVector.h"
+#include "toolchain/semantics/semantics_node.h"
+
+namespace Carbon {
+
+// Wraps the stack of node blocks for SemanticsParseTreeHandler.
+//
+// All pushes and pops will be vlogged.
+class SemanticsNodeBlockStack {
+ public:
+  explicit SemanticsNodeBlockStack(
+      llvm::SmallVector<llvm::SmallVector<SemanticsNodeId>>& node_blocks,
+      llvm::raw_ostream* vlog_stream)
+      : node_blocks_(&node_blocks), vlog_stream_(vlog_stream) {}
+
+  ~SemanticsNodeBlockStack() { CARBON_CHECK(stack_.empty()) << stack_.size(); }
+
+  // Pushes a new node block. It will be invalid unless PeekForAdd is called in
+  // order to support lazy allocation.
+  auto Push() -> void;
+
+  // Pushes a new node block.
+  // TODO: Try to remove this in favor of the lazy alloc in Push.
+  auto PushWithUnconditionalAlloc() -> SemanticsNodeBlockId;
+
+  // Peeks at the top node block. This does not trigger lazy allocation, so the
+  // returned node block may be invalid.
+  auto Peek() -> SemanticsNodeBlockId { return stack_.back(); }
+
+  // Returns the top node block, allocating one if it's still invalid.
+  auto PeekForAdd() -> SemanticsNodeBlockId;
+
+  // Pops the top node block. This will always return a valid node block;
+  // SemanticsNodeBlockId::Empty is returned if one wasn't allocated.
+  auto Pop() -> SemanticsNodeBlockId;
+
+  // Prints the stack for a stack dump.
+  auto PrintForStackDump(llvm::raw_ostream& output) const -> void;
+
+ private:
+  // The underlying node block storage on SemanticsIR. Always non-null.
+  llvm::SmallVector<llvm::SmallVector<SemanticsNodeId>>* const node_blocks_;
+
+  // Whether to print verbose output.
+  llvm::raw_ostream* vlog_stream_;
+
+  // The actual stack.
+  // PushEntry and PopEntry control modification in order to centralize
+  // vlogging.
+  llvm::SmallVector<SemanticsNodeBlockId> stack_;
+};
+
+}  // namespace Carbon
+
+#endif  // CARBON_TOOLCHAIN_SEMANTICS_SEMANTICS_NODE_BLOCK_STACK_H_

+ 47 - 22
toolchain/semantics/semantics_node_stack.cpp

@@ -5,18 +5,23 @@
 #include "toolchain/semantics/semantics_node_stack.h"
 
 #include "common/vlog.h"
-#include "llvm/Support/PrettyStackTrace.h"
 #include "toolchain/semantics/semantics_node.h"
 
 namespace Carbon {
 
-auto SemanticsNodeStack::PushEntry(Entry entry, bool is_node_id) -> void {
-  CARBON_VLOG() << "Push " << stack_.size() << ": "
+auto SemanticsNodeStack::PushEntry(Entry entry, DebugLog debug_log) -> void {
+  CARBON_VLOG() << "Node Push " << stack_.size() << ": "
                 << parse_tree_->node_kind(entry.parse_node) << " -> ";
-  if (is_node_id) {
-    CARBON_VLOG() << entry.node_id;
-  } else {
-    CARBON_VLOG() << entry.name_id;
+  switch (debug_log) {
+    case DebugLog::None:
+      CARBON_VLOG() << "<none>";
+      break;
+    case DebugLog::NodeId:
+      CARBON_VLOG() << entry.node_id;
+      break;
+    case DebugLog::NameId:
+      CARBON_VLOG() << entry.name_id;
+      break;
   }
   CARBON_VLOG() << "\n";
   CARBON_CHECK(stack_.size() < (1 << 20))
@@ -26,7 +31,7 @@ auto SemanticsNodeStack::PushEntry(Entry entry, bool is_node_id) -> void {
 
 auto SemanticsNodeStack::PopEntry() -> Entry {
   auto back = stack_.pop_back_val();
-  CARBON_VLOG() << "Pop " << stack_.size() << ": any ("
+  CARBON_VLOG() << "Node Pop " << stack_.size() << ": any ("
                 << parse_tree_->node_kind(back.parse_node) << ") -> "
                 << back.node_id << "\n";
   return back;
@@ -34,8 +39,8 @@ auto SemanticsNodeStack::PopEntry() -> Entry {
 
 auto SemanticsNodeStack::PopEntry(ParseNodeKind pop_parse_kind) -> Entry {
   auto back = stack_.pop_back_val();
-  CARBON_VLOG() << "Pop " << stack_.size() << ": " << pop_parse_kind << " -> "
-                << back.node_id << "\n";
+  CARBON_VLOG() << "Node Pop " << stack_.size() << ": " << pop_parse_kind
+                << " -> " << back.node_id << "\n";
   RequireParseKind(back, pop_parse_kind);
   return back;
 }
@@ -47,16 +52,36 @@ auto SemanticsNodeStack::RequireParseKind(Entry entry,
       << "Expected " << require_kind << ", found " << actual_kind;
 }
 
+// RequireSoloParseNode and RequireValidId rely on type punning. They read
+// node_id.is_valid, even though that may not be the active union member.
+// These asserts enforce standard layout in order to help ensure that works.
+// TODO: Use is_layout_compatible in C++20.
+static_assert(std::is_standard_layout_v<SemanticsNodeId>,
+              "Need standard layout for type punning");
+static_assert(std::is_standard_layout_v<SemanticsStringId>,
+              "Need standard layout for type punning");
+
 auto SemanticsNodeStack::RequireSoloParseNode(Entry entry) -> void {
+  // See above comment on type punning.
   CARBON_CHECK(!entry.node_id.is_valid())
-      << "Expected invalid node_id on "
-      << parse_tree_->node_kind(entry.parse_node) << ", was " << entry.node_id;
+      << "Expected invalid id on " << parse_tree_->node_kind(entry.parse_node)
+      << ", was " << entry.node_id << " (may not be node)";
 }
 
-auto SemanticsNodeStack::RequireNodeId(Entry entry) -> void {
+auto SemanticsNodeStack::RequireValidId(Entry entry) -> void {
+  // See above comment on type punning.
   CARBON_CHECK(entry.node_id.is_valid())
-      << "Expected valid node_id on "
-      << parse_tree_->node_kind(entry.parse_node);
+      << "Expected valid id on " << parse_tree_->node_kind(entry.parse_node);
+}
+
+auto SemanticsNodeStack::PopAndDiscardId() -> void {
+  auto back = PopEntry();
+  RequireValidId(back);
+}
+
+auto SemanticsNodeStack::PopAndDiscardId(ParseNodeKind pop_parse_kind) -> void {
+  auto back = PopEntry(pop_parse_kind);
+  RequireValidId(back);
 }
 
 auto SemanticsNodeStack::PopAndDiscardSoloParseNode(
@@ -80,48 +105,48 @@ auto SemanticsNodeStack::PopForSoloParseNode(ParseNodeKind pop_parse_kind)
 
 auto SemanticsNodeStack::PopForNodeId() -> SemanticsNodeId {
   auto back = PopEntry();
-  RequireNodeId(back);
+  RequireValidId(back);
   return back.node_id;
 }
 
 auto SemanticsNodeStack::PopForNodeId(ParseNodeKind pop_parse_kind)
     -> SemanticsNodeId {
   auto back = PopEntry(pop_parse_kind);
-  RequireNodeId(back);
+  RequireValidId(back);
   return back.node_id;
 }
 
 auto SemanticsNodeStack::PopForParseNodeAndNodeId()
     -> std::pair<ParseTree::Node, SemanticsNodeId> {
   auto back = PopEntry();
-  RequireNodeId(back);
+  RequireValidId(back);
   return {back.parse_node, back.node_id};
 }
 
 auto SemanticsNodeStack::PopForParseNodeAndNodeId(ParseNodeKind pop_parse_kind)
     -> std::pair<ParseTree::Node, SemanticsNodeId> {
   auto back = PopEntry(pop_parse_kind);
-  RequireNodeId(back);
+  RequireValidId(back);
   return {back.parse_node, back.node_id};
 }
 
 auto SemanticsNodeStack::PopForParseNodeAndNameId()
     -> std::pair<ParseTree::Node, SemanticsStringId> {
   auto back = PopEntry(ParseNodeKind::PatternBinding);
-  RequireNodeId(back);
+  RequireValidId(back);
   return {back.parse_node, back.name_id};
 }
 
 auto SemanticsNodeStack::PeekForNameId() -> SemanticsStringId {
   auto back = stack_.back();
   RequireParseKind(back, ParseNodeKind::PatternBinding);
-  RequireNodeId(back);
+  RequireValidId(back);
   return back.name_id;
 }
 
 auto SemanticsNodeStack::PrintForStackDump(llvm::raw_ostream& output) const
     -> void {
-  output << "node_stack_:\n";
+  output << "SemanticsNodeStack:\n";
   for (int i = 0; i < static_cast<int>(stack_.size()); ++i) {
     const auto& entry = stack_[i];
     auto parse_node_kind = parse_tree_->node_kind(entry.parse_node);

+ 32 - 15
toolchain/semantics/semantics_node_stack.h

@@ -5,6 +5,8 @@
 #ifndef CARBON_TOOLCHAIN_SEMANTICS_SEMANTICS_NODE_STACK_H_
 #define CARBON_TOOLCHAIN_SEMANTICS_SEMANTICS_NODE_STACK_H_
 
+#include <type_traits>
+
 #include "llvm/ADT/SmallVector.h"
 #include "toolchain/parser/parse_node_kind.h"
 #include "toolchain/parser/parse_tree.h"
@@ -36,21 +38,19 @@ class SemanticsNodeStack {
   auto Push(ParseTree::Node parse_node) -> void {
     PushEntry(
         {.parse_node = parse_node, .node_id = SemanticsNodeId::MakeInvalid()},
-        /*is_node_id=*/true);
+        DebugLog::None);
   }
 
   // Pushes a parse tree node onto the stack.
   auto Push(ParseTree::Node parse_node, SemanticsNodeId node_id) -> void {
-    PushEntry({.parse_node = parse_node, .node_id = node_id},
-              /*is_node_id=*/true);
+    PushEntry({.parse_node = parse_node, .node_id = node_id}, DebugLog::NodeId);
   }
 
   // Pushes a PatternBinding parse tree node onto the stack with its name.
   auto Push(ParseTree::Node parse_node, SemanticsStringId name_id) -> void {
     CARBON_CHECK(parse_tree_->node_kind(parse_node) ==
                  ParseNodeKind::PatternBinding);
-    PushEntry({.parse_node = parse_node, .name_id = name_id},
-              /*is_node_id=*/false);
+    PushEntry({.parse_node = parse_node, .name_id = name_id}, DebugLog::NameId);
   }
 
   // Pops the top of the stack without any verification.
@@ -59,6 +59,12 @@ class SemanticsNodeStack {
   // Pops the top of the stack.
   auto PopAndDiscardSoloParseNode(ParseNodeKind pop_parse_kind) -> void;
 
+  // Pops the top of the stack, and discards the ID.
+  auto PopAndDiscardId() -> void;
+
+  // Pops the top of the stack, and discards the ID.
+  auto PopAndDiscardId(ParseNodeKind pop_parse_kind) -> void;
+
   // Pops the top of the stack and returns the parse_node.
   auto PopForSoloParseNode() -> ParseTree::Node;
 
@@ -95,21 +101,34 @@ class SemanticsNodeStack {
   auto PrintForStackDump(llvm::raw_ostream& output) const -> void;
 
  private:
-  // An entry in node_stack_.
+  // An entry in stack_.
   struct Entry {
+    // The node associated with the stack entry.
     ParseTree::Node parse_node;
+
+    // The entries will evaluate as invalid if and only if they're a solo
+    // parse_node. Invalid is used instead of optional to save space.
+    //
+    // A discriminator isn't needed because the caller can determine which field
+    // is used based on the ParseNodeKind.
     union {
-      // The node_id will be invalid if and only if it's a solo parse_node.
       SemanticsNodeId node_id;
-      // The name_id is provided for PatternBindings.
+
+      // Right now name_id is exclusively for PatternBinding, which is enforced.
       SemanticsStringId name_id;
     };
   };
   static_assert(sizeof(Entry) == 8, "Unexpected Entry size");
 
-  // Pushes an entry onto the stack. is_node_id is provided for debug output
-  // only.
-  auto PushEntry(Entry entry, bool is_node_id) -> void;
+  // Which Entry union member to log.
+  enum DebugLog {
+    None,
+    NodeId,
+    NameId,
+  };
+
+  // Pushes an entry onto the stack.
+  auto PushEntry(Entry entry, DebugLog debug_log) -> void;
 
   // Pops an entry.
   auto PopEntry() -> Entry;
@@ -121,12 +140,10 @@ class SemanticsNodeStack {
   auto RequireParseKind(Entry entry, ParseNodeKind require_kind) -> void;
 
   // Requires an entry to have a invalid node_id.
-  // Also works with name_id in the union due to type compatibility.
   auto RequireSoloParseNode(Entry entry) -> void;
 
-  // Requires an entry to have a valid node_id.
-  // Also works with name_id in the union due to type compatibility.
-  auto RequireNodeId(Entry entry) -> void;
+  // Requires an entry to have a valid id.
+  auto RequireValidId(Entry entry) -> void;
 
   // The file's parse tree.
   const ParseTree* parse_tree_;

+ 101 - 51
toolchain/semantics/semantics_parse_tree_handler.cpp

@@ -4,58 +4,43 @@
 
 #include "toolchain/semantics/semantics_parse_tree_handler.h"
 
+#include <functional>
+#include <utility>
+
 #include "common/vlog.h"
 #include "llvm/Support/PrettyStackTrace.h"
 #include "toolchain/lexer/token_kind.h"
 #include "toolchain/lexer/tokenized_buffer.h"
 #include "toolchain/parser/parse_node_kind.h"
 #include "toolchain/semantics/semantics_builtin_kind.h"
+#include "toolchain/semantics/semantics_ir.h"
 #include "toolchain/semantics/semantics_node.h"
 
 namespace Carbon {
 
-class SemanticsParseTreeHandler::PrettyStackTraceNodeStack
-    : public llvm::PrettyStackTraceEntry {
+class PrettyStackTraceFunction : public llvm::PrettyStackTraceEntry {
  public:
-  explicit PrettyStackTraceNodeStack(const SemanticsParseTreeHandler* handler)
-      : handler_(handler) {}
-  ~PrettyStackTraceNodeStack() override = default;
+  explicit PrettyStackTraceFunction(std::function<void(llvm::raw_ostream&)> fn)
+      : fn_(std::move(fn)) {}
+  ~PrettyStackTraceFunction() override = default;
 
-  auto print(llvm::raw_ostream& output) const -> void override {
-    handler_->node_stack_.PrintForStackDump(output);
-  }
+  auto print(llvm::raw_ostream& output) const -> void override { fn_(output); }
 
  private:
-  const SemanticsParseTreeHandler* handler_;
-};
-
-class SemanticsParseTreeHandler::PrettyStackTraceNodeBlockStack
-    : public llvm::PrettyStackTraceEntry {
- public:
-  explicit PrettyStackTraceNodeBlockStack(
-      const SemanticsParseTreeHandler* handler)
-      : handler_(handler) {}
-  ~PrettyStackTraceNodeBlockStack() override = default;
-
-  auto print(llvm::raw_ostream& output) const -> void override {
-    output << "node_block_stack_:\n";
-    for (int i = 0; i < static_cast<int>(handler_->node_block_stack_.size());
-         ++i) {
-      const auto& entry = handler_->node_block_stack_[i];
-      output << "\t" << i << ".\t" << entry << "\n";
-    }
-  }
-
- private:
-  const SemanticsParseTreeHandler* handler_;
+  const std::function<void(llvm::raw_ostream&)> fn_;
 };
 
 auto SemanticsParseTreeHandler::Build() -> void {
-  PrettyStackTraceNodeStack pretty_node_stack(this);
-  PrettyStackTraceNodeBlockStack pretty_node_block_stack(this);
+  PrettyStackTraceFunction pretty_node_stack([&](llvm::raw_ostream& output) {
+    node_stack_.PrintForStackDump(output);
+  });
+  PrettyStackTraceFunction pretty_node_block_stack(
+      [&](llvm::raw_ostream& output) {
+        node_block_stack_.PrintForStackDump(output);
+      });
 
   // Add a block for the ParseTree.
-  node_block_stack_.push_back(semantics_->AddNodeBlock());
+  node_block_stack_.Push();
   PushScope();
 
   for (auto parse_node : parse_tree_->postorder()) {
@@ -69,8 +54,7 @@ auto SemanticsParseTreeHandler::Build() -> void {
     }
   }
 
-  node_block_stack_.pop_back();
-  CARBON_CHECK(node_block_stack_.empty()) << node_block_stack_.size();
+  node_block_stack_.Pop();
 
   PopScope();
   CARBON_CHECK(name_lookup_.empty()) << name_lookup_.size();
@@ -78,8 +62,9 @@ auto SemanticsParseTreeHandler::Build() -> void {
 }
 
 auto SemanticsParseTreeHandler::AddNode(SemanticsNode node) -> SemanticsNodeId {
-  CARBON_VLOG() << "AddNode " << current_block_id() << ": " << node << "\n";
-  return semantics_->AddNode(current_block_id(), node);
+  auto block = node_block_stack_.PeekForAdd();
+  CARBON_VLOG() << "AddNode " << block << ": " << node << "\n";
+  return semantics_->AddNode(block, node);
 }
 
 auto SemanticsParseTreeHandler::AddNodeAndPush(ParseTree::Node parse_node,
@@ -244,7 +229,7 @@ auto SemanticsParseTreeHandler::HandleExpressionStatement(
   // Pop the expression without investigating its contents.
   // TODO: This will probably eventually need to do some "do not discard"
   // analysis.
-  node_stack_.PopAndIgnore();
+  node_stack_.PopAndDiscardId();
   node_stack_.Push(parse_node);
 }
 
@@ -288,23 +273,35 @@ auto SemanticsParseTreeHandler::HandleFunctionDefinition(
   node_stack_.PopAndIgnore();
 
   PopScope();
-  node_block_stack_.pop_back();
+  node_block_stack_.Pop();
   node_stack_.Push(parse_node);
 }
 
 auto SemanticsParseTreeHandler::HandleFunctionDefinitionStart(
     ParseTree::Node parse_node) -> void {
-  node_stack_.PopAndDiscardSoloParseNode(ParseNodeKind::ParameterList);
+  node_stack_.PopForSoloParseNode(ParseNodeKind::ParameterList);
+  auto [param_ir_id, param_refs_id] = finished_params_stack_.pop_back_val();
   auto name_node = node_stack_.PopForSoloParseNode(ParseNodeKind::DeclaredName);
   auto fn_node =
       node_stack_.PopForSoloParseNode(ParseNodeKind::FunctionIntroducer);
 
-  auto decl_id = AddNode(SemanticsNode::MakeFunctionDeclaration(fn_node));
+  SemanticsCallable callable;
+  callable.param_ir_id = param_ir_id;
+  callable.param_refs_id = param_refs_id;
+  auto callable_id = semantics_->AddCallable(callable);
+  auto decl_id =
+      AddNode(SemanticsNode::MakeFunctionDeclaration(fn_node, callable_id));
   // TODO: Propagate the type of the function.
   BindName(name_node, SemanticsNodeId::MakeInvalid(), decl_id);
-  auto block_id = semantics_->AddNodeBlock();
-  AddNode(SemanticsNode::MakeFunctionDefinition(parse_node, decl_id, block_id));
-  node_block_stack_.push_back(block_id);
+
+  // TODO: Consider approaches that allow lazy creation of the definition block.
+  auto outer_block = node_block_stack_.PeekForAdd();
+  auto def_block = node_block_stack_.PushWithUnconditionalAlloc();
+  auto node =
+      SemanticsNode::MakeFunctionDefinition(parse_node, decl_id, def_block);
+  CARBON_VLOG() << "AddNode " << outer_block << ": " << node << "\n";
+  semantics_->AddNode(outer_block, node);
+
   PushScope();
   node_stack_.Push(parse_node);
 }
@@ -451,23 +448,76 @@ auto SemanticsParseTreeHandler::HandlePackageLibrary(
   CARBON_FATAL() << "TODO";
 }
 
+auto SemanticsParseTreeHandler::SaveParam() -> bool {
+  // Copy the last node added to the IR block into the params block.
+  auto ir_id = node_block_stack_.Peek();
+  if (!ir_id.is_valid()) {
+    return false;
+  }
+  auto& ir = semantics_->GetNodeBlock(ir_id);
+  CARBON_CHECK(!ir.empty())
+      << "Should only have a valid ID if a node was added";
+  auto& param = ir.back();
+  auto& params = semantics_->GetNodeBlock(params_stack_.PeekForAdd());
+  if (!params.empty() && param == params.back()) {
+    // The param was already added after a comma.
+    return false;
+  }
+  params.push_back(ir.back());
+  return true;
+}
+
 auto SemanticsParseTreeHandler::HandleParameterList(ParseTree::Node parse_node)
     -> void {
-  // TODO: This should transform into a usable parameter list. For now
-  // it's unused and only stored so that node counts match.
-  node_stack_.PopAndDiscardSoloParseNode(ParseNodeKind::ParameterListStart);
-  node_stack_.Push(parse_node);
+  // If there's a node in the IR block that has yet to be added to the params
+  // block, add it now.
+  SaveParam();
+
+  while (true) {
+    switch (auto parse_kind =
+                parse_tree_->node_kind(node_stack_.PeekParseNode())) {
+      case ParseNodeKind::ParameterListStart:
+        node_stack_.PopAndDiscardSoloParseNode(
+            ParseNodeKind::ParameterListStart);
+        finished_params_stack_.push_back(
+            {node_block_stack_.Pop(), params_stack_.Pop()});
+        node_stack_.Push(parse_node);
+        return;
+
+      case ParseNodeKind::ParameterListComma:
+        node_stack_.PopAndDiscardSoloParseNode(
+            ParseNodeKind::ParameterListComma);
+        break;
+
+      case ParseNodeKind::PatternBinding:
+        node_stack_.PopAndDiscardId(ParseNodeKind::PatternBinding);
+        break;
+
+      default:
+        // This should only occur for invalid parse trees.
+        CARBON_FATAL() << "TODO: " << parse_kind;
+    }
+  }
+
+  llvm_unreachable("loop always exits");
 }
 
 auto SemanticsParseTreeHandler::HandleParameterListComma(
-    ParseTree::Node /*parse_node*/) -> void {
-  CARBON_FATAL() << "TODO";
+    ParseTree::Node parse_node) -> void {
+  node_stack_.Push(parse_node);
+
+  // Copy the last node added to the IR block into the params block.
+  bool had_param_before_comma = SaveParam();
+  CARBON_CHECK(had_param_before_comma)
+      << "TODO: Should have a param before comma, will need error recovery";
 }
 
 auto SemanticsParseTreeHandler::HandleParameterListStart(
     ParseTree::Node parse_node) -> void {
-  // TODO: See HandleParameterList.
   node_stack_.Push(parse_node);
+
+  params_stack_.Push();
+  node_block_stack_.Push();
 }
 
 auto SemanticsParseTreeHandler::HandleParenExpression(

+ 16 - 8
toolchain/semantics/semantics_parse_tree_handler.h

@@ -11,6 +11,7 @@
 #include "toolchain/parser/parse_tree.h"
 #include "toolchain/semantics/semantics_ir.h"
 #include "toolchain/semantics/semantics_node.h"
+#include "toolchain/semantics/semantics_node_block_stack.h"
 #include "toolchain/semantics/semantics_node_stack.h"
 
 namespace Carbon {
@@ -28,7 +29,9 @@ class SemanticsParseTreeHandler {
         parse_tree_(&parse_tree),
         semantics_(&semantics),
         vlog_stream_(vlog_stream),
-        node_stack_(parse_tree, vlog_stream) {}
+        node_stack_(parse_tree, vlog_stream),
+        node_block_stack_(semantics.node_blocks_, vlog_stream),
+        params_stack_(semantics.node_blocks_, vlog_stream) {}
 
   // Outputs the ParseTree information into SemanticsIR.
   auto Build() -> void;
@@ -98,15 +101,15 @@ class SemanticsParseTreeHandler {
                          SemanticsNodeId rhs_id, bool can_convert_lhs)
       -> SemanticsNodeId;
 
+  // Saves a parameter from the top block in node_stack_ to the top block in
+  // params_stack_. Returns false if nothing is copied.
+  auto SaveParam() -> bool;
+
   // Parse node handlers.
 #define CARBON_PARSE_NODE_KIND(Name) \
   auto Handle##Name(ParseTree::Node parse_node)->void;
 #include "toolchain/parser/parse_node_kind.def"
 
-  auto current_block_id() -> SemanticsNodeBlockId {
-    return node_block_stack_.back();
-  }
-
   auto current_scope() -> ScopeStackEntry& { return scope_stack_.back(); }
 
   // Tokens for getting data on literals.
@@ -127,9 +130,14 @@ class SemanticsParseTreeHandler {
   // The stack during Build. Will contain file-level parse nodes on return.
   SemanticsNodeStack node_stack_;
 
-  // The stack of node blocks during build. Only updated on ParseTree nodes that
-  // affect the stack.
-  llvm::SmallVector<SemanticsNodeBlockId> node_block_stack_;
+  // The stack of node blocks being used for general IR generation.
+  SemanticsNodeBlockStack node_block_stack_;
+
+  // The stack of node blocks being used for parameters.
+  SemanticsNodeBlockStack params_stack_;
+
+  llvm::SmallVector<std::pair<SemanticsNodeBlockId, SemanticsNodeBlockId>>
+      finished_params_stack_;
 
   // A stack for scope context.
   llvm::SmallVector<ScopeStackEntry> scope_stack_;

+ 2 - 0
toolchain/semantics/testdata/basics/empty.carbon

@@ -5,6 +5,8 @@
 // AUTOUPDATE
 // RUN: %{carbon-run-semantics}
 // CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: callables: [
+// CHECK:STDOUT: ]
 // CHECK:STDOUT: integer_literals: [
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: strings: [

+ 2 - 0
toolchain/semantics/testdata/basics/empty_decl.carbon

@@ -5,6 +5,8 @@
 // AUTOUPDATE
 // RUN: %{carbon-run-semantics}
 // CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: callables: [
+// CHECK:STDOUT: ]
 // CHECK:STDOUT: integer_literals: [
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: strings: [

+ 7 - 2
toolchain/semantics/testdata/basics/fail_name_lookup.carbon

@@ -5,6 +5,9 @@
 // AUTOUPDATE
 // RUN: %{not} %{carbon-run-semantics}
 // CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: callables: [
+// CHECK:STDOUT:   {param_ir: block0, param_refs: block0}
+// CHECK:STDOUT: ]
 // CHECK:STDOUT: integer_literals: [
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: strings: [
@@ -15,12 +18,14 @@
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block1, type: node1},
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block2, type: node0},
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block3, type: node0},
-// CHECK:STDOUT:   {kind: FunctionDeclaration},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: callable0},
 // CHECK:STDOUT:   {kind: BindName, arg0: str0, arg1: node4},
-// CHECK:STDOUT:   {kind: FunctionDefinition, arg0: node4, arg1: block1},
+// CHECK:STDOUT:   {kind: FunctionDefinition, arg0: node4, arg1: block2},
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: node_blocks: [
 // CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
 // CHECK:STDOUT:     node4,
 // CHECK:STDOUT:     node5,
 // CHECK:STDOUT:     node6,

+ 2 - 2
toolchain/semantics/testdata/basics/verbose.carbon

@@ -6,8 +6,8 @@
 // RUN: %{carbon} -v dump semantics-ir %s | %{FileCheck-allow-unmatched}
 //
 // Only checks a couple statements in order to minimize manual update churn.
-// CHECK:STDERR: Push 0: FunctionIntroducer -> node<invalid>
-// CHECK:STDERR: AddNode block0: {kind: BindName, arg0: str0, arg1: node4}
+// CHECK:STDERR: Node Push 0: FunctionIntroducer -> <none>
+// CHECK:STDERR: AddNode block{{[0-9]+}}: {kind: BindName, arg0: str{{[0-9]+}}, arg1: node{{[0-9]+}}}
 
 fn Foo() {
   return;

+ 7 - 2
toolchain/semantics/testdata/function/basic.carbon

@@ -5,6 +5,9 @@
 // AUTOUPDATE
 // RUN: %{carbon-run-semantics}
 // CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: callables: [
+// CHECK:STDOUT:   {param_ir: block0, param_refs: block0}
+// CHECK:STDOUT: ]
 // CHECK:STDOUT: integer_literals: [
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: strings: [
@@ -15,12 +18,14 @@
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block1, type: node1},
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block2, type: node0},
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block3, type: node0},
-// CHECK:STDOUT:   {kind: FunctionDeclaration},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: callable0},
 // CHECK:STDOUT:   {kind: BindName, arg0: str0, arg1: node4},
-// CHECK:STDOUT:   {kind: FunctionDefinition, arg0: node4, arg1: block1},
+// CHECK:STDOUT:   {kind: FunctionDefinition, arg0: node4, arg1: block2},
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: node_blocks: [
 // CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
 // CHECK:STDOUT:     node4,
 // CHECK:STDOUT:     node5,
 // CHECK:STDOUT:     node6,

+ 13 - 6
toolchain/semantics/testdata/function/order.carbon

@@ -5,6 +5,11 @@
 // AUTOUPDATE
 // RUN: %{carbon-run-semantics}
 // CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: callables: [
+// CHECK:STDOUT:   {param_ir: block0, param_refs: block0}
+// CHECK:STDOUT:   {param_ir: block0, param_refs: block0}
+// CHECK:STDOUT:   {param_ir: block0, param_refs: block0}
+// CHECK:STDOUT: ]
 // CHECK:STDOUT: integer_literals: [
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: strings: [
@@ -17,18 +22,20 @@
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block1, type: node1},
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block2, type: node0},
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block3, type: node0},
-// CHECK:STDOUT:   {kind: FunctionDeclaration},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: callable0},
 // CHECK:STDOUT:   {kind: BindName, arg0: str0, arg1: node4},
-// CHECK:STDOUT:   {kind: FunctionDefinition, arg0: node4, arg1: block1},
-// CHECK:STDOUT:   {kind: FunctionDeclaration},
+// CHECK:STDOUT:   {kind: FunctionDefinition, arg0: node4, arg1: block2},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: callable1},
 // CHECK:STDOUT:   {kind: BindName, arg0: str1, arg1: node7},
-// CHECK:STDOUT:   {kind: FunctionDefinition, arg0: node7, arg1: block2},
-// CHECK:STDOUT:   {kind: FunctionDeclaration},
+// CHECK:STDOUT:   {kind: FunctionDefinition, arg0: node7, arg1: block3},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: callable2},
 // CHECK:STDOUT:   {kind: BindName, arg0: str2, arg1: node10},
-// CHECK:STDOUT:   {kind: FunctionDefinition, arg0: node10, arg1: block3},
+// CHECK:STDOUT:   {kind: FunctionDefinition, arg0: node10, arg1: block4},
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: node_blocks: [
 // CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
 // CHECK:STDOUT:     node4,
 // CHECK:STDOUT:     node5,
 // CHECK:STDOUT:     node6,

+ 47 - 0
toolchain/semantics/testdata/function/params_one.carbon

@@ -0,0 +1,47 @@
+// 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
+//
+// AUTOUPDATE
+// RUN: %{carbon-run-semantics}
+// CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: callables: [
+// CHECK:STDOUT:   {param_ir: block1, param_refs: block2}
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: integer_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: strings: [
+// CHECK:STDOUT:   a,
+// CHECK:STDOUT:   Foo,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: nodes: [
+// CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block0, type: node0},
+// CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block1, type: node1},
+// CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block2, type: node0},
+// CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block3, type: node0},
+// CHECK:STDOUT:   {kind: VarStorage, type: node2},
+// CHECK:STDOUT:   {kind: BindName, arg0: str0, arg1: node4, type: node2},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: callable0},
+// CHECK:STDOUT:   {kind: BindName, arg0: str1, arg1: node6},
+// CHECK:STDOUT:   {kind: FunctionDefinition, arg0: node6, arg1: block4},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: node_blocks: [
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node4,
+// CHECK:STDOUT:     node5,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node5,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node6,
+// CHECK:STDOUT:     node7,
+// CHECK:STDOUT:     node8,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT: ]
+
+fn Foo(a: i32) {}

+ 47 - 0
toolchain/semantics/testdata/function/params_one_comma.carbon

@@ -0,0 +1,47 @@
+// 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
+//
+// AUTOUPDATE
+// RUN: %{carbon-run-semantics}
+// CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: callables: [
+// CHECK:STDOUT:   {param_ir: block1, param_refs: block2}
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: integer_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: strings: [
+// CHECK:STDOUT:   a,
+// CHECK:STDOUT:   Foo,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: nodes: [
+// CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block0, type: node0},
+// CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block1, type: node1},
+// CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block2, type: node0},
+// CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block3, type: node0},
+// CHECK:STDOUT:   {kind: VarStorage, type: node2},
+// CHECK:STDOUT:   {kind: BindName, arg0: str0, arg1: node4, type: node2},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: callable0},
+// CHECK:STDOUT:   {kind: BindName, arg0: str1, arg1: node6},
+// CHECK:STDOUT:   {kind: FunctionDefinition, arg0: node6, arg1: block4},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: node_blocks: [
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node4,
+// CHECK:STDOUT:     node5,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node5,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node6,
+// CHECK:STDOUT:     node7,
+// CHECK:STDOUT:     node8,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT: ]
+
+fn Foo(a: i32,) {}

+ 53 - 0
toolchain/semantics/testdata/function/params_two.carbon

@@ -0,0 +1,53 @@
+// 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
+//
+// AUTOUPDATE
+// RUN: %{carbon-run-semantics}
+// CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: callables: [
+// CHECK:STDOUT:   {param_ir: block1, param_refs: block2}
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: integer_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: strings: [
+// CHECK:STDOUT:   a,
+// CHECK:STDOUT:   b,
+// CHECK:STDOUT:   Foo,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: nodes: [
+// CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block0, type: node0},
+// CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block1, type: node1},
+// CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block2, type: node0},
+// CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block3, type: node0},
+// CHECK:STDOUT:   {kind: VarStorage, type: node2},
+// CHECK:STDOUT:   {kind: BindName, arg0: str0, arg1: node4, type: node2},
+// CHECK:STDOUT:   {kind: VarStorage, type: node2},
+// CHECK:STDOUT:   {kind: BindName, arg0: str1, arg1: node6, type: node2},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: callable0},
+// CHECK:STDOUT:   {kind: BindName, arg0: str2, arg1: node8},
+// CHECK:STDOUT:   {kind: FunctionDefinition, arg0: node8, arg1: block4},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: node_blocks: [
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node4,
+// CHECK:STDOUT:     node5,
+// CHECK:STDOUT:     node6,
+// CHECK:STDOUT:     node7,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node5,
+// CHECK:STDOUT:     node7,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node8,
+// CHECK:STDOUT:     node9,
+// CHECK:STDOUT:     node10,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT: ]
+
+fn Foo(a: i32, b: i32) {}

+ 53 - 0
toolchain/semantics/testdata/function/params_two_comma.carbon

@@ -0,0 +1,53 @@
+// 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
+//
+// AUTOUPDATE
+// RUN: %{carbon-run-semantics}
+// CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: callables: [
+// CHECK:STDOUT:   {param_ir: block1, param_refs: block2}
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: integer_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: strings: [
+// CHECK:STDOUT:   a,
+// CHECK:STDOUT:   b,
+// CHECK:STDOUT:   Foo,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: nodes: [
+// CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block0, type: node0},
+// CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block1, type: node1},
+// CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block2, type: node0},
+// CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block3, type: node0},
+// CHECK:STDOUT:   {kind: VarStorage, type: node2},
+// CHECK:STDOUT:   {kind: BindName, arg0: str0, arg1: node4, type: node2},
+// CHECK:STDOUT:   {kind: VarStorage, type: node2},
+// CHECK:STDOUT:   {kind: BindName, arg0: str1, arg1: node6, type: node2},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: callable0},
+// CHECK:STDOUT:   {kind: BindName, arg0: str2, arg1: node8},
+// CHECK:STDOUT:   {kind: FunctionDefinition, arg0: node8, arg1: block4},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: node_blocks: [
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node4,
+// CHECK:STDOUT:     node5,
+// CHECK:STDOUT:     node6,
+// CHECK:STDOUT:     node7,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node5,
+// CHECK:STDOUT:     node7,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node8,
+// CHECK:STDOUT:     node9,
+// CHECK:STDOUT:     node10,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT: ]
+
+fn Foo(a: i32, b: i32,) {}

+ 7 - 2
toolchain/semantics/testdata/operators/binary_op.carbon

@@ -5,6 +5,9 @@
 // AUTOUPDATE
 // RUN: %{carbon-run-semantics}
 // CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: callables: [
+// CHECK:STDOUT:   {param_ir: block0, param_refs: block0}
+// CHECK:STDOUT: ]
 // CHECK:STDOUT: integer_literals: [
 // CHECK:STDOUT:   12,
 // CHECK:STDOUT:   34,
@@ -17,9 +20,9 @@
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block1, type: node1},
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block2, type: node0},
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block3, type: node0},
-// CHECK:STDOUT:   {kind: FunctionDeclaration},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: callable0},
 // CHECK:STDOUT:   {kind: BindName, arg0: str0, arg1: node4},
-// CHECK:STDOUT:   {kind: FunctionDefinition, arg0: node4, arg1: block1},
+// CHECK:STDOUT:   {kind: FunctionDefinition, arg0: node4, arg1: block2},
 // CHECK:STDOUT:   {kind: IntegerLiteral, arg0: int0, type: node2},
 // CHECK:STDOUT:   {kind: IntegerLiteral, arg0: int1, type: node2},
 // CHECK:STDOUT:   {kind: BinaryOperatorAdd, arg0: node7, arg1: node8, type: node2},
@@ -27,6 +30,8 @@
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: node_blocks: [
 // CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
 // CHECK:STDOUT:     node4,
 // CHECK:STDOUT:     node5,
 // CHECK:STDOUT:     node6,

+ 7 - 2
toolchain/semantics/testdata/operators/fail_type_mismatch.carbon

@@ -5,6 +5,9 @@
 // AUTOUPDATE
 // RUN: %{not} %{carbon-run-semantics}
 // CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: callables: [
+// CHECK:STDOUT:   {param_ir: block0, param_refs: block0}
+// CHECK:STDOUT: ]
 // CHECK:STDOUT: integer_literals: [
 // CHECK:STDOUT:   12,
 // CHECK:STDOUT: ]
@@ -16,9 +19,9 @@
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block1, type: node1},
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block2, type: node0},
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block3, type: node0},
-// CHECK:STDOUT:   {kind: FunctionDeclaration},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: callable0},
 // CHECK:STDOUT:   {kind: BindName, arg0: str0, arg1: node4},
-// CHECK:STDOUT:   {kind: FunctionDefinition, arg0: node4, arg1: block1},
+// CHECK:STDOUT:   {kind: FunctionDefinition, arg0: node4, arg1: block2},
 // CHECK:STDOUT:   {kind: IntegerLiteral, arg0: int0, type: node2},
 // CHECK:STDOUT:   {kind: RealLiteral, type: node3},
 // CHECK:STDOUT:   {kind: BinaryOperatorAdd, arg0: node7, arg1: node8, type: node1},
@@ -26,6 +29,8 @@
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: node_blocks: [
 // CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
 // CHECK:STDOUT:     node4,
 // CHECK:STDOUT:     node5,
 // CHECK:STDOUT:     node6,

+ 7 - 2
toolchain/semantics/testdata/operators/fail_type_mismatch_once.carbon

@@ -5,6 +5,9 @@
 // AUTOUPDATE
 // RUN: %{not} %{carbon-run-semantics}
 // CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: callables: [
+// CHECK:STDOUT:   {param_ir: block0, param_refs: block0}
+// CHECK:STDOUT: ]
 // CHECK:STDOUT: integer_literals: [
 // CHECK:STDOUT:   12,
 // CHECK:STDOUT:   12,
@@ -17,9 +20,9 @@
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block1, type: node1},
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block2, type: node0},
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block3, type: node0},
-// CHECK:STDOUT:   {kind: FunctionDeclaration},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: callable0},
 // CHECK:STDOUT:   {kind: BindName, arg0: str0, arg1: node4},
-// CHECK:STDOUT:   {kind: FunctionDefinition, arg0: node4, arg1: block1},
+// CHECK:STDOUT:   {kind: FunctionDefinition, arg0: node4, arg1: block2},
 // CHECK:STDOUT:   {kind: IntegerLiteral, arg0: int0, type: node2},
 // CHECK:STDOUT:   {kind: RealLiteral, type: node3},
 // CHECK:STDOUT:   {kind: BinaryOperatorAdd, arg0: node7, arg1: node8, type: node1},
@@ -29,6 +32,8 @@
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: node_blocks: [
 // CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
 // CHECK:STDOUT:     node4,
 // CHECK:STDOUT:     node5,
 // CHECK:STDOUT:     node6,

+ 7 - 2
toolchain/semantics/testdata/return/literal.carbon

@@ -5,6 +5,9 @@
 // AUTOUPDATE
 // RUN: %{carbon-run-semantics}
 // CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: callables: [
+// CHECK:STDOUT:   {param_ir: block0, param_refs: block0}
+// CHECK:STDOUT: ]
 // CHECK:STDOUT: integer_literals: [
 // CHECK:STDOUT:   0,
 // CHECK:STDOUT: ]
@@ -16,14 +19,16 @@
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block1, type: node1},
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block2, type: node0},
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block3, type: node0},
-// CHECK:STDOUT:   {kind: FunctionDeclaration},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: callable0},
 // CHECK:STDOUT:   {kind: BindName, arg0: str0, arg1: node4},
-// CHECK:STDOUT:   {kind: FunctionDefinition, arg0: node4, arg1: block1},
+// CHECK:STDOUT:   {kind: FunctionDefinition, arg0: node4, arg1: block2},
 // CHECK:STDOUT:   {kind: IntegerLiteral, arg0: int0, type: node2},
 // CHECK:STDOUT:   {kind: ReturnExpression, arg0: node7, type: node2},
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: node_blocks: [
 // CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
 // CHECK:STDOUT:     node4,
 // CHECK:STDOUT:     node5,
 // CHECK:STDOUT:     node6,

+ 7 - 2
toolchain/semantics/testdata/return/trivial.carbon

@@ -5,6 +5,9 @@
 // AUTOUPDATE
 // RUN: %{carbon-run-semantics}
 // CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: callables: [
+// CHECK:STDOUT:   {param_ir: block0, param_refs: block0}
+// CHECK:STDOUT: ]
 // CHECK:STDOUT: integer_literals: [
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: strings: [
@@ -15,13 +18,15 @@
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block1, type: node1},
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block2, type: node0},
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block3, type: node0},
-// CHECK:STDOUT:   {kind: FunctionDeclaration},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: callable0},
 // CHECK:STDOUT:   {kind: BindName, arg0: str0, arg1: node4},
-// CHECK:STDOUT:   {kind: FunctionDefinition, arg0: node4, arg1: block1},
+// CHECK:STDOUT:   {kind: FunctionDefinition, arg0: node4, arg1: block2},
 // CHECK:STDOUT:   {kind: Return},
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: node_blocks: [
 // CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
 // CHECK:STDOUT:     node4,
 // CHECK:STDOUT:     node5,
 // CHECK:STDOUT:     node6,

+ 7 - 2
toolchain/semantics/testdata/var/decl.carbon

@@ -5,6 +5,9 @@
 // AUTOUPDATE
 // RUN: %{carbon-run-semantics}
 // CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: callables: [
+// CHECK:STDOUT:   {param_ir: block0, param_refs: block0}
+// CHECK:STDOUT: ]
 // CHECK:STDOUT: integer_literals: [
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: strings: [
@@ -16,14 +19,16 @@
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block1, type: node1},
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block2, type: node0},
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block3, type: node0},
-// CHECK:STDOUT:   {kind: FunctionDeclaration},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: callable0},
 // CHECK:STDOUT:   {kind: BindName, arg0: str0, arg1: node4},
-// CHECK:STDOUT:   {kind: FunctionDefinition, arg0: node4, arg1: block1},
+// CHECK:STDOUT:   {kind: FunctionDefinition, arg0: node4, arg1: block2},
 // CHECK:STDOUT:   {kind: VarStorage, type: node2},
 // CHECK:STDOUT:   {kind: BindName, arg0: str1, arg1: node7, type: node2},
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: node_blocks: [
 // CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
 // CHECK:STDOUT:     node4,
 // CHECK:STDOUT:     node5,
 // CHECK:STDOUT:     node6,

+ 7 - 2
toolchain/semantics/testdata/var/decl_with_init.carbon

@@ -5,6 +5,9 @@
 // AUTOUPDATE
 // RUN: %{carbon-run-semantics}
 // CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: callables: [
+// CHECK:STDOUT:   {param_ir: block0, param_refs: block0}
+// CHECK:STDOUT: ]
 // CHECK:STDOUT: integer_literals: [
 // CHECK:STDOUT:   0,
 // CHECK:STDOUT: ]
@@ -17,9 +20,9 @@
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block1, type: node1},
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block2, type: node0},
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block3, type: node0},
-// CHECK:STDOUT:   {kind: FunctionDeclaration},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: callable0},
 // CHECK:STDOUT:   {kind: BindName, arg0: str0, arg1: node4},
-// CHECK:STDOUT:   {kind: FunctionDefinition, arg0: node4, arg1: block1},
+// CHECK:STDOUT:   {kind: FunctionDefinition, arg0: node4, arg1: block2},
 // CHECK:STDOUT:   {kind: VarStorage, type: node2},
 // CHECK:STDOUT:   {kind: BindName, arg0: str1, arg1: node7, type: node2},
 // CHECK:STDOUT:   {kind: IntegerLiteral, arg0: int0, type: node2},
@@ -27,6 +30,8 @@
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: node_blocks: [
 // CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
 // CHECK:STDOUT:     node4,
 // CHECK:STDOUT:     node5,
 // CHECK:STDOUT:     node6,

+ 7 - 2
toolchain/semantics/testdata/var/fail_duplicate_decl.carbon

@@ -5,6 +5,9 @@
 // AUTOUPDATE
 // RUN: %{not} %{carbon-run-semantics}
 // CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: callables: [
+// CHECK:STDOUT:   {param_ir: block0, param_refs: block0}
+// CHECK:STDOUT: ]
 // CHECK:STDOUT: integer_literals: [
 // CHECK:STDOUT:   0,
 // CHECK:STDOUT:   0,
@@ -18,9 +21,9 @@
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block1, type: node1},
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block2, type: node0},
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block3, type: node0},
-// CHECK:STDOUT:   {kind: FunctionDeclaration},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: callable0},
 // CHECK:STDOUT:   {kind: BindName, arg0: str0, arg1: node4},
-// CHECK:STDOUT:   {kind: FunctionDefinition, arg0: node4, arg1: block1},
+// CHECK:STDOUT:   {kind: FunctionDefinition, arg0: node4, arg1: block2},
 // CHECK:STDOUT:   {kind: VarStorage, type: node2},
 // CHECK:STDOUT:   {kind: BindName, arg0: str1, arg1: node7, type: node2},
 // CHECK:STDOUT:   {kind: IntegerLiteral, arg0: int0, type: node2},
@@ -32,6 +35,8 @@
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: node_blocks: [
 // CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
 // CHECK:STDOUT:     node4,
 // CHECK:STDOUT:     node5,
 // CHECK:STDOUT:     node6,

+ 7 - 2
toolchain/semantics/testdata/var/fail_init_type_mismatch.carbon

@@ -5,6 +5,9 @@
 // AUTOUPDATE
 // RUN: %{not} %{carbon-run-semantics}
 // CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: callables: [
+// CHECK:STDOUT:   {param_ir: block0, param_refs: block0}
+// CHECK:STDOUT: ]
 // CHECK:STDOUT: integer_literals: [
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: strings: [
@@ -16,9 +19,9 @@
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block1, type: node1},
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block2, type: node0},
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block3, type: node0},
-// CHECK:STDOUT:   {kind: FunctionDeclaration},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: callable0},
 // CHECK:STDOUT:   {kind: BindName, arg0: str0, arg1: node4},
-// CHECK:STDOUT:   {kind: FunctionDefinition, arg0: node4, arg1: block1},
+// CHECK:STDOUT:   {kind: FunctionDefinition, arg0: node4, arg1: block2},
 // CHECK:STDOUT:   {kind: VarStorage, type: node2},
 // CHECK:STDOUT:   {kind: BindName, arg0: str1, arg1: node7, type: node2},
 // CHECK:STDOUT:   {kind: RealLiteral, type: node3},
@@ -26,6 +29,8 @@
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: node_blocks: [
 // CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
 // CHECK:STDOUT:     node4,
 // CHECK:STDOUT:     node5,
 // CHECK:STDOUT:     node6,

+ 7 - 2
toolchain/semantics/testdata/var/fail_init_with_self.carbon

@@ -5,6 +5,9 @@
 // AUTOUPDATE
 // RUN: %{not} %{carbon-run-semantics}
 // CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: callables: [
+// CHECK:STDOUT:   {param_ir: block0, param_refs: block0}
+// CHECK:STDOUT: ]
 // CHECK:STDOUT: integer_literals: [
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: strings: [
@@ -16,15 +19,17 @@
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block1, type: node1},
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block2, type: node0},
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block3, type: node0},
-// CHECK:STDOUT:   {kind: FunctionDeclaration},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: callable0},
 // CHECK:STDOUT:   {kind: BindName, arg0: str0, arg1: node4},
-// CHECK:STDOUT:   {kind: FunctionDefinition, arg0: node4, arg1: block1},
+// CHECK:STDOUT:   {kind: FunctionDefinition, arg0: node4, arg1: block2},
 // CHECK:STDOUT:   {kind: VarStorage, type: node2},
 // CHECK:STDOUT:   {kind: BindName, arg0: str1, arg1: node7, type: node2},
 // CHECK:STDOUT:   {kind: Assign, arg0: node7, arg1: node1, type: node1},
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: node_blocks: [
 // CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
 // CHECK:STDOUT:     node4,
 // CHECK:STDOUT:     node5,
 // CHECK:STDOUT:     node6,

+ 7 - 2
toolchain/semantics/testdata/var/fail_lookup_outside_scope.carbon

@@ -5,6 +5,9 @@
 // AUTOUPDATE
 // RUN: %{not} %{carbon-run-semantics}
 // CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: callables: [
+// CHECK:STDOUT:   {param_ir: block0, param_refs: block0}
+// CHECK:STDOUT: ]
 // CHECK:STDOUT: integer_literals: [
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: strings: [
@@ -17,9 +20,9 @@
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block1, type: node1},
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block2, type: node0},
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block3, type: node0},
-// CHECK:STDOUT:   {kind: FunctionDeclaration},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: callable0},
 // CHECK:STDOUT:   {kind: BindName, arg0: str0, arg1: node4},
-// CHECK:STDOUT:   {kind: FunctionDefinition, arg0: node4, arg1: block1},
+// CHECK:STDOUT:   {kind: FunctionDefinition, arg0: node4, arg1: block2},
 // CHECK:STDOUT:   {kind: VarStorage, type: node2},
 // CHECK:STDOUT:   {kind: BindName, arg0: str1, arg1: node7, type: node2},
 // CHECK:STDOUT:   {kind: VarStorage, type: node2},
@@ -28,6 +31,8 @@
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: node_blocks: [
 // CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
 // CHECK:STDOUT:     node4,
 // CHECK:STDOUT:     node5,
 // CHECK:STDOUT:     node6,

+ 4 - 0
toolchain/semantics/testdata/var/global_decl.carbon

@@ -5,6 +5,8 @@
 // AUTOUPDATE
 // RUN: %{carbon-run-semantics}
 // CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: callables: [
+// CHECK:STDOUT: ]
 // CHECK:STDOUT: integer_literals: [
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: strings: [
@@ -20,6 +22,8 @@
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: node_blocks: [
 // CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
 // CHECK:STDOUT:     node4,
 // CHECK:STDOUT:     node5,
 // CHECK:STDOUT:   ],

+ 4 - 0
toolchain/semantics/testdata/var/global_decl_with_init.carbon

@@ -5,6 +5,8 @@
 // AUTOUPDATE
 // RUN: %{carbon-run-semantics}
 // CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: callables: [
+// CHECK:STDOUT: ]
 // CHECK:STDOUT: integer_literals: [
 // CHECK:STDOUT:   0,
 // CHECK:STDOUT: ]
@@ -23,6 +25,8 @@
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: node_blocks: [
 // CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
 // CHECK:STDOUT:     node4,
 // CHECK:STDOUT:     node5,
 // CHECK:STDOUT:     node6,

+ 4 - 0
toolchain/semantics/testdata/var/global_lookup.carbon

@@ -5,6 +5,8 @@
 // AUTOUPDATE
 // RUN: %{carbon-run-semantics}
 // CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: callables: [
+// CHECK:STDOUT: ]
 // CHECK:STDOUT: integer_literals: [
 // CHECK:STDOUT:   0,
 // CHECK:STDOUT: ]
@@ -27,6 +29,8 @@
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: node_blocks: [
 // CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
 // CHECK:STDOUT:     node4,
 // CHECK:STDOUT:     node5,
 // CHECK:STDOUT:     node6,

+ 7 - 2
toolchain/semantics/testdata/var/global_lookup_in_scope.carbon

@@ -5,6 +5,9 @@
 // AUTOUPDATE
 // RUN: %{carbon-run-semantics}
 // CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: callables: [
+// CHECK:STDOUT:   {param_ir: block0, param_refs: block0}
+// CHECK:STDOUT: ]
 // CHECK:STDOUT: integer_literals: [
 // CHECK:STDOUT:   0,
 // CHECK:STDOUT: ]
@@ -22,15 +25,17 @@
 // CHECK:STDOUT:   {kind: BindName, arg0: str0, arg1: node4, type: node2},
 // CHECK:STDOUT:   {kind: IntegerLiteral, arg0: int0, type: node2},
 // CHECK:STDOUT:   {kind: Assign, arg0: node4, arg1: node6, type: node2},
-// CHECK:STDOUT:   {kind: FunctionDeclaration},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: callable0},
 // CHECK:STDOUT:   {kind: BindName, arg0: str1, arg1: node8},
-// CHECK:STDOUT:   {kind: FunctionDefinition, arg0: node8, arg1: block1},
+// CHECK:STDOUT:   {kind: FunctionDefinition, arg0: node8, arg1: block2},
 // CHECK:STDOUT:   {kind: VarStorage, type: node2},
 // CHECK:STDOUT:   {kind: BindName, arg0: str2, arg1: node11, type: node2},
 // CHECK:STDOUT:   {kind: Assign, arg0: node11, arg1: node4, type: node2},
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: node_blocks: [
 // CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
 // CHECK:STDOUT:     node4,
 // CHECK:STDOUT:     node5,
 // CHECK:STDOUT:     node6,

+ 7 - 2
toolchain/semantics/testdata/var/lookup.carbon

@@ -5,6 +5,9 @@
 // AUTOUPDATE
 // RUN: %{carbon-run-semantics}
 // CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: callables: [
+// CHECK:STDOUT:   {param_ir: block0, param_refs: block0}
+// CHECK:STDOUT: ]
 // CHECK:STDOUT: integer_literals: [
 // CHECK:STDOUT:   0,
 // CHECK:STDOUT: ]
@@ -17,9 +20,9 @@
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block1, type: node1},
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block2, type: node0},
 // CHECK:STDOUT:   {kind: CrossReference, arg0: ir0, arg1: block3, type: node0},
-// CHECK:STDOUT:   {kind: FunctionDeclaration},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: callable0},
 // CHECK:STDOUT:   {kind: BindName, arg0: str0, arg1: node4},
-// CHECK:STDOUT:   {kind: FunctionDefinition, arg0: node4, arg1: block1},
+// CHECK:STDOUT:   {kind: FunctionDefinition, arg0: node4, arg1: block2},
 // CHECK:STDOUT:   {kind: VarStorage, type: node2},
 // CHECK:STDOUT:   {kind: BindName, arg0: str1, arg1: node7, type: node2},
 // CHECK:STDOUT:   {kind: IntegerLiteral, arg0: int0, type: node2},
@@ -27,6 +30,8 @@
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: node_blocks: [
 // CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
 // CHECK:STDOUT:     node4,
 // CHECK:STDOUT:     node5,
 // CHECK:STDOUT:     node6,