I am trying to first clarify what elements are necessary for program execution and which of those might be worth putting in registers. I call register usage configuration which of those elements are put in a register and which are located in memory. I am trying to anticipate the different configurations we might want to try to see what is worth being made configurable. Feel free to add anything you think should be included and any ideas of optimizations that might have an impact on register usage.
__Definitions__
X86_REG means any of EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP X64_REG means any of RAX, RBX, RCX, RDX, RSI, RDI, RBP, RSP, R8-R15
REG means any one of X86_REG or X64_REG (depending on the platform) excluding those previously used for a special purpose
MEM means an emplacement in memory either accessible from the context or the stack.
GLO means an emplacement in memory determined at linking time (like a global variable) which can be seen as static from the runtime code.
Here are the elements we might want to put in a register or not. Possible configurations are given between brackets separated by commas.
__Runtime elements and possible configurations__
Stack Pointer (xSP, xBP): Mark suggested using xBP to possibly use xSP for object allocation and I am also including xSP in case we might want to keep it in the standard location ( Would it simplify a future Foreign Function Interface?).
Heap Pointer (xSP, GLO): Pointer to the remaining free space on the heap when using a compacting GC, like stop© or to the beginning of the free list when using a non-compacting GC like mark&sweep. If not in a register, I would suggest having it as a global variable since it is common to all Runtime Contexts.
Runtime Context (REG, GLO): Contains all the information specific to a given instance of tachyon. An instance might be a browser tab/window or the compiler itself. The runtime context is an object in memory containing minimally the global object and the root object (Object.prototype). The runtime context object might contain handlers references when accessed from a register to preserve a register for other usage.
Handlers (REG, GLO): Contains references to general handlers to perform the general but less common case for various operations such as additions, multiplication, etc. or for expensive but less common operations such as exception handling. When accessed from a register, the handlers references might be put at the end of the runtime context object.
Argument Nb (REG, MEM): The number of arguments given by the caller of a function. When in a register, can be combined with the Runtime Context to save a register, given that the context object are aligned to a given value, liberating the lower bits of the Runtime Context register. When in memory, it will be saved on the stack as part of the function call protocol.
Return Address (REG, MEM): Return address of the currently executing function. Whether in memory or in a register, it will be saved, restored or set as part of the function call protocol.
Implicit Function Arguments (2 REG, REG + MEM, MEM): Arguments to a function not explicitly part of the function definition (not given between the brackets), such as the function reference (when arguments.callee is used or when calling a closure) and the this object, which can either be any object or the global object. When passed in registers, each implicit argument use a different register. When passed in memory, they are passed on the stack. In the two cases, this is part of the function call protocol. I separated them from the explicit function arguments because it might happen that most functions in a real world application are not used in an object-oriented style or as closures so it would liberate two registers for other arguments. If the distinction turns out not to be really useful, we might simply consider all of the arguments as explicit function arguments in the same order as given in the current IR.
Explicit Function Arguments (k REG + (n-k) MEM): Arguments to a function explicitly part of the function definition (given between the brackets). k are the first k arguments given to a function call from left to right and n is the total number of arguments given to that function call. k is a register usage configuration parameter that applies to the whole program. n is the specific number of arguments at a given call site. The first k arguments are passed in registers and the remaining (n-k) arguments are passed on the stack. The parameter k is part of the function call protocol.
That's it!
As a complementary discussion, so far I have assumed that every register was caller-save. Do you guys think that it might be worth reserving some registers to be callee-save? In that case, when those should be spilled, at the entry of a function or at the site of usage?
Erick