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.
184 lines
6.4 KiB
Makefile
184 lines
6.4 KiB
Makefile
# SplitBit Emulator and Assembler Makefile
|
|
# Anachronaut
|
|
# 10/16/2024
|
|
|
|
# Compiler and flags
|
|
CC ?= gcc
|
|
CFLAGS ?= -Wall -Os
|
|
PREFIX ?= /usr/local
|
|
|
|
# Have the compiler write out which headers each object depends on, so that
|
|
# editing a header rebuilds everything that includes it.
|
|
DEPFLAGS = -MMD -MP
|
|
|
|
# The emulator and the assembler use POSIX interfaces that ISO C does not have:
|
|
# realpath, clock_gettime,
|
|
# strdup, dirname and getopt. Asking for POSIX.1-2008 by name means the build does
|
|
# not rely on the compiler happening to default to a mode where those are visible,
|
|
# and it survives someone overriding CFLAGS, which is why it is kept separate.
|
|
#
|
|
# _XOPEN_SOURCE=700 IS POSIX.1-2008, plus the XSI extensions. The plain
|
|
# _POSIX_C_SOURCE=200809L was here and is not quite enough: realpath is an XSI interface,
|
|
# so under -std=c11 -pedantic it went undeclared and the assembler would not compile. The
|
|
# ordinary build never noticed, because without -std=c11 the compiler's own default
|
|
# already declares it. The README makes a claim about the strict build, so 'make strict'
|
|
# below settles it rather than leaving it to be discovered.
|
|
POSIXFLAGS = -D_XOPEN_SOURCE=700
|
|
|
|
# Directories
|
|
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 the emulator and its three host-side tools.
|
|
all: $(EMU_TARGET) $(ASM_TARGET) $(DSK_TARGET) $(LINT_TARGET)
|
|
|
|
# Emulator binary
|
|
$(EMU_TARGET): $(EMU_OBJS)
|
|
$(CC) $(CFLAGS) -o $(EMU_TARGET) $(EMU_OBJS)
|
|
|
|
# Assembler binary
|
|
$(ASM_TARGET): $(ASM_OBJS)
|
|
$(CC) $(CFLAGS) -o $(ASM_TARGET) $(ASM_OBJS)
|
|
|
|
# Compile emulator source files to object files
|
|
$(OBJ_DIR)/%.o: $(SRC_DIR_EMU)/%.c
|
|
mkdir -p $(OBJ_DIR)
|
|
$(CC) $(CFLAGS) $(POSIXFLAGS) $(DEPFLAGS) -c $< -o $@
|
|
|
|
# Disk tool binary
|
|
$(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)
|
|
$(CC) $(CFLAGS) $(POSIXFLAGS) $(DEPFLAGS) -c $< -o $@
|
|
|
|
# Compile assembler source files to object files
|
|
$(OBJ_DIR)/%.o: $(SRC_DIR_ASM)/%.c
|
|
mkdir -p $(OBJ_DIR)
|
|
$(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) $(LINT_OBJS:.o=.d)
|
|
|
|
# ---- The strict build the README promises ----
|
|
#
|
|
# "The sources are ISO C and build clean under -std=c11 -pedantic with -Wall -Wextra."
|
|
# That is a claim somebody may check by typing it, so the suite checks it first. It was
|
|
# false when this target was written: realpath went undeclared under a feature test macro
|
|
# that did not reach far enough, which the ordinary -Os build never saw.
|
|
STRICT = -std=c11 -pedantic -Wall -Wextra -Werror $(POSIXFLAGS)
|
|
|
|
strict:
|
|
@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) $(LINT_TARGET) strict
|
|
@./Tests/lint.sh
|
|
@echo
|
|
@echo
|
|
@./Tests/run.sh
|
|
@echo
|
|
@./Tests/disk.sh
|
|
@echo
|
|
@./Tests/terminal.sh
|
|
@echo
|
|
@./Tests/native.sh
|
|
@echo
|
|
@./Tests/agree.sh
|
|
@echo
|
|
@./Tests/docs.sh
|
|
|
|
# Rebuild all three tools with the address and undefined behaviour sanitizers and run
|
|
# the test suite under them. Slower than 'make test', and worth running before a
|
|
# release or after anything that touches memory handling.
|
|
#
|
|
# THE WHOLE SUITE, which it did not used to be: it built all three tools sanitized and
|
|
# then ran only run.sh and terminal.sh, so SplitDisk was compiled with the sanitizers and
|
|
# never exercised, and native.sh - which drives the assembler and the emulator harder than
|
|
# anything else here - was skipped. Those are the parts where block arithmetic on disk
|
|
# images and buffer indexing in two assemblers live, which is exactly what the sanitizers
|
|
# are for. Adding the three of them costs about six seconds.
|
|
#
|
|
# The sanitizers catch reads and writes off the end of an array, use after free,
|
|
# leaks, and undefined arithmetic. They also fill fresh allocations with a junk
|
|
# pattern, which is what turns a read of uninitialised memory from something that
|
|
# quietly works into something the tests notice.
|
|
#
|
|
# If the suite fails, the sanitizer binaries are deliberately left in place so
|
|
# that the failing case can be run again by hand. 'make' puts the normal ones back.
|
|
SANITIZE_FLAGS = -Wall -Wextra -g -O1 -fsanitize=address,undefined -fno-omit-frame-pointer
|
|
|
|
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
|
|
@echo
|
|
@./Tests/terminal.sh
|
|
@echo
|
|
@./Tests/native.sh
|
|
@echo
|
|
@./Tests/agree.sh
|
|
@echo
|
|
@./Tests/docs.sh
|
|
@$(MAKE) --no-print-directory clean
|
|
@$(MAKE) --no-print-directory
|
|
@echo "Sanitizer run finished cleanly. Normal binaries rebuilt."
|
|
|
|
# Record the current output of every test as the expected result.
|
|
# Only do this when the current output is known to be correct.
|
|
bless: $(EMU_TARGET) $(ASM_TARGET)
|
|
@./Tests/run.sh --bless
|
|
|
|
# Clean up object and binary files
|
|
clean:
|
|
rm -rf $(OBJ_DIR)
|
|
rm -rf Tests/build
|
|
rm -f $(EMU_TARGET) $(ASM_TARGET) $(DSK_TARGET) $(LINT_TARGET)
|
|
|
|
# Install compiled binaries
|
|
install: $(EMU_TARGET) $(ASM_TARGET) $(DSK_TARGET) $(LINT_TARGET)
|
|
mkdir -p "$(PREFIX)/bin"
|
|
install -m 755 $^ "$(PREFIX)/bin/"
|
|
|
|
# Phony targets
|
|
.PHONY: all clean install test bless sanitize
|