trace: regenerate trace from go@c2b1463

[git-generate]
cd trace
./gen.bash $HOME/work/go

Change-Id: I7db5c75d3ca5d86918a3cac1a1fbeb1fa97b9881
Reviewed-on: https://go-review.googlesource.com/c/exp/+/574135
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Stapelberg <stapelberg@google.com>
Auto-Submit: Michael Knyszek <mknyszek@google.com>
diff --git a/trace/event.go b/trace/event.go
index a73e624..962a63c 100644
--- a/trace/event.go
+++ b/trace/event.go
@@ -570,8 +570,12 @@
 	case go122.EvProcStatus:
 		// N.B. ordering.advance populates e.base.extra.
 		s = procStateTransition(ProcID(e.base.args[0]), ProcState(e.base.extra(version.Go122)[0]), go122ProcStatus2ProcState[e.base.args[1]])
-	case go122.EvGoCreate:
-		s = goStateTransition(GoID(e.base.args[0]), GoNotExist, GoRunnable)
+	case go122.EvGoCreate, go122.EvGoCreateBlocked:
+		status := GoRunnable
+		if e.base.typ == go122.EvGoCreateBlocked {
+			status = GoWaiting
+		}
+		s = goStateTransition(GoID(e.base.args[0]), GoNotExist, status)
 		s.Stack = Stack{table: e.table, id: stackID(e.base.args[1])}
 	case go122.EvGoCreateSyscall:
 		s = goStateTransition(GoID(e.base.args[0]), GoNotExist, GoSyscall)
@@ -590,7 +594,10 @@
 		s = goStateTransition(e.ctx.G, GoRunning, GoWaiting)
 		s.Reason = e.table.strings.mustGet(stringID(e.base.args[0]))
 		s.Stack = e.Stack() // This event references the resource the event happened on.
-	case go122.EvGoUnblock:
+	case go122.EvGoUnblock, go122.EvGoSwitch, go122.EvGoSwitchDestroy:
+		// N.B. GoSwitch and GoSwitchDestroy both emit additional events, but
+		// the first thing they both do is unblock the goroutine they name,
+		// identically to an unblock event (even their arguments match).
 		s = goStateTransition(GoID(e.base.args[0]), GoWaiting, GoRunnable)
 	case go122.EvGoSyscallBegin:
 		s = goStateTransition(e.ctx.G, GoRunning, GoSyscall)
@@ -650,6 +657,9 @@
 	go122.EvUserRegionBegin:     EventRegionBegin,
 	go122.EvUserRegionEnd:       EventRegionEnd,
 	go122.EvUserLog:             EventLog,
+	go122.EvGoSwitch:            EventStateTransition,
+	go122.EvGoSwitchDestroy:     EventStateTransition,
+	go122.EvGoCreateBlocked:     EventStateTransition,
 	evSync:                      EventSync,
 }
 
diff --git a/trace/internal/event/go122/event.go b/trace/internal/event/go122/event.go
index a77fb8b..2baf9ce 100644
--- a/trace/internal/event/go122/event.go
+++ b/trace/internal/event/go122/event.go
@@ -71,6 +71,11 @@
 	EvUserRegionBegin // trace.{Start,With}Region [timestamp, internal task ID, name string ID, stack ID]
 	EvUserRegionEnd   // trace.{End,With}Region [timestamp, internal task ID, name string ID, stack ID]
 	EvUserLog         // trace.Log [timestamp, internal task ID, key string ID, value string ID, stack]
+
+	// Coroutines. Added in Go 1.23.
+	EvGoSwitch        // goroutine switch (coroswitch) [timestamp, goroutine ID, goroutine seq]
+	EvGoSwitchDestroy // goroutine switch and destroy [timestamp, goroutine ID, goroutine seq]
+	EvGoCreateBlocked // goroutine creation (starts blocked) [timestamp, new goroutine ID, new stack ID, stack ID]
 )
 
 // EventString returns the name of a Go 1.22 event.
@@ -336,6 +341,22 @@
 		StackIDs:     []int{4},
 		StringIDs:    []int{2, 3},
 	},
+	EvGoSwitch: {
+		Name:         "GoSwitch",
+		Args:         []string{"dt", "g", "g_seq"},
+		IsTimedEvent: true,
+	},
+	EvGoSwitchDestroy: {
+		Name:         "GoSwitchDestroy",
+		Args:         []string{"dt", "g", "g_seq"},
+		IsTimedEvent: true,
+	},
+	EvGoCreateBlocked: {
+		Name:         "GoCreateBlocked",
+		Args:         []string{"dt", "new_g", "new_stack", "stack"},
+		IsTimedEvent: true,
+		StackIDs:     []int{3, 2},
+	},
 }
 
 type GoStatus uint8
diff --git a/trace/internal/version/version.go b/trace/internal/version/version.go
index 047ca0c..1dc0dcd 100644
--- a/trace/internal/version/version.go
+++ b/trace/internal/version/version.go
@@ -24,7 +24,8 @@
 	Go119   Version = 19
 	Go121   Version = 21
 	Go122   Version = 22
-	Current         = Go122
+	Go123   Version = 23
+	Current         = Go123
 )
 
 var versions = map[Version][]event.Spec{
@@ -35,6 +36,10 @@
 	Go121: nil,
 
 	Go122: go122.Specs(),
+	// Go 1.23 adds backwards-incompatible events, but
+	// traces produced by Go 1.22 are also always valid
+	// Go 1.23 traces.
+	Go123: go122.Specs(),
 }
 
 // Specs returns the set of event.Specs for this version.
diff --git a/trace/order.go b/trace/order.go
index 0e2f600..089d623 100644
--- a/trace/order.go
+++ b/trace/order.go
@@ -78,524 +78,693 @@
 		newCtx = curCtx
 	}
 
-	// Generates an event from the current context.
-	currentEvent := func() Event {
-		return Event{table: evt, ctx: curCtx, base: *ev}
+	f := orderingDispatch[ev.typ]
+	if f == nil {
+		return false, fmt.Errorf("bad event type found while ordering: %v", ev.typ)
+	}
+	newCtx, ok, err := f(o, ev, evt, m, gen, curCtx)
+	if err == nil && ok && ms != nil {
+		// Update the mState for this event.
+		ms.p = newCtx.P
+		ms.g = newCtx.G
+	}
+	return ok, err
+}
+
+type orderingHandleFunc func(o *ordering, ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error)
+
+var orderingDispatch = [256]orderingHandleFunc{
+	// Procs.
+	go122.EvProcsChange: (*ordering).advanceAnnotation,
+	go122.EvProcStart:   (*ordering).advanceProcStart,
+	go122.EvProcStop:    (*ordering).advanceProcStop,
+	go122.EvProcSteal:   (*ordering).advanceProcSteal,
+	go122.EvProcStatus:  (*ordering).advanceProcStatus,
+
+	// Goroutines.
+	go122.EvGoCreate:            (*ordering).advanceGoCreate,
+	go122.EvGoCreateSyscall:     (*ordering).advanceGoCreateSyscall,
+	go122.EvGoStart:             (*ordering).advanceGoStart,
+	go122.EvGoDestroy:           (*ordering).advanceGoStopExec,
+	go122.EvGoDestroySyscall:    (*ordering).advanceGoDestroySyscall,
+	go122.EvGoStop:              (*ordering).advanceGoStopExec,
+	go122.EvGoBlock:             (*ordering).advanceGoStopExec,
+	go122.EvGoUnblock:           (*ordering).advanceGoUnblock,
+	go122.EvGoSyscallBegin:      (*ordering).advanceGoSyscallBegin,
+	go122.EvGoSyscallEnd:        (*ordering).advanceGoSyscallEnd,
+	go122.EvGoSyscallEndBlocked: (*ordering).advanceGoSyscallEndBlocked,
+	go122.EvGoStatus:            (*ordering).advanceGoStatus,
+
+	// STW.
+	go122.EvSTWBegin: (*ordering).advanceGoRangeBegin,
+	go122.EvSTWEnd:   (*ordering).advanceGoRangeEnd,
+
+	// GC events.
+	go122.EvGCActive:           (*ordering).advanceGCActive,
+	go122.EvGCBegin:            (*ordering).advanceGCBegin,
+	go122.EvGCEnd:              (*ordering).advanceGCEnd,
+	go122.EvGCSweepActive:      (*ordering).advanceGCSweepActive,
+	go122.EvGCSweepBegin:       (*ordering).advanceGCSweepBegin,
+	go122.EvGCSweepEnd:         (*ordering).advanceGCSweepEnd,
+	go122.EvGCMarkAssistActive: (*ordering).advanceGoRangeActive,
+	go122.EvGCMarkAssistBegin:  (*ordering).advanceGoRangeBegin,
+	go122.EvGCMarkAssistEnd:    (*ordering).advanceGoRangeEnd,
+	go122.EvHeapAlloc:          (*ordering).advanceHeapMetric,
+	go122.EvHeapGoal:           (*ordering).advanceHeapMetric,
+
+	// Annotations.
+	go122.EvGoLabel:         (*ordering).advanceAnnotation,
+	go122.EvUserTaskBegin:   (*ordering).advanceUserTaskBegin,
+	go122.EvUserTaskEnd:     (*ordering).advanceUserTaskEnd,
+	go122.EvUserRegionBegin: (*ordering).advanceUserRegionBegin,
+	go122.EvUserRegionEnd:   (*ordering).advanceUserRegionEnd,
+	go122.EvUserLog:         (*ordering).advanceAnnotation,
+
+	// Coroutines. Added in Go 1.23.
+	go122.EvGoSwitch:        (*ordering).advanceGoSwitch,
+	go122.EvGoSwitchDestroy: (*ordering).advanceGoSwitch,
+	go122.EvGoCreateBlocked: (*ordering).advanceGoCreate,
+}
+
+func (o *ordering) advanceProcStatus(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+	pid := ProcID(ev.args[0])
+	status := go122.ProcStatus(ev.args[1])
+	if int(status) >= len(go122ProcStatus2ProcState) {
+		return curCtx, false, fmt.Errorf("invalid status for proc %d: %d", pid, status)
+	}
+	oldState := go122ProcStatus2ProcState[status]
+	if s, ok := o.pStates[pid]; ok {
+		if status == go122.ProcSyscallAbandoned && s.status == go122.ProcSyscall {
+			// ProcSyscallAbandoned is a special case of ProcSyscall. It indicates a
+			// potential loss of information, but if we're already in ProcSyscall,
+			// we haven't lost the relevant information. Promote the status and advance.
+			oldState = ProcRunning
+			ev.args[1] = uint64(go122.ProcSyscall)
+		} else if status == go122.ProcSyscallAbandoned && s.status == go122.ProcSyscallAbandoned {
+			// If we're passing through ProcSyscallAbandoned, then there's no promotion
+			// to do. We've lost the M that this P is associated with. However it got there,
+			// it's going to appear as idle in the API, so pass through as idle.
+			oldState = ProcIdle
+			ev.args[1] = uint64(go122.ProcSyscallAbandoned)
+		} else if s.status != status {
+			return curCtx, false, fmt.Errorf("inconsistent status for proc %d: old %v vs. new %v", pid, s.status, status)
+		}
+		s.seq = makeSeq(gen, 0) // Reset seq.
+	} else {
+		o.pStates[pid] = &pState{id: pid, status: status, seq: makeSeq(gen, 0)}
+		if gen == o.initialGen {
+			oldState = ProcUndetermined
+		} else {
+			oldState = ProcNotExist
+		}
+	}
+	ev.extra(version.Go122)[0] = uint64(oldState) // Smuggle in the old state for StateTransition.
+
+	// Bind the proc to the new context, if it's running.
+	newCtx := curCtx
+	if status == go122.ProcRunning || status == go122.ProcSyscall {
+		newCtx.P = pid
+	}
+	// If we're advancing through ProcSyscallAbandoned *but* oldState is running then we've
+	// promoted it to ProcSyscall. However, because it's ProcSyscallAbandoned, we know this
+	// P is about to get stolen and its status very likely isn't being emitted by the same
+	// thread it was bound to. Since this status is Running -> Running and Running is binding,
+	// we need to make sure we emit it in the right context: the context to which it is bound.
+	// Find it, and set our current context to it.
+	if status == go122.ProcSyscallAbandoned && oldState == ProcRunning {
+		// N.B. This is slow but it should be fairly rare.
+		found := false
+		for mid, ms := range o.mStates {
+			if ms.p == pid {
+				curCtx.M = mid
+				curCtx.P = pid
+				curCtx.G = ms.g
+				found = true
+			}
+		}
+		if !found {
+			return curCtx, false, fmt.Errorf("failed to find sched context for proc %d that's about to be stolen", pid)
+		}
+	}
+	o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+	return newCtx, true, nil
+}
+
+func (o *ordering) advanceProcStart(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+	pid := ProcID(ev.args[0])
+	seq := makeSeq(gen, ev.args[1])
+
+	// Try to advance. We might fail here due to sequencing, because the P hasn't
+	// had a status emitted, or because we already have a P and we're in a syscall,
+	// and we haven't observed that it was stolen from us yet.
+	state, ok := o.pStates[pid]
+	if !ok || state.status != go122.ProcIdle || !seq.succeeds(state.seq) || curCtx.P != NoProc {
+		// We can't make an inference as to whether this is bad. We could just be seeing
+		// a ProcStart on a different M before the proc's state was emitted, or before we
+		// got to the right point in the trace.
+		//
+		// Note that we also don't advance here if we have a P and we're in a syscall.
+		return curCtx, false, nil
+	}
+	// We can advance this P. Check some invariants.
+	//
+	// We might have a goroutine if a goroutine is exiting a syscall.
+	reqs := event.SchedReqs{Thread: event.MustHave, Proc: event.MustNotHave, Goroutine: event.MayHave}
+	if err := validateCtx(curCtx, reqs); err != nil {
+		return curCtx, false, err
+	}
+	state.status = go122.ProcRunning
+	state.seq = seq
+	newCtx := curCtx
+	newCtx.P = pid
+	o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+	return newCtx, true, nil
+}
+
+func (o *ordering) advanceProcStop(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+	// We must be able to advance this P.
+	//
+	// There are 2 ways a P can stop: ProcStop and ProcSteal. ProcStop is used when the P
+	// is stopped by the same M that started it, while ProcSteal is used when another M
+	// steals the P by stopping it from a distance.
+	//
+	// Since a P is bound to an M, and we're stopping on the same M we started, it must
+	// always be possible to advance the current M's P from a ProcStop. This is also why
+	// ProcStop doesn't need a sequence number.
+	state, ok := o.pStates[curCtx.P]
+	if !ok {
+		return curCtx, false, fmt.Errorf("event %s for proc (%v) that doesn't exist", go122.EventString(ev.typ), curCtx.P)
+	}
+	if state.status != go122.ProcRunning && state.status != go122.ProcSyscall {
+		return curCtx, false, fmt.Errorf("%s event for proc that's not %s or %s", go122.EventString(ev.typ), go122.ProcRunning, go122.ProcSyscall)
+	}
+	reqs := event.SchedReqs{Thread: event.MustHave, Proc: event.MustHave, Goroutine: event.MayHave}
+	if err := validateCtx(curCtx, reqs); err != nil {
+		return curCtx, false, err
+	}
+	state.status = go122.ProcIdle
+	newCtx := curCtx
+	newCtx.P = NoProc
+	o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+	return newCtx, true, nil
+}
+
+func (o *ordering) advanceProcSteal(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+	pid := ProcID(ev.args[0])
+	seq := makeSeq(gen, ev.args[1])
+	state, ok := o.pStates[pid]
+	if !ok || (state.status != go122.ProcSyscall && state.status != go122.ProcSyscallAbandoned) || !seq.succeeds(state.seq) {
+		// We can't make an inference as to whether this is bad. We could just be seeing
+		// a ProcStart on a different M before the proc's state was emitted, or before we
+		// got to the right point in the trace.
+		return curCtx, false, nil
+	}
+	// We can advance this P. Check some invariants.
+	reqs := event.SchedReqs{Thread: event.MustHave, Proc: event.MayHave, Goroutine: event.MayHave}
+	if err := validateCtx(curCtx, reqs); err != nil {
+		return curCtx, false, err
+	}
+	// Smuggle in the P state that let us advance so we can surface information to the event.
+	// Specifically, we need to make sure that the event is interpreted not as a transition of
+	// ProcRunning -> ProcIdle but ProcIdle -> ProcIdle instead.
+	//
+	// ProcRunning is binding, but we may be running with a P on the current M and we can't
+	// bind another P. This P is about to go ProcIdle anyway.
+	oldStatus := state.status
+	ev.extra(version.Go122)[0] = uint64(oldStatus)
+
+	// Update the P's status and sequence number.
+	state.status = go122.ProcIdle
+	state.seq = seq
+
+	// If we've lost information then don't try to do anything with the M.
+	// It may have moved on and we can't be sure.
+	if oldStatus == go122.ProcSyscallAbandoned {
+		o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+		return curCtx, true, nil
 	}
 
-	switch typ := ev.typ; typ {
-	// Handle procs.
-	case go122.EvProcStatus:
-		pid := ProcID(ev.args[0])
-		status := go122.ProcStatus(ev.args[1])
-		if int(status) >= len(go122ProcStatus2ProcState) {
-			return false, fmt.Errorf("invalid status for proc %d: %d", pid, status)
-		}
-		oldState := go122ProcStatus2ProcState[status]
-		if s, ok := o.pStates[pid]; ok {
-			if status == go122.ProcSyscallAbandoned && s.status == go122.ProcSyscall {
-				// ProcSyscallAbandoned is a special case of ProcSyscall. It indicates a
-				// potential loss of information, but if we're already in ProcSyscall,
-				// we haven't lost the relevant information. Promote the status and advance.
-				oldState = ProcRunning
-				ev.args[1] = uint64(go122.ProcSyscall)
-			} else if status == go122.ProcSyscallAbandoned && s.status == go122.ProcSyscallAbandoned {
-				// If we're passing through ProcSyscallAbandoned, then there's no promotion
-				// to do. We've lost the M that this P is associated with. However it got there,
-				// it's going to appear as idle in the API, so pass through as idle.
-				oldState = ProcIdle
-				ev.args[1] = uint64(go122.ProcSyscallAbandoned)
-			} else if s.status != status {
-				return false, fmt.Errorf("inconsistent status for proc %d: old %v vs. new %v", pid, s.status, status)
-			}
-			s.seq = makeSeq(gen, 0) // Reset seq.
-		} else {
-			o.pStates[pid] = &pState{id: pid, status: status, seq: makeSeq(gen, 0)}
-			if gen == o.initialGen {
-				oldState = ProcUndetermined
-			} else {
-				oldState = ProcNotExist
-			}
-		}
-		ev.extra(version.Go122)[0] = uint64(oldState) // Smuggle in the old state for StateTransition.
+	// Validate that the M we're stealing from is what we expect.
+	mid := ThreadID(ev.args[2]) // The M we're stealing from.
 
-		// Bind the proc to the new context, if it's running.
-		if status == go122.ProcRunning || status == go122.ProcSyscall {
-			newCtx.P = pid
+	newCtx := curCtx
+	if mid == curCtx.M {
+		// We're stealing from ourselves. This behaves like a ProcStop.
+		if curCtx.P != pid {
+			return curCtx, false, fmt.Errorf("tried to self-steal proc %d (thread %d), but got proc %d instead", pid, mid, curCtx.P)
 		}
-		// If we're advancing through ProcSyscallAbandoned *but* oldState is running then we've
-		// promoted it to ProcSyscall. However, because it's ProcSyscallAbandoned, we know this
-		// P is about to get stolen and its status very likely isn't being emitted by the same
-		// thread it was bound to. Since this status is Running -> Running and Running is binding,
-		// we need to make sure we emit it in the right context: the context to which it is bound.
-		// Find it, and set our current context to it.
-		if status == go122.ProcSyscallAbandoned && oldState == ProcRunning {
-			// N.B. This is slow but it should be fairly rare.
-			found := false
-			for mid, ms := range o.mStates {
-				if ms.p == pid {
-					curCtx.M = mid
-					curCtx.P = pid
-					curCtx.G = ms.g
-					found = true
-				}
-			}
-			if !found {
-				return false, fmt.Errorf("failed to find sched context for proc %d that's about to be stolen", pid)
-			}
-		}
-		o.queue.push(currentEvent())
-	case go122.EvProcStart:
-		pid := ProcID(ev.args[0])
-		seq := makeSeq(gen, ev.args[1])
-
-		// Try to advance. We might fail here due to sequencing, because the P hasn't
-		// had a status emitted, or because we already have a P and we're in a syscall,
-		// and we haven't observed that it was stolen from us yet.
-		state, ok := o.pStates[pid]
-		if !ok || state.status != go122.ProcIdle || !seq.succeeds(state.seq) || curCtx.P != NoProc {
-			// We can't make an inference as to whether this is bad. We could just be seeing
-			// a ProcStart on a different M before the proc's state was emitted, or before we
-			// got to the right point in the trace.
-			//
-			// Note that we also don't advance here if we have a P and we're in a syscall.
-			return false, nil
-		}
-		// We can advance this P. Check some invariants.
-		//
-		// We might have a goroutine if a goroutine is exiting a syscall.
-		reqs := event.SchedReqs{Thread: event.MustHave, Proc: event.MustNotHave, Goroutine: event.MayHave}
-		if err := validateCtx(curCtx, reqs); err != nil {
-			return false, err
-		}
-		state.status = go122.ProcRunning
-		state.seq = seq
-		newCtx.P = pid
-		o.queue.push(currentEvent())
-	case go122.EvProcStop:
-		// We must be able to advance this P.
-		//
-		// There are 2 ways a P can stop: ProcStop and ProcSteal. ProcStop is used when the P
-		// is stopped by the same M that started it, while ProcSteal is used when another M
-		// steals the P by stopping it from a distance.
-		//
-		// Since a P is bound to an M, and we're stopping on the same M we started, it must
-		// always be possible to advance the current M's P from a ProcStop. This is also why
-		// ProcStop doesn't need a sequence number.
-		state, ok := o.pStates[curCtx.P]
-		if !ok {
-			return false, fmt.Errorf("event %s for proc (%v) that doesn't exist", go122.EventString(typ), curCtx.P)
-		}
-		if state.status != go122.ProcRunning && state.status != go122.ProcSyscall {
-			return false, fmt.Errorf("%s event for proc that's not %s or %s", go122.EventString(typ), go122.ProcRunning, go122.ProcSyscall)
-		}
-		reqs := event.SchedReqs{Thread: event.MustHave, Proc: event.MustHave, Goroutine: event.MayHave}
-		if err := validateCtx(curCtx, reqs); err != nil {
-			return false, err
-		}
-		state.status = go122.ProcIdle
 		newCtx.P = NoProc
-		o.queue.push(currentEvent())
-	case go122.EvProcSteal:
-		pid := ProcID(ev.args[0])
-		seq := makeSeq(gen, ev.args[1])
-		state, ok := o.pStates[pid]
-		if !ok || (state.status != go122.ProcSyscall && state.status != go122.ProcSyscallAbandoned) || !seq.succeeds(state.seq) {
-			// We can't make an inference as to whether this is bad. We could just be seeing
-			// a ProcStart on a different M before the proc's state was emitted, or before we
-			// got to the right point in the trace.
-			return false, nil
-		}
-		// We can advance this P. Check some invariants.
-		reqs := event.SchedReqs{Thread: event.MustHave, Proc: event.MayHave, Goroutine: event.MayHave}
-		if err := validateCtx(curCtx, reqs); err != nil {
-			return false, err
-		}
-		// Smuggle in the P state that let us advance so we can surface information to the event.
-		// Specifically, we need to make sure that the event is interpreted not as a transition of
-		// ProcRunning -> ProcIdle but ProcIdle -> ProcIdle instead.
-		//
-		// ProcRunning is binding, but we may be running with a P on the current M and we can't
-		// bind another P. This P is about to go ProcIdle anyway.
-		oldStatus := state.status
-		ev.extra(version.Go122)[0] = uint64(oldStatus)
+		o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+		return newCtx, true, nil
+	}
 
-		// Update the P's status and sequence number.
-		state.status = go122.ProcIdle
-		state.seq = seq
+	// We're stealing from some other M.
+	mState, ok := o.mStates[mid]
+	if !ok {
+		return curCtx, false, fmt.Errorf("stole proc from non-existent thread %d", mid)
+	}
 
-		// If we've lost information then don't try to do anything with the M.
-		// It may have moved on and we can't be sure.
-		if oldStatus == go122.ProcSyscallAbandoned {
-			o.queue.push(currentEvent())
-			break
-		}
+	// Make sure we're actually stealing the right P.
+	if mState.p != pid {
+		return curCtx, false, fmt.Errorf("tried to steal proc %d from thread %d, but got proc %d instead", pid, mid, mState.p)
+	}
 
-		// Validate that the M we're stealing from is what we expect.
-		mid := ThreadID(ev.args[2]) // The M we're stealing from.
+	// Tell the M it has no P so it can proceed.
+	//
+	// This is safe because we know the P was in a syscall and
+	// the other M must be trying to get out of the syscall.
+	// GoSyscallEndBlocked cannot advance until the corresponding
+	// M loses its P.
+	mState.p = NoProc
+	o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+	return newCtx, true, nil
+}
 
-		if mid == curCtx.M {
-			// We're stealing from ourselves. This behaves like a ProcStop.
-			if curCtx.P != pid {
-				return false, fmt.Errorf("tried to self-steal proc %d (thread %d), but got proc %d instead", pid, mid, curCtx.P)
-			}
-			newCtx.P = NoProc
-			o.queue.push(currentEvent())
-			break
-		}
+func (o *ordering) advanceGoStatus(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+	gid := GoID(ev.args[0])
+	mid := ThreadID(ev.args[1])
+	status := go122.GoStatus(ev.args[2])
 
-		// We're stealing from some other M.
-		mState, ok := o.mStates[mid]
-		if !ok {
-			return false, fmt.Errorf("stole proc from non-existent thread %d", mid)
+	if int(status) >= len(go122GoStatus2GoState) {
+		return curCtx, false, fmt.Errorf("invalid status for goroutine %d: %d", gid, status)
+	}
+	oldState := go122GoStatus2GoState[status]
+	if s, ok := o.gStates[gid]; ok {
+		if s.status != status {
+			return curCtx, false, fmt.Errorf("inconsistent status for goroutine %d: old %v vs. new %v", gid, s.status, status)
 		}
+		s.seq = makeSeq(gen, 0) // Reset seq.
+	} else if gen == o.initialGen {
+		// Set the state.
+		o.gStates[gid] = &gState{id: gid, status: status, seq: makeSeq(gen, 0)}
+		oldState = GoUndetermined
+	} else {
+		return curCtx, false, fmt.Errorf("found goroutine status for new goroutine after the first generation: id=%v status=%v", gid, status)
+	}
+	ev.extra(version.Go122)[0] = uint64(oldState) // Smuggle in the old state for StateTransition.
 
-		// Make sure we're actually stealing the right P.
-		if mState.p != pid {
-			return false, fmt.Errorf("tried to steal proc %d from thread %d, but got proc %d instead", pid, mid, mState.p)
-		}
-
-		// Tell the M it has no P so it can proceed.
-		//
-		// This is safe because we know the P was in a syscall and
-		// the other M must be trying to get out of the syscall.
-		// GoSyscallEndBlocked cannot advance until the corresponding
-		// M loses its P.
-		mState.p = NoProc
-		o.queue.push(currentEvent())
-
-	// Handle goroutines.
-	case go122.EvGoStatus:
-		gid := GoID(ev.args[0])
-		mid := ThreadID(ev.args[1])
-		status := go122.GoStatus(ev.args[2])
-
-		if int(status) >= len(go122GoStatus2GoState) {
-			return false, fmt.Errorf("invalid status for goroutine %d: %d", gid, status)
-		}
-		oldState := go122GoStatus2GoState[status]
-		if s, ok := o.gStates[gid]; ok {
-			if s.status != status {
-				return false, fmt.Errorf("inconsistent status for goroutine %d: old %v vs. new %v", gid, s.status, status)
-			}
-			s.seq = makeSeq(gen, 0) // Reset seq.
-		} else if gen == o.initialGen {
-			// Set the state.
-			o.gStates[gid] = &gState{id: gid, status: status, seq: makeSeq(gen, 0)}
-			oldState = GoUndetermined
-		} else {
-			return false, fmt.Errorf("found goroutine status for new goroutine after the first generation: id=%v status=%v", gid, status)
-		}
-		ev.extra(version.Go122)[0] = uint64(oldState) // Smuggle in the old state for StateTransition.
-
-		switch status {
-		case go122.GoRunning:
-			// Bind the goroutine to the new context, since it's running.
-			newCtx.G = gid
-		case go122.GoSyscall:
-			if mid == NoThread {
-				return false, fmt.Errorf("found goroutine %d in syscall without a thread", gid)
-			}
-			// Is the syscall on this thread? If so, bind it to the context.
-			// Otherwise, we're talking about a G sitting in a syscall on an M.
-			// Validate the named M.
-			if mid == curCtx.M {
-				if gen != o.initialGen && curCtx.G != gid {
-					// If this isn't the first generation, we *must* have seen this
-					// binding occur already. Even if the G was blocked in a syscall
-					// for multiple generations since trace start, we would have seen
-					// a previous GoStatus event that bound the goroutine to an M.
-					return false, fmt.Errorf("inconsistent thread for syscalling goroutine %d: thread has goroutine %d", gid, curCtx.G)
-				}
-				newCtx.G = gid
-				break
-			}
-			// Now we're talking about a thread and goroutine that have been
-			// blocked on a syscall for the entire generation. This case must
-			// not have a P; the runtime makes sure that all Ps are traced at
-			// the beginning of a generation, which involves taking a P back
-			// from every thread.
-			ms, ok := o.mStates[mid]
-			if ok {
-				// This M has been seen. That means we must have seen this
-				// goroutine go into a syscall on this thread at some point.
-				if ms.g != gid {
-					// But the G on the M doesn't match. Something's wrong.
-					return false, fmt.Errorf("inconsistent thread for syscalling goroutine %d: thread has goroutine %d", gid, ms.g)
-				}
-				// This case is just a Syscall->Syscall event, which needs to
-				// appear as having the G currently bound to this M.
-				curCtx.G = ms.g
-			} else if !ok {
-				// The M hasn't been seen yet. That means this goroutine
-				// has just been sitting in a syscall on this M. Create
-				// a state for it.
-				o.mStates[mid] = &mState{g: gid, p: NoProc}
-				// Don't set curCtx.G in this case because this event is the
-				// binding event (and curCtx represents the "before" state).
-			}
-			// Update the current context to the M we're talking about.
-			curCtx.M = mid
-		}
-		o.queue.push(currentEvent())
-	case go122.EvGoCreate:
-		// Goroutines must be created on a running P, but may or may not be created
-		// by a running goroutine.
-		reqs := event.SchedReqs{Thread: event.MustHave, Proc: event.MustHave, Goroutine: event.MayHave}
-		if err := validateCtx(curCtx, reqs); err != nil {
-			return false, err
-		}
-		// If we have a goroutine, it must be running.
-		if state, ok := o.gStates[curCtx.G]; ok && state.status != go122.GoRunning {
-			return false, fmt.Errorf("%s event for goroutine that's not %s", go122.EventString(typ), GoRunning)
-		}
-		// This goroutine created another. Add a state for it.
-		newgid := GoID(ev.args[0])
-		if _, ok := o.gStates[newgid]; ok {
-			return false, fmt.Errorf("tried to create goroutine (%v) that already exists", newgid)
-		}
-		o.gStates[newgid] = &gState{id: newgid, status: go122.GoRunnable, seq: makeSeq(gen, 0)}
-		o.queue.push(currentEvent())
-	case go122.EvGoDestroy, go122.EvGoStop, go122.EvGoBlock:
-		// These are goroutine events that all require an active running
-		// goroutine on some thread. They must *always* be advance-able,
-		// since running goroutines are bound to their M.
-		if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
-			return false, err
-		}
-		state, ok := o.gStates[curCtx.G]
-		if !ok {
-			return false, fmt.Errorf("event %s for goroutine (%v) that doesn't exist", go122.EventString(typ), curCtx.G)
-		}
-		if state.status != go122.GoRunning {
-			return false, fmt.Errorf("%s event for goroutine that's not %s", go122.EventString(typ), GoRunning)
-		}
-		// Handle each case slightly differently; we just group them together
-		// because they have shared preconditions.
-		switch typ {
-		case go122.EvGoDestroy:
-			// This goroutine is exiting itself.
-			delete(o.gStates, curCtx.G)
-			newCtx.G = NoGoroutine
-		case go122.EvGoStop:
-			// Goroutine stopped (yielded). It's runnable but not running on this M.
-			state.status = go122.GoRunnable
-			newCtx.G = NoGoroutine
-		case go122.EvGoBlock:
-			// Goroutine blocked. It's waiting now and not running on this M.
-			state.status = go122.GoWaiting
-			newCtx.G = NoGoroutine
-		}
-		o.queue.push(currentEvent())
-	case go122.EvGoStart:
-		gid := GoID(ev.args[0])
-		seq := makeSeq(gen, ev.args[1])
-		state, ok := o.gStates[gid]
-		if !ok || state.status != go122.GoRunnable || !seq.succeeds(state.seq) {
-			// We can't make an inference as to whether this is bad. We could just be seeing
-			// a GoStart on a different M before the goroutine was created, before it had its
-			// state emitted, or before we got to the right point in the trace yet.
-			return false, nil
-		}
-		// We can advance this goroutine. Check some invariants.
-		reqs := event.SchedReqs{Thread: event.MustHave, Proc: event.MustHave, Goroutine: event.MustNotHave}
-		if err := validateCtx(curCtx, reqs); err != nil {
-			return false, err
-		}
-		state.status = go122.GoRunning
-		state.seq = seq
+	newCtx := curCtx
+	switch status {
+	case go122.GoRunning:
+		// Bind the goroutine to the new context, since it's running.
 		newCtx.G = gid
-		o.queue.push(currentEvent())
-	case go122.EvGoUnblock:
-		// N.B. These both reference the goroutine to unblock, not the current goroutine.
-		gid := GoID(ev.args[0])
-		seq := makeSeq(gen, ev.args[1])
-		state, ok := o.gStates[gid]
-		if !ok || state.status != go122.GoWaiting || !seq.succeeds(state.seq) {
-			// We can't make an inference as to whether this is bad. We could just be seeing
-			// a GoUnblock on a different M before the goroutine was created and blocked itself,
-			// before it had its state emitted, or before we got to the right point in the trace yet.
-			return false, nil
+	case go122.GoSyscall:
+		if mid == NoThread {
+			return curCtx, false, fmt.Errorf("found goroutine %d in syscall without a thread", gid)
 		}
-		state.status = go122.GoRunnable
-		state.seq = seq
-		// N.B. No context to validate. Basically anything can unblock
-		// a goroutine (e.g. sysmon).
-		o.queue.push(currentEvent())
-	case go122.EvGoSyscallBegin:
-		// Entering a syscall requires an active running goroutine with a
-		// proc on some thread. It is always advancable.
-		if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
-			return false, err
+		// Is the syscall on this thread? If so, bind it to the context.
+		// Otherwise, we're talking about a G sitting in a syscall on an M.
+		// Validate the named M.
+		if mid == curCtx.M {
+			if gen != o.initialGen && curCtx.G != gid {
+				// If this isn't the first generation, we *must* have seen this
+				// binding occur already. Even if the G was blocked in a syscall
+				// for multiple generations since trace start, we would have seen
+				// a previous GoStatus event that bound the goroutine to an M.
+				return curCtx, false, fmt.Errorf("inconsistent thread for syscalling goroutine %d: thread has goroutine %d", gid, curCtx.G)
+			}
+			newCtx.G = gid
+			break
 		}
-		state, ok := o.gStates[curCtx.G]
-		if !ok {
-			return false, fmt.Errorf("event %s for goroutine (%v) that doesn't exist", go122.EventString(typ), curCtx.G)
+		// Now we're talking about a thread and goroutine that have been
+		// blocked on a syscall for the entire generation. This case must
+		// not have a P; the runtime makes sure that all Ps are traced at
+		// the beginning of a generation, which involves taking a P back
+		// from every thread.
+		ms, ok := o.mStates[mid]
+		if ok {
+			// This M has been seen. That means we must have seen this
+			// goroutine go into a syscall on this thread at some point.
+			if ms.g != gid {
+				// But the G on the M doesn't match. Something's wrong.
+				return curCtx, false, fmt.Errorf("inconsistent thread for syscalling goroutine %d: thread has goroutine %d", gid, ms.g)
+			}
+			// This case is just a Syscall->Syscall event, which needs to
+			// appear as having the G currently bound to this M.
+			curCtx.G = ms.g
+		} else if !ok {
+			// The M hasn't been seen yet. That means this goroutine
+			// has just been sitting in a syscall on this M. Create
+			// a state for it.
+			o.mStates[mid] = &mState{g: gid, p: NoProc}
+			// Don't set curCtx.G in this case because this event is the
+			// binding event (and curCtx represents the "before" state).
 		}
-		if state.status != go122.GoRunning {
-			return false, fmt.Errorf("%s event for goroutine that's not %s", go122.EventString(typ), GoRunning)
-		}
-		// Goroutine entered a syscall. It's still running on this P and M.
-		state.status = go122.GoSyscall
-		pState, ok := o.pStates[curCtx.P]
-		if !ok {
-			return false, fmt.Errorf("uninitialized proc %d found during %s", curCtx.P, go122.EventString(typ))
-		}
-		pState.status = go122.ProcSyscall
-		// Validate the P sequence number on the event and advance it.
-		//
-		// We have a P sequence number for what is supposed to be a goroutine event
-		// so that we can correctly model P stealing. Without this sequence number here,
-		// the syscall from which a ProcSteal event is stealing can be ambiguous in the
-		// face of broken timestamps. See the go122-syscall-steal-proc-ambiguous test for
-		// more details.
-		//
-		// Note that because this sequence number only exists as a tool for disambiguation,
-		// we can enforce that we have the right sequence number at this point; we don't need
-		// to back off and see if any other events will advance. This is a running P.
-		pSeq := makeSeq(gen, ev.args[0])
-		if !pSeq.succeeds(pState.seq) {
-			return false, fmt.Errorf("failed to advance %s: can't make sequence: %s -> %s", go122.EventString(typ), pState.seq, pSeq)
-		}
-		pState.seq = pSeq
-		o.queue.push(currentEvent())
-	case go122.EvGoSyscallEnd:
-		// This event is always advance-able because it happens on the same
-		// thread that EvGoSyscallStart happened, and the goroutine can't leave
-		// that thread until its done.
-		if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
-			return false, err
-		}
-		state, ok := o.gStates[curCtx.G]
-		if !ok {
-			return false, fmt.Errorf("event %s for goroutine (%v) that doesn't exist", go122.EventString(typ), curCtx.G)
-		}
-		if state.status != go122.GoSyscall {
-			return false, fmt.Errorf("%s event for goroutine that's not %s", go122.EventString(typ), GoRunning)
-		}
-		state.status = go122.GoRunning
+		// Update the current context to the M we're talking about.
+		curCtx.M = mid
+	}
+	o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+	return newCtx, true, nil
+}
 
-		// Transfer the P back to running from syscall.
-		pState, ok := o.pStates[curCtx.P]
-		if !ok {
-			return false, fmt.Errorf("uninitialized proc %d found during %s", curCtx.P, go122.EventString(typ))
-		}
-		if pState.status != go122.ProcSyscall {
-			return false, fmt.Errorf("expected proc %d in state %v, but got %v instead", curCtx.P, go122.ProcSyscall, pState.status)
-		}
-		pState.status = go122.ProcRunning
-		o.queue.push(currentEvent())
-	case go122.EvGoSyscallEndBlocked:
-		// This event becomes advanceable when its P is not in a syscall state
-		// (lack of a P altogether is also acceptable for advancing).
-		// The transfer out of ProcSyscall can happen either voluntarily via
-		// ProcStop or involuntarily via ProcSteal. We may also acquire a new P
-		// before we get here (after the transfer out) but that's OK: that new
-		// P won't be in the ProcSyscall state anymore.
-		//
-		// Basically: while we have a preemptible P, don't advance, because we
-		// *know* from the event that we're going to lose it at some point during
-		// the syscall. We shouldn't advance until that happens.
-		if curCtx.P != NoProc {
-			pState, ok := o.pStates[curCtx.P]
-			if !ok {
-				return false, fmt.Errorf("uninitialized proc %d found during %s", curCtx.P, go122.EventString(typ))
-			}
-			if pState.status == go122.ProcSyscall {
-				return false, nil
-			}
-		}
-		// As mentioned above, we may have a P here if we ProcStart
-		// before this event.
-		if err := validateCtx(curCtx, event.SchedReqs{Thread: event.MustHave, Proc: event.MayHave, Goroutine: event.MustHave}); err != nil {
-			return false, err
-		}
-		state, ok := o.gStates[curCtx.G]
-		if !ok {
-			return false, fmt.Errorf("event %s for goroutine (%v) that doesn't exist", go122.EventString(typ), curCtx.G)
-		}
-		if state.status != go122.GoSyscall {
-			return false, fmt.Errorf("%s event for goroutine that's not %s", go122.EventString(typ), GoRunning)
-		}
-		newCtx.G = NoGoroutine
-		state.status = go122.GoRunnable
-		o.queue.push(currentEvent())
-	case go122.EvGoCreateSyscall:
-		// This event indicates that a goroutine is effectively
-		// being created out of a cgo callback. Such a goroutine
-		// is 'created' in the syscall state.
-		if err := validateCtx(curCtx, event.SchedReqs{Thread: event.MustHave, Proc: event.MayHave, Goroutine: event.MustNotHave}); err != nil {
-			return false, err
-		}
-		// This goroutine is effectively being created. Add a state for it.
-		newgid := GoID(ev.args[0])
-		if _, ok := o.gStates[newgid]; ok {
-			return false, fmt.Errorf("tried to create goroutine (%v) in syscall that already exists", newgid)
-		}
-		o.gStates[newgid] = &gState{id: newgid, status: go122.GoSyscall, seq: makeSeq(gen, 0)}
-		// Goroutine is executing. Bind it to the context.
-		newCtx.G = newgid
-		o.queue.push(currentEvent())
-	case go122.EvGoDestroySyscall:
-		// This event indicates that a goroutine created for a
-		// cgo callback is disappearing, either because the callback
-		// ending or the C thread that called it is being destroyed.
-		//
-		// Also, treat this as if we lost our P too.
-		// The thread ID may be reused by the platform and we'll get
-		// really confused if we try to steal the P is this is running
-		// with later. The new M with the same ID could even try to
-		// steal back this P from itself!
-		//
-		// The runtime is careful to make sure that any GoCreateSyscall
-		// event will enter the runtime emitting events for reacquiring a P.
-		//
-		// Note: we might have a P here. The P might not be released
-		// eagerly by the runtime, and it might get stolen back later
-		// (or never again, if the program is going to exit).
-		if err := validateCtx(curCtx, event.SchedReqs{Thread: event.MustHave, Proc: event.MayHave, Goroutine: event.MustHave}); err != nil {
-			return false, err
-		}
-		// Check to make sure the goroutine exists in the right state.
-		state, ok := o.gStates[curCtx.G]
-		if !ok {
-			return false, fmt.Errorf("event %s for goroutine (%v) that doesn't exist", go122.EventString(typ), curCtx.G)
-		}
-		if state.status != go122.GoSyscall {
-			return false, fmt.Errorf("%s event for goroutine that's not %v", go122.EventString(typ), GoSyscall)
-		}
+func (o *ordering) advanceGoCreate(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+	// Goroutines must be created on a running P, but may or may not be created
+	// by a running goroutine.
+	reqs := event.SchedReqs{Thread: event.MustHave, Proc: event.MustHave, Goroutine: event.MayHave}
+	if err := validateCtx(curCtx, reqs); err != nil {
+		return curCtx, false, err
+	}
+	// If we have a goroutine, it must be running.
+	if state, ok := o.gStates[curCtx.G]; ok && state.status != go122.GoRunning {
+		return curCtx, false, fmt.Errorf("%s event for goroutine that's not %s", go122.EventString(ev.typ), GoRunning)
+	}
+	// This goroutine created another. Add a state for it.
+	newgid := GoID(ev.args[0])
+	if _, ok := o.gStates[newgid]; ok {
+		return curCtx, false, fmt.Errorf("tried to create goroutine (%v) that already exists", newgid)
+	}
+	status := go122.GoRunnable
+	if ev.typ == go122.EvGoCreateBlocked {
+		status = go122.GoWaiting
+	}
+	o.gStates[newgid] = &gState{id: newgid, status: status, seq: makeSeq(gen, 0)}
+	o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+	return curCtx, true, nil
+}
+
+func (o *ordering) advanceGoStopExec(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+	// These are goroutine events that all require an active running
+	// goroutine on some thread. They must *always* be advance-able,
+	// since running goroutines are bound to their M.
+	if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
+		return curCtx, false, err
+	}
+	state, ok := o.gStates[curCtx.G]
+	if !ok {
+		return curCtx, false, fmt.Errorf("event %s for goroutine (%v) that doesn't exist", go122.EventString(ev.typ), curCtx.G)
+	}
+	if state.status != go122.GoRunning {
+		return curCtx, false, fmt.Errorf("%s event for goroutine that's not %s", go122.EventString(ev.typ), GoRunning)
+	}
+	// Handle each case slightly differently; we just group them together
+	// because they have shared preconditions.
+	newCtx := curCtx
+	switch ev.typ {
+	case go122.EvGoDestroy:
 		// This goroutine is exiting itself.
 		delete(o.gStates, curCtx.G)
 		newCtx.G = NoGoroutine
+	case go122.EvGoStop:
+		// Goroutine stopped (yielded). It's runnable but not running on this M.
+		state.status = go122.GoRunnable
+		newCtx.G = NoGoroutine
+	case go122.EvGoBlock:
+		// Goroutine blocked. It's waiting now and not running on this M.
+		state.status = go122.GoWaiting
+		newCtx.G = NoGoroutine
+	}
+	o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+	return newCtx, true, nil
+}
 
-		// If we have a proc, then we're dissociating from it now. See the comment at the top of the case.
-		if curCtx.P != NoProc {
-			pState, ok := o.pStates[curCtx.P]
-			if !ok {
-				return false, fmt.Errorf("found invalid proc %d during %s", curCtx.P, go122.EventString(typ))
-			}
-			if pState.status != go122.ProcSyscall {
-				return false, fmt.Errorf("proc %d in unexpected state %s during %s", curCtx.P, pState.status, go122.EventString(typ))
-			}
-			// See the go122-create-syscall-reuse-thread-id test case for more details.
-			pState.status = go122.ProcSyscallAbandoned
-			newCtx.P = NoProc
+func (o *ordering) advanceGoStart(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+	gid := GoID(ev.args[0])
+	seq := makeSeq(gen, ev.args[1])
+	state, ok := o.gStates[gid]
+	if !ok || state.status != go122.GoRunnable || !seq.succeeds(state.seq) {
+		// We can't make an inference as to whether this is bad. We could just be seeing
+		// a GoStart on a different M before the goroutine was created, before it had its
+		// state emitted, or before we got to the right point in the trace yet.
+		return curCtx, false, nil
+	}
+	// We can advance this goroutine. Check some invariants.
+	reqs := event.SchedReqs{Thread: event.MustHave, Proc: event.MustHave, Goroutine: event.MustNotHave}
+	if err := validateCtx(curCtx, reqs); err != nil {
+		return curCtx, false, err
+	}
+	state.status = go122.GoRunning
+	state.seq = seq
+	newCtx := curCtx
+	newCtx.G = gid
+	o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+	return newCtx, true, nil
+}
 
-			// Queue an extra self-ProcSteal event.
-			extra := Event{
-				table: evt,
-				ctx:   curCtx,
-				base: baseEvent{
-					typ:  go122.EvProcSteal,
-					time: ev.time,
-				},
-			}
-			extra.base.args[0] = uint64(curCtx.P)
-			extra.base.extra(version.Go122)[0] = uint64(go122.ProcSyscall)
-			o.queue.push(extra)
+func (o *ordering) advanceGoUnblock(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+	// N.B. These both reference the goroutine to unblock, not the current goroutine.
+	gid := GoID(ev.args[0])
+	seq := makeSeq(gen, ev.args[1])
+	state, ok := o.gStates[gid]
+	if !ok || state.status != go122.GoWaiting || !seq.succeeds(state.seq) {
+		// We can't make an inference as to whether this is bad. We could just be seeing
+		// a GoUnblock on a different M before the goroutine was created and blocked itself,
+		// before it had its state emitted, or before we got to the right point in the trace yet.
+		return curCtx, false, nil
+	}
+	state.status = go122.GoRunnable
+	state.seq = seq
+	// N.B. No context to validate. Basically anything can unblock
+	// a goroutine (e.g. sysmon).
+	o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+	return curCtx, true, nil
+}
+
+func (o *ordering) advanceGoSwitch(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+	// GoSwitch and GoSwitchDestroy represent a trio of events:
+	// - Unblock of the goroutine to switch to.
+	// - Block or destroy of the current goroutine.
+	// - Start executing the next goroutine.
+	//
+	// Because it acts like a GoStart for the next goroutine, we can
+	// only advance it if the sequence numbers line up.
+	//
+	// The current goroutine on the thread must be actively running.
+	if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
+		return curCtx, false, err
+	}
+	curGState, ok := o.gStates[curCtx.G]
+	if !ok {
+		return curCtx, false, fmt.Errorf("event %s for goroutine (%v) that doesn't exist", go122.EventString(ev.typ), curCtx.G)
+	}
+	if curGState.status != go122.GoRunning {
+		return curCtx, false, fmt.Errorf("%s event for goroutine that's not %s", go122.EventString(ev.typ), GoRunning)
+	}
+	nextg := GoID(ev.args[0])
+	seq := makeSeq(gen, ev.args[1]) // seq is for nextg, not curCtx.G.
+	nextGState, ok := o.gStates[nextg]
+	if !ok || nextGState.status != go122.GoWaiting || !seq.succeeds(nextGState.seq) {
+		// We can't make an inference as to whether this is bad. We could just be seeing
+		// a GoSwitch on a different M before the goroutine was created, before it had its
+		// state emitted, or before we got to the right point in the trace yet.
+		return curCtx, false, nil
+	}
+	o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+
+	// Update the state of the executing goroutine and emit an event for it
+	// (GoSwitch and GoSwitchDestroy will be interpreted as GoUnblock events
+	// for nextg).
+	switch ev.typ {
+	case go122.EvGoSwitch:
+		// Goroutine blocked. It's waiting now and not running on this M.
+		curGState.status = go122.GoWaiting
+
+		// Emit a GoBlock event.
+		// TODO(mknyszek): Emit a reason.
+		o.queue.push(makeEvent(evt, curCtx, go122.EvGoBlock, ev.time, 0 /* no reason */, 0 /* no stack */))
+	case go122.EvGoSwitchDestroy:
+		// This goroutine is exiting itself.
+		delete(o.gStates, curCtx.G)
+
+		// Emit a GoDestroy event.
+		o.queue.push(makeEvent(evt, curCtx, go122.EvGoDestroy, ev.time))
+	}
+	// Update the state of the next goroutine.
+	nextGState.status = go122.GoRunning
+	nextGState.seq = seq
+	newCtx := curCtx
+	newCtx.G = nextg
+
+	// Queue an event for the next goroutine starting to run.
+	startCtx := curCtx
+	startCtx.G = NoGoroutine
+	o.queue.push(makeEvent(evt, startCtx, go122.EvGoStart, ev.time, uint64(nextg), ev.args[1]))
+	return newCtx, true, nil
+}
+
+func (o *ordering) advanceGoSyscallBegin(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+	// Entering a syscall requires an active running goroutine with a
+	// proc on some thread. It is always advancable.
+	if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
+		return curCtx, false, err
+	}
+	state, ok := o.gStates[curCtx.G]
+	if !ok {
+		return curCtx, false, fmt.Errorf("event %s for goroutine (%v) that doesn't exist", go122.EventString(ev.typ), curCtx.G)
+	}
+	if state.status != go122.GoRunning {
+		return curCtx, false, fmt.Errorf("%s event for goroutine that's not %s", go122.EventString(ev.typ), GoRunning)
+	}
+	// Goroutine entered a syscall. It's still running on this P and M.
+	state.status = go122.GoSyscall
+	pState, ok := o.pStates[curCtx.P]
+	if !ok {
+		return curCtx, false, fmt.Errorf("uninitialized proc %d found during %s", curCtx.P, go122.EventString(ev.typ))
+	}
+	pState.status = go122.ProcSyscall
+	// Validate the P sequence number on the event and advance it.
+	//
+	// We have a P sequence number for what is supposed to be a goroutine event
+	// so that we can correctly model P stealing. Without this sequence number here,
+	// the syscall from which a ProcSteal event is stealing can be ambiguous in the
+	// face of broken timestamps. See the go122-syscall-steal-proc-ambiguous test for
+	// more details.
+	//
+	// Note that because this sequence number only exists as a tool for disambiguation,
+	// we can enforce that we have the right sequence number at this point; we don't need
+	// to back off and see if any other events will advance. This is a running P.
+	pSeq := makeSeq(gen, ev.args[0])
+	if !pSeq.succeeds(pState.seq) {
+		return curCtx, false, fmt.Errorf("failed to advance %s: can't make sequence: %s -> %s", go122.EventString(ev.typ), pState.seq, pSeq)
+	}
+	pState.seq = pSeq
+	o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+	return curCtx, true, nil
+}
+
+func (o *ordering) advanceGoSyscallEnd(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+	// This event is always advance-able because it happens on the same
+	// thread that EvGoSyscallStart happened, and the goroutine can't leave
+	// that thread until its done.
+	if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
+		return curCtx, false, err
+	}
+	state, ok := o.gStates[curCtx.G]
+	if !ok {
+		return curCtx, false, fmt.Errorf("event %s for goroutine (%v) that doesn't exist", go122.EventString(ev.typ), curCtx.G)
+	}
+	if state.status != go122.GoSyscall {
+		return curCtx, false, fmt.Errorf("%s event for goroutine that's not %s", go122.EventString(ev.typ), GoRunning)
+	}
+	state.status = go122.GoRunning
+
+	// Transfer the P back to running from syscall.
+	pState, ok := o.pStates[curCtx.P]
+	if !ok {
+		return curCtx, false, fmt.Errorf("uninitialized proc %d found during %s", curCtx.P, go122.EventString(ev.typ))
+	}
+	if pState.status != go122.ProcSyscall {
+		return curCtx, false, fmt.Errorf("expected proc %d in state %v, but got %v instead", curCtx.P, go122.ProcSyscall, pState.status)
+	}
+	pState.status = go122.ProcRunning
+	o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+	return curCtx, true, nil
+}
+
+func (o *ordering) advanceGoSyscallEndBlocked(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+	// This event becomes advanceable when its P is not in a syscall state
+	// (lack of a P altogether is also acceptable for advancing).
+	// The transfer out of ProcSyscall can happen either voluntarily via
+	// ProcStop or involuntarily via ProcSteal. We may also acquire a new P
+	// before we get here (after the transfer out) but that's OK: that new
+	// P won't be in the ProcSyscall state anymore.
+	//
+	// Basically: while we have a preemptible P, don't advance, because we
+	// *know* from the event that we're going to lose it at some point during
+	// the syscall. We shouldn't advance until that happens.
+	if curCtx.P != NoProc {
+		pState, ok := o.pStates[curCtx.P]
+		if !ok {
+			return curCtx, false, fmt.Errorf("uninitialized proc %d found during %s", curCtx.P, go122.EventString(ev.typ))
 		}
-		o.queue.push(currentEvent())
+		if pState.status == go122.ProcSyscall {
+			return curCtx, false, nil
+		}
+	}
+	// As mentioned above, we may have a P here if we ProcStart
+	// before this event.
+	if err := validateCtx(curCtx, event.SchedReqs{Thread: event.MustHave, Proc: event.MayHave, Goroutine: event.MustHave}); err != nil {
+		return curCtx, false, err
+	}
+	state, ok := o.gStates[curCtx.G]
+	if !ok {
+		return curCtx, false, fmt.Errorf("event %s for goroutine (%v) that doesn't exist", go122.EventString(ev.typ), curCtx.G)
+	}
+	if state.status != go122.GoSyscall {
+		return curCtx, false, fmt.Errorf("%s event for goroutine that's not %s", go122.EventString(ev.typ), GoRunning)
+	}
+	newCtx := curCtx
+	newCtx.G = NoGoroutine
+	state.status = go122.GoRunnable
+	o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+	return newCtx, true, nil
+}
 
+func (o *ordering) advanceGoCreateSyscall(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+	// This event indicates that a goroutine is effectively
+	// being created out of a cgo callback. Such a goroutine
+	// is 'created' in the syscall state.
+	if err := validateCtx(curCtx, event.SchedReqs{Thread: event.MustHave, Proc: event.MayHave, Goroutine: event.MustNotHave}); err != nil {
+		return curCtx, false, err
+	}
+	// This goroutine is effectively being created. Add a state for it.
+	newgid := GoID(ev.args[0])
+	if _, ok := o.gStates[newgid]; ok {
+		return curCtx, false, fmt.Errorf("tried to create goroutine (%v) in syscall that already exists", newgid)
+	}
+	o.gStates[newgid] = &gState{id: newgid, status: go122.GoSyscall, seq: makeSeq(gen, 0)}
+	// Goroutine is executing. Bind it to the context.
+	newCtx := curCtx
+	newCtx.G = newgid
+	o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+	return newCtx, true, nil
+}
+
+func (o *ordering) advanceGoDestroySyscall(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+	// This event indicates that a goroutine created for a
+	// cgo callback is disappearing, either because the callback
+	// ending or the C thread that called it is being destroyed.
+	//
+	// Also, treat this as if we lost our P too.
+	// The thread ID may be reused by the platform and we'll get
+	// really confused if we try to steal the P is this is running
+	// with later. The new M with the same ID could even try to
+	// steal back this P from itself!
+	//
+	// The runtime is careful to make sure that any GoCreateSyscall
+	// event will enter the runtime emitting events for reacquiring a P.
+	//
+	// Note: we might have a P here. The P might not be released
+	// eagerly by the runtime, and it might get stolen back later
+	// (or never again, if the program is going to exit).
+	if err := validateCtx(curCtx, event.SchedReqs{Thread: event.MustHave, Proc: event.MayHave, Goroutine: event.MustHave}); err != nil {
+		return curCtx, false, err
+	}
+	// Check to make sure the goroutine exists in the right state.
+	state, ok := o.gStates[curCtx.G]
+	if !ok {
+		return curCtx, false, fmt.Errorf("event %s for goroutine (%v) that doesn't exist", go122.EventString(ev.typ), curCtx.G)
+	}
+	if state.status != go122.GoSyscall {
+		return curCtx, false, fmt.Errorf("%s event for goroutine that's not %v", go122.EventString(ev.typ), GoSyscall)
+	}
+	// This goroutine is exiting itself.
+	delete(o.gStates, curCtx.G)
+	newCtx := curCtx
+	newCtx.G = NoGoroutine
+
+	// If we have a proc, then we're dissociating from it now. See the comment at the top of the case.
+	if curCtx.P != NoProc {
+		pState, ok := o.pStates[curCtx.P]
+		if !ok {
+			return curCtx, false, fmt.Errorf("found invalid proc %d during %s", curCtx.P, go122.EventString(ev.typ))
+		}
+		if pState.status != go122.ProcSyscall {
+			return curCtx, false, fmt.Errorf("proc %d in unexpected state %s during %s", curCtx.P, pState.status, go122.EventString(ev.typ))
+		}
+		// See the go122-create-syscall-reuse-thread-id test case for more details.
+		pState.status = go122.ProcSyscallAbandoned
+		newCtx.P = NoProc
+
+		// Queue an extra self-ProcSteal event.
+		extra := makeEvent(evt, curCtx, go122.EvProcSteal, ev.time, uint64(curCtx.P))
+		extra.base.extra(version.Go122)[0] = uint64(go122.ProcSyscall)
+		o.queue.push(extra)
+	}
+	o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+	return newCtx, true, nil
+}
+
+func (o *ordering) advanceUserTaskBegin(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
 	// Handle tasks. Tasks are interesting because:
 	// - There's no Begin event required to reference a task.
 	// - End for a particular task ID can appear multiple times.
@@ -603,265 +772,291 @@
 	// thing we have to be sure of is that a task didn't begin
 	// after it had already begun. Task IDs are allowed to be
 	// reused, so we don't care about a Begin after an End.
-	case go122.EvUserTaskBegin:
-		id := TaskID(ev.args[0])
-		if _, ok := o.activeTasks[id]; ok {
-			return false, fmt.Errorf("task ID conflict: %d", id)
-		}
-		// Get the parent ID, but don't validate it. There's no guarantee
-		// we actually have information on whether it's active.
-		parentID := TaskID(ev.args[1])
-		if parentID == BackgroundTask {
-			// Note: a value of 0 here actually means no parent, *not* the
-			// background task. Automatic background task attachment only
-			// applies to regions.
-			parentID = NoTask
-			ev.args[1] = uint64(NoTask)
-		}
+	id := TaskID(ev.args[0])
+	if _, ok := o.activeTasks[id]; ok {
+		return curCtx, false, fmt.Errorf("task ID conflict: %d", id)
+	}
+	// Get the parent ID, but don't validate it. There's no guarantee
+	// we actually have information on whether it's active.
+	parentID := TaskID(ev.args[1])
+	if parentID == BackgroundTask {
+		// Note: a value of 0 here actually means no parent, *not* the
+		// background task. Automatic background task attachment only
+		// applies to regions.
+		parentID = NoTask
+		ev.args[1] = uint64(NoTask)
+	}
 
-		// Validate the name and record it. We'll need to pass it through to
-		// EvUserTaskEnd.
-		nameID := stringID(ev.args[2])
-		name, ok := evt.strings.get(nameID)
-		if !ok {
-			return false, fmt.Errorf("invalid string ID %v for %v event", nameID, typ)
-		}
-		o.activeTasks[id] = taskState{name: name, parentID: parentID}
-		if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
-			return false, err
-		}
-		o.queue.push(currentEvent())
-	case go122.EvUserTaskEnd:
-		id := TaskID(ev.args[0])
-		if ts, ok := o.activeTasks[id]; ok {
-			// Smuggle the task info. This may happen in a different generation,
-			// which may not have the name in its string table. Add it to the extra
-			// strings table so we can look it up later.
-			ev.extra(version.Go122)[0] = uint64(ts.parentID)
-			ev.extra(version.Go122)[1] = uint64(evt.addExtraString(ts.name))
-			delete(o.activeTasks, id)
-		} else {
-			// Explicitly clear the task info.
-			ev.extra(version.Go122)[0] = uint64(NoTask)
-			ev.extra(version.Go122)[1] = uint64(evt.addExtraString(""))
-		}
-		if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
-			return false, err
-		}
-		o.queue.push(currentEvent())
+	// Validate the name and record it. We'll need to pass it through to
+	// EvUserTaskEnd.
+	nameID := stringID(ev.args[2])
+	name, ok := evt.strings.get(nameID)
+	if !ok {
+		return curCtx, false, fmt.Errorf("invalid string ID %v for %v event", nameID, ev.typ)
+	}
+	o.activeTasks[id] = taskState{name: name, parentID: parentID}
+	if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
+		return curCtx, false, err
+	}
+	o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+	return curCtx, true, nil
+}
 
-	// Handle user regions.
-	case go122.EvUserRegionBegin:
-		if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
-			return false, err
-		}
-		tid := TaskID(ev.args[0])
-		nameID := stringID(ev.args[1])
-		name, ok := evt.strings.get(nameID)
-		if !ok {
-			return false, fmt.Errorf("invalid string ID %v for %v event", nameID, typ)
-		}
-		gState, ok := o.gStates[curCtx.G]
-		if !ok {
-			return false, fmt.Errorf("encountered EvUserRegionBegin without known state for current goroutine %d", curCtx.G)
-		}
-		if err := gState.beginRegion(userRegion{tid, name}); err != nil {
-			return false, err
-		}
-		o.queue.push(currentEvent())
-	case go122.EvUserRegionEnd:
-		if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
-			return false, err
-		}
-		tid := TaskID(ev.args[0])
-		nameID := stringID(ev.args[1])
-		name, ok := evt.strings.get(nameID)
-		if !ok {
-			return false, fmt.Errorf("invalid string ID %v for %v event", nameID, typ)
-		}
-		gState, ok := o.gStates[curCtx.G]
-		if !ok {
-			return false, fmt.Errorf("encountered EvUserRegionEnd without known state for current goroutine %d", curCtx.G)
-		}
-		if err := gState.endRegion(userRegion{tid, name}); err != nil {
-			return false, err
-		}
-		o.queue.push(currentEvent())
+func (o *ordering) advanceUserTaskEnd(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+	id := TaskID(ev.args[0])
+	if ts, ok := o.activeTasks[id]; ok {
+		// Smuggle the task info. This may happen in a different generation,
+		// which may not have the name in its string table. Add it to the extra
+		// strings table so we can look it up later.
+		ev.extra(version.Go122)[0] = uint64(ts.parentID)
+		ev.extra(version.Go122)[1] = uint64(evt.addExtraString(ts.name))
+		delete(o.activeTasks, id)
+	} else {
+		// Explicitly clear the task info.
+		ev.extra(version.Go122)[0] = uint64(NoTask)
+		ev.extra(version.Go122)[1] = uint64(evt.addExtraString(""))
+	}
+	if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
+		return curCtx, false, err
+	}
+	o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+	return curCtx, true, nil
+}
 
-	// Handle the GC mark phase.
-	//
-	// We have sequence numbers for both start and end because they
-	// can happen on completely different threads. We want an explicit
-	// partial order edge between start and end here, otherwise we're
-	// relying entirely on timestamps to make sure we don't advance a
-	// GCEnd for a _different_ GC cycle if timestamps are wildly broken.
-	case go122.EvGCActive:
-		seq := ev.args[0]
-		if gen == o.initialGen {
-			if o.gcState != gcUndetermined {
-				return false, fmt.Errorf("GCActive in the first generation isn't first GC event")
-			}
-			o.gcSeq = seq
-			o.gcState = gcRunning
-			o.queue.push(currentEvent())
-			break
-		}
-		if seq != o.gcSeq+1 {
-			// This is not the right GC cycle.
-			return false, nil
-		}
-		if o.gcState != gcRunning {
-			return false, fmt.Errorf("encountered GCActive while GC was not in progress")
-		}
-		o.gcSeq = seq
-		if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
-			return false, err
-		}
-		o.queue.push(currentEvent())
-	case go122.EvGCBegin:
-		seq := ev.args[0]
-		if o.gcState == gcUndetermined {
-			o.gcSeq = seq
-			o.gcState = gcRunning
-			o.queue.push(currentEvent())
-			break
-		}
-		if seq != o.gcSeq+1 {
-			// This is not the right GC cycle.
-			return false, nil
-		}
-		if o.gcState == gcRunning {
-			return false, fmt.Errorf("encountered GCBegin while GC was already in progress")
+func (o *ordering) advanceUserRegionBegin(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+	if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
+		return curCtx, false, err
+	}
+	tid := TaskID(ev.args[0])
+	nameID := stringID(ev.args[1])
+	name, ok := evt.strings.get(nameID)
+	if !ok {
+		return curCtx, false, fmt.Errorf("invalid string ID %v for %v event", nameID, ev.typ)
+	}
+	gState, ok := o.gStates[curCtx.G]
+	if !ok {
+		return curCtx, false, fmt.Errorf("encountered EvUserRegionBegin without known state for current goroutine %d", curCtx.G)
+	}
+	if err := gState.beginRegion(userRegion{tid, name}); err != nil {
+		return curCtx, false, err
+	}
+	o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+	return curCtx, true, nil
+}
+
+func (o *ordering) advanceUserRegionEnd(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+	if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
+		return curCtx, false, err
+	}
+	tid := TaskID(ev.args[0])
+	nameID := stringID(ev.args[1])
+	name, ok := evt.strings.get(nameID)
+	if !ok {
+		return curCtx, false, fmt.Errorf("invalid string ID %v for %v event", nameID, ev.typ)
+	}
+	gState, ok := o.gStates[curCtx.G]
+	if !ok {
+		return curCtx, false, fmt.Errorf("encountered EvUserRegionEnd without known state for current goroutine %d", curCtx.G)
+	}
+	if err := gState.endRegion(userRegion{tid, name}); err != nil {
+		return curCtx, false, err
+	}
+	o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+	return curCtx, true, nil
+}
+
+// Handle the GC mark phase.
+//
+// We have sequence numbers for both start and end because they
+// can happen on completely different threads. We want an explicit
+// partial order edge between start and end here, otherwise we're
+// relying entirely on timestamps to make sure we don't advance a
+// GCEnd for a _different_ GC cycle if timestamps are wildly broken.
+func (o *ordering) advanceGCActive(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+	seq := ev.args[0]
+	if gen == o.initialGen {
+		if o.gcState != gcUndetermined {
+			return curCtx, false, fmt.Errorf("GCActive in the first generation isn't first GC event")
 		}
 		o.gcSeq = seq
 		o.gcState = gcRunning
-		if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
-			return false, err
-		}
-		o.queue.push(currentEvent())
-	case go122.EvGCEnd:
-		seq := ev.args[0]
-		if seq != o.gcSeq+1 {
-			// This is not the right GC cycle.
-			return false, nil
-		}
-		if o.gcState == gcNotRunning {
-			return false, fmt.Errorf("encountered GCEnd when GC was not in progress")
-		}
-		if o.gcState == gcUndetermined {
-			return false, fmt.Errorf("encountered GCEnd when GC was in an undetermined state")
-		}
+		o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+		return curCtx, true, nil
+	}
+	if seq != o.gcSeq+1 {
+		// This is not the right GC cycle.
+		return curCtx, false, nil
+	}
+	if o.gcState != gcRunning {
+		return curCtx, false, fmt.Errorf("encountered GCActive while GC was not in progress")
+	}
+	o.gcSeq = seq
+	if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
+		return curCtx, false, err
+	}
+	o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+	return curCtx, true, nil
+}
+
+func (o *ordering) advanceGCBegin(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+	seq := ev.args[0]
+	if o.gcState == gcUndetermined {
 		o.gcSeq = seq
-		o.gcState = gcNotRunning
-		if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
-			return false, err
-		}
-		o.queue.push(currentEvent())
+		o.gcState = gcRunning
+		o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+		return curCtx, true, nil
+	}
+	if seq != o.gcSeq+1 {
+		// This is not the right GC cycle.
+		return curCtx, false, nil
+	}
+	if o.gcState == gcRunning {
+		return curCtx, false, fmt.Errorf("encountered GCBegin while GC was already in progress")
+	}
+	o.gcSeq = seq
+	o.gcState = gcRunning
+	if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
+		return curCtx, false, err
+	}
+	o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+	return curCtx, true, nil
+}
 
+func (o *ordering) advanceGCEnd(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+	seq := ev.args[0]
+	if seq != o.gcSeq+1 {
+		// This is not the right GC cycle.
+		return curCtx, false, nil
+	}
+	if o.gcState == gcNotRunning {
+		return curCtx, false, fmt.Errorf("encountered GCEnd when GC was not in progress")
+	}
+	if o.gcState == gcUndetermined {
+		return curCtx, false, fmt.Errorf("encountered GCEnd when GC was in an undetermined state")
+	}
+	o.gcSeq = seq
+	o.gcState = gcNotRunning
+	if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
+		return curCtx, false, err
+	}
+	o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+	return curCtx, true, nil
+}
+
+func (o *ordering) advanceAnnotation(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
 	// Handle simple instantaneous events that require a G.
-	case go122.EvGoLabel, go122.EvProcsChange, go122.EvUserLog:
-		if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
-			return false, err
-		}
-		o.queue.push(currentEvent())
+	if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
+		return curCtx, false, err
+	}
+	o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+	return curCtx, true, nil
+}
 
-	// Handle allocation states, which don't require a G.
-	case go122.EvHeapAlloc, go122.EvHeapGoal:
-		if err := validateCtx(curCtx, event.SchedReqs{Thread: event.MustHave, Proc: event.MustHave, Goroutine: event.MayHave}); err != nil {
-			return false, err
-		}
-		o.queue.push(currentEvent())
+func (o *ordering) advanceHeapMetric(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+	// Handle allocation metrics, which don't require a G.
+	if err := validateCtx(curCtx, event.SchedReqs{Thread: event.MustHave, Proc: event.MustHave, Goroutine: event.MayHave}); err != nil {
+		return curCtx, false, err
+	}
+	o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+	return curCtx, true, nil
+}
 
+func (o *ordering) advanceGCSweepBegin(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
 	// Handle sweep, which is bound to a P and doesn't require a G.
-	case go122.EvGCSweepBegin:
-		if err := validateCtx(curCtx, event.SchedReqs{Thread: event.MustHave, Proc: event.MustHave, Goroutine: event.MayHave}); err != nil {
-			return false, err
-		}
-		if err := o.pStates[curCtx.P].beginRange(makeRangeType(typ, 0)); err != nil {
-			return false, err
-		}
-		o.queue.push(currentEvent())
-	case go122.EvGCSweepActive:
-		pid := ProcID(ev.args[0])
-		// N.B. In practice Ps can't block while they're sweeping, so this can only
-		// ever reference curCtx.P. However, be lenient about this like we are with
-		// GCMarkAssistActive; there's no reason the runtime couldn't change to block
-		// in the middle of a sweep.
-		pState, ok := o.pStates[pid]
-		if !ok {
-			return false, fmt.Errorf("encountered GCSweepActive for unknown proc %d", pid)
-		}
-		if err := pState.activeRange(makeRangeType(typ, 0), gen == o.initialGen); err != nil {
-			return false, err
-		}
-		o.queue.push(currentEvent())
-	case go122.EvGCSweepEnd:
-		if err := validateCtx(curCtx, event.SchedReqs{Thread: event.MustHave, Proc: event.MustHave, Goroutine: event.MayHave}); err != nil {
-			return false, err
-		}
-		_, err := o.pStates[curCtx.P].endRange(typ)
-		if err != nil {
-			return false, err
-		}
-		o.queue.push(currentEvent())
+	if err := validateCtx(curCtx, event.SchedReqs{Thread: event.MustHave, Proc: event.MustHave, Goroutine: event.MayHave}); err != nil {
+		return curCtx, false, err
+	}
+	if err := o.pStates[curCtx.P].beginRange(makeRangeType(ev.typ, 0)); err != nil {
+		return curCtx, false, err
+	}
+	o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+	return curCtx, true, nil
+}
 
+func (o *ordering) advanceGCSweepActive(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+	pid := ProcID(ev.args[0])
+	// N.B. In practice Ps can't block while they're sweeping, so this can only
+	// ever reference curCtx.P. However, be lenient about this like we are with
+	// GCMarkAssistActive; there's no reason the runtime couldn't change to block
+	// in the middle of a sweep.
+	pState, ok := o.pStates[pid]
+	if !ok {
+		return curCtx, false, fmt.Errorf("encountered GCSweepActive for unknown proc %d", pid)
+	}
+	if err := pState.activeRange(makeRangeType(ev.typ, 0), gen == o.initialGen); err != nil {
+		return curCtx, false, err
+	}
+	o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+	return curCtx, true, nil
+}
+
+func (o *ordering) advanceGCSweepEnd(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+	if err := validateCtx(curCtx, event.SchedReqs{Thread: event.MustHave, Proc: event.MustHave, Goroutine: event.MayHave}); err != nil {
+		return curCtx, false, err
+	}
+	_, err := o.pStates[curCtx.P].endRange(ev.typ)
+	if err != nil {
+		return curCtx, false, err
+	}
+	o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+	return curCtx, true, nil
+}
+
+func (o *ordering) advanceGoRangeBegin(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
 	// Handle special goroutine-bound event ranges.
-	case go122.EvSTWBegin, go122.EvGCMarkAssistBegin:
-		if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
-			return false, err
-		}
-		desc := stringID(0)
-		if typ == go122.EvSTWBegin {
-			desc = stringID(ev.args[0])
-		}
-		gState, ok := o.gStates[curCtx.G]
-		if !ok {
-			return false, fmt.Errorf("encountered event of type %d without known state for current goroutine %d", typ, curCtx.G)
-		}
-		if err := gState.beginRange(makeRangeType(typ, desc)); err != nil {
-			return false, err
-		}
-		o.queue.push(currentEvent())
-	case go122.EvGCMarkAssistActive:
-		gid := GoID(ev.args[0])
-		// N.B. Like GoStatus, this can happen at any time, because it can
-		// reference a non-running goroutine. Don't check anything about the
-		// current scheduler context.
-		gState, ok := o.gStates[gid]
-		if !ok {
-			return false, fmt.Errorf("uninitialized goroutine %d found during %s", gid, go122.EventString(typ))
-		}
-		if err := gState.activeRange(makeRangeType(typ, 0), gen == o.initialGen); err != nil {
-			return false, err
-		}
-		o.queue.push(currentEvent())
-	case go122.EvSTWEnd, go122.EvGCMarkAssistEnd:
-		if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
-			return false, err
-		}
-		gState, ok := o.gStates[curCtx.G]
-		if !ok {
-			return false, fmt.Errorf("encountered event of type %d without known state for current goroutine %d", typ, curCtx.G)
-		}
-		desc, err := gState.endRange(typ)
-		if err != nil {
-			return false, err
-		}
-		if typ == go122.EvSTWEnd {
-			// Smuggle the kind into the event.
-			// Don't use ev.extra here so we have symmetry with STWBegin.
-			ev.args[0] = uint64(desc)
-		}
-		o.queue.push(currentEvent())
-	default:
-		return false, fmt.Errorf("bad event type found while ordering: %v", ev.typ)
+	if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
+		return curCtx, false, err
 	}
-	if ms != nil {
-		// Update the mState for this event.
-		ms.p = newCtx.P
-		ms.g = newCtx.G
+	desc := stringID(0)
+	if ev.typ == go122.EvSTWBegin {
+		desc = stringID(ev.args[0])
 	}
-	return true, nil
+	gState, ok := o.gStates[curCtx.G]
+	if !ok {
+		return curCtx, false, fmt.Errorf("encountered event of type %d without known state for current goroutine %d", ev.typ, curCtx.G)
+	}
+	if err := gState.beginRange(makeRangeType(ev.typ, desc)); err != nil {
+		return curCtx, false, err
+	}
+	o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+	return curCtx, true, nil
+}
+
+func (o *ordering) advanceGoRangeActive(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+	gid := GoID(ev.args[0])
+	// N.B. Like GoStatus, this can happen at any time, because it can
+	// reference a non-running goroutine. Don't check anything about the
+	// current scheduler context.
+	gState, ok := o.gStates[gid]
+	if !ok {
+		return curCtx, false, fmt.Errorf("uninitialized goroutine %d found during %s", gid, go122.EventString(ev.typ))
+	}
+	if err := gState.activeRange(makeRangeType(ev.typ, 0), gen == o.initialGen); err != nil {
+		return curCtx, false, err
+	}
+	o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+	return curCtx, true, nil
+}
+
+func (o *ordering) advanceGoRangeEnd(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+	if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
+		return curCtx, false, err
+	}
+	gState, ok := o.gStates[curCtx.G]
+	if !ok {
+		return curCtx, false, fmt.Errorf("encountered event of type %d without known state for current goroutine %d", ev.typ, curCtx.G)
+	}
+	desc, err := gState.endRange(ev.typ)
+	if err != nil {
+		return curCtx, false, err
+	}
+	if ev.typ == go122.EvSTWEnd {
+		// Smuggle the kind into the event.
+		// Don't use ev.extra here so we have symmetry with STWBegin.
+		ev.args[0] = uint64(desc)
+	}
+	o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+	return curCtx, true, nil
 }
 
 // Next returns the next event in the ordering.
@@ -1159,3 +1354,21 @@
 	q.start++
 	return value, true
 }
+
+// makeEvent creates an Event from the provided information.
+//
+// It's just a convenience function; it's always OK to construct
+// an Event manually if this isn't quite the right way to express
+// the contents of the event.
+func makeEvent(table *evTable, ctx schedCtx, typ event.Type, time Time, args ...uint64) Event {
+	ev := Event{
+		table: table,
+		ctx:   ctx,
+		base: baseEvent{
+			typ:  typ,
+			time: time,
+		},
+	}
+	copy(ev.base.args[:], args)
+	return ev
+}
diff --git a/trace/reader.go b/trace/reader.go
index 088565e..315c5b2 100644
--- a/trace/reader.go
+++ b/trace/reader.go
@@ -50,7 +50,7 @@
 		return &Reader{
 			go121Events: convertOldFormat(tr),
 		}, nil
-	case version.Go122:
+	case version.Go122, version.Go123:
 		return &Reader{
 			r: br,
 			order: ordering{