Skip to content

Commit 5aa6acb

Browse files
cuonglmgopherbot
authored andcommitted
go/analysis: add Sizes that matches gc size computations
For golang/go#60431 For golang/go#60734 Change-Id: I6a15a24e3e121635b33d77fde9170a41514c0db1 Reviewed-on: https://go-review.googlesource.com/c/tools/+/501495 Run-TryBot: Cuong Manh Le <[email protected]> Reviewed-by: Damien Neil <[email protected]> Auto-Submit: Cuong Manh Le <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Run-TryBot: Robert Griesemer <[email protected]> Auto-Submit: Robert Griesemer <[email protected]> gopls-CI: kokoro <[email protected]> Reviewed-by: Robert Griesemer <[email protected]>
1 parent 5a89a3b commit 5aa6acb

File tree

3 files changed

+323
-1
lines changed

3 files changed

+323
-1
lines changed

go/analysis/unitchecker/sizes.go

+237
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package unitchecker
6+
7+
import (
8+
"fmt"
9+
"go/types"
10+
)
11+
12+
type gcSizes struct {
13+
WordSize int64 // word size in bytes - must be >= 4 (32bits)
14+
MaxAlign int64 // maximum alignment in bytes - must be >= 1
15+
}
16+
17+
func (s *gcSizes) Alignof(T types.Type) int64 {
18+
// For arrays and structs, alignment is defined in terms
19+
// of alignment of the elements and fields, respectively.
20+
switch t := T.Underlying().(type) {
21+
case *types.Array:
22+
// spec: "For a variable x of array type: unsafe.Alignof(x)
23+
// is the same as unsafe.Alignof(x[0]), but at least 1."
24+
return s.Alignof(t.Elem())
25+
case *types.Struct:
26+
if t.NumFields() == 0 && isSyncAtomicAlign64(T) {
27+
// Special case: sync/atomic.align64 is an
28+
// empty struct we recognize as a signal that
29+
// the struct it contains must be
30+
// 64-bit-aligned.
31+
//
32+
// This logic is equivalent to the logic in
33+
// cmd/compile/internal/types/size.go:calcStructOffset
34+
return 8
35+
}
36+
37+
// spec: "For a variable x of struct type: unsafe.Alignof(x)
38+
// is the largest of the values unsafe.Alignof(x.f) for each
39+
// field f of x, but at least 1."
40+
max := int64(1)
41+
for i, nf := 0, t.NumFields(); i < nf; i++ {
42+
if a := s.Alignof(t.Field(i).Type()); a > max {
43+
max = a
44+
}
45+
}
46+
return max
47+
case *types.Slice, *types.Interface:
48+
// Multiword data structures are effectively structs
49+
// in which each element has size PtrSize.
50+
return s.WordSize
51+
case *types.Basic:
52+
// Strings are like slices and interfaces.
53+
if t.Info()&types.IsString != 0 {
54+
return s.WordSize
55+
}
56+
}
57+
a := s.Sizeof(T) // may be 0
58+
// spec: "For a variable x of any type: unsafe.Alignof(x) is at least 1."
59+
if a < 1 {
60+
return 1
61+
}
62+
// complex{64,128} are aligned like [2]float{32,64}.
63+
if isComplex(T) {
64+
a /= 2
65+
}
66+
if a > s.MaxAlign {
67+
return s.MaxAlign
68+
}
69+
return a
70+
}
71+
72+
func isComplex(T types.Type) bool {
73+
basic, ok := T.Underlying().(*types.Basic)
74+
return ok && basic.Info()&types.IsComplex != 0
75+
}
76+
77+
func (s *gcSizes) Offsetsof(fields []*types.Var) []int64 {
78+
offsets := make([]int64, len(fields))
79+
var offs int64
80+
for i, f := range fields {
81+
if offs < 0 {
82+
// all remaining offsets are too large
83+
offsets[i] = -1
84+
continue
85+
}
86+
// offs >= 0
87+
typ := f.Type()
88+
a := s.Alignof(typ)
89+
offs = roundUp(offs, a) // possibly < 0 if align overflows
90+
offsets[i] = offs
91+
if d := s.Sizeof(typ); d >= 0 && offs >= 0 {
92+
offs += d // ok to overflow to < 0
93+
} else {
94+
offs = -1
95+
}
96+
}
97+
return offsets
98+
}
99+
100+
func (s *gcSizes) Sizeof(T types.Type) int64 {
101+
switch t := T.Underlying().(type) {
102+
case *types.Basic:
103+
k := t.Kind()
104+
if int(k) < len(basicSizes) {
105+
if s := basicSizes[k]; s > 0 {
106+
return int64(s)
107+
}
108+
}
109+
switch k {
110+
case types.String:
111+
return s.WordSize * 2
112+
case types.Int, types.Uint, types.Uintptr, types.UnsafePointer:
113+
return s.WordSize
114+
}
115+
panic(fmt.Sprintf("unimplemented basic: %v (kind %v)", T, k))
116+
case *types.Array:
117+
n := t.Len()
118+
if n <= 0 {
119+
return 0
120+
}
121+
// n > 0
122+
// gc: Size includes alignment padding.
123+
esize := s.Sizeof(t.Elem())
124+
if esize < 0 {
125+
return -1 // array element too large
126+
}
127+
if esize == 0 {
128+
return 0 // 0-size element
129+
}
130+
// esize > 0
131+
// Final size is esize * n; and size must be <= maxInt64.
132+
const maxInt64 = 1<<63 - 1
133+
if esize > maxInt64/n {
134+
return -1 // esize * n overflows
135+
}
136+
return esize * n
137+
case *types.Slice:
138+
return s.WordSize * 3
139+
case *types.Struct:
140+
n := t.NumFields()
141+
if n == 0 {
142+
return 0
143+
}
144+
// n > 0
145+
fields := make([]*types.Var, n)
146+
for i := range fields {
147+
fields[i] = t.Field(i)
148+
}
149+
offsets := s.Offsetsof(fields)
150+
// gc: The last field of a non-zero-sized struct is not allowed to
151+
// have size 0.
152+
last := s.Sizeof(fields[n-1].Type())
153+
if last == 0 && offsets[n-1] > 0 {
154+
last = 1
155+
}
156+
// gc: Size includes alignment padding.
157+
return roundUp(offsets[n-1]+last, s.Alignof(t)) // may overflow to < 0 which is ok
158+
case *types.Interface:
159+
return s.WordSize * 2
160+
case *types.Chan, *types.Map, *types.Pointer, *types.Signature:
161+
return s.WordSize
162+
default:
163+
panic(fmt.Sprintf("Sizeof(%T) unimplemented", t))
164+
}
165+
}
166+
167+
func isSyncAtomicAlign64(T types.Type) bool {
168+
named, ok := T.(*types.Named)
169+
if !ok {
170+
return false
171+
}
172+
obj := named.Obj()
173+
return obj.Name() == "align64" &&
174+
obj.Pkg() != nil &&
175+
(obj.Pkg().Path() == "sync/atomic" ||
176+
obj.Pkg().Path() == "runtime/internal/atomic")
177+
}
178+
179+
// roundUp rounds o to a multiple of r, r is a power of 2.
180+
func roundUp(o int64, r int64) int64 {
181+
if r < 1 || r > 8 || r&(r-1) != 0 {
182+
panic(fmt.Sprintf("Round %d", r))
183+
}
184+
return (o + r - 1) &^ (r - 1)
185+
}
186+
187+
var basicSizes = [...]byte{
188+
types.Invalid: 1,
189+
types.Bool: 1,
190+
types.Int8: 1,
191+
types.Int16: 2,
192+
types.Int32: 4,
193+
types.Int64: 8,
194+
types.Uint8: 1,
195+
types.Uint16: 2,
196+
types.Uint32: 4,
197+
types.Uint64: 8,
198+
types.Float32: 4,
199+
types.Float64: 8,
200+
types.Complex64: 8,
201+
types.Complex128: 16,
202+
}
203+
204+
// common architecture word sizes and alignments
205+
var gcArchSizes = map[string]*gcSizes{
206+
"386": {4, 4},
207+
"amd64": {8, 8},
208+
"amd64p32": {4, 8},
209+
"arm": {4, 4},
210+
"arm64": {8, 8},
211+
"loong64": {8, 8},
212+
"mips": {4, 4},
213+
"mipsle": {4, 4},
214+
"mips64": {8, 8},
215+
"mips64le": {8, 8},
216+
"ppc64": {8, 8},
217+
"ppc64le": {8, 8},
218+
"riscv64": {8, 8},
219+
"s390x": {8, 8},
220+
"sparc64": {8, 8},
221+
"wasm": {8, 8},
222+
// When adding more architectures here,
223+
// update the doc string of sizesFor below.
224+
}
225+
226+
// sizesFor returns the go/types.Sizes used by a compiler for an architecture.
227+
// The result is nil if a compiler/architecture pair is not known.
228+
func sizesFor(compiler, arch string) types.Sizes {
229+
if compiler != "gc" {
230+
return nil
231+
}
232+
s, ok := gcArchSizes[arch]
233+
if !ok {
234+
return nil
235+
}
236+
return s
237+
}

go/analysis/unitchecker/sizes_test.go

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package unitchecker
6+
7+
import (
8+
"go/ast"
9+
"go/importer"
10+
"go/parser"
11+
"go/token"
12+
"go/types"
13+
"testing"
14+
)
15+
16+
type gcSizeTest struct {
17+
name string
18+
src string
19+
}
20+
21+
var gcSizesTests = []gcSizeTest{
22+
{
23+
"issue60431",
24+
`package main
25+
26+
import "unsafe"
27+
28+
// The foo struct size is expected to be rounded up to 16 bytes.
29+
type foo struct {
30+
a int64
31+
b bool
32+
}
33+
34+
func main() {
35+
var _ [unsafe.Sizeof(foo{}) - 16]byte
36+
println(unsafe.Sizeof(foo{}))
37+
}`,
38+
},
39+
{
40+
"issue60734",
41+
`package main
42+
43+
import (
44+
"unsafe"
45+
)
46+
47+
// The Data struct size is expected to be rounded up to 16 bytes.
48+
type Data struct {
49+
Value uint32 // 4 bytes
50+
Label [10]byte // 10 bytes
51+
Active bool // 1 byte
52+
// padded with 1 byte to make it align
53+
}
54+
55+
const (
56+
dataSize = unsafe.Sizeof(Data{})
57+
dataSizeLiteral = 16
58+
)
59+
60+
func main() {
61+
_ = [16]byte{0, 132, 95, 237, 80, 104, 111, 110, 101, 0, 0, 0, 0, 0, 1, 0}
62+
_ = [dataSize]byte{0, 132, 95, 237, 80, 104, 111, 110, 101, 0, 0, 0, 0, 0, 1, 0}
63+
_ = [dataSizeLiteral]byte{0, 132, 95, 237, 80, 104, 111, 110, 101, 0, 0, 0, 0, 0, 1, 0}
64+
}`,
65+
},
66+
}
67+
68+
func TestGCSizes(t *testing.T) {
69+
for _, tc := range gcSizesTests {
70+
tc := tc
71+
t.Run(tc.name, func(t *testing.T) {
72+
t.Parallel()
73+
fset := token.NewFileSet()
74+
f, err := parser.ParseFile(fset, "x.go", tc.src, 0)
75+
if err != nil {
76+
t.Fatal(err)
77+
}
78+
79+
conf := types.Config{Importer: importer.Default(), Sizes: sizesFor("gc", "amd64")}
80+
if _, err := conf.Check("main.go", fset, []*ast.File{f}, nil); err != nil {
81+
t.Fatal(err) // type error
82+
}
83+
})
84+
}
85+
}

go/analysis/unitchecker/unitchecker.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ func run(fset *token.FileSet, cfg *Config, analyzers []*analysis.Analyzer) ([]re
218218
})
219219
tc := &types.Config{
220220
Importer: importer,
221-
Sizes: types.SizesFor("gc", build.Default.GOARCH), // assume gccgo ≡ gc?
221+
Sizes: sizesFor("gc", build.Default.GOARCH), // assume gccgo ≡ gc?
222222
}
223223
info := &types.Info{
224224
Types: make(map[ast.Expr]types.TypeAndValue),

0 commit comments

Comments
 (0)