Check the memory map against itself, not only against the code

The CosmOS README's Data row gave the system 0x0000-0x3FFF and a loaded
application 0x2000 and above: two columns of one row that cannot both be
true. Program was doubled to 0x3FFF when CosmOS outgrew its first map and
that number was copied into the Data row as well, where the answer is
0x1FFF.

docs.sh measured both segments against the CosmOS column and passed the
table anyway, because it never read the column beside it. A number checked
against the code and not against the number next to it is still unchecked,
so it now reads both and compares them - and compares two further copies of
the same fact that had gone stale on their own: the minimal application in
the README, still based where applications lived before the doubling, and
the map cosmos.asm opens with, which somebody reading the system reads
before they read the README.

Each of the three checks was confirmed by breaking the fact and watching it
fail; the first reproduces exactly the text this commit removes. While in
that header, the command list said five commands and CosmOS has eleven and
a search path besides, and "dump is next" outlived the monitor.
This commit is contained in:
Anachronaut
2026-08-25 22:54:14 -04:00
parent db3d349da8
commit aa7bdc6acd
3 changed files with 78 additions and 16 deletions
+9 -3
View File
@@ -439,7 +439,7 @@ CosmOS divides the two SplitBit address spaces by convention:
| Memory | CosmOS | Loaded application | | Memory | CosmOS | Loaded application |
| -- | -- | -- | | -- | -- | -- |
| Program Memory | `0x0000` through `0x3FFF` | `0x4000` and above | | Program Memory | `0x0000` through `0x3FFF` | `0x4000` and above |
| Data Memory | `0x0000` through `0x3FFF` | `0x2000` and above | | Data Memory | `0x0000` through `0x1FFF` | `0x2000` and above |
Both of CosmOS's halves were doubled once it outgrew the first ones. **The division is a Both of CosmOS's halves were doubled once it outgrew the first ones. **The division is a
convention and nothing enforced it**, so CosmOS quietly grew past `0x1FFF` and the next convention and nothing enforced it**, so CosmOS quietly grew past `0x1FFF` and the next
@@ -448,6 +448,12 @@ fails later, in whatever part of the shell the program happened to cover. `make
measures both segments against the numbers in this table, so the table is checked rather measures both segments against the numbers in this table, so the table is checked rather
than merely written down. than merely written down.
**The table is checked against itself as well.** The first version of that check read only
the CosmOS column, and so it passed a table whose Data row gave the system `0x3FFF` and an
application `0x2000` - two columns that cannot both be true, sitting next to each other.
Measuring one number against the code and never against the number beside it is how a
specification contradicts itself in public.
Applications state their actual Program and Data addresses with `#Base`. The SplitBit Applications state their actual Program and Data addresses with `#Base`. The SplitBit
assembler then writes an SBEX loadable image containing those addresses, the entry point, assembler then writes an SBEX loadable image containing those addresses, the entry point,
the segment lengths, and any vectors the application needs. CosmOS does not relocate the segment lengths, and any vectors the application needs. CosmOS does not relocate
@@ -475,7 +481,7 @@ A minimal CosmOS application therefore looks like this:
#Include services.asm #Include services.asm
#Program #Program
#Base 0x2000 #Base 0x4000
start: start:
SETD.0 Message SETD.0 Message
@@ -483,7 +489,7 @@ start:
SWI osExit SWI osExit
#Data #Data
#Base 0x1000 #Base 0x2000
Message: Message:
"Hello from CosmOS." "Hello from CosmOS."
+14 -10
View File
@@ -10,10 +10,10 @@
; The system keeps to the bottom of both memories, and everything above is for whatever ; The system keeps to the bottom of both memories, and everything above is for whatever
; it is running: ; it is running:
; ;
; Program Memory 0x0000 - 0x1FFF the system ; Program Memory 0x0000 - 0x3FFF the system
; 0x2000 - a loaded program's code ; 0x4000 - a loaded program's code
; Data Memory 0x0000 - 0x0FFF the system ; Data Memory 0x0000 - 0x1FFF the system
; 0x1000 - a loaded program's data ; 0x2000 - a loaded program's data
; ;
; Nothing enforces that. Nothing can: the fence guards a range, and this is a convention ; Nothing enforces that. Nothing can: the fence guards a range, and this is a convention
; about which range belongs to whom rather than a rule about what may be touched. The ; about which range belongs to whom rather than a rule about what may be touched. The
@@ -25,14 +25,20 @@
; ;
; ---- What it can do ---- ; ---- What it can do ----
; ;
; dir List what is on the disk. ; dir List what is in the current directory.
; cd Go somewhere else, or to the root when told nothing.
; mkdir Make a directory. rmdir Remove an empty one.
; delete Remove a file. rename Give one another name.
; load Read a program off the disk and put it where it asks to go. ; load Read a program off the disk and put it where it asks to go.
; run Start the program that was loaded. ; run Start the program that was loaded.
; help Say what these are. ; help Say what these are.
; exit Stop. ; exit Stop, or leave the monitor if that is where you are.
; ;
; dump is next. The dispatch below is a chain of comparisons, which is the right shape for ; Anything else that is not one of those is looked for as a program and run if it is
; five commands and the wrong shape for twenty; when it grows, the table that ; found, so most of what the machine does is not in this list at all.
;
; The dispatch below is a chain of comparisons, which is the right shape for five commands
; and the wrong shape for twenty; it is eleven now, and when it grows the table that
; dispatchTest.asm demonstrates is where it should go. ; dispatchTest.asm demonstrates is where it should go.
; ;
; Written by Anachronaut ; Written by Anachronaut
@@ -65,8 +71,6 @@ bootNoDisk:
CALL printString CALL printString
CALL newLine CALL newLine
; ---- The loop ----
; ---- The loop ---- ; ---- The loop ----
; ;
; The shell has two modes and one prompt that says which. Ordinary mode runs programs; ; The shell has two modes and one prompt that says which. Ordinary mode runs programs;
+55 -3
View File
@@ -467,9 +467,12 @@ for name, text in (("the Programming Manual", pm), ("the Assembler Manual", am),
# That is why this reads the numbers out of the table rather than being told them: the # That is why this reads the numbers out of the table rather than being told them: the
# table is the specification, and a table nothing checks is the thing that goes stale. # table is the specification, and a table nothing checks is the thing that goes stale.
readme = open("Programs/CosmOS/README.md").read() readme = open("Programs/CosmOS/README.md").read()
row = re.compile(r"\|\s*(Program|Data) Memory\s*\|\s*`0x0000` through `0x([0-9A-Fa-f]{4})`") row = re.compile(r"\|\s*(Program|Data) Memory\s*\|\s*`0x0000` through `0x([0-9A-Fa-f]{4})`"
limits = {kind: int(top, 16) + 1 for kind, top in row.findall(readme)} r"\s*\|\s*`0x([0-9A-Fa-f]{4})` and above\s*\|")
if len(limits) != 2: table = {kind: (int(top, 16) + 1, int(base, 16)) for kind, top, base in row.findall(readme)}
limits = {kind: room for kind, (room, base) in table.items()}
bases = {kind: base for kind, (room, base) in table.items()}
if len(table) != 2:
problems.append("the CosmOS README no longer states a memory map this can check") problems.append("the CosmOS README no longer states a memory map this can check")
else: else:
built = subprocess.run(["./Assembler", "-I", "Programs/CosmOS/Source", built = subprocess.run(["./Assembler", "-I", "Programs/CosmOS/Source",
@@ -487,6 +490,55 @@ else:
" reaches into the %d bytes an application is loaded at, and loading" " reaches into the %d bytes an application is loaded at, and loading"
" one will overwrite it" % (kind, used, room, used - room)) " one will overwrite it" % (kind, used, room, used - room))
# ---- And the table against itself ----
#
# The check above reads the CosmOS column and never the one beside it, so it passed a
# Data row that gave the system 0x0000-0x3FFF and an application 0x2000 and above -
# two halves of one sentence contradicting each other in print. A number checked
# against the code and not against the number next to it is still unchecked.
for kind in ("Program", "Data"):
room, base = table[kind]
if base != room:
problems.append(
"the CosmOS README gives the system %s Memory up to 0x%04X and puts an"
" application at 0x%04X - the two columns of that row disagree"
% (kind, room - 1, base))
# ---- And the example under it ----
#
# The minimal application in the same section is what somebody copies, so it is the
# part of the map most worth being right. It went stale across the doubling while the
# table above it was corrected.
example = re.search(r"```asm\n(.*?)```", readme, re.S)
if not example:
problems.append("the CosmOS README no longer shows a minimal application")
else:
shown = dict(zip(("Program", "Data"),
re.findall(r"#Base 0x([0-9A-Fa-f]{4})", example.group(1))))
for kind in ("Program", "Data"):
if kind not in shown:
problems.append("the minimal CosmOS application shows no %s #Base" % kind)
elif int(shown[kind], 16) != bases[kind]:
problems.append(
"the minimal CosmOS application is based at 0x%s in %s Memory and the"
" map above it says 0x%04X" % (shown[kind].upper(), kind, bases[kind]))
# ---- And the copy in the source ----
#
# cosmos.asm opens with the same map in its own words, because somebody reading the
# system reads that before they read the README. Three copies of one fact, so all
# three are compared.
header = open("Programs/CosmOS/Source/cosmos.asm").read()[:4096]
stated = dict(re.findall(
r"(Program|Data) Memory\s+0x0000 - 0x([0-9A-Fa-f]{4})\s+the system", header))
for kind in ("Program", "Data"):
if kind not in stated:
problems.append("cosmos.asm no longer opens with a %s Memory map" % kind)
elif int(stated[kind], 16) + 1 != limits[kind]:
problems.append(
"cosmos.asm says the system keeps below 0x%s in %s Memory and the CosmOS"
" README says 0x%04X" % (stated[kind].upper(), kind, limits[kind] - 1))
if problems: if problems:
print("The manuals and the code disagree:") print("The manuals and the code disagree:")
for p in problems: for p in problems: