zig icon indicating copy to clipboard operation
zig copied to clipboard

Zig 0.8.0 Regression: callconv(.Interrupt) on x86_64 causes LLVM codegen to fail

Open yakuri354 opened this issue 2 years ago • 2 comments

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.

yakuri354 avatar May 17 '22 07:05 yakuri354

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
};

drew-gpf avatar May 17 '22 23:05 drew-gpf

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.

yakuri354 avatar May 18 '22 11:05 yakuri354

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.

nohenry avatar Jul 12 '23 22:07 nohenry

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.

ethindp avatar Aug 04 '23 21:08 ethindp

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.

sfiedler0 avatar Mar 26 '24 08:03 sfiedler0