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 a scope overflow error. Try increasing your "maxScope" count.
    • #1018 : This is a scope underflow error. It means that you are using an operation (such as OPopScope) while there are not enough scopes in the chain.
    • #1021 : This is an invalid jump address error. This shouldn't occur if you use the Jump API that is presented before.
    • #1023 : This is a stack overflow error. Try increasing the maxStack property of your method.
    • #1024 : This is a stack underflow error. It means that you are using an operation (such as ORet) while there are not enough values on the stack.
    • #1025 : This is an invalid register error. Try increasing the nRegs property of your method.
    • #1030 : This is a stack unbalanced error. 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.

version #10854, modified 2011-08-19 16:33:52 by adnez