Control Instructions

The last major collection of instructions that our imaginary machine implements are the control instructions. These are the assembly language equivalents of the control instructions in C. In other words, these are used instead of for loops, if..else constructs, and while loops.

Control instructions are very primitive in assembly language, though there is one instruction which does not appear in many of the higher level languages: the goto or jmp (standing for jump) instruction. This instruction allows-na a program to branch off and continue execution from an arbitrary address. One supplies it with the address of a location in memory (as an operand) and the processor changes its instruction pointer to that new address, so that execution continues from that new position.

Of course, the JMP instruction is not much use on its own. A program needs to make decisions about when to jump and when to continue execution at the current location without branching off. Thus, the JMP instruction is complemented with numerous conditional jump instructions. These jump only when a certain condition (usually based on the state of th flags) is true. They make a kind of primitive if..else statement. If the flags are set one way, then branch, otherwise continue execution at the present location.

In order to set the flags in a meaningful way, one often calls a compare instruction before making a conditional jump. A compare simply subtracts two values (without storing the result) and sets the flags appropriately. If a compare instruction is followed immediately by a conditional jump instruction, the jump will occur based on the value of the subtraction that is performed by the compare. In particular, if one value is bigger than the other, or the values are the same, different flags will be set. If the two values were the same for example, the result of the subtraction would be zero, setting the zero flag. If the first is bigger than the second, the sign flag will be set, and if neither is the case, neither of the sign and zero flags will be set. Instead they will be cleared to zero by the compare instruction.

Of course one needs a range of compare instructions for comparing different kinds of values (memory with accumulator, register with accumulator, immidiate value with accumulator, etc).

The compare instructions are implemented quite easily. One performs the required subtraction, and stored the result in a temporary variable tmep so that none of the original data is changed. Calling setflags with temp as a parameter then sets the appropriate flags, in precisely the same manner as for the subtraction instructions. The only flag which is not set is the carry flag. This needs to be set if the result of the subtraction was a borrow. This would only occur in a situation where we were subtracting a bigger value from a smaller one. The result of a subtraction, in 16 bits, would be a negative value, which in our unsigned data types, gets stored as a large positive value. To test for this, we check to see if the result is larger than what we started with. Note the parentheses around the comparison (which makes use of the > comparison operator in C). Once the comparison is done by C, the result is either true or false, and it is this true/false value (enclosed in parentheses so there is no confusion) that is assigned to the carry flag.

The implementation of the conditional jumps is quite straightforward. An if statement tests whether the appropriate flags are set or not, and the instruction pointer is updated with either the address to branch to if the branch should be taken, or the next instruction location otherwise.

One final special control instruction pair is the CALL and RET pair of instructions. These are used for implementing subroutines (i.e. procedures or functions) in assembly language. CALL is like JMP in that execution continues from the new location. However, before the jump is made, CALL also stores the current execution address (i.e. the current IP pointer) on the stack. RET is called at the end of a subroutine. It retrieves the location stored on the stack and jumps back to that location to continue execution from where the program left off. This of course assumes that the stack has not been modified during the subroutine in such a way as to bury the stack location where the return address is being stored. An assembly language programmer has to be careful to PUSH and POP information on the stack in the correct order so that the stack is left as it was found, at the end of a subroutine.

The implementation of the CALL and RET instructions is straightforward. It uses the same tricks as we have used elsewhere, so there is no need to describe it in any more detail here.