Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions cl/array_alignment_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package cl

import (
"testing"

llssa "github.com/xgo-dev/llgo/ssa"
"github.com/xgo-dev/llvm"
)

func TestGoArrayAccessAlignment(t *testing.T) {
const source = `package arrayalignment
type A [2]int64
type N [2]A
type S struct { B byte; V A }
type T struct { B byte; V N }
func Read(p *S) A { return p.V }
func Write(p *S, v A) { p.V = v }
func Address(p *S) *A { return &p.V }
func ReadPtr(p *A) A { return *p }
func WritePtr(p *A, v A) { *p = v }
func ReadNested(p *T) N { return p.V }
func WriteNested(p *T, v N) { p.V = v }
func AddressNested(p *T) *N { return &p.V }
func ReadNestedPtr(p *N) N { return *p }
func WriteNestedPtr(p *N, v N) { *p = v }
`
for _, target := range []llssa.Target{{GOOS: "windows", GOARCH: "386"}, {GOOS: "linux", GOARCH: "386"}, {GOOS: "linux", GOARCH: "amd64"}} {
t.Run(target.GOOS+"/"+target.GOARCH, func(t *testing.T) {
ssaPkg, _, files := buildGoSSAPkg(t, source)
prog := newLLSSAProgForTarget(t, &target)
defer prog.Dispose()
pkg, err := NewPackage(prog, ssaPkg, files)
if err != nil {
t.Fatal(err)
}
defer pkg.Module().Dispose()
want := prog.GoWordSize()
for _, name := range []string{"S", "T"} {
typ := prog.Type(ssaPkg.Pkg.Scope().Lookup(name).Type(), llssa.GoDecl)
if offset, alignment := prog.OffsetOf(typ, 1), prog.AlignOf(typ); offset != uint64(want) || alignment != uint64(want) {
t.Errorf("%s field-offset/struct-alignment = %d/%d, want %d/%d", name, offset, alignment, want, want)
}
}
// Address/AddressNested let field pointers escape to callers. The pointer
// consumers must honor the same alignment even without a visible FieldAddr.
for _, name := range []string{"Read", "Write", "ReadPtr", "WritePtr", "ReadNested", "WriteNested", "ReadNestedPtr", "WriteNestedPtr"} {
t.Run(name, func(t *testing.T) {
fn := mustNamedFunction(t, pkg.Module(), "arrayalignment."+name)
accesses := 0
for block := fn.FirstBasicBlock(); !block.IsNil(); block = llvm.NextBasicBlock(block) {
for inst := block.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
var typ llvm.Type
if !inst.IsALoadInst().IsNil() {
typ = inst.Type()
} else if !inst.IsAStoreInst().IsNil() {
typ = inst.Operand(0).Type()
} else {
continue
}
if typ.TypeKind() != llvm.ArrayTypeKind {
continue
}
accesses++
if inst.Alignment() != want {
t.Errorf("array access must use alignment %d: %s", want, inst.String())
}
}
}
if accesses != 1 {
t.Errorf("got %d array accesses, want 1:\n%s", accesses, fn.String())
}
})
}
if err := llvm.VerifyModule(pkg.Module(), llvm.ReturnStatusAction); err != nil {
t.Fatal(err)
}
obj, err := prog.TargetMachine().EmitToMemoryBuffer(pkg.Module(), llvm.ObjectFile)
if err != nil {
t.Fatal(err)
}
obj.Dispose()
})
}
}
107 changes: 107 additions & 0 deletions ssa/array_alignment_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
//go:build !llgo

package ssa

import (
"go/importer"
"go/token"
"go/types"
"testing"

"github.com/xgo-dev/llvm"
)

func TestGoArrayAccessAlignment(t *testing.T) {
Initialize(InitAll)
markerPkg, err := importer.Default().Import("structs")
if err != nil {
t.Fatal(err)
}
marker := markerPkg.Scope().Lookup("HostLayout").Type()
for _, target := range []Target{
{GOOS: "windows", GOARCH: "386"}, {GOOS: "linux", GOARCH: "386"},
{GOOS: "linux", GOARCH: "arm", GOARM: "5"}, {GOOS: "linux", GOARCH: "arm", GOARM: "7"},
{GOOS: "linux", GOARCH: "amd64"}, {GOOS: "linux", GOARCH: "arm64"},
{GOOS: "js", GOARCH: "wasm", WasmProfile: "j32", LLVMTarget: "wasm32-unknown-emscripten"},
} {
t.Run(target.GOOS+"/"+target.GOARCH+target.GOARM, func(t *testing.T) {
prog := NewProgram(&target)
defer prog.Dispose()
sizes := prog.TypeSizes(types.SizesFor("gc", target.GOARCH))
pkg := prog.NewPackage("example.com/arrayalignment", "arrayalignment")
rawPkg := types.NewPackage("example.com/arrayalignment", "arrayalignment")
field := func(name string, typ types.Type) *types.Var {
return types.NewField(token.NoPos, rawPkg, name, typ, false)
}
named := func(name string, typ types.Type) *types.Named {
return types.NewNamed(types.NewTypeName(token.NoPos, rawPkg, name, nil), typ, nil)
}
intArray := types.NewArray(types.Typ[types.Int64], 2)
defined := named("Defined", intArray)
nativeFields := []*types.Var{field("V", types.Typ[types.Int64])}
native := named("Native", types.NewStruct(nativeFields, nil))
prog.SetTypeCallConv(rawPkg.Path()+".Native", CDecl, nativeFields...)
host := types.NewStruct([]*types.Var{field("_", marker), field("V", types.Typ[types.Int64])}, nil)
_, vector := layoutTestVector()
for _, tc := range []struct {
name string
raw types.Type
want int
}{
{"int64", intArray, int(sizes.Alignof(intArray))},
{"float64", types.NewArray(types.Typ[types.Float64], 2), int(sizes.Alignof(types.Typ[types.Float64]))},
{"complex128", types.NewArray(types.Typ[types.Complex128], 2), int(sizes.Alignof(types.Typ[types.Complex128]))},
{"named", defined, int(sizes.Alignof(defined))},
{"nested", types.NewArray(defined, 2), int(sizes.Alignof(defined))},
{"named_nested", named("Matrix", types.NewArray(defined, 2)), int(sizes.Alignof(defined))},
{"byte", types.NewArray(types.Typ[types.Byte], 3), 1},
{"pointer", types.NewArray(types.NewPointer(types.Typ[types.Byte]), 2), prog.PointerSize()},
{"native", types.NewArray(native, 2), int(prog.AlignOf(prog.Type(native, CDecl)))},
{"host", types.NewArray(host, 2), int(sizes.Alignof(host))},
{"vector", types.NewArray(vector, 2), int(prog.simdAlignments[0])},
} {
for _, viaField := range []bool{false, true} {
name := tc.name + "_pointer"
elem := tc.raw
if viaField {
name = tc.name + "_field"
elem = types.NewStruct([]*types.Var{field("B", types.Typ[types.Byte]), field("V", tc.raw)}, nil)
}
t.Run(name, func(t *testing.T) {
sig := types.NewSignatureType(nil, nil, nil, types.NewTuple(types.NewVar(token.NoPos, nil, "p", types.NewPointer(elem))), types.NewTuple(types.NewVar(token.NoPos, nil, "", tc.raw)), false)
fn := pkg.NewFunc(name, sig, CDecl)
b := fn.MakeBody(1)
defer b.Dispose()
ptr := fn.Param(0)
if viaField {
ptr = b.FieldAddr(ptr, 1)
}
typ := prog.Elem(ptr.Type)
stored := b.Store(ptr, prog.Zero(typ))
loaded := b.Load(ptr)
b.Return(loaded)
b.EndBuild()
for name, inst := range map[string]llvm.Value{"load": loaded.impl, "store": stored.impl} {
if got := inst.Alignment(); got != tc.want {
t.Errorf("%s alignment = %d, want %d", name, got, tc.want)
}
}
})
}
}
if err := llvm.VerifyModule(pkg.Module(), llvm.ReturnStatusAction); err != nil {
t.Fatal(err)
}
if target.GOARCH == "wasm" {
// These standalone functions have not undergone Wasm section
// finalization; validate their memory IR without emitting an object.
return
}
obj, err := prog.TargetMachine().EmitToMemoryBuffer(pkg.Module(), llvm.ObjectFile)
if err != nil {
t.Fatal(err)
}
obj.Dispose()
})
}
}
20 changes: 18 additions & 2 deletions ssa/expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,25 @@ func (v Expr) IsNil() bool {
return v.Type == nil
}

// SetOrdering sets the ordering of the atomic operation.
// SetOrdering sets the ordering of the atomic operation. The caller must
// provide eight-byte alignment for 64-bit atomic loads and stores.
func (v Expr) SetOrdering(ordering AtomicOrdering) Expr {
v.memoryInstruction().SetOrdering(ordering)
inst := v.memoryInstruction()
inst.SetOrdering(ordering)
if ordering != OrderingNotAtomic {
// Load and Store start with the ordinary Go pointer guarantee, which
// can be only four bytes on 32-bit targets. Atomic i64 accesses have
// a stronger alignment contract, independent of that ordinary layout.
var typ llvm.Type
if !inst.IsALoadInst().IsNil() {
typ = inst.Type()
} else if !inst.IsAStoreInst().IsNil() {
typ = inst.Operand(0).Type()
}
if !typ.IsNil() && typ.TypeKind() == llvm.IntegerTypeKind && typ.IntTypeWidth() == 64 && inst.Alignment() < 8 {
inst.SetAlignment(8)
}
}
return v
}

Expand Down
33 changes: 32 additions & 1 deletion ssa/memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,7 @@ func (b Builder) Load(ptr Expr) Expr {
return Expr{b.fromAtomicStorageValue(storageType, stored), telem}
}
stored := llvm.CreateLoad(b.impl, b.Prog.storageType(storageType), ptr.impl)
b.setGoAccessAlignment(stored, storageType)
return Expr{b.fromStorageValue(storageType, stored), telem}
}

Expand All @@ -542,7 +543,37 @@ func (b Builder) Store(ptr, val Expr) Expr {
if b.Prog.needsWidePointerStorage(storageType) {
return Expr{b.impl.CreateStore(b.toAtomicStorageValue(storageType, val.impl), ptr.impl), b.Prog.Void()}
}
return Expr{b.impl.CreateStore(b.toStorageValue(storageType, val.impl), ptr.impl), b.Prog.Void()}
stored := b.impl.CreateStore(b.toStorageValue(storageType, val.impl), ptr.impl)
b.setGoAccessAlignment(stored, storageType)
return Expr{stored, b.Prog.Void()}
}

// On 32-bit Go targets, a pointer to a basic value or an array of basic values
// may refer to a four-byte-aligned struct field even when LLVM gives its storage
// type eight-byte ABI alignment. This includes complex values and nested arrays.
// FieldAddr can escape, so accesses must honor the Go pointer guarantee rather
// than the original allocation's alignment. An address that is more strongly
// aligned at runtime still satisfies this weaker alignment requirement.
func (b Builder) setGoAccessAlignment(inst llvm.Value, typ Type) {
if b.Prog.GoWordSize() != 4 {
return
}
// Arrays inherit their element's alignment. Unwrap named and nested arrays,
// but leave other element types (including native, HostLayout, and SIMD
// structs) with their own storage alignment.
raw := typ.RawType()
for {
array, ok := raw.Underlying().(*types.Array)
if !ok {
break
}
raw = array.Elem()
}
if _, ok := raw.Underlying().(*types.Basic); ok {
if b.Prog.AlignOf(typ) > uint64(b.Prog.GoWordSize()) {
inst.SetAlignment(int(b.Prog.GoWordSize()))
}
}
}

// Advance returns the pointer ptr advanced by offset.
Expand Down
Loading
Loading