2020-04-03 05:09:15 +00:00
// Originally from https://github.com/mdempsky/maligned, adapted to work with nogo.
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package maligned
import (
"fmt"
"go/build"
"go/token"
"go/types"
"sort"
"strings"
)
2020-10-12 08:11:05 +00:00
func malign ( _ token . Pos , str * types . Struct ) error {
2020-04-03 05:09:15 +00:00
wordSize := int64 ( 8 )
maxAlign := int64 ( 8 )
switch build . Default . GOARCH {
case "386" , "arm" :
wordSize , maxAlign = 4 , 4
case "amd64p32" :
wordSize = 4
}
s := gcSizes { wordSize , maxAlign }
sz , opt := s . Sizeof ( str ) , optimalSize ( str , & s )
if sz != opt {
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 ) )
}
return nil
}
func optimalSort ( sizes gcSizes , str * types . Struct ) string {
nf := str . NumFields ( )
fields := make ( [ ] * types . Var , nf )
alignofs := make ( [ ] int64 , nf )
sizeofs := make ( [ ] int64 , nf )
for i := 0 ; i < nf ; i ++ {
fields [ i ] = str . Field ( i )
ft := fields [ i ] . Type ( )
alignofs [ i ] = sizes . Alignof ( ft )
sizeofs [ i ] = sizes . Sizeof ( ft )
}
sort . Sort ( & byAlignAndSize { fields , alignofs , sizeofs } )
// Reverse fields
for left , right := 0 , len ( fields ) - 1 ; left < right ; left , right = left + 1 , right - 1 {
fields [ left ] , fields [ right ] = fields [ right ] , fields [ left ]
}
ss := make ( [ ] string , nf )
for i , f := range fields {
ss [ i ] = fmt . Sprintf ( " %s %s" , f . Name ( ) , f . Type ( ) . String ( ) )
}
return strings . Join ( ss , "\n" )
}
func optimalSize ( str * types . Struct , sizes * gcSizes ) int64 {
nf := str . NumFields ( )
fields := make ( [ ] * types . Var , nf )
alignofs := make ( [ ] int64 , nf )
sizeofs := make ( [ ] int64 , nf )
for i := 0 ; i < nf ; i ++ {
fields [ i ] = str . Field ( i )
ft := fields [ i ] . Type ( )
alignofs [ i ] = sizes . Alignof ( ft )
sizeofs [ i ] = sizes . Sizeof ( ft )
}
sort . Sort ( & byAlignAndSize { fields , alignofs , sizeofs } )
return sizes . Sizeof ( types . NewStruct ( fields , nil ) )
}
type byAlignAndSize struct {
fields [ ] * types . Var
alignofs [ ] int64
sizeofs [ ] int64
}
func ( s * byAlignAndSize ) Len ( ) int { return len ( s . fields ) }
func ( s * byAlignAndSize ) Swap ( i , j int ) {
s . fields [ i ] , s . fields [ j ] = s . fields [ j ] , s . fields [ i ]
s . alignofs [ i ] , s . alignofs [ j ] = s . alignofs [ j ] , s . alignofs [ i ]
s . sizeofs [ i ] , s . sizeofs [ j ] = s . sizeofs [ j ] , s . sizeofs [ i ]
}
func ( s * byAlignAndSize ) Less ( i , j int ) bool {
// Place zero sized objects before non-zero sized objects.
if s . sizeofs [ i ] == 0 && s . sizeofs [ j ] != 0 {
return true
}
if s . sizeofs [ j ] == 0 && s . sizeofs [ i ] != 0 {
return false
}
// Next, place more tightly aligned objects before less tightly aligned objects.
if s . alignofs [ i ] != s . alignofs [ j ] {
return s . alignofs [ i ] > s . alignofs [ j ]
}
// Lastly, order by size.
if s . sizeofs [ i ] != s . sizeofs [ j ] {
return s . sizeofs [ i ] > s . sizeofs [ j ]
}
return false
}
// Code below based on go/types.StdSizes.
type gcSizes struct {
WordSize int64
MaxAlign int64
}
func ( s * gcSizes ) Alignof ( T types . Type ) int64 {
// NOTE: On amd64, complex64 is 8 byte aligned,
// even though float32 is only 4 byte aligned.
// For arrays and structs, alignment is defined in terms
// of alignment of the elements and fields, respectively.
switch t := T . Underlying ( ) . ( type ) {
case * types . Array :
// spec: "For a variable x of array type: unsafe.Alignof(x)
// is the same as unsafe.Alignof(x[0]), but at least 1."
return s . Alignof ( t . Elem ( ) )
case * types . Struct :
// spec: "For a variable x of struct type: unsafe.Alignof(x)
// is the largest of the values unsafe.Alignof(x.f) for each
// field f of x, but at least 1."
max := int64 ( 1 )
for i , nf := 0 , t . NumFields ( ) ; i < nf ; i ++ {
if a := s . Alignof ( t . Field ( i ) . Type ( ) ) ; a > max {
max = a
}
}
return max
}
a := s . Sizeof ( T ) // may be 0
// spec: "For a variable x of any type: unsafe.Alignof(x) is at least 1."
if a < 1 {
return 1
}
if a > s . MaxAlign {
return s . MaxAlign
}
return a
}
var basicSizes = [ ... ] byte {
types . Bool : 1 ,
types . Int8 : 1 ,
types . Int16 : 2 ,
types . Int32 : 4 ,
types . Int64 : 8 ,
types . Uint8 : 1 ,
types . Uint16 : 2 ,
types . Uint32 : 4 ,
types . Uint64 : 8 ,
types . Float32 : 4 ,
types . Float64 : 8 ,
types . Complex64 : 8 ,
types . Complex128 : 16 ,
}
func ( s * gcSizes ) Sizeof ( T types . Type ) int64 {
switch t := T . Underlying ( ) . ( type ) {
case * types . Basic :
k := t . Kind ( )
if int ( k ) < len ( basicSizes ) {
if s := basicSizes [ k ] ; s > 0 {
return int64 ( s )
}
}
if k == types . String {
return s . WordSize * 2
}
case * types . Array :
n := t . Len ( )
if n == 0 {
return 0
}
a := s . Alignof ( t . Elem ( ) )
z := s . Sizeof ( t . Elem ( ) )
return align ( z , a ) * ( n - 1 ) + z
case * types . Slice :
return s . WordSize * 3
case * types . Struct :
nf := t . NumFields ( )
if nf == 0 {
return 0
}
var o int64
max := int64 ( 1 )
for i := 0 ; i < nf ; i ++ {
ft := t . Field ( i ) . Type ( )
a , sz := s . Alignof ( ft ) , s . Sizeof ( ft )
if a > max {
max = a
}
if i == nf - 1 && sz == 0 && o != 0 {
sz = 1
}
o = align ( o , a ) + sz
}
return align ( o , max )
case * types . Interface :
return s . WordSize * 2
}
return s . WordSize // catch-all
}
// align returns the smallest y >= x such that y % a == 0.
func align ( x , a int64 ) int64 {
y := x + a - 1
return y - y % a
}