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
+24 -7
View File
@@ -29,24 +29,28 @@ POSIXFLAGS = -D_XOPEN_SOURCE=700
SRC_DIR_EMU = Source/Emulator
SRC_DIR_ASM = Source/Assembler
SRC_DIR_DSK = Source/DiskTool
SRC_DIR_LINT = Source/Linter
OBJ_DIR = Object
# Source files
EMU_SRCS = emulator.c io.c controller.c utility.c cpu.c bootstrap.c assembly.c
ASM_SRCS = Assembler.c assembly.c firstPass.c Assm-util.c secondPass.c
DSK_SRCS = SplitDisk.c
LINT_SRCS = Linter.c
EMU_OBJS = $(EMU_SRCS:%.c=$(OBJ_DIR)/%.o)
ASM_OBJS = $(ASM_SRCS:%.c=$(OBJ_DIR)/%.o)
DSK_OBJS = $(DSK_SRCS:%.c=$(OBJ_DIR)/%.o)
LINT_OBJS = $(LINT_SRCS:%.c=$(OBJ_DIR)/%.o) $(OBJ_DIR)/assembly.o
# Output binary names
EMU_TARGET = SplitBit
ASM_TARGET = Assembler
DSK_TARGET = SplitDisk
LINT_TARGET = SplitLint
# Default target: build both emulator and assembler
all: $(EMU_TARGET) $(ASM_TARGET) $(DSK_TARGET)
# Default target: build the emulator and its three host-side tools.
all: $(EMU_TARGET) $(ASM_TARGET) $(DSK_TARGET) $(LINT_TARGET)
# Emulator binary
$(EMU_TARGET): $(EMU_OBJS)
@@ -65,6 +69,15 @@ $(OBJ_DIR)/%.o: $(SRC_DIR_EMU)/%.c
$(DSK_TARGET): $(DSK_OBJS)
$(CC) $(CFLAGS) -o $(DSK_TARGET) $(DSK_OBJS)
# Assembly source linter. The instruction table is shared with the assembler and
# emulator so that adding an opcode cannot leave the linter with a private copy.
$(LINT_TARGET): $(LINT_OBJS)
$(CC) $(CFLAGS) -o $(LINT_TARGET) $(LINT_OBJS)
$(OBJ_DIR)/Linter.o: $(SRC_DIR_LINT)/Linter.c
@mkdir -p $(OBJ_DIR)
$(CC) $(CFLAGS) $(POSIXFLAGS) $(DEPFLAGS) -c $< -o $@
# Compile disk tool source files to object files
$(OBJ_DIR)/%.o: $(SRC_DIR_DSK)/%.c
@mkdir -p $(OBJ_DIR)
@@ -76,7 +89,7 @@ $(OBJ_DIR)/%.o: $(SRC_DIR_ASM)/%.c
$(CC) $(CFLAGS) $(POSIXFLAGS) $(DEPFLAGS) -c $< -o $@
# Pull in the header dependencies written out by the compiler above.
-include $(EMU_OBJS:.o=.d) $(ASM_OBJS:.o=.d) $(DSK_OBJS:.o=.d)
-include $(EMU_OBJS:.o=.d) $(ASM_OBJS:.o=.d) $(DSK_OBJS:.o=.d) $(LINT_OBJS:.o=.d)
# ---- The strict build the README promises ----
#
@@ -87,13 +100,15 @@ $(OBJ_DIR)/%.o: $(SRC_DIR_ASM)/%.c
STRICT = -std=c11 -pedantic -Wall -Wextra -Werror $(POSIXFLAGS)
strict:
@for source in $(SRC_DIR_EMU)/*.c $(SRC_DIR_ASM)/*.c $(SRC_DIR_DSK)/*.c; do \
@for source in $(SRC_DIR_EMU)/*.c $(SRC_DIR_ASM)/*.c $(SRC_DIR_DSK)/*.c $(SRC_DIR_LINT)/*.c; do \
$(CC) $(STRICT) -c $$source -o /dev/null || exit 1; \
done
@echo "The sources build clean under -std=c11 -pedantic."
# Run the test suite against the programs in Programs/
test: $(EMU_TARGET) $(ASM_TARGET) $(DSK_TARGET) strict
test: $(EMU_TARGET) $(ASM_TARGET) $(DSK_TARGET) $(LINT_TARGET) strict
@./Tests/lint.sh
@echo
@echo
@./Tests/run.sh
@echo
@@ -131,6 +146,8 @@ sanitize:
@$(MAKE) --no-print-directory clean
@$(MAKE) --no-print-directory CFLAGS="$(SANITIZE_FLAGS)"
@echo "Running the test suite under AddressSanitizer and UndefinedBehaviorSanitizer."
@./Tests/lint.sh
@echo
@./Tests/run.sh
@echo
@./Tests/disk.sh
@@ -155,10 +172,10 @@ bless: $(EMU_TARGET) $(ASM_TARGET)
clean:
rm -rf $(OBJ_DIR)
rm -rf Tests/build
rm -f $(EMU_TARGET) $(ASM_TARGET) $(DSK_TARGET)
rm -f $(EMU_TARGET) $(ASM_TARGET) $(DSK_TARGET) $(LINT_TARGET)
# Install compiled binaries
install: $(EMU_TARGET) $(ASM_TARGET) $(DSK_TARGET)
install: $(EMU_TARGET) $(ASM_TARGET) $(DSK_TARGET) $(LINT_TARGET)
mkdir -p "$(PREFIX)/bin"
install -m 755 $^ "$(PREFIX)/bin/"