Skip to content

Loom is Dead

Unfortunately, I ran out of motivation for Loom. I solicited some people to take over the project on the mailing lists, but without any takers, I think it’s time we called it.

There’s been some very nice work done in the AS3 bytecode generation space by Richard Szalay (FLemit and FLoxy), and mocking implementations (my original motivation for Loom) by both Richard (ASMock) and Drew Bourne (mock-as3 and mockolate). I encourage you to check out and support their work.

I’m leaving the source for Loom out on the web, of course. I know at least one person on the Tamarin lists has been using it as a reference for a project they are working on. I learned a lot from the process of writing Loom, and I hope that others find it useful too. If you are interested in taking over the project or learning more about the implementation, please contact me using the contact link on this site.

Thanks to everybody on the Loom forums and across the world for motivation, encouragement, and support. Long live Flex and AS3!

Loom Source Posted

Loom is still a work in progress, but it was time to get the source out there so the project can get some momentum from contributors. You can now find it on the Google Code site for the Loom project.

Looking for Loom Contributors

Check out the details on the Loom discussion group (which is no longer private so come on in!) :)

greaterequals Opcode in Tamarin/AVM2 Spec

While working on Loom today, I ran across the opcode 0xaf, which matches two opcodes in the AVM2 spec: greaterthan and greaterequals. Checking the AVM2 spec errata on the Tamarin project page produced the following:

“On page 66, instruction name is greaterequals but format, forms, and description are those of greaterthan’.”

Awesome. Thanks guys, that really helps me to know what the PROPER OPCODE VALUE IS SUPPOSED TO BE. It’s obviously in the source code for the Tamarin C++ project, but it would have been nice if somebody had put this on the errata.

So, to clarify the matter once and for all:
greaterequals: 0xb0
greaterthan: 0xaf

Loom Private Alpha 4 Released

Got the issue with getters/setters fixed, so they are now fully advisable. I think I have also figured out why custom namespaced functions are not advisable, but since I don’t think custom namespace functions get used terribly regularly by most developers, I am planning on putting this item behind the SWFLoader, which is the bit that will make Loom useful for mainstream Flex development.

Of course, not much will be happening on this front for a few weeks since I am getting married on Saturday, and have family in town most of the week. I’ll be back on Loom full force when I return from vacation in May.

Loom Private Alpha 3 Released

Last night I fixed the issues with advising methods with ...rest and optional arguments, and came up with a potentially nifty strategy for dealing with getter/setter advice.

Getters/setters are a little harder to advise since their functions are seen by the AVM as properties, and as a result you can’t pass their function references around. I think I have it tackled though, and should have it all working in a few days.

Loom Private Alpha 2 Released

Last night, I released the second private alpha to the Loom discussion group. Loom can now subclass any base class. There are a few known issues with certain method signatures and another small issue with getters and setters.

I’ll be remedying these issues this week with a view to a third private alpha by the close of the upcoming weekend, after which I’ll be back on the SWFLoader in preparation for a public beta. I’m hoping to get this all out before I get married on 4/25, since after that I’ll be out of action for a few weeks in Europe on our honeymoon!

Tagged

VerifyError: Error #1032: Cpool index X is out of range X

Whenever you are working with an ABC file and loading it in to the AVM, the AVM will validate the ABC on the way in. I ran in to an issue tonight where I was dynamically weaving the opcodes for a method in Loom, and I forgot to add a NamespaceSet to the ConstantPool. If you do something like this, you end up with this rather nondescript error.

Now, in fairness, the AVM does tell you that there is a problem with the constant pool and it does tell you which index it can’t find. If your index was 6 for example (which my one was), it means that one of the parts of your ABC file references the seventh item in a pool that has less than seven items (since the code in the AVM is written in C++ and is 0-based for collection indices). Typically this would not be confusing for a programmer, but the ABC code starts at 1-based positions for all the constant pools, and after thinking that way for a while you can get tricked by error messages that don’t indicate what the starting loop position is.

Hopefully you won’t run in to this issue, but if you do, now you’ll know why.

Tagged

Private Loom Alpha Released

I just sent the Loom library out for a private alpha with a limited group of people. If you are interested in joining in, just ping me on the contact form on this blog or hit me up directly from the Loom discussion group.

Tagged

Loom Lives!

About fifteen minutes ago, I successfully weaved the final opcodes required to create an AOP dynamic proxy with Loom. I am totally stoked.

It’s been almost three months of work, admittedly on and off. I think if I strung together all the hours I have put in to the project, it would be about 120-160 hours altogether here and there. The first revision of the ABC parser that I worked on in January and February worked great but wasn’t very usable, so it ended up getting replaced by the new version. The new version of the parser and serializer along with the opcode enumeration are a big improvement, and were so easy to use that I was able to weave a dynamic proxy in just a few hours (most of which was spent tackling the learning curve of AVM “assembly” and diagnosing/fixing an opcode bug I found along the way).

So without further ado, here’s a sneak peak in to the unfinished API for Loom. Below is a copy of a really quick n’ dirty test case that I put together to make sure that Loom was working as expected. The test case does as follows.

1) Loads the ABC bytecode for a class called loom.template:BaseClass.
2) Parses the ABC code and enhances it with several Loom-related items: a Dictionary to hold anonymous advise handling functions, a setHandler() method to accept these functions, and a method call proxyInvocation() that handles dynamic method invocation.
3) Stomps the opcodes for all the instance methods with dynamically weaved opcodes that tell the method to invoke proxyInvocation().
4) Re-serializes the enhanced class definition to ABC bytecode.
5) Loads the enhanced bytecode in to the Flash Player/AVM

Then we run the tests. The bytecode loading in step #5 is currently asynchronous so I have to do this through an event listener, but the next item for me to write is a Loom SWF loader that will enhance bytecode on the fly, making it appear to the app as if the proxy definitions were added by the compiler.

Without further ado, here’s the code. I’ll be releasing Loom as a private alpha this weekend (so touch base via the contact form on this blog if you want to be added to the list of testers), and a public beta will be made available once the SWF loader is finished.

BaseClass.as

package loom.template
{
  public class BaseClass
  {    
    public function BaseClass(constructorArg1 : String, constructorArg2 : String)
    {
      trace("BaseClass::constructor()");
    }
 
    public function methodCallOne(arg1 : String, arg2 : Number) : int
    {
      trace("BaseClass::methodCallOne()");
      return 100;
    }
 
    public function methodCallTwo(arg1 : String, arg2 : Number, arg3 : Object) : void
    {
      trace("BaseClass::methodCallTwo()");
    } 
 
    public function methodCallThree(arg1 : String, arg2 : Number) : String
    {
      trace("BaseClass::methodCallThree()");
      return "stringValue";
    }
  }
}

Quick n’ Dirty Test Demonstrating Loom Weaving a Dynamic Subclass

package loom.util
{
  import flash.events.Event;
  import flash.utils.describeType;
  import flash.utils.getDefinitionByName;
 
  import loom.TestConstants;
  import loom.abc.AbcFile;
  import loom.loom_namespace;
  import loom.swf.AbcClassLoader;
  import loom.template.BaseClass;
  import loom.template.MethodInvocation;
 
  import net.digitalprimates.fluint.tests.TestCase;
 
  public class DynamicProxyFactoryTest extends TestCase
  {
    public function testCreateProxy() : void
    {
      var fixture : DynamicProxyFactory = new DynamicProxyFactory();
      var proxyFile : AbcFile = new AbcDeserializer(TestConstants.getBaseClassTemplate()).deserialize();
 
      // We enhance the loaded ABC file to save the work the compiler already did, and just add to/replace it
      fixture.createProxy(proxyFile);
 
      var classLoader : AbcClassLoader = new AbcClassLoader();
      classLoader.addEventListener(
        Event.COMPLETE,
        function (event : Event) : void
        {
          var classRef : * = getDefinitionByName("loom.template::DynamicSubClass");
          trace(describeType(classRef));
 
          // This would blow up if the types are not compatible
          var instance : BaseClass = new classRef(null, null);
          assertTrue(instance is BaseClass); // redundant, yet satisfying :)
 
          // Run original method invocation
          trace(">>> Original method invocation");
          trace(instance.methodCallOne("Hello Original Method Invocation!", -1));
 
          // Set up a handler
          instance.loom_namespace::setHandler(
            "methodCallOne",
            function (invocation : MethodInvocation) : *
            {
              trace("Before advice for: " + invocation);
 
              try
              {
                return invocation.proceed();
              }
              catch (e : Error)
              {
                trace("Throws advice for: " + e);
              }
              finally
              {
                trace("After advice for: " + invocation);
              }
            }
          );
          trace(">>> AOP method invocation");
          trace(instance.methodCallOne("Hello Dynamic Method Invocation!", 1234));
        }
      );
 
      // Load in the proxied class definition
      classLoader.loadClassDefinitionsFromBytecode([
       new AbcSerializer().serializeAbcFile(proxyFile)
      ]);
    }
  }
}

Output

<type name="loom.template::DynamicSubClass" base="Class" isDynamic="true" isFinal="true" isStatic="true">
  <extendsClass type="Class"/>
  <extendsClass type="Object"/>
  <accessor name="prototype" access="readonly" type="*" declaredBy="Class"/>
  <factory type="loom.template::DynamicSubClass">
    <extendsClass type="loom.template::BaseClass"/>
    <extendsClass type="Object"/>
    <constructor>
      <parameter index="1" type="String" optional="false"/>
      <parameter index="2" type="String" optional="false"/>
    </constructor>
    <method name="methodCallThree" declaredBy="loom.template::DynamicSubClass" returnType="String">
      <parameter index="1" type="String" optional="false"/>
      <parameter index="2" type="Number" optional="false"/>
    </method>
    <variable name="handlerMappings" type="flash.utils::Dictionary" uri="http://loom.ninjitsoft.com"/>
    <method name="setHandler" declaredBy="loom.template::DynamicSubClass" returnType="void" uri="http://loom.ninjitsoft.com">
      <parameter index="1" type="String" optional="false"/>
      <parameter index="2" type="Function" optional="false"/>
    </method>
    <method name="methodCallOne" declaredBy="loom.template::DynamicSubClass" returnType="int">
      <parameter index="1" type="String" optional="false"/>
      <parameter index="2" type="Number" optional="false"/>
    </method>
    <method name="methodCallTwo" declaredBy="loom.template::DynamicSubClass" returnType="void">
      <parameter index="1" type="String" optional="false"/>
      <parameter index="2" type="Number" optional="false"/>
      <parameter index="3" type="Object" optional="false"/>
    </method>
    <method name="proxyInvocation" declaredBy="loom.template::DynamicSubClass" returnType="*" uri="http://loom.ninjitsoft.com">
      <parameter index="1" type="loom.template::MethodInvocation" optional="false"/>
    </method>
  </factory>
</type>
BaseClass::constructor()
>>> Original method invocation
BaseClass::methodCallOne()
100
>>> AOP method invocation
Before advice for: methodCallOne(Hello Dynamic Method Invocation!, 1234)
BaseClass::methodCallOne()
After advice for: methodCallOne(Hello Dynamic Method Invocation!, 1234)
100
Tagged