Pop quiz time!
What appears on stage with the following code:
1 2 3 4
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
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
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]
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
DisplayObjectContainer, it’s instance is pushed onto the
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
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
above. As you can see, the call has
(1) argument - in this case the
circle0 appears on the stack afterwards because it is
Just in case you are wondering, the number of arguments specified are popped
off the stack and put into registers before the property,
called. That’s why addChild is called on
this rather than
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
This is why we have 2 circles. Note, I’ve called this instance
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…
addChild returns what was passed to it, so that’s why
back on the stack.
getproperty :x // circle0, circle1 => circle0, 0
This line is reading the value of
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…]