Utilizing Functions Without the Stack

Hi, I want to use LLVM for code-gen, but I’m having some issues with the stack requirement. LLVM assumes you are using the C-stack for all function calls, but I am implementing my own call-stack.

Is the only option here if I want to use functions (call/ret instructions) to create my own backend? Or is there some way I can handle everything myself on my own stack, and simply tell ret and call exactly where to go manually?

Some languages avoid the C stack by only emitting tail calls, so you never have more than one function on the C stack at a time. Then you can just manage your language’s “stack” separately, however you want. For example, GHC generates LLVM IR like this. The downside is that LLVM optimizations are written to expect C-like code, so optimizations are less effective on IR emitted this way.

Existing backends don’t really have any way to customize the way the stack is used. Maybe you can modify the implementation of segmented stacks. (See Segmented Stacks in LLVM — LLVM 15.0.0git documentation .)

Hey, thanks for the response. I actually am moving away from a tail-call CPS backend into a segmented stacks backend.

I can’t use LLVM’s segmented stacks because I need control over the stacks (to copy and overwrite stack-space as needed). So I was hoping I could just give ret/call some different prologue/epilogue instructions (to access memory instead of the c-stack) without having to completely modify the backend…

LLVM does not support a movable stack; if you need that, it would require substantial code changes. Even if you managed to get the allocation working, there’s a bunch of code that assumes the address of the stack frame is a constant, which would be painful to fix.

If you need each stack frame to be allocated by your runtime, but the address of each frame stays fixed, you might be able to make that work if you’re willing to hack at LLVM a bit. Lowering for call/ret/etc only happens in a couple specific places in each backend. And local variables/spill slots are represented as abstract “frame indexes” until very late in the pipeline.

Have you considered swapping to a clean, new stack before calling LLVM generated code? How would this be different from what you are proposing?

Many architectures have instructions that use the dedicated stack register, so if you want those operations to affect your managed stack memory, it makes sense to load your stack into the stack register and leave the existing prologue/epilogue code as it is.

I am fine with keeping the C-stack around for native-code interop (i.e. runtime functions will be using the C-stack). But for my languages main function-call system, it is using its own stack. I allocate the memory and keep my own global pointers myself.

So this would require a fork of LLVM that I would have to maintain for my project?

I need to copy portions of the stack and then wholly overwrite other portions of the stack as needed, so I am not sure that this would be enough. But I may be misunderstanding… All of my code is LLVM generated, minus the runtime work which is C++ and uses the C-stack.

Yes, that would require a fork. Or you could propose a new feature upstream, but it might be hard to design something that works beyond your exact use-case.