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:     …