Sunday, January 23, 2011

[Adobe Alchemy Hacks] Compile *.as Source Files Assembly to SWF and SWC

With alchemy, we can compile AS3 source files assembly, which means we can use some AVM2 inline assembly language in our AS3 source file. Here is the code snippet for using inline asm and accessing memory in AS3:

/*
[Adobe Alchemy Hacks]
AlchemyAD_hack.as
{Simple example for using inline asm and 
accessing memory in AS3}
By Bruce Jawn (January/23/2011)
[http://bruce-lab.blogspot.com]

To compile this source file with Alchemy: 
cd /cygdrive/f/alchemy/
java -Xms16M -Xmx196M -jar F:/alchemy/bin/asc.jar -AS3 -strict -import F:/alchemy/flashlibs/global.abc -import F:/alchemy/flashlibs/playerglobal.abc -config Alchemy::Debugger=false -config Alchemy::NoDebugger=true -config Alchemy::Shell=false -config Alchemy::NoShell=true -config Alchemy::LogLevel=0 -config Alchemy::Vector=true -config Alchemy::NoVector=false -config Alchemy::SetjmpAbuse=false -swf AlchemyAD_hack,800,600,60 AlchemyAD_hack.as
*/
package
{    
 import flash.display.Sprite;
 import flash.text.TextField;
 import flash.utils.ByteArray;
 import flash.utils.Endian;
 import flash.system.ApplicationDomain;
 public class AlchemyAD_hack extends Sprite{

public function AlchemyAD_hack () 
{ 
  /*Create the print shell*/
  var MyShell:TextField=new TextField();
  MyShell.height=600;
  addChild(MyShell);
  function print(output:*):void
  {
   MyShell.appendText(String(output));
   MyShell.appendText("\n");
  }
  
  /*Test Memory Write*/
  //ByteArray for the test
  var testData:ByteArray = new ByteArray();
  testData.endian = Endian.LITTLE_ENDIAN;
  testData.length=0xffff*4;
  //select testdata in memory
  ApplicationDomain.currentDomain.domainMemory=testData;
  var AdrInt:int=0;
  //the test value we will write into testData via memory
  var testValue:int=123;
  //write the testValue into testData
  ApplicationDomain.currentDomain.domainMemory[0] = testValue;
  //Check if testValue has been written into testData
  print(testData[0]);//should print 123
  
  /*Test Memory Read*/
  var readedValue:int=ApplicationDomain.currentDomain.domainMemory[0];
  print(readedValue);//should print 123 
  
  /*Test Inline ASM*/
  //label and jump
  __asm(jump, target('myLable'));
  print("not jumped!");//this line will be skipped
  __asm(label, lbl('myLable'));
  print("jumped!"); 
  //switch jump
  var myState:int=1;
  __asm(push(myState), switchjump('state0','state1','state2'));
  __asm(lbl('state0'));
  print("This is state0.");
  __asm(lbl('state1'));
  print("This is state1.");
  __asm(lbl('state2'));
  print("This is state2.");
  //iftrue jump
  var temp:int=1;
  __asm(push(temp!=0), iftrue, target('turejump'));
  print("iftrue not jumped!");//this line will be skipped
  __asm(label, lbl('turejump'));
  print("iftrue jumped!"); 
  
  /*Test Alchemy Memory Instructions*/
  //All memory opcodes listed here:
  /*
  Get a 32 bit value at the location addr and return as an int:
  _mr32(addr:int):int{ return __xasm(push(addr), op(0x37)); }

  Get a 16 bit unsigned value at the location addr and return as an int:. 
  _mru16(addr:int):int { return __xasm(push(addr), op(0x36)); }

  Get a 16 bit signed value at the location addr and return as an int: 
  _mrs16(addr:int):int { return __xasm(push(addr), op(0x36)); } // li16

  Get a 8 bit value at the location addr and return as an int:
  _mru8(addr:int):int { return __xasm(push(addr), op(0x35)); }
  
  Get a 8 bit value at the location addr and return as an int: 
  _mrs8(addr:int):int { return __xasm(push(addr), op(0x35)); }

  Get a float value at the location addr and return as an Number:
  _mrf(addr:int):Number { return __xasm(push(addr), op(0x38)); }

  Get a double value at the location addr and return as an Number:
  _mrd(addr:int):Number { return __xasm(push(addr), op(0x39)); }

  Write an int as a 32 bit value at the location addr: 
  _mw32(addr:int, val:int):void { __asm(push(val), push(addr), op(0x3c)); }

  Write an int as a 16 bit value at the location addr: 
  _mw16(addr:int, val:int):void { __asm(push(val), push(addr), op(0x3b)); }

  Write an int as a 8 bit value at the location addr: 
  _mw8(addr:int, val:int):void { __asm(push(val), push(addr), op(0x3a)); }

  Write a Number as a float at the location addr: 
  _mwf(addr:int, val:Number):void { __asm(push(val), push(addr), op(0x3d)); }

  Write a Number as a double at the location addr:
  _mwd(addr:int, val:Number):void { __asm(push(val), push(addr), op(0x3e)); }
  */
  
  //Write an int 654321 as a 32 bit value at the location 1000
  __asm(push(654321),push(1000),op(0x3c));
  //Trace the memory
  ApplicationDomain.currentDomain.domainMemory.position=1000;
  print(ApplicationDomain.currentDomain.domainMemory.readInt());//should print 654321
  //Get a 32 bit value at the location 1000 and return as an int
  var temp:int=__xasm(push(1000), op(0x37));
  print(temp);//should print 654321

  /*Test some AVM2 Instructions*/
  //More AVM2 Instructions can be found at:
  //http://www.anotherbigidea.com/javaswf/avm2/AVM2Instructions.html
  
  //test add: 0xA0 
  var var1:int=123;
  var var2:int=321;
  //write (var1+var2)=444 to testData[0] via memory
  //ApplicationDomain.currentDomain.domainMemory.position=AdrInt;
  //ApplicationDomain.currentDomain.domainMemory.writeInt(var1+var2);
  __asm(push(var1), push(var2), op(0xA0), push(AdrInt), op(0x3c));
  testData.position=0;
  print(testData.readInt());//should print 444
  
  //test subtract: 0xA1
  var result:int=__xasm(push(var1), push(var2), op(0xA1));//var result=var1-var2;
  print(result);//should print -198
  //write (var1-var2)=-198 to testData[1] via memory in a different way
  __asm(push(result),push(AdrInt+4),op(0x3c));
  testData.position=4;
  print(testData.readInt());//should print -198
  
}//end of function AlchemyAD_hack

}//end of class
}//end of pacakge
/*
References:
http://labs.adobe.com/wiki/index.php/Alchemy:Documentation:Developing_with_Alchemy:AS3_API
http://unitzeroone.com/blog/2009/05/22/another-scream-on-flash-alchemy-memory-and-compilers/
http://blog.frankula.com/?p=211
http://forums.adobe.com/message/2616985
http://forums.adobe.com/message/3001861
Special thanks to Bernd Paradies (http://forums.adobe.com/people/Bernd%20Paradies)
*/
======
Update: 20, Feb., 2011
All AVM2 Opcode names for Alchemy can be found here:(ASC source code)
http://opensource.adobe.com/svn/opensource/flex/sdk/trunk/modules/asc/src/java/macromedia/abc/Opcodes.java
so for example, the following code
var result:int=__xasm(push(var1), push(var2), op(0xA1));
can be also writen as
var result:int=__xasm(push(var1), push(var2), subtract);
======
The output:
123
123
jumped!
This is state2.
iftrue jumped!
654321
654321
444
-198
-198

And when we compile C/C++ code to swf or swc, we can manuly modify the *.as file generated during the compilation process, for optimization and then use Alchemy asc to compile the modified *.as to swf or swc. By default the temp *.as file will be deleted, to get that file, we can simply copy and paste that file before it deleted during the compilation, or modify the "gcc" file in "alchemy\achacks" folder, remove/commentize the last two lines:
# remove junk TODO failure leaves stuff around!
if(!$ENV{ACHACKS_TMPS})
{ sys("rm", "-f", <$$.achacks.*>) }
Now we have the generated *.as files from the compiler, something like "19048.achacks.as".
It's easy to compile the *.as to a swf, use the command in the code snippet above.

To compile the *.as to swc, there are several ways.
First way, you can compile the *.as to swf, unzip the swc compiled before and replace the "library.swf" with the new swf. I've tried this but there are some problems I haven't solved.
Second way, modify the gcc file, you can follow this post: http://blog.frankula.com/?p=211.
Third way, use the makefile from this project:
http://alchemy-hacks.googlecode.com/svn/trunk/tricks/
Fourth way, follow this post
http://unitzeroone.com/blog/2009/05/22/another-scream-on-flash-alchemy-memory-and-compilers/
Fifth way, mentioned by Bernd Paradies, can be found here: http://forums.adobe.com/message/3001861
Final way, this is what I recommend, use the wrapper by Ed McManus.
Ed McManus posted the script here: http://forums.adobe.com/message/2616985,
but there are some errors caused by the forum formatting. I fixed the errors,
one obvious error is extra spaces in "nbsp;", another big problem is when you try to use the compiled swc, flashdevelop will throw the error "Target Matching “[xX][mM][lL]” is Not Allowed", thanks to this post http://www.anujgakhar.com/2009/02/17/the-processing-instruction-target-matching-xxmmll-is-not-allowed/, I figure out this problem is caused by the spaces before
catalog.xml's header. I made changes to the script and now it works properly.

You can download the fixed version(alc-asc) here: http://flaswf.googlecode.com/svn/trunk/QuickAlchemy/Hack/SWC/
To use it, put it into your "alchemy/achacks" folder, go to cygwin use command
such as "alc-asc modifiedAlchemy.as outputLib.swc".

Links:
http://forums.adobe.com/message/2616985
http://forums.adobe.com/message/3001861
http://blog.frankula.com/?p=211
http://www.anujgakhar.com/2009/02/17/the-processing-instruction-target-matching-xxmmll-is-not-allowed/
http://unitzeroone.com/blog/2009/05/22/another-scream-on-flash-alchemy-memory-and-compilers/
And Ed McManus wrote some very good documents on Alchemy -
General Porting Tips:
https://github.com/emcmanus/flashsnes/blob/master/docs/General_Porting_Tips
Alchemy VM Architecture:
https://github.com/emcmanus/flashsnes/blob/master/docs/Alchemy_VM_Architecture.txt
Performance:
https://github.com/emcmanus/flashsnes/blob/master/docs/Performance

2 comments:

  1. Hello Bruce,

    This seems very interesting! ;)

    I just did a repack for alchemy with 0 config step. It has an option (clean) to not delete temporary files.

    http://www.covergraph.com/blog/

    I would integrate well the alc-asc, but we must see if there is sufficient interest.

    This hack allows you to integrate the memory access Alchemy without writing C file, right? is it as more effective as Azoth? there is always the problem of weight added to the final application ..

    ReplyDelete
  2. @Alama:
    "This hack allows you to integrate the memory access Alchemy without writing C file, right? "
    Yes!
    "is it as more effective as Azoth?"
    Yes, this way should be at least as effective as Azoth.
    And about the added weight, it seems that alc will compile .as to swf in uncompressed format (FWS) by default. You can use other tools to compress the final swf to CWS, which will result in smaller size.

    ReplyDelete

Sponsors