hxasm : haXe Flash9 Assembler Library
hxASM enables you to program directly in Flash9 assembler and it will compile for your a SWF compatible with Flash Player 9 that can be either directly loaded from memory or saved into a file.
The library can dynamicaly generate a ByteArray representing a SWF file that contains the compiled assembler methods. The code can then be executed by using the flash.display.Loader.loadBytes method.
Installation
In order to install the library, you need to :
- Download and install haXe using the haXe Installer
- On Windows, download and install FlashDevelop 2.0
- Install the haXe FlashDevelop Plugin FlashDevelop Plugin
- Install the
hxASMlibrary that is available through haxelib, by running the following command :
haxelib install hxasm
Usage
First, create a new haXe Project and add the -lib hxasm to the Extra Parameters. Then you can start writing in Flash9 assembler. Here's a small example :
import hxasm.Bytecode; class Test { static var loader : flash.display.Loader; static function main() { // create a new bytecode Context var ctx = new hxasm.Context(); // defines a class called Main ctx.beginClass("Main"); // the type 'int' in Flash9 var tint = ctx.type("int"); // create a member method called 'test' // with 0 arguments and return type 'int' var m = ctx.beginMethod("test",[],tint); // the maximum size of the stack in this method m.maxStack = 1; // write bytecode into the current method ctx.ops([ OInt(666), ORet, ]); // we are done with all the bytecode writing ctx.finalize(); // create an output and write the bytecode to it var o = new hxasm.Output(); hxasm.Writer.write(o,ctx); var swf = o.getBytes(); // load the SWF bytes loader = new flash.display.Loader(); loader.contentLoaderInfo.addEventListener(flash.events.Event.COMPLETE,onLoaded); loader.loadBytes(swf); } // the data has been succesfully loaded public static function onLoaded(e) { // get the Main class var m = loader.contentLoaderInfo.applicationDomain.getDefinition("Main"); // create an instance of it var inst : Dynamic = Type.createInstance(m,[]); // call the 'test' method trace(inst.test()); // this should display '666' } }
Another example that shows good performances is the Fibonnacci recursive calculus. It's defined in haXe as the following method :
static function fib( n : Int ) : Int { if( n <= 1 ) return 1; return fib(n - 1) + fib(n - 2); }
Here's the corresponding Flash9 bytecode :
// fib takes an integer argument and returns an integer var m = ctx.beginMethod("fib",[tint],tint); // we will have up to 3 values on the stack m.maxStack = 3; ctx.ops([ OReg(1), // register 1 = first argument OSmallInt(1), // the integer 1 OJump(JGt,3), // jump 3 bytes if reg1 > 1 OInt(1), ORet, // return 1 ODecrIReg(1), // decrement register 1 OThis, OReg(1), // call this.fib(reg1) with 1 argument OCallProperty(ctx.property("fib"),1), ODecrIReg(1), // decrement register 1 OThis, OReg(1), // call this.fib(reg1) with 1 argument OCallProperty(ctx.property("fib"),1), OOp(OpIAdd), // add the two values ORet, // returns ]);
When timed, fib(35) shows a +30% speedup in assembler versus the AS3/haXe version.
Performing jumps
It's not always easy to count bytes when writing a OJump opcode. There is an API to make it more easy to works with jumps. Here's the modified fib version that uses this API :
var m = ctx.beginMethod("fib",[tint],tint); m.maxStack = 3; ctx.ops([ OReg(1), OSmallInt(1), ]); var j = ctx.jump(JGt); // prepare a jump ctx.ops([ OInt(1), ORet, ]); j(); // patch the jump with current position ctx.ops([ ODecrIReg(1), OThis, OReg(1), OCallProperty(ctx.property("fib"),1), ODecrIReg(1), OThis, OReg(1), OCallProperty(ctx.property("fib"),1), OOp(OpIAdd), ORet, ]);
The ctx.jump method writes a OJump opcode, then returns a function. This function can be called when you reach the place of the jump target.
There's also a ctx.backwardJump that works the following :
var j = ctx.backwardJump(); // .... j(JAlways); // jump to saved position
FAQ
- In order to read an array, first push on the stack the array and the index, then use
OGetProp(ctx.arrayProp) - In order to write into an array, first push on the stack the array, the index and the value, then use
OSetProp(ctx.arrayProp) - I'm getting
VerifyError #1023: this is astack overflowerror. Try increasing themaxStackproperty of your method. - I'm getting
VerifyError #1024: this is astack underflowerror. It means that you are using an operation (such asORet) while there is not enough values on the stack. - I'm getting
VerifyError #1025: this is ainvalid register error. Try increasing thenRegsproperty of your method. - I'm getting
VerifyError #1030: this is astack unbalancederror. It means that two branches of a Jump results in different stack sizes when they join back. All jumps or code leading to a given position should result in the same stack size. - I'm getting
VerifyError #1021: this is aninvalid jump addresserror. This shouldn't occur if you use the Jump API that is presented before.
ASM Reference
A good reference of the Flash9 AVM2 instructions can be found here. The names are not always the same in this reference and in hxASM, but they should be similar.
If you edit hxasm/OpWriter.hx you'll see for each opcode the hex code AVM2 is using.
Here's the list of opcodes defined in hxASM :
OBreakPoint; ONop; OThrow; OGetSuper( v : IName ); OSetSuper( v : IName ); ORegKill( r : Register ); OLabel; OJump( j : JumpStyle, delta : Int ); OSwitch( def : Int, deltas : Array<Int> ); OPushWith; OPopScope; OForIn; OHasNext; ONull; OUndefined; OForEach; OSmallInt( v : Int ); OInt( v : Int ); OTrue; OFalse; ONaN; OPop; ODup; OSwap; OString( v : Index<String> ); OIntRef( v : Index<Int> ); OFloat( v : Index<Float> ); OScope; ONamespace( v : Index<Namespace> ); ONext( r1 : Register, r2 : Register ); OFunction( f : Index<MethodType> ); OCallStack( nargs : Int ); OConstruct( nargs : Int ); OCallMethod( slot : Slot, nargs : Int ); OCallStatic( meth : Index<MethodType>, nargs : Int ); OCallSuper( name : IName, nargs : Int ); OCallProperty( name : IName, nargs : Int ); ORetVoid; ORet; OConstructSuper( nargs : Int ); OConstructProperty( name : IName, nargs : Int ); OCallPropLex( name : IName, nargs : Int ); OCallSuperVoid( name : IName, nargs : Int ); OCallPropVoid( name : IName, nargs : Int ); OObject( nfields : Int ); OArray( nvalues : Int ); ONewBlock; OClassDef( c : Index<ClassDef> ); OCatch( c : Int ); OFindPropStrict( p : IName ); OFindProp( p : IName ); OFindDefinition( d : IName ); OGetLex( p : IName ); OSetProp( p : IName ); OReg( r : Register ); OSetReg( r : Register ); OGetGlobalScope; OGetScope( n : Int ); OGetProp( p : IName ); OInitProp( p : IName ); ODeleteProp( p : IName ); OGetSlot( s : Slot ); OSetSlot( s : Slot ); OToString; OToXml; OToXmlAttr; OToInt; OToUInt; OToNumber; OToBool; OToObject; OCheckIsXml; OCast( t : IName ); OAsAny; OAsString; OAsType( t : IName ); OAsObject; OIncrReg( r : Register ); ODecrReg( r : Register ); OTypeof; OInstanceOf; OIsType( t : IName ); OIncrIReg( r : Register ); ODecrIReg( r : Register ); OThis; OSetThis; ODebugReg( name : Index<String>, r : Register, line : Int ); ODebugLine( line : Int ); ODebugFile( file : Index<String> ); OBreakPointLine( n : Int ); OTimestamp; OOp( op : Operation );
The different possible jumps are :
JNotLt; JNotLte; JNotGt; JNotGte; JAlways; JTrue; JFalse; JEq; JNeq; JLt; JLte; JGt; JGte; JPhysEq; JPhysNeq;
And the different operations are the following :
OpAs; OpNeg; OpIncr; OpDecr; OpNot; OpBitNot; OpAdd; OpSub; OpMul; OpDiv; OpMod; OpShl; OpShr; OpUShr; OpAnd; OpOr; OpXor; OpEq; OpPhysEq; OpLt; OpLte; OpGt; OpGte; OpIs; OpIn; OpIIncr; OpIDecr; OpINeg; OpIAdd; OpISub; OpIMul;
Don't hesitate to ask on the haXe mailing list if you have any question about hxASM usage.
Source Code
hxasm is released under BSD license and the source code repository is available on http://code.google.com/p/hxasm.