// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Runtime -> tracer API.

package runtime

import (
	
	_  // for go:linkname
)

// gTraceState is per-G state for the tracer.
type gTraceState struct {
	traceSchedResourceState
}

// reset resets the gTraceState for a new goroutine.
func ( *gTraceState) () {
	.seq = [2]uint64{}
	// N.B. s.statusTraced is managed and cleared separately.
}

// mTraceState is per-M state for the tracer.
type mTraceState struct {
	seqlock atomic.Uintptr // seqlock indicating that this M is writing to a trace buffer.
	buf     [2]*traceBuf   // Per-M traceBuf for writing. Indexed by trace.gen%2.
	link    *m             // Snapshot of alllink or freelink.
}

// pTraceState is per-P state for the tracer.
type pTraceState struct {
	traceSchedResourceState

	// mSyscallID is the ID of the M this was bound to before entering a syscall.
	mSyscallID int64

	// maySweep indicates the sweep events should be traced.
	// This is used to defer the sweep start event until a span
	// has actually been swept.
	maySweep bool

	// inSweep indicates that at least one sweep event has been traced.
	inSweep bool

	// swept and reclaimed track the number of bytes swept and reclaimed
	// by sweeping in the current sweep loop (while maySweep was true).
	swept, reclaimed uintptr
}

// traceLockInit initializes global trace locks.
func traceLockInit() {
	// Sharing a lock rank here is fine because they should never be accessed
	// together. If they are, we want to find out immediately.
	lockInit(&trace.stringTab[0].lock, lockRankTraceStrings)
	lockInit(&trace.stringTab[0].tab.mem.lock, lockRankTraceStrings)
	lockInit(&trace.stringTab[1].lock, lockRankTraceStrings)
	lockInit(&trace.stringTab[1].tab.mem.lock, lockRankTraceStrings)
	lockInit(&trace.stackTab[0].tab.mem.lock, lockRankTraceStackTab)
	lockInit(&trace.stackTab[1].tab.mem.lock, lockRankTraceStackTab)
	lockInit(&trace.typeTab[0].tab.mem.lock, lockRankTraceTypeTab)
	lockInit(&trace.typeTab[1].tab.mem.lock, lockRankTraceTypeTab)
	lockInit(&trace.lock, lockRankTrace)
}

// lockRankMayTraceFlush records the lock ranking effects of a
// potential call to traceFlush.
//
// nosplit because traceAcquire is nosplit.
//
//go:nosplit
func lockRankMayTraceFlush() {
	lockWithRankMayAcquire(&trace.lock, getLockRank(&trace.lock))
}

// traceBlockReason is an enumeration of reasons a goroutine might block.
// This is the interface the rest of the runtime uses to tell the
// tracer why a goroutine blocked. The tracer then propagates this information
// into the trace however it sees fit.
//
// Note that traceBlockReasons should not be compared, since reasons that are
// distinct by name may *not* be distinct by value.
type traceBlockReason uint8

const (
	traceBlockGeneric traceBlockReason = iota
	traceBlockForever
	traceBlockNet
	traceBlockSelect
	traceBlockCondWait
	traceBlockSync
	traceBlockChanSend
	traceBlockChanRecv
	traceBlockGCMarkAssist
	traceBlockGCSweep
	traceBlockSystemGoroutine
	traceBlockPreempted
	traceBlockDebugCall
	traceBlockUntilGCEnds
	traceBlockSleep
)

var traceBlockReasonStrings = [...]string{
	traceBlockGeneric:         "unspecified",
	traceBlockForever:         "forever",
	traceBlockNet:             "network",
	traceBlockSelect:          "select",
	traceBlockCondWait:        "sync.(*Cond).Wait",
	traceBlockSync:            "sync",
	traceBlockChanSend:        "chan send",
	traceBlockChanRecv:        "chan receive",
	traceBlockGCMarkAssist:    "GC mark assist wait for work",
	traceBlockGCSweep:         "GC background sweeper wait",
	traceBlockSystemGoroutine: "system goroutine wait",
	traceBlockPreempted:       "preempted",
	traceBlockDebugCall:       "wait for debug call",
	traceBlockUntilGCEnds:     "wait until GC ends",
	traceBlockSleep:           "sleep",
}

// traceGoStopReason is an enumeration of reasons a goroutine might yield.
//
// Note that traceGoStopReasons should not be compared, since reasons that are
// distinct by name may *not* be distinct by value.
type traceGoStopReason uint8

const (
	traceGoStopGeneric traceGoStopReason = iota
	traceGoStopGoSched
	traceGoStopPreempted
)

var traceGoStopReasonStrings = [...]string{
	traceGoStopGeneric:   "unspecified",
	traceGoStopGoSched:   "runtime.Gosched",
	traceGoStopPreempted: "preempted",
}

// traceEnabled returns true if the trace is currently enabled.
//
//go:nosplit
func traceEnabled() bool {
	return trace.enabled
}

// traceAllocFreeEnabled returns true if the trace is currently enabled
// and alloc/free events are also enabled.
//
//go:nosplit
func traceAllocFreeEnabled() bool {
	return trace.enabledWithAllocFree
}

// traceShuttingDown returns true if the trace is currently shutting down.
func traceShuttingDown() bool {
	return trace.shutdown.Load()
}

// traceLocker represents an M writing trace events. While a traceLocker value
// is valid, the tracer observes all operations on the G/M/P or trace events being
// written as happening atomically.
type traceLocker struct {
	mp  *m
	gen uintptr
}

// debugTraceReentrancy checks if the trace is reentrant.
//
// This is optional because throwing in a function makes it instantly
// not inlineable, and we want traceAcquire to be inlineable for
// low overhead when the trace is disabled.
const debugTraceReentrancy = false

// traceAcquire prepares this M for writing one or more trace events.
//
// nosplit because it's called on the syscall path when stack movement is forbidden.
//
//go:nosplit
func traceAcquire() traceLocker {
	if !traceEnabled() {
		return traceLocker{}
	}
	return traceAcquireEnabled()
}

// traceTryAcquire is like traceAcquire, but may return an invalid traceLocker even
// if tracing is enabled. For example, it will return !ok if traceAcquire is being
// called with an active traceAcquire on the M (reentrant locking). This exists for
// optimistically emitting events in the few contexts where tracing is now allowed.
//
// nosplit for alignment with traceTryAcquire, so it can be used in the
// same contexts.
//
//go:nosplit
func traceTryAcquire() traceLocker {
	if !traceEnabled() {
		return traceLocker{}
	}
	return traceTryAcquireEnabled()
}

// traceAcquireEnabled is the traceEnabled path for traceAcquire. It's explicitly
// broken out to make traceAcquire inlineable to keep the overhead of the tracer
// when it's disabled low.
//
// nosplit because it's called by traceAcquire, which is nosplit.
//
//go:nosplit
func traceAcquireEnabled() traceLocker {
	// Any time we acquire a traceLocker, we may flush a trace buffer. But
	// buffer flushes are rare. Record the lock edge even if it doesn't happen
	// this time.
	lockRankMayTraceFlush()

	// Prevent preemption.
	 := acquirem()

	// Acquire the trace seqlock. This prevents traceAdvance from moving forward
	// until all Ms are observed to be outside of their seqlock critical section.
	//
	// Note: The seqlock is mutated here and also in traceCPUSample. If you update
	// usage of the seqlock here, make sure to also look at what traceCPUSample is
	// doing.
	 := .trace.seqlock.Add(1)
	if debugTraceReentrancy && %2 != 1 {
		throw("bad use of trace.seqlock or tracer is reentrant")
	}

	// N.B. This load of gen appears redundant with the one in traceEnabled.
	// However, it's very important that the gen we use for writing to the trace
	// is acquired under a traceLocker so traceAdvance can make sure no stale
	// gen values are being used.
	//
	// Because we're doing this load again, it also means that the trace
	// might end up being disabled when we load it. In that case we need to undo
	// what we did and bail.
	 := trace.gen.Load()
	if  == 0 {
		.trace.seqlock.Add(1)
		releasem()
		return traceLocker{}
	}
	return traceLocker{, }
}

// traceTryAcquireEnabled is like traceAcquireEnabled but may return an invalid
// traceLocker under some conditions. See traceTryAcquire for more details.
//
// nosplit for alignment with traceAcquireEnabled, so it can be used in the
// same contexts.
//
//go:nosplit
func traceTryAcquireEnabled() traceLocker {
	// Any time we acquire a traceLocker, we may flush a trace buffer. But
	// buffer flushes are rare. Record the lock edge even if it doesn't happen
	// this time.
	lockRankMayTraceFlush()

	// Check if we're already locked. If so, return an invalid traceLocker.
	if getg().m.trace.seqlock.Load()%2 == 1 {
		return traceLocker{}
	}
	return traceAcquireEnabled()
}

// ok returns true if the traceLocker is valid (i.e. tracing is enabled).
//
// nosplit because it's called on the syscall path when stack movement is forbidden.
//
//go:nosplit
func ( traceLocker) () bool {
	return .gen != 0
}

// traceRelease indicates that this M is done writing trace events.
//
// nosplit because it's called on the syscall path when stack movement is forbidden.
//
//go:nosplit
func traceRelease( traceLocker) {
	 := .mp.trace.seqlock.Add(1)
	if debugTraceReentrancy && %2 != 0 {
		print("runtime: seq=", , "\n")
		throw("bad use of trace.seqlock")
	}
	releasem(.mp)
}

// traceExitingSyscall marks a goroutine as exiting the syscall slow path.
//
// Must be paired with a traceExitedSyscall call.
func traceExitingSyscall() {
	trace.exitingSyscall.Add(1)
}

// traceExitedSyscall marks a goroutine as having exited the syscall slow path.
func traceExitedSyscall() {
	trace.exitingSyscall.Add(-1)
}

// Gomaxprocs emits a ProcsChange event.
func ( traceLocker) ( int32) {
	.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvProcsChange, traceArg(), .stack(1))
}

// ProcStart traces a ProcStart event.
//
// Must be called with a valid P.
func ( traceLocker) () {
	 := .mp.p.ptr()
	// Procs are typically started within the scheduler when there is no user goroutine. If there is a user goroutine,
	// it must be in _Gsyscall because the only time a goroutine is allowed to have its Proc moved around from under it
	// is during a syscall.
	.eventWriter(traceGoSyscall, traceProcIdle).commit(traceEvProcStart, traceArg(.id), .trace.nextSeq(.gen))
}

// ProcStop traces a ProcStop event.
func ( traceLocker) ( *p) {
	// The only time a goroutine is allowed to have its Proc moved around
	// from under it is during a syscall.
	.eventWriter(traceGoSyscall, traceProcRunning).commit(traceEvProcStop)
}

// GCActive traces a GCActive event.
//
// Must be emitted by an actively running goroutine on an active P. This restriction can be changed
// easily and only depends on where it's currently called.
func ( traceLocker) () {
	.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvGCActive, traceArg(trace.seqGC))
	// N.B. Only one GC can be running at a time, so this is naturally
	// serialized by the caller.
	trace.seqGC++
}

// GCStart traces a GCBegin event.
//
// Must be emitted by an actively running goroutine on an active P. This restriction can be changed
// easily and only depends on where it's currently called.
func ( traceLocker) () {
	.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvGCBegin, traceArg(trace.seqGC), .stack(3))
	// N.B. Only one GC can be running at a time, so this is naturally
	// serialized by the caller.
	trace.seqGC++
}

// GCDone traces a GCEnd event.
//
// Must be emitted by an actively running goroutine on an active P. This restriction can be changed
// easily and only depends on where it's currently called.
func ( traceLocker) () {
	.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvGCEnd, traceArg(trace.seqGC))
	// N.B. Only one GC can be running at a time, so this is naturally
	// serialized by the caller.
	trace.seqGC++
}

// STWStart traces a STWBegin event.
func ( traceLocker) ( stwReason) {
	// Although the current P may be in _Pgcstop here, we model the P as running during the STW. This deviates from the
	// runtime's state tracking, but it's more accurate and doesn't result in any loss of information.
	.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvSTWBegin, .string(.String()), .stack(2))
}

// STWDone traces a STWEnd event.
func ( traceLocker) () {
	// Although the current P may be in _Pgcstop here, we model the P as running during the STW. This deviates from the
	// runtime's state tracking, but it's more accurate and doesn't result in any loss of information.
	.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvSTWEnd)
}

// GCSweepStart prepares to trace a sweep loop. This does not
// emit any events until traceGCSweepSpan is called.
//
// GCSweepStart must be paired with traceGCSweepDone and there
// must be no preemption points between these two calls.
//
// Must be called with a valid P.
func ( traceLocker) () {
	// Delay the actual GCSweepBegin event until the first span
	// sweep. If we don't sweep anything, don't emit any events.
	 := .mp.p.ptr()
	if .trace.maySweep {
		throw("double traceGCSweepStart")
	}
	.trace.maySweep, .trace.swept, .trace.reclaimed = true, 0, 0
}

// GCSweepSpan traces the sweep of a single span. If this is
// the first span swept since traceGCSweepStart was called, this
// will emit a GCSweepBegin event.
//
// This may be called outside a traceGCSweepStart/traceGCSweepDone
// pair; however, it will not emit any trace events in this case.
//
// Must be called with a valid P.
func ( traceLocker) ( uintptr) {
	 := .mp.p.ptr()
	if .trace.maySweep {
		if .trace.swept == 0 {
			.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvGCSweepBegin, .stack(1))
			.trace.inSweep = true
		}
		.trace.swept += 
	}
}

// GCSweepDone finishes tracing a sweep loop. If any memory was
// swept (i.e. traceGCSweepSpan emitted an event) then this will emit
// a GCSweepEnd event.
//
// Must be called with a valid P.
func ( traceLocker) () {
	 := .mp.p.ptr()
	if !.trace.maySweep {
		throw("missing traceGCSweepStart")
	}
	if .trace.inSweep {
		.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvGCSweepEnd, traceArg(.trace.swept), traceArg(.trace.reclaimed))
		.trace.inSweep = false
	}
	.trace.maySweep = false
}

// GCMarkAssistStart emits a MarkAssistBegin event.
func ( traceLocker) () {
	.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvGCMarkAssistBegin, .stack(1))
}

// GCMarkAssistDone emits a MarkAssistEnd event.
func ( traceLocker) () {
	.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvGCMarkAssistEnd)
}

// GoCreate emits a GoCreate event.
func ( traceLocker) ( *g,  uintptr,  bool) {
	.trace.setStatusTraced(.gen)
	 := traceEvGoCreate
	if  {
		 = traceEvGoCreateBlocked
	}
	.eventWriter(traceGoRunning, traceProcRunning).commit(, traceArg(.goid), .startPC(), .stack(2))
}

// GoStart emits a GoStart event.
//
// Must be called with a valid P.
func ( traceLocker) () {
	 := getg().m.curg
	 := .m.p
	 := .eventWriter(traceGoRunnable, traceProcRunning)
	 = .write(traceEvGoStart, traceArg(.goid), .trace.nextSeq(.gen))
	if .ptr().gcMarkWorkerMode != gcMarkWorkerNotWorker {
		 = .write(traceEvGoLabel, trace.markWorkerLabels[.gen%2][.ptr().gcMarkWorkerMode])
	}
	.end()
}

// GoEnd emits a GoDestroy event.
//
// TODO(mknyszek): Rename this to GoDestroy.
func ( traceLocker) () {
	.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvGoDestroy)
}

// GoSched emits a GoStop event with a GoSched reason.
func ( traceLocker) () {
	.GoStop(traceGoStopGoSched)
}

// GoPreempt emits a GoStop event with a GoPreempted reason.
func ( traceLocker) () {
	.GoStop(traceGoStopPreempted)
}

// GoStop emits a GoStop event with the provided reason.
func ( traceLocker) ( traceGoStopReason) {
	.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvGoStop, traceArg(trace.goStopReasons[.gen%2][]), .stack(1))
}

// GoPark emits a GoBlock event with the provided reason.
//
// TODO(mknyszek): Replace traceBlockReason with waitReason. It's silly
// that we have both, and waitReason is way more descriptive.
func ( traceLocker) ( traceBlockReason,  int) {
	.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvGoBlock, traceArg(trace.goBlockReasons[.gen%2][]), .stack())
}

// GoUnpark emits a GoUnblock event.
func ( traceLocker) ( *g,  int) {
	// Emit a GoWaiting status if necessary for the unblocked goroutine.
	 := .eventWriter(traceGoRunning, traceProcRunning)
	// Careful: don't use the event writer. We never want status or in-progress events
	// to trigger more in-progress events.
	.w = emitUnblockStatus(.w, , .gen)
	.commit(traceEvGoUnblock, traceArg(.goid), .trace.nextSeq(.gen), .stack())
}

// GoCoroswitch emits a GoSwitch event. If destroy is true, the calling goroutine
// is simultaneously being destroyed.
func ( traceLocker) ( *g,  bool) {
	// Emit a GoWaiting status if necessary for the unblocked goroutine.
	 := .eventWriter(traceGoRunning, traceProcRunning)
	// Careful: don't use the event writer. We never want status or in-progress events
	// to trigger more in-progress events.
	.w = emitUnblockStatus(.w, , .gen)
	 := traceEvGoSwitch
	if  {
		 = traceEvGoSwitchDestroy
	}
	.commit(, traceArg(.goid), .trace.nextSeq(.gen))
}

// emitUnblockStatus emits a GoStatus GoWaiting event for a goroutine about to be
// unblocked to the trace writer.
func emitUnblockStatus( traceWriter,  *g,  uintptr) traceWriter {
	if !.trace.statusWasTraced() && .trace.acquireStatus() {
		// TODO(go.dev/issue/65634): Although it would be nice to add a stack trace here of gp,
		// we cannot safely do so. gp is in _Gwaiting and so we don't have ownership of its stack.
		// We can fix this by acquiring the goroutine's scan bit.
		 = .writeGoStatus(.goid, -1, traceGoWaiting, .inMarkAssist, 0)
	}
	return 
}

// GoSysCall emits a GoSyscallBegin event.
//
// Must be called with a valid P.
func ( traceLocker) () {
	// Scribble down the M that the P is currently attached to.
	 := .mp.p.ptr()
	.trace.mSyscallID = int64(.mp.procid)
	.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvGoSyscallBegin, .trace.nextSeq(.gen), .stack(1))
}

// GoSysExit emits a GoSyscallEnd event, possibly along with a GoSyscallBlocked event
// if lostP is true.
//
// lostP must be true in all cases that a goroutine loses its P during a syscall.
// This means it's not sufficient to check if it has no P. In particular, it needs to be
// true in the following cases:
// - The goroutine lost its P, it ran some other code, and then got it back. It's now running with that P.
// - The goroutine lost its P and was unable to reacquire it, and is now running without a P.
// - The goroutine lost its P and acquired a different one, and is now running with that P.
func ( traceLocker) ( bool) {
	 := traceEvGoSyscallEnd
	 := traceProcSyscall // Procs implicitly enter traceProcSyscall on GoSyscallBegin.
	if  {
		 = traceEvGoSyscallEndBlocked
		 = traceProcRunning // If a G has a P when emitting this event, it reacquired a P and is indeed running.
	} else {
		.mp.p.ptr().trace.mSyscallID = -1
	}
	.eventWriter(traceGoSyscall, ).commit()
}

// ProcSteal indicates that our current M stole a P from another M.
//
// inSyscall indicates that we're stealing the P from a syscall context.
//
// The caller must have ownership of pp.
func ( traceLocker) ( *p,  bool) {
	// Grab the M ID we stole from.
	 := .trace.mSyscallID
	.trace.mSyscallID = -1

	// The status of the proc and goroutine, if we need to emit one here, is not evident from the
	// context of just emitting this event alone. There are two cases. Either we're trying to steal
	// the P just to get its attention (e.g. STW or sysmon retake) or we're trying to steal a P for
	// ourselves specifically to keep running. The two contexts look different, but can be summarized
	// fairly succinctly. In the former, we're a regular running goroutine and proc, if we have either.
	// In the latter, we're a goroutine in a syscall.
	 := traceGoRunning
	 := traceProcRunning
	if  {
		 = traceGoSyscall
		 = traceProcSyscallAbandoned
	}
	 := .eventWriter(, )

	// Emit the status of the P we're stealing. We may have *just* done this when creating the event
	// writer but it's not guaranteed, even if inSyscall is true. Although it might seem like from a
	// syscall context we're always stealing a P for ourselves, we may have not wired it up yet (so
	// it wouldn't be visible to eventWriter) or we may not even intend to wire it up to ourselves
	// at all (e.g. entersyscall_gcwait).
	if !.trace.statusWasTraced(.gen) && .trace.acquireStatus(.gen) {
		// Careful: don't use the event writer. We never want status or in-progress events
		// to trigger more in-progress events.
		.w = .w.writeProcStatus(uint64(.id), traceProcSyscallAbandoned, .trace.inSweep)
	}
	.commit(traceEvProcSteal, traceArg(.id), .trace.nextSeq(.gen), traceArg())
}

// HeapAlloc emits a HeapAlloc event.
func ( traceLocker) ( uint64) {
	.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvHeapAlloc, traceArg())
}

// HeapGoal reads the current heap goal and emits a HeapGoal event.
func ( traceLocker) () {
	 := gcController.heapGoal()
	if  == ^uint64(0) {
		// Heap-based triggering is disabled.
		 = 0
	}
	.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvHeapGoal, traceArg())
}

// GoCreateSyscall indicates that a goroutine has transitioned from dead to GoSyscall.
//
// Unlike GoCreate, the caller must be running on gp.
//
// This occurs when C code calls into Go. On pthread platforms it occurs only when
// a C thread calls into Go code for the first time.
func ( traceLocker) ( *g) {
	// N.B. We should never trace a status for this goroutine (which we're currently running on),
	// since we want this to appear like goroutine creation.
	.trace.setStatusTraced(.gen)
	.eventWriter(traceGoBad, traceProcBad).commit(traceEvGoCreateSyscall, traceArg(.goid))
}

// GoDestroySyscall indicates that a goroutine has transitioned from GoSyscall to dead.
//
// Must not have a P.
//
// This occurs when Go code returns back to C. On pthread platforms it occurs only when
// the C thread is destroyed.
func ( traceLocker) () {
	// N.B. If we trace a status here, we must never have a P, and we must be on a goroutine
	// that is in the syscall state.
	.eventWriter(traceGoSyscall, traceProcBad).commit(traceEvGoDestroySyscall)
}

// To access runtime functions from runtime/trace.
// See runtime/trace/annotation.go

// trace_userTaskCreate emits a UserTaskCreate event.
//
//go:linkname trace_userTaskCreate runtime/trace.userTaskCreate
func trace_userTaskCreate(,  uint64,  string) {
	 := traceAcquire()
	if !.ok() {
		// Need to do this check because the caller won't have it.
		return
	}
	.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvUserTaskBegin, traceArg(), traceArg(), .string(), .stack(3))
	traceRelease()
}

// trace_userTaskEnd emits a UserTaskEnd event.
//
//go:linkname trace_userTaskEnd runtime/trace.userTaskEnd
func trace_userTaskEnd( uint64) {
	 := traceAcquire()
	if !.ok() {
		// Need to do this check because the caller won't have it.
		return
	}
	.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvUserTaskEnd, traceArg(), .stack(2))
	traceRelease()
}

// trace_userTaskEnd emits a UserRegionBegin or UserRegionEnd event,
// depending on mode (0 == Begin, 1 == End).
//
// TODO(mknyszek): Just make this two functions.
//
//go:linkname trace_userRegion runtime/trace.userRegion
func trace_userRegion(,  uint64,  string) {
	 := traceAcquire()
	if !.ok() {
		// Need to do this check because the caller won't have it.
		return
	}
	var  traceEv
	switch  {
	case 0:
		 = traceEvUserRegionBegin
	case 1:
		 = traceEvUserRegionEnd
	default:
		return
	}
	.eventWriter(traceGoRunning, traceProcRunning).commit(, traceArg(), .string(), .stack(3))
	traceRelease()
}

// trace_userTaskEnd emits a UserRegionBegin or UserRegionEnd event.
//
//go:linkname trace_userLog runtime/trace.userLog
func trace_userLog( uint64, ,  string) {
	 := traceAcquire()
	if !.ok() {
		// Need to do this check because the caller won't have it.
		return
	}
	.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvUserLog, traceArg(), .string(), .uniqueString(), .stack(3))
	traceRelease()
}

// traceThreadDestroy is called when a thread is removed from
// sched.freem.
//
// mp must not be able to emit trace events anymore.
//
// sched.lock must be held to synchronize with traceAdvance.
func traceThreadDestroy( *m) {
	assertLockHeld(&sched.lock)

	// Flush all outstanding buffers to maintain the invariant
	// that an M only has active buffers while on sched.freem
	// or allm.
	//
	// Perform a traceAcquire/traceRelease on behalf of mp to
	// synchronize with the tracer trying to flush our buffer
	// as well.
	 := .trace.seqlock.Add(1)
	if debugTraceReentrancy && %2 != 1 {
		throw("bad use of trace.seqlock or tracer is reentrant")
	}
	systemstack(func() {
		lock(&trace.lock)
		for  := range .trace.buf {
			if .trace.buf[] != nil {
				// N.B. traceBufFlush accepts a generation, but it
				// really just cares about gen%2.
				traceBufFlush(.trace.buf[], uintptr())
				.trace.buf[] = nil
			}
		}
		unlock(&trace.lock)
	})
	 := .trace.seqlock.Add(1)
	if  != +1 {
		print("runtime: seq1=", , "\n")
		throw("bad use of trace.seqlock")
	}
}