I did a compiler for the 6502 a while back (https://cowlark.com/cowgol/; it's a self-hosted strongly typed programming language designed to run on small machines. You can actually run the compiler itself on a 6502, if you're very patient). It definitely has some functionality holes, the biggest of which is the limited stack. Trying to manage a stack frame is awful and basically rules the architecture out for C-like languages.
I don't think that's true.
Implementing C in a way that makes constant use of data in a stack frame is something that happens on machines that don't have many registers, such as PDP11 or 32 bit x86 (8 registers), or even VAX, 32 bit ARM or x86_64 (16 registers). A frame pointer is just a crutch for assembly language programmers so they don't have to manually keep track of constantly changing offsets to stack frame items in an environment where things are constantly being pushed and popped on the stack within a function. A compiler can easily keep track without a frame pointer.
On a machine with more registers -- for example all the RISC machines with 32 registers -- the usual thing is to decrement the stack pointer by 16 or 32 (etc) bytes as the first instruction in a function and save a few registers into that space. At the end of the function those same registers are reloaded from the stack frame and the stack pointer is incremented. In between, you usually don't touch the stack at *all* unless you've got some structs or arrays as local variables in the function. In that case you usually just make a pointer to them by adding an offset to the stack pointer and keep that pointer throughout the function and access them exactly the same as if they were on the heap or anywhere else.
The registers you're saving into the stack frame (callee-save registers) aren't all the registers the function touches. Most of the registers used in a function are just for temporary values that don't need to be preserved across calls to other functions. The saved registers are just to make space to store (typically) some of the arguments of the function and a loop counter or two and not very much else. It's typically 2 or 3 or 4 things.
The 6502 can effectively be used as a machine with *256* registers. As such, you should generate code for it like for a RISC, not like for a 386. Copy a few contiguous Zero Page locations to a stack frame at the start of a function and restore them at the end (both of which it makes sense to do using a helper function) and never touch the stack in between. Obviously you do want to use a couple of the ZP locations as a stack pointer for your C saved registers, not the hardware stack.
The 6502 has SO MANY registers that you can go further than a typical RISC machine and statically allocate different callee-save registers -- and main (non ZP) memory save slots for them -- for each function. If you can do whole-program analysis then you can find all the call chains. Functions A and B only need to have different register (and save space) allocations if there is a call chain from A to B or from B to A. Most programs won't have call chains deep enough to run out of 256 bytes of callee-save registers (or, say, 224 bytes if you reserve 32 bytes for temporary registers shared by all functions). You only need to copy Zero Page locations (and save space) to a real FIFO stack when recursion (or a call via a function pointer) actually happens, which is usually pretty rare.