cmd/viewer,types/views,various: avoid allocations in pointer field getters whenever possible

In this PR, we add a generic views.ValuePointer type that can be used as a view for pointers
to basic types and struct types that do not require deep cloning and do not have corresponding
view types. Its Get/GetOk methods return stack-allocated shallow copies of the underlying value.

We then update the cmd/viewer codegen to produce getters that return either concrete views
when available or ValuePointer views when not, for pointer fields in generated view types.
This allows us to avoid unnecessary allocations compared to returning pointers to newly
allocated shallow copies.

Updates #14570

Signed-off-by: Nick Khyl <nickk@tailscale.com>
This commit is contained in:
Nick Khyl
2025-01-08 17:21:44 -06:00
committed by Nick Khyl
parent e4385f1c02
commit da9965d51c
15 changed files with 219 additions and 163 deletions

View File

@@ -162,15 +162,8 @@ func (v *TestBundleView) UnmarshalJSON(b []byte) error {
return nil
}
func (v TestBundleView) Name() string { return v.ж.Name }
func (v TestBundleView) Nested() *TestValueStruct {
if v.ж.Nested == nil {
return nil
}
x := *v.ж.Nested
return &x
}
func (v TestBundleView) Name() string { return v.ж.Name }
func (v TestBundleView) Nested() TestValueStructView { return v.ж.Nested.View() }
func (v TestBundleView) Equal(v2 TestBundleView) bool { return v.ж.Equal(v2.ж) }
// A compilation failure here means this code must be regenerated, with the command at the top of this file.

View File

@@ -16,6 +16,7 @@ import (
"slices"
"go4.org/mem"
"tailscale.com/types/ptr"
)
func unmarshalSliceFromJSON[T any](b []byte, x *[]T) error {
@@ -690,6 +691,85 @@ func (m MapFn[K, T, V]) All() iter.Seq2[K, V] {
}
}
// ValuePointer provides a read-only view of a pointer to a value type,
// such as a primitive type or an immutable struct. Its Value and ValueOk
// methods return a stack-allocated shallow copy of the underlying value.
// It is the caller's responsibility to ensure that T
// is free from memory aliasing/mutation concerns.
type ValuePointer[T any] struct {
// ж is the underlying value, named with a hard-to-type
// character that looks pointy like a pointer.
// It is named distinctively to make you think of how dangerous it is to escape
// to callers. You must not let callers be able to mutate it.
ж *T
}
// Valid reports whether the underlying pointer is non-nil.
func (p ValuePointer[T]) Valid() bool {
return p.ж != nil
}
// Get returns a shallow copy of the value if the underlying pointer is non-nil.
// Otherwise, it returns a zero value.
func (p ValuePointer[T]) Get() T {
v, _ := p.GetOk()
return v
}
// GetOk returns a shallow copy of the underlying value and true if the underlying
// pointer is non-nil. Otherwise, it returns a zero value and false.
func (p ValuePointer[T]) GetOk() (value T, ok bool) {
if p.ж == nil {
return value, false // value holds a zero value
}
return *p.ж, true
}
// GetOr returns a shallow copy of the underlying value if it is non-nil.
// Otherwise, it returns the provided default value.
func (p ValuePointer[T]) GetOr(def T) T {
if p.ж == nil {
return def
}
return *p.ж
}
// Clone returns a shallow copy of the underlying value.
func (p ValuePointer[T]) Clone() *T {
if p.ж == nil {
return nil
}
return ptr.To(*p.ж)
}
// String implements [fmt.Stringer].
func (p ValuePointer[T]) String() string {
if p.ж == nil {
return "nil"
}
return fmt.Sprint(p.ж)
}
// ValuePointerOf returns an immutable view of a pointer to an immutable value.
// It is the caller's responsibility to ensure that T
// is free from memory aliasing/mutation concerns.
func ValuePointerOf[T any](v *T) ValuePointer[T] {
return ValuePointer[T]{v}
}
// MarshalJSON implements [json.Marshaler].
func (p ValuePointer[T]) MarshalJSON() ([]byte, error) {
return json.Marshal(p.ж)
}
// UnmarshalJSON implements [json.Unmarshaler].
func (p *ValuePointer[T]) UnmarshalJSON(b []byte) error {
if p.ж != nil {
return errors.New("already initialized")
}
return json.Unmarshal(b, &p.ж)
}
// ContainsPointers reports whether T contains any pointers,
// either explicitly or implicitly.
// It has special handling for some types that contain pointers