CSCI 255 — Chapter 6 miscellany

The PowerPoint

Let’s take a look at addressing modes at slide 105.

The MIPS instruction set

The sometimes missing multiply

You won’t find the mul instruction in Wikipedia page on the MIPS instruction set. In the original MIPS instruction set there was an instruction called mult which stored the result of adding two 32-bit integers into a special register pair, HI and LO. If you wanted to multiply the registers $t4 and $t5 and store the result in $t6, you used the following two instruction sequence:
        mult   $t3,$t4
        mflo   $t6
There was also a restriction in the architecture that no multiply or divide instruction could be issued in the two instructions following the mflo.

Assembly idioms

Assembly idioms, or pseudo instructions, looks like assembly language instructions, but are actually translated into sequences of one or more machine instructions.

Many of the assembly idioms provided the MIPS assembly language with “machine instructions” that were common on other computers. For example
        move   $t8,$t9
is translated to
        addu   $t8,$t9,$0
Also, for load immediate,
        li     $t3,1000
is translated to
        addi   $t3,$0,1000

A few assembly idioms translate to multiple machine instructions. For example
        mul    $t8,$t9,3
is translated to
        addi   $at,$0,3
        mul    $t8,$t9,$at
To provide a 32-bit load immediate,
        li     $t3,10000000
is translated to
        lui    $t3,$0,15
        ori    $t3,16960
This works because 15 is (1000000 >> 16) & 0xFFFF and 16960 is 1000000 & 0xFFFF.

Big vs little endian

Take a look at the following references to see how the big vs little endian divide impacts network programming.

Size of integer data types

The careful programmer includes stidin.h and uses types with names like intN_t and uintN_t. The integer data types of the XC32 compiler are found on page 144 of the MPLAB® XC32 C/C++ Compiler User’s Guide. Note that both the int and long are 32 bits. This was not the case with the Arduino.

Arrays and structures

The textbook covers array in Section 6.4.5, after the discussion of control structures; but CSCI 255 will do arrays first!

Finding the location of an array element

Start with an array declaration.
        cType X[] ;
The address of X[i] can be computed as Base(X) + i×Size(cType). Because the sizes of most C data types are a multiple of two, shifts are usually used in place of multiplication.

Try to determine the appropriate code for implemented the following C program sequence.

int V[15], i ;
V[3] = 17 ;
V[i] = V[i+3] ;

C stores arrays in row-major order. This significantly complicates determining the address of elements in multi-dimensional arrays. This time start with the declaration of a two-dimensional array:
        cType M[R][C] ;
The address of M[i][j] can be computed as Base(M) + i×R×Size(cType) + j×Size(cType).

By the way, once these formulas were taught in the second programming course. However, Java does things a different way.

Here’s a harder one. Figure out the assembly code to implement the following C code.

int I[100][100], r ;
I[17][20] = 5 ;
I[r][r] = 1 ;

Finding the location of a structure member

This should be easier. Just find the offset of the member from the beginning of a structure, which should be a constant number, and then add it to the base.

In practice, this can be harder. Although members must be stored in the order they are listed in the structure definition, the padding between elements is implementation-defined. Consider the following structure definition.

struct noteInfo {
   int frequency ;
   long duration ;
} ;

If long variables must be aligned on addresses divisible by 8, the offset to duration would be 8. Otherwise, it would be 4. You’d need to write a section of C code similar to the following to figure how this works on your favorite compiler.

struct noteInfo sillyNote ;
long offset = (void *) &sillyNote - (void *)&sillyNote.duration ;
printf("Offset to duration is %ld\n", offset) ;

Pointers

Pointers are easy because the pointer already contains the base address. Just add in the appropriate offset. Remember: V[i] is the same as *(V+i).

Getting down the details

It can be challenging for inexperienced assembler programmers to implement arrays operations correctly. The first thing you have to do is get the address of V[i] which is V+i.

Assume register that $tv already contains V, the address of the V[0] and $ti already contains i. In the case, if V is an array of integers, the code to store the address of V[i] into $tx would be something like the following:

        sll    $tx,$ti,2        # multiply i by 4 to obtain offset
        add    $tx,$tv,$tx      # add offset to base

Once you have the address, you can use lw to read from the array and sw to write to the array. An assignment state such as U[i] = V[j] could be implemented with something like the following:

        la     $t0,V
        lw     $t1,j
        sll    $t1,$t1,2
        add    $t1,$t1,$t0     # $t1 is &V[j]
        lw     $t2,0($t1)      # $t2 is V[j]
        la     $t0,U
        lw     $t1,i
        sll    $t1,$t1,2
        add    $t1,$t1,$t0     # $t1 is &U[i]
        sw     $t2,0($t1)      # U[i] set to V[j]

Control flow

First follow the rules of the Translating C to C: Control Structures page. The unconditional goto can be implemented with a single j (jump) instruction.

A conditional goto such as
        if ($ti) goto λ
where $ti is a MIPS register containing the value of a C expression, can be implemented as:
        bne   $ti,$0,λ

Comparison and relational operators

However, the translation of the C comparison and relational operators remain. The MIPS slt instructions combined with the bitwise instructions would be sufficient for doing all of this. That is left as an exercise for the reader.

However, be careful! Is x <= y if and only if x < y+1? Is x < y if and only if x-y < 0?

Logical operators

The ! operator is easy, but the && and || binary operators and the ? : ternary conditional can short circuit and must be implemented with some form of the if as shown in the following table.

τ = exp1 && exp2 ;

if (! (exp1))
   τ = 0 ;
else if (! (exp2))
   τ = 0 ;
else
   τ = 1 ;

τ = exp1 || exp2 ;
if (exp1)
   τ = 1 ;
else if (exp2)
   τ = 1 ;
else
   τ = 0 ;
τ = exp1 ? exp2 : exp3 ;

if (exp1)
   τ = exp2 ;
else
   τ = exp3 ;

The real world

However, both compilers and humans can do much better than these inflexible rules.

Calling functions

Those other architectures....