Skip to content

Commit 5fa4aac

Browse files
cuonglmgopherbot
authored andcommitted
go/types, types2: add Sizes computation to match gc behavior
Fixes #60431 Fixes #60734 Fixes #61035 Change-Id: I82513da3e1714e8271fae220fe242bf2bfb4eb9d Reviewed-on: https://go-review.googlesource.com/c/go/+/506856 Auto-Submit: Cuong Manh Le <[email protected]> Run-TryBot: Cuong Manh Le <[email protected]> Reviewed-by: Robert Griesemer <[email protected]> Run-TryBot: Robert Griesemer <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Matthew Dempsky <[email protected]> Auto-Submit: Robert Griesemer <[email protected]>
1 parent 9f03e83 commit 5fa4aac

File tree

8 files changed

+477
-28
lines changed

8 files changed

+477
-28
lines changed
+169
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
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 types2
6+
7+
type gcSizes struct {
8+
WordSize int64 // word size in bytes - must be >= 4 (32bits)
9+
MaxAlign int64 // maximum alignment in bytes - must be >= 1
10+
}
11+
12+
func (s *gcSizes) Alignof(T Type) (result int64) {
13+
defer func() {
14+
assert(result >= 1)
15+
}()
16+
17+
// For arrays and structs, alignment is defined in terms
18+
// of alignment of the elements and fields, respectively.
19+
switch t := under(T).(type) {
20+
case *Array:
21+
// spec: "For a variable x of array type: unsafe.Alignof(x)
22+
// is the same as unsafe.Alignof(x[0]), but at least 1."
23+
return s.Alignof(t.elem)
24+
case *Struct:
25+
if len(t.fields) == 0 && IsSyncAtomicAlign64(T) {
26+
// Special case: sync/atomic.align64 is an
27+
// empty struct we recognize as a signal that
28+
// the struct it contains must be
29+
// 64-bit-aligned.
30+
//
31+
// This logic is equivalent to the logic in
32+
// cmd/compile/internal/types/size.go:calcStructOffset
33+
return 8
34+
}
35+
36+
// spec: "For a variable x of struct type: unsafe.Alignof(x)
37+
// is the largest of the values unsafe.Alignof(x.f) for each
38+
// field f of x, but at least 1."
39+
max := int64(1)
40+
for _, f := range t.fields {
41+
if a := s.Alignof(f.typ); a > max {
42+
max = a
43+
}
44+
}
45+
return max
46+
case *Slice, *Interface:
47+
// Multiword data structures are effectively structs
48+
// in which each element has size WordSize.
49+
// Type parameters lead to variable sizes/alignments;
50+
// StdSizes.Alignof won't be called for them.
51+
assert(!isTypeParam(T))
52+
return s.WordSize
53+
case *Basic:
54+
// Strings are like slices and interfaces.
55+
if t.Info()&IsString != 0 {
56+
return s.WordSize
57+
}
58+
case *TypeParam, *Union:
59+
unreachable()
60+
}
61+
a := s.Sizeof(T) // may be 0 or negative
62+
// spec: "For a variable x of any type: unsafe.Alignof(x) is at least 1."
63+
if a < 1 {
64+
return 1
65+
}
66+
// complex{64,128} are aligned like [2]float{32,64}.
67+
if isComplex(T) {
68+
a /= 2
69+
}
70+
if a > s.MaxAlign {
71+
return s.MaxAlign
72+
}
73+
return a
74+
}
75+
76+
func (s *gcSizes) Offsetsof(fields []*Var) []int64 {
77+
offsets := make([]int64, len(fields))
78+
var offs int64
79+
for i, f := range fields {
80+
if offs < 0 {
81+
// all remaining offsets are too large
82+
offsets[i] = -1
83+
continue
84+
}
85+
// offs >= 0
86+
a := s.Alignof(f.typ)
87+
offs = align(offs, a) // possibly < 0 if align overflows
88+
offsets[i] = offs
89+
if d := s.Sizeof(f.typ); d >= 0 && offs >= 0 {
90+
offs += d // ok to overflow to < 0
91+
} else {
92+
offs = -1 // f.typ or offs is too large
93+
}
94+
}
95+
return offsets
96+
}
97+
98+
func (s *gcSizes) Sizeof(T Type) int64 {
99+
switch t := under(T).(type) {
100+
case *Basic:
101+
assert(isTyped(T))
102+
k := t.kind
103+
if int(k) < len(basicSizes) {
104+
if s := basicSizes[k]; s > 0 {
105+
return int64(s)
106+
}
107+
}
108+
if k == String {
109+
return s.WordSize * 2
110+
}
111+
case *Array:
112+
n := t.len
113+
if n <= 0 {
114+
return 0
115+
}
116+
// n > 0
117+
esize := s.Sizeof(t.elem)
118+
if esize < 0 {
119+
return -1 // element too large
120+
}
121+
if esize == 0 {
122+
return 0 // 0-size element
123+
}
124+
// esize > 0
125+
// Final size is esize * n; and size must be <= maxInt64.
126+
const maxInt64 = 1<<63 - 1
127+
if esize > maxInt64/n {
128+
return -1 // esize * n overflows
129+
}
130+
return esize * n
131+
case *Slice:
132+
return s.WordSize * 3
133+
case *Struct:
134+
n := t.NumFields()
135+
if n == 0 {
136+
return 0
137+
}
138+
offsets := s.Offsetsof(t.fields)
139+
offs := offsets[n-1]
140+
size := s.Sizeof(t.fields[n-1].typ)
141+
if offs < 0 || size < 0 {
142+
return -1 // type too large
143+
}
144+
// gc: The last field of a non-zero-sized struct is not allowed to
145+
// have size 0.
146+
if offs > 0 && size == 0 {
147+
size = 1
148+
}
149+
// gc: Size includes alignment padding.
150+
return align(offs+size, s.Alignof(t)) // may overflow to < 0 which is ok
151+
case *Interface:
152+
// Type parameters lead to variable sizes/alignments;
153+
// StdSizes.Sizeof won't be called for them.
154+
assert(!isTypeParam(T))
155+
return s.WordSize * 2
156+
case *TypeParam, *Union:
157+
unreachable()
158+
}
159+
return s.WordSize // catch-all
160+
}
161+
162+
// gcSizesFor returns the Sizes used by gc for an architecture.
163+
// The result is nil if a compiler/architecture pair is not known.
164+
func gcSizesFor(compiler, arch string) *gcSizes {
165+
if compiler != "gc" {
166+
return nil
167+
}
168+
return gcArchSizes[arch]
169+
}

src/cmd/compile/internal/types2/sizes.go

+6-11
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ func (s *StdSizes) Sizeof(T Type) int64 {
227227
}
228228

229229
// common architecture word sizes and alignments
230-
var gcArchSizes = map[string]*StdSizes{
230+
var gcArchSizes = map[string]*gcSizes{
231231
"386": {4, 4},
232232
"amd64": {8, 8},
233233
"amd64p32": {4, 8},
@@ -255,20 +255,15 @@ var gcArchSizes = map[string]*StdSizes{
255255
// "386", "amd64", "amd64p32", "arm", "arm64", "loong64", "mips", "mipsle",
256256
// "mips64", "mips64le", "ppc64", "ppc64le", "riscv64", "s390x", "sparc64", "wasm".
257257
func SizesFor(compiler, arch string) Sizes {
258-
var m map[string]*StdSizes
259258
switch compiler {
260259
case "gc":
261-
m = gcArchSizes
260+
return gcSizesFor(compiler, arch)
262261
case "gccgo":
263-
m = gccgoArchSizes
264-
default:
265-
return nil
266-
}
267-
s, ok := m[arch]
268-
if !ok {
269-
return nil
262+
if s, ok := gccgoArchSizes[arch]; ok {
263+
return s
264+
}
270265
}
271-
return s
266+
return nil
272267
}
273268

274269
// stdSizes is used if Config.Sizes == nil.

src/cmd/compile/internal/types2/sizes_test.go

+59
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,62 @@ var s struct {
133133
})
134134
}
135135
}
136+
137+
type gcSizeTest struct {
138+
name string
139+
src string
140+
}
141+
142+
var gcSizesTests = []gcSizeTest{
143+
{
144+
"issue60431",
145+
`
146+
package main
147+
148+
import "unsafe"
149+
150+
// The foo struct size is expected to be rounded up to 16 bytes.
151+
type foo struct {
152+
a int64
153+
b bool
154+
}
155+
156+
func main() {
157+
assert(unsafe.Sizeof(foo{}) == 16)
158+
}`,
159+
},
160+
{
161+
"issue60734",
162+
`
163+
package main
164+
165+
import (
166+
"unsafe"
167+
)
168+
169+
// The Data struct size is expected to be rounded up to 16 bytes.
170+
type Data struct {
171+
Value uint32 // 4 bytes
172+
Label [10]byte // 10 bytes
173+
Active bool // 1 byte
174+
// padded with 1 byte to make it align
175+
}
176+
177+
func main() {
178+
assert(unsafe.Sizeof(Data{}) == 16)
179+
}
180+
`,
181+
},
182+
}
183+
184+
func TestGCSizes(t *testing.T) {
185+
types2.DefPredeclaredTestFuncs()
186+
for _, tc := range gcSizesTests {
187+
tc := tc
188+
t.Run(tc.name, func(t *testing.T) {
189+
t.Parallel()
190+
conf := types2.Config{Importer: defaultImporter(), Sizes: types2.SizesFor("gc", "amd64")}
191+
mustTypecheck(tc.src, &conf, nil)
192+
})
193+
}
194+
}

0 commit comments

Comments
 (0)