My first suggestion is to put your notes on Google docs. Your notes seem pretty good so far.
Some notes:
Since the heap pointer is common to all code executing in a given thread, it makes some sense to keep it in a register, as opposed to duplicating it in multiple context objects. On the other hand, on x86-32, we have very few registers already, so it might make sense to have it in some kind of global context or thread-specific context. If we optimize code well enough, we could actually significantly decrease the use of allocations. So, perhaps a good initial option would be to keep it in a register because its easy, and later on, do some profiling to see how much allocations we do, and think about moving it in a global variable.
The context object will probably store quite a few objects from the low-level JavaScript library hierarchy. It might also contain performance counters.
What's not super clear to me is how context switching will be achieved. Tachyon compiler code needs its own global object, so do program instances, but some low-level library code should execute in the context of (using the global object of) the calling code.
I would not differentiate between implicit and explicit call arguments. They are essentially the same thing at a low-level. Our optimization system will take care of specializing functions and eliminating the unnecessary call arguments when possible.
The return address should probably be pushed on the stack. Ideally, short-running functions should be aggressively inlined, and the long-running functions that are left uninlined will only need to read the return address once.
I would keep the call protocol simple for now, so having everything caller-save makes sense.
- Maxime
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
Tachyon-list mailing list Tachyon-list@iro.umontreal.ca https://webmail.iro.umontreal.ca/mailman/listinfo/tachyon-list