format.abc: ActionScript Bytecode
format.abc is the successor to hxAsm and is included in the File Formats library. This library can dynamically compile Flash9 assembly code into ABC (ActionScript Bytecode) which can be executed by the AVM2 (Flash 9+).
When compiling to SWF, you can execute this code using format.swf to wrap the bytecode, then loading this movie using the flash.display.Loader.loadBytes method.
Usage
First, create a new Haxe Project and include the format library. Then you can start writing in Flash9 assembler. Here's a small example :
import format.abc.Data; import format.swf.Data; class Test { static var loader : flash.display.Loader; static function main() { var ctx = new format.abc.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(667), ORet, ]); // we are done with all the bytecode writing ctx.finalize(); // compile ActionScript bytecode var abcOutput = new haxe.io.BytesOutput(); new format.abc.Writer(abcOutput).write(ctx.getData()); var abcBytes:haxe.io.Bytes = abcOutput.getBytes(); // create a new SWF var swfOutput:haxe.io.BytesOutput = new haxe.io.BytesOutput(); var swfFile:format.swf.SWF = { header: { version : 9, compressed : false, width : 400, height : 300, fps : cast(30, Float), nframes : 1 }, tags: [ TSandBox(25), // Flash9 Sandbox TActionScript3(abcBytes), // ActionScript block TShowFrame // Show frame ] } // write SWF var writer:format.swf.Writer = new format.swf.Writer(swfOutput); writer.write(swfFile); var swfBytes:haxe.io.Bytes = swfOutput.getBytes(); // load locally loader = new flash.display.Loader(); loader.contentLoaderInfo.addEventListener(flash.events.Event.COMPLETE, onLoaded); loader.loadBytes(swfBytes.getData()); } // 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 '667' } }
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 a "VerifyError":
#1017: This is ascope overflowerror. Try increasing your "maxScope" count.#1018: This is ascope underflowerror. It means that you are using an operation (such asOPopScope) while there are not enough scopes in the chain.#1021: This is aninvalid jump addresserror. This shouldn't occur if you use the Jump API that is presented before.#1023: This is astack overflowerror. Try increasing themaxStackproperty of your method.#1024: This is astack underflowerror. It means that you are using an operation (such asORet) while there are not enough values on the stack.#1025: This is aninvalid register error. Try increasing thenRegsproperty of your method.#1030: This is astack unbalancederror. It means that two branches of a Jump result in different stack sizes when they join back. All jumps or code leading to a given position should result in the same stack size.
ASM Reference
A good reference of the Flash9 AVM2 instructions can be found here. The names in this reference are not always the same as in format.abc, but they should be similar. If you edit format/abc/OpWriter.hx you'll see for each opcode the hex code AVM2 is using.
A list of available opcodes, jump styles, and operators can be found in format/abc/Data.hx.
Don't hesitate to ask on the Haxe mailing list if you have any question about format.abc usage.