--- /dev/null
+// Copyright 2023 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 types2
+
+type gcSizes struct {
+ WordSize int64 // word size in bytes - must be >= 4 (32bits)
+ MaxAlign int64 // maximum alignment in bytes - must be >= 1
+}
+
+func (s *gcSizes) Alignof(T Type) (result int64) {
+ defer func() {
+ assert(result >= 1)
+ }()
+
+ // For arrays and structs, alignment is defined in terms
+ // of alignment of the elements and fields, respectively.
+ switch t := under(T).(type) {
+ case *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 *Struct:
+ if len(t.fields) == 0 && IsSyncAtomicAlign64(T) {
+ // Special case: sync/atomic.align64 is an
+ // empty struct we recognize as a signal that
+ // the struct it contains must be
+ // 64-bit-aligned.
+ //
+ // This logic is equivalent to the logic in
+ // cmd/compile/internal/types/size.go:calcStructOffset
+ return 8
+ }
+
+ // 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 _, f := range t.fields {
+ if a := s.Alignof(f.typ); a > max {
+ max = a
+ }
+ }
+ return max
+ case *Slice, *Interface:
+ // Multiword data structures are effectively structs
+ // in which each element has size WordSize.
+ // Type parameters lead to variable sizes/alignments;
+ // StdSizes.Alignof won't be called for them.
+ assert(!isTypeParam(T))
+ return s.WordSize
+ case *Basic:
+ // Strings are like slices and interfaces.
+ if t.Info()&IsString != 0 {
+ return s.WordSize
+ }
+ case *TypeParam, *Union:
+ unreachable()
+ }
+ a := s.Sizeof(T) // may be 0 or negative
+ // spec: "For a variable x of any type: unsafe.Alignof(x) is at least 1."
+ if a < 1 {
+ return 1
+ }
+ // complex{64,128} are aligned like [2]float{32,64}.
+ if isComplex(T) {
+ a /= 2
+ }
+ if a > s.MaxAlign {
+ return s.MaxAlign
+ }
+ return a
+}
+
+func (s *gcSizes) Offsetsof(fields []*Var) []int64 {
+ offsets := make([]int64, len(fields))
+ var offs int64
+ for i, f := range fields {
+ if offs < 0 {
+ // all remaining offsets are too large
+ offsets[i] = -1
+ continue
+ }
+ // offs >= 0
+ a := s.Alignof(f.typ)
+ offs = align(offs, a) // possibly < 0 if align overflows
+ offsets[i] = offs
+ if d := s.Sizeof(f.typ); d >= 0 && offs >= 0 {
+ offs += d // ok to overflow to < 0
+ } else {
+ offs = -1 // f.typ or offs is too large
+ }
+ }
+ return offsets
+}
+
+func (s *gcSizes) Sizeof(T Type) int64 {
+ switch t := under(T).(type) {
+ case *Basic:
+ assert(isTyped(T))
+ k := t.kind
+ if int(k) < len(basicSizes) {
+ if s := basicSizes[k]; s > 0 {
+ return int64(s)
+ }
+ }
+ if k == String {
+ return s.WordSize * 2
+ }
+ case *Array:
+ n := t.len
+ if n <= 0 {
+ return 0
+ }
+ // n > 0
+ esize := s.Sizeof(t.elem)
+ if esize < 0 {
+ return -1 // element too large
+ }
+ if esize == 0 {
+ return 0 // 0-size element
+ }
+ // esize > 0
+ // Final size is esize * n; and size must be <= maxInt64.
+ const maxInt64 = 1<<63 - 1
+ if esize > maxInt64/n {
+ return -1 // esize * n overflows
+ }
+ return esize * n
+ case *Slice:
+ return s.WordSize * 3
+ case *Struct:
+ n := t.NumFields()
+ if n == 0 {
+ return 0
+ }
+ offsets := s.Offsetsof(t.fields)
+ offs := offsets[n-1]
+ size := s.Sizeof(t.fields[n-1].typ)
+ if offs < 0 || size < 0 {
+ return -1 // type too large
+ }
+ // gc: The last field of a non-zero-sized struct is not allowed to
+ // have size 0.
+ if offs > 0 && size == 0 {
+ size = 1
+ }
+ // gc: Size includes alignment padding.
+ return align(offs+size, s.Alignof(t)) // may overflow to < 0 which is ok
+ case *Interface:
+ // Type parameters lead to variable sizes/alignments;
+ // StdSizes.Sizeof won't be called for them.
+ assert(!isTypeParam(T))
+ return s.WordSize * 2
+ case *TypeParam, *Union:
+ unreachable()
+ }
+ return s.WordSize // catch-all
+}
+
+// gcSizesFor returns the Sizes used by gc for an architecture.
+// The result is nil if a compiler/architecture pair is not known.
+func gcSizesFor(compiler, arch string) *gcSizes {
+ if compiler != "gc" {
+ return nil
+ }
+ return gcArchSizes[arch]
+}
}
// common architecture word sizes and alignments
-var gcArchSizes = map[string]*StdSizes{
+var gcArchSizes = map[string]*gcSizes{
"386": {4, 4},
"amd64": {8, 8},
"amd64p32": {4, 8},
// "386", "amd64", "amd64p32", "arm", "arm64", "loong64", "mips", "mipsle",
// "mips64", "mips64le", "ppc64", "ppc64le", "riscv64", "s390x", "sparc64", "wasm".
func SizesFor(compiler, arch string) Sizes {
- var m map[string]*StdSizes
switch compiler {
case "gc":
- m = gcArchSizes
+ return gcSizesFor(compiler, arch)
case "gccgo":
- m = gccgoArchSizes
- default:
- return nil
- }
- s, ok := m[arch]
- if !ok {
- return nil
+ if s, ok := gccgoArchSizes[arch]; ok {
+ return s
+ }
}
- return s
+ return nil
}
// stdSizes is used if Config.Sizes == nil.
})
}
}
+
+type gcSizeTest struct {
+ name string
+ src string
+}
+
+var gcSizesTests = []gcSizeTest{
+ {
+ "issue60431",
+ `
+package main
+
+import "unsafe"
+
+// The foo struct size is expected to be rounded up to 16 bytes.
+type foo struct {
+ a int64
+ b bool
+}
+
+func main() {
+ assert(unsafe.Sizeof(foo{}) == 16)
+}`,
+ },
+ {
+ "issue60734",
+ `
+package main
+
+import (
+ "unsafe"
+)
+
+// The Data struct size is expected to be rounded up to 16 bytes.
+type Data struct {
+ Value uint32 // 4 bytes
+ Label [10]byte // 10 bytes
+ Active bool // 1 byte
+ // padded with 1 byte to make it align
+}
+
+func main() {
+ assert(unsafe.Sizeof(Data{}) == 16)
+}
+`,
+ },
+}
+
+func TestGCSizes(t *testing.T) {
+ types2.DefPredeclaredTestFuncs()
+ for _, tc := range gcSizesTests {
+ tc := tc
+ t.Run(tc.name, func(t *testing.T) {
+ t.Parallel()
+ conf := types2.Config{Importer: defaultImporter(), Sizes: types2.SizesFor("gc", "amd64")}
+ mustTypecheck(tc.src, &conf, nil)
+ })
+ }
+}
--- /dev/null
+// Code generated by "go test -run=Generate -write=all"; DO NOT EDIT.
+
+// Copyright 2023 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 types
+
+type gcSizes struct {
+ WordSize int64 // word size in bytes - must be >= 4 (32bits)
+ MaxAlign int64 // maximum alignment in bytes - must be >= 1
+}
+
+func (s *gcSizes) Alignof(T Type) (result int64) {
+ defer func() {
+ assert(result >= 1)
+ }()
+
+ // For arrays and structs, alignment is defined in terms
+ // of alignment of the elements and fields, respectively.
+ switch t := under(T).(type) {
+ case *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 *Struct:
+ if len(t.fields) == 0 && _IsSyncAtomicAlign64(T) {
+ // Special case: sync/atomic.align64 is an
+ // empty struct we recognize as a signal that
+ // the struct it contains must be
+ // 64-bit-aligned.
+ //
+ // This logic is equivalent to the logic in
+ // cmd/compile/internal/types/size.go:calcStructOffset
+ return 8
+ }
+
+ // 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 _, f := range t.fields {
+ if a := s.Alignof(f.typ); a > max {
+ max = a
+ }
+ }
+ return max
+ case *Slice, *Interface:
+ // Multiword data structures are effectively structs
+ // in which each element has size WordSize.
+ // Type parameters lead to variable sizes/alignments;
+ // StdSizes.Alignof won't be called for them.
+ assert(!isTypeParam(T))
+ return s.WordSize
+ case *Basic:
+ // Strings are like slices and interfaces.
+ if t.Info()&IsString != 0 {
+ return s.WordSize
+ }
+ case *TypeParam, *Union:
+ unreachable()
+ }
+ a := s.Sizeof(T) // may be 0 or negative
+ // spec: "For a variable x of any type: unsafe.Alignof(x) is at least 1."
+ if a < 1 {
+ return 1
+ }
+ // complex{64,128} are aligned like [2]float{32,64}.
+ if isComplex(T) {
+ a /= 2
+ }
+ if a > s.MaxAlign {
+ return s.MaxAlign
+ }
+ return a
+}
+
+func (s *gcSizes) Offsetsof(fields []*Var) []int64 {
+ offsets := make([]int64, len(fields))
+ var offs int64
+ for i, f := range fields {
+ if offs < 0 {
+ // all remaining offsets are too large
+ offsets[i] = -1
+ continue
+ }
+ // offs >= 0
+ a := s.Alignof(f.typ)
+ offs = align(offs, a) // possibly < 0 if align overflows
+ offsets[i] = offs
+ if d := s.Sizeof(f.typ); d >= 0 && offs >= 0 {
+ offs += d // ok to overflow to < 0
+ } else {
+ offs = -1 // f.typ or offs is too large
+ }
+ }
+ return offsets
+}
+
+func (s *gcSizes) Sizeof(T Type) int64 {
+ switch t := under(T).(type) {
+ case *Basic:
+ assert(isTyped(T))
+ k := t.kind
+ if int(k) < len(basicSizes) {
+ if s := basicSizes[k]; s > 0 {
+ return int64(s)
+ }
+ }
+ if k == String {
+ return s.WordSize * 2
+ }
+ case *Array:
+ n := t.len
+ if n <= 0 {
+ return 0
+ }
+ // n > 0
+ esize := s.Sizeof(t.elem)
+ if esize < 0 {
+ return -1 // element too large
+ }
+ if esize == 0 {
+ return 0 // 0-size element
+ }
+ // esize > 0
+ // Final size is esize * n; and size must be <= maxInt64.
+ const maxInt64 = 1<<63 - 1
+ if esize > maxInt64/n {
+ return -1 // esize * n overflows
+ }
+ return esize * n
+ case *Slice:
+ return s.WordSize * 3
+ case *Struct:
+ n := t.NumFields()
+ if n == 0 {
+ return 0
+ }
+ offsets := s.Offsetsof(t.fields)
+ offs := offsets[n-1]
+ size := s.Sizeof(t.fields[n-1].typ)
+ if offs < 0 || size < 0 {
+ return -1 // type too large
+ }
+ // gc: The last field of a non-zero-sized struct is not allowed to
+ // have size 0.
+ if offs > 0 && size == 0 {
+ size = 1
+ }
+ // gc: Size includes alignment padding.
+ return align(offs+size, s.Alignof(t)) // may overflow to < 0 which is ok
+ case *Interface:
+ // Type parameters lead to variable sizes/alignments;
+ // StdSizes.Sizeof won't be called for them.
+ assert(!isTypeParam(T))
+ return s.WordSize * 2
+ case *TypeParam, *Union:
+ unreachable()
+ }
+ return s.WordSize // catch-all
+}
+
+// gcSizesFor returns the Sizes used by gc for an architecture.
+// The result is nil if a compiler/architecture pair is not known.
+func gcSizesFor(compiler, arch string) *gcSizes {
+ if compiler != "gc" {
+ return nil
+ }
+ return gcArchSizes[arch]
+}
"context.go": nil,
"context_test.go": nil,
"gccgosizes.go": nil,
+ "gcsizes.go": func(f *ast.File) { renameIdent(f, "IsSyncAtomicAlign64", "_IsSyncAtomicAlign64") },
"hilbert_test.go": func(f *ast.File) { renameImportPath(f, `"cmd/compile/internal/types2"`, `"go/types"`) },
"infer.go": func(f *ast.File) {
fixTokenPos(f)
}
// common architecture word sizes and alignments
-var gcArchSizes = map[string]*StdSizes{
+var gcArchSizes = map[string]*gcSizes{
"386": {4, 4},
"amd64": {8, 8},
"amd64p32": {4, 8},
// "386", "amd64", "amd64p32", "arm", "arm64", "loong64", "mips", "mipsle",
// "mips64", "mips64le", "ppc64", "ppc64le", "riscv64", "s390x", "sparc64", "wasm".
func SizesFor(compiler, arch string) Sizes {
- var m map[string]*StdSizes
switch compiler {
case "gc":
- m = gcArchSizes
+ return gcSizesFor(compiler, arch)
case "gccgo":
- m = gccgoArchSizes
- default:
- return nil
- }
- s, ok := m[arch]
- if !ok {
- return nil
+ if s, ok := gccgoArchSizes[arch]; ok {
+ return s
+ }
}
- return s
+ return nil
}
// stdSizes is used if Config.Sizes == nil.
})
}
}
+
+type gcSizeTest struct {
+ name string
+ src string
+}
+
+var gcSizesTests = []gcSizeTest{
+ {
+ "issue60431",
+ `
+package main
+
+import "unsafe"
+
+// The foo struct size is expected to be rounded up to 16 bytes.
+type foo struct {
+ a int64
+ b bool
+}
+
+func main() {
+ assert(unsafe.Sizeof(foo{}) == 16)
+}`,
+ },
+ {
+ "issue60734",
+ `
+package main
+
+import (
+ "unsafe"
+)
+
+// The Data struct size is expected to be rounded up to 16 bytes.
+type Data struct {
+ Value uint32 // 4 bytes
+ Label [10]byte // 10 bytes
+ Active bool // 1 byte
+ // padded with 1 byte to make it align
+}
+
+func main() {
+ assert(unsafe.Sizeof(Data{}) == 16)
+}
+`,
+ },
+}
+
+func TestGCSizes(t *testing.T) {
+ types.DefPredeclaredTestFuncs()
+ for _, tc := range gcSizesTests {
+ tc := tc
+ t.Run(tc.name, func(t *testing.T) {
+ t.Parallel()
+ conf := types.Config{Importer: importer.Default(), Sizes: types.SizesFor("gc", "amd64")}
+ mustTypecheck(tc.src, &conf, nil)
+ })
+ }
+}
type S3 struct { // offset
a int64 // 0
b int32 // 8
-} // 12
+} // 16
type S4 struct { // offset
S3 // 0
int32 // 12
-} // 16
+} // 24
type S5 struct { // offset
a [3]int32 // 0
- b int32 // 12
+ b int32 // 16
} // 16
func (S2) m() {}
assert(unsafe.Sizeof(y2) == 8)
var y3 S3
- assert(unsafe.Sizeof(y3) == 12)
+ assert(unsafe.Sizeof(y3) == 16)
var y4 S4
- assert(unsafe.Sizeof(y4) == 16)
+ assert(unsafe.Sizeof(y4) == 24)
var y5 S5
assert(unsafe.Sizeof(y5) == 16)
var a3 [10]S3
- assert(unsafe.Sizeof(a3) == 156)
+ assert(unsafe.Sizeof(a3) == 160)
// test case for issue 5670
type T struct {