Commit Graph
6 Commits
Author SHA1 Message Date
AnachronautandClaude Opus 5 2e9cb9af7e Comments are thrown away by the reader, not read one byte at a time
A comment used to walk through tokGet and srcNext and be classified,
character by character, on the way to being discarded. Comments are most
of what this assembler reads: 87 per cent of what Say.asm pulls in once
services.asm is counted, 59 per cent of CosmOS. The house style here is
dense commentary, so the assembler is penalised more than most by its own
sources.

The saving is not the classifying, it is the BOOKKEEPING. srcNext loads
and stores the walking pointer through memory for every character and
asks numCompare whether the buffer is used up. srcSkipComment keeps the
pointer in a data pointer for a whole run and the newline in B, so a
comment byte costs a load, a compare and two steps. A run is capped at
255 so one byte can count it, which is the only reason it loops.

Measured, each version with its own rebuilt images:

              without        with
  hello       791,957     586,184
  Say       9,924,401   4,506,702   2.20x
  Files    13,197,710   7,188,351   1.84x
  Keys     23,091,447  15,069,880   1.53x
  cosmos  886,498,996 789,982,899   1.12x

Which tracks the comment ratios: Say gains most and cosmos least, in
proportion to how much of each is prose.

---- And two mistakes worth keeping ----

The scratch went among the READER'S STATE, which is a block copied whole
by a count written down somewhere else - so every saved file lost the
last four bytes of itself and an include came back with its pointer
wrong. The comment above that block says not to do this, in capitals.
That is twice this week: scriptCopyState had the same shape this morning.

And a file that ends inside a comment has to put back the file that
included it, exactly as srcAtEnd does for a character. NOTHING IN THIS
REPOSITORY ENDS THAT WAY - every source here ends on a line of code with
a newline after it - so break.sh could not catch an error in that path
because nothing reached it. tail.asm is generated with no newline on its
last line for that reason, and usestail.asm names a label after the
include, which is what goes missing when the include never returns.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-09-06 21:33:15 -04:00
AnachronautandClaude Opus 5 4e3258e3f7 Put the libraries on the disk, and say which include is missing
Sieve-16.asm, Life.asm and Fib-16.asm could be read on the machine and not assembled on it.
All three #Include print.asm, and print.asm was not on the disk.

/LIB WAS STILL A HAND-WRITTEN LIST. Programs/Libraries is what an #Include means when it is
not a CosmOS source - print, the integer helpers, the maths - and nobody had thought to name
them, which is the exact failure mirroring the sources was meant to end. It is mirrored now,
so the next one nobody thinks of is there anyway.

AND THE ASSEMBLER SAID NOTHING USEFUL. A missing include returned a failure and printed no
message, so the catch-all at the top said "nothing was written", which is true and useless -
finding out why meant reading the assembler rather than the message. It names the file now,
and where it looked:

  cannot find print.asm, not beside the file that asked and not as /Lib/print.asm

Both halves are worth having, because a name that is in neither place is usually one or the
other spelt wrong. The same shape as More's "error 2": a program that knows exactly what
happened, reporting a number or nothing at all.

The disk also depends on the recipe that lays it out now. It did not, so adding the libraries
left an image built without them and the next run reported the same thing missing - which
sends you looking at the change you just made rather than at the stale thing in front of you.
It cost twenty minutes to notice.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-08-29 15:27:24 -04:00
AnachronautandClaude Opus 5 06bdbf7728 D5: move in, and give the assembler somewhere to look
The demo disk is three directories instead of thirty nine names in one list
with cosmos.asm sitting between fileStream.asm and sbfs.asm:

  /Apps    what you run
  /Source  what you name to the assembler
  /Lib     what those include

The split is by ROLE rather than by which directory the host keeps a file in.
Everything in /Lib is named by an #Include somewhere and by nothing else, which
is what makes it a library rather than a source.

THAT LAYOUT WAS NOT POSSIBLE UNTIL NOW, and finding out why is what this rung
actually cost. An include on the machine was a bare name resolved where you
stood, so every source that calls a service had to sit in the same directory as
services.asm - which is every source worth having. The first arrangement of
this disk put the examples in a directory of their own and none of them would
assemble.

So the native assembler has a search path: beside you, then /Lib. The same rule
the shell already uses for a program it does not recognise, applied to the
thing that reads source, and the same reasoning for it being two fixed places
rather than a list - a list needs somewhere to live between one boot and the
next, and there is no such place yet. It also brings the native assembler
nearer the host one, which has searched -I directories since before there was a
machine to run this on.

The reader's per-file state grew from 293 bytes to 301, because the name it
keeps is a path now and every block of a file is asked for by it. Six of those
would no longer fit the room set aside, so the include list moved up a page.
Both numbers are written down in two places on purpose and both were changed.

dir said cosmos.asm was 17,460 bytes. It is 82,996. The size came out of the
block count's LOW BYTE shifted up and the tail beneath it, which is sixteen
bits, so anything from 256 blocks upward came back as itself less 65,536 - a
plausible number, and wrong. Files that big say their size in blocks now.
Printing the true figure wants decimal printing twenty four bits wide, which is
a page of console.asm to say something nobody reads more precisely than "big".

The Assembler Manual's line about SBFS being flat was the last thing in the
repository still claiming it, and docs.sh now looks for that phrase and three
like it in all four documents. Not a section that is wrong - one clause inside
a paragraph that is otherwise right, which is the shape this kind of staleness
takes.

The duplicate puts are gone with the wildcard that caused them, so building the
disk is quiet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-08-25 10:04:12 -04:00
AnachronautandClaude Opus 5 fb335681d2 M4: SplitBit assembles SplitBit, and then assembles itself
> load Asm.sbx
    > run cosmos.asm
    wrote cosmos.bin: program 7036, data 2448, labels 475
    > run Asm.asm
    wrote Asm.sbx: program 7533, data 4099, labels 555

Both byte for byte identical to what the host assembler builds from the
same source. The machine now builds the operating system it is running on,
and builds the thing that built it.

THE CHECK THAT MATTERS MOST IS THE THIRD ONE. A binary that matches could
still have come from an assembler wrong in some way this particular source
happens not to exercise. So Tests/native.sh boots the CosmOS that CosmOS
built and has THAT assemble CosmOS again - and the second generation is
identical to the first, down to the cycle count. It is a fixed point: the
machinery has been through itself. After this the host is a convenience
rather than a necessity.

WHAT STOOD IN THE WAY was not the assembler. It loaded, faulted at 7,780
cycles, and the fault was in CosmOS: a loaded program is staged at 0x8000
before being blitted into place, so the whole FILE has to fit in the 32,768
bytes above it. The assembler's file was 33,983, and 22K of that was
zeroed scratch buffers - because #Reserve emits what it reserves.

None of that is initialised data. It is scratch, wanted only while the
assembler runs, and while it runs everything above its own data is free.
So the buffers are a MAP now rather than declarations - Assembler/scratch.asm
writes down six addresses and the file carries none of it. 33,983 bytes
became 11,648, and the assembler could load itself.

The map has a file of its own because the reader and the label table both
need addresses out of it while neither includes the other.

The sizes are cut to the largest thing it is asked to build, and that turns
out not to be the operating system: the assembler is 555 labels and 11,648
bytes of output against CosmOS's 475 and 9,564. The hardest thing this
assembles is itself.

Also: sizing it for CosmOS meant raising the label table, and raising the
label table is what pushed the file over the staging limit. The two facts
only met because the first one was tried.

Speed, measured rather than guessed: CosmOS takes 80,168,646 cycles, which
is eighty seconds of emulated time and under a second under --fast. Most of
it is a straight walk of 475 label names, several thousand times. Sorting
or bucketing that is easy and was deliberately not written before there was
something to measure.

make run-cosmos now puts every source file on the disk, so the whole thing
can be done rather than read about.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-08-21 12:03:31 -04:00
AnachronautandClaude Opus 5 c5e4ec3455 M2: the native assembler builds applications
> load Asm.sbx
    > run Say.asm
    wrote Say.sbx: program 46, data 93, labels 7
    > load Say.sbx
    > run built by the machine itself
    it says: built by the machine itself

The machine assembles an application and then runs what it built. Say,
greet and Files all come out byte for byte identical to the C assembler's,
and Tests/native.sh checks all three on every run alongside the boot image
M1 already covered.

WHAT IT TOOK, and it was more than #Include and #Base:

  #Include   The reader is a stack of readers. The current file's whole
             state goes aside - buffer and all, 292 bytes - the new one
             opens, and the end of it pops the old one back. A file goes in
             once; including it twice does nothing, which is what lets two
             libraries depend on a third. The list is forgotten between the
             passes, because the second has to walk the same tree.
  #Base      Cursors start there, so labels hold the addresses the program
             will really have. A program that says where it goes gets the
             SBEX header and a .sbx name; one that says nothing gets SPBT
             and .bin. A program that bases one segment and leaves the
             other unbased with content in it is refused.
  #Reserve   Runs of zeroes, moved over in the first pass and written in
  #Align     the second. How many an #Align comes to depends on where the
             cursor has reached, which is why both passes keep a cursor.
  #Vectors   Names are read and numbered, pinned where the source pins
             them, so SWI osPrintString resolves. Every application needs
             this - a program that calls a service names a vector declared
             in a file it includes.

THE TWO PASSES ARE NOW ONE LOOP, walked twice, with Emitting the only
difference. They have to agree about the length of every token, and the way
they stop agreeing is by being two pieces of code that drifted apart -
which is the exact shape of the bug this assembler found in the C one.
Sharing the body means there is nothing to drift. What is left is checked
anyway: the second pass compares its own totals against the first's and
refuses to write the file if they differ.

THE BUG WORTH RECORDING. The tokenizer holds one character of lookahead,
and at an #Include that character belongs to the file being put aside. It
was carried across and handed back on the way out, which is wrong: a file
runs out in the middle of whatever the tokenizer happens to be doing, so
the character arrives in the middle of a word. `start:` came back as `s`
and then `tart:` - and the result assembled into a perfectly plausible
file. The fix is to undo the read instead, so the character is simply still
there when the file is opened again and the question of when to hand it
back never arises.

Three smaller ones, all old friends: three places took the CONTENTS of a
buffer where they wanted its ADDRESS; vecTakeAuto returned its answer in A,
which a RET puts back; and pass two re-declared every vector because only
the label table was being skipped on the second walk.

sameText moved down into numbers.asm from the label table - four parts want
it now, and a reader test that includes neither labels nor tokens has to
build on its own.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-08-20 22:50:39 -04:00
AnachronautandClaude Opus 5 dcb331c151 SplitBit assembles SplitBit: M1, a single file with no includes
Programs/CosmOS/Assembler/ is an assembler written in SplitBit assembly. It
runs under CosmOS, reads source off a SplitBit disk, and writes a binary back
to it with no host involved anywhere:

    > run Asm.sbx hello.asm
    wrote hello.bin: program 17, data 14, labels 2

THE ACCEPTANCE TEST IS THE BYTES. Tests/native.sh assembles Programs/hello.asm
both ways and compares the two files byte for byte, then runs the one the
machine built. "It ran" and "the sizes look right" both pass for a binary with
a label one byte out, which is a program that jumps into the middle of an
instruction - so the only honest test is the one SplitDisk and sbfs.asm
already work under: two implementations of one written specification, each
checking the other. The files are identical and the result prints Hello,
World! in 70 cycles.

hello.asm is the target because it is the oldest program in the repository.
The first thing this machine ever ran is now the first thing it assembles for
itself.

TWO PASSES OVER STREAMED SOURCE. The C assembler reads every token of every
file into one array; that cannot port, because cosmos.asm alone is 56,047
bytes against 64K of Data Memory. The native one streams through a 256 byte
window, twice, and keeps only the label table between the passes. Two passes
suffice because every length is known without resolving anything - an
instruction's from its shape, a value's is one, a string's is its characters
and a zero - so the first pass fixes every address and the second never needs
a fixup list. A forward reference stops being a special case and becomes the
reason there are two passes at all.

The parts, each checked before anything was built on it:
  source.asm    characters out of a file of any size, with a line number
  token.asm     tokens out of characters, one character of lookahead
  classify.asm  what a token is, in the C assembler's order, which IS the
                language: keyword, instruction, value, string, label
  labels.asm    names packed in an arena, four bytes of index each
  numbers.asm   sixteen bit arithmetic, since sbfs.asm's cannot be reached
  table.asm     the instruction set, generated by the same script the
                monitor's copy is, and now BOTH are checked by docs.sh

readTest.asm and tokenTest.asm check the reader and the tokenizer on their
own, recorded as cosmosSource and cosmosTokens. A wrong classification does
not produce a wrong byte somewhere obvious; it produces a right looking
program of the wrong length, so it is worth catching where it happens.

WHAT IT REFUSES: #Include, #Base, #Align, #Reserve and #Vectors are refused
by name rather than ignored. Skipping a directive would produce a file that
looked right and was the wrong length, which is the worst thing an assembler
can do.

Two traps worth recording, both already known to this project and both hit
again: CALL restores A, B and DP0-DP2, so three routines returning an answer
in A had it undone by their own return; and numStep works on DP0, so three
sites that set DP1 left a pointer that never advanced.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-08-20 22:13:15 -04:00