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

Lowering of `Branch` / `BranchIf` / `BranchWithArg` / `BlockArg`. (#2904)

This gives us complete lowering of `if` expressions plus `and` and `or`.

Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
Richard Smith 2 лет назад
Родитель
Сommit
0d4d392d12
44 измененных файлов с 453 добавлено и 91 удалено
  1. 1 0
      toolchain/lowering/BUILD
  2. 52 0
      toolchain/lowering/lowering_block_worklist.h
  3. 13 5
      toolchain/lowering/lowering_context.cpp
  4. 48 3
      toolchain/lowering/lowering_function_context.cpp
  5. 40 0
      toolchain/lowering/lowering_function_context.h
  6. 48 9
      toolchain/lowering/lowering_handle.cpp
  7. 0 2
      toolchain/lowering/testdata/basics/false_true.carbon
  8. 0 1
      toolchain/lowering/testdata/basics/zero.carbon
  9. 4 6
      toolchain/lowering/testdata/function/call/empty_struct.carbon
  10. 0 2
      toolchain/lowering/testdata/function/call/i32.carbon
  11. 0 2
      toolchain/lowering/testdata/function/call/params_one.carbon
  12. 0 2
      toolchain/lowering/testdata/function/call/params_one_comma.carbon
  13. 0 2
      toolchain/lowering/testdata/function/call/params_two.carbon
  14. 0 2
      toolchain/lowering/testdata/function/call/params_two_comma.carbon
  15. 0 2
      toolchain/lowering/testdata/function/call/params_zero.carbon
  16. 2 4
      toolchain/lowering/testdata/function/call/var_param.carbon
  17. 0 1
      toolchain/lowering/testdata/function/definition/empty_struct.carbon
  18. 0 1
      toolchain/lowering/testdata/function/definition/params_one.carbon
  19. 0 1
      toolchain/lowering/testdata/function/definition/params_two.carbon
  20. 0 1
      toolchain/lowering/testdata/function/definition/params_zero.carbon
  21. 38 0
      toolchain/lowering/testdata/if_expression/basic.carbon
  22. 45 0
      toolchain/lowering/testdata/if_expression/empty_block.carbon
  23. 35 0
      toolchain/lowering/testdata/operators/and.carbon
  24. 24 0
      toolchain/lowering/testdata/operators/and_empty_block.carbon
  25. 2 3
      toolchain/lowering/testdata/operators/not.carbon
  26. 36 0
      toolchain/lowering/testdata/operators/or.carbon
  27. 25 0
      toolchain/lowering/testdata/operators/or_empty_block.carbon
  28. 0 1
      toolchain/lowering/testdata/return/no_value.carbon
  29. 0 1
      toolchain/lowering/testdata/return/value.carbon
  30. 2 3
      toolchain/lowering/testdata/return/var.carbon
  31. 4 5
      toolchain/lowering/testdata/struct/empty.carbon
  32. 6 7
      toolchain/lowering/testdata/struct/member_access.carbon
  33. 4 5
      toolchain/lowering/testdata/struct/one_entry.carbon
  34. 4 5
      toolchain/lowering/testdata/struct/two_entries.carbon
  35. 2 3
      toolchain/lowering/testdata/var/local.carbon
  36. 2 1
      toolchain/semantics/semantics_handle.cpp
  37. 2 1
      toolchain/semantics/semantics_handle_if_expression.cpp
  38. 5 1
      toolchain/semantics/semantics_node.h
  39. 1 1
      toolchain/semantics/testdata/if_expression/basic.carbon
  40. 2 2
      toolchain/semantics/testdata/if_expression/constant_condition.carbon
  41. 1 1
      toolchain/semantics/testdata/if_expression/control_flow.carbon
  42. 3 3
      toolchain/semantics/testdata/if_expression/nested.carbon
  43. 1 1
      toolchain/semantics/testdata/operators/and.carbon
  44. 1 1
      toolchain/semantics/testdata/operators/or.carbon

+ 1 - 0
toolchain/lowering/BUILD

@@ -27,6 +27,7 @@ cc_library(
         "lowering_handle.cpp",
     ],
     hdrs = [
+        "lowering_block_worklist.h",
         "lowering_context.h",
         "lowering_function_context.h",
     ],

+ 52 - 0
toolchain/lowering/lowering_block_worklist.h

@@ -0,0 +1,52 @@
+// 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_LOWERING_LOWERING_BLOCK_WORKLIST_H_
+#define CARBON_TOOLCHAIN_LOWERING_LOWERING_BLOCK_WORKLIST_H_
+
+#include "llvm/ADT/DenseSet.h"
+#include "llvm/ADT/SmallVector.h"
+#include "toolchain/semantics/semantics_node.h"
+
+namespace Carbon {
+
+// A worklist for blocks that need to be lowered.
+//
+// The blocks form a tree, where the sequence of blocks that are pushed
+// following a `Pop` that returned block B are the children of B. Blocks are
+// popped in a preorder depth-first traversal over this tree, where blocks that
+// are children of the same block are popped in the same order in which they
+// were pushed.
+//
+// This traversal order is intended to produce readable IR:
+//
+// - In the absence of control flow back-edges, branches will typically branch
+//   to blocks emitted later, although this is not guaranteed.
+// - A branch and the blocks that it branches to will typically be placed close
+//   together.
+class LoweringBlockWorklist {
+ public:
+  // Add a block to the work list.
+  auto Push(SemanticsNodeBlockId id) -> void { worklist_.push_back(id); }
+
+  // Pop the next block to lower.
+  auto Pop() -> SemanticsNodeBlockId {
+    // Reverse the order of the blocks added since the last `Pop`, so that we
+    // pop them in the order that they were `Pushed` in.
+    std::reverse(worklist_.begin() + size_after_last_pop_, worklist_.end());
+    SemanticsNodeBlockId result = worklist_.pop_back_val();
+    size_after_last_pop_ = worklist_.size();
+    return result;
+  }
+
+  auto empty() -> bool { return worklist_.empty(); }
+
+ private:
+  llvm::SmallVector<SemanticsNodeBlockId> worklist_;
+  int size_after_last_pop_ = 0;
+};
+
+}  // namespace Carbon
+
+#endif  // CARBON_TOOLCHAIN_LOWERING_LOWERING_BLOCK_WORKLIST_H_

+ 13 - 5
toolchain/lowering/lowering_context.cpp

@@ -103,16 +103,24 @@ auto LoweringContext::BuildFunctionDefinition(SemanticsFunctionId function_id)
     function_lowering.SetLocal(param_storage, llvm_function->getArg(i));
   }
 
-  CARBON_VLOG() << "Lowering " << function.body_id << "\n";
-  for (const auto& node_id : semantics_ir().GetNodeBlock(function.body_id)) {
-    auto node = semantics_ir().GetNode(node_id);
-    CARBON_VLOG() << "Lowering " << node_id << ": " << node << "\n";
-    switch (node.kind()) {
+  // Add the entry block to the worklist.
+  function_lowering.GetBlock(function.body_id);
+
+  while (!function_lowering.block_worklist().empty()) {
+    SemanticsNodeBlockId block = function_lowering.block_worklist().Pop();
+    CARBON_VLOG() << "Lowering " << block << "\n";
+    function_lowering.builder().SetInsertPoint(
+        function_lowering.GetBlock(block));
+    for (const auto& node_id : semantics_ir().GetNodeBlock(block)) {
+      auto node = semantics_ir().GetNode(node_id);
+      CARBON_VLOG() << "Lowering " << node_id << ": " << node << "\n";
+      switch (node.kind()) {
 #define CARBON_SEMANTICS_NODE_KIND(Name)                    \
   case SemanticsNodeKind::Name:                             \
     LoweringHandle##Name(function_lowering, node_id, node); \
     break;
 #include "toolchain/semantics/semantics_node_kind.def"
+      }
     }
   }
 }

+ 48 - 3
toolchain/lowering/lowering_function_context.cpp

@@ -14,9 +14,54 @@ LoweringFunctionContext::LoweringFunctionContext(
     LoweringContext& lowering_context, llvm::Function* function)
     : lowering_context_(&lowering_context),
       function_(function),
-      builder_(lowering_context.llvm_context()) {
-  builder_.SetInsertPoint(
-      llvm::BasicBlock::Create(llvm_context(), "entry", function_));
+      builder_(lowering_context.llvm_context()) {}
+
+auto LoweringFunctionContext::GetBlock(SemanticsNodeBlockId block_id)
+    -> llvm::BasicBlock* {
+  llvm::BasicBlock*& entry = blocks_[block_id];
+  if (!entry) {
+    entry = llvm::BasicBlock::Create(llvm_context(), "", function_);
+    block_worklist().Push(block_id);
+  }
+  return entry;
+}
+
+auto LoweringFunctionContext::TryToReuseBlock(SemanticsNodeBlockId block_id,
+                                              llvm::BasicBlock* block) -> bool {
+  if (!blocks_.insert({block_id, block}).second) {
+    return false;
+  }
+  block_worklist().Push(block_id);
+  if (block == synthetic_block_) {
+    synthetic_block_ = nullptr;
+  }
+  return true;
+}
+
+auto LoweringFunctionContext::GetBlockArg(SemanticsNodeBlockId block_id,
+                                          SemanticsTypeId type_id)
+    -> llvm::PHINode* {
+  llvm::BasicBlock* block = GetBlock(block_id);
+
+  // Find the existing phi, if any.
+  auto phis = block->phis();
+  if (!phis.empty()) {
+    CARBON_CHECK(std::next(phis.begin()) == phis.end())
+        << "Expected at most one phi, found "
+        << std::distance(phis.begin(), phis.end());
+    return &*phis.begin();
+  }
+
+  // The number of predecessor slots to reserve.
+  static constexpr unsigned NumReservedPredecessors = 2;
+  auto* phi = llvm::PHINode::Create(GetType(type_id), NumReservedPredecessors);
+  phi->insertInto(block, block->begin());
+  return phi;
+}
+
+auto LoweringFunctionContext::CreateSyntheticBlock() -> llvm::BasicBlock* {
+  synthetic_block_ = llvm::BasicBlock::Create(llvm_context(), "", function_);
+  return synthetic_block_;
 }
 
 auto LoweringFunctionContext::GetLocalLoaded(SemanticsNodeId node_id)

+ 40 - 0
toolchain/lowering/lowering_function_context.h

@@ -5,9 +5,12 @@
 #ifndef CARBON_TOOLCHAIN_LOWERING_LOWERING_FUNCTION_CONTEXT_H_
 #define CARBON_TOOLCHAIN_LOWERING_LOWERING_FUNCTION_CONTEXT_H_
 
+#include <optional>
+
 #include "llvm/IR/IRBuilder.h"
 #include "llvm/IR/LLVMContext.h"
 #include "llvm/IR/Module.h"
+#include "toolchain/lowering/lowering_block_worklist.h"
 #include "toolchain/lowering/lowering_context.h"
 #include "toolchain/semantics/semantics_ir.h"
 #include "toolchain/semantics/semantics_node.h"
@@ -21,6 +24,21 @@ class LoweringFunctionContext {
   explicit LoweringFunctionContext(LoweringContext& lowering_context,
                                    llvm::Function* function);
 
+  // Returns a basic block corresponding to the start of the given semantics
+  // block, and enqueues it for emission.
+  auto GetBlock(SemanticsNodeBlockId block_id) -> llvm::BasicBlock*;
+
+  // If we have not yet allocated a `BasicBlock` for this `block_id`, set it to
+  // `block`, and enqueue `block_id` for emission. Returns whether we set the
+  // block.
+  auto TryToReuseBlock(SemanticsNodeBlockId block_id, llvm::BasicBlock* block)
+      -> bool;
+
+  // Returns a phi node corresponding to the block argument of the given basic
+  // block.
+  auto GetBlockArg(SemanticsNodeBlockId block_id, SemanticsTypeId type_id)
+      -> llvm::PHINode*;
+
   // Returns a local (versus global) value for the given node.
   auto GetLocal(SemanticsNodeId node_id) -> llvm::Value* {
     auto it = locals_.find(node_id);
@@ -48,6 +66,17 @@ class LoweringFunctionContext {
     return lowering_context_->GetType(type_id);
   }
 
+  // Create a synthetic block that corresponds to no SemanticsNodeBlockId. Such
+  // a block should only ever have a single predecessor, and is used when we
+  // need multiple `llvm::BasicBlock`s to model the linear control flow in a
+  // single SemanticsIR block.
+  auto CreateSyntheticBlock() -> llvm::BasicBlock*;
+
+  // Determine whether block is the most recently created synthetic block.
+  auto IsCurrentSyntheticBlock(llvm::BasicBlock* block) -> bool {
+    return synthetic_block_ == block;
+  }
+
   auto llvm_context() -> llvm::LLVMContext& {
     return lowering_context_->llvm_context();
   }
@@ -55,6 +84,7 @@ class LoweringFunctionContext {
     return lowering_context_->llvm_module();
   }
   auto builder() -> llvm::IRBuilder<>& { return builder_; }
+  auto block_worklist() -> LoweringBlockWorklist& { return block_worklist_; }
   auto semantics_ir() -> const SemanticsIR& {
     return lowering_context_->semantics_ir();
   }
@@ -68,6 +98,16 @@ class LoweringFunctionContext {
 
   llvm::IRBuilder<> builder_;
 
+  // A worklist for blocks we need to lower.
+  LoweringBlockWorklist block_worklist_;
+
+  // Maps a function's SemanticsIR blocks to lowered blocks.
+  llvm::DenseMap<SemanticsNodeBlockId, llvm::BasicBlock*> blocks_;
+
+  // The synthetic block we most recently created. May be null if there is no
+  // such block.
+  llvm::BasicBlock* synthetic_block_ = nullptr;
+
   // Maps a function's SemanticsIR nodes to lowered values.
   // TODO: Handle nested scopes. Right now this is just cleared at the end of
   // every block.

+ 48 - 9
toolchain/lowering/lowering_handle.cpp

@@ -38,10 +38,11 @@ auto LoweringHandleBindName(LoweringFunctionContext& /*context*/,
   // Probably need to do something here, but not necessary for now.
 }
 
-auto LoweringHandleBlockArg(LoweringFunctionContext& /*context*/,
-                            SemanticsNodeId /*node_id*/, SemanticsNode node)
+auto LoweringHandleBlockArg(LoweringFunctionContext& context,
+                            SemanticsNodeId node_id, SemanticsNode node)
     -> void {
-  CARBON_FATAL() << "TODO: Add support: " << node;
+  SemanticsNodeBlockId block_id = node.GetAsBlockArg();
+  context.SetLocal(node_id, context.GetBlockArg(block_id, node.type_id()));
 }
 
 auto LoweringHandleBoolLiteral(LoweringFunctionContext& context,
@@ -52,22 +53,60 @@ auto LoweringHandleBoolLiteral(LoweringFunctionContext& context,
   context.SetLocal(node_id, v);
 }
 
-auto LoweringHandleBranch(LoweringFunctionContext& /*context*/,
+auto LoweringHandleBranch(LoweringFunctionContext& context,
                           SemanticsNodeId /*node_id*/, SemanticsNode node)
     -> void {
-  CARBON_FATAL() << "TODO: Add support: " << node;
+  SemanticsNodeBlockId target_block_id = node.GetAsBranch();
+
+  // Opportunistically avoid creating a BasicBlock that contains just a branch.
+  llvm::BasicBlock* block = context.builder().GetInsertBlock();
+  if (block->empty() && context.TryToReuseBlock(target_block_id, block)) {
+    // Reuse this block as the branch target.
+  } else {
+    context.builder().CreateBr(context.GetBlock(target_block_id));
+  }
+
+  context.builder().ClearInsertionPoint();
 }
 
-auto LoweringHandleBranchIf(LoweringFunctionContext& /*context*/,
+auto LoweringHandleBranchIf(LoweringFunctionContext& context,
                             SemanticsNodeId /*node_id*/, SemanticsNode node)
     -> void {
-  CARBON_FATAL() << "TODO: Add support: " << node;
+  auto [target_block_id, cond_id] = node.GetAsBranchIf();
+  llvm::Value* cond = context.GetLocalLoaded(cond_id);
+  llvm::BasicBlock* then_block = context.GetBlock(target_block_id);
+  llvm::BasicBlock* else_block = context.CreateSyntheticBlock();
+  context.builder().CreateCondBr(cond, then_block, else_block);
+  context.builder().SetInsertPoint(else_block);
 }
 
-auto LoweringHandleBranchWithArg(LoweringFunctionContext& /*context*/,
+auto LoweringHandleBranchWithArg(LoweringFunctionContext& context,
                                  SemanticsNodeId /*node_id*/,
                                  SemanticsNode node) -> void {
-  CARBON_FATAL() << "TODO: Add support: " << node;
+  auto [target_block_id, arg_id] = node.GetAsBranchWithArg();
+  llvm::Value* arg = context.GetLocalLoaded(arg_id);
+  SemanticsTypeId arg_type_id =
+      context.semantics_ir().GetNode(arg_id).type_id();
+
+  // Opportunistically avoid creating a BasicBlock that contains just a branch.
+  // We only do this for a block that we know will only have a single
+  // predecessor, so that we can correctly populate the predecessors of the
+  // PHINode.
+  llvm::BasicBlock* block = context.builder().GetInsertBlock();
+  llvm::BasicBlock* phi_predecessor = block;
+  if (block->empty() && context.IsCurrentSyntheticBlock(block) &&
+      context.TryToReuseBlock(target_block_id, block)) {
+    // Reuse this block as the branch target.
+    phi_predecessor = block->getSinglePredecessor();
+    CARBON_CHECK(phi_predecessor)
+        << "Synthetic block did not have a single predecessor";
+  } else {
+    context.builder().CreateBr(context.GetBlock(target_block_id));
+  }
+
+  context.GetBlockArg(target_block_id, arg_type_id)
+      ->addIncoming(arg, phi_predecessor);
+  context.builder().ClearInsertionPoint();
 }
 
 auto LoweringHandleBuiltin(LoweringFunctionContext& /*context*/,

+ 0 - 2
toolchain/lowering/testdata/basics/false_true.carbon

@@ -7,12 +7,10 @@
 // CHECK:STDOUT: source_filename = "false_true.carbon"
 // CHECK:STDOUT:
 // CHECK:STDOUT: define i1 @F() {
-// CHECK:STDOUT: entry:
 // CHECK:STDOUT:   ret i1 false
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: define i1 @T() {
-// CHECK:STDOUT: entry:
 // CHECK:STDOUT:   ret i1 true
 // CHECK:STDOUT: }
 

+ 0 - 1
toolchain/lowering/testdata/basics/zero.carbon

@@ -7,7 +7,6 @@
 // CHECK:STDOUT: source_filename = "zero.carbon"
 // CHECK:STDOUT:
 // CHECK:STDOUT: define i32 @Main() {
-// CHECK:STDOUT: entry:
 // CHECK:STDOUT:   ret i32 0
 // CHECK:STDOUT: }
 

+ 4 - 6
toolchain/lowering/testdata/function/call/empty_struct.carbon

@@ -10,19 +10,17 @@
 // CHECK:STDOUT: %EmptyTupleType = type {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: define %StructLiteralType @Echo(%StructLiteralType %a) {
-// CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %StructLiteralValue = alloca %StructLiteralType, align 8
-// CHECK:STDOUT:   %0 = load %StructLiteralType, ptr %StructLiteralValue, align 1
-// CHECK:STDOUT:   ret %StructLiteralType %0
+// CHECK:STDOUT:   %1 = load %StructLiteralType, ptr %StructLiteralValue, align 1
+// CHECK:STDOUT:   ret %StructLiteralType %1
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: define %EmptyTupleType @Main() {
-// CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %StructLiteralValue = alloca %StructLiteralType, align 8
 // CHECK:STDOUT:   %var = alloca %StructLiteralType, align 8
 // CHECK:STDOUT:   %StructLiteralValue1 = alloca %StructLiteralType, align 8
-// CHECK:STDOUT:   %0 = load %StructLiteralType, ptr %StructLiteralValue1, align 1
-// CHECK:STDOUT:   %Echo = call %StructLiteralType @Echo(%StructLiteralType %0)
+// CHECK:STDOUT:   %1 = load %StructLiteralType, ptr %StructLiteralValue1, align 1
+// CHECK:STDOUT:   %Echo = call %StructLiteralType @Echo(%StructLiteralType %1)
 // CHECK:STDOUT:   store %StructLiteralType %Echo, ptr %var, align 1
 // CHECK:STDOUT: }
 

+ 0 - 2
toolchain/lowering/testdata/function/call/i32.carbon

@@ -9,12 +9,10 @@
 // CHECK:STDOUT: %EmptyTupleType = type {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: define i32 @Echo(i32 %a) {
-// CHECK:STDOUT: entry:
 // CHECK:STDOUT:   ret i32 %a
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: define %EmptyTupleType @Main() {
-// CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %var = alloca i32, align 4
 // CHECK:STDOUT:   %Echo = call i32 @Echo(i32 1)
 // CHECK:STDOUT:   store i32 %Echo, ptr %var, align 4

+ 0 - 2
toolchain/lowering/testdata/function/call/params_one.carbon

@@ -9,11 +9,9 @@
 // CHECK:STDOUT: %EmptyTupleType = type {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: define %EmptyTupleType @Foo(i32 %a) {
-// CHECK:STDOUT: entry:
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: define %EmptyTupleType @Main() {
-// CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %Foo = call %EmptyTupleType @Foo(i32 1)
 // CHECK:STDOUT: }
 

+ 0 - 2
toolchain/lowering/testdata/function/call/params_one_comma.carbon

@@ -9,11 +9,9 @@
 // CHECK:STDOUT: %EmptyTupleType = type {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: define %EmptyTupleType @Foo(i32 %a) {
-// CHECK:STDOUT: entry:
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: define %EmptyTupleType @Main() {
-// CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %Foo = call %EmptyTupleType @Foo(i32 1)
 // CHECK:STDOUT:   %Foo1 = call %EmptyTupleType @Foo(i32 1)
 // CHECK:STDOUT: }

+ 0 - 2
toolchain/lowering/testdata/function/call/params_two.carbon

@@ -9,11 +9,9 @@
 // CHECK:STDOUT: %EmptyTupleType = type {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: define %EmptyTupleType @Foo(i32 %a, i32 %b) {
-// CHECK:STDOUT: entry:
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: define %EmptyTupleType @Main() {
-// CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %Foo = call %EmptyTupleType @Foo(i32 1, i32 2)
 // CHECK:STDOUT: }
 

+ 0 - 2
toolchain/lowering/testdata/function/call/params_two_comma.carbon

@@ -9,11 +9,9 @@
 // CHECK:STDOUT: %EmptyTupleType = type {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: define %EmptyTupleType @Foo(i32 %a, i32 %b) {
-// CHECK:STDOUT: entry:
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: define %EmptyTupleType @Main() {
-// CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %Foo = call %EmptyTupleType @Foo(i32 1, i32 2)
 // CHECK:STDOUT:   %Foo1 = call %EmptyTupleType @Foo(i32 1, i32 2)
 // CHECK:STDOUT: }

+ 0 - 2
toolchain/lowering/testdata/function/call/params_zero.carbon

@@ -9,11 +9,9 @@
 // CHECK:STDOUT: %EmptyTupleType = type {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: define %EmptyTupleType @Foo() {
-// CHECK:STDOUT: entry:
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: define %EmptyTupleType @Main() {
-// CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %Foo = call %EmptyTupleType @Foo()
 // CHECK:STDOUT: }
 

+ 2 - 4
toolchain/lowering/testdata/function/call/var_param.carbon

@@ -9,15 +9,13 @@
 // CHECK:STDOUT: %EmptyTupleType = type {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: define %EmptyTupleType @DoNothing(i32 %a) {
-// CHECK:STDOUT: entry:
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: define %EmptyTupleType @Main() {
-// CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %var = alloca i32, align 4
 // CHECK:STDOUT:   store i32 0, ptr %var, align 4
-// CHECK:STDOUT:   %0 = load i32, ptr %var, align 4
-// CHECK:STDOUT:   %DoNothing = call %EmptyTupleType @DoNothing(i32 %0)
+// CHECK:STDOUT:   %1 = load i32, ptr %var, align 4
+// CHECK:STDOUT:   %DoNothing = call %EmptyTupleType @DoNothing(i32 %1)
 // CHECK:STDOUT: }
 
 fn DoNothing(a: i32) {}

+ 0 - 1
toolchain/lowering/testdata/function/definition/empty_struct.carbon

@@ -10,7 +10,6 @@
 // CHECK:STDOUT: %StructLiteralType = type {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: define %EmptyTupleType @Echo(%StructLiteralType %a) {
-// CHECK:STDOUT: entry:
 // CHECK:STDOUT: }
 
 fn Echo(a: {}) {

+ 0 - 1
toolchain/lowering/testdata/function/definition/params_one.carbon

@@ -9,7 +9,6 @@
 // CHECK:STDOUT: %EmptyTupleType = type {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: define %EmptyTupleType @Foo(i32 %a) {
-// CHECK:STDOUT: entry:
 // CHECK:STDOUT: }
 
 fn Foo(a: i32) {}

+ 0 - 1
toolchain/lowering/testdata/function/definition/params_two.carbon

@@ -9,7 +9,6 @@
 // CHECK:STDOUT: %EmptyTupleType = type {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: define %EmptyTupleType @Foo(i32 %a, i32 %b) {
-// CHECK:STDOUT: entry:
 // CHECK:STDOUT: }
 
 fn Foo(a: i32, b: i32) {}

+ 0 - 1
toolchain/lowering/testdata/function/definition/params_zero.carbon

@@ -9,7 +9,6 @@
 // CHECK:STDOUT: %EmptyTupleType = type {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: define %EmptyTupleType @Foo() {
-// CHECK:STDOUT: entry:
 // CHECK:STDOUT: }
 
 fn Foo() {}

+ 38 - 0
toolchain/lowering/testdata/if_expression/basic.carbon

@@ -0,0 +1,38 @@
+// 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
+// CHECK:STDOUT: ; ModuleID = 'basic.carbon'
+// CHECK:STDOUT: source_filename = "basic.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i32 @F() {
+// CHECK:STDOUT:   ret i32 1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i32 @G() {
+// CHECK:STDOUT:   ret i32 2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i32 @Select(i1 %b) {
+// CHECK:STDOUT:   br i1 %b, label %1, label %2
+// CHECK:STDOUT:
+// CHECK:STDOUT: 1:                                                ; preds = %0
+// CHECK:STDOUT:   %F = call i32 @F()
+// CHECK:STDOUT:   br label %3
+// CHECK:STDOUT:
+// CHECK:STDOUT: 2:                                                ; preds = %0
+// CHECK:STDOUT:   %G = call i32 @G()
+// CHECK:STDOUT:   br label %3
+// CHECK:STDOUT:
+// CHECK:STDOUT: 3:                                                ; preds = %2, %1
+// CHECK:STDOUT:   %4 = phi i32 [ %F, %1 ], [ %G, %2 ]
+// CHECK:STDOUT:   ret i32 %4
+// CHECK:STDOUT: }
+
+fn F() -> i32 { return 1; }
+fn G() -> i32 { return 2; }
+
+fn Select(b: bool) -> i32 {
+  return if b then F() else G();
+}

+ 45 - 0
toolchain/lowering/testdata/if_expression/empty_block.carbon

@@ -0,0 +1,45 @@
+// 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
+// CHECK:STDOUT: ; ModuleID = 'empty_block.carbon'
+// CHECK:STDOUT: source_filename = "empty_block.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i32 @Select(i1 %b, i1 %c, i1 %d) {
+// CHECK:STDOUT:   br i1 %b, label %1, label %2
+// CHECK:STDOUT:
+// CHECK:STDOUT: 1:                                                ; preds = %0
+// CHECK:STDOUT:   br i1 %c, label %3, label %4
+// CHECK:STDOUT:
+// CHECK:STDOUT: 2:                                                ; preds = %0
+// CHECK:STDOUT:   br i1 %d, label %9, label %10
+// CHECK:STDOUT:
+// CHECK:STDOUT: 3:                                                ; preds = %1
+// CHECK:STDOUT:   br label %5
+// CHECK:STDOUT:
+// CHECK:STDOUT: 4:                                                ; preds = %1
+// CHECK:STDOUT:   br label %5
+// CHECK:STDOUT:
+// CHECK:STDOUT: 5:                                                ; preds = %4, %3
+// CHECK:STDOUT:   %6 = phi i32 [ 1, %3 ], [ 2, %4 ]
+// CHECK:STDOUT:   br label %7
+// CHECK:STDOUT:
+// CHECK:STDOUT: 7:                                                ; preds = %11, %5
+// CHECK:STDOUT:   %8 = phi i32 [ %6, %5 ], [ %12, %11 ]
+// CHECK:STDOUT:   ret i32 %8
+// CHECK:STDOUT:
+// CHECK:STDOUT: 9:                                                ; preds = %2
+// CHECK:STDOUT:   br label %11
+// CHECK:STDOUT:
+// CHECK:STDOUT: 10:                                               ; preds = %2
+// CHECK:STDOUT:   br label %11
+// CHECK:STDOUT:
+// CHECK:STDOUT: 11:                                               ; preds = %10, %9
+// CHECK:STDOUT:   %12 = phi i32 [ 3, %9 ], [ 4, %10 ]
+// CHECK:STDOUT:   br label %7
+// CHECK:STDOUT: }
+
+fn Select(b: bool, c: bool, d: bool) -> i32 {
+  return if b then if c then 1 else 2 else if d then 3 else 4;
+}

+ 35 - 0
toolchain/lowering/testdata/operators/and.carbon

@@ -0,0 +1,35 @@
+// 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
+// CHECK:STDOUT: ; ModuleID = 'and.carbon'
+// CHECK:STDOUT: source_filename = "and.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i1 @F() {
+// CHECK:STDOUT:   ret i1 true
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i1 @G() {
+// CHECK:STDOUT:   ret i1 true
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i1 @And() {
+// CHECK:STDOUT:   %F = call i1 @F()
+// CHECK:STDOUT:   br i1 %F, label %1, label %2
+// CHECK:STDOUT:
+// CHECK:STDOUT: 1:                                                ; preds = %0
+// CHECK:STDOUT:   %G = call i1 @G()
+// CHECK:STDOUT:   br label %2
+// CHECK:STDOUT:
+// CHECK:STDOUT: 2:                                                ; preds = %1, %0
+// CHECK:STDOUT:   %3 = phi i1 [ false, %0 ], [ %G, %1 ]
+// CHECK:STDOUT:   ret i1 %3
+// CHECK:STDOUT: }
+
+fn F() -> bool { return true; }
+fn G() -> bool { return true; }
+
+fn And() -> bool {
+  return F() and G();
+}

+ 24 - 0
toolchain/lowering/testdata/operators/and_empty_block.carbon

@@ -0,0 +1,24 @@
+// 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
+// CHECK:STDOUT: ; ModuleID = 'and_empty_block.carbon'
+// CHECK:STDOUT: source_filename = "and_empty_block.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i1 @And(i1 %b, i1 %c) {
+// CHECK:STDOUT:   br i1 %b, label %1, label %2
+// CHECK:STDOUT:
+// CHECK:STDOUT: 1:                                                ; preds = %0
+// CHECK:STDOUT:   br label %2
+// CHECK:STDOUT:
+// CHECK:STDOUT: 2:                                                ; preds = %1, %0
+// CHECK:STDOUT:   %3 = phi i1 [ false, %0 ], [ %c, %1 ]
+// CHECK:STDOUT:   ret i1 %3
+// CHECK:STDOUT: }
+
+fn And(b: bool, c: bool) -> bool {
+  // Note that in this case, we must generate an empty block so that the `phi`
+  // has two distinct predecessors.
+  return b and c;
+}

+ 2 - 3
toolchain/lowering/testdata/operators/not.carbon

@@ -7,9 +7,8 @@
 // CHECK:STDOUT: source_filename = "not.carbon"
 // CHECK:STDOUT:
 // CHECK:STDOUT: define i1 @Not(i1 %b) {
-// CHECK:STDOUT: entry:
-// CHECK:STDOUT:   %0 = xor i1 %b, true
-// CHECK:STDOUT:   ret i1 %0
+// CHECK:STDOUT:   %1 = xor i1 %b, true
+// CHECK:STDOUT:   ret i1 %1
 // CHECK:STDOUT: }
 
 fn Not(b: bool) -> bool {

+ 36 - 0
toolchain/lowering/testdata/operators/or.carbon

@@ -0,0 +1,36 @@
+// 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
+// CHECK:STDOUT: ; ModuleID = 'or.carbon'
+// CHECK:STDOUT: source_filename = "or.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i1 @F() {
+// CHECK:STDOUT:   ret i1 true
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i1 @G() {
+// CHECK:STDOUT:   ret i1 true
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i1 @Or() {
+// CHECK:STDOUT:   %F = call i1 @F()
+// CHECK:STDOUT:   %1 = xor i1 %F, true
+// CHECK:STDOUT:   br i1 %1, label %2, label %3
+// CHECK:STDOUT:
+// CHECK:STDOUT: 2:                                                ; preds = %0
+// CHECK:STDOUT:   %G = call i1 @G()
+// CHECK:STDOUT:   br label %3
+// CHECK:STDOUT:
+// CHECK:STDOUT: 3:                                                ; preds = %2, %0
+// CHECK:STDOUT:   %4 = phi i1 [ true, %0 ], [ %G, %2 ]
+// CHECK:STDOUT:   ret i1 %4
+// CHECK:STDOUT: }
+
+fn F() -> bool { return true; }
+fn G() -> bool { return true; }
+
+fn Or() -> bool {
+  return F() or G();
+}

+ 25 - 0
toolchain/lowering/testdata/operators/or_empty_block.carbon

@@ -0,0 +1,25 @@
+// 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
+// CHECK:STDOUT: ; ModuleID = 'or_empty_block.carbon'
+// CHECK:STDOUT: source_filename = "or_empty_block.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i1 @Or(i1 %b, i1 %c) {
+// CHECK:STDOUT:   %1 = xor i1 %b, true
+// CHECK:STDOUT:   br i1 %1, label %2, label %3
+// CHECK:STDOUT:
+// CHECK:STDOUT: 2:                                                ; preds = %0
+// CHECK:STDOUT:   br label %3
+// CHECK:STDOUT:
+// CHECK:STDOUT: 3:                                                ; preds = %2, %0
+// CHECK:STDOUT:   %4 = phi i1 [ true, %0 ], [ %c, %2 ]
+// CHECK:STDOUT:   ret i1 %4
+// CHECK:STDOUT: }
+
+fn Or(b: bool, c: bool) -> bool {
+  // Note that in this case, we must generate an empty block so that the `phi`
+  // has two distinct predecessors.
+  return b or c;
+}

+ 0 - 1
toolchain/lowering/testdata/return/no_value.carbon

@@ -9,7 +9,6 @@
 // CHECK:STDOUT: %EmptyTupleType = type {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: define %EmptyTupleType @Main() {
-// CHECK:STDOUT: entry:
 // CHECK:STDOUT:   ret void
 // CHECK:STDOUT: }
 

+ 0 - 1
toolchain/lowering/testdata/return/value.carbon

@@ -7,7 +7,6 @@
 // CHECK:STDOUT: source_filename = "value.carbon"
 // CHECK:STDOUT:
 // CHECK:STDOUT: define i32 @Main() {
-// CHECK:STDOUT: entry:
 // CHECK:STDOUT:   ret i32 0
 // CHECK:STDOUT: }
 

+ 2 - 3
toolchain/lowering/testdata/return/var.carbon

@@ -7,11 +7,10 @@
 // CHECK:STDOUT: source_filename = "var.carbon"
 // CHECK:STDOUT:
 // CHECK:STDOUT: define i32 @Main() {
-// CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %var = alloca i32, align 4
 // CHECK:STDOUT:   store i32 0, ptr %var, align 4
-// CHECK:STDOUT:   %0 = load i32, ptr %var, align 4
-// CHECK:STDOUT:   ret i32 %0
+// CHECK:STDOUT:   %1 = load i32, ptr %var, align 4
+// CHECK:STDOUT:   ret i32 %1
 // CHECK:STDOUT: }
 
 fn Main() -> i32 {

+ 4 - 5
toolchain/lowering/testdata/struct/empty.carbon

@@ -9,16 +9,15 @@
 // CHECK:STDOUT: %StructLiteralType = type {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: define i32 @Run() {
-// CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %StructLiteralValue = alloca %StructLiteralType, align 8
 // CHECK:STDOUT:   %var = alloca %StructLiteralType, align 8
 // CHECK:STDOUT:   %StructLiteralValue1 = alloca %StructLiteralType, align 8
-// CHECK:STDOUT:   %0 = load %StructLiteralType, ptr %StructLiteralValue1, align 1
-// CHECK:STDOUT:   store %StructLiteralType %0, ptr %var, align 1
+// CHECK:STDOUT:   %1 = load %StructLiteralType, ptr %StructLiteralValue1, align 1
+// CHECK:STDOUT:   store %StructLiteralType %1, ptr %var, align 1
 // CHECK:STDOUT:   %StructLiteralValue2 = alloca %StructLiteralType, align 8
 // CHECK:STDOUT:   %var3 = alloca %StructLiteralType, align 8
-// CHECK:STDOUT:   %1 = load %StructLiteralType, ptr %var, align 1
-// CHECK:STDOUT:   store %StructLiteralType %1, ptr %var3, align 1
+// CHECK:STDOUT:   %2 = load %StructLiteralType, ptr %var, align 1
+// CHECK:STDOUT:   store %StructLiteralType %2, ptr %var3, align 1
 // CHECK:STDOUT:   ret i32 0
 // CHECK:STDOUT: }
 

+ 6 - 7
toolchain/lowering/testdata/struct/member_access.carbon

@@ -9,22 +9,21 @@
 // CHECK:STDOUT: %StructLiteralType = type { double, i32 }
 // CHECK:STDOUT:
 // CHECK:STDOUT: define i32 @Run() {
-// CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %var = alloca %StructLiteralType, align 8
 // CHECK:STDOUT:   %StructLiteralValue = alloca %StructLiteralType, align 8
 // CHECK:STDOUT:   %a = getelementptr inbounds %StructLiteralType, ptr %StructLiteralValue, i32 0, i32 0
 // CHECK:STDOUT:   store double 0.000000e+00, ptr %a, align 8
 // CHECK:STDOUT:   %b = getelementptr inbounds %StructLiteralType, ptr %StructLiteralValue, i32 0, i32 1
 // CHECK:STDOUT:   store i32 1, ptr %b, align 4
-// CHECK:STDOUT:   %0 = load %StructLiteralType, ptr %StructLiteralValue, align 8
-// CHECK:STDOUT:   store %StructLiteralType %0, ptr %var, align 8
+// CHECK:STDOUT:   %1 = load %StructLiteralType, ptr %StructLiteralValue, align 8
+// CHECK:STDOUT:   store %StructLiteralType %1, ptr %var, align 8
 // CHECK:STDOUT:   %var1 = alloca i32, align 4
 // CHECK:STDOUT:   %b2 = getelementptr inbounds %StructLiteralType, ptr %var, i32 0, i32 1
-// CHECK:STDOUT:   %1 = load i32, ptr %b2, align 4
-// CHECK:STDOUT:   store i32 %1, ptr %var1, align 4
+// CHECK:STDOUT:   %2 = load i32, ptr %b2, align 4
+// CHECK:STDOUT:   store i32 %2, ptr %var1, align 4
 // CHECK:STDOUT:   %var3 = alloca i32, align 4
-// CHECK:STDOUT:   %2 = load i32, ptr %var1, align 4
-// CHECK:STDOUT:   store i32 %2, ptr %var3, align 4
+// CHECK:STDOUT:   %3 = load i32, ptr %var1, align 4
+// CHECK:STDOUT:   store i32 %3, ptr %var3, align 4
 // CHECK:STDOUT:   ret i32 0
 // CHECK:STDOUT: }
 

+ 4 - 5
toolchain/lowering/testdata/struct/one_entry.carbon

@@ -9,16 +9,15 @@
 // CHECK:STDOUT: %StructLiteralType = type { i32 }
 // CHECK:STDOUT:
 // CHECK:STDOUT: define i32 @Run() {
-// CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %var = alloca %StructLiteralType, align 8
 // CHECK:STDOUT:   %StructLiteralValue = alloca %StructLiteralType, align 8
 // CHECK:STDOUT:   %a = getelementptr inbounds %StructLiteralType, ptr %StructLiteralValue, i32 0, i32 0
 // CHECK:STDOUT:   store i32 4, ptr %a, align 4
-// CHECK:STDOUT:   %0 = load %StructLiteralType, ptr %StructLiteralValue, align 4
-// CHECK:STDOUT:   store %StructLiteralType %0, ptr %var, align 4
+// CHECK:STDOUT:   %1 = load %StructLiteralType, ptr %StructLiteralValue, align 4
+// CHECK:STDOUT:   store %StructLiteralType %1, ptr %var, align 4
 // CHECK:STDOUT:   %var1 = alloca %StructLiteralType, align 8
-// CHECK:STDOUT:   %1 = load %StructLiteralType, ptr %var, align 4
-// CHECK:STDOUT:   store %StructLiteralType %1, ptr %var1, align 4
+// CHECK:STDOUT:   %2 = load %StructLiteralType, ptr %var, align 4
+// CHECK:STDOUT:   store %StructLiteralType %2, ptr %var1, align 4
 // CHECK:STDOUT:   ret i32 0
 // CHECK:STDOUT: }
 

+ 4 - 5
toolchain/lowering/testdata/struct/two_entries.carbon

@@ -9,18 +9,17 @@
 // CHECK:STDOUT: %StructLiteralType = type { i32, i32 }
 // CHECK:STDOUT:
 // CHECK:STDOUT: define i32 @Run() {
-// CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %var = alloca %StructLiteralType, align 8
 // CHECK:STDOUT:   %StructLiteralValue = alloca %StructLiteralType, align 8
 // CHECK:STDOUT:   %a = getelementptr inbounds %StructLiteralType, ptr %StructLiteralValue, i32 0, i32 0
 // CHECK:STDOUT:   store i32 1, ptr %a, align 4
 // CHECK:STDOUT:   %b = getelementptr inbounds %StructLiteralType, ptr %StructLiteralValue, i32 0, i32 1
 // CHECK:STDOUT:   store i32 2, ptr %b, align 4
-// CHECK:STDOUT:   %0 = load %StructLiteralType, ptr %StructLiteralValue, align 4
-// CHECK:STDOUT:   store %StructLiteralType %0, ptr %var, align 4
+// CHECK:STDOUT:   %1 = load %StructLiteralType, ptr %StructLiteralValue, align 4
+// CHECK:STDOUT:   store %StructLiteralType %1, ptr %var, align 4
 // CHECK:STDOUT:   %var1 = alloca %StructLiteralType, align 8
-// CHECK:STDOUT:   %1 = load %StructLiteralType, ptr %var, align 4
-// CHECK:STDOUT:   store %StructLiteralType %1, ptr %var1, align 4
+// CHECK:STDOUT:   %2 = load %StructLiteralType, ptr %var, align 4
+// CHECK:STDOUT:   store %StructLiteralType %2, ptr %var1, align 4
 // CHECK:STDOUT:   ret i32 0
 // CHECK:STDOUT: }
 

+ 2 - 3
toolchain/lowering/testdata/var/local.carbon

@@ -7,11 +7,10 @@
 // CHECK:STDOUT: source_filename = "local.carbon"
 // CHECK:STDOUT:
 // CHECK:STDOUT: define i32 @Run() {
-// CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %var = alloca i32, align 4
 // CHECK:STDOUT:   store i32 1, ptr %var, align 4
-// CHECK:STDOUT:   %0 = load i32, ptr %var, align 4
-// CHECK:STDOUT:   ret i32 %0
+// CHECK:STDOUT:   %1 = load i32, ptr %var, align 4
+// CHECK:STDOUT:   ret i32 %1
 // CHECK:STDOUT: }
 
 fn Run() -> i32 {

+ 2 - 1
toolchain/semantics/semantics_handle.cpp

@@ -228,7 +228,8 @@ auto SemanticsHandleInfixOperator(SemanticsContext& context,
       context.AddNodeAndPush(
           parse_node,
           SemanticsNode::BlockArg::Make(
-              parse_node, context.semantics_ir().GetNode(rhs_id).type_id()));
+              parse_node, context.semantics_ir().GetNode(rhs_id).type_id(),
+              resume_block_id));
       break;
     }
 

+ 2 - 1
toolchain/semantics/semantics_handle_if_expression.cpp

@@ -68,7 +68,8 @@ auto SemanticsHandleIfExpressionElse(SemanticsContext& context,
 
   // Obtain the value in the resumption block and push it.
   context.AddNodeAndPush(
-      if_node, SemanticsNode::BlockArg::Make(if_node, result_type_id));
+      if_node,
+      SemanticsNode::BlockArg::Make(if_node, result_type_id, resume_block_id));
   return true;
 }
 

+ 5 - 1
toolchain/semantics/semantics_node.h

@@ -285,7 +285,8 @@ class SemanticsNode {
                                           SemanticsStringId /*name_id*/,
                                           SemanticsNodeId /*node_id*/>;
 
-  using BlockArg = Factory<SemanticsNodeKind::BlockArg>;
+  using BlockArg =
+      Factory<SemanticsNodeKind::BlockArg, SemanticsNodeBlockId /*block_id*/>;
 
   using BoolLiteral =
       Factory<SemanticsNodeKind::BoolLiteral, SemanticsBoolValue /*value*/>;
@@ -450,6 +451,9 @@ struct SemanticsIdMapInfo {
 
 // Support use of Id types as DenseMap/DenseSet keys.
 template <>
+struct llvm::DenseMapInfo<Carbon::SemanticsNodeBlockId>
+    : public Carbon::SemanticsIdMapInfo<Carbon::SemanticsNodeBlockId> {};
+template <>
 struct llvm::DenseMapInfo<Carbon::SemanticsNodeId>
     : public Carbon::SemanticsIdMapInfo<Carbon::SemanticsNodeId> {};
 template <>

+ 1 - 1
toolchain/semantics/testdata/if_expression/basic.carbon

@@ -35,7 +35,7 @@
 // CHECK:STDOUT:   {kind: BinaryOperatorAdd, arg0: node+4, arg1: node+2, type: type1},
 // CHECK:STDOUT:   {kind: BranchWithArg, arg0: block7, arg1: node+9},
 // CHECK:STDOUT:   {kind: BranchWithArg, arg0: block7, arg1: node+10},
-// CHECK:STDOUT:   {kind: BlockArg, type: type1},
+// CHECK:STDOUT:   {kind: BlockArg, arg0: block7, type: type1},
 // CHECK:STDOUT:   {kind: ReturnExpression, arg0: node+13, type: type1},
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: node_blocks: [

+ 2 - 2
toolchain/semantics/testdata/if_expression/constant_condition.carbon

@@ -41,7 +41,7 @@
 // CHECK:STDOUT:   {kind: Call, arg0: block0, arg1: function1, type: type0},
 // CHECK:STDOUT:   {kind: BranchWithArg, arg0: block7, arg1: node+10},
 // CHECK:STDOUT:   {kind: BranchWithArg, arg0: block7, arg1: node+11},
-// CHECK:STDOUT:   {kind: BlockArg, type: type0},
+// CHECK:STDOUT:   {kind: BlockArg, arg0: block7, type: type0},
 // CHECK:STDOUT:   {kind: ReturnExpression, arg0: node+14, type: type0},
 // CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function3},
 // CHECK:STDOUT:   {kind: BoolLiteral, arg0: false, type: type1},
@@ -51,7 +51,7 @@
 // CHECK:STDOUT:   {kind: Call, arg0: block0, arg1: function1, type: type0},
 // CHECK:STDOUT:   {kind: BranchWithArg, arg0: block11, arg1: node+20},
 // CHECK:STDOUT:   {kind: BranchWithArg, arg0: block11, arg1: node+21},
-// CHECK:STDOUT:   {kind: BlockArg, type: type0},
+// CHECK:STDOUT:   {kind: BlockArg, arg0: block11, type: type0},
 // CHECK:STDOUT:   {kind: ReturnExpression, arg0: node+24, type: type0},
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: node_blocks: [

+ 1 - 1
toolchain/semantics/testdata/if_expression/control_flow.carbon

@@ -41,7 +41,7 @@
 // CHECK:STDOUT:   {kind: Call, arg0: block0, arg1: function1, type: type0},
 // CHECK:STDOUT:   {kind: BranchWithArg, arg0: block9, arg1: node+11},
 // CHECK:STDOUT:   {kind: BranchWithArg, arg0: block9, arg1: node+12},
-// CHECK:STDOUT:   {kind: BlockArg, type: type0},
+// CHECK:STDOUT:   {kind: BlockArg, arg0: block9, type: type0},
 // CHECK:STDOUT:   {kind: ReturnExpression, arg0: node+15, type: type0},
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: node_blocks: [

+ 3 - 3
toolchain/semantics/testdata/if_expression/nested.carbon

@@ -41,17 +41,17 @@
 // CHECK:STDOUT:   {kind: IntegerLiteral, arg0: int1, type: type1},
 // CHECK:STDOUT:   {kind: BranchWithArg, arg0: block9, arg1: node+11},
 // CHECK:STDOUT:   {kind: BranchWithArg, arg0: block9, arg1: node+12},
-// CHECK:STDOUT:   {kind: BlockArg, type: type1},
+// CHECK:STDOUT:   {kind: BlockArg, arg0: block9, type: type1},
 // CHECK:STDOUT:   {kind: BranchIf, arg0: block11, arg1: node+4},
 // CHECK:STDOUT:   {kind: Branch, arg0: block10},
 // CHECK:STDOUT:   {kind: IntegerLiteral, arg0: int2, type: type1},
 // CHECK:STDOUT:   {kind: IntegerLiteral, arg0: int3, type: type1},
 // CHECK:STDOUT:   {kind: BranchWithArg, arg0: block12, arg1: node+18},
 // CHECK:STDOUT:   {kind: BranchWithArg, arg0: block12, arg1: node+19},
-// CHECK:STDOUT:   {kind: BlockArg, type: type1},
+// CHECK:STDOUT:   {kind: BlockArg, arg0: block12, type: type1},
 // CHECK:STDOUT:   {kind: BranchWithArg, arg0: block13, arg1: node+15},
 // CHECK:STDOUT:   {kind: BranchWithArg, arg0: block13, arg1: node+22},
-// CHECK:STDOUT:   {kind: BlockArg, type: type1},
+// CHECK:STDOUT:   {kind: BlockArg, arg0: block13, type: type1},
 // CHECK:STDOUT:   {kind: ReturnExpression, arg0: node+25, type: type1},
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: node_blocks: [

+ 1 - 1
toolchain/semantics/testdata/operators/and.carbon

@@ -35,7 +35,7 @@
 // CHECK:STDOUT:   {kind: BranchWithArg, arg0: block5, arg1: node+8},
 // CHECK:STDOUT:   {kind: Call, arg0: block0, arg1: function1, type: type0},
 // CHECK:STDOUT:   {kind: BranchWithArg, arg0: block5, arg1: node+11},
-// CHECK:STDOUT:   {kind: BlockArg, type: type0},
+// CHECK:STDOUT:   {kind: BlockArg, arg0: block5, type: type0},
 // CHECK:STDOUT:   {kind: ReturnExpression, arg0: node+13, type: type0},
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: node_blocks: [

+ 1 - 1
toolchain/semantics/testdata/operators/or.carbon

@@ -36,7 +36,7 @@
 // CHECK:STDOUT:   {kind: BranchWithArg, arg0: block5, arg1: node+9},
 // CHECK:STDOUT:   {kind: Call, arg0: block0, arg1: function1, type: type0},
 // CHECK:STDOUT:   {kind: BranchWithArg, arg0: block5, arg1: node+12},
-// CHECK:STDOUT:   {kind: BlockArg, type: type0},
+// CHECK:STDOUT:   {kind: BlockArg, arg0: block5, type: type0},
 // CHECK:STDOUT:   {kind: ReturnExpression, arg0: node+14, type: type0},
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: node_blocks: [