mirror of
https://github.com/tailscale/tailscale.git
synced 2025-08-11 05:07:33 +00:00
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:
@@ -282,7 +282,7 @@ func (i *jsIPN) run(jsCallbacks js.Value) {
|
||||
MachineKey: p.Machine().String(),
|
||||
NodeKey: p.Key().String(),
|
||||
},
|
||||
Online: p.Online(),
|
||||
Online: p.Online().Clone(),
|
||||
TailscaleSSHEnabled: p.Hostinfo().TailscaleSSHEnabled(),
|
||||
}
|
||||
}),
|
||||
|
@@ -37,9 +37,14 @@ type Map struct {
|
||||
StructWithPtrKey map[StructWithPtrs]int `json:"-"`
|
||||
}
|
||||
|
||||
type StructWithNoView struct {
|
||||
Value int
|
||||
}
|
||||
|
||||
type StructWithPtrs struct {
|
||||
Value *StructWithoutPtrs
|
||||
Int *int
|
||||
Value *StructWithoutPtrs
|
||||
Int *int
|
||||
NoView *StructWithNoView
|
||||
|
||||
NoCloneValue *StructWithoutPtrs `codegen:"noclone"`
|
||||
}
|
||||
|
@@ -28,6 +28,9 @@ func (src *StructWithPtrs) Clone() *StructWithPtrs {
|
||||
if dst.Int != nil {
|
||||
dst.Int = ptr.To(*src.Int)
|
||||
}
|
||||
if dst.NoView != nil {
|
||||
dst.NoView = ptr.To(*src.NoView)
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
@@ -35,6 +38,7 @@ func (src *StructWithPtrs) Clone() *StructWithPtrs {
|
||||
var _StructWithPtrsCloneNeedsRegeneration = StructWithPtrs(struct {
|
||||
Value *StructWithoutPtrs
|
||||
Int *int
|
||||
NoView *StructWithNoView
|
||||
NoCloneValue *StructWithoutPtrs
|
||||
}{})
|
||||
|
||||
|
@@ -61,20 +61,11 @@ func (v *StructWithPtrsView) UnmarshalJSON(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v StructWithPtrsView) Value() *StructWithoutPtrs {
|
||||
if v.ж.Value == nil {
|
||||
return nil
|
||||
}
|
||||
x := *v.ж.Value
|
||||
return &x
|
||||
}
|
||||
func (v StructWithPtrsView) Value() StructWithoutPtrsView { return v.ж.Value.View() }
|
||||
func (v StructWithPtrsView) Int() views.ValuePointer[int] { return views.ValuePointerOf(v.ж.Int) }
|
||||
|
||||
func (v StructWithPtrsView) Int() *int {
|
||||
if v.ж.Int == nil {
|
||||
return nil
|
||||
}
|
||||
x := *v.ж.Int
|
||||
return &x
|
||||
func (v StructWithPtrsView) NoView() views.ValuePointer[StructWithNoView] {
|
||||
return views.ValuePointerOf(v.ж.NoView)
|
||||
}
|
||||
|
||||
func (v StructWithPtrsView) NoCloneValue() *StructWithoutPtrs { return v.ж.NoCloneValue }
|
||||
@@ -85,6 +76,7 @@ func (v StructWithPtrsView) Equal(v2 StructWithPtrsView) bool { return v.ж.Equa
|
||||
var _StructWithPtrsViewNeedsRegeneration = StructWithPtrs(struct {
|
||||
Value *StructWithoutPtrs
|
||||
Int *int
|
||||
NoView *StructWithNoView
|
||||
NoCloneValue *StructWithoutPtrs
|
||||
}{})
|
||||
|
||||
@@ -424,12 +416,8 @@ func (v *GenericIntStructView[T]) UnmarshalJSON(b []byte) error {
|
||||
}
|
||||
|
||||
func (v GenericIntStructView[T]) Value() T { return v.ж.Value }
|
||||
func (v GenericIntStructView[T]) Pointer() *T {
|
||||
if v.ж.Pointer == nil {
|
||||
return nil
|
||||
}
|
||||
x := *v.ж.Pointer
|
||||
return &x
|
||||
func (v GenericIntStructView[T]) Pointer() views.ValuePointer[T] {
|
||||
return views.ValuePointerOf(v.ж.Pointer)
|
||||
}
|
||||
|
||||
func (v GenericIntStructView[T]) Slice() views.Slice[T] { return views.SliceOf(v.ж.Slice) }
|
||||
@@ -500,12 +488,8 @@ func (v *GenericNoPtrsStructView[T]) UnmarshalJSON(b []byte) error {
|
||||
}
|
||||
|
||||
func (v GenericNoPtrsStructView[T]) Value() T { return v.ж.Value }
|
||||
func (v GenericNoPtrsStructView[T]) Pointer() *T {
|
||||
if v.ж.Pointer == nil {
|
||||
return nil
|
||||
}
|
||||
x := *v.ж.Pointer
|
||||
return &x
|
||||
func (v GenericNoPtrsStructView[T]) Pointer() views.ValuePointer[T] {
|
||||
return views.ValuePointerOf(v.ж.Pointer)
|
||||
}
|
||||
|
||||
func (v GenericNoPtrsStructView[T]) Slice() views.Slice[T] { return views.SliceOf(v.ж.Slice) }
|
||||
@@ -722,19 +706,14 @@ func (v *StructWithTypeAliasFieldsView) UnmarshalJSON(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v StructWithTypeAliasFieldsView) WithPtr() StructWithPtrsView { return v.ж.WithPtr.View() }
|
||||
func (v StructWithTypeAliasFieldsView) WithPtr() StructWithPtrsAliasView { return v.ж.WithPtr.View() }
|
||||
func (v StructWithTypeAliasFieldsView) WithoutPtr() StructWithoutPtrsAlias { return v.ж.WithoutPtr }
|
||||
func (v StructWithTypeAliasFieldsView) WithPtrByPtr() StructWithPtrsAliasView {
|
||||
return v.ж.WithPtrByPtr.View()
|
||||
}
|
||||
func (v StructWithTypeAliasFieldsView) WithoutPtrByPtr() *StructWithoutPtrsAlias {
|
||||
if v.ж.WithoutPtrByPtr == nil {
|
||||
return nil
|
||||
}
|
||||
x := *v.ж.WithoutPtrByPtr
|
||||
return &x
|
||||
func (v StructWithTypeAliasFieldsView) WithoutPtrByPtr() StructWithoutPtrsAliasView {
|
||||
return v.ж.WithoutPtrByPtr.View()
|
||||
}
|
||||
|
||||
func (v StructWithTypeAliasFieldsView) SliceWithPtrs() views.SliceView[*StructWithPtrsAlias, StructWithPtrsAliasView] {
|
||||
return views.SliceOfViews[*StructWithPtrsAlias, StructWithPtrsAliasView](v.ж.SliceWithPtrs)
|
||||
}
|
||||
|
@@ -79,13 +79,7 @@ func (v *{{.ViewName}}{{.TypeParamNames}}) UnmarshalJSON(b []byte) error {
|
||||
{{end}}
|
||||
{{define "makeViewField"}}func (v {{.ViewName}}{{.TypeParamNames}}) {{.FieldName}}() {{.FieldViewName}} { return {{.MakeViewFnName}}(&v.ж.{{.FieldName}}) }
|
||||
{{end}}
|
||||
{{define "valuePointerField"}}func (v {{.ViewName}}{{.TypeParamNames}}) {{.FieldName}}() {{.FieldType}} {
|
||||
if v.ж.{{.FieldName}} == nil {
|
||||
return nil
|
||||
}
|
||||
x := *v.ж.{{.FieldName}}
|
||||
return &x
|
||||
}
|
||||
{{define "valuePointerField"}}func (v {{.ViewName}}{{.TypeParamNames}}) {{.FieldName}}() views.ValuePointer[{{.FieldType}}] { return views.ValuePointerOf(v.ж.{{.FieldName}}) }
|
||||
|
||||
{{end}}
|
||||
{{define "mapField"}}
|
||||
@@ -126,7 +120,7 @@ func requiresCloning(t types.Type) (shallow, deep bool, base types.Type) {
|
||||
return p, p, t
|
||||
}
|
||||
|
||||
func genView(buf *bytes.Buffer, it *codegen.ImportTracker, typ *types.Named, thisPkg *types.Package) {
|
||||
func genView(buf *bytes.Buffer, it *codegen.ImportTracker, typ *types.Named, _ *types.Package) {
|
||||
t, ok := typ.Underlying().(*types.Struct)
|
||||
if !ok || codegen.IsViewType(t) {
|
||||
return
|
||||
@@ -354,10 +348,32 @@ func genView(buf *bytes.Buffer, it *codegen.ImportTracker, typ *types.Named, thi
|
||||
} else {
|
||||
writeTemplate("unsupportedField")
|
||||
}
|
||||
} else {
|
||||
args.FieldType = it.QualifiedName(ptr)
|
||||
writeTemplate("valuePointerField")
|
||||
continue
|
||||
}
|
||||
|
||||
// If a view type is already defined for the base type, use it as the field's view type.
|
||||
if viewType := viewTypeForValueType(base); viewType != nil {
|
||||
args.FieldType = it.QualifiedName(base)
|
||||
args.FieldViewName = it.QualifiedName(viewType)
|
||||
writeTemplate("viewField")
|
||||
continue
|
||||
}
|
||||
|
||||
// Otherwise, if the unaliased base type is a named type whose view type will be generated by this viewer invocation,
|
||||
// append the "View" suffix to the unaliased base type name and use it as the field's view type.
|
||||
if base, ok := types.Unalias(base).(*types.Named); ok && slices.Contains(typeNames, it.QualifiedName(base)) {
|
||||
baseTypeName := it.QualifiedName(base)
|
||||
args.FieldType = baseTypeName
|
||||
args.FieldViewName = appendNameSuffix(args.FieldType, "View")
|
||||
writeTemplate("viewField")
|
||||
continue
|
||||
}
|
||||
|
||||
// Otherwise, if the base type does not require deep cloning, has no existing view type,
|
||||
// and will not have a generated view type, use views.ValuePointer[T] as the field's view type.
|
||||
// Its Get/GetOk methods return stack-allocated shallow copies of the field's value.
|
||||
args.FieldType = it.QualifiedName(base)
|
||||
writeTemplate("valuePointerField")
|
||||
continue
|
||||
case *types.Interface:
|
||||
// If fieldType is an interface with a "View() {ViewType}" method, it can be used to clone the field.
|
||||
@@ -405,6 +421,33 @@ func appendNameSuffix(name, suffix string) string {
|
||||
return name + suffix
|
||||
}
|
||||
|
||||
func typeNameOf(typ types.Type) (name *types.TypeName, ok bool) {
|
||||
switch t := typ.(type) {
|
||||
case *types.Alias:
|
||||
return t.Obj(), true
|
||||
case *types.Named:
|
||||
return t.Obj(), true
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
func lookupViewType(typ types.Type) types.Type {
|
||||
for {
|
||||
if typeName, ok := typeNameOf(typ); ok && typeName.Pkg() != nil {
|
||||
if viewTypeObj := typeName.Pkg().Scope().Lookup(typeName.Name() + "View"); viewTypeObj != nil {
|
||||
return viewTypeObj.Type()
|
||||
}
|
||||
}
|
||||
switch alias := typ.(type) {
|
||||
case *types.Alias:
|
||||
typ = alias.Rhs()
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func viewTypeForValueType(typ types.Type) types.Type {
|
||||
if ptr, ok := typ.(*types.Pointer); ok {
|
||||
return viewTypeForValueType(ptr.Elem())
|
||||
@@ -417,7 +460,12 @@ func viewTypeForValueType(typ types.Type) types.Type {
|
||||
if !ok || sig.Results().Len() != 1 {
|
||||
return nil
|
||||
}
|
||||
return sig.Results().At(0).Type()
|
||||
viewType := sig.Results().At(0).Type()
|
||||
// Check if the typ's package defines an alias for the view type, and use it if so.
|
||||
if viewTypeAlias, ok := lookupViewType(typ).(*types.Alias); ok && types.AssignableTo(viewType, viewTypeAlias) {
|
||||
viewType = viewTypeAlias
|
||||
}
|
||||
return viewType
|
||||
}
|
||||
|
||||
func viewTypeForContainerType(typ types.Type) (*types.Named, *types.Func) {
|
||||
|
Reference in New Issue
Block a user