Files
SplitBit-Emulator/Programs/CosmOS/Source/services.asm
T
AnachronautandClaude Opus 5 9f7dffdeca S1: the write side learns to stream
osFileStart, osFileWrite and osFileDone are the mirror of osFileInfo and
osFileBlock. A program can now write a file it never holds: Pour writes twelve
blocks and a tail while keeping 256 bytes of it at a time, and the host tool
reads all 3,112 bytes back with every block where it was put.

ONE WRITE IS OPEN AT A TIME AND COSMOS HOLDS IT. Reading needs no state - a name
and an index are the whole question - but writing safely does, because the new
file has to exist before the old one is thrown away and something has to
remember which temporary belongs to which name. Keeping that here means the
careful order is written once instead of in every program that streams.

Nothing already on the disk is touched until osFileDone, so a disk without room
says so while the old file is still there. That is stronger than osFileSave can
manage, where the size is only known once the caller has every byte in hand.
osFileSave stays: Edit and Files hand over whole documents and have no reason to
learn any of this.

osFileWrite refuses an index past the end of the file, and that refusal is not
politeness. Files are contiguous, so block nine of a three block file is a real
block belonging to something else, and writing it would put one file's bytes
inside another with nothing anywhere saying so. Checked both ways: the tail
block is allowed and the one past it is not.

Three bugs, all of them the same shape - a register or pointer used for two
things at once:

DP3 carried the block count in and was popped high byte first, which is the
wrong way round from every reader in the system and made the count two hundred
and fifty six times too big.

sbfsStreamStart took the name in DP0 and then wanted DP0 for something else
before it had read it, so it walked whatever it last pointed at and reported
that it could find no room.

sbfsStreamWrite kept the caller's block in DP3 across a find - DP3 being the
pointer a return does not put back, which is exactly why the find uses it too.
What went to the disk was whatever the scan last looked at. It goes in memory
now, and the file is correct because every block says which block it is; a
check on the length alone would have passed all three of these.

Writing no longer finds the file for each block either. Nothing moves a file
once it is made, so where it starts is settled when the temporary is created.
That was not even slow - a scan stops the moment it matches - but it was a walk
of the directory per block for an answer that cannot change, and it is 28 per
cent of the cost of writing forty blocks.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-08-25 14:53:37 -04:00

132 lines
7.4 KiB
NASM

; The services the system offers, named and numbered.
;
; Both sides include this. The system follows it with handlers for the ones it implements.
; A program that only calls them includes this and nothing else, and can then say them by
; name, because a line with a name and nothing after it declares what a vector is called
; and what number it has without claiming to implement it.
;
; THE NUMBERS ARE WRITTEN DOWN HERE, and that is the only place they are written. They
; used to be decided by the order of the lines, which worked and was quietly fragile: a
; service inserted in the middle renumbered everything after it, and a program already
; assembled against the old numbers would go on calling the number rather than the name.
; Worse, the numbers a program got for its OWN traps moved depending on whether it had
; included this file, and a program that had not was given 16 - which is osPrintString.
;
; So these are pinned. They come from the range set aside for numbers that two separately
; assembled programs have to agree about; everything a program names for itself is drawn
; from higher up and cannot collide with these however it is built. Adding a service takes
; the next free number here and disturbs nothing.
;
; Written by Anachronaut
#Vectors
osPrintString 0d16 ; DP0 names a string. Prints it.
osReadLine 0d17 ; DP0 names somewhere to put a line read from the console.
osExit 0d18 ; Give the machine back to the system.
osArgument 0d19 ; DP0 names somewhere to put the rest of the run command.
; ---- What the system does with the disk on a program's behalf ----
;
; A loaded program that wanted a file used to include the whole filesystem, which is two
; and a half kilobytes of it carrying a private copy of code the system already has
; running. These are that code, reachable.
;
; NOTHING HERE MOUNTS ANYTHING. The system mounted the disk before it read the prompt, and
; there is one disk with one buffer registered as one bank; a program mounting it again was
; only ever an artefact of having its own copy of the library.
;
; Sizes are in bytes and fit the registers exactly. A file that can be read into Data
; Memory is under 64K by definition, so its length is sixteen bits: coming back it is DP3,
; going out it is A and B together, and neither direction needs a record in memory that
; both sides have to agree on the shape of.
osFileRead 0d20 ; DP0 names it, DP1 says where. Q is zero if it read, DP3 is how many bytes.
osFileSave 0d21 ; DP0 names it, DP1 is the bytes, A and B are how many. Q is zero if it saved.
osFileDelete 0d22 ; DP0 names it. Q is zero if it went.
osFileRename 0d23 ; DP0 is the name it has, DP1 the name it should have. Q is zero if it moved.
; ---- Reading a file that will not fit ----
;
; osFileRead answers with a whole file in Data Memory, which settles it for anything under
; 64K and settles nothing above. These two are the other way of asking: how big is it, and
; then give me one block of it at a time. Nothing is kept between the calls but the number
; of the block wanted, so there is no handle to open, none to close, and nothing left
; behind by a program that stops in the middle. The system remembers where the last file it
; was asked about lives, so asking for four hundred blocks of one file costs one search of
; the directory rather than four hundred; that is a speed, not a promise, and a caller
; never has to know about it.
;
; THESE TWO SAY WHY WHEN THE ANSWER IS NO, which the others do not. Everywhere else the
; only useful thing to do about a failure is to give up, so one value is enough. These
; exist to be asked questions with - is it there, is there any more of it - and the
; difference between "no disk", "no such file" and "that was the last block" is the answer
; rather than an excuse.
;
; 1 there is no disk
; 2 there is no file of that name
; 3 that block is past the end of the file (osFileBlock only)
; 4 the disk would not read it (osFileBlock only)
osFileInfo 0d26 ; DP0 names it. Q is zero if it is there, DP3 is how many blocks.
osFileBlock 0d27 ; DP0 names it, DP1 says where, A and B are which block from zero.
; ---- Moving about ----
;
; DP0 names a directory. Q is zero if the machine is now in it.
;
; WHAT A PROGRAM CHANGES HERE, THE SHELL PUTS BACK when the program stops - the same
; discipline the Stack and the vector table are held to, and for the same reason. A program
; is entitled to move about; the shell is entitled to find itself where it left off.
;
; This is what makes a bare name mean something to a program: everything a program opens is
; relative to here, so a program given a directory to work in can say "notes.txt" and mean
; the one in it.
osChangeDir 0d28
; ---- Writing a file a block at a time ----
;
; The mirror of osFileInfo and osFileBlock, and the way to write something too big to hold
; in memory. osFileSave stays for a whole document handed over at once, which is what a
; text editor has and what most programs want.
;
; ONE WRITE IS OPEN AT A TIME AND THE SYSTEM HOLDS IT. Reading needs no state - a name and
; an index are the whole question - but writing safely does, because the new file has to
; exist before the old one is thrown away and something must remember which temporary
; belongs to which name. Keeping that here means the careful order is written once instead
; of in every program that streams.
;
; Nothing that already exists is touched until osFileDone, so a disk without room says so
; while the old file is still there.
;
; osFileStart is told the size the way an entry holds one, blocks and a tail, rather than a
; count of bytes - so it reaches the whole disk. osFileSave is handed a byte count in two
; registers and cannot write more than 65,535.
osFileStart 0d29 ; DP0 names it, DP3 is whole blocks, A is bytes in the tail.
osFileWrite 0d30 ; DP1 is the block, A and B together are which one, from zero.
osFileDone 0d31 ; No arguments. The temporary takes the name.
; Q is zero if it read, DP3 is how many of its bytes are the file's:
; a whole 0d256 except in a last block that is short. That count is
; why DP3 answers and not a register - 0d256 does not fit in a byte,
; and a count that lied about a full block would make every reader
; treat the end of a file as a special case.
; ---- And with the console ----
;
; printString is already up there. This is the other half of what a program prints: a
; number, in decimal, without leading zeroes. A and B together, so one service covers both
; a line number and a byte count and there is no need for two.
osPrintNumber 0d24
; ---- Stopping to look ----
;
; A breakpoint. Put SWI osBreak anywhere in a program and the system shows every register as
; the program had them, waits for a key, and carries on.
;
; NOTHING IS OVERWRITTEN, which is what makes this simple. A breakpoint that replaced an
; instruction would have to put it back to continue, and putting it back disarms the
; breakpoint - so firing twice would need the instruction to be stepped over and the
; breakpoint replaced behind it, and this machine has no way to step one instruction. An SWI
; costs two bytes of the program and fires for ever, because there was never anything to
; restore. The price is that it is part of the program: a build with breakpoints in it has
; different addresses from one without.
osBreak 0d25