Yes it is better for the callee to construct "arguments" because the compiler can tell with a simple analysis when the callee does not need it.
In your example code for a call:
funcObj = arg 0 this = arg 1 param1 = arg 2 param2 = arg 3
what is funcObj? Do you mean that A.B(C,D) is compiled to
param_this = A param_func = property B of param_this param_1 = C param_2 = D call param_func, param_this, param_1, param_2
Note that all the "param_XXX" are locations defined by the calling convention (for example, machine registers in the calling convention which is likely to be the fastest). There's also a hidden location for the return address (say "param_ret") which will likely be a machine register in the fastest calling convention. There's also a location, say "param_count", for the number of explicit parameters that are passed. Note that param_func is only needed in functions that are closures, to access the free variables of the closure, so it can be eliminated from the calling convention (the callee can reconstruct it).
Here are some ideas for the x86 machine code to generate, which is along the lines of what I said in our last meetings. When calling a function f with N actual parameters, the code in the caller should look like what follows (for simplicity I ignore stack overflow checks and all calls are treated like obj.f(arg1,...) and in the case of a call like f(arg1,...) where f is a global, we simply do as if "obj" was the global object). For this calling convention the assignment of registers could be:
stack_ptr is in %EBP param_this is in %ESI param_ret is in %EDI param_count is in %CL (with the encoding N-1 for N actual parameters) param_1 is in %EAX param_2 is in %EBX param_3 is in %EDX
So the call obj.f(arg1,arg2), i.e. N=2 actual parameters, translates to:
mov arg1,%EAX mov arg2,%EBX mov obj,%ESI sub $32,%EBP ;; offset depends on frame size mov $return_point,%EDI mov $N-1,%CL ;; optimization: no instruction when N=1 because %CL is already 0 jmp f_entry_point ;; depends on what the compiler knows about f
.data ...frame descriptor for GC... return_point: ...
And the function f(x,y,z), i.e. with M=3 formal parameters, has this code:
f_entry_point: sub $M-1,%CL jnz f_fix_params
f_correct_params: ... function body ...
f_fix_params: add $1,%CL jz f_undefined_2 add $1,%CL jz f_undefined_1 add $1,%CL jz f_undefined_0 sub $M,%CL call pop_params_handler ;; move %EBP to discard the last %CL parameters jmp f_correct_params
f_undefined_0: mov $undefined,%EAX f_undefined_1: mov $undefined,%EBX f_undefined_2: mov $undefined,%EDX jmp f_correct_params
The "jmp f_entry_point" instruction which transfers control to function f in the caller is tricky because it greatly depends on what the compiler knows about "f". Here are the cases from most knowledge about f to least knowledge about f.
1) If f is statically known at the call site, then a direct jump to f_correct_params (or f_undefined_0, or f_undefined_1, ...) can be done because the values of N and M are known at the call site. The parameter count does not have to be stored in %CL and checked.
2) If f is a property of the global object, then the jump instruction in f's caller can be
jmp f_as_function
where f_as_function is the address just after f's binding in the global object. The global object is composed of both machine code and cells that contain the value of each global property. For each property there is a cell for the value and a jump instruction. So for "f" there would be:
f_as_variable: .word f_entry_point f_as_function: jmp f_entry_point
When f is assigned a new value, the new value is put in the cell "f_as_variable" and the code "jmp f_entry_point" is replaced with "call unknown_binding_handler". The handler "unknown_binding_handler", which is called at the next call to f, is responsible for fetching the value of f, checking that it is a function, and if it is, replacing the "call unknown_binding_handler" by "jmp f_entry_point". That way, in the common case that f is a function and only bound once, the handler will only be called once. Note that initially the "as_function" part contains a "call unknown_binding_handler".
3) Otherwise a property lookup of property f must be performed on the object in %ESI and control must be transferred to the value if it is a function. This can be done by calling a handler. So the code would simply be
call prop_lookup_and_call_handler .data "f"
Note that the fact we are looking up "f" is encoded efficiently by having the name of "f" (or a pointer to a unique symbol or descriptor) immediately after the call of the handler. The handler pops the return address and uses it to access the name of the property.
Marc
On 2010-08-23, at 10:57 AM, Maxime Chevalier-Boisvert wrote:
Good suggestions. I will make the changes this afternoon.
- Maxime
On 10-08-23 10:34 AM, Erick Lavoie wrote:
En y repensant,
je suggèrerais que l'argument objet soit construit par la fonction appelée pour éviter le coût de sa construction dans les cas où il n'est pas utilisé. Puisque l'objet argument serait donc construit et non passé en argument, je suggèrerais une instruction spécifique pour sa construction plutôt que de le récupérer comme un argument explicite, donc quelque chose comme:
t1 = make_arg_obj
et que cette instruction apparaisse toujours après les instructions de récupération des arguments.
Erick
Le 10-08-23 10:02 , Erick Lavoie a écrit :
Salut,
je viens de remarquer que l'ordre de récupération des arguments au début d'une fonction n'est pas le même que celui de passage de ces arguments à l'instruction call. Je suggèrerais de les rendre symétriques. En suivant l'ordre de call, ça donnerait:
funcObj = arg 0 this = arg 1 x1 = arg 2 ... xn = arg n+1 argObj = arg n+2
Si n arguments sont passés à call de la façon suivante:
call [func], [global or obj], x1, ..., xn
Erick
Tachyon-list mailing list Tachyon-list@iro.umontreal.ca https://webmail.iro.umontreal.ca/mailman/listinfo/tachyon-list