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 }