From 5fa4aac0cec637fd9415fb260e3fbc2975377e00 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Thu, 29 Jun 2023 00:41:32 +0700 Subject: [PATCH] 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 Run-TryBot: Cuong Manh Le Reviewed-by: Robert Griesemer Run-TryBot: Robert Griesemer TryBot-Result: Gopher Robot Reviewed-by: Matthew Dempsky Auto-Submit: Robert Griesemer --- src/cmd/compile/internal/types2/gcsizes.go | 169 +++++++++++++++++ src/cmd/compile/internal/types2/sizes.go | 17 +- src/cmd/compile/internal/types2/sizes_test.go | 59 ++++++ src/go/types/gcsizes.go | 171 ++++++++++++++++++ src/go/types/generate_test.go | 1 + src/go/types/sizes.go | 17 +- src/go/types/sizes_test.go | 59 ++++++ .../types/testdata/check/builtins0.go | 12 +- 8 files changed, 477 insertions(+), 28 deletions(-) create mode 100644 src/cmd/compile/internal/types2/gcsizes.go create mode 100644 src/go/types/gcsizes.go diff --git a/src/cmd/compile/internal/types2/gcsizes.go b/src/cmd/compile/internal/types2/gcsizes.go new file mode 100644 index 0000000000000..fe961e30ef1e6 --- /dev/null +++ b/src/cmd/compile/internal/types2/gcsizes.go @@ -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] +} diff --git a/src/cmd/compile/internal/types2/sizes.go b/src/cmd/compile/internal/types2/sizes.go index 59f600a05b10b..cc0288da4d1b5 100644 --- a/src/cmd/compile/internal/types2/sizes.go +++ b/src/cmd/compile/internal/types2/sizes.go @@ -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 - } - 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. diff --git a/src/cmd/compile/internal/types2/sizes_test.go b/src/cmd/compile/internal/types2/sizes_test.go index 7af89583f2d1a..9a772f4b1523f 100644 --- a/src/cmd/compile/internal/types2/sizes_test.go +++ b/src/cmd/compile/internal/types2/sizes_test.go @@ -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) + }) + } +} diff --git a/src/go/types/gcsizes.go b/src/go/types/gcsizes.go new file mode 100644 index 0000000000000..9a7c0cf43c9f1 --- /dev/null +++ b/src/go/types/gcsizes.go @@ -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] +} diff --git a/src/go/types/generate_test.go b/src/go/types/generate_test.go index 7f338270aafbc..d1552c4fe229e 100644 --- a/src/go/types/generate_test.go +++ b/src/go/types/generate_test.go @@ -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) diff --git a/src/go/types/sizes.go b/src/go/types/sizes.go index 2dcaebe402a4b..c329752b3a034 100644 --- a/src/go/types/sizes.go +++ b/src/go/types/sizes.go @@ -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 - } - 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. diff --git a/src/go/types/sizes_test.go b/src/go/types/sizes_test.go index f2e7e8ab2e888..825bc1f9f52ad 100644 --- a/src/go/types/sizes_test.go +++ b/src/go/types/sizes_test.go @@ -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) + }) + } +} diff --git a/src/internal/types/testdata/check/builtins0.go b/src/internal/types/testdata/check/builtins0.go index ed4769ee8c415..12d8fbfd0e41e 100644 --- a/src/internal/types/testdata/check/builtins0.go +++ b/src/internal/types/testdata/check/builtins0.go @@ -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 {