I put the original mail in a document on google docs. I didn't bother
answering individually to each mail so here are my answers.
Handlers and runtime context (RC):
_Size_
At first I wanted to avoid duplication between the different RCs but
let's see how much memory would really be used if things got duplicated.
Let's assume for now that the RC might contain the global object
reference, the root object reference, the heap pointer and all the
handlers. Maxime talked of ~100 handlers so I'll take her word for it.
So the size of the RC object would be ~400 or ~800 bytes depending on
the platform (the 3 references becomes negligible). Given something like
100 instances of tachyon (1 per browser tab for a super power user) the
memory used for that would be ~40 or ~80 kB.
I don't know how many "objects from the low-level Javascript library"
Maxime was referring to but as long as it is 10-100 references, it still
seems reasonable. However, we should not plan on supporting something
like 100 000 user-level threads, each with their own RC, using this scheme.
_Switching_
If I understand the problem correctly, different user-level instances of
tachyon should never be able to directly call functions or access
objects from one another. So the two cases that might require a context
change are between the VM and a user-level instance (such as
saving/resuming the execution of an instance) or between a user-level
instance and the VM (such as calling the compiler with eval).
In the first case, the VM should have access to all instance contexts to
manage them so I guess the context changes will be manually coded where
we need them.
In the second case, I see two ways of handling the problem. My first
idea is that we might identify which calls might result in a context
switch (like eval) and add the necessary code on the caller side. My
second idea is that we might use an "expected context" on the callee side.
Each compiled function would have an expected context that would be
associated with it at linking time. An expected context is the
reference to a runtime context object. The expected context would be
checked at the function entry. Three cases might happen:
1. The current context is the same as the expected context. Nothing
would be done.
2. The current context is different from the expected context. The
current context is saved on the stack, and replaced by the expected
context. The current context would be restored when returning from the
function.
3. The expected context is a special "identity value" which would make
the function behave as in (1), whatever context is currently active.
This would allow standard library functions to work with both the
compiler and the user-level code without having to duplicate them (as in
a statically linked context).
The runtime overhead would be 1 check at each function entry and 1 check
at each return point. The space overhead would be 1 reference in each
stack frame where there is a context change. Once the context has been
changed, subsequent calls to functions expecting the same context would
not incur an additional space overhead.
_Interesting Configurations_
I initially decided to separate the different conceptual elements in
case some other interesting combinations could be found along the way.
However, it doesn't mean that all combinations are interesting. As Marc
pointed out, having only handlers accessible from registers might not be
worth it. So here is a listing of which combinations I think might be
worth trying. I call a unique combination, a scenario.
As a reminder the different elements are:
-Stack Pointer (SP)
-Heap Pointer (HP)
-Runtime Context (RC)
-Handlers (Hdlr)
-Argument Nb (ArgNb)
-Return Address (RA)
-Implicit Args (ImpArg)
-Explicit Args (ExpArg)
REG is any of X86_REG or X64_REG. Non-specified items are passed on the
stack or accessible from memory as globals. ExpArg(k) means first k args
of ExpArg.
Worst-Case
1. Only the stack pointer (SP) in a register. Everything else passed on
the stack or accessible globally. Used as a base reference to calculate
the speedup achieved by passing things in registers.
Maximize of function arguments passing
2. SP in xSP,
ImpArg and ExpArg(5) in registers;
Predominance to runtime context accesses
3. SP in xSP,
RC in REG,
HP, Hdlr, ArgNb accessible from RC,
ImpArg and ExpArg(4) in registers;
Predominance to object allocation
4. SP in xBP,
HP in xSP,
ImpArg and ExpArg(4) in registers;
RC + Object allocation
5. SP in xBP,
HP in xSP,
RC in Reg,
HP, Hdlr, ArgNb accessible from RC,
ImpArg and ExpArg(3) in registers;
RC + Return Address
6. SP in xSP,
RA in Reg,
RC in Reg,
HP, Hdlr, ArgNb accessible from RC,
ImpArg and ExpArg(3) in registers;
Most of runtime elements
7. SP in xBP,
HP in xSP,
RA in Reg,
RC in Reg,
HP, Hdlr, ArgNb accessible from RC,
ImpArg and ExpArg(2) in registers;
In each scenario, the total number of registers reserved for register
allocation is always ImpArg + ExpArg(k).
Further things to try would be:
- Passing the ImpArg on the stack;
- Making RA and maybe some other registers callee-save to liberate them
for register allocation, possibly selectively for functions with certain
properties (like more than n temporaries).
I would suggest doing this once we figured which scenario works best.
I am not settled yet on how the code generation will be parametrized
since non-trivial changes to handlers might be required for the
different configurations to work. I'll see when we agree on a number of
interesting scenarios.
Erick