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
Afficher les réponses par date
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
Here are the cases from most knowledge about f to least knowledge about f.
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.
If f is a property of the global object, then the jump instruction in f's caller can be
jmp f_as_function
If I understand correctly how it could be implemented, as you generate code for all global properties read, write and update, you reserve a new slot in the global object for each different property name and you resolve the exact address of "f_as_function" at linking time, once you know the exact position of the global object in memory.
However, how do we know the exact address of "f_as_function" on the global object in the presence of possible property deletions?
Suppose:
var foo = function () { return 123; }; // Add 'foo' property on global as a function var bar = function () { return 456; }; // Add 'bar' property on global as a function foo(); // (1) delete foo; // Remove 'foo' property from global bar(); // What is the address of "bar_as_function"?
Also:
var foo = function () { return 42; }; // Second add of 'foo' property on global foo(); // Is "foo_as_function" address the same as (1)?
We might keep the slot of deleted values so that all other slot's addresses stay the same, assuming deletion of global object properties is not sufficiently frequent as to waste a significant amount of space.
While thinking about the global object handling I came accross other issues we need to take into account:
1. What about a nested function whose 'this' property is being used
var obj = {}; obj.foo = function () { function bar () { this.x = 42; }; bar(); }; obj.foo(); // global now has a x property!
2. Or a constructor called without the 'new' operator?
function Constr () { this.y = 42; return this; }; var a = new Constr(); // a has a y property! var b = Constr(); // global now has a y property! var C = Constr; var d = C(); // the constructor might be called with an additional level of // indirection
3. Or a variable in a function?
// Suppose global doesn't have a z property function biz() { z = 42; }; biz(); // global now has a z property
Cases 1, 2 and 3 forces the global object to allow new properties to be added at runtime on the global object. A delete might also occurs in those same cases and the corresponding property on the global object should be deleted. To make things more fun, let's recall that an access to a non-existing property on the global object throws an exception but not an existing property assigned to 'undefined':
// Suppose that global doesn't have a z property print(z); // Uncaught ReferenceError: z is not defined
z = undefined; print(z); // prints undefined
4. What about multiple nesting of objects?
var foo = {}; // Declaration of a foo namespace foo.bar = function () { return 42; }; // module function
Case 4 is a style advocated by Douglas Crockford in "Javascript: The Good Parts" to limit interferences between different "modules" in the absence of a proper module system, therefore I would expect this style to be used more and more by real web applications. This style might limit the usefulness of the original optimization since an object lookup must still be performed on 'foo' to find 'bar'. I think we should try to also optimize multiple levels of lookup in the cases where the structure of intermediate objects is known at compile time and/or stable at runtime.
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"
Runtime optimization of property lookup and method calls on objects in general were not discussed in your previous mail and might be fast enough to accelerate function calls on the global object without having to treat it as a special case. Maybe we should try to implement those first?
Erick