Load a program that has no data

A five instruction program that writes one port and exits has no Data Segment at
all, and the loader stopped the machine dead on it. It asked the memory
controller to move a segment of no bytes, and a length of zero asks for the
whole 64K - which is the machine's rule, and a reasonable one, since two bytes
cannot say 65536 and a transfer of nothing is not usually what anybody meant. It
is exactly what was meant here. 64K did not fit, the controller refused, and the
load stopped half done.

ON A TERMINAL THAT PRINTS A FAULT WITH AN ADDRESS. Behind a window it is a
frozen picture and no reason at all, which is how it was found and is a separate
problem from this one.

The header says how long each segment is, so the loader knows before it asks.
Both bytes are already in hand, so the test costs one OR. Nothing is lost by
skipping the transfer: a blit leaves the controller's addresses past whatever it
touched, and a blit of nothing would have left them where they already are,
which is where the vectors are read from next.

Guarded for the code segment too. A program with no code is equally assemblable
and would have stopped in exactly the same place.

Mode.sbx is the fix's test and a program worth having on its own: forty columns
or eighty, whichever the screen is not in, which is what a person wanting Snake
drawn twice the size actually needs. Ten instructions and no data, deliberately
- it prints its two digits a register at a time rather than from a string, so it
stays the smallest shape a loadable program can take. Nothing else on that disk
had ever been that shape, which is why nothing had ever tried it.

Reported by the user, who wrote the program.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
Anachronaut
2026-09-01 11:47:20 -04:00
co-authored by Claude Opus 5
parent 373454ec00
commit 81e544eb3d
14 changed files with 128 additions and 12 deletions
+50
View File
@@ -0,0 +1,50 @@
; Forty columns or eighty, whichever the screen is not in.
;
; A machine wakes up in the forty column mode and CosmOS asks for eighty, because that is
; what its own output was written for. A game is the other way round: Snake on a forty column
; screen is the same board drawn twice the size, which is what a person sitting in front of
; it actually wants.
;
; ---- The smallest program this system can load ----
;
; Ten instructions and NO DATA AT ALL, which is not a curiosity: it is the shape that found
; a bug in the loader. Every program written for CosmOS until this one had something in its
; Data Segment, so the loader had never been asked to move a segment of no bytes - and a
; length of zero asks the memory controller for the whole 64K, which does not fit, which it
; refused, which stopped the machine in the middle of loading. On a terminal that printed a
; fault. Behind a window it looked exactly like a hang.
;
; So this is kept dataless on purpose. The two digits it prints are put in a register one at
; a time rather than being a string, which is the only reason it can say anything at all.
;
; Written by Anachronaut
#Include services.asm
#Program
#Base 0x4000
start:
INA 0x31 ; Which mode the screen is in now.
BRA modeWide ; Nought is the forty column one, so go the other way.
; Anything else becomes forty, and that deliberately includes bitmap mode. A program that
; left the screen with no text on it left nowhere to print, so coming back to a mode that
; has characters in it is more use than refusing.
RSTA
OUTA 0x31
INIA 0x34 ; '4'
BRI modeSay
modeWide:
INIA 0x01
OUTA 0x31
INIA 0x38 ; '8'
modeSay:
OUTA 0x00
INIA 0x30 ; '0'
OUTA 0x00
INIA 0x0A
OUTA 0x00
RSTA
SWI osExit
+2
View File
@@ -572,6 +572,8 @@ from every assembly file in it. Several are old programs written for the bare ma
| Compare | Compares two files a block at a time, stopping at their real tails rather than comparing unused bytes in the final disk blocks. | | Compare | Compares two files a block at a time, stopping at their real tails rather than comparing unused bytes in the final disk blocks. |
| Wander | Goes to the directory it is given and reads a file there by a bare name. The only thing that moves the machine from inside a program, and so the only thing that can check the shell puts the working directory back afterwards. | | Wander | Goes to the directory it is given and reads a file there by a bare name. The only thing that moves the machine from inside a program, and so the only thing that can check the shell puts the working directory back afterwards. |
| More | A forward-only pager. Space advances a screen, Return one line, and q stops. | | More | A forward-only pager. Space advances a screen, Return one line, and q stops. |
| Press | Says what the console handed it, in hexadecimal and by name. It reads a line and then keys, because the keys that are not characters are dropped in line mode and delivered in key mode, and both halves of that rule want showing. |
| Mode | Forty columns or eighty, whichever the screen is not in. Ten instructions and no data at all, which is the point of it: it is the smallest shape a loadable program can take, and the loader used to stop the machine dead on one. |
### The Monitor: ### The Monitor:
+27 -4
View File
@@ -1548,11 +1548,29 @@ loadVersionKnown:
LDA.0 LDA.0
OUTA 0xE6 OUTA 0xE6
INCD.0 INCD.0
LDA.0 LDB.0
OUTA 0xE7 OUTB 0xE7
; ---- Nothing to move is not the same as everything to move ----
;
; A LENGTH OF ZERO ASKS THE CONTROLLER FOR THE WHOLE 64K, which is the machine's rule and
; a sensible one for a length somebody typed: two bytes cannot say 65536, and a transfer
; of no bytes is not usually what anybody meant. It is exactly what is meant here. A
; segment can genuinely be empty - a five instruction program that only writes to a port
; has no data at all - and this asked to move 65536 bytes into a bank that has not got
; them, so the controller refused and the machine stopped in the middle of loading.
;
; The header says how long the segment is, so the loader knows before it asks. It has
; both bytes of the length in hand here, so testing them costs one instruction.
;
; NOTHING IS LOST BY SKIPPING IT. A blit leaves the controller's addresses past whatever
; it touched, and a blit of nothing would have left them exactly where they are.
OR
BRQ loadNoCode
INIA 0x01 INIA 0x01
OUTA 0xE8 ; Blit. OUTA 0xE8 ; Blit.
loadNoCode:
; Then the data. A blit leaves its addresses past whatever it touched, so the source is ; Then the data. A blit leaves its addresses past whatever it touched, so the source is
; already sitting on the first byte of the data and only the destination changes. ; already sitting on the first byte of the data and only the destination changes.
@@ -1571,11 +1589,16 @@ loadVersionKnown:
LDA.0 LDA.0
OUTA 0xE6 OUTA 0xE6
INCD.0 INCD.0
LDA.0 LDB.0
OUTA 0xE7 OUTB 0xE7
; And the same for the data, which is the segment that is actually empty in practice.
OR
BRQ loadNoData
INIA 0x01 INIA 0x01
OUTA 0xE8 ; Blit. OUTA 0xE8 ; Blit.
loadNoData:
; ---- The vectors it brought ---- ; ---- The vectors it brought ----
; ;
+2 -2
View File
@@ -79,7 +79,7 @@ from `make`, not from here.
### 1. Recorded output ### 1. Recorded output
`Tests/run.sh` assembles each program named in `Tests/manifest`, runs it, and compares `Tests/run.sh` assembles each program named in `Tests/manifest`, runs it, and compares
everything it printed against a file in `Tests/expected`. 189 tests, of which 127 run, 35 everything it printed against a file in `Tests/expected`. 190 tests, of which 128 run, 35
only assemble, 16 are expected to fail to assemble, and 11 boot from ROM with no image only assemble, 16 are expected to fail to assemble, and 11 boot from ROM with no image
given at all. given at all.
@@ -443,7 +443,7 @@ than reading all of them:
## The Lint Baseline: ## The Lint Baseline:
`Tests/lint-baseline.txt` records how many warnings of each rule SplitLint finds in each `Tests/lint-baseline.txt` records how many warnings of each rule SplitLint finds in each
file across the whole corpus - 34 file-and-rule pairs. It is checked on every run, and it file across the whole corpus - 33 file-and-rule pairs. It is checked on every run, and it
is checked **in both directions.** is checked **in both directions.**
A new warning appearing is a regression. A recorded warning *disappearing* is also A new warning appearing is a regression. A recorded warning *disappearing* is also
+2 -1
View File
@@ -21,6 +21,7 @@ Say.sbx 156
Break.sbx 149 Break.sbx 149
Grid.sbx 559 Grid.sbx 559
Press.sbx 872 Press.sbx 872
Mode.sbx 48
notes.txt 21 notes.txt 21
Apps <dir> Apps <dir>
hi.script 121 hi.script 121
@@ -32,7 +33,7 @@ outer.script 376
inner.script 44 inner.script 44
loop.script 35 loop.script 35
crossed.txt 560 crossed.txt 560
19 files, 1 directory 20 files, 1 directory
> exit > exit
halted halted
Execution halted. Execution halted.
+2 -1
View File
@@ -11,6 +11,7 @@ Say.sbx 156
Break.sbx 149 Break.sbx 149
Grid.sbx 559 Grid.sbx 559
Press.sbx 872 Press.sbx 872
Mode.sbx 48
notes.txt 21 notes.txt 21
Apps <dir> Apps <dir>
hi.script 121 hi.script 121
@@ -21,7 +22,7 @@ nonl.script 38
outer.script 376 outer.script 376
inner.script 44 inner.script 44
loop.script 35 loop.script 35
18 files, 1 directory 19 files, 1 directory
> drive 1 > drive 1
> dir > dir
other.txt 28 other.txt 28
+2 -1
View File
@@ -18,6 +18,7 @@ Say.sbx 156
Break.sbx 149 Break.sbx 149
Grid.sbx 559 Grid.sbx 559
Press.sbx 872 Press.sbx 872
Mode.sbx 48
notes.txt 21 notes.txt 21
Apps <dir> Apps <dir>
hi.script 121 hi.script 121
@@ -28,7 +29,7 @@ nonl.script 38
outer.script 376 outer.script 376
inner.script 44 inner.script 44
loop.script 35 loop.script 35
18 files, 1 directory 19 files, 1 directory
> exit > exit
halted halted
Execution halted. Execution halted.
+15
View File
@@ -0,0 +1,15 @@
CosmOS
> Mode
40
finished
> echo a narrow screen
a narrow screen
> Mode
80
finished
> echo and a wide one again
and a wide one again
> exit
halted
Execution halted.
[exit 0]
+2 -1
View File
@@ -11,6 +11,7 @@ Say.sbx 156
Break.sbx 149 Break.sbx 149
Grid.sbx 559 Grid.sbx 559
Press.sbx 872 Press.sbx 872
Mode.sbx 48
notes.txt 21 notes.txt 21
Apps <dir> Apps <dir>
hi.script 121 hi.script 121
@@ -21,7 +22,7 @@ nonl.script 38
outer.script 376 outer.script 376
inner.script 44 inner.script 44
loop.script 35 loop.script 35
18 files, 1 directory 19 files, 1 directory
> load > load
load what? load what?
> load nosuch.sbx > load nosuch.sbx
+2 -1
View File
@@ -9,6 +9,7 @@ Say.sbx 156
Break.sbx 149 Break.sbx 149
Grid.sbx 559 Grid.sbx 559
Press.sbx 872 Press.sbx 872
Mode.sbx 48
notes.txt 21 notes.txt 21
Apps <dir> Apps <dir>
hi.script 121 hi.script 121
@@ -19,7 +20,7 @@ nonl.script 38
outer.script 376 outer.script 376
inner.script 44 inner.script 44
loop.script 35 loop.script 35
18 files, 1 directory 19 files, 1 directory
> load Say.sbx > load Say.sbx
loaded, starting at 4000 loaded, starting at 4000
> run the disk took its time > run the disk took its time
+5
View File
@@ -0,0 +1,5 @@
Mode
echo a narrow screen
Mode
echo and a wide one again
exit
-1
View File
@@ -15,7 +15,6 @@ Programs/CosmOS/Assembler/token.asm redundant-assignment 4
Programs/CosmOS/Assembler/token.asm redundant-setd 2 Programs/CosmOS/Assembler/token.asm redundant-setd 2
Programs/CosmOS/Assembler/tokenTest.asm redundant-setd 1 Programs/CosmOS/Assembler/tokenTest.asm redundant-setd 1
Programs/CosmOS/Source/config.asm redundant-assignment 1 Programs/CosmOS/Source/config.asm redundant-assignment 1
Programs/CosmOS/Source/cosmos.asm redundant-assignment 1
Programs/CosmOS/Source/cosmos.asm redundant-setd 10 Programs/CosmOS/Source/cosmos.asm redundant-setd 10
Programs/CosmOS/Source/sbfs.asm branch-to-next 1 Programs/CosmOS/Source/sbfs.asm branch-to-next 1
Programs/CosmOS/Source/sbfs.asm redundant-assignment 4 Programs/CosmOS/Source/sbfs.asm redundant-assignment 4
+6
View File
@@ -118,6 +118,12 @@ for i in 1 2 3 4 5 6 7 8; do "$TOOL" put "$DISKS/sbfs.img" "filler$i.txt" >/dev/
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \ "$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Press.asm" -o "$WORK/Press.sbx" >/dev/null "$ROOT/Programs/CosmOS/Apps/Press.asm" -o "$WORK/Press.sbx" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Press.sbx" >/dev/null "$TOOL" put "$DISKS/cosmos.img" "$WORK/Press.sbx" >/dev/null
# Mode.sbx has NO DATA SEGMENT, which is the whole reason it is here. Nothing else on this
# disk is that shape, and the loader used to stop the machine dead on it: a segment of no
# bytes asks the memory controller for the whole 64K, which does not fit, which it refuses.
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Mode.asm" -o "$WORK/Mode.sbx" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Mode.sbx" >/dev/null
printf 'this is not a program' > notes.txt printf 'this is not a program' > notes.txt
"$TOOL" put "$DISKS/cosmos.img" notes.txt >/dev/null "$TOOL" put "$DISKS/cosmos.img" notes.txt >/dev/null
+11
View File
@@ -305,6 +305,17 @@ cosmosPress | CosmOS/Source/cosmos.asm | run | cosmosPre
# end - the window's path through the gatherer - and it has its own copy of the line mode # end - the window's path through the gatherer - and it has its own copy of the line mode
# rule, so it needs its own test. The two recordings should agree, which is the point. # rule, so it needs its own test. The two recordings should agree, which is the point.
cosmosPressKeys | CosmOS/Source/cosmos.asm | run | - | - | disks/cosmos.img | cosmosPress.in cosmosPressKeys | CosmOS/Source/cosmos.asm | run | - | - | disks/cosmos.img | cosmosPress.in
# A program with NO DATA SEGMENT, loaded and run. Every program written for this system had
# something in its data until somebody wrote one that did not, and the loader stopped the
# machine dead: a segment of no bytes asks the controller for the whole 64K, which does not
# fit, so it refused in the middle of loading. The most basic form a program can take, and
# nothing had ever tried it.
#
# It is run twice, because it is a toggle and the second run has to find the mode the first
# one left. The line between them is there so the recording shows the shell still working on
# a forty column screen, which is a width nothing else here ever runs at - and the shell
# reads that width for itself now, every line it edits.
cosmosNoData | CosmOS/Source/cosmos.asm | run | cosmosNoData.in | - | disks/cosmos.img
# The shell editing the line it is being given, which is the whole reason the keys were # The shell editing the line it is being given, which is the whole reason the keys were
# made to arrive. Every line here is typed wrong and then corrected with a different key, and # made to arrive. Every line here is typed wrong and then corrected with a different key, and
# every one of them comes out as the same command - so the recording says both what the # every one of them comes out as the same command - so the recording says both what the