NCL 408: Indirect Registers
So far, every register operand we've written has identified one particular register:
MOVE r4 100
D.TXT r4
D.BLT
r4 means integer register 4. Which register the instruction uses is decided when we write the program.
But a register number is also just a number.
What if the program could choose that number while it was running?
Choosing a Register
Consider:
MOVE r0 4
MOVE rr0 100
The first instruction puts 4 into r0.
The second instruction uses rr0.
rr0 means:
the integer register whose number is stored in
r0
Since r0 contains 4, this instruction:
MOVE rr0 100
is currently writing to r4.
We can verify that normally:
MOVE r0 4
MOVE rr0 100
D.TXT r4
D.BLT
The Display shows:
100
This is indirect register access.
Instead of naming the register directly, we use another integer register to select it.
Reading Indirectly
Indirect registers work as sources too.
MOVE r0 4
MOVE r4 100
MOVE r1 rr0
Since r0 contains 4, rr0 refers to r4.
The last instruction therefore reads the value from r4 and writes it into r1.
Afterward:
r0 = 4
r1 = 100
r4 = 100
Change the value in r0, and the same rr0 operand refers to a different integer register.
For example:
MOVE r4 100
MOVE r5 200
MOVE r0 4
D.TXT rr0
D.TXT " "
MOVE r0 5
D.TXT rr0
D.BLT
The Display shows:
100 200
The instructions using rr0 didn't change.
The register selected by rr0 did.
Integer and String Registers
Indirect access can select either kind of general-purpose register.
| Operand | Meaning |
|---|---|
rr0 |
Integer register whose number is stored in r0 |
sr0 |
String register whose number is stored in r0 |
For example:
MOVE r0 3
SMOVE sr0 "Hello"
D.TXT s3
D.BLT
Since r0 contains 3, sr0 refers to s3.
The Display shows:
Hello
Indirect register operands have two parts:
rr0uses the value inr0to select an integer register.sr0uses the value inr0to select a string register.
In both cases, r0 is an ordinary integer register containing the register number. The leading r or s determines which kind of register that number selects.
There is only one level of indirection. rr0 reads r0 once and uses its value as an integer register number.
A Small Indexed Collection
In NCL 402, we learned to use part of the value stack as indexed storage.
If our data is already stored in consecutive registers, indirect access gives us another option.
Suppose:
MOVE r8 10
MOVE r9 20
MOVE r10 30
MOVE r11 40
We can think of these four registers as a small collection beginning at register 8.
If an index is stored in r0:
MOVE r0 2
we can calculate the corresponding register number:
ADD r1 r0 8
r1 now contains 10.
That means:
D.TXT rr1
D.BLT
displays:
30
The same code can access any of the four values. Only the index needs to change.
MOVE r0 0
ADD r1 r0 8
D.TXT rr1
selects r8.
MOVE r0 1
ADD r1 r0 8
D.TXT rr1
selects r9.
The familiar calculation:
base + index
has appeared again.
This time, the result isn't a position in the value stack.
It's a register number.
Registers or Indexed Data?
Indirect registers don't replace the indexed value-stack storage we learned in NCL 402.
They solve different problems.
| Need | Good fit |
|---|---|
| Temporary values and subroutine interfaces | PUSH and POP |
| Small collection usually accessed by fixed names | Consecutive registers |
| Small collection needing occasional runtime indexing | Consecutive registers with indirect access |
| Larger indexed collection | Indexed value-stack storage |
Our Noughts and Crosses board is a good example of a small collection:
#cell0 r20
#cell1 r21
#cell2 r22
#cell3 r23
#cell4 r24
#cell5 r25
#cell6 r26
#cell7 r27
#cell8 r28
There are only nine cells, and direct access is often useful.
If we specifically mean the center cell:
MOVE #cell4 #player
is wonderfully clear.
Likewise, a diagonal can still be written using its meaningful aliases:
ADD #line #cell0 #cell4
ADD #line #line #cell8
Indirect access doesn't make those expressions better.
It becomes useful when the register itself is chosen at runtime.
Our game already has exactly such a value:
#selection r0
#selection ranges from 0 through 8.
The board occupies integer registers 20 through 28.
So:
ADD r1 #selection 20
MOVE rr1 #player
turns the selection into the corresponding board register and places the player's value there.
If #selection is 0, r1 becomes 20, so rr1 refers to r20.
If #selection is 4, r1 becomes 24, so rr1 refers to r24.
If #selection is 8, r1 becomes 28, so rr1 refers to r28.
The board didn't need to move anywhere.
It was already stored somewhere we could index.
Does this data need to move into indexed storage, or is it already stored somewhere that can be indexed?
For something much larger, the answer may be different. A 64-cell chess board cannot reasonably consume 64 integer registers when the entire CPU only has 32.
The indexed value-stack techniques from NCL 402 remain useful for collections like that.
Representation depends on how the data is used.
Existing Instructions Become Dynamic
Indirect registers aren't special versions of MOVE.
They're register operands.
That means we can use them with other instructions too.
Suppose two integer registers contain the numbers of registers we want to exchange:
MOVE r0 8
MOVE r1 9
We can write:
SWAP rr0 rr1
If r0 contains 8 and r1 contains 9, this swaps r8 and r9.
Change those values and the same SWAP instruction exchanges a different pair of registers.
This is especially useful because the locations are selected at runtime.
Consider trying to build the same operation manually:
MOVE r2 rr0
MOVE rr0 rr1
MOVE rr1 r2
That works only if r2 is safe to use as temporary storage.
But the values in r0 and r1 are chosen at runtime. One of them could select r2 itself.
Our temporary register could collide with one of the values we're trying to exchange.
SWAP rr0 rr1
needs no scratch register.
Back in NCL 401, SWAP saved us from manually exchanging two known registers.
Now the same abstraction can exchange two registers that weren't known until the program was already running.
Invalid Register Numbers
Indirect access still has to select a register that exists.
For rr0, the value in r0 must identify a valid integer register.
For sr0, it must identify a valid string register.
For example:
MOVE r0 100
MOVE rr0 5
attempts to access integer register 100.
There is no such register.
An invalid register access causes a fatal:
REGISTER_ERROR
The same applies when reading indirectly.
If a value will be used as a register number, your program is responsible for making sure that value is valid.
Try It
Start with three values:
MOVE r8 10
MOVE r9 20
MOVE r10 30
Use r0 as an index:
MOVE r0 1
Without directly naming r9, change the selected value to 99.
First, turn the index into a register number:
ADD r1 r0 8
Then use that register number indirectly:
MOVE rr1 99
Display all three values:
D.TXT r8
D.TXT " "
D.TXT r9
D.TXT " "
D.TXT r10
D.BLT
You should see:
10 99 30
Now change only:
MOVE r0 1
Try 0 and 2.
The same ADD and indirect MOVE can modify any of the three registers.
Our Noughts and Crosses board already lives in nine consecutive registers. Until now, a runtime selection couldn't directly choose one of them, so the game used control flow to bridge that gap.
It doesn't need to anymore.
In the next lesson, NCL 409: Refactoring the Game, we're going back to the game and rebuilding it with what we've learned.