Static Initializers

Static Initializers is almost the last topic of our language features in this series about cross-compiling ActionScript to JavaScript. The next post will be about weak links, Proxies, and Dictionaries, which are kind of in between language and runtime features.

 

Initializing Instance Members

You are probably very familiar with these kind of member initializations:

// ActionScript:
public class Foo
{
    public const m_instance_member : String = "Instance Member";
}

Our cross-compiler can transform those expressions without any problems:

// JavaScript:
var Foo = function() {};
Foo.prototype.m_instance_member = "Instance Member";

So far, so good. But what if we initialize those members by calling a function or method?

// ActionScript
public class Foo
{
    private function init_instance_member() : String { return "Instance Member"; }
    public const m_instance_member : String = init_instance_member();
}

This kind of initialization is allowed in ActionScript as long as you don’t introduce any circularity. For example Foo.m_instance_member should not be initialized with a value from a different class that depends on Foo. But even without any circularity  those kind of initializations are problematic, because initializing a member by calling a function or method may introduce side effects that are hard to resolve statically. I suspect for that reason Google’s Dart does not allow member initializations with side effects and I think that was a smart decision.

In this example your cross-compiler might be smart enough (FalconJS is not) to detect that m_instance_member can be initialized with init_instance_member() because init_instance_member itself does not have any side effects. But I could easily create examples where it would be impossible to decide whether calling a function would be “safe” or not. For the purpose of our discussion let’s just assume that expressions may have side effects that we need to “neutralize” when cross-compiling to JavaScript.

How can side effects be avoided without affecting the behavior? There is probably more than one solution to this problem. I propose this transformation:

// JavaScript:
var Foo = function() 
{
   var self = this;
   self.m_instance_member = self.init_instance_member();
};
Foo.prototype.init_instance_member = function() { return "Instance Member"; };
Foo.prototype.m_instance_member = null;

This is what I call “neutralizing side effects”. Side effects are not a problem anymore once the whole app is up and running. We know that instance members can only be accessed after creating an object. Placing our initialization code with potential side effects into constructors will guarantee that members get properly initialized. Side effects naturally disappear once code is placed in constructors.

 

Initializing Class Members

Class members are initialized as follows :

// ActionScript:
public class Foo
{
    public static const s_class_member : String = "Class Member";
}

Transforming class member initializers without side effects is straight forward:

// JavaScript:
var Foo = function() {};
Foo.s_class_member = "Class Member";

Calling a function or method to obtain a value for a class member may introduce side effects:

// ActionScript
public class Foo
{
    public static function init_class_member() : String { return "Class Member"; }
    public static const s_class_member : String = init_class_member();
}

Initializing class members with return values from functions is allowed in ActionScript. But the transformations to JavaScript are much more complicated than for instance member initializations. First off, our cross-compiler needs to emit a static function that we want to call before the class is being used:

// JavaScript:
var Foo = function() {};
Foo.__static_init = function()
{
    Foo.__static_init = function() {};
    Foo.s_class_member = Foo.init_class_member();
}

The first line in Foo.__static_init  makes sure that __static_init only executes the initialization code once. Now we have to make sure to inject  __static_init calls before the class is used. There are three situations where injecting __static_init needs to happen:

  • when an object of that class gets created.
  • when code outside of that class accesses a static class member.
  • when code outside of that class calls a static class method.

The first case is easy and can be taken care of by adding __static_init to the constructor:

// JavaScript:
var Foo = function() 
{
   Foo.__static_init();
};

For accessing a static class member we have to inject fairly expensive code:

// ActionScript
var s : String = Foo.s_class_member;
// JavaScript
var s = (Foo.__static_init(), Foo.s_class_member);

This transformation takes advantage of JavaScript’s comma operator. In this case the highlighted expression reads “execute Foo.__static_init first, forget about the return result but move on to Foo.s_class_member“, which then becomes the expression that gets assigned to the left hand side.

Calling static class methods works the same:

// ActionScript
var s : String = Foo.init_class_member();
// JavaScript
var s = (Foo.__static_init(), Foo.init_class_member());

As you can imagine initializing static class member and methods is very expensive but unavoidable. The costs can be so high that I recommend adding a compiler switch (i.e. -js-warn-static-init) that warns developers about potential upcoming performance problems.

 

Class Initializers

There is another type of initializations that is similar to class member initializations called class initializations. Frankly, I don’t like class initializers and for some odd reasons I have only seen class initializers in what I would classify as “sub-prime” ActionScript code. But it doesn’t really matter what I think about class initializers. Class initializers are allowed in ActionScript, so we have to cross-compile them:

// ActionScript
public class Foo
{
    trace( "Class Initializer called" );
}
Yes, in ActionScript you can just add free-floating code to the class body and those lines will be executed the first time the class is used even before the class members are being initialized. In order to demonstrate that behavior let’s add a class initializer to our Foo example, which also initializes class members:
// ActionScript
public class Foo
{
    public static function init_class_member() : String
    {
        trace( "Foo.init_class_member called" );
        return "Class Member";
    }
    public static const s_class_member : String = init_class_member();
    trace( "Foo's class initializer called" );
}
var s : String = Foo.s_class_member;

I added an additional trace statement to init_class_member so we can see when it is being called. The expected behavior is that you should see “Foo's class initializer called” followed by “Foo.init_class_member called“. Fortunately we can achieve that behavior by simply adding our class initializing code to __static_init before static members are being initialized:

Foo.__static_init = function()
{
    Foo.__static_init = function() {};
    trace( "Foo's class initializer called" );
    Foo.s_class_member = Foo.init_class_member();
}

Conclusion

We learned that member initializations for instances and classes may contain expressions with side effects, which our cross-compiler must neutralize. Instance members can safely be initialized in their constructors. For class members we need to emit a __static_init function, that gets called before the class is being used, that is, when we instantiate an object, or access a member or method of that class. Class initializations can be treated as a special case of class member initializations by adding free floating class code to __static_init before members get initialized.

In general it is safe to assume that initializations with code that may contain side effects are expensive and will increase code size and tax the performance of your generated JavaScript. The easiest way to avoid code bloat and performance degradation caused by static initializers is by refactoring the original ActionScript code. For that reason I recommend adding a compiler switch that warns the developer when the cross-compiler encounters initializations with side effect code.