Skip to content

Commit

Permalink
go/types, types2: add Sizes computation to match gc behavior
Browse files Browse the repository at this point in the history
Fixes #60431
Fixes #60734
Fixes #61035

Change-Id: I82513da3e1714e8271fae220fe242bf2bfb4eb9d
Reviewed-on: https://go-review.googlesource.com/c/go/+/506856
Auto-Submit: Cuong Manh Le <cuong.manhle.vn@gmail.com>
Run-TryBot: Cuong Manh Le <cuong.manhle.vn@gmail.com>
Reviewed-by: Robert Griesemer <gri@google.com>
Run-TryBot: Robert Griesemer <gri@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
Auto-Submit: Robert Griesemer <gri@google.com>
  • Loading branch information
cuonglm authored and gopherbot committed Aug 16, 2023
1 parent 9f03e83 commit 5fa4aac
Show file tree
Hide file tree
Showing 8 changed files with 477 additions and 28 deletions.
169 changes: 169 additions & 0 deletions src/cmd/compile/internal/types2/gcsizes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// 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]
}
17 changes: 6 additions & 11 deletions src/cmd/compile/internal/types2/sizes.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ func (s *StdSizes) Sizeof(T Type) int64 {
}

// 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},
Expand Down Expand Up @@ -255,20 +255,15 @@ var gcArchSizes = map[string]*StdSizes{
// "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.
Expand Down
59 changes: 59 additions & 0 deletions src/cmd/compile/internal/types2/sizes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,62 @@ var s struct {
})
}
}

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)
})
}
}
Loading

0 comments on commit 5fa4aac

Please sign in to comment.