github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/cmd/structlayout-optimize/main.go (about)

     1  // structlayout-optimize reorders struct fields to minimize the amount
     2  // of padding.
     3  package main
     4  
     5  import (
     6  	"encoding/json"
     7  	"flag"
     8  	"fmt"
     9  	"log"
    10  	"os"
    11  	"sort"
    12  	"strings"
    13  
    14  	"github.com/amarpal/go-tools/lintcmd/version"
    15  	st "github.com/amarpal/go-tools/structlayout"
    16  )
    17  
    18  var (
    19  	fJSON    bool
    20  	fRecurse bool
    21  	fVersion bool
    22  )
    23  
    24  func init() {
    25  	flag.BoolVar(&fJSON, "json", false, "Format data as JSON")
    26  	flag.BoolVar(&fRecurse, "r", false, "Break up structs and reorder their fields freely")
    27  	flag.BoolVar(&fVersion, "version", false, "Print version and exit")
    28  }
    29  
    30  func main() {
    31  	log.SetFlags(0)
    32  	flag.Parse()
    33  
    34  	if fVersion {
    35  		version.Print(version.Version, version.MachineVersion)
    36  		os.Exit(0)
    37  	}
    38  
    39  	var in []st.Field
    40  	if err := json.NewDecoder(os.Stdin).Decode(&in); err != nil {
    41  		log.Fatal(err)
    42  	}
    43  	if len(in) == 0 {
    44  		return
    45  	}
    46  	if !fRecurse {
    47  		in = combine(in)
    48  	}
    49  	var fields []st.Field
    50  	for _, field := range in {
    51  		if field.IsPadding {
    52  			continue
    53  		}
    54  		fields = append(fields, field)
    55  	}
    56  	optimize(fields)
    57  	fields = pad(fields)
    58  
    59  	if fJSON {
    60  		json.NewEncoder(os.Stdout).Encode(fields)
    61  	} else {
    62  		for _, field := range fields {
    63  			fmt.Println(field)
    64  		}
    65  	}
    66  }
    67  
    68  func combine(fields []st.Field) []st.Field {
    69  	new := st.Field{}
    70  	cur := ""
    71  	var out []st.Field
    72  	wasPad := true
    73  	for _, field := range fields {
    74  		var prefix string
    75  		if field.IsPadding {
    76  			wasPad = true
    77  			continue
    78  		}
    79  		p := strings.Split(field.Name, ".")
    80  		prefix = strings.Join(p[:2], ".")
    81  		if field.Align > new.Align {
    82  			new.Align = field.Align
    83  		}
    84  		if !wasPad {
    85  			new.End = field.Start
    86  			new.Size = new.End - new.Start
    87  		}
    88  		if prefix != cur {
    89  			if cur != "" {
    90  				out = append(out, new)
    91  			}
    92  			cur = prefix
    93  			new = field
    94  			new.Name = prefix
    95  		} else {
    96  			new.Type = "struct"
    97  		}
    98  		wasPad = false
    99  	}
   100  	new.Size = new.End - new.Start
   101  	out = append(out, new)
   102  	return out
   103  }
   104  
   105  func optimize(fields []st.Field) {
   106  	sort.Sort(&byAlignAndSize{fields})
   107  }
   108  
   109  func pad(fields []st.Field) []st.Field {
   110  	if len(fields) == 0 {
   111  		return nil
   112  	}
   113  	var out []st.Field
   114  	pos := int64(0)
   115  	offsets := offsetsof(fields)
   116  	alignment := int64(1)
   117  	for i, field := range fields {
   118  		if field.Align > alignment {
   119  			alignment = field.Align
   120  		}
   121  		if offsets[i] > pos {
   122  			padding := offsets[i] - pos
   123  			out = append(out, st.Field{
   124  				IsPadding: true,
   125  				Start:     pos,
   126  				End:       pos + padding,
   127  				Size:      padding,
   128  			})
   129  			pos += padding
   130  		}
   131  		field.Start = pos
   132  		field.End = pos + field.Size
   133  		out = append(out, field)
   134  		pos += field.Size
   135  	}
   136  	sz := size(out)
   137  	pad := align(sz, alignment) - sz
   138  	if pad > 0 {
   139  		field := out[len(out)-1]
   140  		out = append(out, st.Field{
   141  			IsPadding: true,
   142  			Start:     field.End,
   143  			End:       field.End + pad,
   144  			Size:      pad,
   145  		})
   146  	}
   147  	return out
   148  }
   149  
   150  func size(fields []st.Field) int64 {
   151  	n := int64(0)
   152  	for _, field := range fields {
   153  		n += field.Size
   154  	}
   155  	return n
   156  }
   157  
   158  type byAlignAndSize struct {
   159  	fields []st.Field
   160  }
   161  
   162  func (s *byAlignAndSize) Len() int { return len(s.fields) }
   163  func (s *byAlignAndSize) Swap(i, j int) {
   164  	s.fields[i], s.fields[j] = s.fields[j], s.fields[i]
   165  }
   166  
   167  func (s *byAlignAndSize) Less(i, j int) bool {
   168  	// Place zero sized objects before non-zero sized objects.
   169  	if s.fields[i].Size == 0 && s.fields[j].Size != 0 {
   170  		return true
   171  	}
   172  	if s.fields[j].Size == 0 && s.fields[i].Size != 0 {
   173  		return false
   174  	}
   175  
   176  	// Next, place more tightly aligned objects before less tightly aligned objects.
   177  	if s.fields[i].Align != s.fields[j].Align {
   178  		return s.fields[i].Align > s.fields[j].Align
   179  	}
   180  
   181  	// Lastly, order by size.
   182  	if s.fields[i].Size != s.fields[j].Size {
   183  		return s.fields[i].Size > s.fields[j].Size
   184  	}
   185  
   186  	return false
   187  }
   188  
   189  func offsetsof(fields []st.Field) []int64 {
   190  	offsets := make([]int64, len(fields))
   191  	var o int64
   192  	for i, f := range fields {
   193  		a := f.Align
   194  		o = align(o, a)
   195  		offsets[i] = o
   196  		o += f.Size
   197  	}
   198  	return offsets
   199  }
   200  
   201  // align returns the smallest y >= x such that y % a == 0.
   202  func align(x, a int64) int64 {
   203  	y := x + a - 1
   204  	return y - y%a
   205  }