switch
statement. This is an interesting branch instruction because it has multiple targets. It is almost a direct translation of the switch statement because it has two parameters, a default case and an array of possible targets.
Recently support for this opcode was added to AbcExplorationLib.
Given the following ActionScript code:
var x;
for(x = 1;x < 5;x++) {
switch(x) {
case 1:
print("one");
break;
case 2:
print("two");
break;
case 3:
print("three");
break;
case 4:
print("four");
break;
}
}
We compile this file to a .abc file using the following command:
$ java -jar /opt/flex3sdk/lib/asc.jar testswitch.as
testswitch.abc, 280 bytes written
By using a little IronPython example included with AbcExplorationLib we can see how this library interprets the LookupSwitch opcode:
$ mono /opt/IronPython-2.0/ipy.exe ../ipyexample/abccontents.py testswitch.abc
...
Instructions:
getlocal_0
pushscope
pushbyte 1
getglobalscope
swap
setslot 1
jump dest177
dest12:
label
jump dest74
dest17:
label
findpropertystrict M.print
pushstring "one"
callprop M.print
pop
jump dest165
dest30:
label
findpropertystrict M.print
pushstring "two"
callprop M.print
pop
jump dest165
dest43:
label
findpropertystrict M.print
pushstring "three"
callprop M.print
pop
jump dest165
dest56:
label
findpropertystrict M.print
pushstring "four"
callprop M.print
pop
jump dest165
dest69:
label
jump dest165
dest74:
getglobalscope
getslot 1
setlocal_1
pushbyte 1
getlocal_1
ifstrictneq dest91
pushshort 0
jump dest143
dest91:
pushbyte 2
getlocal_1
ifstrictneq dest104
pushshort 1
jump dest143
dest104:
pushbyte 3
getlocal_1
ifstrictneq dest117
pushshort 2
jump dest143
dest117:
pushbyte 4
getlocal_1
ifstrictneq dest130
pushshort 3
jump dest143
dest130:
pushfalse
iffalse SolvedReference dest141
pushshort 4
jump dest143
dest141:
pushshort 4
dest143:
kill
lookupswitch dest69 dest17,dest30,dest43,dest56,dest69
dest165:
getglobalscope
getslot 1
increment
setlocal_1
getlocal_1
getglobalscope
swap
setslot 1
kill
dest177:
getglobalscope
getslot 1
pushbyte 5
iflt dest12
returnvoid
As described in the documentation the parameters of the LookupSwitch instruction are specified as relative byte offsets that specify the target. In order to give a higher level representation of the code, these relative offsets are converted to symbolic references. This process is detailed in the "Using F# Active Patterns to encapsulate complex conditions" post.
This process starts in the following functions:
static member ReadAndProcessInstructions(aInput:BinaryReader,
count,
constantPool:ConstantPoolInfo) =
let instructionsAndOffsets =
(AvmMethodBody.ReadingInstructions([],
aInput,
count,
constantPool)) in
let destinations =
AvmMethodBody.CollectDestinations(instructionsAndOffsets,
Map.empty,
instructionsAndOffsets)
in
AvmMethodBody.UpdateCodeWithDestinations(
destinations,
instructionsAndOffsets,[]) |> List.to_array
The
CollectDestinations
method collects all the absolute offsets used by branch instructions and stores them in a dictionary with a generated label. The UpdateCodeWithDestinations
method modifies the instruction list use the generated labels.For example to add support in
CollectDestinations
the following code was added:
static member CheckLookupSwitchCase baseOffset
(totalInstructions:(int64*AbcFileInstruction)
list) =
fun (destinations:Map<int64,string>) target ->
match target with
| UnSolvedReference(relativeOffset) when
(AvmMethodBody.IsDestinationDefined(int(baseOffset+relativeOffset),
totalInstructions)) ->
destinations.Add(int64(baseOffset+relativeOffset),
sprintf "dest%d" (baseOffset+relativeOffset))
| _ -> destinations
static member CollectDestinations(instructions:(int64*AbcFileInstruction) list,
destinations:Map<int64,string>,
totalInstructions:(int64*AbcFileInstruction) list) =
match instructions with
...
| ((offset,(LookupSwitch(defaultBranch,cases)))::rest) ->
let baseOffset = int(offset) in
AvmMethodBody.CollectDestinations(rest,
Seq.append [defaultBranch] cases |>
Seq.fold (AvmMethodBody.CheckLookupSwitchCase baseOffset totalInstructions ) destinations,
totalInstructions)
...
Then the code is modified in the
UpdateCodeWithDestinations
method to use the generated labels.
static member UpdateCodeWithDestinations(destinations:Map<int64,string>,
instructions,
resultingInstructions) =
let processedInstructions =
match instructions with
...
| ((offset,LookupSwitch(defaultCase,cases))::rest) ->
(offset,
LookupSwitch(AvmMethodBody.SolveSwitchCase defaultCase offset destinations,
Array.map (fun c->AvmMethodBody.SolveSwitchCase c offset destinations) cases))::rest
| _ -> instructions
...