Skip to content

cmd/cgo: alignment issue with int128 inside of a struct #69086

@mwetterw

Description

@mwetterw

Go version

go version go1.23.0 linux/amd64

Output of go env in your module/workspace:

GO111MODULE=''
GOARCH='amd64'
GOBIN=''
GOCACHE='/home/mwetterw/.cache/go-build'
GOENV='/home/mwetterw/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMODCACHE='/home/mwetterw/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/home/mwetterw/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/lib/go'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/lib/go/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.23.0'
GODEBUG=''
GOTELEMETRY='local'
GOTELEMETRYDIR='/home/mwetterw/.config/go/telemetry'
GCCGO='gccgo'
GOAMD64='v1'
AR='ar'
CC='gcc'
CXX='g++'
CGO_ENABLED='1'
GOMOD='/home/mwetterw/testGo/int128/go.mod'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build3621668412=/tmp/go-build -gno-record-gcc-switches'

What did you do?

From Go, I passed to C a struct containing one int128 (and another field before it so that Go needs to guess the padding between the first member and that int128 member).
From C, I wanted to read what I got from Go.

go.mod

module int128

go 1.23.0

main.go

package main

import (
        "fmt"
        "unsafe"
)

// #include "int128.h"
import "C"

func toC() [16]byte {
        return [16]byte{17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}
}

func main() {
        x := C.get_uint128()
        fmt.Printf("%[1]T --- %[1]v\n", x)

        C.set_uint128([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})
        fmt.Printf("%[1]T --- %[1]v\n", C.get_uint128())

        s := C.get_s128()
        fmt.Printf("\ns128 sz=%d sz_x=%d off_x=%d off_num=%d off_len=%d\n",
                unsafe.Sizeof(s), unsafe.Sizeof(s.x), unsafe.Offsetof(s.x), unsafe.Offsetof(s.num), unsafe.Offsetof(s.len))
        fmt.Printf("%[1]T --- %[1]v\n", s)
        fmt.Printf("%[1]T --- %[1]v\n", s.num)
        fmt.Printf("%[1]T --- %[1]v\n", s.len)

        C.set_s128(C.struct_s128{
                num: toC(),
                len: 56,
        })
        s = C.get_s128()
        fmt.Printf("\n%[1]T --- %[1]v\n", s)
        fmt.Printf("%[1]T --- %[1]v\n", s.num)
        fmt.Printf("%[1]T --- %[1]v\n", s.len)

        C.print_s128(C.struct_s128{
                num: toC(),
                len: 56,
        })
}

int128.h

#pragma once

void set_uint128(unsigned __int128 n);
unsigned __int128 get_uint128();

struct s128 {
    int x;
    unsigned __int128 num;
    unsigned char len;
};

void set_s128(struct s128 n);
struct s128 get_s128();

void print_s128(struct s128 x);

int128.c

#include <stdio.h>  // printf
#include <stddef.h> // offsetof

#include "int128.h"

unsigned __int128 x = ((unsigned __int128)0x0102030405060708ULL << 64)
                                        | 0x090a0b0c0d0e0f10ULL;
struct s128 s = {
    .num = ((unsigned __int128)0x0102030405060708ULL << 64)
                             | 0x090a0b0c0d0e0f10ULL,
    .len = 42
};

void print_s128(struct s128 x)
{
    printf("C(sz=%zu sz_x=%zu off_x=%zu off_num=%zu off_len=%zu) => "
           "%hhu.%hhu.%hhu.%hhu.%hhu.%hhu.%hhu.%hhu.%hhu.%hhu.%hhu.%hhu.%hhu.%hhu.%hhu.%hhu/%hhu\n",
           sizeof(x),
           sizeof(x.x),
           offsetof(struct s128, x),
           offsetof(struct s128, num),
           offsetof(struct s128, len),
           (unsigned char)(x.num >> 120) & 255,
           (unsigned char)(x.num >> 112) & 255,
           (unsigned char)(x.num >> 104) & 255,
           (unsigned char)(x.num >>  96) & 255,
           (unsigned char)(x.num >>  88) & 255,
           (unsigned char)(x.num >>  80) & 255,
           (unsigned char)(x.num >>  72) & 255,
           (unsigned char)(x.num >>  64) & 255,
           (unsigned char)(x.num >>  56) & 255,
           (unsigned char)(x.num >>  48) & 255,
           (unsigned char)(x.num >>  40) & 255,
           (unsigned char)(x.num >>  32) & 255,
           (unsigned char)(x.num >>  24) & 255,
           (unsigned char)(x.num >>  16) & 255,
           (unsigned char)(x.num >>   8) & 255,
           (unsigned char)x.num          & 255,
           x.len);
}

void set_uint128(unsigned __int128 n)
{
    x = n;
}

unsigned __int128 get_uint128()
{
    return x;
}

void set_s128(struct s128 n)
{
    s = n;
}

struct s128 get_s128()
{
    return s;
}

What did you see happen?

The result is corrupted: C doesn't receive the correct int128.
This is because Go seems to not correctly guess the padding for int128 inside of a struct.

main._Ctype___int128unsigned --- [16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1]
main._Ctype___int128unsigned --- [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16]

s128 sz=44 sz_x=4 off_x=0 off_num=12 off_len=28
main._Ctype_struct_s128 --- {0 [0 0 0 0 0 0 0 0] [0 0 0 0 16 15 14 13 12 11 10 9 8 7 6 5] 4 [3 2 1 42 0 0 0 0 0 0 0 0 0 0 0]}
main._Ctype___int128unsigned --- [0 0 0 0 16 15 14 13 12 11 10 9 8 7 6 5]
main._Ctype_uchar --- 4

main._Ctype_struct_s128 --- {0 [0 0 0 0 0 0 0 0] [17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32] 56 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]}
main._Ctype___int128unsigned --- [17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32]
main._Ctype_uchar --- 56
C(sz=48 sz_x=4 off_x=0 off_num=16 off_len=32) => 0.0.0.56.32.31.30.29.28.27.26.25.24.23.22.21/0

Look how C and Go both have a different conception of that C struct:

  • For C, the struct is 48 bytes long, and the int128 field is at offset 16.
  • For Go, the struct is 44 bytes long, and the int128 field is at offset 12.

What did you expect to see?

I expected Go to correctly guess the padding of the C struct, and to receive the correct int128 from C perspective.

C self-aligns the int128: i.e. it is aligned to a multiple of 16, which is its own size.

I would have expected to see this result:

main._Ctype___int128unsigned --- [16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1]
main._Ctype___int128unsigned --- [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16]

s128 sz=48 sz_x=4 off_x=0 off_num=16 off_len=32
main._Ctype_struct_s128 --- {0 [0 0 0 0 0 0 0 0 0 0 0 0] [16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1] 42 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]}
main._Ctype___int128unsigned --- [16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1]
main._Ctype_uchar --- 42

main._Ctype_struct_s128 --- {0 [0 0 0 0 0 0 0 0 0 0 0 0] [17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32] 56 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]}
main._Ctype___int128unsigned --- [17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32]
main._Ctype_uchar --- 56
C(sz=48 sz_x=4 off_x=0 off_num=16 off_len=32) => 32.31.30.29.28.27.26.25.24.23.22.21.20.19.18.17/56

Correct behavior can be obtained by making the C padding explicit and visible to Go:

struct s128 {
  int x;
  unsigned char _pad[12];
  unsigned __int128 num;
  unsigned char len;
};

Metadata

Metadata

Labels

FrozenDueToAgeNeedsFixThe path to resolution is known, but the work has not been done.compiler/runtimeIssues related to the Go compiler and/or runtime.

Type

No type

Projects

Status

Done

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions