When designing a machine to perform a computation, we would often
prefer to arrange for components to be shared by different parts of
the computation rather than duplicate the components. Consider a
machine that includes two GCD computations--one that finds the GCD of
the contents of registers a and b and one that finds the
GCD of the contents of registers c and d. We might start
by assuming we have a primitive gcd operation, then expand the
two instances of gcd in terms of more primitive operations.
Figure
shows just the GCD portions of the
resulting machine's data paths, without showing how they connect to
the rest of the machine. The figure also shows the corresponding
portions of the machine's controller sequence.
This machine has two remainder operation boxes and two boxes for
testing equality. If the duplicated components are complicated, as is the
remainder box, this will not be an economical way to build the
machine. We can avoid duplicating the data-path components by using
the same components for both GCD computations, provided that doing so
will not affect the rest of the larger machine's computation. If the
values in registers a and b are not needed by the time the
controller gets to gcd-2 (or if these values can be moved to
other registers for safekeeping), we can change the machine so that
it uses registers a and b, rather than registers c
and d, in computing the second GCD as well as the first. If we
do this, we obtain the controller sequence shown in
figure
.
We have removed the duplicate data-path components
(so that the data paths are again as in figure
),
but the controller
now has two GCD sequences that differ only in their entry-point
labels. It would be better to replace these two sequences by branches
to a single sequence--a gcd subroutine--at the end of
which we branch back to the correct place in the main instruction
sequence. We can accomplish this as follows: Before branching to
gcd, we place a distinguishing value (such as 0 or 1) into a special
register,
continue. At the end of the gcd subroutine we
return either to after-gcd-1 or to after-gcd-2, depending
on the value of the continue register.
Figure
shows the relevant portion of the
resulting controller sequence, which includes only a single copy of the
gcd instructions.
This is a reasonable approach for handling small problems, but it would be awkward if there were many instances of GCD computations in the controller sequence. To decide where to continue executing after the gcd subroutine, we would need tests in the data paths and branch instructions in the controller for all the places that use gcd. A more powerful method for implementing subroutines is to have the continue register hold the label of the entry point in the controller sequence at which execution should continue when the subroutine is finished. Implementing this strategy requires a new kind of connection between the data paths and the controller of a register machine: There must be a way to assign to a register a label in the controller sequence in such a way that this value can be fetched from the register and used to continue execution at the designated entry point.
To reflect this ability, we will extend the assign
instruction of the register-machine language to allow a register to be
assigned as value a label from the controller sequence (as a special
kind of constant). We will also extend the goto instruction to
allow execution to continue at the entry point described by the
contents of a register rather than only at an entry point described by
a constant label. Using these new constructs we can terminate the
gcd subroutine with a branch to the location stored in the
continue register. This leads to the controller sequence shown in
figure
.
A machine with more than one subroutine could use multiple continuation registers (e.g., gcd-continue, factorial-continue) or we could have all subroutines share a single continue register. Sharing is more economical, but we must be careful if we have a subroutine (sub1) that calls another subroutine (sub2). Unless sub1 saves the contents of continue in some other register before setting up continue for the call to sub2, sub1 will not know where to go when it is finished. The mechanism developed in the next section to handle recursion also provides a better solution to this problem of nested subroutine calls.