Files
SplitBit-Emulator/Programs/CosmOS/Apps/Pour.asm
T
Anachronaut 9c144469b4 Take the SplitLint findings that are one operation, leave the rest
Twenty four more sites, and the interesting part is which ones were left
alone. A rule emerged while reading them and it held all the way through:
apply where the repetition is INSIDE one operation, skip where the author's
own structure says it is a new thought, and never where two equal values
mean different things.

Taken:

- Five registers reassigned to a value they already held, where both are
  the same quantity: two masks in one expression in Snake, two spaces
  printed by the monitor, both halves of block zero in waitTest, and a RSTA
  in Pour that the very next instruction overwrote.
- Eighteen SETDs that reload a pointer inside one operation - a store back
  into the variable just read, or an INCD stepping to the second byte of a
  two byte value. Those read correctly without the reload.
- sbfsNext, which branched to the label on the line below it.

Left, with reasons that are the useful part of this:

- Eight registers where the same number means two different things. CosmOS
  and the loader set A to 1 for a blit command and then to 1 again for a
  bank number; Asm compares a type against 3 and then a status against 3.
  Removing those couples one quantity to another that is equal by accident
  and would part company silently.
- Ten RSTAs that open the RSTA/RSTB/CCF/ADD "return zero" block. The
  redundancy is what makes that idiom self contained; taking it out makes
  the return value depend on the line above.
- Eleven SETDs that begin an arm of a comparison chain. Each arm loads,
  compares and branches, and they get reordered - the repetition is the
  reason a new arm can be dropped in anywhere.
- Twenty five SETDs separated from their pointer by a blank line or a
  comment, which is the author saying a new thought starts here.
- Two CCFs before arithmetic, which this codebase writes unconditionally.
- Three redundant branches in test programs whose recorded output includes
  addresses, where three fewer bytes moves what the test demonstrates.

Nine recorded outputs moved and every one is a size in a listing or, for
Life, five more generations inside the same cycle budget. Behaviour is
unchanged everywhere: cosmosSnake and cosmosEdit pass byte for byte while
Snake loses eight bytes and Edit twelve.

CosmOS is 10,902 bytes of program against 10,937, and the native assembler
12,173 against 12,183. The CosmOS README's size for Edit moved twice in one
sitting, and this morning's check caught it both times - which it could not
have done before that claim was reworded to name what it was about.
2026-08-26 18:02:35 -04:00

242 lines
3.8 KiB
NASM

; Writes a file one block at a time, without ever holding the whole of it.
;
; This is the write side of what Stream demonstrates for reading: a file bigger than the
; memory building it. It writes as many blocks as it is asked for, each one filled with a
; pattern that says which block it is, so that what comes off the disk afterwards can be
; checked against what should have gone on rather than merely being the right length.
;
; The argument is how many blocks, in decimal. The last one is deliberately a part block,
; because a tail is the case every off-by-one in a filesystem hides in.
;
; Written by Anachronaut
#Include services.asm
#Program
#Base 0x4000
start:
SETD.0 Argument
INIB 0d8
SWI osArgument
SETD.0 Argument
LDA.0
BRA useDefault
CALL readCount
BRI counted
useDefault:
INIA 0d4
SETD.0 Blocks
STA.0
counted:
; Nothing may be zero blocks: the tail below would then be the whole file.
SETD.0 Blocks
LDA.0
BNA haveCount
INIA 0d1
STA.0
haveCount:
; Whole blocks, and a tail of forty bytes on the end of them.
SETD.0 Name
SETD.3 0x00 0x00
SETD.0 Blocks
LDA.0
PSHA
POPB
RSTA
PSHA
PSHB
POPD.3
SETD.0 Name
INIA 0d40
SWI osFileStart
BNQ startFailed
; Each whole block, filled with its own number.
RSTA
SETD.0 Which
STA.0
nextBlock:
SETD.0 Which
LDA.0
SETD.2 Blocks
LDB.2
CCF
SUB
BRQ theTail
CALL fillBlock
SETD.1 Block
SETD.0 Which
LDA.0
RSTB
PSHA
POPB
RSTA
SWI osFileWrite
BNQ writeFailed
SETD.0 Which
LDA.0
INCA
STA.0
BRI nextBlock
theTail:
; And the part block at the end, which is the same fill cut short by the size given at
; the start. Only the first forty bytes of it will belong to the file.
CALL fillBlock
SETD.1 Block
SETD.0 Which
LDA.0
RSTB
PSHA
POPB
RSTA
SWI osFileWrite
BNQ writeFailed
; And how big it turned out to be, which here is what was asked for: this one knows its
; size from the start. Something that did not - an assembler, say - would ask for more
; than it needed and say the truth here.
RSTA
PSHA
SETD.0 Blocks
LDA.0
PSHA
POPD.3
INIA 0d40
SWI osFileDone
BNQ doneFailed
SETD.0 Wrote
SWI osPrintString
SETD.0 Blocks
LDB.0
RSTA ; A and B together are the number, so the count is the low half.
SWI osPrintNumber
SETD.0 AndTail
SWI osPrintString
SWI osExit
; The block becomes 256 copies of the block number plus a fixed byte, so that a block
; written into the wrong place is visible rather than merely being bytes.
fillBlock:
SETD.0 Block
SETD.1 Which
LDA.1
INIB 0x41
CCF
ADD
MVQA
RSTB
fillLoop:
STA.0
INCD.0
DECB
BNB fillLoop
RET
; The argument, in decimal, into Blocks. Anything that is not a digit ends it.
readCount:
RSTA
SETD.1 Blocks
STA.1
SETD.0 Argument
countLoop:
LDA.0
BRA countDone
INIB 0x30
CCF
SUB
MVQA
INIB 0d10
CCF
SUB
BNC countDone ; Not a digit, so the number ended.
SETD.1 Blocks
LDB.1
PSHA
INIA 0d10
CALL timesTen
POPA
SETD.1 Scratch
LDB.1
CCF
ADD
MVQA
SETD.1 Blocks
STA.1
INCD.0
BRI countLoop
countDone:
RET
; B times ten into Scratch, by adding it up. Nothing here is bigger than a byte.
timesTen:
RSTA
SETD.0 Scratch
STA.0
INIA 0d10
tenLoop:
PSHA
SETD.0 Scratch
LDA.0
SETD.2 TenHold
STB.2
LDB.2
CCF
ADD
MVQA
STA.0
POPA
DECA
BNA tenLoop
RET
startFailed:
SETD.0 NoStart
SWI osPrintString
SWI osExit
writeFailed:
SETD.0 NoWrite
SWI osPrintString
SWI osExit
doneFailed:
SETD.0 NoDone
SWI osPrintString
SWI osExit
#Data
#Base 0x2000
Name:
"poured.dat"
Argument:
#Reserve 0d9
Blocks:
0x00
Which:
0x00
Scratch:
0x00
TenHold:
0x00
Block:
#Reserve 0d256
Wrote:
"poured "
AndTail:
" blocks and a tail of 40
"
NoStart:
"could not start it
"
NoWrite:
"could not write a block
"
NoDone:
"could not finish it
"