--- /dev/null
+// Copyright 2025 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 out_test
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "internal/testenv"
+ "internal/goarch"
+ "os"
+ "path/filepath"
+ "regexp"
+ "strconv"
+ "strings"
+ "testing"
+)
+
+type methodAlign struct {
+ Method string
+ Align int
+}
+
+var wantAligns = map[string]int{
+ "ReturnEmpty": 1,
+ "ReturnOnlyUint8": 1,
+ "ReturnOnlyUint16": 2,
+ "ReturnOnlyUint32": 4,
+ "ReturnOnlyUint64": goarch.PtrSize,
+ "ReturnOnlyInt": goarch.PtrSize,
+ "ReturnOnlyPtr": goarch.PtrSize,
+ "ReturnByteSlice": goarch.PtrSize,
+ "ReturnString": goarch.PtrSize,
+ "InputAndReturnUint8": 1,
+ "MixedTypes": goarch.PtrSize,
+}
+
+// TestAligned tests that the generated _cgo_export.c file has the wanted
+// align attributes for struct types used as arguments or results of
+// //exported functions.
+func TestAligned(t *testing.T) {
+ testenv.MustHaveGoRun(t)
+ testenv.MustHaveCGO(t)
+
+ testdata, err := filepath.Abs("testdata")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ objDir := t.TempDir()
+
+ cmd := testenv.Command(t, testenv.GoToolPath(t), "tool", "cgo",
+ "-objdir", objDir,
+ filepath.Join(testdata, "aligned.go"))
+ cmd.Stderr = new(bytes.Buffer)
+
+ err = cmd.Run()
+ if err != nil {
+ t.Fatalf("%#q: %v\n%s", cmd, err, cmd.Stderr)
+ }
+
+ haveAligns, err := parseAlign(filepath.Join(objDir, "_cgo_export.c"))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Check that we have all the wanted methods
+ if len(haveAligns) != len(wantAligns) {
+ t.Fatalf("have %d methods with aligned, want %d", len(haveAligns), len(wantAligns))
+ }
+
+ for i := range haveAligns {
+ method := haveAligns[i].Method
+ haveAlign := haveAligns[i].Align
+
+ wantAlign, ok := wantAligns[method]
+ if !ok {
+ t.Errorf("method %s: have aligned %d, want missing entry", method, haveAlign)
+ } else if haveAlign != wantAlign {
+ t.Errorf("method %s: have aligned %d, want %d", method, haveAlign, wantAlign)
+ }
+ }
+}
+
+func parseAlign(filename string) ([]methodAlign, error) {
+ file, err := os.Open(filename)
+ if err != nil {
+ return nil, fmt.Errorf("failed to open file: %w", err)
+ }
+ defer file.Close()
+
+ var results []methodAlign
+ scanner := bufio.NewScanner(file)
+
+ // Regex to match function declarations like "struct MethodName_return MethodName("
+ funcRegex := regexp.MustCompile(`^struct\s+(\w+)_return\s+(\w+)\(`)
+ // Regex to match simple function declarations like "GoSlice MethodName("
+ simpleFuncRegex := regexp.MustCompile(`^Go\w+\s+(\w+)\(`)
+ // Regex to match void-returning exported functions like "void ReturnEmpty("
+ voidFuncRegex := regexp.MustCompile(`^void\s+(\w+)\(`)
+ // Regex to match align attributes like "__attribute__((aligned(8)))"
+ alignRegex := regexp.MustCompile(`__attribute__\(\(aligned\((\d+)\)\)\)`)
+
+ var currentMethod string
+
+ for scanner.Scan() {
+ line := strings.TrimSpace(scanner.Text())
+
+ // Check if this line declares a function with struct return type
+ if matches := funcRegex.FindStringSubmatch(line); matches != nil {
+ currentMethod = matches[2] // Extract the method name
+ } else if matches := simpleFuncRegex.FindStringSubmatch(line); matches != nil {
+ // Check if this line declares a function with simple return type (like GoSlice)
+ currentMethod = matches[1] // Extract the method name
+ } else if matches := voidFuncRegex.FindStringSubmatch(line); matches != nil {
+ // Check if this line declares a void-returning function
+ currentMethod = matches[1] // Extract the method name
+ }
+
+ // Check if this line contains align information
+ if alignMatches := alignRegex.FindStringSubmatch(line); alignMatches != nil && currentMethod != "" {
+ alignStr := alignMatches[1]
+ align, err := strconv.Atoi(alignStr)
+ if err != nil {
+ // Skip this entry if we can't parse the align as integer
+ currentMethod = ""
+ continue
+ }
+ results = append(results, methodAlign{
+ Method: currentMethod,
+ Align: align,
+ })
+ currentMethod = "" // Reset for next method
+ }
+ }
+
+ if err := scanner.Err(); err != nil {
+ return nil, fmt.Errorf("error reading file: %w", err)
+ }
+
+ return results, nil
+}
--- /dev/null
+// Copyright 2025 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 main
+
+import "C"
+
+//export ReturnEmpty
+func ReturnEmpty() {
+ return
+}
+
+//export ReturnOnlyUint8
+func ReturnOnlyUint8() (uint8, uint8, uint8) {
+ return 1, 2, 3
+}
+
+//export ReturnOnlyUint16
+func ReturnOnlyUint16() (uint16, uint16, uint16) {
+ return 1, 2, 3
+}
+
+//export ReturnOnlyUint32
+func ReturnOnlyUint32() (uint32, uint32, uint32) {
+ return 1, 2, 3
+}
+
+//export ReturnOnlyUint64
+func ReturnOnlyUint64() (uint64, uint64, uint64) {
+ return 1, 2, 3
+}
+
+//export ReturnOnlyInt
+func ReturnOnlyInt() (int, int, int) {
+ return 1, 2, 3
+}
+
+//export ReturnOnlyPtr
+func ReturnOnlyPtr() (*int, *int, *int) {
+ a, b, c := 1, 2, 3
+ return &a, &b, &c
+}
+
+//export ReturnString
+func ReturnString() string {
+ return "hello"
+}
+
+//export ReturnByteSlice
+func ReturnByteSlice() []byte {
+ return []byte{1, 2, 3}
+}
+
+//export InputAndReturnUint8
+func InputAndReturnUint8(a, b, c uint8) (uint8, uint8, uint8) {
+ return a, b, c
+}
+
+//export MixedTypes
+func MixedTypes(a uint8, b uint16, c uint32, d uint64, e int, f *int) (uint8, uint16, uint32, uint64, int, *int) {
+ return a, b, c, d, e, f
+}
fmt.Fprintf(gotype, "struct {\n")
off := int64(0)
npad := 0
+ // the align is at least 1 (for char)
+ maxAlign := int64(1)
argField := func(typ ast.Expr, namePat string, args ...interface{}) {
name := fmt.Sprintf(namePat, args...)
t := p.cgoType(typ)
noSourceConf.Fprint(gotype, fset, typ)
fmt.Fprintf(gotype, "\n")
off += t.Size
+ // keep track of the maximum alignment among all fields
+ // so that we can align the struct correctly
+ if t.Align > maxAlign {
+ maxAlign = t.Align
+ }
}
if fn.Recv != nil {
argField(fn.Recv.List[0].Type, "recv")
// string.h for memset, and is also robust to C++
// types with constructors. Both GCC and LLVM optimize
// this into just zeroing _cgo_a.
- fmt.Fprintf(fgcc, "\ttypedef %s %v _cgo_argtype;\n", ctype.String(), p.packedAttribute())
+ //
+ // The struct should be aligned to the maximum alignment
+ // of any of its fields. This to avoid alignment
+ // issues.
+ fmt.Fprintf(fgcc, "\ttypedef %s %v __attribute__((aligned(%d))) _cgo_argtype;\n", ctype.String(), p.packedAttribute(), maxAlign)
fmt.Fprintf(fgcc, "\tstatic _cgo_argtype _cgo_zero;\n")
fmt.Fprintf(fgcc, "\t_cgo_argtype _cgo_a = _cgo_zero;\n")
if gccResult != "void" && (len(fntype.Results.List) > 1 || len(fntype.Results.List[0].Names) > 1) {