Hubbry Logo
Random-access machineRandom-access machineMain
Open search
Random-access machine
Community hub
Random-access machine
logo
7 pages, 0 posts
0 subscribers
Be the first to start a discussion here.
Be the first to start a discussion here.
Contribute something
Random-access machine
Random-access machine
from Wikipedia

In computer science, random-access machine (RAM or RA-machine) is a model of computation that describes an abstract machine in the general class of register machines. The RA-machine is very similar to the counter machine but with the added capability of 'indirect addressing' of its registers. The 'registers' are intuitively equivalent to main memory of a common computer, except for the additional ability of registers to store natural numbers of any size. Like the counter machine, the RA-machine contains the execution instructions in the finite-state portion of the machine (the so-called Harvard architecture).

The RA-machine's equivalent of the universal Turing machine – with its program in the registers as well as its data – is called the random-access stored-program machine or RASP-machine. It is an example of the so-called von Neumann architecture and is closest to the common notion of a computer.

Together with the Turing machine and counter-machine models, the RA-machine and RASP-machine models are used for computational complexity analysis. Van Emde Boas (1990) calls these three together with the pointer machine, "sequential machine" models, to distinguish them from "parallel random-access machine" models.

Informal description

[edit]

An RA-machine consists of the following:

  • an infinite number of memory locations called "registers"; each register has an address which is a natural number or zero; each register can store exactly one natural number of any size, or a zero
  • the instruction table, or just "table", containing execution instructions; the exact instruction set varies depending on the author; common instructions include: increment, decrement, clear to zero, copy, conditional jump, halt; other instructions are unnecessary because they can be created by combinations of instructions from the instruction set
  • one special register called the "instruction register" (IR); this register points to the instruction being executed in the instruction table

For a description of a similar concept, but humorous, see the esoteric programming language Brainfuck.[1]

Introduction to the model

[edit]

The concept of a random-access machine (RAM) starts with the simplest model of all, the so-called counter machine model. Two additions move it away from the counter machine, however. The first enhances the machine with the convenience of indirect addressing; the second moves the model toward the more conventional accumulator-based computer with the addition of one or more auxiliary (dedicated) registers, the most common of which is called "the accumulator".

Formal definition

[edit]

A random-access machine (RAM) is an abstract computational-machine model identical to a multiple-register counter machine with the addition of indirect addressing. At the discretion of instruction from its finite-state machine's TABLE, the machine derives a "target" register's address either (i) directly from the instruction itself, or (ii) indirectly from the contents (e.g. number, label) of the "pointer" register specified in the instruction.

By definition: A register is a location with both an address (a unique, distinguishable designation/locator equivalent to a natural number) and a content – a single natural number. For precision we will use the quasi-formal symbolism from Boolos-Burgess-Jeffrey (2002) to specify a register, its contents, and an operation on a register:

  • [r] means "the contents of register with address r". The label "r" here is a "variable" that can be filled with a natural number or a letter (e.g. "A") or a name.
  • → means "copy/deposit into", or "replaces", but without destruction of the source
Example: [3] +1 → 3; means "The contents of source register with address "3", plus 1, is put into destination register with address "3" (here source and destination are the same place). If [3]=37, that is, the contents of register 3 is the number "37", then 37+1 = 38 will be put into register 3.
Example: [3] → 5; means "The contents of source register with address "3" is put into destination register with address "5". If [3]=38, that is, the contents of register 3 is the number 38, then this number will be put into register 5. The contents of register 3 are not disturbed by this operation, so [3] continues to be 38, now the same as [5].

Definition: A direct instruction is one that specifies in the instruction itself the address of the source or destination register whose contents will be the subject of the instruction. Definition: An indirect instruction is one that specifies a "pointer register", the contents of which is the address of a "target" register. The target register can be either a source or a destination (the various COPY instructions provide examples of this). A register can address itself indirectly.

For want of a standard/convention this article will specify "direct/indirect", abbreviated as "d/i", as a parameter (or parameters) in the instruction:
Example: COPY ( d, A, i, N ) means directly d get the source register's address (register "A") from the instruction itself but indirectly i get the destination address from pointer-register N. Suppose [N]=3, then register 3 is the destination and the instruction will do the following: [A] → 3.

Definition: The contents of source register is used by the instruction. The source register's address can be specified either (i) directly by the instruction, or (ii) indirectly by the pointer register specified by the instruction.

Definition: The contents of the pointer register is the address of the "target" register.

Definition: The contents of the pointer register points to the target register – the "target" may be either a source or a destination register.

Definition: The destination register is where the instruction deposits its result. The source register's address can be specified either (i) directly by the instruction, or (ii) indirectly by the pointer register specified by the instruction. The source and destination registers can be one.

Refresher: The counter-machine model

[edit]
Melzak (1961) provides an easy visualization of a counter machine: its "registers" are holes in the ground, and these holes hold pebbles. Per an instruction, into and out of these holes "the computer" (person or machine) adds (INCrements) or removes (DECrements) a single pebble. As needed, additional pebbles come from, and excess pebbles go back into, an infinite supply; if the hole is too small to accommodate the pebbles the "computer" digs the hole bigger.
Minsky (1961) and Hopcroft-Ullman 1979 (p. 171) offer the visualization of a multi-tape Turing machine with as many left-ended tapes as "registers". Each tape's length is unbounded to the right, and every square is blank except for the left end, which is marked. The distance of a tape's "head" from its left end, measured in numbers of tape-squares, represents the natural number in "the register". To DECrement the count of squares the tape head moves left; INCrement it moves right. There is no need to print or erase marks on the tape; the only conditional instructions are to check to see if the head is at the left end, by testing a left-end mark with a "Jump-if-marked instruction".
The following instruction "mnemonics" e.g. "CLR (r)" are arbitrary; no standard exists.

The register machine has, for a memory external to its finite-state machine – an unbounded (cf: footnote|countable and unbounded) collection of discrete and uniquely labelled locations with unbounded capacity, called "registers". These registers hold only natural numbers (zero and positive integers). Per a list of sequential instructions in the finite-state machine's TABLE, a few (e.g. 2) types of primitive operations operate on the contents of these "registers". Finally, a conditional-expression in the form of an IF-THEN-ELSE is available to test the contents of one or two registers and "branch/jump" the finite-state machine out of the default instruction-sequence.

Base model 1: The model closest to Minsky's (1961) visualization and to Lambek (1961):

  • { INCrement contents of register r, DECrement contents of register r, IF contents of register r is Zero THEN Jump to instruction Iz ELSE continue to next instruction }:
Instruction Mnemonic Action on register(s) "r" Action on finite-state machine's Instruction Register, IR
INCrement INC ( r ) [r] + 1 → r [IR] + 1 → IR
DECrement DEC ( r ) [r] - 1 → r [IR] + 1 → IR
Jump if Zero JZ ( r, z ) none IF [r] = 0 THEN z → IR ELSE [IR] + 1 → IR
Halt H none [IR] → IR

Base model 2: The "successor" model (named after the successor function of the Peano axioms):

  • { INCrement the contents of register r, CLeaR the contents of register r, IF contents of register rj Equals the contents of register rk THEN Jump to instruction Iz ELSE goto to next instruction }
Instruction Mnemonic Action on register(s) "r" Action on finite-state machine's Instruction Register, IR
CLeaR CLR ( r ) 0 → r [IR] + 1 → IR
INCrement INC ( r ) [r] + 1 → r [IR] + 1 → IR
Jump if Equal JE (r1, r2, z) none IF [r1] = [r2] THEN z → IR ELSE [IR] + 1 → IR
Halt H none [IR] → IR

Base model 3: Used by Elgot-Robinson (1964) in their investigation of bounded and unbounded RASPs – the "successor" model with COPY in the place of CLEAR:

  • { INCrement the contents of register r, COPY the contents of register rj to register rk, IF contents of register rj Equals the contents of register rk then Jump to instruction Iz ELSE goto to next instruction }
Instruction Mnemonic Action on register(s) "r" Action on finite-state machine's Instruction Register, IR
COPY COPY (r1, r2) [r1] → r2 [IR] + 1 → IR
INCrement INC ( r ) [r] + 1 → r [IR] + 1 → IR
Jump if Equal JE (r1, r2, z) none IF [r1] = [r2] THEN z → IR ELSE [IR] + 1 → IR
Halt H none [IR] → IR

Creating "convenience instructions" from the base sets

[edit]

The three base sets 1, 2, or 3 above are equivalent in the sense that one can create the instructions of one set using the instructions of another set (an interesting exercise: a hint from Minsky (1967) – declare a reserved register e.g. call it "0" (or Z for "zero" or E for "erase") to contain the number 0). The choice of model will depend on which an author finds easiest to use in a demonstration, or a proof, etc.

Moreover, from base sets 1, 2, or 3 we can create any of the primitive recursive functions ( cf Minsky (1967), Boolos-Burgess-Jeffrey (2002) ). (How to cast the net wider to capture the total and partial mu recursive functions will be discussed in context of indirect addressing). However, building the primitive recursive functions is difficult because the instruction sets are so ... primitive (tiny). One solution is to expand a particular set with "convenience instructions" from another set:

These will not be subroutines in the conventional sense but rather blocks of instructions created from the base set and given a mnemonic. In a formal sense, to use these blocks we need to either (i) "expand" them into their base-instruction equivalents – they will require the use of temporary or "auxiliary" registers so the model must take this into account, or (ii) design our machines/models with the instructions 'built in'.
Example: Base set 1. To create CLR (r) use the block of instructions to count down register r to zero. Observe the use of the hint mentioned above:
  • CLR (r) =equiv
  • loop: JZ (r, exit)
  • DEC (r)
  • JZ (0, loop)
  • exit: etc.

Again, all of this is for convenience only; none of this increases the model's intrinsic power.

For example: the most expanded set would include each unique instruction from the three sets, plus unconditional jump J (z) i.e.:

  • { CLR (r), DEC (r), INC (r), CPY ( rs, rd ), JZ (r, z), JE ( rj, rk, z ), J(z) }

Most authors pick one or the other of the conditional jumps, e.g. Shepherdson-Sturgis (1963) use the above set minus JE (to be perfectly accurate they use JNZ – Jump if Not Zero in place of JZ; yet another possible convenience instruction).

The "indirect" operation

[edit]

Example of indirect addressing

[edit]

In our daily lives the notion of an "indirect operation" is not unusual.

Example: A treasure hunt.
At location "Tom_&_Becky's_cave_in_pirate_chest" will be where we can find a map directing us to "the treasure":
(1) We go to location "Tom_&_Becky's_cave..." and dig around until we find a wooden box
(2) Inside the box is a map to the location of the treasure: "under_Thatcher's_front_porch"
(3) We go to location "under_Thatcher's_front_porch", jackhammer away the concrete, and discover "the treasure": a sack of rusty door-knobs.

Indirection specifies a location identified as the pirate chest in "Tom_&_Becky's_cave..." that acts as a pointer to any other location (including itself): its contents (the treasure map) provides the "address" of the target location "under_Thatcher's_front_porch" where the real action is occurring.

Why the need for an indirect operation: Two major problems with the counter-machine model

[edit]

In the following one must remember that these models are abstract models with two fundamental differences from anything physically real: unbounded numbers of registers each with unbounded capacities. The problem appears most dramatically when one tries to use a counter-machine model to build a RASP that is Turing equivalent and thus compute any partial mu recursive function:

  • Melzak (1961) added indirection to his "hole-and-pebble" model so that his model could modify itself with a "computed goto" and provides two examples of its use ("Decimal representation in the scale of d" and "Sorting by magnitude", whether these are used in his proof that the model is Turing equivalent is unclear since "the program itself is left to the reader as an exercise" (p. 292)). Minsky (1961, 1967) was able to demonstrate that, with suitable (but difficult-to-use) Gödel number encoding, the register model did not need indirection to be Turing equivalent; but it did need at least one unbounded register. As noted below, Minsky (1967) hints at the problem for a RASP but doesn't offer a solution. Elgot and Robinson (1964) proved that their RASP model P0 – it has no indirection capability – cannot compute all "recursive sequential functions" (ones that have parameters of arbitrary length) if it does not have the capability of modifying its own instructions, but it can via Gödel numbers if it does (p. 395-397; in particular figure 2 and footnote p. 395). On the other hand their RASP model P'0 equipped with an "index register" (indirect addressing) can compute all the "partial recursive sequential functions" (the mu recursive functions) (p. 397-398).
Cook and Reckhow (1973) say it most succinctly:
The indirect instructions are necessary in order for a fixed program to access an unbounded number of registers as the inputs vary." (p. 73)
  • Unbounded capacities of registers versus bounded capacities of state-machine instructions: The so-called finite state part of the machine is supposed to be – by the normal definition of algorithm – very finite both in the number of "states" (instructions) and the instructions' sizes (their capacity to hold symbols/signs). So how does a state machine move an arbitrarily large constant directly into a register, e.g. MOVE (k, r) (Move constant k to register r)? If huge constants are necessary they must either start out in the registers themselves or be created by the state machine using a finite number of instructions e.g. multiply and add subroutines using INC and DEC (but not a quasi-infinite number of these!).
Sometimes the constant k will be created by use of CLR ( r ) followed by INC ( r ) repeated k times – e.g. to put the constant k=3 into register r, i.e. 3 → r, so at the end of the instruction [r]=3: CLR (r), INC (r), INC (r), INC (r). This trick is mentioned by Kleene (1952) p. 223. The problem arises when the number to be created exhausts the number of instructions available to the finite state machine; there is always a bigger constant than the number of instructions available to the finite state machine.
  • Unbounded numbers of registers versus bounded state-machine instructions: This is more severe than the first problem. In particular, this problem arises when we attempt to build a so-called RASP, a "universal machine" (see more at Universal Turing machine) that uses its finite-state machine to interpret a "program of instructions" located in its registers – i.e. we are building what is nowadays called a computer with the von Neumann architecture.
Observe that the counter machine's finite-state machine must call out a register explicitly (directly) by its name/number: INC (65,356) calls out register number "65,365" explicitly. If the number of registers exceeds the capability of the finite state machine to address them, then registers outside the bounds will be unreachable. For example, if the finite-state machine can only reach 65,536 = 216 registers then how can it reach the 65,537th?

So how do we address a register beyond the bounds of the finite-state machine? One approach would be to modify the program-instructions (the ones stored in the registers) so that they contain more than one command. But this too can be exhausted unless an instruction is of (potentially) unbounded size. So why not use just one "über-instruction" – one really really big number – that contains all the program instructions encoded into it! This is how Minsky solves the problem, but the Gödel numbering he uses represents a great inconvenience to the model, and the result is nothing at all like our intuitive notion of a "stored program computer".

Elgot and Robinson (1964) come to a similar conclusion with respect to a RASP that is "finitely determined". Indeed it can access an unbounded number of registers (e.g. to fetch instructions from them) but only if the RASP allows "self modification" of its program instructions, and has encoded its "data" in a Gödel number (Fig. 2 p. 396).

In the context of a more computer-like model using his RPT (repeat) instruction Minsky (1967) tantalizes us with a solution to the problem (cf p. 214, p. 259) but offers no firm resolution. He asserts:

"In general a RPT operation could not be an instruction in the finite-state part of the machine ... this might exhaust any particular amount of storage allowed in the finite part of the computer [sic, his name for his RAM models]. RPT operations require infinite registers of their own." (p. 214).

He offers us a bounded RPT that together with CLR (r) and INC (r) can compute any primitive recursive function, and he offers the unbounded RPT quoted above that as playing the role of μ operator; it together with CLR (r) and INC (r) can compute the mu recursive functions. But he does not discuss "indirection" or the RAM model per se.

From the references in Hartmanis (1971) it appears that Cook (in his lecture notes while at UC Berkeley, 1970) has firmed up the notion of indirect addressing. This becomes clearer in the paper of Cook and Reckhow (1973) – Cook is Reckhow's Master's thesis advisor. Hartmanis' model – quite similar to Melzak's (1961) model – uses two and three-register adds and subtracts and two parameter copies; Cook and Reckhow's model reduce the number of parameters (registers called out in the program instructions) to one call-out by use of an accumulator "AC".

The solution in a nutshell: Design our machine/model with unbounded indirection – provide an unbounded "address" register that can potentially name (call out) any register no matter how many there are. For this to work, in general, the unbounded register requires an ability to be cleared and then incremented (and, possibly, decremented) by a potentially infinite loop. In this sense the solution represents the unbounded μ operator that can, if necessary, hunt ad infinitum along the unbounded string of registers until it finds what it is looking for. The pointer register is exactly like any other register with one exception: under the circumstances called "indirect addressing" it provides its contents, rather than the address-operand in the state machine's TABLE, to be the address of the target register (including possibly itself!).

Bounded indirection and the primitive recursive functions

[edit]

If we eschew the Minsky approach of one monster number in one register, and specify that our machine model will be "like a computer" we have to confront this problem of indirection if we are to compute the recursive functions (also called the μ-recursive functions ) – both total and partial varieties.

Our simpler counter-machine model can do a "bounded" form of indirection – and thereby compute the sub-class of primitive recursive functions – by using a primitive recursive "operator" called "definition by cases" (defined in Kleene (1952) p. 229 and Boolos-Burgess-Jeffrey p. 74). Such a "bounded indirection" is a laborious, tedious affair. "Definition by cases" requires the machine to determine/distinguish the contents of the pointer register by attempting, time after time until success, to match this contents against a number/name that the case operator explicitly declares. Thus the definition by cases starts from e.g. the lower bound address and continues ad nauseam toward the upper bound address attempting to make a match:

Is the number in register N equal to 0? If not then is it equal to 1? 2? 3? ... 65364? If not then we're at the last number 65365 and this had better be the one, else we have a problem!

"Bounded" indirection will not allow us to compute the partial recursive functions – for those we need unbounded indirection aka the μ operator.

Suppose we had been able to continue on to number 65367, and in fact that register had what we were looking for. Then we could have completed our calculation successfully! But suppose 65367 didn't have what we needed. How far should we continue to go?

To be Turing equivalent the counter machine needs to either use the unfortunate single-register Minsky Gödel number method, or be augmented with an ability to explore the ends of its register string, ad infinitum if necessary. (A failure to find something "out there" defines what it means for an algorithm to fail to terminate; cf Kleene (1952) pp. 316ff Chapter XII Partial Recursive Functions, in particular p. 323-325.) See more on this in the example below.

Unbounded indirection and the partial recursive functions

[edit]

For unbounded indirection we require a "hardware" change in our machine model. Once we make this change the model is no longer a counter machine, but rather a random-access machine.

Now when e.g. INC is specified, the finite-state machine's instruction will have to specify where the address of the register of interest will come from. This where can be either (i) the state machine's instruction that provides an explicit label, or (ii) the pointer-register whose contents is the address of interest. Whenever an instruction specifies a register address it now will also need to specify an additional parameter "i/d" – "indirect/direct". In a sense this new "i/d" parameter is a "switch" that flips one way to get the direct address as specified in the instruction or the other way to get the indirect address from the pointer register (which pointer register – in some models every register can be a pointer register – is specified by the instruction). This "mutually exclusive but exhaustive choice" is yet another example of "definition by cases", and the arithmetic equivalent shown in the example below is derived from the definition in Kleene (1952) p. 229.

Example: CPY ( indirectsource, rsource, directdestination, rdestination )
Assign a code to specify direct addressing as d="0" and indirect addressing as i="1". Then our machine can determine the source address as follows:
i*[rs] + (1-i)*rs
For example, suppose the contents of register 3 are "5" (i.e. [3]=5 ) and the contents of register 4 are "2" (i.e. [4]=2 ):
Example: CPY ( 1, 3, 0, 4 ) = CPY ( indirect, reg 3, direct, reg 4 )
1*[3] + 0*3 = [3] = source-register address 5
0*[4] + 1*4 = 4 = destination-register address 4
Example: CPY ( 0, 3, 0, 4 )
0*[3] + 1*3 = 3 = source-register address 3
0*[4] + 1*4 = 4 = destination-register address 4
Example: CPY ( 0, 3, 1, 4 )
0*[3] + 1*3 = 3 = source-register address 3
1*[4] + 0*4 = [4] = destination-register address 2

The indirect COPY instruction

[edit]

Probably the most useful of the added instructions is COPY. Indeed, Elgot-Robinson (1964) provide their models P0 and P'0 with the COPY instructions, and Cook-Reckhow (1973) provide their accumulator-based model with only two indirect instructions – COPY to accumulator indirectly, COPY from accumulator indirectly.

A plethora of instructions: Because any instruction acting on a single register can be augmented with its indirect "dual" (including conditional and unconditional jumps, cf the Elgot-Robinson model), the inclusion of indirect instructions will double the number of single parameter/register instructions (e.g. INC (d, r), INC (i, r)). Worse, every two parameter/register instruction will have 4 possible varieties, e.g.:

CPY (d, rs, d, rd ) = COPY directly from source-register directly to destination-register
CPY (i, rsp, d, rd ) = COPY to destination-register indirectly using the source address to be found in the source-pointer register rsp.
CPY (d, rs, i, rdp ) = COPY contents of source-register indirectly into register using destination address to be found in the destination-pointer register rdp.
CPY (i, rsp, i, rdp ) = COPY indirectly the contents of the source register with address to be found in source-pointer register rsp, into the destination register with address to be found in the destination-pointer register rdp)

In a similar manner every three-register instruction that involves two source registers rs1 rs2 and a destination register rd will result in 8 varieties, for example the addition:

[rs1] + [rs2] → rd

will yield:

  • ADD ( d, rs1, d, rs2, d, rd )
  • ADD ( i, rsp1, d, rs2, d, rd )
  • ADD ( d, rs1, i, rsp2, d, rd )
  • ADD ( i, rsp1, i, rsp2, d, rd )
  • ADD ( d, rs1, d, rs2, i, rdp )
  • ADD ( i, rsp1, d, rs2, i, rdp )
  • ADD ( d, rs1, i, rsp2, i, rdp )
  • ADD ( i, rsp1, i, rsp2, i, rdp )

If we designate one register to be the "accumulator" (see below) and place strong restrictions on the various instructions allowed then we can greatly reduce the plethora of direct and indirect operations. However, one must be sure that the resulting reduced instruction-set is sufficient, and we must be aware that the reduction will come at the expense of more instructions per "significant" operation.

The notion of "accumulator A"

[edit]

Historical convention dedicates a register to the accumulator, an "arithmetic organ" that literally accumulates its number during a sequence of arithmetic operations:

"The first part of our arithmetic organ ... should be a parallel storage organ which can receive a number and add it to the one already in it, which is also able to clear its contents and which can store what it contains. We will call such an organ an Accumulator. It is quite conventional in principle in past and present computing machines of the most varied types, e.g. desk multipliers, standard IBM counters, more modern relay machines, the ENIAC" (boldface in original: Goldstine and von Neumann, 1946; p. 98 in Bell and Newell 1971).

However, the accumulator comes at the expense of more instructions per arithmetic "operation", in particular with respect to what are called 'read-modify-write' instructions such as "Increment indirectly the contents of the register pointed to by register r2 ". "A" designates the "accumulator" register A:

Label Instruction A r2 r378,426 Description
. . . 378,426 17
INCi ( r2 ): CPY ( i, r2, d, A ) 17 378,426 17 Contents of r2 points to r378,426 with contents "17": copy this to A
INC ( A ) 18 378,426 17 Incement contents of A
CPY ( d, A, i, r2 ) 18 378,426 18 Contents of r2 points to r378,426: copy contents of A into r378,426

If we stick with a specific name for the accumulator, e.g. "A", we can imply the accumulator in the instructions, for example,

INC ( A ) = INCA

However, when we write the CPY instructions without the accumulator called out the instructions are ambiguous or they must have empty parameters:

CPY ( d, r2, d, A ) = CPY (d, r2, , )
CPY ( d, A, d, r2 ) = CPY ( , , d, r2)

Historically what has happened is these two CPY instructions have received distinctive names; however, no convention exists. Tradition (e.g. Knuth's (1973) imaginary MIX computer) uses two names called LOAD and STORE. Here we are adding the "i/d" parameter:

LDA ( d/i, rs ) =def CPY ( d/i, rs, d, A )
STA ( d/i, rd ) =def CPY ( d, A, d/i, rd )

The typical accumulator-based model will have all its two-variable arithmetic and constant operations (e.g. ADD (A, r), SUB (A, r) ) use (i) the accumulator's contents, together with (ii) a specified register's contents. The one-variable operations (e.g. INC (A), DEC (A) and CLR (A) ) require only the accumulator. Both instruction-types deposit the result (e.g. sum, difference, product, quotient or remainder) in the accumulator.

Example: INCA = [A] +1 → A
Example: ADDA (rs) = [A] + [rs] → A
Example: MULA (rs) = [A] * [rs] → A

If we so choose, we can abbreviate the mnemonics because at least one source-register and the destination register is always the accumulator A. Thus we have :

{ LDA (i/d, rs), STA (i/d, rd), CLRA, INCA, DECA, ADDA (rs), SUBA (rs), MULA (rs), DIVA (rs), etc.)

The notion of indirect address register "N"

[edit]

If our model has an unbounded accumulator can we bound all the other registers? Not until we provide for at least one unbounded register from which we derive our indirect addresses.

The minimalist approach is to use itself (Schönhage does this).

Another approach (Schönhage does this too) is to declare a specific register the "indirect address register" and confine indirection relative to this register (Schonhage's RAM0 model uses both A and N registers for indirect as well as direct instructions). Again our new register has no conventional name – perhaps "N" from "iNdex", or "iNdirect" or "address Number".

For maximum flexibility, as we have done for the accumulator A – we will consider N just another register subject to increment, decrement, clear, test, direct copy, etc. Again we can shrink the instruction to a single-parameter that provides for direction and indirection, for example.

LDAN (i/d) = CPY (i/d, N, d, A); LoaD Accumulator via iNdirection register
STAN (i/d) = CPY (d, A, i/d, N). STore Accumulator via iNdirection register

Why is this such an interesting approach? At least two reasons:

(1) An instruction set with no parameters:

Schönhage does this to produce his RAM0 instruction set. See section below.

(2) Reduce a RAM to a Post-Turing machine:

Posing as minimalists, we reduce all the registers excepting the accumulator A and indirection register N e.g. r = { r0, r1, r2, ... } to an unbounded string of (very-) bounded-capacity pigeon-holes. These will do nothing but hold (very-) bounded numbers e.g. a lone bit with value { 0, 1 }. Likewise we shrink the accumulator to a single bit. We restrict any arithmetic to the registers { A, N }, use indirect operations to pull the contents of registers into the accumulator and write 0 or 1 from the accumulator to a register:

{ LDA (i, N), STA (i, N), CLR (A/N), INC (A/N), DEC(N), JZ (A/N, Iz), JZ (Iz), H }

We push further and eliminate A altogether by the use of two "constant" registers called "ERASE" and "PRINT": [ERASE]=0, [PRINT]=1.

{ CPY (d, ERASE, i, N), CPY (d, PRINT, i, N), CLR (N), INC (N), DEC (N), JZ (i, N, Iz), JZ (Iz), H }

Rename the COPY instructions and call INC (N) = RIGHT, DEC (N) = LEFT and we have the same instructions as the Post-Turing machine, plus an extra CLRN :

{ ERASE, PRINT, CLRN, RIGHT, LEFT, JZ (i, N, Iz), JZ (Iz), H }

Turing equivalence of the RAM with indirection

[edit]

In the section above we informally showed that a RAM with an unbounded indirection capability produces a Post–Turing machine. The Post–Turing machine is Turing equivalent, so we have shown that the RAM with indirection is Turing equivalent.

We give here a slightly more formal demonstration. Begin by designing our model with three reserved registers "E", "P", and "N", plus an unbounded set of registers 1, 2, ..., n to the right. The registers 1, 2, ..., n will be considered "the squares of the tape". Register "N" points to "the scanned square" that "the head" is currently observing. The "head" can be thought of as being in the conditional jump – observe that it uses indirect addressing (cf Elgot-Robinson p. 398). As we decrement or increment "N" the (apparent) head will "move left" or "right" along the squares. We will move the contents of "E"=0 or "P"=1 to the "scanned square" as pointed to by N, using the indirect CPY.

The fact that our tape is left-ended presents us with a minor problem: Whenever LEFT occurs our instructions will have to test to determine whether or not the contents of "N" is zero; if so we should leave its count at "0" (this is our choice as designers – for example we might have the machine/model "trigger an event" of our choosing).

Instruction set 1 (augmented): { INC (N), DEC (N), CLR (N), CPY (d, rs,i, N), JZ ( i, r, z ), HALT }

The following table both defines the Post-Turing instructions in terms of their RAM equivalent instructions and gives an example of their functioning. The (apparent)location of the head along the tape of registers r0-r5 . . . is shown shaded:

Mnemonic label: E P N r0 r1 r2 r3 r4 r5 etc. Action on registers Action on finite-state machine Instruction Register IR
start: 0 1 3 1 0
R right: INC ( N ) 0 1 4 1 0 [N] +1 → N [IR] +1 → IR
P print: CPY ( d, P, i, N ) 0 1 4 1 1 [P]=1 → [N]=r4 [IR] +1 → IR
E erase: CPY ( d, E, i, N ) 0 1 4 1 0 [E]=0 → [N]=r4 [IR] +1 → IR
L left: JZ ( i, N, end ) 0 1 4 1 0 none IF N =r4] =0 THEN "end" → IR else [IR]+1 → IR
DEC ( N ) 0 1 3 1 0 [N] -1 → N
J0 ( halt ) jump_if_blank: JZ ( i, N, end ) 0 1 3 1 0 none IF N =r3] =0 THEN "end" → IR else [IR]+1 → IR
J1 ( halt ) jump_if_mark: JZ ( i, N, halt ) 0 1 3 1 0 N =r3] → A IF N =r3] =0 THEN "end" → IR else [IR]+1 → IR
end . . . etc. 0 1 3 1 0
halt: H 0 1 3 1 0 none [IR] +1 → IR

Example: Bounded indirection yields a machine that is not Turing equivalent

[edit]

Throughout this demonstration we have to keep in mind that the instructions in the finite-state machine's TABLE is bounded, i.e. finite:

"Besides a merely being a finite set of rules which gives a sequence of operations for solving a specific type of problem, an algorithm has five important features [Finiteness, Definiteness, Input, Output, Effectiveness]" (italics added, Knuth p. 4-7).
The difficulty arises because the registers have explicit "names" (numbers) and our machine must call each out by name in order to "access" it.

We will build the indirect CPY ( i, q, d, φ ) with the CASE operator. The address of the target register will be specified by the contents of register "q"; once the CASE operator has determined what this number is, CPY will directly deposit the contents of the register with that number into register "φ". We will need an additional register that we will call "y" – it serves as an up-counter.

So the following is actually a constructive demonstration or proof that we can indeed simulate the indirect CPY ( i, q, d, φ ) without a "hardware" design change to our counter machine/model. However, note that because this indirect CPY is "bounded" by the size/extent of the finite-state machine, a RASP using this indirect CPY can only calculate the primitive recursive functions, not the full suite of mu recursive functions.

The CASE "operator" is described in Kleene (1952) (p. 229) and in Boolos-Burgess-Jeffrey (2002) (p. 74); the latter authors emphasize its utility. The following definition is per Kleene but modified to reflect the familiar "IF-THEN-ELSE" construction.

The CASE operator "returns" a natural number into φ depending on which "case" is satisfied, starting with "case_0" and going successively through "case_last"; if no case is satisfied then the number called "default" (aka "woops") is returned into φ (here x designates some selection of parameters, e.g. register q and the string r0, ... rlast )):

Definition by cases φ (x, y):

  • case_0: IF Q0(x, y) is true THEN φ0(x, y) ELSE
  • case_1: IF Q1(x, y) is true THEN φ1(x, y) ELSE
  • cases_2 through case_next_to_last: etc. . . . . . . . . ELSE
  • case_last: IF Qlast(x, y) is true THEN φlast(x, y) ELSE
  • default: do φdefault(x, y)

Kleene require that the "predicates" Qn that doing the testing are all mutually exclusive – "predicates" are functions that produce only { true, false } for output; Boolos-Burgess-Jeffrey add the requirement that the cases are "exhaustive".

We begin with a number in register q that represents the address of the target register. But what is this number? The "predicates" will test it to find out, one trial after another: JE (q, y, z) followed by INC (y). Once the number is identified explicitly, the CASE operator directly/explicitly copies the contents of this register to φ:

Definition by cases CPY (i, q, d, φ) =def φ (q, r0, ..., rlast, y) =
  • case_0: IF CLR (y), [q] - [y]=0 THEN CPY ( r0, φ ), J (exit) ELSE
  • case_1: IF INC (y), [q] = [y]=1 THEN CPY ( r1, φ ), J (exit) ELSE
  • case_2 through case n: IF . . . THEN . . . ELSE
  • case_n: IF INC (y), [q] = [y]=n THEN CPY ( rn, φ ), J (exit) ELSE
  • case_n+1 to case_last: IF . . . THEN . . . ELSE
  • case_last: IF INC (y), [q] = [y]="last" THEN CPY ( rlast, φ ), J (exit) ELSE
  • default: woops

Case_0 ( the base step of the recursion on y) looks like this:

  • case_0:
  • CLR ( y ) ; set register y = 0
  • JE ( q, y, _φ0 )
  • J ( case_1 )
  • _φ0: CPY ( r0, φ )
  • J ( exit )
  • case_1: etc.

Case_n (the induction step) looks like this; remember, each instance of "n", "n+1", ..., "last" must be an explicit natural number:

  • case_n:
  • INC ( y )
  • JE ( q, y, _φn )
  • J ( case_n+1)
  • _φn: CPY ( rn, φ )
  • J ( exit )
  • case__n+1: etc.

Case_last stops the induction and bounds the CASE operator (and thereby bounds the "indirect copy" operator):

  • case_last:
  • INC ( y )
  • JE ( q, y, _φlast )
  • J ( woops )
  • _φlast: CPY ( rlast, φ )
  • J ( exit )
  • woops: how do we handle an out-of-bounds attempt?
  • exit: etc.

If the CASE could continue ad infinitum it would be the mu operator. But it can't – its finite-state machine's "state register" has reached its maximum count (e.g. 65365 = 11111111,111111112 ) or its table has run out of instructions; it is a finite machine, after all.

Examples of models

[edit]

Register-to-register ("read-modify-write") model of Cook and Reckhow (1973)

[edit]

The commonly encountered Cook and Rechkow model is a bit like the ternary-register Malzek model (written with Knuth mnemonics – the original instructions had no mnemonics excepting TRA, Read, Print).

  • LOAD ( C, rd ) ; C → rd, C is any integer
Example: LOAD ( 0, 5 ) will clear register 5.
  • ADD ( rs1, rs2, rd ) ; [rs1] + [rs2] → rd, the registers can be the same or different;
Example: ADD ( A, A, A ) will double the contents of register A.
  • SUB ( rs1, rs2, rd ) ; [rs1] - [rs2] → rd, the registers can be the same or different:
Example: SUB ( 3, 3, 3 ) will clear register 3.
  • COPY ( i, rp, d, rd ) ; [[rp] ] → rd, Indirectly copy the contents of the source-register pointed to by pointer-register rp into the destination register.
  • COPY ( d, rs, i, rp ) ; [rs] → [rp]. Copy the contents of source register rs into the destination-register pointed to by the pointer-register rp.
  • JNZ ( r, Iz ) ; Conditional jump if [r] is positive; i.e. IF [r] > 0 THEN jump to instruction z else continue in sequence (Cook and Reckhow call this: "TRAnsfer control to line m if Xj > 0")
  • READ ( rd ) ; copy "the input" into destination register rd
  • PRINT ( rs ) ; copy the contents of source register rs to "the output."

Schönhage's RAM0 and RAM1 (1980)

[edit]

Schönhage (1980) describes a very primitive, atomized model chosen for his proof of the equivalence of his SMM pointer machine model:

"In order to avoid any explicit addressing the RAM0 has the accumulator with contents z and an additional address register with current contents n (initially 0)" (p. 494)

RAM1 model: Schönhage demonstrates how his construction can be used to form the more common, usable form of "successor"-like RAM (using this article's mnemonics):

  • LDA k ; k --> A , k is a constant, an explicit number such as "47"
  • LDA ( d, r ) ; [r] → A ; directly load A
  • LDA ( i, r ) ; [[r]] → A ; indirectly load A
  • STA ( d, r ) ; [A] → r ; directly store A
  • STA ( i, r ) ; [A] → [r] ; indirectly store A
  • JEA ( r, z ) ; IF [A] = [r] then Iz else continue
  • INCA ; [A] + 1 --> A

RAM0 model: Schönhage's RAM0 machine has 6 instructions indicated by a single letter (the 6th "C xxx" seems to involve 'skip over next parameter'. Schönhage designated the accumulator with "z", "N" with "n", etc. Rather than Schönhage's mnemonics we will use the mnemonics developed above.

  • (Z), CLRA: 0 → A
  • (A), INCA: [A] +1 → A
  • (N), CPYAN: [A] → N
  • (A), LDAA: [[A]] → A ; contents of A points to register address; put register's contents into A
  • (S), STAN: [A] → [N] ; contents of N points to register address; put contents of A into register pointed to by N
  • (C), JAZ ( z ): [A] = 0 then go to Iz ; ambiguous in his treatment

Indirection comes (i) from CPYAN (copy/transfer contents A to N) working with store_A_via_N STAN, and from (ii) the peculiar indirection instruction LDAA ( [[A]] → [A] ).

Footnotes

[edit]

See also

[edit]
[edit]

References

[edit]
Revisions and contributorsEdit on WikipediaRead on Wikipedia
from Grokipedia
A random-access machine (RAM) is an abstract in that represents a simplified von Neumann-style computer with direct, constant-time access to an unbounded array of memory registers, each capable of storing integers of arbitrary precision. The model includes a of central registers, a to track instruction execution, read-only input and write-only output tapes, and a basic instruction set comprising data movement (load/store), arithmetic operations (, , ), comparisons, conditional branching, and halting. Each instruction executes in unit time, regardless of size, enabling straightforward analysis of . The RAM model emerged from early computer architectures and was formalized in the 1960s to bridge practical computing with theoretical foundations like the . A foundational contribution came from J. C. Shepherdson and H. E. Sturgis, who in 1963 proved that register machines with indirect addressing— a key feature allowing computed addresses for memory access—can compute all partial recursive functions, confirming the model's . This work built on von Neumann's stored-program concepts and addressed limitations in earlier models by incorporating , which more closely mimics real hardware while simplifying complexity proofs. In algorithm design and analysis, the RAM model serves as a standard for measuring time and by counting operations on word-sized data, assuming uniform access costs and supporting abstractions like pointers. It underpins big-O notation evaluations in sequential computing and extends to parallel variants like the parallel random-access machine (PRAM) for concurrent algorithms. Common variants distinguish unit-cost assumptions (where arithmetic on words is O(1)) from logarithmic-cost models (where costs scale with bit length), reflecting trade-offs in realism versus simplicity.

Introduction

Informal Description

The random-access machine (RAM) is an abstract computing device modeled after traditional serial computers, featuring an unbounded array of registers that store non-negative of arbitrary precision and a that sequentially directs the execution of a fixed program. Each register functions as a cell addressable by a non-negative index, allowing the machine to perform operations like loading, storing, and arithmetic directly on these values. The hallmark of the RAM is its random-access capability, which permits the machine to reference and manipulate any register in constant time by specifying its , in contrast to models that require traversing memory linearly. This direct addressing mechanism enables efficient non-local data operations, mirroring the memory addressing in practical computing hardware while abstracting away physical constraints like finite storage. The model assumes a unit-cost criterion, where basic operations (including arithmetic on arbitrary-precision integers) execute in constant time, simplifying theoretical analysis but diverging from real hardware limitations for large operands. As a theoretical construct, the RAM simplifies of computational processes by emphasizing algorithmic steps over mechanical details, much like how real computers use addressable for rapid . It builds on precursor models like counter machines, which in some variants limit access via a fixed number of registers, by introducing flexible direct addressing to better approximate modern . Fundamentally, the RAM is equivalent in power to the , capable of simulating any with linear time and space overhead, thus bridging intuitive hardware-inspired models with foundational .

Historical Context and Motivation

The (RAM) model emerged in the and as an advancement over earlier counter-machine models, aiming to better simulate the structures found in practical computing devices within theoretical frameworks. Building on the register machine formalizations introduced by J.C. Shepherdson and H.E. Sturgis in , which demonstrated the equivalence of such machines to through a limited instruction set including increments, decrements, and zero-testing with indirect addressing, the RAM incorporated an unbounded array of memory cells accessible by direct indexing. This evolution addressed the limitations of tape-based models like , providing a more intuitive representation of algorithmic . The primary motivation for developing the RAM was to create a theoretical model that efficiently captures operations, enabling precise analysis of in algorithms without the overhead of linear tape traversal. In contrast to certain counter machine models, such as Minsky's (1961) with a fixed number of registers and no computed addressing, the RAM allowed for arbitrary arithmetic and memory access in constant time per operation, mirroring the von Neumann architecture's direct addressing—while building on the unbounded registers and indirect addressing of general register machines like those of Shepherdson and Sturgis. This made it particularly suitable for studying computational efficiency in realistic scenarios, such as evaluating the running times of sorting or searching algorithms. Key formalization of the RAM for complexity purposes came from Stephen A. Cook and Robert A. Reckhow in 1972, who defined time-bounded variants to explore resource constraints, proving equivalences to multitape Turing machines while highlighting its utility for nonuniform complexity classes. The model's adoption in complexity theory, including discussions of P versus NP, stemmed from its balance of simplicity—relying on a small set of primitive instructions—and realism in modeling memory access costs, facilitating proofs of polynomial-time equivalence between RAM and Turing machine computations. Post-1980 extensions refined the RAM to account for word sizes in modern analysis, introducing the word RAM variant where memory cells hold fixed-length words (typically Θ(logn)\Theta(\log n) bits) to incorporate realistic arithmetic costs for large integers, enhancing its applicability to design and lower bound proofs in data structures.

Background Models

Counter-Machine Model

The counter-machine model is a foundational computational device consisting of an infinite sequence of registers R1,R2,R_1, R_2, \dots, each holding a non-negative value. The basic instruction set includes increment (INC rr), which adds 1 to the contents of register rr; decrement (DEC rr), which subtracts 1 from the contents of register rr if it is greater than 0 (and may halt or skip otherwise, depending on the variant); and jump if zero (JZ r,zr, z), which transfers control to instruction label zz if register rr contains 0, otherwise proceeding to the next instruction. Unconditional jumps (J zz) can also be included or simulated using the basic set. Execution proceeds via a that advances sequentially through a fixed program, incrementing by 1 after each instruction unless a jump alters it. Registers are addressed directly by their fixed indices in the instructions, limiting dynamic access to computed register numbers without indirect mechanisms. A starts with inputs placed in initial registers (others at 0) and halts when reaching the end of the program or via a special halt instruction, with output typically in a designated register. For example, to add the values in registers R0R_0 (holding xx) and R1R_1 (holding yy), resulting in x+yx + y in R0R_0, the following program uses a loop to decrement R1R_1 while incrementing R0R_0:
  1. JZDEC R1,5R_1, 5 (decrement R1R_1; if now 0, jump to end)
  2. INC R0R_0
  3. J 1 (unconditional jump back to step 1)
  4. HALT
This loops yy times, effectively transferring the count from R1R_1 to increments on R0R_0. With unbounded registers, counter machines compute precisely the partial recursive functions, equivalent in power to Turing machines, though their sequential register access makes them inefficient for tasks requiring random indexing. The random-access machine builds on this foundation by adding indirect addressing to enable computed register selection.

Limitations of Counter Machines

Counter machines, characterized by instructions that directly address a fixed set of counters via operations like increment, decrement, and zero-testing, exhibit fundamental inefficiencies in achieving random access to memory. Since the finite program can only reference a fixed finite number of registers (those explicitly named by constant indices), simulating unbounded memory or dynamic data structures requires encoding the entire state into these fixed registers using numerical methods, such as Gödel's β-function or product of primes. This encoding allows Turing completeness but makes operations on large data structures computationally expensive. A related limitation arises from the absence of indirect addressing, which hampers the implementation of pointers or dynamic structures. To mimic a pointer to a variable location, the must store the target index in a counter and use conditional logic—such as chains of zero-tests—to select the appropriate fixed-register operation based on the index value. For large indices, this requires either extensive branching (proportional to the index in program or time via repeated decrements) or reliance on the encoding scheme, amplifying runtime proportional to the . Counter machines are Turing-complete and can simulate random-access machines, yet the simulation incurs a time overhead exponential in the bit length of register contents (i.e., the "register size" in bits). Basic arithmetic like addition of two b-bit numbers requires looping over the smaller value, taking up to 2^b steps in the worst case, which limits their applicability in complexity theory where polynomial equivalence to other models is essential.

Formal RAM Definition

Basic Instruction Set

The basic instruction set of the random-access machine (RAM) model consists of a minimal collection of operations that enable direct manipulation of registers and , forming the foundation for without indirect addressing. These instructions operate on an unbounded array of registers R0,R1,R2,R_0, R_1, R_2, \dots, each holding a non-negative , with all registers initialized to zero at the start. The program is a finite sequence of instructions stored in , executed sequentially via a that begins at the first instruction and increments by one after each step unless altered by a jump; execution halts if the program counter references an invalid or out-of-bounds instruction. This is a variant with an accumulator for convenience; the original 1963 model by Shepherdson and Sturgis uses increment, decrement, zeroing, direct copy, and zero-test jumps without constants or accumulator. The core instructions are as follows:
  • LOAD kk: Loads the non-negative constant integer kk into the accumulator.
  • STORE XiX_i: Stores the current value of the accumulator into register RiR_i.
  • ADD XiX_i: Adds the value in register RiR_i to the accumulator, replacing the accumulator's value.
  • SUB XiX_i: Subtracts the value in register RiR_i from the accumulator, replacing the accumulator's value (if the result is negative, it is typically set to zero in basic variants).
  • JUMP jj: Unconditionally sets the to line jj of the program.
  • JZERO jj: If the accumulator is zero, sets the to line jj; otherwise, increments the normally.
Formally, the instruction set is denoted as I={LOAD kk0}{STORE Xi,ADD Xi,SUB Xii0}{JUMP j,JZERO jj is a program line}I = \{\text{LOAD } k \mid k \geq 0\} \cup \{\text{STORE } X_i, \text{ADD } X_i, \text{SUB } X_i \mid i \geq 0\} \cup \{\text{JUMP } j, \text{JZERO } j \mid j \text{ is a program line}\}, where XiX_i represents access to register RiR_i and jj indexes a specific instruction in the program. This set draws inspiration from the operations of counter machines, which use increment, decrement, and zero-testing for basic arithmetic and control. Without indirect addressing, these instructions suffice to compute exactly the class of primitive recursive functions, providing a complete for recursive computations bounded by primitive .

Indirect Addressing Mechanism

The indirect addressing mechanism in the random-access machine (RAM) model extends the basic direct addressing capabilities by allowing the content of a register to serve as an for accessing another register, thereby enabling dynamic and pointer-like access. This feature is central to the RAM's ability to perform to an unbounded number of registers using a fixed program. Specifically, in indirect operations, an specifies a register whose value is interpreted as the target rather than a direct register index. For instance, an indirect load instruction, such as LOADIND XiX_i, retrieves the value from the register whose index is given by the contents of XiX_i and places it into the accumulator or another designated register. Similarly, indirect store instructions like STOREIND XiX_i write a value (from the accumulator or source register) to the register addressed by the value in XiX_i. These operations use the register's non-negative content as an index, with for invalid (negative) values in some variants. This supports a range of operations beyond simple loads and stores, including indirect and , where arithmetic is performed on values at addresses specified indirectly. For example, an indirect ADD might compute the sum of the accumulator and the contents of the register addressed by XjX_j, storing the result back in the accumulator. In practice, these instructions are often denoted with notation like XiXXjX_i \leftarrow X_{X_j} for copying from the indirectly addressed register XXjX_{X_j} to XiX_i, highlighting the nested access enabled by . The pointer-like behavior introduced by indirect addressing allows for arbitrary levels of nesting, where the address in one register can point to another that itself holds an , facilitating complex data structures and computations that would be inefficient or impossible in models without this capability. This mechanism elevates the RAM beyond counter machines by permitting efficient access to non-contiguous locations, thus supporting more flexible program structures for handling variable-sized inputs. Unbounded , in particular, enables the RAM to simulate pointer operations akin to those in higher-level programming languages, while remaining grounded in the register-based architecture. Overall, indirect addressing is the defining feature that provides the RAM with its "" property, allowing direct jumps to any register based on computed addresses rather than sequential increments.

Core Components

Accumulator Register A

In the random-access machine (RAM) model, the accumulator register A serves as a dedicated special-purpose register that temporarily holds values during arithmetic computations and data transfer operations, distinct from the infinite of numbered registers R_1, R_2, \dots used for general storage. This separation ensures A functions primarily as an operational buffer, capable of storing natural numbers of arbitrary size without being directly addressable as part of the main register sequence. The usage of A is integral to core instructions: LOAD operations transfer a value from a specified register (or memory location) into A; ADD and SUB instructions perform addition or subtraction on A using a value from another register, updating A with the result; and STORE instructions copy the contents of A to a target register. By design, these instructions implicitly target A as the destination or source, eliminating the need for explicit references to A in the instruction encoding and thereby streamlining program representation. Although A is sometimes formulated equivalently to a base register R_0 in register-only variants of the model, it remains semantically distinct due to its exclusive role in mediating all arithmetic and load/store activities, preventing direct manipulation outside these operations. For instance, to compute the sum of values in registers R_i and R_j and store it in R_k, the instruction sequence would be: LOAD i (placing R_i into A), ADD j (adding R_j to A), and STORE k (transferring the result from A to R_k). Indirect addressing may also load values into A by first setting an address in a separate register before executing LOAD.

Indirect Address Register N

In certain formulations of the random-access machine (RAM) model, an indirect address register, often denoted , serves as a specialized component designed to store an value representing a for indirect access operations. This enables the machine to dynamically reference other registers based on the content of (or a similar register), rather than using fixed indices in instructions. For instance, an instruction may transfer the contents of the register numbered by the value in to the accumulator, allowing operations on variable locations without embedding es directly in the program. Such functionality is integral to the model's ability to handle complex data structures efficiently. N facilitates chained indirection by permitting its own value to be modified through standard arithmetic instructions, thereby supporting sequences of indirect operations. Common instructions involving indirect addressing include variants that load from or store to the register addressed by N's content into/from the accumulator. These operations enable the simulation of pointers, where N acts as a temporary holder for computed addresses, streamlining access to non-contiguous memory areas in theoretical computations. While minimal RAM definitions may omit a dedicated indirect register to focus on direct addressing via instruction operands, it is a feature in many influential formulations, enhancing the model's expressiveness without increasing the instruction set's complexity. By providing a mechanism for manipulation, such a register allows the RAM to overcome limitations of direct-only models, such as inefficient traversal of data, while maintaining simplicity in program design. This inclusion is particularly valued in analyses of computational and equivalence to other models. Unlike the accumulator register A, which processes data values through arithmetic and logical operations, N is reserved solely for address values in these variants, ensuring a strict separation that avoids conflating computational data with navigational pointers. This choice promotes clarity in the machine's state and supports rigorous proofs of the RAM's computational capabilities.

Indirection and Operations

Example of Indirect Addressing

To illustrate indirect addressing in the random-access machine (RAM) model, consider a simple program that copies the contents of register RkR_k to register RmR_m, where the index kk is dynamically stored in register R1R_1. This operation leverages the indirect load mechanism, where the address of the source register is computed at runtime using the value in R1R_1, enabling access to arbitrary registers without sequential scanning. The program uses the following two instructions, assuming a standard RAM instruction set with an accumulator AA:

1. LOAD (1) // Indirect load: A ← R[R_1] (i.e., load the contents of the register indexed by R_1 into A) 2. STORE m // Direct store: R_m ← A (store the accumulator value into R_m)

1. LOAD (1) // Indirect load: A ← R[R_1] (i.e., load the contents of the register indexed by R_1 into A) 2. STORE m // Direct store: R_m ← A (store the accumulator value into R_m)

Here, LOAD (1) performs the indirect operation by first retrieving the value in R1R_1 (which is kk) and then loading the contents of RkR_k into AA. The subsequent STORE m transfers this value directly to the target register. This sequence executes in constant time, typically two steps, regardless of the magnitude of kk. For a step-by-step execution trace, assume the following initial register states (with k=5k = 5, m=10m = 10, and all other registers initialized to 0):
StepInstructionA (Accumulator)R_1R_5R_10Description
0 (Initial)-05420R_1 holds the index k=5; R_5 holds the value to copy (42); R_10 is the target.
1LOAD (1)425420Indirect load: Retrieve index from R_1 (5), then load R_5's value (42) into A. Registers unchanged.
2STORE 104254242Direct store: Transfer A's value (42) to R_10. A remains 42 (per standard RAM semantics).
The final state shows R10=42R_{10} = 42, successfully copying the value from RkR_k in constant time. This contrasts with sequential access models like counter machines, where reaching RkR_k would require O(k)O(k) increment operations, highlighting the RAM's efficiency for arbitrary register access.

Indirect COPY Instruction

The indirect COPY instruction, often denoted as COPY *X_i to *X_j, enables the transfer of data from the register whose is specified by the contents of register X_i to the register whose is specified by the contents of register X_j. This operation facilitates direct manipulation of locations determined dynamically by register values, serving as a fundamental mechanism for pointer-like behavior in the RAM model. In the register-to-register variant defined by Cook and Reckhow, such indirect copies are primitive instructions, such as Xi ← X_{X_j}, which loads the contents from the register indexed by X_j into Xi, and can be composed for full source-to-destination transfers using a temporary register. In accumulator-based RAM models featuring an indirect register N, the indirect COPY is typically derived from base indirect LOAD and STORE operations rather than provided as a primitive. The implementation requires a temporary register T to preserve the value during transfer and proceeds as follows: first, load X_i into A and store to N (setting N to the source ); then, perform an indirect LOAD using N to move the value into A and store it to T; next, load X_j into A and store to N (setting N to the destination ); finally, load T into A and perform an indirect STORE using N to transfer the value from A to the destination. This sequence requires six instructions. This instruction is essential for pointer manipulation, as it allows programs to copy between arbitrarily addressed locations, enabling structures like linked lists or dynamic arrays within the fixed instruction set of the RAM. In extended RAM variants, such as those incorporating efficiency optimizations, the indirect COPY is provided as a single primitive instruction to avoid the overhead of multi-step sequences and accumulator intermediation, streamlining operations on large-scale . A known as bounded indirect COPY restricts computations to prevent overflow, ensuring that the indices in X_i and X_j do not exceed a predefined limit (e.g., the current input size n) during evaluation; this mitigates issues in models with logarithmic cost functions where excessive indirection could lead to unbounded time or space usage. Such bounding aligns with computations by limiting recursion depth in resolution.

Addressing Counter-Machine Shortcomings

The indirect addressing mechanism in the random-access machine (RAM) model directly addresses the first primary limitation of counter machines by enabling constant-time (O(1)) access to any numbered register, eliminating the need for sequential loops that require O(n) time to reach the nth register. This improvement arises from instructions that load or store values using an held in a dedicated register (such as the indirect address register N), allowing the finite-state control to reference arbitrary locations without incremental traversal. A second key shortcoming of counter machines—inefficient simulation of dynamic data structures like —is resolved through RAM's support for true pointers and dynamic addressing. In counter machines, array access typically demands quadratic time due to nested loops for indexing, whereas RAM indirection permits efficient pointer manipulation, simulating in linear time by treating registers as addressable pointers to elements. With , RAM programs can simulate counter machines in linear time, while counter machines can simulate RAM programs with only overhead, demonstrating the models' close equivalence in computational efficiency despite the addressing enhancements. This minimal addition of to the counter machine framework achieves full without requiring auxiliary storage like a tape, providing a streamlined model for analyzing recursive function computability.

Computational Power

Bounded Indirection and Primitive Recursive Functions

In the random-access machine (RAM) model, bounded refers to a restriction where the values stored in address registers or the depth of nested operations are limited to at most some ff of the input size nn. This limitation ensures that memory accesses cannot grow in an unbounded manner relative to the input, preventing the simulation of operations that would require arbitrary or search. The model, introduced by Shepherdson and Sturgis as a variant of register machines, uses instructions like increment, zero, and conditional jump, but with addresses constrained by this bound to maintain within total functions. RAMs with bounded indirection compute precisely the class of primitive recursive functions, as formalized through representations akin to Kleene's T predicate, which encodes computations but restricted here to total, halting processes without unbounded minimization. Every can be realized by such a machine via composition and primitive recursion schemata translated into bounded indirect operations, while the converse holds because any computation under the indirection bound can be mapped back to primitive recursive definitions. All such computations terminate on every input, distinguishing them from partial recursive functions that may diverge. A key illustration of this restriction is the iteration theorem for , which bounds loop iterations by a primitive recursive predicate; in the RAM context, this corresponds to the indirection limit, precluding the computation of functions like the A(m,n)A(m, n), defined as A(0,n)=n+1A(0, n) = n + 1, A(m+1,0)=A(m,1)A(m + 1, 0) = A(m, 1), and A(m+1,n+1)=A(m,A(m+1,n))A(m + 1, n + 1) = A(m, A(m + 1, n)), which grows faster than any primitive recursive function and requires unbounded nesting beyond the allowed depth.

Unbounded Indirection and Partial Recursive Functions

Unbounded in the random-access machine (RAM) model permits the use of arbitrary register values as addresses, enabling nested addressing chains of unlimited depth through sequential instruction execution, without any predefined limit on levels. This feature extends the model's expressive power beyond fixed-depth addressing, allowing dynamic access to registers based on computed values during program runtime. As formalized in early models, such is realized via instructions that load or store values indirectly through an index register, where the itself is the content of another register. The RAM equipped with unbounded computes precisely the class of μ-recursive functions, equivalent to the partial recursive functions, by supporting the minimization operator (μ-operator) that performs unbounded search for the smallest input yielding a zero output. This equivalence arises from the ability to simulate register machines that incorporate indirect jumps and loops, enabling the construction of programs for arbitrary partial recursive functions via composition of basic operations like successor, zero-testing, and indirect transfers. Specifically, the μ-operator is implemented through iterative decrement and conditional branching using indirect addressing to manage loop counters and search indices dynamically. Unbounded indirection facilitates the direct encoding of simulations within RAM programs, where indirect jumps emulate state transitions and tape head movements by using register contents to select operational codes or positions. Unlike primitive recursive functions, which are total and require bounded , partial recursive functions in this model may fail to terminate, reflecting non-halting computations such as infinite searches in minimization, thus capturing the full scope of effectively computable but potentially undefined functions on natural numbers.

Turing Completeness with Unbounded Indirection

The random-access machine (RAM) model with an unbounded number of registers and support for unbounded indirection achieves Turing completeness, enabling it to perform any computation that a Turing machine can execute. A sketch of the simulation from Turing machine to RAM involves encoding the Turing machine's tape cells directly into RAM registers, where each register RiR_i holds the symbol at tape position ii (with negative positions mapped to positive indices via an offset, such as z+b|z| + b for some base bb). The head position is stored in a register HH, and the current symbol is read using indirect addressing: load the accumulator AA with the contents of RHR_H, denoted as A[H]A \leftarrow [H]. Writing a symbol follows similarly with an indirect store: [H]A[H] \leftarrow A. Head movement updates HH via addition or subtraction instructions, allocating new registers lazily for unvisited positions (initialized to 0, representing the blank symbol). The Turing machine's finite states and transition table are hardcoded as a sequence of RAM instructions, including conditional jumps for state changes. Each Turing machine step thus corresponds to a constant number of RAM operations, though in the logarithmic-cost RAM variant—where accessing register kk costs O(logk)O(\log k) time due to binary address manipulation—the overall simulation runs in O(tlogt)O(t \log t) time for a tt-step Turing machine computation. In the reverse direction, a simulates a RAM by maintaining its configuration on the work tape using to encode the registers and instructions as a single or sequence of binary pairs (, content), tracking only the finitely many active registers during execution. To perform a direct load from register kk, the Turing machine scans the tape to find the encoded pair for kk, decodes the content, and updates a simulated accumulator; indirect addressing chains this process by first resolving the target . Instruction execution involves rewriting the tape configuration according to the RAM's program, encoded similarly via for universality. This requires O(t)O(t) passes over the tape per step, yielding a simulation time of O(t2)O(t^2) in the logarithmic-cost model. This mutual simulation establishes formal equivalence between the models, with the O(tlogt)O(t \log t) overhead in the RAM-to-Turing-machine direction playing a crucial role in complexity theory for bridging time bounds across models. Unbounded registers combined with indirection in the RAM suffice for universal computation, as they allow arbitrary addressing and state manipulation mirroring Turing machine capabilities.

Model Variants

Register-to-Register Model (Cook and Reckhow, 1973)

The register-to-register model of the random-access machine (RAM), introduced by and Ronald Reckhow, features an infinite sequence of registers X0,X1,X_0, X_1, \dots, each capable of holding arbitrary integers, with a finite program dictating operations directly between these registers without an accumulator. Instructions in this model specify operations such as , , constant loading, indirect addressing, conditional transfer, input, and output, all executed in a read-modify-write manner; for instance, the instruction XiXj+XkX_i \leftarrow X_j + X_k reads the values from registers XjX_j and XkX_k, computes their sum, and writes the result to XiX_i in a single step. This design eliminates the need for an intermediate accumulator, enabling more direct and parallel-like register interactions compared to accumulator-based variants. A key aspect of the model is its uniform criterion, where each instruction incurs a defined by a function l(n)l(n) that depends on the input nn, such as a constant l(n)=1l(n) = 1 or a logarithmic l(n)lognl(n) \approx \log |n| to account for the of register values. The read-modify-write operations assume single-cycle access to registers, promoting a simplified of computational steps without distinguishing between access and arithmetic. This uniformity facilitates precise measurements, as the total execution time is the sum of individual instruction costs. In complexity theory, the register-to-register RAM serves as a foundational model for studying deterministic and nondeterministic time hierarchies, with results showing that nondeterministic RAMs running in time t(n)t(n) can recognize language classes beyond those of deterministic ones under certain cost functions.

Schönhage's RAM0 and RAM1 (1980)

In 1980, Arnold Schönhage introduced the RAM0 and RAM1 models as refined variants of random-access machines, specifically tailored to model storage modification machines (SMMs) and address operations on real computers with word-based architectures. These models emphasize uniform-cost instructions on words of length logarithmic in the input size nn, typically logn\log n bits, to simulate efficient access and computation in practical settings. RAM0 serves as the basic model, supporting fundamental arithmetic operations such as and on these words, with each instruction executing in constant time regardless of word size. This uniform cost criterion ensures that operations remain efficient even as word lengths grow with problem scale, providing a realistic for analyzing algorithms on unbounded . RAM1 extends RAM0 by incorporating and division as additional constant-time operations on logn\log n-bit words, enabling more advanced algebraic computations without proportional time penalties. Both models operate on an infinite sequence of registers, with indirect addressing via computed indices, but they differ from simpler register-to-register variants by explicitly accounting for word-level parallelism in modern hardware. Schönhage demonstrated that RAM0, RAM1, and SMMs can simulate each other in real time, establishing their equivalence for . This formulation bridges models with practical word-oriented processing, avoiding the logarithmic slowdowns in bit-by-bit operations. These models have proven particularly valuable for evaluating algorithms involving large integers, where word sizes scale with input magnitude. For instance, they facilitate precise analysis of (FFT) efficiency in multiplication routines, as the constant-time arithmetic in RAM1 aligns with optimized implementations on word-addressable machines. By focusing on logn\log n-bit operations, RAM0 and RAM1 offer a balanced framework for proving time bounds in arithmetic-heavy computations, influencing subsequent work on realistic RAM simulations.

Limitations and Examples

Non-Turing Equivalence in Bounded Indirection

In the random-access machine (RAM) model with bounded indirection, the number of consecutive indirect addressing operations is limited to a fixed depth , which restricts the machine's ability to simulate arbitrary depths. This limitation prevents the computation of functions that require unbounded nesting of indirect references, such as those in the full class of partial recursive functions. Instead, the model can only compute functions up to a certain level in the , specifically those in Ek\mathcal{E}^k, where the hierarchy classifies primitive recursive functions by growth rates. A concrete example of this non-Turing equivalence is the attempt to compute the Ackermann function A(m,n)A(m,n), defined as: A(0,n)=n+1,A(m+1,0)=A(m,1),A(m+1,n+1)=A(m,A(m+1,n)).\begin{align*} A(0,n) &= n + 1, \\ A(m+1,0) &= A(m,1), \\ A(m+1,n+1) &= A(m, A(m+1,n)). \end{align*} This function is total and computable but not , as it grows faster than any primitive recursive function. For fixed m, A(m,n)A(m,n) is primitive recursive and belongs to Em+3\mathcal{E}^{m+3} in the , but the full two-argument function exceeds the primitive recursive class. To implement A(m,n)A(m,n) on a RAM, a typical approach uses indirect addressing to simulate a stack, where registers store return addresses and parameters, and indirect loads/jumps manage the call stack. With unbounded , this simulates the full recursion tree effectively. However, with indirection bounded to depth k, the stack can only nest up to k levels deep before direct addressing is forced, halting the computation prematurely for cases requiring deeper nesting. For instance, computing A(4,n)A(4,n) requires a recursion depth exceeding any fixed k (since the nesting level grows hyper-exponentially with m), causing the program to loop finitely or terminate with an incorrect value after exhausting the indirection bound. Consider a simplified program trace for a RAM attempting A(m,n)A(m,n) with k=2 bounded indirection, starting with registers R0 = m, R1 = n, and a stack base in R100. The initial setup loads parameters indirectly: LOAD R2 <- [R0] (m, depth 1), LOAD R3 <- [R1] (n, depth 1). For the recursive case A(m+1,n+1), the program pushes parameters to the stack using indirect stores: STORE [R100] <- R2 (depth 1), STORE [[R100]] <- R3 (depth 2, max for k=2). It then jumps indirectly to the subroutine: JUMP [R200] (depth 1). Upon recursion, attempting a further indirect push for A(m, A(m+1,n)) requires depth 3 (STORE [[[R100]]] <- ...), which violates the bound and reverts to direct addressing, corrupting the stack and causing infinite looping in the base case resolution or early termination without computing the correct value. This failure point illustrates why unbounded is essential for , as bounded k confines the model to primitive recursive computations equivalent to Ek\mathcal{E}^k.

Finite vs. Unbounded Memory Considerations

In the random-access machine (RAM) model, finite memory configurations impose significant restrictions on computational capability. A RAM with a fixed number of registers, such as 2^n registers addressable by n-bit addresses where each register holds an n-bit word, possesses a total state space of 2^{n(2^n + 1)} configurations, rendering it equivalent to a . Consequently, such a model can only recognize regular languages and cannot handle context-free languages like {a^n b^n | n ≥ 0}, as the bounded storage prevents tracking unbounded nesting or counting beyond the fixed state limit. In contrast, the unbounded memory RAM variant assumes an infinite array of registers, each capable of storing arbitrarily large integers, providing the theoretical ideal for achieving . This model can simulate any computation by encoding the tape contents across registers and performing indirect addressing to mimic tape operations, ensuring equivalence in computable functions and recognizable recursively enumerable languages. Real-world computers approximate this unbounded ideal through techniques like and paging, which extend effective addressable space beyond physical limits by swapping data between fast cache/RAM and slower storage, though with added latency costs not present in the pure model. Key considerations in these models include overflow handling and limitations during simulations. Standard RAM definitions often assume no arithmetic overflow, treating registers as unbounded integers to simplify analysis, though practical implementations may require explicit checks or to manage wrap-around in fixed-width registers. When simulating unbounded RAM on finite hardware, constraints limit the effective to available physical or virtual resources, potentially halting computations for inputs exceeding capacity and necessitating approximations like garbage collection or dynamic allocation.
Add your contribution
Related Hubs
Contribute something
User Avatar
No comments yet.