zig
zig copied to clipboard
Zig 0.8.0 Regression: callconv(.Interrupt) on x86_64 causes LLVM codegen to fail
Zig Version
0.8.0
Steps to Reproduce
Compile any function that uses callconv(.Interrupt)
with target x86_64-freestanding-none
, for example:
export fn handler(frame: *u8, err: u64) callconv(.Interrupt) void {
_ = frame;
_ = err;
}
The issue reproduced in Godbolt: https://godbolt.org/z/1xM1eG648
Expected Behavior
The same as in zig 0.7.1: successful compilation
Actual Behavior
The compiler crashed with the following message:
broken LLVM module found: Calling convention parameter requires byval
void (i8*, i64)* @handler
This is a bug in the Zig compiler.thread 1 panic:
Unable to dump stack trace: debug info stripped
Compiler returned: 139
All newer zig versions fail with the same error.
This LLVM issue looks related: https://github.com/llvm/llvm-project/issues/46017
However your problem is 2-fold: not only is Zig not properly checking args for an interrupt (and, crucially, saying exactly why these parameters wouldn't work) but your arguments are not correct; the function only takes an interrupt frame struct as the first parameter, which may or may not contain an error code.
As an aside I think this could be done better as it is occasionally useful to modify the instruction pointer from an interrupt routine, but with this calling convention hack I don't see any way of doing that without writing to memory that the compiler thinks is constant.
This compiles on zig trunk, for example:
const IdtFrameNoError = extern struct {
ip: u64,
cs: u64,
flags: u64,
sp: u64,
ss: u64
};
const std = @import("std");
export fn handler(frame: IdtFrameNoError) callconv(.Interrupt) void {
std.debug.panic("panic at instruction 0x{X}", .{frame.ip});
}
Where the only real change is that the struct given is extern
(otherwise zig defines the layout!) and there is only one non-pointer arg.
To access the error code (for interrupts which push one) you need to use a struct like this:
const IdtFrame = extern struct {
ec: u64,
ip: u64,
cs: u64,
flags: u64,
sp: u64,
ss: u64
};
Thank you, looks like the LLVM docs are either outdated or just wrong about this:
/// X86_INTR - x86 hardware interrupt context. Callee may take one or two
/// parameters, where the 1st represents a pointer to hardware context frame
/// and the 2nd represents hardware error code, the presence of the later
/// depends on the interrupt vector taken. Valid for both 32- and 64-bit
/// subtargets.
X86_INTR = 83,
Having a better compiler message would definetely help, although in spite of the immutability requirement I don't quite see any real use cases for this LLVM feature besides serving as a toy/demo.
Also, I think the Zig docs should have an entry about this so others don't run into the same problem.
Seems like this compiles in 0.9.0 but fails in 0.10.0 and trunk https://godbolt.org/z/nG6s6sK4e. The following error is emitted:
LLVM ERROR: unsupported x86 interrupt prototype
Not sure if this is just an issue with LLVM or what.
I'm running into this problem as well. I've tried both the two-argument and one-argument forms for the error code and it doesn't like it.
I'm running into this problem as well. I've tried both the two-argument and one-argument forms for the error code and it doesn't like it.
Me too. Currently I'm writing a kernel in Zig (https://codeberg.org/sfiedler/zig_os) and it would be nice if the Interrupt calling convention would work. If that worked, I could again use the ability of Zig to communicate with LLVM.
The error that is dropped currently:
error: Missing param element type for x86_intrcc upgrade (Producer: 'zig 0.12.0' Reader: 'LLVM 17.0.1')
EDIT: Now I wrote the bare functionality by myself (with a bit of assembly), but calling functions from that interrupt handler is difficult and I can't test it, so it would still be huge if the interrupt calling convention would work.