BSM-8 — How a Program Runs

A step-by-step walkthrough showing the actual Python source code alongside memory at each step.
Program: PUSH 3 • PUSH 7 • PUSH 5 • POP • ADD • PRINT • HALT
This pushes three values onto the stack, discards the last one with POP, adds the remaining two (3 + 7 = 10), and prints the result.

PC — current instruction
Live data (program or stack)
SP — next free stack slot
Ghost — in RAM but past SP, unreachable
Empty (zero)
Step 0 — Program Loaded
Before anything runs, load_program() opens the assembled program file and writes each token into RAM one slot at a time starting at slot 0. Instruction names are stored as strings; numeric arguments are stored as integers. Every other slot stays at zero. PC starts at 0 (first instruction). SP starts at 255 (top of the stack region — pointing at the next free slot, which is the entire stack since nothing has been pushed yet).
PC: 0
SP: 255
bsm8.py — BSM8.__init__() and load_program()
class BSM8:
    def __init__(self):
        self.RAM         = BoundedMemory()     # 256 slots, all zero
        self.HEAP_START  = 128
        self.STACK_START = 255
        self.PC          = 0               # start at first slot
        self.SP          = self.STACK_START    # SP = 255

    def load_program(self, program):
        with open(program, 'r') as file:
            content = file.read().split()
            addr = self.PROGRAM_START          # addr = 0
            for token in content:
                if token.isdigit():
                    self.RAM[addr] = int(token) # numeric → integer
                else:
                    self.RAM[addr] = str(token) # name → string
                addr += 1                   # next slot
Program Region (slots 0–9)
SlotValue
0PUSH ← PC
13
2PUSH
37
4PUSH
55
6POP
7ADD
8PRINT
9HALT
Stack Region (slots 252–255)
SlotValue
2520
2530
2540
2550 ← SP

Step 1 — PUSH 3
Every instruction starts with a fetch: read whatever is at RAM[PC], then immediately advance PC by 1. At this moment PC points past the instruction name and lands on its argument. PUSH then reads that argument (the value 3 at slot 1), writes it into RAM[SP] (slot 255 — the current top of the stack), advances PC past the argument, and moves SP down by one to mark slot 255 as occupied. The next free slot is now 254.
PC: 0
SP: 254
bsm8.py — step()
def step(self):
    if self.PC < self.HEAP_START:        # 0 < 128 ✓  (still in program region)

        # ── FETCH ──────────────────────────────────────────
        instruction = self.RAM[self.PC]  # RAM[0] → 'PUSH'
        self.PC += 1                    # PC: 0 → 1  (now pointing at the argument)

        # ── EXECUTE ─────────────────────────────────────────
        elif instruction == 'PUSH':
            self.RAM[self.SP] = self.RAM[self.PC]  # RAM[255] = RAM[1] = 3
            self.PC += 1                          # PC: 1 → 2  (skip past argument)
            self.SP -= 1                          # SP: 255 → 254  (stack grew)
Program Region (slots 0–9)
SlotValue
0PUSH ← PC
13
2PUSH
37
4PUSH
55
6POP
7ADD
8PRINT
9HALT
Stack Region (slots 252–255)
SlotValue
2520
2530
2540 ← SP (next free)
2553

Step 2 — PUSH 7
The exact same code path runs again. This time PC is at slot 2 (another PUSH). The argument 7 is at slot 3. It gets written into RAM[254] — the current SP. SP drops to 253. The stack now holds two values: 3 at slot 255, 7 at slot 254. The top of the stack is always at RAM[SP+1].
PC: 2
SP: 253
bsm8.py — step()
def step(self):
    if self.PC < self.HEAP_START:        # 2 < 128 ✓

        # ── FETCH ──────────────────────────────────────────
        instruction = self.RAM[self.PC]  # RAM[2] → 'PUSH'
        self.PC += 1                    # PC: 2 → 3

        # ── EXECUTE ─────────────────────────────────────────
        elif instruction == 'PUSH':
            self.RAM[self.SP] = self.RAM[self.PC]  # RAM[254] = RAM[3] = 7
            self.PC += 1                          # PC: 3 → 4
            self.SP -= 1                          # SP: 254 → 253
Program Region (slots 0–9)
SlotValue
0PUSH
13
2PUSH ← PC
37
4PUSH
55
6POP
7ADD
8PRINT
9HALT
Stack Region (slots 252–255)
SlotValue
2520
2530 ← SP (next free)
2547
2553

Step 3 — PUSH 5
Same pattern. The value 5 (argument at slot 5) is written into RAM[253]. SP drops to 252. Three values are now live on the stack: 3 at 255, 7 at 254, 5 at 253. The next free slot is 252. We are about to pop the 5 right back off — this push exists just to demonstrate what POP does.
PC: 4
SP: 252
bsm8.py — step()
def step(self):
    if self.PC < self.HEAP_START:        # 4 < 128 ✓

        # ── FETCH ──────────────────────────────────────────
        instruction = self.RAM[self.PC]  # RAM[4] → 'PUSH'
        self.PC += 1                    # PC: 4 → 5

        # ── EXECUTE ─────────────────────────────────────────
        elif instruction == 'PUSH':
            self.RAM[self.SP] = self.RAM[self.PC]  # RAM[253] = RAM[5] = 5
            self.PC += 1                          # PC: 5 → 6
            self.SP -= 1                          # SP: 253 → 252
Program Region (slots 0–9)
SlotValue
0PUSH
13
2PUSH
37
4PUSH ← PC
55
6POP
7ADD
8PRINT
9HALT
Stack Region (slots 252–255)
SlotValue
2520 ← SP (next free)
2535
2547
2553

Step 4 — POP
POP discards the top value without reading it. It does this by simply moving SP up by one. SP goes from 252 to 253. Slot 253 (where the 5 lives) is now “past” SP — the machine considers it unreachable. The value 5 is still physically sitting in RAM at slot 253, but nothing will ever read it again because SP has moved above it. The next push will overwrite that slot without complaint. The stack now holds: 7 at slot 254, 3 at slot 255.
PC: 6
SP: 253
bsm8.py — step()
def step(self):
    if self.PC < self.HEAP_START:        # 6 < 128 ✓

        # ── FETCH ──────────────────────────────────────────
        instruction = self.RAM[self.PC]  # RAM[6] → 'POP'
        self.PC += 1                    # PC: 6 → 7

        # ── EXECUTE ─────────────────────────────────────────
        elif instruction == 'POP':
            self.SP += 1               # SP: 252 → 253  (slot 253 becomes unreachable)
                                        # The value 5 is still at RAM[253] but SP
                                        # has moved above it — it cannot be reached.
Program Region (slots 0–9)
SlotValue
0PUSH
13
2PUSH
37
4PUSH
55
6POP ← PC
7ADD
8PRINT
9HALT
Stack Region (slots 252–255)
SlotValue
2520
2535 ← SP (ghost — was popped)
2547
2553

Step 5 — ADD
ADD pops two values and pushes one result. It pops by moving SP up (exposing the slot), reading the value, then repeating for the second. The two values are added together. The result is written back into the slot where the second value was, then SP moves back down by one to account for the one value now on the stack. Pops two, pushes one: net change is SP moves up by one overall. The old 7 at slot 254 is left behind as a ghost.
PC: 7
SP: 254
bsm8.py — step()
def step(self):
    if self.PC < self.HEAP_START:        # 7 < 128 ✓

        # ── FETCH ──────────────────────────────────────────
        instruction = self.RAM[self.PC]  # RAM[7] → 'ADD'
        self.PC += 1                    # PC: 7 → 8

        # ── EXECUTE ─────────────────────────────────────────
        elif instruction == 'ADD':
            self.SP += 1               # SP: 253 → 254  (move up to expose top)
            a = self.RAM[self.SP]        # a = RAM[254] = 7  (first pop)
            self.SP += 1               # SP: 254 → 255
            b = self.RAM[self.SP]        # b = RAM[255] = 3  (second pop)
            c = a + b                    # c = 7 + 3 = 10
            self.RAM[self.SP] = c        # RAM[255] = 10  (overwrite with result)
            self.SP -= 1               # SP: 255 → 254  (push: mark result as live)
Program Region (slots 0–9)
SlotValue
0PUSH
13
2PUSH
37
4PUSH
55
6POP
7ADD ← PC
8PRINT
9HALT
Stack Region (slots 252–255)
SlotValue
2520
2535 (ghost)
2547 ← SP (ghost — consumed by ADD)
25510

Step 6 — PRINT
PRINT pops the top of the stack and sends it to the terminal. SP moves up by one (from 254 to 255), which exposes slot 255. The value at RAM[255] (which is 10) is read and printed. SP is now 255, the same position it started at — the stack is empty again. Slots 253, 254, and 255 all still hold their old values physically, but they are all past SP and will never be read.
PC: 8
SP: 255
bsm8.py — step()
def step(self):
    if self.PC < self.HEAP_START:        # 8 < 128 ✓

        # ── FETCH ──────────────────────────────────────────
        instruction = self.RAM[self.PC]  # RAM[8] → 'PRINT'
        self.PC += 1                    # PC: 8 → 9

        # ── EXECUTE ─────────────────────────────────────────
        if instruction == 'PRINT':
            self.SP += 1               # SP: 254 → 255  (move up to expose top)
            print(self.RAM[self.SP])   # print(RAM[255]) → prints 10
Program Region (slots 0–9)
SlotValue
0PUSH
13
2PUSH
37
4PUSH
55
6POP
7ADD
8PRINT ← PC
9HALT
Stack Region (slots 252–255)
SlotValue
2520
2535 (ghost)
2547 (ghost)
25510 ← SP (stack empty)
Terminal output: 10

Step 7 — HALT
The fetch reads RAM[9] and gets 'HALT'. PC advances to 10 (past the end of the program) but it does not matter — the execute phase returns True immediately, which signals the main loop to stop. The VM exits cleanly.
PC: 9
SP: 255
bsm8.py — step()
def step(self):
    if self.PC < self.HEAP_START:        # 9 < 128 ✓

        # ── FETCH ──────────────────────────────────────────
        instruction = self.RAM[self.PC]  # RAM[9] → 'HALT'
        self.PC += 1                    # PC: 9 → 10  (past end of program)

        # ── EXECUTE ─────────────────────────────────────────
        elif instruction == 'HALT':
            return True               # signals the main loop to stop

    # Back in __main__:
    result = vm.step()
    if result is True:
        exit(0)                          # ← we land here
Program Region (slots 0–9)
SlotValue
0PUSH
13
2PUSH
37
4PUSH
55
6POP
7ADD
8PRINT
9HALT ← PC
Stack Region (slots 252–255)
SlotValue
2520
2535 (ghost)
2547 (ghost)
25510 ← SP (stack empty)
Machine stopped.