Files
LithosAnanake/v4/src/stack.c
T
rajamesandClaude Opus 5.5 0da7e32a0b feat(v4.0.0): the stacks are guarded (D-16); DEPTH, PICK and ROLL
Ruled 2026-10-04, revising D-2: stack overflow and underflow are errors
that are shown and return to the prompt, not silent wrap-around.

- Each stack counts what it holds.  Before every opcode the executor
  checks that the stacks hold what it takes and have room for what it
  leaves; otherwise the opcode does nothing and the node faults, as for a
  bad address, to that kind's handler.  Every fault empties both stacks.
- The fault handler is now a table of five jumps: address, data overflow,
  data underflow, return overflow, return underflow.  The host node says
  "Stack overflow", "Stack underflow", "Return stack overflow",
  "Return stack underflow", then ERROR and the prompt.
- Two registers, DSTACK-DEPTH and RSTACK-DEPTH: a fetch reads the depth,
  a store empties the stack.  QUIT, ABORT and the error exits empty the
  return stack before they call anything; ABORT empties the data stack.
- capsule/forth.v4: DEPTH, PICK and ROLL, to FORTH-79 (counting from
  one).  PICK and ROLL set the values above the one wanted aside in
  memory, and work with the stack full.
- Division by zero now takes its operands off the stack, as v3 does.
- A colon with no room for its entry abandons the line.
- tests: every opcode at every depth of both stacks; the faults, the
  registers and the three words from the prompt.

The sizes are unchanged: ten values, nine return entries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 17:31:18 -04:00

117 lines
3.6 KiB
C

/* stack.c -- F18 circular hardware stacks.
*
* The two decompositions (T/S + 8-ring, R + 8-ring) are exactly equivalent to
* flat circular buffers of depth 10 and 9 respectively. The argument, since
* the ISA is specified in terms of the decomposition and the differential
* test in tests/test_stack.c is written against the flat form:
*
* Data stack, 10 deep. Read the stack top to bottom as
*
* T, S, ring[(head-1) mod 8], ring[(head-2) mod 8], ..., ring[head mod 8]
*
* -- that is, ring[(head-1) mod 8] sits immediately below S, and head names
* the oldest slot. push(x) does ring[head]=S; S=T; T=x; head=(head+1) mod 8,
* which appends at the top and rotates the oldest entry into the ring.
* pop() takes T, promotes S, and pulls ring[(head-1) mod 8] up into S while
* stepping head back one -- so the element that was third becomes second and
* the ring is re-seated one position earlier. That is a 10-slot circular
* buffer whose slot 0 is T and slot 1 is S, and the head arithmetic is
* bookkeeping for which ring slot is oldest.
*
* Return stack, 9 deep, same argument with one register instead of two: the
* stack reads R, ring[(head-1) mod 8], ..., ring[head mod 8].
*
* "No overflow or underflow; pushing past the bottom silently overwrites the
* oldest entry" (D-2) is not an extra rule here -- it is what a fixed-depth
* circular buffer with no bounds check does on its own, once the pointer is
* allowed to wrap. Nothing below tests head against anything.
*/
#include "v4/stack.h"
void v4_dstack_reset(v4_dstack *st)
{
st->t = 0;
st->s = 0;
st->head = 0;
st->depth = 0;
for (unsigned i = 0; i < V4_DATA_RING; i++) st->ring[i] = 0;
v4_guard_fill(st->guard_head, V4_DATA_BOUND, V4_GUARD_PATTERN_HEAD);
v4_guard_fill(st->guard_tail, V4_DATA_BOUND, V4_GUARD_PATTERN_TAIL);
}
int v4_dstack_guards_intact(const v4_dstack *st)
{
return v4_guard_intact(st->guard_head, V4_DATA_BOUND, V4_GUARD_PATTERN_HEAD)
&& v4_guard_intact(st->guard_tail, V4_DATA_BOUND, V4_GUARD_PATTERN_TAIL);
}
void v4_dstack_push(v4_dstack *st, v4_cell x)
{
st->ring[st->head] = st->s;
st->s = st->t;
st->t = x;
st->head = (st->head + 1u) % V4_DATA_RING;
if (st->depth < V4_DATA_DEPTH) st->depth++;
}
void v4_dstack_clear(v4_dstack *st) { st->depth = 0; }
void v4_rstack_clear(v4_rstack *st) { st->depth = 0; }
v4_cell v4_dstack_pop(v4_dstack *st)
{
v4_cell x = st->t;
st->t = st->s;
st->head = (st->head + V4_DATA_RING - 1u) % V4_DATA_RING;
st->s = st->ring[st->head];
if (st->depth > 0) st->depth--;
return x;
}
v4_cell v4_dstack_peek(const v4_dstack *st)
{
return st->t;
}
v4_cell v4_dstack_peek2(const v4_dstack *st)
{
return st->s;
}
void v4_rstack_reset(v4_rstack *st)
{
st->r = 0;
st->head = 0;
st->depth = 0;
for (unsigned i = 0; i < V4_RET_RING; i++) st->ring[i] = 0;
v4_guard_fill(st->guard_head, V4_RET_BOUND, V4_GUARD_PATTERN_HEAD);
v4_guard_fill(st->guard_tail, V4_RET_BOUND, V4_GUARD_PATTERN_TAIL);
}
int v4_rstack_guards_intact(const v4_rstack *st)
{
return v4_guard_intact(st->guard_head, V4_RET_BOUND, V4_GUARD_PATTERN_HEAD)
&& v4_guard_intact(st->guard_tail, V4_RET_BOUND, V4_GUARD_PATTERN_TAIL);
}
void v4_rstack_push(v4_rstack *st, v4_cell x)
{
st->ring[st->head] = st->r;
st->r = x;
st->head = (st->head + 1u) % V4_RET_RING;
if (st->depth < V4_RET_DEPTH) st->depth++;
}
v4_cell v4_rstack_pop(v4_rstack *st)
{
v4_cell x = st->r;
st->head = (st->head + V4_RET_RING - 1u) % V4_RET_RING;
st->r = st->ring[st->head];
if (st->depth > 0) st->depth--;
return x;
}
v4_cell v4_rstack_peek(const v4_rstack *st)
{
return st->r;
}