mirror of
https://github.com/golang/go
synced 2024-10-04 15:09:59 +00:00
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 <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>
This commit is contained in:
parent
9f03e8367d
commit
5fa4aac0ce
169
src/cmd/compile/internal/types2/gcsizes.go
Normal file
169
src/cmd/compile/internal/types2/gcsizes.go
Normal file
|
@ -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]
|
||||
}
|
|
@ -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},
|
||||
|
@ -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
|
||||
if s, ok := gccgoArchSizes[arch]; ok {
|
||||
return s
|
||||
}
|
||||
}
|
||||
s, ok := m[arch]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return s
|
||||
return nil
|
||||
}
|
||||
|
||||
// stdSizes is used if Config.Sizes == nil.
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
171
src/go/types/gcsizes.go
Normal file
171
src/go/types/gcsizes.go
Normal file
|
@ -0,0 +1,171 @@
|
|||
// 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]
|
||||
}
|
|
@ -102,6 +102,7 @@ var filemap = map[string]action{
|
|||
"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)
|
||||
|
|
|
@ -229,7 +229,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},
|
||||
|
@ -257,20 +257,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
|
||||
if s, ok := gccgoArchSizes[arch]; ok {
|
||||
return s
|
||||
}
|
||||
}
|
||||
s, ok := m[arch]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return s
|
||||
return nil
|
||||
}
|
||||
|
||||
// stdSizes is used if Config.Sizes == nil.
|
||||
|
|
|
@ -134,3 +134,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) {
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
12
src/internal/types/testdata/check/builtins0.go
vendored
12
src/internal/types/testdata/check/builtins0.go
vendored
|
@ -792,16 +792,16 @@ type S2 struct{ // offset
|
|||
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() {}
|
||||
|
@ -936,16 +936,16 @@ func Sizeof1() {
|
|||
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 {
|
||||
|
|
Loading…
Reference in a new issue