NCL 401: The Value Stack

So far, whenever we wanted to keep a value around, we put it in a register.

That works well when the value has a clear purpose and deserves its own register.

Sometimes, though, we only need to put a value aside for a moment.

Suppose we want to temporarily reuse r0:

MOVE r0 42
MOVE r1 r0

MOVE r0 100
D.TXT r0

MOVE r0 r1
D.TXT r0
D.BLT

This works.

r1 is acting as temporary storage while we use r0 for something else.

NCL gives us another place to store temporary values: the value stack.

Pushing a Value

The PUSH instruction places a value onto the value stack.

PUSH <value>

For example:

MOVE r0 42
PUSH r0

This copies the current value of r0 onto the stack.

It does not change r0.

We can now overwrite the register without losing the value we pushed:

MOVE r0 42
PUSH r0

MOVE r0 100

At this point, r0 contains 100, while the stack still contains 42.

Popping a Value

The POP instruction removes the most recently pushed value and stores it in a destination register.

POP <dst>

For example:

MOVE r0 42
PUSH r0

MOVE r0 100
POP r0

After the POP, r0 contains 42 again.

Unlike reading a register, POP removes the value from the stack.

We can rewrite our earlier example without using r1 as temporary storage:

MOVE r0 42
PUSH r0

MOVE r0 100
D.TXT r0

POP r0
D.TXT r0
D.BLT

The stack lets us put a value aside, use the register for something else, then recover the original value later.

Last In, First Out

The value stack can hold more than one value.

Consider:

PUSH 10
PUSH 20
PUSH 30

POP r0
POP r1
POP r2

We can trace the stack as each instruction executes.

In this table, the top of the stack is on the right.

Instruction Value stack r0 r1 r2
PUSH 10 10
PUSH 20 10, 20
PUSH 30 10, 20, 30
POP r0 10, 20 30
POP r1 10 30 20
POP r2 empty 30 20 10

Notice the order.

10 was pushed first, but popped last.

30 was pushed last, but popped first.

This is called last in, first out, usually shortened to LIFO.

The most recently pushed value is called the top of the stack.

PUSH adds a value to the top.

POP removes the value from the top.

Using the Stack as Temporary Storage

LIFO behavior is especially useful when several temporary values need to be preserved.

For example:

MOVE r0 5
PUSH r0

MUL r0 r0 4
PUSH r0

ADD r0 r0 3
D.TXT r0
D.TXT " "

POP r0
D.TXT r0
D.TXT " "

POP r0
D.TXT r0
D.BLT

The values change like this:

5
20
23

Then the stack restores the earlier values in reverse order:

23 20 5

Each PUSH remembers a value.

Each POP returns to the most recently remembered one.

Building a Register Swap

Suppose two registers contain:

MOVE r0 10
MOVE r1 20

We want to exchange their values so that:

r0 = 20
r1 = 10

Using only MOVE, we need a third register to hold one value temporarily:

MOVE r2 r0
MOVE r0 r1
MOVE r1 r2
Instruction r0 r1 r2
Before 10 20
MOVE r2 r0 10 20 10
MOVE r0 r1 20 20 10
MOVE r1 r2 20 10 10

Now that we have the value stack, we can avoid using a scratch register:

PUSH r0
MOVE r0 r1
POP r1

The stack temporarily holds the original value of r0.

Instruction Value stack r0 r1
Before empty 10 20
PUSH r0 10 10 20
MOVE r0 r1 10 20 20
POP r1 empty 20 10

This is already useful: temporary storage no longer needs to occupy another register.

Swapping two registers is common enough that NCL also provides a direct instruction:

SWAP r0 r1

This exchanges the values in the two registers in a single instruction.

So we now know three ways to perform the same operation:

Method Instructions Scratch register required
Three MOVEs 3 Yes
PUSH, MOVE, POP 3 No
SWAP 1 No

The more specialized instruction does not give the processor a fundamentally new ability.

It gives a useful operation a direct name.

Keeping the Stack Balanced

When the value stack is being used for temporary storage, values pushed onto it should eventually be removed again.

For example:

PUSH 10
PUSH 20

POP r0

After this code runs, 20 has been popped into r0, but 10 is still on the stack.

Nothing automatically removes it.

If code repeatedly pushes more values than it pops, values continue accumulating on the stack.

A section of code that restores the stack to the state it had before is often described as being balanced.

For example:

PUSH r0
PUSH r1

POP r1
POP r0

adds two values, then removes two values.

The stack ends exactly as it began.

Stack Limits

The NCS/e value stack can hold up to 128 values.

Trying to push another value when all 128 positions are occupied is called stack overflow.

Trying to pop a value when the stack is empty is called stack underflow.

Both conditions cause a fatal STACK_ERROR.

Operation Invalid condition Result
PUSH Value stack is full Fatal STACK_ERROR
POP Value stack is empty Fatal STACK_ERROR

A stack error usually means that the program's pushes and pops do not match the way the program expects.

Try it

Start with:

MOVE r0 10
PUSH r0

ADD r0 r0 5
PUSH r0

MUL r0 r0 2

At this point:

r0 = 30

and two earlier values are waiting on the stack.

Using POP, D.TXT, and D.BLT, display:

30 15 10

Try tracing the stack before running the program.


PUSH and POP let us use the value stack without worrying about where its values are stored.

In the next lesson, NCL 402: Indexed Data, we'll look at how the processor keeps track of that storage — and what happens when we control it ourselves.