aboutsummaryrefslogtreecommitdiff
path: root/src/cmd
diff options
context:
space:
mode:
authorCuong Manh Le <cuong.manhle.vn@gmail.com>2023-06-29 00:41:32 +0700
committerGopher Robot <gobot@golang.org>2023-08-16 21:00:48 +0000
commit5fa4aac0cec637fd9415fb260e3fbc2975377e00 (patch)
tree324d7283af39fa9c5ae913710afabfe8aab080d4 /src/cmd
parent9f03e8367d85d75675b2f2e90873e3293799d8aa (diff)
downloadgo-5fa4aac0cec637fd9415fb260e3fbc2975377e00.tar.xz
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>
Diffstat (limited to 'src/cmd')
-rw-r--r--src/cmd/compile/internal/types2/gcsizes.go169
-rw-r--r--src/cmd/compile/internal/types2/sizes.go17
-rw-r--r--src/cmd/compile/internal/types2/sizes_test.go59
3 files changed, 234 insertions, 11 deletions
diff --git a/src/cmd/compile/internal/types2/gcsizes.go b/src/cmd/compile/internal/types2/gcsizes.go
new file mode 100644
index 0000000000..fe961e30ef
--- /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 59f600a05b..cc0288da4d 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 7af89583f2..9a772f4b15 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)
+ })
+ }
+}