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
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;
};
Go version
go version go1.23.0 linux/amd64
Output of
go envin your module/workspace: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
main.go
int128.h
int128.c
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.
Look how C and Go both have a different conception of that C struct:
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:
Correct behavior can be obtained by making the C padding explicit and visible to Go: