Add SplitLint, and let it be told when something is deliberate

SplitLint reports valid assembly that has a shorter direct expression:
zero loads that could be RSTA or RSTB, Q moved through the stack where MVQA
would do, self-cancelling push and pop pairs, assignments overwritten
before use, unreachable fallthrough, one-byte pointer moves that could be
INCD or DECD, a branch to the label directly below it, a SETD reloading an
address the pointer already holds, and branches whose carry is known.

Its model is deliberately local and conservative: every label and every
directive forgets all known state, so a claim only ever lives inside a
straight-line region. It knows the calling convention - CALL forgets DP3
and keeps the rest, RCAL and SWI forget everything - and it shares
assembly.o with the assembler, so an added opcode cannot leave it holding a
private copy of the instruction table.

260 warnings across the corpus, of which three were wrong in the way that
matters: branchTest.asm and interruptFlagTest.asm exist to check that a
branch whose carry is known behaves correctly, so a diagnostic saying the
outcome is known is exactly right and exactly unwanted.

A line whose comment says "splitlint: <reason>" is now not reported on. THE
REASON IS REQUIRED and a bare marker is refused, because a suppression
nobody explained outlives whatever made it necessary. Suppressed warnings
are not counted, so --fatal-warnings does not fail on one, and the number
of them is printed at the end so the claim is visible rather than silent.

Tests/lint.sh checked a TOTAL: twenty three warnings expected, twenty three
found. That number stays right while the thing behind it goes wrong - a
rule that stopped firing while another fired twice would pass, and so would
a rule reporting at the wrong line. It now checks which warning came out
and at which line, that nothing else came out, and that the four lines
meant to stay quiet did. Confirmed by breaking one rule's message and
watching it name that rule: the old assertion passed the same sabotage,
because the warning still fired and the count never moved.

Written with the user while I was away; my part is the suppression
mechanism, the harness rewrite, and the three marks in the test programs.
This commit is contained in:
Anachronaut
2026-08-26 17:23:17 -04:00
parent c216c83e12
commit 2b079324ae
7 changed files with 1007 additions and 11 deletions
+33 -1
View File
@@ -23,6 +23,7 @@ wrote Asm.sbx: program 7533, data 4099, labels 555
| [`Source/Emulator`](Source/Emulator) | The machine: CPU, memory controller, devices, console, disk |
| [`Source/Assembler`](Source/Assembler) | The assembler that runs on a host |
| [`Source/DiskTool`](Source/DiskTool) | SplitDisk, which reads and writes SplitBit's filesystem |
| [`Source/Linter`](Source/Linter) | SplitLint, which points out needlessly long assembly forms |
| [`Programs/Examples`](Programs/Examples) | Programs to read: hello, a calculator, Fibonacci, a prime sieve, Life |
| [`Programs/Libraries`](Programs/Libraries) | Code included by name rather than linked, since there is no linker |
| [`Programs/Loader`](Programs/Loader) | The standalone loader CosmOS grew out of |
@@ -49,7 +50,7 @@ wrote Asm.sbx: program 7533, data 4099, labels 555
## Getting Started:
Clone it and build the three tools. You need gcc and make, or similar:
Clone it and build the four tools. You need gcc and make, or similar:
```
git clone https://github.com/RealBusinessAccount/SplitBit-Emulator.git
@@ -156,6 +157,37 @@ this is what turns such a count into a list of routine names.
Without `-o` the output takes the source file's name, in the directory you called the assembler from, with the extension the format asks for: `.bin` for a boot image and `.sbx` for a loadable program. Included files are looked for beside the file that includes them, and then along the directories given with `-I`.
## Checking Assembly: SplitLint
```
./SplitLint [--fatal-warnings] <sourcefile> [sourcefile ...]
```
SplitLint reports valid assembly that has a shorter direct expression, beginning with
zero loads that can use `RSTA` or `RSTB`, Q-to-register transfers that need not pass
through the stack, self-push/pop pairs, register assignments overwritten by the next
instruction, instructions with no ordinary fallthrough path, and one-byte Data Pointer
changes that can use `INCD` or `DECD`, including direct branches whose target is already
the next labeled address. It also tracks symbolic Data Pointer bases and known offsets to
find a `SETD` that reloads an address the pointer already holds. Device input and stack
pops are excluded from dead-assignment checks because consuming their input is itself an effect. SplitLint's control-flow
knowledge is deliberately local: labels begin reachable regions, while an unparsed
directive or data item ends the current claim. It does not yet expand includes or build a
complete control-flow graph. Warnings normally leave a successful exit status, while
`--fatal-warnings` makes any warning fail the command for use in automated checks.
**A line whose comment says `splitlint: <reason>` is not reported on, and the reason is
required.** A suppression with no explanation is a way to make a tool quiet rather than a
way to say something, and a bare marker is refused rather than honoured. The corpus has
three of them, all in test programs: `branchTest.asm` exists to check that a branch whose
carry is known behaves correctly, so a diagnostic saying the outcome is known is exactly
right and exactly unwanted. Suppressions are counted and reported at the end of a run, so
that the claim they make is visible rather than silent.
The same local model tracks whether carry is known set or clear. It reports a redundant
`CCF`, a `BRC` or `BNC` whose outcome is already determined, and computes carry through
increment, decrement, addition, and subtraction when their inputs are known.
## Managing Disks: SplitDisk
```