Compound Operators, New and You.

Pop quiz time!

What appears on stage with the following code:

1
2
3
4
public function newChild() :void
{
  addChild(new CircleSprite()).x += 50;
}

This is what appears:

Of course, all you clever observant folks will have noticed that that the circle’s x coordinate is being set using a compound operator, +=. That’s the culprit in this unusual case.

Now, putting aside any crazy reasons for wanting to write code like that in the first place, what’s actually going on here? Well, one way to find that out is by shrinking ourselves down and entering Inner (ABC) Space! (SPACE… SPAce… space…)

Yep, it’s time to get roll up our sleeves and get oh, so dirty rooting around in the ActionScript Bytecode (herein known as ABC to spare my keyboard). It’s not going to be pretty, and I’ll probably misinterpret some of it but, just like a good orbital nuking, it’s the only way to be sure. Also, like an orbital nuking, it’s a little bit over the top, but I wanted an excuse to start becoming a little more au fait with ABC because, well, because I’m a geek and I like knowing how things work.

So, here are those lines once more, but in a format closer to what the Flash player VM sees:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function :CompoundOperatorsAndNew:::newChild()::void
maxStack:3 localCount:1 initScopeDepth:9 maxScopeDepth:10
getlocal0
pushscope
findpropstrict  :addChild
findpropstrict  :CircleSprite
constructprop   :CircleSprite (0)
callproperty    :addChild (1)
findpropstrict  :addChild
findpropstrict  :CircleSprite
constructprop   :CircleSprite (0)
callproperty    :addChild (1)
getproperty     :x
pushbyte        50
add
setproperty     :x
returnvoid
0 Extras
0 Traits Entries

Line groups 7-8 and 11-12 are the ones which cause the problem. 2 circle classes are being created with the lines constructprop :CircleSprite (0), and both are being added as children with callproperty :addChild (1).

Before we muse on why Flash compiled the ActionScript at the top of the article into this ABC, let’s have a closer look at the bytecode and try and work out what it is doing.

In general bytecode, like assembler, is pretty straightforward. There’s just an awful lot of it to trek through. So don’t be intimidated.

One thing to be aware of however are the values on the stack. The stack is just a last in - first out list of items which operands (the word before the colon in the ABC listing) have access to. Different operands affect different numbers of items on the stack. The AVM 2 Overview document Adobe provide will tell you exactly how many, and what it’ll do to them.

Ok, let’s take them a line at a time. I’ll stick in comments to the right of the lines showing the contents of the stack before and after the line is executed, like this: // [stack contents before line] => [stack contents after line].

function :CompoundOperatorsAndNew:::newChild()::void
maxStack:3 localCount:1 initScopeDepth:9 maxScopeDepth:10

Ok, we’re going to ignore this bit. All it’s doing is setting up the stack and a few other bits so the code in the function can start to execute in a sane fashion. The most important thing here is localCount which tells us how many arguments are in the registers having been passed to the function. You may notice there are no arguments, but localCount is 1. Well, that’s because the instance of the object that the method was called on is always passed as the first argument. That’s what the this keyword refers to. The compiler passes it for you automatically.

getlocal0 // [empty] => this

Grabs the item in register 0 and puts it on the stack. As mentioned above, the this object is in register 0, so that is what is put on the stack.

pushscope // this => [empty]

Moves this onto the scope stack. The scope stack holds the objects to be searched when certain items and properties are requested. You can think of this little code flourish as what allows you to refer to a variable or method in the current instance of the class, more or less.

findpropstrict :addChild // [empty] => this

This line searches the objects in the scope stack for a property called addChild, and puts it on the stack. In this case, since the class we’re in inherits from DisplayObjectContainer, it’s instance is pushed onto the stack.

findpropstrict :CircleSprite // this => this, class CircleSprite

Here the class definition for CircleSprite is being searched for.

constructprop :CircleSprite (0) // this, class CircleSprite => this, circle0

This grabs the class definition which was found for CircleSprite and constructs a new object from it and places it on the stack. I’ve numbered the instance, just so things are clearer later on. The number in brackets after the class name indicates how many arguments to pop off the stack and pass to the class constructor as arguments - in this case, there are none.

callproperty :addChild (1) // this, circle0 => circle0

Here, addChild is called on the object found by findpropstrict :addChild above. As you can see, the call has (1) argument - in this case the circle0 instance. circle0 appears on the stack afterwards because it is returned from addChild.

Just in case you are wondering, the number of arguments specified are popped off the stack and put into registers before the property, addChild is called. That’s why addChild is called on this rather than circle0.

findpropstrict :addChild // circle0 => circle0, this

Behaves exactly as described earlier. Also, this is where the confusion starts.

findpropstrict :CircleSprite // circle0, this => circle0, this, class CircleSprite

Again, the same as before.

constructprop :CircleSprite (0) // circle0, this, class CircleSprite => circle0, this, circle1

Ditto.

This is why we have 2 circles. Note, I’ve called this instance circle1 in order to differentiate it from the previously constructed instance. Internally, CircleSprite counts it’s instances, so circle0 matches the circle marked 0 on the screen, and circle1 matches the circle marked 1.

callproperty :addChild (1) // circle0, this, circle1 => circle0, circle1

Looking familiar? Good! That means you’re paying attention…

Remember, addChild returns what was passed to it, so that’s why circle1 is back on the stack.

getproperty :x // circle0, circle1 => circle0, 0

This line is reading the value of x from circle1, which will be 0. It’s shoved on to the stack.

pushbyte 50 // circle0, 0 => circle0, 0, 50

Pushes are pretty straightforward - it’s pushing the literal value onto the stack. It’s interesting to note here that this code is pushing a byte, rather than an int or a number - the compiler seems to choose the “narrowest” possible type for a literal.

add // circle0, 0, 50 => circle0, 50

Does exactly what it says on the tin, which is to add things. In this case, the two most recent items on the stack are added, and the result is pushed back onto the stack.

setproperty :x // circle0, 50 => [empty]

Here, x is set to the value which is popped from the stack, and it’s set on the object that’s popped next. So, in this case, circle0.x is set to 50 - hence the movie having circle 0 to the right of circle 1.

returnvoid // [empty] => [empty]

It, er, returns void…

So there you have it. Your first dive into ABC. prod. Are you still awake? Honestly, this stuff is interesting. Really.

One nice thing about looking at ABC is that you can see which idioms compile to more terse ABC code. As a very general rule of thumb, the terser the code, the faster it will run. This is not a law though - you still have to profile things. Unfortunately, Adobe don’t provide any normalised speed of execution for each operand, so it is hard to get a good grasp of what operand sequence is fastest, except by experience.

Hopefully, I’ll be taking a bit more of a look at what ABC is output by the flex compiler over the next few months. I may even get around comparing the quality of the output to that of Haxe. If I’m feeling particularly daring, I may even write some bytecode. I know, the party never stops around me…

[Those of you who actually make it this far may have noticed that I haven’t actually answered the “why” of my initial problem. I’ve left that as an exercise for the reader. Or, more honestly, I’m still thinking about it…]

Comments