Prevent Zig from emitting function prologues for noreturn functions?

I’m working on a project that involves writing some embedded RISC-V code in Zig. I’ve got this code snippet:

export fn main() noreturn {
    const x = fib(15);
    asm volatile ("ecall"
        :
        : [arg0] "{s1}" (x),
    );
    unreachable;
}

fn fib(x: u32) u32 {
    if (x <= 2) {
        return 1;
    }
    return fib(x - 1) +% fib(x - 2);
}

If I compile this with -O small -target riscv32-freestanding-none (Godbolt link), the main function produces this assembly code:

example.main:
  addi sp, sp, -16
  sw ra, 12(sp)
  sw s1, 8(sp)
  li a0, 15
  call example.fib
  mv s1, a0
  ecall

Is there a way I can get Zig not to generate the addi and sw instructions that push ra and s1 onto the stack? Since this function is noreturn, we never pop them off the stack, so it seems like we’re just wasting 16 bytes of memory. (In today’s economy, that’s like five bucks’ worth!) Maybe this is useful for certain debuggers or control-flow integrity features or something, but I’m not using any of those.

callconv(.naked) generates the kind of code I want, but you’re not allowed to call other runtime functions from inside a callconv(.naked) function.

I think this problem might be common to languages that use LLVM for backend optimizations. Currently, Zig’s self-hosted backend hasn’t delved deeply into optimizations yet.

For C++, even a [[noreturn]] function could still throw an exception. So I guess LLVM is powerless when it comes to removing the prologue; maybe you can ask in the LLVM-related community.

This might be one area where a self-hosted backend could gain an advantage in the future, but considering that current zig program optimizations still rely on LLVM, there might not be a good solution right now.