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