runtime: don't write unique string to trace if it's length zero

While we're here, document that ID 0 is implicitly assigned to an empty
set of data for both stacks and strings.

Change-Id: Ic52ff3a1132abc5a8f6f6c4e4357e31e6e7799fc
Reviewed-on: https://go-review.googlesource.com/c/go/+/723061
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
Auto-Submit: Michael Knyszek <mknyszek@google.com>
This commit is contained in:
Michael Anthony Knyszek 2025-11-19 03:51:20 +00:00 committed by Gopher Robot
parent d4f5650cc5
commit 0bc192368a
3 changed files with 18 additions and 3 deletions

View file

@ -23,6 +23,13 @@ import (
"unsafe"
)
// traceMap is a map of a variable-sized array of bytes to a unique ID.
//
// Because traceMap just operates on raw bytes, this type is used as the
// backing store for both the trace string table and trace stack table,
// the latter of which is just an array of PCs.
//
// ID 0 is reserved for arrays of bytes of size zero.
type traceMap struct {
root atomic.UnsafePointer // *traceMapNode (can't use generics because it's notinheap)
_ cpu.CacheLinePad

View file

@ -136,8 +136,9 @@ func traceStack(skip int, gp *g, tab *traceStackTable) uint64 {
return id
}
// traceStackTable maps stack traces (arrays of PC's) to unique uint32 ids.
// It is lock-free for reading.
// traceStackTable maps stack traces (arrays of PC's) to unique IDs.
//
// ID 0 is reserved for a zero-length stack.
type traceStackTable struct {
tab traceMap
}
@ -145,8 +146,10 @@ type traceStackTable struct {
// put returns a unique id for the stack trace pcs and caches it in the table,
// if it sees the trace for the first time.
func (t *traceStackTable) put(pcs []uintptr) uint64 {
// Even though put will handle this for us, taking the address of pcs forces a bounds check
// that will fail if len(pcs) == 0.
if len(pcs) == 0 {
return 0
return 0 // ID 0 is reserved for zero-length stacks.
}
id, _ := t.tab.put(noescape(unsafe.Pointer(&pcs[0])), uintptr(len(pcs))*unsafe.Sizeof(uintptr(0)))
return id

View file

@ -12,6 +12,8 @@ import "internal/trace/tracev2"
// traceStringTable is map of string -> unique ID that also manages
// writing strings out into the trace.
//
// ID 0 is reserved for the empty string.
type traceStringTable struct {
// lock protects buf.
lock mutex
@ -37,6 +39,9 @@ func (t *traceStringTable) put(gen uintptr, s string) uint64 {
// emit emits a string and creates an ID for it, but doesn't add it to the table. Returns the ID.
func (t *traceStringTable) emit(gen uintptr, s string) uint64 {
if len(s) == 0 {
return 0 // Empty strings are implicitly assigned ID 0 already.
}
// Grab an ID and write the string to the buffer.
id := t.tab.stealID()
systemstack(func() {