Skip to content

Compiler Failure when handling tuple pointers cast as anyopaque pointers #22103

@mangoa01

Description

@mangoa01

Zig Version

0.14.0-dev.2340+182cdf74b

Steps to Reproduce and Observed Behavior

When attempting to provide a function with arguments using a tuple, there is an LLMV error which aborts the compilation in some circumstances. The following code shows the behavior:

const std = @import("std");
const meta = std.meta;
const testing = std.testing;

const ActionEntry = union(State) {
    off: *const fn (*Light) void,
    on: *const fn (*Light, usize) void,
    broken: void,
};

const State = enum(u8) {
    off = 0,
    on,
    broken,
};

const action_map = [_]ActionEntry{
    .{ .off = &Light.off_action },
    .{ .on = &Light.on_action },
    .{ .broken = {} },
};

pub fn action(
    current_state: State,
    action_args: *const anyopaque,
) void {
    //std.debug.print("state: {any}, action_args: {any}\n", .{ current_state, action_args });
    switch (current_state) {
        inline else => |s| executeAction(s, action_args),
    }
}

fn executeAction(
    comptime state: State,
    args: *const anyopaque,
) void {
    const action_function = @field(action_map[@intFromEnum(state)], @tagName(state));
    if (@TypeOf(action_function) == void) return;
    const action_args: *const meta.ArgsTuple(@typeInfo(@TypeOf(action_function)).pointer.child) =
        @ptrCast(@alignCast(args));
    //@compileLog(action_function, action_args.*);
    //std.debug.print("action_args: {any}\n", .{action_args.*});
    @call(.auto, action_function, action_args.*);
}

const Light = struct {
    on_count: usize = 0,
    off_count: usize = 0,

    fn off_action(
        light: *Light,
    ) void {
        light.off_count += 1;
        std.debug.print("in off state: {d}\n", .{light.off_count});
    }

    fn on_action(
        light: *Light,
        incr: usize,
    ) void {
        light.on_count += incr;
        std.debug.print("in on state: {d}\n", .{light.on_count});
    }
};

test "action dispatch" {
    var light1: Light = .{};
    const off_action_args: struct { *Light } = .{&light1};
    action(.off, @ptrCast(&off_action_args));
    try testing.expectEqual(@as(usize, 1), light1.off_count);

    const on_action_args: struct { *Light, usize } = .{ &light1, 2 };
    action(.on, @ptrCast(&on_action_args));
    try testing.expectEqual(@as(usize, 2), light1.on_count);

    const broken_action_args: struct { *Light } = .{&light1};
    action(.broken, @ptrCast(&broken_action_args));
    try testing.expectEqual(@as(usize, 1), light1.off_count);
    try testing.expectEqual(@as(usize, 2), light1.on_count);
}

// The previous test yields:

// in off state: 1
// in on state: 2
// All 1 tests passed.

// Uncommenting the following test causes a compilation failure.
// My expectation is that it should be the same since the only
// difference is where the address of the tuple is taken.

//test "action dispatch alternate" {
//    var light1: Light = .{};
//    const off_action_args: *const struct { *Light } = &.{&light1};
//    action(.off, @ptrCast(off_action_args));
//    try testing.expectEqual(@as(usize, 1), light1.off_count);
//
//    const on_action_args: *const struct { *Light, usize } = &.{ &light1, 2 };
//    action(.on, @ptrCast(on_action_args));
//    try testing.expectEqual(@as(usize, 2), light1.on_count);
//}

//Invalid indices for GEP pointer type!
//  %10 = getelementptr inbounds { ptr }, ptr %8, i64 0, i64 0, !dbg !48869
//Invalid indices for GEP pointer type!
//  %16 = getelementptr inbounds { ptr, i64 }, ptr %4, i64 0, i64 0, !dbg !48882
//Invalid indices for GEP pointer type!
//  %17 = getelementptr inbounds { ptr, i64 }, ptr %4, i64 0, i64 1, !dbg !48882
//LLVM ERROR: Broken module found, compilation aborted!

// Uncommenting the following test causes test failures.
// I did _not_ expect this to compile at all since it seems to rely on
// anonymous structure types, which I thought had been removed from the language.

//test "action dispatch other" {
//    var light1: Light = .{};
//    const off_action_args = &.{&light1};
//    action(.off, @ptrCast(off_action_args));
//    try testing.expectEqual(@as(usize, 1), light1.off_count);
//
//    const on_action_args = &.{ &light1, 2 };
//    action(.on, @ptrCast(on_action_args));
//    try testing.expectEqual(@as(usize, 2), light1.on_count);
//}

//in off state: 1              --> from test 1
//in on state: 2               --> from test 1
//in on state: 2               --> from test 1
//in off state: 1              --> from test 2, correct result
//in on state: 140732277796144 --> from test 2, incorrect value as the second argument
//expected 2, found 140732277796144
//2/2 foo.test.action dispatch other...FAIL (TestExpectedEqual)
// /home/andrewm/opt/ziglang/zig-linux-x86_64-0.14.0-dev.2340+182cdf74b/lib/std/testing.zig:97:17: 0x1040ecc in expectEqualInner__anon_259 (test)
//                return error.TestExpectedEqual;
//                ^
// /home/andrewm/working/micromod-zig/projects/bottom_up/litprog/parts/supporting-code/experiments/foo.zig:111:5: 0x10d1b3e in test.action dispatch other (test)
//    try testing.expectEqual(@as(usize, 2), light1.on_count);
//    ^
//1 passed; 0 skipped; 1 failed.

// The output of the following test is the same as the previous one:

//test "action dispatch other" {
//    var light1: Light = .{};
//    const off_action_args = .{&light1};
//    action(.off, @ptrCast(&off_action_args));
//    try testing.expectEqual(@as(usize, 1), light1.off_count);
//
//    const on_action_args = .{ &light1, 2 };
//    action(.on, @ptrCast(&on_action_args));
//    try testing.expectEqual(@as(usize, 2), light1.on_count);
//}

Expected Behavior

Comments in the code indicate the actual output and expectations.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugObserved behavior contradicts documented or intended behavior

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions