Andreas Moshovos
Spring 2007
The C Switch Statement
In this section we will be implementing in assembly a C switch statement. Let’s us use a specific example. Assume that we somehow know that a integer variable p takes values in the range of 0 through 7. The code that we are interested in is:
int a#
int b = 0x12345678#
int c = 0x11223344#
int p = some value#
switch (p)
{
case 0: a = b + c# break#
case 1: a = b - c# break#
case 3: a = b# break#
case 7: a = c# break#
default: a = 0#
}
The default statement is executed in all cases that are not explicitly referred by a “case” statement. Accordingly, our switch statement is equivalent to the following:
switch (p)
{
case 0: a = b + c# break#
case 1: a = b - c# break#
case 3: a = b# break#
case 7: a = c# break#
case 2:
case 4:
case 5:
case 6:
a = 0#
}
If we were to draw a control flow diagram for this code it would look as follows:
Notice that the first box, is a decision box with many different branches. There are five different possible targets out of this box. We can synthesize this behavior using several if-the statements. So in C we could implement the switch also as:
if (p == 0) goto case0;
if (p == 1) goto case1;
if (p == 3) goto case3;
if (p == 7) goto case7;
goto casedefault;
case0:
a = b+c;
goto done;
case1:
a=b-c;
goto done;
case3:
a = b;
goto done;
case7:
a = c;
goto done;
casedefault:
a = 0;
done:
You should now be able to convert that into assembly using branches. Assume for this code that p is in r8:
beq r8, r0, case0
cmpeqi r9,
r8, 1
bne r9, r0, case1
cmpeqi r9, r8,
3
bne r9, r0, case3
cmpeqi r9, r8,
7
bne r9, r0, case7
br casedefault
case0:
…
br after
case1:
…
br after
case3:
…
br after
case7:
…
br after
casedefault:
…
after:
In the worst case scenario we will have to go through all comparisons to reach the “br casedefault” for this simple switch statement. What if our switch statement had 50 cases? It would be nice if there was a faster way of implementing multi-way branches. And fortunately there is, we can use a jump table. A jump table is an array with an element for each possible value of the switch variable (p in our case). The value of each jump table element is an address. This is the address where the code for the corresponding case statement lies in memory. Our assembly implementation with thus have a data segment containing the jumptable with appropriate values. The code will start by using p as an index to read an element from the jumptable (read jumptable[p]). The next step would be to use an appropriate control flow instruction so that execution continues at the address pointed to by the jumptable[p] element.
In pseudo-code notation, out implementation will look like this (note this will not work in C):
unsigned int jumpTable[8] = {case0, case1,
defaultcase, case3, defaultcase, defaultcase, defaultcase, case7}#
goto jumpTable[p]#
Here every element of jumpTable[] is an address to a piece of code that implements the corresponding case. For example jumpTable[1] contains the address “case1” which should be a label placed at the beginning of the block of instructions that implement case1. JumpTable[2] is “defaultlabel” because when p is 2 we want the code implementing the default case. In memory my code and jumptable will look as follows:
Here’s the implementation in assembly. The embedded comments explain the logic behind this code. This is by no means the best implementation of each required step. We will progressively see better ways of calculating arrays indexes and of accessing memory variables.
.data
#
the jumpTable follows, each element is a code label
that will be defined later on
jTable: .word
case0,case1,case2,case3,case4,case5,case6,case7
va: .word 0x0
# variable a
vb: .word 0x12345678
# variable b
vc: .word 0x11223344
# variable c
vp: .word 0x3
# variable p
.text
.global main
main:
# we want to read element jTable[p]
# this is at address jTable + 4 x p in memory since
each jTable element is four bytes long
movia r8, jTable
# r8 = &jTable[0]
the address where the jTable array starts at in memory
movhi r9, %hiadj(vp)
ldwio r9, %lo(vp)(r9) # load vp into r9
add r9, r9, r9 # r9 = 2 x r9
add r9, r9, r9 # r9 = 2 x r9 = 4 x vp
add r8, r8, r9 # r8 = jTable + 4 x vp = &jTable[vp]
ldwio r8, 0(r8) #
r8 = jTable[vp]
jmp r8
# PC = jTable[vp]
case0:
movhi r8,
%hiadj(va)
ldwio r9,
%lo(va)+4(r8) #
r9 = vb
ldwio r10, %lo(va)+8(r8) # r10 = vc
add r9, r9, r10 # r9 = vb + vc
stwio r9,
%lo(va)(r8) #
va = vb + vc
br
after # this implements the “break”, i.e.,
continue execution after the switch (after is label)
case1:
movhi r8,
%hiadj(va)
ldwio r9,
%lo(va)+4(r8) #
r9 = vb
ldwio r10, %lo(va)+8(r8) # r10 = vc
sub r9, r9, r10 # r9 = vb - vc
stwio r9,
%lo(va)(r8) #
va = vb - vc
br
after # this implements the “break”, i.e.,
continue execution after the switch (after is label)
case3:
movhi r8,
%hiadj(va)
ldwio r9,
%lo(va)+4(r8) #
r9 = vb
stwio r9, %lo(va)(r8) # va = vb
br
after # this implements the “break”, i.e.,
continue execution after the switch (after is label)
case7:
movhi r8,
%hiadj(va)
ldwio r10,
%lo(va)+8(r8) #
r9 = vc
stwio r10, %lo(va)(r8) # va = vc
br
after # this implements the “break”, i.e.,
continue execution after the switch (after is label)
case2:
case4:
case5:
case6:
movhi r8, %hiadj(va)
stwio r0, %lo(va)(r8) # va = 0
after:
There is a new instruction in this code: “jmp r8”. This does PC = r8. In general, jmp takes a single register input operand and sets the PC to the register’s value. Jmp can be used to also implement function pointers and virtual functions in C++.
Beyond what we will discuss in the lectures:
In our previous example we assumed that we knew in advance the range of values p would take. How would we handle the general case where p could take any possible value? Since p is a long-word then there are 2^32 possibilities. Hence the naïve implementation would require a jumpTable with 4G entries. This is not possible in NIOS II since memory is only 4G bytes and a 4G jumptable would require 16G (4 bytes per entry). The trick is to use an if statement to bound the switch statement. To do so we look at the case values and determine the min and max. We then build a jumptable with (max – min + 1) elements. Then the code becomes
jumpTable: .word case_min, …, case_max
if (p < mix || p > max) then jmp case_default;
jmp jumpTable[p – min];
case_min: …
case_max: …
case_default: …