#safe to call `callconv(.Unspecified)` function pointer?
1 messages · Page 1 of 1 (latest)
callconv(.Unspecified) is what regular functions are that should be fine
any vtable in zig is relying on that being fine
specifically: i had an untagged union passed, in debug mode, and the union tag inside the function was not the same as what the caller passed it, so i got a panic
k repro time
on this branch, zig build bench --release=safe
then:
$ lldb ./zig-out/bin/bench
(lldb) b tail.zig:37
(lldb) run web-host/public/flappyai.ch8 1000
// it hits the breakpoint...
(lldb) watch set expression &self.code[0x204] // this is the index of self.code that gets written
(lldb) c
// hits the watchpoint
-> 145 cpu.code[cpu.pc] = inst;
(lldb) p inst.decoded
(tail.Decoded) {
payload = {
xy = "\v\r"
xnn = (0 = '\v', 1 = '\r')
nnn = 3339
xyn = "\v\r"
}
tag = xy
}
(lldb) step
-> 146 @call(.always_tail, inst.func, .{ cpu, inst.decoded });
(lldb) step
-> 106 pub fn addRegisters(cpu: *Cpu, decoded: Decoded) void {
(lldb) p decoded
(tail.Decoded) {
payload = {
xy = "\b\xf9"
xnn = (0 = '\b', 1 = '\xf9')
nnn = 63752
xyn = "\b\xf9\U00000003"
}
tag = xnn
}
the tag changed from xy to xnn
is this a miscomp or am i just stupid
trying to zig reduce to see if i can find anything
i wrote a small test passing bare unions through a function pointer and it wasn't obviously broken
To be clear, where is the function being called, and where was it passed from?
Ie, is it being called from C code?
Are there multiple Zig compilation units?
no C code, only one zig unit
caller is https://github.com/190n/zip8/blob/oddity/src/tail.zig#L146
it calls https://github.com/190n/zip8/blob/oddity/src/tailfuncs.zig#L107, although the inactive union field occurs on different functions in that file depending on build options
CHIP-8 emulator in Zig. Contribute to 190n/zip8 development by creating an account on GitHub.
CHIP-8 emulator in Zig. Contribute to 190n/zip8 development by creating an account on GitHub.
ok it also doesn't seem to happen if i don't use tail calls
const std = @import("std");
const SideData = union {
foo: u8,
bar: u8,
};
const Inst = struct {
func: Gadget,
data: SideData,
};
const Emulator = struct {
memory: [128]u8,
code: [64]Inst,
pc: u8 = 0,
};
const Gadget = *const fn (*Emulator, SideData) void;
fn foo(emulator: *Emulator, side_data: SideData) void {
_ = emulator;
std.log.info("foo {}", .{side_data.foo});
}
fn bar(emulator: *Emulator, side_data: SideData) void {
_ = emulator;
_ = side_data;
@trap();
}
fn invalid(emulator: *Emulator, _: SideData) void {
_ = emulator;
}
fn decode(emulator: *Emulator, _: SideData) void {
const raw_instruction = std.mem.readInt(u16, emulator.memory[emulator.pc..][0..2], .big);
const inst: Inst = switch ((raw_instruction >> 8) & 0xf) {
0xf => .{ .func = &foo, .data = .{ .foo = @truncate(raw_instruction) } },
0xb => .{ .func = &bar, .data = .{ .bar = @truncate(raw_instruction) } },
else => .{ .func = &invalid, .data = undefined },
};
@call(.always_tail, inst.func, .{ emulator, inst.data });
}
pub fn main() void {
var emulator = Emulator{
.memory = undefined,
.code = undefined,
};
@memset(&emulator.code, .{ .func = &decode, .data = undefined });
const rom = [_]u8{ 0x0f, 0x23, 0x0b, 0x80 };
@memcpy(emulator.memory[0..rom.len], &rom);
emulator.code[emulator.pc].func(&emulator, emulator.code[emulator.pc].data);
}
smaller example
inside decode, inst.data has tag .foo, but when foo is called the tag is 0x4c
ugh i think i'm hitting https://github.com/ziglang/zig/issues/9703
I've provided a small example here how tailcalling corrupts the arugment, while calling it with never_tail makes it work: https://zig.godbolt.org/z/Erdj97ser const std = @import("std"...
always_tail is unfortunately quite buggy in general