Operator Notation

Consider the infix expression (X  Y) + (W  U), with parentheses added to
make the evaluation order perfectly obvious.

This is an arithmetic expression written in standard form, called “infix form”. There
are two other forms, prefix and postfix, which are occasionally used in software.

Infix form has the operator in between two operands, as in “X + Y”. It is what we
are used to, but it is hard for computers to process.

The two other forms are prefix and postfix. I give the LISP variant of prefix.

Prefix:(+ (  X Y) (  W U) )

Postfix:XYWU+

Implicit in each of these examples is the assumption that single letter variables are used.

Each of the prefix and postfix is easier for a computer to interpret than is infix.

The assumption is that the expression is scanned either left to right or right to left.
Here, we arbitrarily scan left to right.

We return to this expression, but first consider a much simpler expression.

Problem with Scanning an Infix Expression

Suppose that we have a five symbol expression. We are scanning left to right.
We assume:all variables are single alphabetic characters
Each alphabetic characters is taken singly and represents a variable

At the beginning we have five unknown symbols:

We read the first character and find it to be a variable:X 

We read the next character and find it to be an operator:X + 
Something is being added.

We read the next character and find it to be a variable:X + Y .

Can we do the addition now? It depends on what the next character is.

There are two basic possibilities:X + Y + Z
X + Y  Z

In the first option, we can do the addition immediately, forming (X + Y) to which Z is
later added. In the second option, we must wait and first do the multiplication to get
(Y  Z) to which X is added.

Scanning a Postfix Expression

With the same assumptions, we scan a five symbol postfix expression.

At the beginning we have five unknown symbols:

We read the first character and find it to be a variable:Y 

We read the next character and find it also to be a variable:Y Z 
At this point we need an operator.

We read the next character and find it to be an operator:Y Z 
Because this is postfix, the evaluator can immediately
form the product term (Y  Z)

We read the next character and find it to be a variable:Y Z  X 
At this point, we need another operator.
We have two operands: (Y  Z) and X.

We read the last character and find it to be an operator:Y Z  X +
We do the addition and have the term (Y  Z) + X.

The Stack Data Type

Stacks are LIFO (Last In, First Out) data structures. Stacks are the data structure most
naturally fit to evaluate expressions in postfix notation.

The stack is best viewed as an abstract data type with specific methods, each of which
has a well defined semantic (we can say exactly what it does).

We can also consider the stack as having a top. Items are added to
the stack top and then removed from the stack top.

The position of the stack top is indicated by a stack pointer (SP).
More on this later; it has two different definitions, each of which is valid.

The stack data type has three necessary methods, and two optional methods.

The mandatory methods are Push, Pop, and IsEmpty.

The standard optional methods are Top, and IsFull.

Note:When studying computer networks, we speak of a “protocol stack”, which has
nothing to do with the ADT (Abstract Data Type) stack that we study today.
We shall discuss the TCP/IP protocol stack later in this course.

Pushing Items onto the Stack

Consider the sequence: Push (X), Push (Y), and Push (Z). This sequence adds three
items to the stack, in the specified order.

Push X

Push Y

Push Z

After the third operation, Z is at the top of the stack and is the first to be removed
when a Pop operation is performed.

The SP (Stack Pointer) is a data item internal to the stack that indicates the location
of the top of the stack. It is never made public by any proper stack implementation.

Evaluation of the Postfix Expression Y Z  X +

Postfix notation is also called RPN for Reverse Polish Notation.

Rules:1.Scan left to right.
2.Upon reading a variable, push it onto the stack.
3.Upon reading a dyadic operator, pop two variables from the stack,
perform the operation, and push the result.

We read the first character and find it to be a variable:Y 

We read the next character and find it also to be a variable:Y Z 

We read the next character and find it to be an operator:Y Z 

Evaluation of the Postfix Expression Y Z  X +
Continues

We read the next character and find it to be a variable:Y Z  X 

We read the last character and find it to be an operator:Y Z  X +

After this, the stack will be popped and the value placed into some variable.

This example just looked at the expression, without considering the assignment.

Stack–Based (Zero Operand) Machine

Evaluate the expression Z = (X  Y) + (W  U)
which must be evaluated in its postfix (RPN) version: XYWU+

PushX

X

PushY

X / Y

Mult

XY

PushW

XY / W

PushU

XY / W / U

Mult

XY / WU

Add

XY +WU

PopZ

More Explicit Evaluation: W = Y  Z

It might be good at this point to make explicit a point that many consider obvious.

Suppose that before the evaluation above, W = 0, Y = 10, and Z = 15.

The push–pop code to execute this instruction will go as follows:

PUSH Y // Value of Y is placed on the stack.

PUSH Z // Value of Z is placed onto the stack.

MULT // Pop the top two values, multiply them
// and push the result onto the stack.

POP W // Pop the value at stack top and place into W.

At the start, here is what we have.

More Explicit Evaluation: Part 2

PUSH Y // Value of Y is placed on the stack.

PUSH Z // Value of Z is placed onto the stack.

More Explicit Evaluation: Part 3

MULT // Pop the top two values, multiply them

// and push the result onto the stack.

POP W // Pop the value at stack top and place into W.

Practical Evaluation of Postfix Operators

Here are some steps for manual evaluation.

1.Scan the postfix expression left to right. Find the first operator.

2.Determine how many operands that operator requires. Call it N.
Select the previous N operands, apply to the operator.

3.Remove the operator and its operands from the expression.
Replace these with the operands results.

4.Repeat

Here is an example, using single digit numbers to start with.

6 5 + 4 2 – *

(6 5 +) 4 2 – *

(11) 4 2 – *

(11) (4 2 –) *

(11) (2) *

22

Practical Evaluation of Prefix Operators

Note: We assume Lisp notation, with full use of parentheses.

1.Scan the expression left to right. If the expression does not contain another
parenthesized expression, evaluate it. Otherwise, attempt to evaluate its
subexpression. Formally, this is viewed as a tree traversal.

2.Keep “moving up” until the last is evaluated. Here is an example.
Again, it will start with single digit numbers in order to be more easily read.

Evaluate the prefix expression:( + ( * 4 2 ) ( – 5 2 ) ( * ( + 6 4) (+ 1 1 ) ) )

(* 4 2) can be evaluated, so:( + 8 ( – 5 2 ) ( * ( + 6 4) (+ 1 1 ) ) )

(– 5 2) is defined as 5 – 2, so:( + 8 3 ( * ( + 6 4) (+ 1 1 ) ) )

(+ 6 4) can be evaluated, so:( + 8 3 ( * 10 (+ 1 1 ) ) )

(+ 1 1) can be evaluated, so:( + 8 3 ( * 10 2 ) )

(* 10 2) can be evaluated, so:( + 8 3 20 )

(+ 8 3 20) = 31, so:31

Expression Tree for the Prefix Evaluation

The expression is ( + ( * 4 2 ) ( – 5 2 ) ( * ( + 6 4) (+ 1 1 ) ) ).

Software to evaluate this prefix expression would first build this tree (not very hard to do)
and then evaluate it. For example, the node (+ 6 4) would be replaced by 10, etc.

Expression Tree Evaluation

Single Accumulator Machine

Evaluate the expression Z = (X  Y) + (W  U)

This requires an extra storage location, which I call T for “Temp”.

LoadX// AC now has X

MultY// AC now has (X  Y)

StoreT// Now T = (X  Y). AC still has (X  Y)

LoadW// AC now has W

MultU// AC now has (W  U)

AddT// AC now has (X  Y) + (W  U)

StoreZ// And so does Z.

Multiple Register Machines

Evaluate the expression Z = (X  Y) + (W  U)

Standard CISC Approach(There are many variants of this one.)

LoadR1, X// R1 has X

MultR1, Y// R1 has (X  Y)

LoadR2, W// R2 has W

MultR2, U// R2 has (W  U)

AddR1, R2// R1 has (X  Y) + (W  U)

StoreR1, Z// And so does Z

Load–Store RISC
LoadR1, X// R1 has X

LoadR2, Y// R2 has Y

MultR1, R2// R1 has (X  Y)

LoadR2, W// R2 now has W

LoadR3, U// R3 has U

MultR2, R3// R2 has (W  U)

AddR1, R2// R1 has (X  Y) + (W  U)

StoreR1, Z// And so does Z.

Instruction Types

There are basic instruction types that are commonly supported.

1.Data Movement (Better called “Data Copy”)
These copy bytes of data from a source to a destination.
If X and Y are 32–bit real numbers, then the instruction Y = X makes a
copy of the four bytes associated with X and places them in the address for Y.

2.ArithmeticThe standard arithmetic operators are usually supported.
If division is supported, one usually also has the mod and rem functions.
On business–oriented machines, decimal arithmetic is always supported.
Graphics–oriented machines usually support saturation arithmetic.
Real number arithmetic is often not handled directly in the CPU, but by
a coprocessor attached to it.

Early on (Intel 80486 / 80487) this was a cost consideration.
RISC machines follow this approach in order to keep the CPU simple.

3.Boolean LogicHere I differ from the book’s description. Boolean instructions
are often used for non–Boolean purposes, such as bit manipulation.

The real Boolean instructions are the conditional branches.

More Instruction Types

4.Bit ManipulationThese use instructions that appear to be Boolean, but
in fact operate differently. This is a distinction lost on many students,
perhaps because it is rarely important. More on this in a moment.

5.Input / OutputThe computer must communicate with external devices,
including permanent storage, printers and keyboards, process control devices
(through dedicated I/O registers), etc.

A few architectures have a dedicated input device and a dedicated output
device. All commercial machines have “addressable” I/O devices; i.e., the
CPU issues a device identifier that appears to be an address to select the device.

From the CPU viewpoint, each I/O device is nothing more than a set of registers
(control, status, and data) and some timing constraints.

6.Transfer of ControlThese alter the normal sequential execution of code.
At the primitive machine language level, all we have is unconditional jumps,
conditional jumps, and subroutine invocations. Higher level languages elaborate
these features with “structured constructs” such as conditional statements,
counted loops, and conditional loops.

More on Logical Instructions vs. Bitwise Instructions

This is quite important in C, C++, and Java.

Consider the standard implementation of Boolean values as 8–bit bytes. This
is done for convenience in addressing by the CPU, as single bits are not addressable.

LetA = 0000 0000C = 0000 0010
B = 0000 0001D = 0000 0011

Logical operators in C++ANDExpression & Expression
OR||Expression || Expression

Bitwise operators in C++ANDExpression & Expression
OR|Expression | Expression
XOR^Expression ^ Expression

LogicalBitwise
A & B= 0(FALSE)A & B= 0000 0000
A || B = 1(TRUE)A | B= 0000 0001
CD= 1(TRUE)CD= 0000 0010
C || D= 1(TRUE)C | D= 0000 0011

Source:The Annotated C++ Reference Manual (Sections 5.11 – 5.15)
Margaret Ellis and Bjarne Stroustrup, Addison–Wesley, 1990.

A Context for Bitwise Operators

For simplicity I consider a very old (late 1960’s) Line Printer, a predecessor to today’s
laser printer. We examine the Status/Control register for the LP–11.

This register is called “LPS” for “Line Printer Status” in the literature.

We have here two status bits and a control bit.

Status bits
Bit 15ErrorIf Error = 1, then there is a device error, such as
power off, no paper in the printer, etc.

Bit 7DoneIf Done = 1, the printer is ready for the next line.

Control bit
Bit 6IEIf IE = 1, the printer is enabled to raise an interrupt
whenever Done becomes 1 or Error becomes 1.

More on the LPS Register

Why this arrangement of bits?

The PDP–11, for which the LP–11 was used, did not support 8–bit arithmetic.
A 16–bit integer was the smallest that the CPU would handle.

Viewed as a 16–bit signed integer, we note that the error bit (Bit 15) is the
sign bit. To test for an error, we just read the LPS into a register and test if it is negative.

Testing the Done Bit
Recall that the Done Bit is bit 7 and that 0000 0000 1000 0000 is 0x0080.

LPSE000 0000 DI00 0000
0x00800000 0000 1000 0000
LPS & 0x00800000 0000 D000 0000

If ( 0 = = (LPS & 0x0080) ) then the Line Printer is Not Done

Still More on the LPS Register

Testing and Setting the Interrupt Enable Bit
Recall that the Done Bit is bit 6 and that0000 0000 0100 0000 is 0x0040.
1111 1111 1011 1111 is 0xFFBF.

Testing the Interrupt Enable Bit
LPSE000 0000 DI00 0000
0x00400000 0000 0100 0000
LPS & 0x00400000 0000 0I00 0000

If ( 0 = = (LPS & 0x0040) ) then the Line Printer Interrupt is disabled.

Yet More on the LPS Register

Enabling Interrupts (Setting the I Bit)
LPSE000 0000 DI00 0000
0x00400000 0000 0100 0000
LPS | 0x0040E000 0000 D100 0000

Setting LPS = LPS | 0x0040 enables the interrupt and leaves the other bits unchanged.

Disabling Interrupts (Clearing the I Bit)
LPSE000 0000 DI00 0000
0xFFBF1111 1111 1011 1111
LPS & 0xFFBFE000 0000 D000 0000

Setting LPS = LPS 0xFFBF disables the interrupt and leaves the other bits unchanged.

Overview of Addressing Modes

Many computer instructions involve memory, and must generate memory addresses.

In this introduction, we shall discuss addressing modes in general terms. Later,
we shall investigate examples specifically based on IA–32 code.

All modes of addressing memory are based on an EA (Effective Address), which
is the actual memory address issued by the control unit.

First, we study some modes that do not address memory.

Immediate Mode

In this mode, there is no reference to memory. The argument is part of the instruction.

The simplest example is the Boz–7 instruction HALT. There is no argument.

In the IA–32 trap , int 21h, the hexadecimal value is part of the machine code.
The machine language would have two bytes: 0xCC 21; the first byte is the opcode.

Here is another example with decimal notation. MOV EAX, 1234.
This sets the value in the EAX register to decimal 1234.

Register Direct

Here is a simple IA–32 example: MOV EAX, EBX.
It moves the value in the EBX register into the EAX register. Memory is not referenced.

Calculating the Effective Address

By definition of the term “Effective Address”, all memory references use that address
when accessing memory. M[EA] denotes the contents of the effective address.

Consider two basic operators: Load from memory and Store to memory
Load Register from Memory:R  M[EA]
Store Register to Memory:M[EA]  (R)

Understanding addressing modes then becomes equivalent to understanding how
to calculate the Effective Address.

Each instruction referencing memory has fields (collections of bits) indicating how
to compute the EA. In general, the only fields used are:

the Address Fieldthis contains an address, which may be modified.

the Index Registerthis contains the register used as “array index”.
If this field is 0, indexing is not used.

the Indirect Bitthis is used for Indirect and Indexed–Indirect addressing.
If this is 1, indirect addressing is used; otherwise not.

Instructions that do not use an address field conventionally have that field set to 0.

Direct and Indirect Addressing

Opcode / Registers / Mode Flags / Address Field

In each of direct and indirect addressing, the computation is simple:
Just copy the address field itself.

In direct addressing, the address field contains the address of the argument.

In indirect addressing, the address field contains the address of a pointer to the
argument. Put another way, the contents of the memory indicated by the address field
contain the address of the argument.

In subroutine linkage, argument passing by preference basically uses indirect addressing.

Direct and Indirect Addressing (Example)

Consider the following address map as an example for our addressing modes.

Z represents address 0x0A. Z == 0x0A and M[Z] = 5.

Address / 5 / 6 / 7 / 8 / 9 / A / B / C / D / E / F / 10 / 11
Contents / 11 / 32 / 2C / 1E / 56 / 5 / 7A / 10 / 3 / F / E / D / 8

Direct AddressingLDR %R1, Z

Effective Address:EA = ZHere EA = 0x0A.

Effect:R1  M[Z]Here R1  M[0x0A] or R1 = 5.

Indirect AddressingLDR %R1, *Z

Effective Address:EA = M[Z]Here EA = M[0x0A] = 5

Effect:R1  M[EA] = M [ M[Z] ]
Here R1  M[0x05] or R1 = 11.

NOTE:These examples are based on the Boz–7 architecture, rather simpler than
the IA–32. The Boz–7 is a didactic design by the author of these notes.

Indexed Addressing

This is used to support array addressing in all computers. Languages such as C and
C++ implement this directly using “zero based” arrays.

In this mode, the contents of a general–purpose register are used as an index.

The contents of the register are added to the address field to form the effective address.

EA = (Address Field) + (Register)

Although we expect the index to be smaller than the value of the address, this
may not always (or often) be the case.

The contents of the address field should be considered as having a different data type
from those of the register, which holds a signed integer.

Indexed Addressing (Example)

LDR %R1, Z, 3// Z indexed by the general–purpose register R3.

Z represents address 0x0A. Z == 0x0A and M[Z] = 5. (R3) = = 4

Address / 5 / 6 / 7 / 8 / 9 / A / B / C / D / E / F / 10 / 11
Contents / 11 / 32 / 2C / 1E / 56 / 5 / 7A / 10 / 3 / F / E / D / 8

Effective address:EA = Z + (%R3)Here EA = 0x0A + 4 = 0x0E

Effect:R1  M[EA] = M[0x0E] or R1 = F

In this, the most common, variant of indexed addressing we see Z as an array.
The first five elements of the array are placed in memory as follows:

Memory Map / 0x0A / 0x0B / 0x0C / 0x0D / 0x0E
Contents: / Z[0] / Z[1] / Z[2] / Z[3] / Z[4]

Based Addressing

This is quite similar to indexed addressing, so much so that it seems redundant.

This mode arises from an entirely different consideration.

Indexed addressing basically supports the use of arrays.

Based addressing supports the Operating System in partitioning the user address space.
User addresses are mapped into physical addresses that cannot conflict.

Indexed–Indirect Addressing

This is a combination of both modes. Question: Which is done first?

Pre–Indexed Indirect: The indexing is done first. We have an array of pointers.

Post–Indexed Indirect: The indirect is done first. We have a pointer to an array.

Indexed–Indirect Addressing (Example)

Let Z refer to address 0xA, so that M[Z] = = 0x05. Let (R3) = 0x07.

Address / 5 / 6 / 7 / 8 / 9 / A / B / C / D / E / F / 10 / 11
Contents / 11 / 32 / 2C / 1E / 56 / 5 / 7A / 9 / 3 / F / E / D / 8

Pre–Indexed Indirect:LDR %R1, *Z, 3

Effective addressEA = M[ Z + (R3) ] = M[0xA + 0x7]
= M[ 0x11] = 0x08.

EffectR1 M[ M[Z + (R3)] ] or R1 M[0x08] or R1 1E

Post–Indexed Indirect:LDR %R1, *Z, 3

Effective addressEA= M[Z] + (R3) = M[0x0A] + 0x07
= 0x05 + 0x07 = 0x0C.

EffectR1 M[0x0C] or R1 0x09.

Double Indirect Addressing: Use 1

Suppose a common data structure that is used by a number of programs.
In modern systems, this could be a DLL (Dynamic Linked Library) module.

Each program contains a pointer to this structure, used to access that structure.

Singly indirect addressing, in which the pointer is stored in every program, presents
problems when the data block is moved by the memory manager. Every reference to
that block must be found and adjusted.

In doubly indirect addressing, all programs have a pointer to a data structure which itself
is a pointer. When the data block is moved, only this one pointer must be adjusted.

As an extra benefit, this intermediate pointer can be expanded to a data structure
containing an ACL (Access Control List) and other descriptors for the data block.