Stack based languages can be converted into register based languages with register allocation. Register allocation can be solved with graph coloring, which is fairly advanced. However, if performance is not critical, then it can be done with a simpler algorithm. I think based on comments that others have left that in the case of Rosetta 2, it merely simulates a stack with registers. However, I think that what you are actually wondering is about how this can be done in the more general case. In a stack based language, the program is almost a linear substructure, excluding loads and stores. Thus a temporary should be dead once it is used, unless it is loaded or stored. When a substructure is linear and the number of pushs and pops of each instruction is known ahead of time, then one can simply keep a list (or set and stack) of live numbered temporaries and create a new temporary for each result and use the most recent temporaries to pass into instructions. Unfortunately, real world programs contain control flow, which means the number of pushs and pops may not be known at compile time. To handle control flow, one might want to consider simulating a stack.
I don't know much about stack based assembly instructions; however, I have tried working with converting an intermediate representation both to 3 address code and back to 4 address code, when working on a hobby compiler, where performance is not critical. I hope everything I have said is accurate and clear.
bl
instructions that presumably call functions in the runtime to emulate the x87 instructions. I found it tedious to trace into it (for some reason the program dies in lldb if you try to single-step or continue after a breakpoint) but I strongly suspect the virtual x87 registers are just kept in memory, so that the x87 stack pointer is just a pointer and can be moved with regular arithmetic. $\endgroup$