I've been tinkering with Javascript and x86_64 assembly code and in doing so, I've tried to achieve a syntax in Javascript as close as possible to the AT&T syntax so that writting assembly code feels almost like inline assembly with gcc.
I have not extensively surveyed all instructions yet but here is an example of what it might look like. The javascript notation and the corresponding assembly code in AT&T syntax are shown side-by-side:
var a = new x86_Assembler(); a. add ($(-8), ESP). // addl $-8,%esp maintain 16 byte alignment for Mach!: push (EAX). // push %eax mov (_16(ESP), EAX). // movl 16(%esp),%eax get pointer to handlers call (_12(EAX)). // call *12(%eax) call handlers[1], printf add ($(12), ESP). // addl $12,%esp maintain 16 byte alignment
add ($(-4), ESP). // addl $-4,%esp maintain 16 byte alignment push ($(11)). // push $11 dup (). // MACRO: duplicate top of stack mov (_16(ESP), EAX). // movl 16(%esp),%eax get pointer to handlers call (_16(EAX)). // call *16(%eax) call handlers[2], add add ($(12), ESP). // addl $12,%esp maintain 16 byte alignment
ret ();
The example above covers usage of immediate values, registers, memory accesses on 0,1 and 2 operands instructions. Notice how by having each instruction function return the "this" object, all instructions functions can be chained together. Also notice that "$", "_", "_12" and "_16" are functions which return either and immediate, or a memory access object. Finally, "ESP" and "EAX" are register objects.
One interesting thing to note is the possibility to use "macros" of assembly instructions such as shown by the usage of "dup". Such macros could be installed on the "a" object for a one shot usage, therefore being limited to the scope of the "a" variable, without polluting its prototype. A definition of dup could be:
a.dup = function () { return this. mov (_(ESP), EAX). // movl (%esp), %eax get top of stack value add ($(-4), ESP). // addl $-4, %esp add space on stack for the value mov (EAX, _(ESP)); // movl %eax, (%esp) copy value on top of stack }
Other more useful macros could be promoted to the prototype object.
To achieve that kind of syntax without polluting the global object we could use the following idiom:
function () // A function or method on an object, or an anonymous function { // used as a local namespace
// Definitions of used methods and objects const $ = x86_Assembler.prototype.$; const ESP = x86_Assembler.prototype.register.ESP; const EAX = x86_Assembler.prototype.register.EAX; const _ = x86_Assembler.prototype.memory._; const _12 = x86_Assembler.prototype.memory._12; const _16 = x86_Assembler.prototype.memory._16;
var a = new x86_Assembler(); // Can be defined here or outside the function
// Macro definitions such as "dup"
// Some assembly code here
}; // When used as a namespace, it should be called right away I will survey more extensively other instruction usages to see if there are corner cases.
Erick
Afficher les réponses par date
I'm really hoping we'll be manually writing the least amount of x86-specific code as possible. I think you need to be careful and try to keep the JavaScript code intuitive and "safe".
I'm not sure it's a good idea to define names like $, _, _16, ESP, EAX etc. as global values. Those names are very short and not very distinctive. We probably want something like x86Reg.EAX, or a.regs.EAX (in the assembler object), etc.
Besides that, returning the "this" in each instruction function is a good idea. This is what is done in C++ stream objects (e.g.: iostream).
- Maxime
I've been tinkering with Javascript and x86_64 assembly code and in doing so, I've tried to achieve a syntax in Javascript as close as possible to the AT&T syntax so that writting assembly code feels almost like inline assembly with gcc.
I have not extensively surveyed all instructions yet but here is an example of what it might look like. The javascript notation and the corresponding assembly code in AT&T syntax are shown side-by-side:
var a = new x86_Assembler(); a. add ($(-8), ESP). // addl $-8,%esp maintain 16 byte alignment for Mach!: push (EAX). // push %eax mov (_16(ESP), EAX). // movl 16(%esp),%eax get pointer to handlers call (_12(EAX)). // call *12(%eax) call handlers[1], printf add ($(12), ESP). // addl $12,%esp maintain 16 byte alignment
add ($(-4), ESP). // addl $-4,%esp maintain 16 byte alignment push ($(11)). // push $11 dup (). // MACRO: duplicate top of stack mov (_16(ESP), EAX). // movl 16(%esp),%eax get pointer to handlers call (_16(EAX)). // call *16(%eax) call handlers[2], add add ($(12), ESP). // addl $12,%esp maintain 16 byte alignment
ret ();
The example above covers usage of immediate values, registers, memory accesses on 0,1 and 2 operands instructions. Notice how by having each instruction function return the "this" object, all instructions functions can be chained together. Also notice that "$", "_", "_12" and "_16" are functions which return either and immediate, or a memory access object. Finally, "ESP" and "EAX" are register objects.
One interesting thing to note is the possibility to use "macros" of assembly instructions such as shown by the usage of "dup". Such macros could be installed on the "a" object for a one shot usage, therefore being limited to the scope of the "a" variable, without polluting its prototype. A definition of dup could be:
a.dup = function () { return this. mov (_(ESP), EAX). // movl (%esp), %eax get top of stack value add ($(-4), ESP). // addl $-4, %esp add space on stack for the value mov (EAX, _(ESP)); // movl %eax, (%esp) copy value on top of stack }
Other more useful macros could be promoted to the prototype object.
To achieve that kind of syntax without polluting the global object we could use the following idiom:
function () // A function or method on an object, or an anonymous function { // used as a local namespace
// Definitions of used methods and objects const $ = x86_Assembler.prototype.$; const ESP = x86_Assembler.prototype.register.ESP; const EAX = x86_Assembler.prototype.register.EAX; const _ = x86_Assembler.prototype.memory._; const _12 = x86_Assembler.prototype.memory._12; const _16 = x86_Assembler.prototype.memory._16; var a = new x86_Assembler(); // Can be defined here or outside the
function
// Macro definitions such as "dup" // Some assembly code here
}; // When used as a namespace, it should be called right away I will survey more extensively other instruction usages to see if there are corner cases.
Erick _______________________________________________ Tachyon-list mailing list Tachyon-list@iro.umontreal.ca https://webmail.iro.umontreal.ca/mailman/listinfo/tachyon-list
On 2010-07-02, at 4:13 PM, chevalma@iro.umontreal.ca wrote:
I'm really hoping we'll be manually writing the least amount of x86-specific code as possible. I think you need to be careful and try to keep the JavaScript code intuitive and "safe".
I'm not sure it's a good idea to define names like $, _, _16, ESP, EAX etc. as global values. Those names are very short and not very distinctive. We probably want something like x86Reg.EAX, or a.regs.EAX (in the assembler object), etc.
I generally agree with Maxime. Although your idea is clever, I think that it is not important to have (almost) *exactly* the same syntax as AT&T. What is important is that it generally has the same structure. So this would be fine:
a.add(a.lit(-8), a.ESP);
and
a.add(a.EAX, a.mem(22, a.ESP, a.EAX, 4));
Also, I'm not sure the *identifier* _16 is generalizable (what if you want an offset of 1749?).
Besides that, returning the "this" in each instruction function is a good idea. This is what is done in C++ stream objects (e.g.: iostream).
I agree here too, so that instructions can be chained.
Marc
I'm progressing fairly well in the IR translation. I now have code generation for closures, and exception handling. I also believe I'm supporting all primitive operators. I added code to Marc's ast pass 1 to transform expressions of the form x += y into x = x + y, to minimize the code generation effort. I'm in the process of implementing code generation for the with statement. After this, I'll be missing the for-in loop and the switch statement, which I hope to complete by Wednesday.
I wanted to run some ideas by you guys:
1) To implement the for-in loop, we will need some way to iterate over the property names of an object. This will require some sort of iterator object, and property iteration instructions at the IR level. I am considering using a simple index integer as an iterator, and having two IR instructions, one to find the next property index, and one to get the property name at the current index. The index would be initialized at 0, and could possibly go to -1 when there are no more properties to be found.
Another possibility is to make the iteration work with an abstract iterator object, with iterator manipulations to get an iterator on an object, get the next property, and check if the iterator is still valid.
2) Many of the IR instruction classes I wrote so far are very resemblant. I am considering writing a function to generate a "generic" instruction constructor using a closure (an instruction class maker function), in order to minimize the amount of code. This way, it would actually be easy to have an instruction class for each arithmetic operator, for example, and the "instanceof" check would still work on instruction classes created this way.
3) I've been brainstorming about inline IR, and how to code handlers to implement things like function calls and arithmetic operators. It seems to me like it's still not 100% clear how to factor these in terms of JavaScript code and inline IR, or even how to structure the lower levels of IR to make them most convenient to use.
I was discussing with Erick, and I was thinking that, for a first version of our compiler, we could write all handlers for function calls, arithmetic operators, etc., in x86 directly. Once we see what kind of code has to actually go in there, it will become clearer how we can refactor this into higher level IR to make it more optimizable/specializable/portable. For a first compiler version, we could write handlers to only support a few simple types, and a mimimal subset of the language in this way.
- Maxime
On 2010-07-12, at 10:44 AM, Maxime Chevalier-Boisvert wrote:
I'm progressing fairly well in the IR translation. I now have code generation for closures, and exception handling. I also believe I'm supporting all primitive operators. I added code to Marc's ast pass 1 to transform expressions of the form x += y into x = x + y, to minimize the code generation effort.
Be careful... that transformation is not valid, i.e.
obj[i++] += 1; is not equivalent to obj[i++] = obj[i++] + 1
And because JavaScript lacks C++ references and pointers, there is no other way to express x += y in general (for arbitrary x and y).
However, I think it is valid to transform ++x into x += 1 and x++ into (x += 1) - 1 . Similar for decrement.
I'm in the process of implementing code generation for the with statement. After this, I'll be missing the for-in loop and the switch statement, which I hope to complete by Wednesday.
I wanted to run some ideas by you guys:
- To implement the for-in loop, we will need some way to iterate over
the property names of an object. This will require some sort of iterator object, and property iteration instructions at the IR level. I am considering using a simple index integer as an iterator, and having two IR instructions, one to find the next property index, and one to get the property name at the current index. The index would be initialized at 0, and could possibly go to -1 when there are no more properties to be found.
Another possibility is to make the iteration work with an abstract iterator object, with iterator manipulations to get an iterator on an object, get the next property, and check if the iterator is still valid.
It is very hard to implement an efficient for-in loop. The reason is that the semantics of JavaScript (if I recall correctly) requires that all the properties of the object *at the moment the for-in loop is started* must be iterated on by the loop. Properties that are added during the loop need not be iterated on. The problem is that it is possible that the layout of the hashtable will change during the loop (due to a GC, a property being added or deleted which causes the hashtable to change size and thus the position of the keys). So... the best I can think of is the conversion of the hashtable into an array at the moment the for-in loop is entered, and then the for-in loop is just a normal for loop iterating over the elements of the array.
While I'm at it, whenever there is a choice, please write loops that count down rather than up, as this requires fewer live variables (less register pressure). So
for (var i=t.length-1; i>=0; i--) { ... t[i] ... }
is better than
for (var i=0; i<t.length; i++) { ... t[i] ... }
if it doesn't matter in which order the elements are processed.
- Many of the IR instruction classes I wrote so far are very
resemblant. I am considering writing a function to generate a "generic" instruction constructor using a closure (an instruction class maker function), in order to minimize the amount of code. This way, it would actually be easy to have an instruction class for each arithmetic operator, for example, and the "instanceof" check would still work on instruction classes created this way.
Seems fine. How will this impact performance?
- I've been brainstorming about inline IR, and how to code handlers to
implement things like function calls and arithmetic operators. It seems to me like it's still not 100% clear how to factor these in terms of JavaScript code and inline IR, or even how to structure the lower levels of IR to make them most convenient to use.
I was discussing with Erick, and I was thinking that, for a first version of our compiler, we could write all handlers for function calls, arithmetic operators, etc., in x86 directly. Once we see what kind of code has to actually go in there, it will become clearer how we can refactor this into higher level IR to make it more optimizable/specializable/portable. For a first compiler version, we could write handlers to only support a few simple types, and a mimimal subset of the language in this way.
That's what I did for the first version of Gambit. I ended up with more than 5000 lines of assembly code... not a good thing.
But I'm OK with the idea. If we use our x86 assembler for this, we can even write abstractions (in JavaScript) for particular patterns of x86 code (in effect a kind of virtual machine layer). For example, we could define that ARG1 will refer to the register that contains the first parameter of a function, so that it is easy to change this without having to rewrite the assembly code.
Marc
Be careful... that transformation is not valid, i.e. obj[i++] += 1;
is not equivalent to obj[i++] = obj[i++] + 1
Oh well, I'll try to find a way to generalize the code generation for assignments.
the semantics of JavaScript (if I recall correctly) requires that
all the properties of the object *at the moment the for-in loop is started* must be iterated on by the loop. [...] the best I can think of is the conversion of the hashtable into an array at the moment the for-in loop is entered, and then the for-in loop is just a normal for loop iterating over the elements of the array.
Then we will probably want an instruction to get an array of available property names, and perhaps later make a special case for arrays.
Seems fine. How will this impact performance?
The new instruction constructors will be closures assigned to globals, eg:
var AddInstr = makeInstr(...);
Where makeInstr is the function that creates the closure in function of the number of parameters desired, the desired instruction mnemonic name, the parent instruction, and other parameters, such as a handler function we might want to associate with this instruction, etc.
The resulting code might be slightly less fast, but it won't be an impossible optimization challenge. I also don't think that instantiating instructions is a performance bottleneck.
That's what I did for the first version of Gambit. I ended up with
more than 5000 lines of assembly code... not a good thing.
I'd like to try and see what it looks like for a small subset of JS (enough to implement fibonacci, quicksort, etc.), just to see what it looks like, and then examine ways of expressing the same handlers in portable MIR/LIR.
- Maxime
On 2010-07-12, at 12:41 PM, Maxime Chevalier-Boisvert wrote:
That's what I did for the first version of Gambit. I ended up with
more than 5000 lines of assembly code... not a good thing.
I'd like to try and see what it looks like for a small subset of JS (enough to implement fibonacci, quicksort, etc.), just to see what it looks like, and then examine ways of expressing the same handlers in portable MIR/LIR.
Sounds good!
Marc