Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmd/cgo: unaligned access warning with zig #62480

Closed
egonelbre opened this issue Sep 6, 2023 · 6 comments
Closed

cmd/cgo: unaligned access warning with zig #62480

egonelbre opened this issue Sep 6, 2023 · 6 comments
Labels
compiler/runtime Issues related to the Go compiler and/or runtime. NeedsFix The path to resolution is known, but the work has not been done.
Milestone

Comments

@egonelbre
Copy link
Contributor

What version of Go are you using (go version)?

$ go version
go version go1.21.0 windows/amd64

Does this issue reproduce with the latest release?

Yes.

What did you do?

This is the smallest repro

package main

/*
#include <stdint.h>

typedef struct Data {
	int32_t a;
	int64_t x;
} Data;
*/
import "C"

//export call
func call(v *C.int, p C.Data) {}

func main() {}

Then using zig 0.11 for CC and compiling for linux/arm:

$ CGO_ENABLED=1 \
   CC="/path/to/zig cc -target arm-linux-gnueabihf" \
   GOARCH=arm \
   GOOS=linux \
   go build -ldflags="-s -w" -buildmode c-shared -o out.so .

_cgo_export.c:29:8: warning: field p1 within '_cgo_argtype' is less aligned than 'Data' (aka 'struct Data')
and is usually due to '_cgo_argtype' being packed, which can lead to unaligned accesses [-Wunaligned-access]
                Data p1;
                     ^
1 warning generated.

Ideally there wouldn't be a warning.

I would guess the same warning will be shown with clang, however the setup for cross-compile is more annoying and I didn't get it to work at the moment.

Debugging

Using go tool cgo it shows that it ends up generating:

CGO_NO_SANITIZE_THREAD
void call(int* v, Data p)
{
	size_t _cgo_ctxt = _cgo_wait_runtime_init_done();
	typedef struct {
		int* p0;
		Data p1;
	} __attribute__((__packed__)) _cgo_argtype;
	static _cgo_argtype _cgo_zero;
	_cgo_argtype _cgo_a = _cgo_zero;
	_cgo_a.p0 = v;
	_cgo_a.p1 = p;
	_cgo_tsan_release();
	crosscall2(_cgoexp_bd29973577f7_call, &_cgo_a, 20, _cgo_ctxt);
	_cgo_tsan_acquire();
	_cgo_release_context(_cgo_ctxt);
}

It seems this warning was added in https://reviews.llvm.org/D116221

As far as I understand the warning is happening due to having __attribute__((__packed__)) and typedef struct Data not having it. After adding __attribute__((__packed__)) to the Data struct the warning disappears.

@gopherbot gopherbot added the compiler/runtime Issues related to the Go compiler and/or runtime. label Sep 6, 2023
@mknyszek
Copy link
Contributor

mknyszek commented Sep 6, 2023

In triage, the issue appears to be that int64_t is 8-byte aligned on linux/arm, but the equivalent Go type (int64) is 4-byte aligned. We agree that cgo should probably be generating a memcpy to fix up the alignment, but that won't inherently fix the fact that the compiler issues a warning about the type. We also all agree there aren't easy answers here.

@mknyszek mknyszek added this to the Backlog milestone Sep 6, 2023
@mknyszek mknyszek added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Sep 6, 2023
@ianlancetaylor
Copy link
Contributor

This generated C code is constructing a Go argument list that is going to be passed to a Go function. The Go argument list must exactly match what the Go function expects to find on the stack. For Go, for linux-arm, a uint64 value is aligned at a four-byte boundary, not an eight-byte boundary. So ``attribute((packed))` is required.

This does mean that the Data field is misaligned compared to what the C compiler expects. However, upon reflection, I don't agree with the compiler warning. I think it is clear that it is OK to write, in C,

typedef struct {
		int* p0;
		int64_t p1;
} __attribute__((__packed__)) _cgo_argtype;

Even if the alignment of int64_t is normally eight bytes, in this case the p1 field will be aligned at a four-byte boundary. The C compiler should handle that correctly. That's what __attribute__((packed)) means.

I don't see why that changes when p1 becomes a struct type that expects an eight-byte alignment.

So it seems to me that this is a bug in how zig handles __attribute__((packed)). Happy to hear other thoughts.

@egonelbre
Copy link
Contributor Author

So, the warning comes from LLVM (not zig directly itself). I suspect the relevant issue is llvm/llvm-project#55520.

So using clang 14+ leads to the same warning https://c.godbolt.org/z/4cP5E9TaE.

As far as I can understand it's an early warning for:

typedef struct Data {
    int32_t a;
    int64_t b;
} Data;

typedef struct {
    int8_t x;
    Data p1;
} __attribute__((__packed__)) _cgo_argtype;

void useData(Data *v) {}

void use(_cgo_argtype *v) {
    useData(&v->p1); // <-- this is an unaligned pointer to Data, however `useData` doesn't necessarily know it's unaligned.
}

The linked issue (llvm/llvm-project#55520 (comment)) mentions:

As long as you don't take the address ("&" etc.) of the misaligned member, the compiler should reliably generate correct code.

So, the cgo generated code seems correct.

Even if the alignment of int64_t is normally eight bytes, in this case the p1 field will be aligned at a four-byte boundary. The C compiler should handle that correctly. That's what attribute((packed)) means.

Yeah, that's what's the confusing thing. I was also under the assumption that the expected alignment for arm is 4. If it's aligned less, like the C example above, then the warning makes more sense.

Maybe it's because, that the compiler doesn't know how aligned it should be -- e.g. maybe some NEON instruction expects higher alignment?


I guess the solution would be to silence that warning (-Wno-unaligned-access) in cgo code?

@ianlancetaylor
Copy link
Contributor

It should be possible for cmd/cgo to write out something like

#pragma GCC diagnostic ignored "-Wunaligned-access"

in the same place where it emits a pragma ignoring -Waddress-of-packed-member.

@gopherbot
Copy link

Change https://go.dev/cl/526915 mentions this issue: cmd/cgo: silence unaligned-access

@gopherbot
Copy link

Change https://go.dev/cl/528935 mentions this issue: cmd/cgo: silence unaligned-access

gopherbot pushed a commit that referenced this issue Sep 18, 2023
Clang 14+ introduced a warning when using mixed packed and unpacked structs.
This can cause problems when taking an address of the unpacked struct, which
may end up having a different alignment than expected.

This is not a problem in cgo, which does not take pointers from the packed
struct.

Updated version of https://go.dev/cl/526915, which includes
"-Wunknown-warning-option" for compilers that do not have the specific flag.

Fixes #62480

Change-Id: I788c6604d0ed5267949f4367f148fa26d2116f51
Reviewed-on: https://go-review.googlesource.com/c/go/+/528935
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Carlos Amedee <carlos@golang.org>
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Than McIntosh <thanm@google.com>
@dmitshur dmitshur added NeedsFix The path to resolution is known, but the work has not been done. and removed NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. labels Sep 18, 2023
@dmitshur dmitshur modified the milestones: Backlog, Go1.22 Sep 18, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compiler/runtime Issues related to the Go compiler and/or runtime. NeedsFix The path to resolution is known, but the work has not been done.
Projects
None yet
Development

No branches or pull requests

5 participants