github.com/prysmaticlabs/prysm@v1.4.4/tools/analyzers/maligned/maligned.go (about)

     1  // Originally from https://github.com/mdempsky/maligned, adapted to work with nogo.
     2  
     3  // Copyright 2013 The Go Authors. All rights reserved.
     4  // Use of this source code is governed by a BSD-style
     5  // license that can be found in the LICENSE file.
     6  
     7  package maligned
     8  
     9  import (
    10  	"fmt"
    11  	"go/build"
    12  	"go/token"
    13  	"go/types"
    14  	"sort"
    15  	"strings"
    16  )
    17  
    18  func malign(_ token.Pos, str *types.Struct) error {
    19  	wordSize := int64(8)
    20  	maxAlign := int64(8)
    21  	switch build.Default.GOARCH {
    22  	case "386", "arm":
    23  		wordSize, maxAlign = 4, 4
    24  	case "amd64p32":
    25  		wordSize = 4
    26  	}
    27  
    28  	s := gcSizes{wordSize, maxAlign}
    29  	sz, opt := s.Sizeof(str), optimalSize(str, &s)
    30  	if sz != opt {
    31  		return fmt.Errorf("maligned struct of size %d could be %d if fields were sorted by type. See: https://bit.ly/malignedstruct\n\nOptimal sort order is\n%s", sz, opt, optimalSort(s, str))
    32  	}
    33  	return nil
    34  }
    35  
    36  func optimalSort(sizes gcSizes, str *types.Struct) string {
    37  	nf := str.NumFields()
    38  	fields := make([]*types.Var, nf)
    39  	alignofs := make([]int64, nf)
    40  	sizeofs := make([]int64, nf)
    41  	for i := 0; i < nf; i++ {
    42  		fields[i] = str.Field(i)
    43  		ft := fields[i].Type()
    44  		alignofs[i] = sizes.Alignof(ft)
    45  		sizeofs[i] = sizes.Sizeof(ft)
    46  	}
    47  	sort.Sort(&byAlignAndSize{fields, alignofs, sizeofs})
    48  	// Reverse fields
    49  	for left, right := 0, len(fields)-1; left < right; left, right = left+1, right-1 {
    50  		fields[left], fields[right] = fields[right], fields[left]
    51  	}
    52  	ss := make([]string, nf)
    53  	for i, f := range fields {
    54  		ss[i] = fmt.Sprintf("  %s %s", f.Name(), f.Type().String())
    55  	}
    56  
    57  	return strings.Join(ss, "\n")
    58  }
    59  
    60  func optimalSize(str *types.Struct, sizes *gcSizes) int64 {
    61  	nf := str.NumFields()
    62  	fields := make([]*types.Var, nf)
    63  	alignofs := make([]int64, nf)
    64  	sizeofs := make([]int64, nf)
    65  	for i := 0; i < nf; i++ {
    66  		fields[i] = str.Field(i)
    67  		ft := fields[i].Type()
    68  		alignofs[i] = sizes.Alignof(ft)
    69  		sizeofs[i] = sizes.Sizeof(ft)
    70  	}
    71  	sort.Sort(&byAlignAndSize{fields, alignofs, sizeofs})
    72  	return sizes.Sizeof(types.NewStruct(fields, nil))
    73  }
    74  
    75  type byAlignAndSize struct {
    76  	fields   []*types.Var
    77  	alignofs []int64
    78  	sizeofs  []int64
    79  }
    80  
    81  // Len --
    82  func (s *byAlignAndSize) Len() int { return len(s.fields) }
    83  
    84  // Swap --
    85  func (s *byAlignAndSize) Swap(i, j int) {
    86  	s.fields[i], s.fields[j] = s.fields[j], s.fields[i]
    87  	s.alignofs[i], s.alignofs[j] = s.alignofs[j], s.alignofs[i]
    88  	s.sizeofs[i], s.sizeofs[j] = s.sizeofs[j], s.sizeofs[i]
    89  }
    90  
    91  // Less --
    92  func (s *byAlignAndSize) Less(i, j int) bool {
    93  	// Place zero sized objects before non-zero sized objects.
    94  	if s.sizeofs[i] == 0 && s.sizeofs[j] != 0 {
    95  		return true
    96  	}
    97  	if s.sizeofs[j] == 0 && s.sizeofs[i] != 0 {
    98  		return false
    99  	}
   100  
   101  	// Next, place more tightly aligned objects before less tightly aligned objects.
   102  	if s.alignofs[i] != s.alignofs[j] {
   103  		return s.alignofs[i] > s.alignofs[j]
   104  	}
   105  
   106  	// Lastly, order by size.
   107  	if s.sizeofs[i] != s.sizeofs[j] {
   108  		return s.sizeofs[i] > s.sizeofs[j]
   109  	}
   110  
   111  	return false
   112  }
   113  
   114  // Code below based on go/types.StdSizes.
   115  
   116  type gcSizes struct {
   117  	WordSize int64
   118  	MaxAlign int64
   119  }
   120  
   121  // Alignof --
   122  func (s *gcSizes) Alignof(T types.Type) int64 {
   123  	// NOTE: On amd64, complex64 is 8 byte aligned,
   124  	// even though float32 is only 4 byte aligned.
   125  
   126  	// For arrays and structs, alignment is defined in terms
   127  	// of alignment of the elements and fields, respectively.
   128  	switch t := T.Underlying().(type) {
   129  	case *types.Array:
   130  		// spec: "For a variable x of array type: unsafe.Alignof(x)
   131  		// is the same as unsafe.Alignof(x[0]), but at least 1."
   132  		return s.Alignof(t.Elem())
   133  	case *types.Struct:
   134  		// spec: "For a variable x of struct type: unsafe.Alignof(x)
   135  		// is the largest of the values unsafe.Alignof(x.f) for each
   136  		// field f of x, but at least 1."
   137  		max := int64(1)
   138  		for i, nf := 0, t.NumFields(); i < nf; i++ {
   139  			if a := s.Alignof(t.Field(i).Type()); a > max {
   140  				max = a
   141  			}
   142  		}
   143  		return max
   144  	}
   145  	a := s.Sizeof(T) // may be 0
   146  	// spec: "For a variable x of any type: unsafe.Alignof(x) is at least 1."
   147  	if a < 1 {
   148  		return 1
   149  	}
   150  	if a > s.MaxAlign {
   151  		return s.MaxAlign
   152  	}
   153  	return a
   154  }
   155  
   156  var basicSizes = [...]byte{
   157  	types.Bool:       1,
   158  	types.Int8:       1,
   159  	types.Int16:      2,
   160  	types.Int32:      4,
   161  	types.Int64:      8,
   162  	types.Uint8:      1,
   163  	types.Uint16:     2,
   164  	types.Uint32:     4,
   165  	types.Uint64:     8,
   166  	types.Float32:    4,
   167  	types.Float64:    8,
   168  	types.Complex64:  8,
   169  	types.Complex128: 16,
   170  }
   171  
   172  // Sizeof --
   173  func (s *gcSizes) Sizeof(T types.Type) int64 {
   174  	switch t := T.Underlying().(type) {
   175  	case *types.Basic:
   176  		k := t.Kind()
   177  		if int(k) < len(basicSizes) {
   178  			if s := basicSizes[k]; s > 0 {
   179  				return int64(s)
   180  			}
   181  		}
   182  		if k == types.String {
   183  			return s.WordSize * 2
   184  		}
   185  	case *types.Array:
   186  		n := t.Len()
   187  		if n == 0 {
   188  			return 0
   189  		}
   190  		a := s.Alignof(t.Elem())
   191  		z := s.Sizeof(t.Elem())
   192  		return align(z, a)*(n-1) + z
   193  	case *types.Slice:
   194  		return s.WordSize * 3
   195  	case *types.Struct:
   196  		nf := t.NumFields()
   197  		if nf == 0 {
   198  			return 0
   199  		}
   200  
   201  		var o int64
   202  		max := int64(1)
   203  		for i := 0; i < nf; i++ {
   204  			ft := t.Field(i).Type()
   205  			a, sz := s.Alignof(ft), s.Sizeof(ft)
   206  			if a > max {
   207  				max = a
   208  			}
   209  			if i == nf-1 && sz == 0 && o != 0 {
   210  				sz = 1
   211  			}
   212  			o = align(o, a) + sz
   213  		}
   214  		return align(o, max)
   215  	case *types.Interface:
   216  		return s.WordSize * 2
   217  	}
   218  	return s.WordSize // catch-all
   219  }
   220  
   221  // align returns the smallest y >= x such that y % a == 0.
   222  func align(x, a int64) int64 {
   223  	y := x + a - 1
   224  	return y - y%a
   225  }