go/types, types2: correct alignment of atomic.Int64

atomic.Int64 has special logic in the compiler to ensure it's 8-byte
aligned on 32-bit architectures. The equivalent logic is missing in
go/types, which means the compiler and go/types can come to different
conclusions about the layout of types.

Fix this by mirroring the compiler's logic into go/types.

Fixes #53884.

Change-Id: I3f58a56babb76634839a161ca174c8f085fe3ba4
Reviewed-on: https://go-review.googlesource.com/c/go/+/417555
Reviewed-by: Robert Findley <rfindley@google.com>
This commit is contained in:
Austin Clements 2022-07-13 15:18:41 -04:00
parent 4651ebf961
commit 2aa473cc54
5 changed files with 128 additions and 2 deletions

View file

@ -169,6 +169,8 @@ func calcStructOffset(errtype *Type, t *Type, o int64, flag int) int64 {
}
// 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 duplicated in go/types and cmd/compile/internal/types2.
if isStruct && t.NumFields() == 0 && t.Sym() != nil && t.Sym().Name == "align64" && isAtomicStdPkg(t.Sym().Pkg) {
maxalign = 8
}

View file

@ -53,6 +53,17 @@ func (s *StdSizes) Alignof(T Type) int64 {
// 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."
@ -93,6 +104,18 @@ func (s *StdSizes) Alignof(T Type) int64 {
return a
}
func isSyncAtomicAlign64(T Type) bool {
named, ok := T.(*Named)
if !ok {
return false
}
obj := named.Obj()
return obj.Name() == "align64" &&
obj.Pkg() != nil &&
(obj.Pkg().Path() == "sync/atomic" ||
obj.Pkg().Path() == "runtime/internal/atomic")
}
func (s *StdSizes) Offsetsof(fields []*Var) []int64 {
offsets := make([]int64, len(fields))
var o int64

View file

@ -14,12 +14,15 @@ import (
// findStructType typechecks src and returns the first struct type encountered.
func findStructType(t *testing.T, src string) *types2.Struct {
return findStructTypeConfig(t, src, &types2.Config{})
}
func findStructTypeConfig(t *testing.T, src string, conf *types2.Config) *types2.Struct {
f, err := parseSrc("x.go", src)
if err != nil {
t.Fatal(err)
}
info := types2.Info{Types: make(map[syntax.Expr]types2.TypeAndValue)}
var conf types2.Config
_, err = conf.Check("x", []*syntax.File{f}, &info)
if err != nil {
t.Fatal(err)
@ -105,3 +108,39 @@ const _ = unsafe.Offsetof(struct{ x int64 }{}.x)
_ = conf.Sizes.Alignof(tv.Type)
}
}
// Issue #53884.
func TestAtomicAlign(t *testing.T) {
const src = `
package main
import "sync/atomic"
var s struct {
x int32
y atomic.Int64
z int64
}
`
want := []int64{0, 8, 16}
for _, arch := range []string{"386", "amd64"} {
t.Run(arch, func(t *testing.T) {
conf := types2.Config{
Importer: defaultImporter(),
Sizes: types2.SizesFor("gc", arch),
}
ts := findStructTypeConfig(t, src, &conf)
var fields []*types2.Var
// Make a copy manually :(
for i := 0; i < ts.NumFields(); i++ {
fields = append(fields, ts.Field(i))
}
offsets := conf.Sizes.Offsetsof(fields)
if offsets[0] != want[0] || offsets[1] != want[1] || offsets[2] != want[2] {
t.Errorf("OffsetsOf(%v) = %v want %v", ts, offsets, want)
}
})
}
}

View file

@ -53,6 +53,17 @@ func (s *StdSizes) Alignof(T Type) int64 {
// 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."
@ -93,6 +104,18 @@ func (s *StdSizes) Alignof(T Type) int64 {
return a
}
func isSyncAtomicAlign64(T Type) bool {
named, ok := T.(*Named)
if !ok {
return false
}
obj := named.Obj()
return obj.Name() == "align64" &&
obj.Pkg() != nil &&
(obj.Pkg().Path() == "sync/atomic" ||
obj.Pkg().Path() == "runtime/internal/atomic")
}
func (s *StdSizes) Offsetsof(fields []*Var) []int64 {
offsets := make([]int64, len(fields))
var o int64

View file

@ -17,13 +17,16 @@ import (
// findStructType typechecks src and returns the first struct type encountered.
func findStructType(t *testing.T, src string) *types.Struct {
return findStructTypeConfig(t, src, &types.Config{})
}
func findStructTypeConfig(t *testing.T, src string, conf *types.Config) *types.Struct {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "x.go", src, 0)
if err != nil {
t.Fatal(err)
}
info := types.Info{Types: make(map[ast.Expr]types.TypeAndValue)}
var conf types.Config
_, err = conf.Check("x", fset, []*ast.File{f}, &info)
if err != nil {
t.Fatal(err)
@ -110,3 +113,39 @@ const _ = unsafe.Offsetof(struct{ x int64 }{}.x)
_ = conf.Sizes.Alignof(tv.Type)
}
}
// Issue #53884.
func TestAtomicAlign(t *testing.T) {
const src = `
package main
import "sync/atomic"
var s struct {
x int32
y atomic.Int64
z int64
}
`
want := []int64{0, 8, 16}
for _, arch := range []string{"386", "amd64"} {
t.Run(arch, func(t *testing.T) {
conf := types.Config{
Importer: importer.Default(),
Sizes: types.SizesFor("gc", arch),
}
ts := findStructTypeConfig(t, src, &conf)
var fields []*types.Var
// Make a copy manually :(
for i := 0; i < ts.NumFields(); i++ {
fields = append(fields, ts.Field(i))
}
offsets := conf.Sizes.Offsetsof(fields)
if offsets[0] != want[0] || offsets[1] != want[1] || offsets[2] != want[2] {
t.Errorf("OffsetsOf(%v) = %v want %v", ts, offsets, want)
}
})
}
}