]> Cypherpunks.ru repositories - gostls13.git/commitdiff
go/types, types2: add Sizes computation to match gc behavior
authorCuong Manh Le <cuong.manhle.vn@gmail.com>
Wed, 28 Jun 2023 17:41:32 +0000 (00:41 +0700)
committerGopher Robot <gobot@golang.org>
Wed, 16 Aug 2023 21:00:48 +0000 (21:00 +0000)
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>

src/cmd/compile/internal/types2/gcsizes.go [new file with mode: 0644]
src/cmd/compile/internal/types2/sizes.go
src/cmd/compile/internal/types2/sizes_test.go
src/go/types/gcsizes.go [new file with mode: 0644]
src/go/types/generate_test.go
src/go/types/sizes.go
src/go/types/sizes_test.go
src/internal/types/testdata/check/builtins0.go

diff --git a/src/cmd/compile/internal/types2/gcsizes.go b/src/cmd/compile/internal/types2/gcsizes.go
new file mode 100644 (file)
index 0000000..fe961e3
--- /dev/null
@@ -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]
+}
index 59f600a05b10b99f6a4fd11872e81de4af13a13a..cc0288da4d1b538566d4e598f3a424801f5ac356 100644 (file)
@@ -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.
index 7af89583f2d1a5fee982b7516d731fdbb1a1940b..9a772f4b1523fe23e604ad0980a1af3fd99be097 100644 (file)
@@ -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 (file)
index 0000000..9a7c0cf
--- /dev/null
@@ -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]
+}
index 7f338270aafbcfc80d4aa4f4bc970306fcea9d48..d1552c4fe229eb163329bbbaf3198fbb7cc9ece1 100644 (file)
@@ -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)
index 2dcaebe402a4b5e325e673ef632e0c4c3c14f3ab..c329752b3a034d0f9ed931f87b32d60c139269c9 100644 (file)
@@ -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.
index f2e7e8ab2e8885ae4c0fb87fe074dec33681f3bb..825bc1f9f52ada9fd94f02bbdf9a2ad3b80debc1 100644 (file)
@@ -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)
+               })
+       }
+}
index ed4769ee8c415c8e525b962d47459fc1d9a774df..12d8fbfd0e41e6bb0f748f63f8127cba5f3295e3 100644 (file)
@@ -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 {