254 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			254 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// 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/ast"
 | 
						|
	"go/build"
 | 
						|
	"go/token"
 | 
						|
	"go/types"
 | 
						|
	"sort"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"golang.org/x/tools/go/loader"
 | 
						|
)
 | 
						|
 | 
						|
var fset = token.NewFileSet()
 | 
						|
 | 
						|
type Issue struct {
 | 
						|
	OldSize, NewSize int
 | 
						|
	NewStructDef     string
 | 
						|
	Pos              token.Position
 | 
						|
}
 | 
						|
 | 
						|
func Run(prog *loader.Program) []Issue {
 | 
						|
	flagVerbose := true
 | 
						|
	fset = prog.Fset
 | 
						|
 | 
						|
	var issues []Issue
 | 
						|
 | 
						|
	for _, pkg := range prog.InitialPackages() {
 | 
						|
		for _, file := range pkg.Files {
 | 
						|
			ast.Inspect(file, func(node ast.Node) bool {
 | 
						|
				if s, ok := node.(*ast.StructType); ok {
 | 
						|
					i := malign(node.Pos(), pkg.Types[s].Type.(*types.Struct), flagVerbose)
 | 
						|
					if i != nil {
 | 
						|
						issues = append(issues, *i)
 | 
						|
					}
 | 
						|
				}
 | 
						|
				return true
 | 
						|
			})
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return issues
 | 
						|
}
 | 
						|
 | 
						|
func malign(pos token.Pos, str *types.Struct, verbose bool) *Issue {
 | 
						|
	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 := s.Sizeof(str)
 | 
						|
	opt, fields := optimalSize(str, &s, verbose)
 | 
						|
	if sz == opt {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	newStructDefParts := []string{"struct{"}
 | 
						|
 | 
						|
	var w int
 | 
						|
	for _, f := range fields {
 | 
						|
		if n := len(f.Name()); n > w {
 | 
						|
			w = n
 | 
						|
		}
 | 
						|
	}
 | 
						|
	spaces := strings.Repeat(" ", w)
 | 
						|
	for _, f := range fields {
 | 
						|
		line := fmt.Sprintf("\t%s%s\t%s,", f.Name(), spaces[len(f.Name()):], f.Type().String())
 | 
						|
		newStructDefParts = append(newStructDefParts, line)
 | 
						|
	}
 | 
						|
	newStructDefParts = append(newStructDefParts, "}")
 | 
						|
 | 
						|
	return &Issue{
 | 
						|
		OldSize:      int(sz),
 | 
						|
		NewSize:      int(opt),
 | 
						|
		NewStructDef: strings.Join(newStructDefParts, "\n"),
 | 
						|
		Pos:          fset.Position(pos),
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func optimalSize(str *types.Struct, sizes *gcSizes, stable bool) (int64, []*types.Var) {
 | 
						|
	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)
 | 
						|
	}
 | 
						|
	if stable { // Stable keeps as much of the order as possible, but slower
 | 
						|
		sort.Stable(&byAlignAndSize{fields, alignofs, sizeofs})
 | 
						|
	} else {
 | 
						|
		sort.Sort(&byAlignAndSize{fields, alignofs, sizeofs})
 | 
						|
	}
 | 
						|
	return sizes.Sizeof(types.NewStruct(fields, nil)), fields
 | 
						|
}
 | 
						|
 | 
						|
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
 | 
						|
}
 |