Inline function in switch produces undefined behavior


pub fn doSyscall() void {
    const proc = Process.currentOrPanic();
    const syscall_num = proc.trapframe.?.a7;

    lib.printAndDec("syscall_num: ", syscall_num);

    switch (syscall_num) {
        SYSCALL_EXIT => exitSys(proc),
        SYSCALL_EXEC => execSys(proc),
        SYSCALL_OPEN => openSys(proc),
        else => {
            lib.printAndInt("address: ", proc.trapframe.?.epc);
            lib.kpanic("Unknown syscall");
        },
    }
}

fn execSys(proc: *Process) void {
    const S = struct {
        var path_buff: [MAX_PATH]u8 = undefined;
    };
    const path_user_address = proc.trapframe.?.a0;
    proc.pagetable.?.copyFrom(path_user_address, @ptrCast(&S.path_buff), MAX_PATH) catch |e| {
        lib.printErr(e);
        lib.kpanic("Failed to copy path from user to kernel");
    };

    exec(@ptrCast(&S.path_buff)) catch |e| {
        lib.printErr(e);
        lib.kpanic("Failed to exec /init");
    };
}

fn exitSys(proc: *Process) void {
    const status = proc.trapframe.?.a0;
    proc.exit(@intCast(status));
}

fn openSys(proc: *Process) void {
    const S = struct {
        var path_buff: [MAX_PATH]u8 = undefined;
    };

    const path_user_address = proc.trapframe.?.a0;
    proc.pagetable.?.copyFrom(path_user_address, @ptrCast(&S.path_buff), MAX_PATH) catch |e| {
        lib.printErr(e);
        lib.kpanic("Failed to copy path from user to kernel");
    };
    const mode = proc.trapframe.?.a1;
    if (mode & File.O_CREATE != 0) {}
}

Had a random store page fault and after spending an hour debugging I changed the functions in the switch from inline to regular and It worked as expected. Is this documented anywhere?

Can you post the inlined version you were using? I’m only seeing the outlined version in your example. It would help to have both so we can take a look at it.

The only switch in your example I see is this one:

    switch (syscall_num) {
        SYSCALL_EXIT => exitSys(proc),
        SYSCALL_EXEC => execSys(proc),
        SYSCALL_OPEN => openSys(proc),
        else => {
            lib.printAndInt("address: ", proc.trapframe.?.epc);
            lib.kpanic("Unknown syscall");
        },

I meant the functions where inline fn not fn

Ah, sorry - I read your post wrong. Were you using the @call syntax to inline or just marking the function as inline directly? It still helps to see a “before and after” to make sure we’re talking about the same features here. If this is a bug, then if the compiler decided to inline something, it could cause it quietly without knowing why.

I’m going to ask some “did you try plugging it in” questions here just to make sure we’re on the same page. I see that in two places you are directly using ? syntax to unpack an optional. If you’re in ReleaseFast, unreachable statements can get stripped out of your code and secretly hide null access.

What optimizations and build settings are you using? After that, the thing I’d check is if you’re actually getting values back or if they are in fact null. In this case, I’d use orelse or an if (x) |y| instead of directly accessing them to create more consistent behavior between optimization levels. Can you try that and let me know how that goes? I can’t imagine that inline would have something to do with that, but I’m trying to rule out some obvious pain points.

inline fn exitSys(proc: *Process)

and it was being called as it is in the first example. running currently in debug output. the only change was removing the inline keyword from the function. i can dig a bit deeper into the bug. it was a storage page fault in a completely un related section of the code. essentially for some reason the stack pointer got set incorrectly and it tried to write data into a guard page