Commit Graph
20 Commits
Author SHA1 Message Date
AnachronautandClaude Opus 5 f3d8985bc4 Vectors in the symbol table, with both of the numbers they have
A vector is the one thing about a program that nothing else can tell
you. A pinned vector has its number in the source that pinned it, but a
vector the assembler numbered has that number nowhere at all - not in
the source, not in the binary in any form a reader can find. Until now
there was no way to learn that a vector became number 64.

It also cost two hops to follow by hand. The name in "SWI osPrintString"
is not the name of the routine that implements it, so finding the code
meant searching for the vector, reading the handler's name off the
Vector Segment, and searching again. A vector row now names the handler
and gives the line the two were tied together on.

Both numbers, at the user's asking, because neither can be worked out
from the other without knowing which table the vector is in: the Number
is what a program writes and the machine dispatches on, the Address is
where the handler's address is stored, base plus twice the number. The
slot is computed with the same expression the loader is given, so what
the table says and what gets written there cannot drift apart. A vector
a program only declares is listed too - that is how a program says which
vectors it calls, and how two programs can be checked against each other
for agreeing about a number.

A device has no name of its own, being named by the port it is plugged
into, so it is listed under its handler.

The first field is now Kind rather than Memory, because Vector and
Device are not memories. Sorted Program, Data, Vector, Device.

docs.sh checks the six fields against the manual and against real dumps
of two programs - Keys, a loadable program with all four kinds, and
cosmos, a boot image whose segments both start at zero. It now also
checks that a row's name really appears on the line the row names, which
is what catches the string-newline bug fixed in ca6c8ca coming back.
Verified with break.sh four ways: wrong slot arithmetic, vectors
dropped, a field renamed in the manual, and that bug reintroduced.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-09-05 10:51:50 -04:00
AnachronautandClaude Opus 5 3527812c41 A symbol table says which memory, and where the name was written
The dump was an address and a name. Both of the questions it gets asked
were only half answered.

"What is at this address" was ambiguous, because Program and Data are
separate memories and an address alone does not say which one. That is
easy to miss in a loadable program, where the segments are usually based
far apart - and immediate in a boot image, where both start at zero:
replCalculator has a Program 0003 and a Data 0003 and the old file
printed both as "0003 <name>".

"Where is this defined" was not answered at all, and it is the one that
matters more as a program grows. A name defined once and called in forty
places is hard to find by searching. Lander's table names five files
besides its own; CosmOS and its libraries define over a thousand names
across a dozen.

So: memory, address, name, file, line, separated by tabs, sorted by
memory and then address with Program first. Tabs because that makes it a
table cut, awk and sort already read, and no heading line because
nothing should have to know to skip one. Everything needed was already
being passed to addLabel and thrown away; the file name points at the
copy the include list owns, which outlives the label table.

The manual describes the five fields, and docs.sh now settles that
description against a real dump - the shape, not the values, so that an
example cannot go stale and turn editing a program into editing a
manual. Verified with break.sh three ways: a reordered field, a dropped
field, and a field renamed in the manual.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-09-05 10:21:06 -04:00
AnachronautandClaude Opus 5 000a6d39cb Somewhere to send the fault about there being nowhere to send it
Dispatching through a vector with nothing in it was the one fault this machine
could not hand over, because the thing that would hand it over is the thing that
has just found nothing to hand it to. It stopped the machine and no program
could do anything about it - so calling a service the system does not implement
was fatal, and that is an ordinary mistake to make.

Two new fault vectors: 5 when a software vector was empty, 6 when a device
interrupted and its hardware entry was. Separate, because they are separate
mistakes with separate fixes - one is a program calling something that is not
there, the other a program that asked to be interrupted and forgot the handler.

WHICH ENTRY WAS EMPTY ARRIVES IN Q, and it is the only thing on this machine a
handler is given in a register. Not a fault cause register by another route: the
vector still says what happened and Q says which of the 256 entries it happened
about, which is a parameter and not a cause. It costs no new state at all,
because the frame already saved the Q the interrupted program had and RETI puts
it back.

The escalation happens once. If vector 5 or 6 is itself empty the machine stops
the way it always did, having genuinely run out of places to go.

swiFaultTest is what guards that, and it was written long before any of this: it
installs nothing, so it must still get the old halt. Breaking the escalation
fails the two new tests and not that one; making the escalation unbounded fails
that one and not the two new ones. Each break fails exactly the half it belongs
to.

noDeviceTest is fed no input on purpose. The console raises its line once when
input ENDS as well as when a byte arrives - which exists so a program driven by
interrupts is told when nothing more is coming - so with no input at all, that
end is what turns up.

Groundwork for CosmOS's fault screen, which wanted to catch these two and could
not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-09-01 14:40:53 -04:00
Anachronaut cd5f548736 Move the opcode map: nothing in 0x0X, and room for a return variant
Three blocks move and nothing else changes. Branches take 0x60, subroutines
take 0x70, and the ALU moves up into the 0x10 block the two of them used to
share. Order within each block is preserved exactly - this relocates them,
it does not rethink them.

WHAT IT BUYS IS AN EMPTY 0x00 TO 0x0F. Program Memory that was never
written, or a load that stopped part way and left zeroes in its tail, used
to read as a long run of ADDs: the machine carried on through them, arrived
somewhere unpredictable, and whatever broke there was a long way from the
byte that caused it. Now it faults where it is met:

  Fault: 0x00 at Program Address 0x0004 is not an instruction.

That is the address of the byte after the last real instruction, which is
the difference between a diagnosis and a search. Reserving the whole nibble
rather than just 0x00 means a run into blank memory faults wherever it
starts rather than only when it lands on the right byte. runOffTest records
it, and the block is left empty for whatever turns out to want it.

The other half is room: branches and subroutines had filled 0x10 to 0x1F
between them, so a service return that keeps Q and DP3 had nowhere to sit
next to its family. It has 0x76 waiting now.

Five places wrote an opcode down that the scripted remap did not reach, and
four of them were found by tests rather than by looking:

- secondPass.c lists which opcodes take an address, and firstPass.c knows
  SWI by number. Missing those made XOR read as a branch.
- Asm.asm knows SWI by number too, being the other assembler. Missing it
  made the native and host assemblers disagree byte for byte, which is
  exactly the check that exists to catch a thing known in two places.
- loaderTest.asm carries a hand written payload, and its RETI was 0x19. To
  the assembler those are numbers and to the program they are data, so
  nothing but running it could notice. It says so in a comment now.
- The Assembler Manual prints the bytes hello.asm assembles to, and two of
  them were branches.

The monitor's recorded disassembly moved by exactly the bytes it should:
18 became 72 wherever SWI appears, with SETD and INIB untouched and every
disassembled line still reading the same.
2026-08-27 18:05:54 -04:00
Anachronaut c3188ed657 Seventy becomes seventy one: a machine that can wait
HALT is terminal - stepCPU returns at once when the Halt Flag is up, so a
halted machine does not execute, service devices, or take an interrupt -
and that has to stay true, because every test ends with a halt and "halted"
is how a program says it has finished. The consequence was that SplitBit
had no way to wait at all. Every wait was a spin, and a spin is bus
traffic: 11.5% of Type over a 14K file on a disk of ten thousand cycles,
after read-ahead had already hidden three quarters of the latency.

WAIT is 0xFE, one byte, no operands, sitting under HALT where the
instruction that almost stops the machine belongs. Three decisions in it:

- A line already standing means there is nothing to wait for, so WAIT does
  nothing. That is what makes test-then-wait race-free.
- Any line ends the wait, masked or not, so a program can sleep on a device
  it has no handler for and read its status afterwards. Masking says who
  answers a request, not whether it happened.
- A line that wakes the CPU without being dispatched is taken down by the
  WAIT. Left standing it would be found by the next WAIT, which would
  return at once - the program would spin exactly as before while looking
  as though it slept.

Waiting is NOT a Status bit, and that is the trap avoided rather than a
gap: Status rides into the interrupt frame and comes back out, so a machine
interrupted mid-wait would return from its handler still waiting, and wait
again for what it had already been given. An internal field instead.

Idle cycles are counted apart from bus cycles and the halt line says so
when there are any, which is what makes the difference observable at all -
with the line-clearing removed the total moves by ONE cycle, 20,100 against
20,099, and only the idle half changes, halving to 9,976. A test on
totals could never have seen it. Tests/terminal.sh asks that question,
being the file for things a recorded output cannot see, and fails with the
clear removed while "both reads finished" still passes.

Three collisions, all found by building it:

- 0xFE was the assembler's "not an instruction" sentinel. getOpcode now
  answers a negative NOT_AN_OPCODE, which is outside the range of every
  possible answer instead of inside the unused part of it.
- 0xFE was also what faultTest and faultResumeTest executed to provoke a
  fault. They now use 0xFD and say why, because they did not fail when it
  became an instruction - they HUNG, having started sleeping instead.
- Keys.asm has had a label called "wait" for a year, and mnemonics are
  matched uppercased. What that reported was "Branch without label" at the
  BRQ thirty lines away. The assembler now refuses a label that is already
  an instruction, at the label, by name; every instruction added takes a
  word out of the space of label names, so this will happen again.
2026-08-26 11:11:25 -04:00
AnachronautandClaude Opus 5 19ab36a201 The assembler can say where everything ended up
-S writes every label and the address it was given, in address order.

Nothing else knows that. A program on the disk is bytes; the monitor can
disassemble it but has no idea what any of it is called. So counting which
addresses a program calls says a great deal and names nothing - the answer
arrives as a column of numbers and somebody works out by hand which routine
each one is inside.

It was deferred when the native assembler was planned, as a listing and symbol
dump nobody needed yet. Finding out where the assembler spends its time is what
needed it: the top six call targets were addresses until this existed and are
numStep, numCompare, tokGet, srcNext, numAddByte and clsSameName with it.

Sorted by address rather than by name, because the question asked of it is
always "what is at this address".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-08-25 18:15:50 -04:00
AnachronautandClaude Opus 5 b6004bdcde Say "boot image" where that is what is meant
"Binary" was doing three jobs. It meant an SPBT file that the machine starts
from; it meant whatever the assembler happened to produce, which is now
either that or a loadable program; and it meant a compiled host tool. A word
that means three things means none of them, and the first of the three has a
name already - this project has been calling them boot images for a while
and the manuals had not caught up.

  Where it means an SPBT file       -> boot image
  Where it means either output      -> output
  Where it means a host executable  -> left alone
  Where it means base two           -> left alone

The user facing messages move with it:

  Error: No boot image specified.
  Usage: ./SplitBit [OPTIONS] <boot image>
  Error: This is not a SplitBit boot image.
  Error: This boot image is in format version 2, and this emulator reads 1.
  Successfully wrote SplitBit boot image to "hello.bin".

The assembler's own help was the interesting case. Its -o writes either
format, so "the binary" there was never right - it is "the output" now, and
the message that names the format is the one that says which it wrote.

No recorded output contained the word, so nothing needed re-blessing.
Checked before starting rather than after.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-08-21 14:50:03 -04:00
Anachronaut 1d1a14318c Programs can now list and share vectors. 2026-08-17 21:39:27 -04:00
Anachronaut a842c884e8 Added assembler check for string outside Data Segment. 2026-08-17 20:10:33 -04:00
Anachronaut 9e3425d34b Programs can now pin specific routines to specific vectors in SplitBit assembly. Added snake game. 2026-08-17 19:12:42 -04:00
Anachronaut 91c9d49d1b CosmOS pre-alpha and launchable application versions of old programs. 2026-08-17 15:31:49 -04:00
Anachronaut eff6902bcf Block device peripheral and SBFS file system implemented. 2026-08-16 14:03:29 -04:00
Anachronaut 04dfcd707b Memory controller implemented. 2026-08-15 20:43:53 -04:00
Anachronaut 6d1966d500 Interrupt system implemented, some new programs. 2026-08-15 00:44:13 -04:00
Anachronaut 638b68b25c Long standing assembler bugs fixed, new path system. Make compatibility update. 2026-08-14 16:53:28 -04:00
Anachronaut c2440ae5fa Various bug fixes to assembler, added more data pointers. 2026-08-13 23:41:22 -04:00
Anachronaut b50210d127 Fixed use-after-free in Assembler
Use after free occurred in main() in the case that a program being assembled was longer than the initially allocated intermediate array.
2026-04-27 16:58:43 -04:00
Anachronaut 228cf0593a Added stuff. Fixed bug.
Data Pointer value now properly displayed in debug mode.
Added conditional calls to match conditional branches.
2024-10-31 13:30:15 -04:00
Anachronaut 992e44ca12 CALL and RET instructions.
Changed PSHP and POPP into CALL and RET, streamlining their use for subroutine calls.
Updated Assembler and Emulator.
New demonstration programs.
2024-10-27 19:18:54 -04:00
Anachronaut dfa5ad2638 Assembler Completed
Added the Assembler.
Added instructions for the assembler to README.md
Added Assembler Manual
Modified Makefile to build the Assembler.
2024-10-26 15:31:24 -04:00