// 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.

// Trace string management.

package runtime

import "internal/trace/tracev2"

// Trace strings.

// 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
	buf  *traceBuf // string batches to write out to the trace.

	// tab is a mapping of string -> unique ID.
	tab traceMap
}

// put adds a string to the table, emits it, and returns a unique ID for it.
func (t *traceStringTable) put(gen uintptr, s string) uint64 {
	// Truncate the string now to avoid wasting space in the
	// traceMap and to stay within traceRegionAlloc's block size limit.
	if len(s) > tracev2.MaxEventTrailerDataSize {
		s = s[:tracev2.MaxEventTrailerDataSize]
	}
	// Put the string in the table.
	ss := stringStructOf(&s)
	id, added := t.tab.put(ss.str, uintptr(ss.len))
	if added {
		// Write the string to the buffer.
		systemstack(func() {
			t.writeString(gen, id, s)
		})
	}
	return id
}

// 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() {
		t.writeString(gen, id, s)
	})
	return id
}

// writeString writes the string to t.buf.
//
// Must run on the systemstack because it acquires t.lock.
//
//go:systemstack
func (t *traceStringTable) writeString(gen uintptr, id uint64, s string) {
	// Truncate the string if necessary.
	if len(s) > tracev2.MaxEventTrailerDataSize {
		s = s[:tracev2.MaxEventTrailerDataSize]
	}

	lock(&t.lock)
	w := unsafeTraceWriter(gen, t.buf)

	// Ensure we have a place to write to.
	var flushed bool
	w, flushed = w.ensure(2 + 2*traceBytesPerNumber + len(s) /* tracev2.EvStrings + tracev2.EvString + ID + len + string data */)
	if flushed {
		// Annotate the batch as containing strings.
		w.byte(byte(tracev2.EvStrings))
	}

	// Write out the string.
	w.byte(byte(tracev2.EvString))
	w.varint(id)
	w.varint(uint64(len(s)))
	w.stringData(s)

	// Store back buf in case it was updated during ensure.
	t.buf = w.traceBuf
	unlock(&t.lock)
}

// reset clears the string table and flushes any buffers it has.
//
// Must be called only once the caller is certain nothing else will be
// added to this table.
func (t *traceStringTable) reset(gen uintptr) {
	if t.buf != nil {
		systemstack(func() {
			lock(&trace.lock)
			traceBufFlush(t.buf, gen)
			unlock(&trace.lock)
		})
		t.buf = nil
	}

	// Reset the table.
	t.tab.reset()
}
