From a685a6edb6d88ab9765b12435bfe6b5b25063500 Mon Sep 17 00:00:00 2001 From: Michael Anthony Knyszek Date: Mon, 25 Mar 2024 14:57:13 +0000 Subject: [PATCH] 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 Reviewed-by: Michael Stapelberg Auto-Submit: Michael Knyszek --- trace/event.go | 16 +- trace/internal/event/go122/event.go | 21 + trace/internal/version/version.go | 7 +- trace/order.go | 1673 +++++++++++++++------------ trace/reader.go | 2 +- 5 files changed, 984 insertions(+), 735 deletions(-) diff --git a/trace/event.go b/trace/event.go index a73e624e..962a63c7 100644 --- a/trace/event.go +++ b/trace/event.go @@ -570,8 +570,12 @@ func (e Event) StateTransition() StateTransition { 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 @@ func (e Event) StateTransition() StateTransition { 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 @@ var go122Type2Kind = [...]EventKind{ 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 a77fb8bf..2baf9cea 100644 --- a/trace/internal/event/go122/event.go +++ b/trace/internal/event/go122/event.go @@ -71,6 +71,11 @@ const ( 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 @@ var specs = [...]event.Spec{ 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 047ca0c2..1dc0dcda 100644 --- a/trace/internal/version/version.go +++ b/trace/internal/version/version.go @@ -24,7 +24,8 @@ const ( 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 @@ var versions = map[Version][]event.Spec{ 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 0e2f600c..089d6238 100644 --- a/trace/order.go +++ b/trace/order.go @@ -78,524 +78,693 @@ func (o *ordering) Advance(ev *baseEvent, evt *evTable, m ThreadID, gen uint64) newCtx = curCtx } - // Generates an event from the current context. - currentEvent := func() Event { - return Event{table: evt, ctx: curCtx, base: *ev} - } - - 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. + 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 { - o.pStates[pid] = &pState{id: pid, status: status, seq: makeSeq(gen, 0)} - if gen == o.initialGen { - oldState = ProcUndetermined - } else { - oldState = ProcNotExist - } + oldState = ProcNotExist } - ev.extra(version.Go122)[0] = uint64(oldState) // Smuggle in the old state for StateTransition. + } + 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. - 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 false, fmt.Errorf("failed to find sched context for proc %d that's about to be stolen", pid) + // 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 } } - 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 + if !found { + return curCtx, false, fmt.Errorf("failed to find sched context for proc %d that's about to be stolen", pid) } - // 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. + } + 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. // - // 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 + // 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 + } + + // Validate that the M we're stealing from is what we expect. + mid := ThreadID(ev.args[2]) // The M we're stealing from. + + 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) } - 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) - - // 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(currentEvent()) - break - } + o.queue.push(Event{table: evt, ctx: curCtx, base: *ev}) + return newCtx, true, nil + } - // Validate that the M we're stealing from is what we expect. - mid := ThreadID(ev.args[2]) // The M we're stealing from. + // 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 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 - } + // 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) + } - // 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) - } + // 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 +} - // 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) - } +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]) - // 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) + 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. + + newCtx := curCtx + 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 curCtx, false, fmt.Errorf("found goroutine %d in syscall without a thread", gid) } - 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) + // 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) } - 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.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 + 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 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). + } + // 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 +} + +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 - 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 - } - 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) - } - // Goroutine entered a syscall. It's still running on this P and M. - state.status = go122.GoSyscall + 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 +} + +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 +} + +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 false, fmt.Errorf("uninitialized proc %d found during %s", curCtx.P, go122.EventString(typ)) + 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 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 pState.status == go122.ProcSyscall { + return curCtx, false, nil } - 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 + } + // 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 +} - // Transfer the P back to running from syscall. +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 false, fmt.Errorf("uninitialized proc %d found during %s", curCtx.P, go122.EventString(typ)) + return curCtx, false, fmt.Errorf("found invalid proc %d during %s", curCtx.P, go122.EventString(ev.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 - } + return curCtx, false, fmt.Errorf("proc %d in unexpected state %s during %s", curCtx.P, pState.status, go122.EventString(ev.typ)) } - // 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) - } - // This goroutine is exiting itself. - delete(o.gStates, curCtx.G) - newCtx.G = NoGoroutine + // See the go122-create-syscall-reuse-thread-id test case for more details. + pState.status = go122.ProcSyscallAbandoned + newCtx.P = NoProc - // 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 - - // 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) - } - o.queue.push(currentEvent()) + // 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 @@ func (o *ordering) Advance(ev *baseEvent, evt *evTable, m ThreadID, gen uint64) // 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]) + } + 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 } - return true, nil + 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 @@ func (q *queue[T]) pop() (T, bool) { 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 088565e9..315c5b27 100644 --- a/trace/reader.go +++ b/trace/reader.go @@ -50,7 +50,7 @@ func NewReader(r io.Reader) (*Reader, error) { return &Reader{ go121Events: convertOldFormat(tr), }, nil - case version.Go122: + case version.Go122, version.Go123: return &Reader{ r: br, order: ordering{