瀏覽代碼

Restructure pattern matching to support producing results (#6929)

See
[here](https://docs.google.com/document/d/1rWcueFwIfZox6GKVGxiUG4cBzjrZ6djXiIDGyJDtrE4/edit?tab=t.0#heading=h.o26vowcup0iq)
for the motivation. Note that this change only provides the
infrastructure for producing and consuming results; the actual usage is
in a separate PR.

Assisted-by: Gemini 3.1 Pro via Antigravity
Geoff Romer 1 月之前
父節點
當前提交
8e824d02be
共有 1 個文件被更改,包括 248 次插入117 次删除
  1. 248 117
      toolchain/check/pattern_match.cpp

+ 248 - 117
toolchain/check/pattern_match.cpp

@@ -42,19 +42,59 @@ enum class MatchKind : uint8_t {
 };
 
 // The collected state of a pattern-matching operation.
+//
+// Conceptually, pattern matching is a recursive traversal of the pattern inst
+// tree: we match a pattern inst to a scrutinee inst by converting the scrutinee
+// as needed, matching any subpatterns against corresponding parts of the
+// scrutinee, and assembling the results of those sub-matches to form the result
+// of the whole match.
+//
+// This recursive traversal is implemented as a stack of work items, each
+// associated with a particular pattern inst. There are two types of work items,
+// PreWork and PostWork, which correspond to the work that is done before and
+// after visiting an inst's subpatterns, and are handled by DoPreWork and
+// DoPostWork overloads, respectively. Note that when there are no subpatterns,
+// DoPreWork may push a PostWork onto the stack, or may do the post-work (if
+// any) locally.
+//
+// DoPostWork is primarily responsible for computing the pattern's result and
+// adding it to result_stack_. However, the result of matching a pattern is
+// often not needed, so to avoid emitting unnecessary SemIR, it should only do
+// that if need_subpattern_results() is true.
+//
+// The traversal behavior depends on the kind of matching being performed. In
+// particular, many parts of a function signature pattern are irrelevant to the
+// caller, or to the callee, in which case no work will be done in that part of
+// the traversal. If an entire subpattern is known to be irrelevant in the
+// current matching context, it will not be traversed at all.
 class MatchContext {
  public:
-  struct WorkItem : Printable<WorkItem> {
-    SemIR::InstId pattern_id;
+  struct PreWork : Printable<PreWork> {
     // `None` when processing the callee side.
     SemIR::InstId scrutinee_id;
 
+    auto Print(llvm::raw_ostream& out) const -> void {
+      out << "{PreWork, scrutinee_id: " << scrutinee_id << "}";
+    }
+  };
+
+  struct PostWork : Printable<PostWork> {
+    auto Print(llvm::raw_ostream& out) const -> void { out << "{PostWork}"; }
+  };
+
+  struct WorkItem : Printable<WorkItem> {
+    SemIR::InstId pattern_id;
+
+    std::variant<PreWork, PostWork> work;
+
     // If true, disables diagnostics that would otherwise require scrutinee_id
     // to be tagged with `ref`. Only affects caller pattern matching.
     bool allow_unmarked_ref = false;
+
     auto Print(llvm::raw_ostream& out) const -> void {
-      out << "{pattern_id: " << pattern_id << ", scrutinee_id: " << scrutinee_id
-          << ", allow_unmarked_ref = " << allow_unmarked_ref << "}";
+      out << "{pattern_id: " << pattern_id << ", work: ";
+      std::visit([&](const auto& work) { out << work; }, work);
+      out << ", allow_unmarked_ref: " << allow_unmarked_ref << "}";
     }
   };
 
@@ -65,70 +105,85 @@ class MatchContext {
                                             SemIR::SpecificId::None)
       : kind_(kind), callee_specific_id_(callee_specific_id) {}
 
-  // Adds a work item to the stack.
-  auto AddWork(WorkItem work_item) -> void { stack_.push_back(work_item); }
+  // Whether the result of the work item at the top of the stack is needed.
+  auto need_subpattern_results() const -> bool {
+    return !results_stack_.empty();
+  }
+
+  // Adds `entry` to the front of the worklist.
+  auto AddWork(WorkItem entry) -> void { stack_.push_back(entry); }
+
+  // Sets `entry.work` to `PostWork` and adds it to the front of the worklist.
+  auto AddAsPostWork(WorkItem entry) -> void {
+    entry.work = PostWork{};
+    AddWork(entry);
+  }
 
   // Processes all work items on the stack.
   auto DoWork(Context& context) -> void;
 
   // Returns an inst block of references to all the emitted `Call` arguments.
   // Can only be called once, at the end of Caller pattern matching.
-  auto CallerResults(Context& context) && -> SemIR::InstBlockId;
+  auto GetCallArgs(Context& context) && -> SemIR::InstBlockId;
 
   // Returns an inst block of references to all the emitted `Call` params,
   // and an inst block of references to the `Call` param patterns they were
   // emitted to match. Can only be called once, at the end of Callee pattern
   // matching.
-  struct ParamsAndPatterns {
+  struct ParamBlocks {
     SemIR::InstBlockId call_param_patterns_id;
     SemIR::InstBlockId call_params_id;
   };
-  auto CalleeResults(Context& context) && -> ParamsAndPatterns;
+  auto GetCallParams(Context& context) && -> ParamBlocks;
 
   // Returns the number of call parameters that have been emitted so far.
   auto param_count() -> int { return call_params_.size(); }
   ~MatchContext();
 
  private:
-  // Emits the pattern-match insts necessary to match the pattern inst
-  // `entry.pattern_id` against the scrutinee value `entry.scrutinee_id`, and
-  // adds to `stack_` any work necessary to traverse into its subpatterns. This
-  // behavior is contingent on the kind of match being performed, as indicated
-  // by kind_`. For example, when performing a callee pattern match, this does
-  // not emit insts for patterns on the caller side. However, it still traverses
-  // into subpatterns if any of their descendants might emit insts.
-  // TODO: Require that `entry.scrutinee_id` is valid if and only if insts
-  // should be emitted, once we start emitting `Param` insts in the
-  // `ParamPattern` case.
-  auto EmitPatternMatch(Context& context, MatchContext::WorkItem entry) -> void;
-
-  // Implementations of `EmitPatternMatch` for particular pattern inst kinds.
-  auto DoEmitPatternMatch(Context& context,
-                          SemIR::AnyBindingPattern binding_pattern,
-                          WorkItem entry) -> void;
-  auto DoEmitPatternMatch(Context& context,
-                          SemIR::AnyParamPattern param_pattern, WorkItem entry)
-      -> void;
-  auto DoEmitPatternMatch(Context& context,
-                          SemIR::ReturnSlotPattern return_slot_pattern,
-                          WorkItem entry) -> void;
-  auto DoEmitPatternMatch(Context& context, SemIR::VarPattern var_pattern,
-                          WorkItem entry) -> void;
-  auto DoEmitPatternMatch(Context& context, SemIR::TuplePattern tuple_pattern,
-                          WorkItem entry) -> void;
+  // Dispatches `entry` to the appropriate DoWork method based on the kinds of
+  // `entry.pattern_id` and `entry.work`.
+  auto Dispatch(Context& context, WorkItem entry) -> void;
+
+  // Do the pre-work for `entry`. `entry.work` must be a `PreWork` containing
+  // `scrutinee_id`, and the pattern argument must be the value of
+  // `entry.pattern_id` in `context`.
+  auto DoPreWork(Context& context, SemIR::AnyBindingPattern binding_pattern,
+                 SemIR::InstId scrutinee_id, WorkItem entry) -> void;
+  auto DoPreWork(Context& context, SemIR::AnyParamPattern param_pattern,
+                 SemIR::InstId scrutinee_id, WorkItem entry) -> void;
+  auto DoPreWork(Context& context, SemIR::ReturnSlotPattern return_slot_pattern,
+                 SemIR::InstId scrutinee_id, WorkItem entry) -> void;
+  auto DoPreWork(Context& context, SemIR::VarPattern var_pattern,
+                 SemIR::InstId scrutinee_id, WorkItem entry) -> void;
+  auto DoPreWork(Context& context, SemIR::TuplePattern tuple_pattern,
+                 SemIR::InstId scrutinee_id, WorkItem entry) -> void;
+
+  // Do the post-work for `entry`. `entry.work` must be a `PostWork`, and
+  // the pattern argument must be the value of `entry.pattern_id` in `context`.
+  auto DoPostWork(Context& context, SemIR::VarPattern var_pattern,
+                  WorkItem entry) -> void;
+  auto DoPostWork(Context& context, SemIR::AnyParamPattern param_pattern,
+                  WorkItem entry) -> void;
+  auto DoPostWork(Context& context, SemIR::TuplePattern tuple_pattern,
+                  WorkItem entry) -> void;
 
   // Performs the core logic of matching a variable pattern whose type is
   // `pattern_type_id`, but returns the scrutinee that its subpattern should be
   // matched with, rather than pushing it onto the worklist. This is factored
   // out so it can be reused when handling a `FormBindingPattern` or
   // `FormParamPattern` with an initializing form.
-  auto DoEmitVarPatternMatchImpl(Context& context,
-                                 SemIR::TypeId pattern_type_id,
-                                 WorkItem entry) const -> SemIR::InstId;
+  auto DoVarPreWorkImpl(Context& context, SemIR::TypeId pattern_type_id,
+                        SemIR::InstId scrutinee_id, WorkItem entry) const
+      -> SemIR::InstId;
 
   // The stack of work to be processed.
   llvm::SmallVector<WorkItem> stack_;
 
+  // The stack of in-progress match results. Each array in the stack represents
+  // a single result, which may have multiple sub-results.
+  ArrayStack<SemIR::InstId> results_stack_;
+
   // The in-progress contents of the `Call` arguments block. This is populated
   // only when kind_ is Caller.
   llvm::SmallVector<SemIR::InstId> call_args_;
@@ -152,18 +207,18 @@ class MatchContext {
 
 auto MatchContext::DoWork(Context& context) -> void {
   while (!stack_.empty()) {
-    EmitPatternMatch(context, stack_.pop_back_val());
+    Dispatch(context, stack_.pop_back_val());
   }
 }
 
-auto MatchContext::CallerResults(Context& context) && -> SemIR::InstBlockId {
+auto MatchContext::GetCallArgs(Context& context) && -> SemIR::InstBlockId {
   CARBON_CHECK(kind_ == MatchKind::Caller);
   auto block_id = context.inst_blocks().Add(call_args_);
   call_args_.clear();
   return block_id;
 }
 
-auto MatchContext::CalleeResults(Context& context) && -> ParamsAndPatterns {
+auto MatchContext::GetCallParams(Context& context) && -> ParamBlocks {
   CARBON_CHECK(kind_ == MatchKind::Callee);
   CARBON_CHECK(call_params_.size() == call_param_patterns_.size());
   auto call_param_patterns_id = context.inst_blocks().Add(call_param_patterns_);
@@ -297,9 +352,10 @@ static auto ConversionKindFor(Context& context, SemIR::Inst pattern,
   }
 }
 
-auto MatchContext::DoEmitPatternMatch(Context& context,
-                                      SemIR::AnyBindingPattern binding_pattern,
-                                      MatchContext::WorkItem entry) -> void {
+auto MatchContext::DoPreWork(Context& context,
+                             SemIR::AnyBindingPattern binding_pattern,
+                             SemIR::InstId scrutinee_id,
+                             MatchContext::WorkItem entry) -> void {
   if (kind_ == MatchKind::Caller) {
     CARBON_CHECK(
         binding_pattern.kind == SemIR::SymbolicBindingPattern::Kind,
@@ -326,13 +382,13 @@ auto MatchContext::DoEmitPatternMatch(Context& context,
       conversion_kind = ConversionTarget::Discarded;
     }
     value_id =
-        Convert(context, SemIR::LocId(entry.scrutinee_id), entry.scrutinee_id,
+        Convert(context, SemIR::LocId(scrutinee_id), scrutinee_id,
                 {.kind = conversion_kind,
                  .type_id = context.insts().Get(bind_name_id).type_id()});
   } else {
     // In a function call, conversion is handled while matching the enclosing
     // `*ParamPattern`.
-    value_id = entry.scrutinee_id;
+    value_id = scrutinee_id;
   }
   if (bind_name_id.has_value()) {
     auto bind_name = context.insts().GetAs<SemIR::AnyBinding>(bind_name_id);
@@ -341,6 +397,9 @@ auto MatchContext::DoEmitPatternMatch(Context& context,
     ReplaceInstBeforeConstantUse(context, bind_name_id, bind_name);
     context.inst_block_stack().AddInstId(bind_name_id);
   }
+  if (need_subpattern_results()) {
+    results_stack_.AppendToTop(value_id);
+  }
 }
 
 // Returns the inst kind to use for the parameter corresponding to the given
@@ -378,9 +437,10 @@ static auto ParamKindFor(Context& context, SemIR::Inst param_pattern,
   }
 }
 
-auto MatchContext::DoEmitPatternMatch(Context& context,
-                                      SemIR::AnyParamPattern param_pattern,
-                                      WorkItem entry) -> void {
+auto MatchContext::DoPreWork(Context& context,
+                             SemIR::AnyParamPattern param_pattern,
+                             SemIR::InstId scrutinee_id, WorkItem entry)
+    -> void {
   // If the form is initializing, match this as a `VarPattern` before matching
   // it as a parameter pattern.
   if (param_pattern.kind == SemIR::FormParamPattern::Kind) {
@@ -388,31 +448,34 @@ auto MatchContext::DoEmitPatternMatch(Context& context,
         context.constant_values().GetInstId(param_pattern.form_id);
     if (context.insts().Get(form_inst_id).kind() == SemIR::InitForm::Kind) {
       auto new_scrutinee_id =
-          DoEmitVarPatternMatchImpl(context, param_pattern.type_id, entry);
-      entry.scrutinee_id = new_scrutinee_id;
+          DoVarPreWorkImpl(context, param_pattern.type_id, scrutinee_id, entry);
+      scrutinee_id = new_scrutinee_id;
     }
   }
 
   switch (kind_) {
     case MatchKind::Caller: {
-      CARBON_CHECK(entry.scrutinee_id.has_value());
-      if (entry.scrutinee_id == SemIR::ErrorInst::InstId) {
+      CARBON_CHECK(scrutinee_id.has_value());
+      if (scrutinee_id == SemIR::ErrorInst::InstId) {
         call_args_.push_back(SemIR::ErrorInst::InstId);
       } else {
         auto scrutinee_type_id = ExtractScrutineeType(
             context.sem_ir(),
             SemIR::GetTypeOfInstInSpecific(
                 context.sem_ir(), callee_specific_id_, entry.pattern_id));
-        call_args_.push_back(Convert(
-            context, SemIR::LocId(entry.scrutinee_id), entry.scrutinee_id,
-            {.kind = ConversionKindFor(context, param_pattern, entry),
-             .type_id = scrutinee_type_id}));
+        call_args_.push_back(
+            Convert(context, SemIR::LocId(scrutinee_id), scrutinee_id,
+                    {.kind = ConversionKindFor(context, param_pattern, entry),
+                     .type_id = scrutinee_type_id}));
       }
       // Do not traverse farther, because the caller side of the pattern
       // ends here.
       break;
     }
     case MatchKind::Callee: {
+      if (need_subpattern_results()) {
+        AddAsPostWork(entry);
+      }
       SemIR::AnyParam param = {
           .kind = ParamKindFor(context, param_pattern, entry),
           .type_id =
@@ -424,7 +487,7 @@ auto MatchContext::DoEmitPatternMatch(Context& context,
           AddInst(context, SemIR::LocIdAndInst::UncheckedLoc(
                                SemIR::LocId(entry.pattern_id), param));
       AddWork({.pattern_id = param_pattern.subpattern_id,
-               .scrutinee_id = param_id});
+               .work = PreWork{.scrutinee_id = param_id}});
       call_params_.push_back(param_id);
       call_param_patterns_.push_back(entry.pattern_id);
       break;
@@ -435,9 +498,16 @@ auto MatchContext::DoEmitPatternMatch(Context& context,
   }
 }
 
-auto MatchContext::DoEmitPatternMatch(
-    Context& context, SemIR::ReturnSlotPattern return_slot_pattern,
-    WorkItem entry) -> void {
+auto MatchContext::DoPostWork(Context& /*context*/,
+                              SemIR::AnyParamPattern /*param_pattern*/,
+                              WorkItem /*entry*/) -> void {
+  // No-op: the subpattern's result is this pattern's result.
+}
+
+auto MatchContext::DoPreWork(Context& context,
+                             SemIR::ReturnSlotPattern return_slot_pattern,
+                             SemIR::InstId scrutinee_id, WorkItem entry)
+    -> void {
   CARBON_CHECK(kind_ == MatchKind::Callee);
   auto type_id =
       ExtractScrutineeType(context.sem_ir(), return_slot_pattern.type_id);
@@ -445,27 +515,33 @@ auto MatchContext::DoEmitPatternMatch(
       context, SemIR::LocId(entry.pattern_id),
       {.type_id = type_id,
        .type_inst_id = context.types().GetTypeInstId(type_id),
-       .storage_id = entry.scrutinee_id});
+       .storage_id = scrutinee_id});
   bool already_in_lookup =
       context.scope_stack()
           .LookupOrAddName(SemIR::NameId::ReturnSlot, return_slot_id)
           .has_value();
   CARBON_CHECK(!already_in_lookup);
+  if (need_subpattern_results()) {
+    results_stack_.AppendToTop(return_slot_id);
+  }
 }
 
-auto MatchContext::DoEmitPatternMatch(Context& context,
-                                      SemIR::VarPattern var_pattern,
-                                      WorkItem entry) -> void {
+auto MatchContext::DoPreWork(Context& context, SemIR::VarPattern var_pattern,
+                             SemIR::InstId scrutinee_id, WorkItem entry)
+    -> void {
   auto new_scrutinee_id =
-      DoEmitVarPatternMatchImpl(context, var_pattern.type_id, entry);
+      DoVarPreWorkImpl(context, var_pattern.type_id, scrutinee_id, entry);
+  if (need_subpattern_results()) {
+    AddAsPostWork(entry);
+  }
   AddWork({.pattern_id = var_pattern.subpattern_id,
-           .scrutinee_id = new_scrutinee_id});
+           .work = PreWork{.scrutinee_id = new_scrutinee_id}});
 }
 
-auto MatchContext::DoEmitVarPatternMatchImpl(Context& context,
-                                             SemIR::TypeId pattern_type_id,
-                                             WorkItem entry) const
-    -> SemIR::InstId {
+auto MatchContext::DoVarPreWorkImpl(Context& context,
+                                    SemIR::TypeId pattern_type_id,
+                                    SemIR::InstId scrutinee_id,
+                                    WorkItem entry) const -> SemIR::InstId {
   auto storage_id = SemIR::InstId::None;
   switch (kind_) {
     case MatchKind::Callee: {
@@ -486,7 +562,7 @@ auto MatchContext::DoEmitVarPatternMatchImpl(Context& context,
       storage_id = AddInst<SemIR::TemporaryStorage>(
           context, SemIR::LocId(entry.pattern_id),
           {.type_id = ExtractScrutineeType(context.sem_ir(), pattern_type_id)});
-      CARBON_CHECK(entry.scrutinee_id.has_value());
+      CARBON_CHECK(scrutinee_id.has_value());
       break;
     }
   }
@@ -496,9 +572,9 @@ auto MatchContext::DoEmitVarPatternMatchImpl(Context& context,
   if (context.scope_stack().PeekIndex() == ScopeIndex::Package) {
     context.global_init().Resume();
   }
-  if (entry.scrutinee_id.has_value()) {
+  if (scrutinee_id.has_value()) {
     auto init_id = Initialize(context, SemIR::LocId(entry.pattern_id),
-                              storage_id, entry.scrutinee_id);
+                              storage_id, scrutinee_id);
     // If we created a `TemporaryStorage` to hold the var, create a
     // corresponding `Temporary` to model that its initialization is complete.
     // TODO: If the subpattern is a binding, we may want to destroy the
@@ -523,32 +599,43 @@ auto MatchContext::DoEmitVarPatternMatchImpl(Context& context,
   return storage_id;
 }
 
-auto MatchContext::DoEmitPatternMatch(Context& context,
-                                      SemIR::TuplePattern tuple_pattern,
-                                      WorkItem entry) -> void {
+auto MatchContext::DoPostWork(Context& /*context*/,
+                              SemIR::VarPattern /*var_pattern*/,
+                              WorkItem /*entry*/) -> void {
+  // No-op: the subpattern's result is this pattern's result.
+}
+
+auto MatchContext::DoPreWork(Context& context,
+                             SemIR::TuplePattern tuple_pattern,
+                             SemIR::InstId scrutinee_id, WorkItem entry)
+    -> void {
   if (tuple_pattern.type_id == SemIR::ErrorInst::TypeId) {
     return;
   }
   auto subpattern_ids = context.inst_blocks().Get(tuple_pattern.elements_id);
+  if (need_subpattern_results()) {
+    results_stack_.PushArray();
+    AddAsPostWork(entry);
+  }
   auto add_all_subscrutinees =
       [&](llvm::ArrayRef<SemIR::InstId> subscrutinee_ids) {
         for (auto [subpattern_id, subscrutinee_id] :
              llvm::reverse(llvm::zip_equal(subpattern_ids, subscrutinee_ids))) {
-          AddWork(
-              {.pattern_id = subpattern_id, .scrutinee_id = subscrutinee_id});
+          AddWork({.pattern_id = subpattern_id,
+                   .work = PreWork{.scrutinee_id = subscrutinee_id}});
         }
       };
-  if (!entry.scrutinee_id.has_value()) {
+  if (!scrutinee_id.has_value()) {
     CARBON_CHECK(kind_ == MatchKind::Callee);
     // If we don't have a scrutinee yet, we're still on the caller side of the
     // pattern, so the subpatterns don't have a scrutinee either.
     for (auto subpattern_id : llvm::reverse(subpattern_ids)) {
-      AddWork(
-          {.pattern_id = subpattern_id, .scrutinee_id = SemIR::InstId::None});
+      AddWork({.pattern_id = subpattern_id,
+               .work = PreWork{.scrutinee_id = SemIR::InstId::None}});
     }
     return;
   }
-  auto scrutinee = context.insts().GetWithLocId(entry.scrutinee_id);
+  auto scrutinee = context.insts().GetWithLocId(scrutinee_id);
   if (auto scrutinee_literal = scrutinee.inst.TryAs<SemIR::TupleLiteral>()) {
     auto subscrutinee_ids =
         context.inst_blocks().Get(scrutinee_literal->elements_id);
@@ -568,9 +655,8 @@ auto MatchContext::DoEmitPatternMatch(Context& context,
 
   auto tuple_type_id =
       ExtractScrutineeType(context.sem_ir(), tuple_pattern.type_id);
-  auto converted_scrutinee_id =
-      ConvertToValueOrRefOfType(context, SemIR::LocId(entry.pattern_id),
-                                entry.scrutinee_id, tuple_type_id);
+  auto converted_scrutinee_id = ConvertToValueOrRefOfType(
+      context, SemIR::LocId(entry.pattern_id), scrutinee_id, tuple_type_id);
   if (auto scrutinee_value =
           context.insts().TryGetAs<SemIR::TupleValue>(converted_scrutinee_id)) {
     add_all_subscrutinees(
@@ -594,8 +680,20 @@ auto MatchContext::DoEmitPatternMatch(Context& context,
   add_all_subscrutinees(subscrutinee_ids);
 }
 
-auto MatchContext::EmitPatternMatch(Context& context,
-                                    MatchContext::WorkItem entry) -> void {
+auto MatchContext::DoPostWork(Context& context,
+                              SemIR::TuplePattern tuple_pattern, WorkItem entry)
+    -> void {
+  auto elements_id = context.inst_blocks().Add(results_stack_.PeekArray());
+  results_stack_.PopArray();
+  auto tuple_value_id =
+      AddInst<SemIR::TupleValue>(context, SemIR::LocId(entry.pattern_id),
+                                 {.type_id = SemIR::ExtractScrutineeType(
+                                      context.sem_ir(), tuple_pattern.type_id),
+                                  .elements_id = elements_id});
+  results_stack_.AppendToTop(tuple_value_id);
+}
+
+auto MatchContext::Dispatch(Context& context, WorkItem entry) -> void {
   if (entry.pattern_id == SemIR::ErrorInst::InstId) {
     return;
   }
@@ -608,30 +706,58 @@ auto MatchContext::EmitPatternMatch(Context& context,
         }
       });
   auto pattern = context.insts().Get(entry.pattern_id);
-  CARBON_KIND_SWITCH(pattern) {
-    case CARBON_KIND_ANY(SemIR::AnyBindingPattern, any_binding_pattern): {
-      DoEmitPatternMatch(context, any_binding_pattern, entry);
-      break;
-    }
-    case CARBON_KIND_ANY(SemIR::AnyParamPattern, any_param_pattern): {
-      DoEmitPatternMatch(context, any_param_pattern, entry);
-      break;
-    }
-    case CARBON_KIND(SemIR::ReturnSlotPattern return_slot_pattern): {
-      DoEmitPatternMatch(context, return_slot_pattern, entry);
-      break;
-    }
-    case CARBON_KIND(SemIR::VarPattern var_pattern): {
-      DoEmitPatternMatch(context, var_pattern, entry);
+  CARBON_KIND_SWITCH(entry.work) {
+    case CARBON_KIND(PreWork work): {
+      // TODO: Require that `work.scrutinee_id` is valid if and only if insts
+      // should be emitted, once we start emitting `Param` insts in the
+      // `ParamPattern` case.
+      CARBON_KIND_SWITCH(pattern) {
+        case CARBON_KIND_ANY(SemIR::AnyBindingPattern, any_binding_pattern): {
+          DoPreWork(context, any_binding_pattern, work.scrutinee_id, entry);
+          break;
+        }
+        case CARBON_KIND_ANY(SemIR::AnyParamPattern, any_param_pattern): {
+          DoPreWork(context, any_param_pattern, work.scrutinee_id, entry);
+          break;
+        }
+        case CARBON_KIND(SemIR::ReturnSlotPattern return_slot_pattern): {
+          DoPreWork(context, return_slot_pattern, work.scrutinee_id, entry);
+          break;
+        }
+        case CARBON_KIND(SemIR::VarPattern var_pattern): {
+          DoPreWork(context, var_pattern, work.scrutinee_id, entry);
+          break;
+        }
+        case CARBON_KIND(SemIR::TuplePattern tuple_pattern): {
+          DoPreWork(context, tuple_pattern, work.scrutinee_id, entry);
+          break;
+        }
+        default: {
+          CARBON_FATAL("Inst kind not handled: {0}", pattern.kind());
+        }
+      }
       break;
     }
-    case CARBON_KIND(SemIR::TuplePattern tuple_pattern): {
-      DoEmitPatternMatch(context, tuple_pattern, entry);
+    case CARBON_KIND(PostWork _): {
+      CARBON_KIND_SWITCH(pattern) {
+        case CARBON_KIND_ANY(SemIR::AnyParamPattern, any_param_pattern): {
+          DoPostWork(context, any_param_pattern, entry);
+          break;
+        }
+        case CARBON_KIND(SemIR::VarPattern var_pattern): {
+          DoPostWork(context, var_pattern, entry);
+          break;
+        }
+        case CARBON_KIND(SemIR::TuplePattern tuple_pattern): {
+          DoPostWork(context, tuple_pattern, entry);
+          break;
+        }
+        default: {
+          CARBON_FATAL("Inst kind not handled: {0}", pattern.kind());
+        }
+      }
       break;
     }
-    default: {
-      CARBON_FATAL("Inst kind not handled: {0}", pattern.kind());
-    }
   }
 }
 
@@ -655,7 +781,8 @@ auto CalleePatternMatch(Context& context,
     for (SemIR::InstId inst_id :
          llvm::reverse(context.inst_blocks().Get(implicit_param_patterns_id))) {
       match.AddWork(
-          {.pattern_id = inst_id, .scrutinee_id = SemIR::InstId::None});
+          {.pattern_id = inst_id,
+           .work = MatchContext::PreWork{.scrutinee_id = SemIR::InstId::None}});
     }
   }
   match.DoWork(context);
@@ -665,7 +792,8 @@ auto CalleePatternMatch(Context& context,
     for (SemIR::InstId inst_id :
          llvm::reverse(context.inst_blocks().Get(param_patterns_id))) {
       match.AddWork(
-          {.pattern_id = inst_id, .scrutinee_id = SemIR::InstId::None});
+          {.pattern_id = inst_id,
+           .work = MatchContext::PreWork{.scrutinee_id = SemIR::InstId::None}});
     }
   }
   match.DoWork(context);
@@ -674,13 +802,14 @@ auto CalleePatternMatch(Context& context,
   for (auto return_pattern_id :
        context.inst_blocks().GetOrEmpty(return_patterns_id)) {
     match.AddWork(
-        {.pattern_id = return_pattern_id, .scrutinee_id = SemIR::InstId::None});
+        {.pattern_id = return_pattern_id,
+         .work = MatchContext::PreWork{.scrutinee_id = SemIR::InstId::None}});
   }
   match.DoWork(context);
   auto return_end = SemIR::CallParamIndex(match.param_count());
 
   match.DoWork(context);
-  auto blocks = std::move(match).CalleeResults(context);
+  auto blocks = std::move(match).GetCallParams(context);
   return {.call_param_patterns_id = blocks.call_param_patterns_id,
           .call_params_id = blocks.call_params_id,
           .param_ranges = {implicit_end, explicit_end, return_end}};
@@ -702,7 +831,8 @@ auto CallerPatternMatch(Context& context, SemIR::SpecificId specific_id,
        llvm::zip_equal(return_patterns, return_arg_ids)) {
     if (return_arg_id.has_value()) {
       match.AddWork(
-          {.pattern_id = return_pattern_id, .scrutinee_id = return_arg_id});
+          {.pattern_id = return_pattern_id,
+           .work = MatchContext::PreWork{.scrutinee_id = return_arg_id}});
     } else {
       CARBON_CHECK(return_arg_ids.size() == 1,
                    "TODO: do the match even if return_arg_id is None, so that "
@@ -714,24 +844,25 @@ auto CallerPatternMatch(Context& context, SemIR::SpecificId specific_id,
   for (auto [arg_id, param_pattern_id] : llvm::reverse(llvm::zip_equal(
            arg_refs, context.inst_blocks().GetOrEmpty(param_patterns_id)))) {
     match.AddWork({.pattern_id = param_pattern_id,
-                   .scrutinee_id = arg_id,
+                   .work = MatchContext::PreWork{.scrutinee_id = arg_id},
                    .allow_unmarked_ref = is_operator_syntax});
   }
 
   if (self_pattern_id.has_value()) {
     match.AddWork({.pattern_id = self_pattern_id,
-                   .scrutinee_id = self_arg_id,
+                   .work = MatchContext::PreWork{.scrutinee_id = self_arg_id},
                    .allow_unmarked_ref = true});
   }
 
   match.DoWork(context);
-  return std::move(match).CallerResults(context);
+  return std::move(match).GetCallArgs(context);
 }
 
 auto LocalPatternMatch(Context& context, SemIR::InstId pattern_id,
                        SemIR::InstId scrutinee_id) -> void {
   MatchContext match(MatchKind::Local);
-  match.AddWork({.pattern_id = pattern_id, .scrutinee_id = scrutinee_id});
+  match.AddWork({.pattern_id = pattern_id,
+                 .work = MatchContext::PreWork{.scrutinee_id = scrutinee_id}});
   match.DoWork(context);
 }