# Makefile for the bootstrap compiler. # # Copyright © 2021-2026 Samuel Lidén Borell # # SPDX-License-Identifier: EUPL-1.2+ OR LGPL-2.1-or-later srcdir = . builddir = . VPATH = $(srcdir) MKDIR = mkdir MKDIR_P = $(MKDIR) -p RM = rm RM_F = $(RM) -f HOST_CC = $(CC) CFLAGS = -g HOST_CFLAGS = $(CFLAGS) HOST_LDFLAGS = $(LDFLAGS) BASE_WARNINGS = -Wall -Wextra -pedantic \ -Walloca \ -Wbad-function-cast \ -Wcast-align \ -Wconversion \ -Wdate-time \ -Wmissing-declarations \ -Wmissing-prototypes \ -Wmissing-noreturn \ -Wnested-externs \ -Wnull-dereference \ -Wold-style-definition \ -Wshadow \ -Wshift-negative-value \ -Wshift-overflow \ -Wstrict-aliasing \ -Wstrict-overflow=5 \ -Wstrict-prototypes \ -Wswitch-enum \ -Wundef \ -Wunused \ -Wvla \ -Wwrite-strings BASE_CFLAGS = -std=c89 $(WARNINGS) $(BASE_WARNINGS) # Note: GCC has an "r" after "analyze", but clang does not CLANG_ANALYZE_FLAGS = --analyzer-output text # GCC analyzer-malloc-leak gives false positives when a local variable # contains the sole reference to an object, and this sole reference goes # "out of scope" when a noreturn function is called. This happens on # GCC 14.2 at least. GCC_ANALYZER_FLAGS = -Wno-analyzer-malloc-leak # the following cppcheck warnings give too many false positives: # - style # - unusedFunction # - missingInclude CPPCHECK_FLAGS = \ -q --enable=warning,performance,portability \ --check-level=exhaustive --inconclusive \ -D__STDC_VERSION__=202311 --error-exitcode=1 # sparse gives some false positives in system headers for me SPARSE_FLAGS = -Wsparse-all -Wno-unknown-attribute -Wno-default-bitfield-sign # Stage 1 compiler (written in C) C_SOURCES = \ $(srcdir)/apichk.c \ $(srcdir)/ast.c \ $(srcdir)/b64url.c \ $(srcdir)/builtins.c \ $(srcdir)/funccall.c \ $(srcdir)/intrange.c \ $(srcdir)/main.c \ $(srcdir)/parsedecl.c \ $(srcdir)/parseexpr.c \ $(srcdir)/parsemod.c \ $(srcdir)/parsespec.c \ $(srcdir)/parsestmt.c \ $(srcdir)/out.c \ $(srcdir)/outcommon.c \ $(srcdir)/outdecl.c \ $(srcdir)/outexpr.c \ $(srcdir)/outstmt.c \ $(srcdir)/tree.c \ $(srcdir)/token.c \ $(srcdir)/typechk.c \ $(srcdir)/typecompat.c \ $(srcdir)/util.c \ $(srcdir)/varstate.c C_HEADERS = \ $(srcdir)/out.h \ $(srcdir)/compiler.h \ $(srcdir)/semchk.h \ $(srcdir)/token.h OBJECTS = \ $(builddir)/apichk.o \ $(builddir)/ast.o \ $(builddir)/b64url.o \ $(builddir)/builtins.o \ $(builddir)/funccall.o \ $(builddir)/intrange.o \ $(builddir)/main.o \ $(builddir)/parsedecl.o \ $(builddir)/parseexpr.o \ $(builddir)/parsemod.o \ $(builddir)/parsespec.o \ $(builddir)/parsestmt.o \ $(builddir)/out.o \ $(builddir)/outcommon.o \ $(builddir)/outdecl.o \ $(builddir)/outexpr.o \ $(builddir)/outstmt.o \ $(builddir)/tree.o \ $(builddir)/token.o \ $(builddir)/typechk.o \ $(builddir)/typecompat.o \ $(builddir)/util.o \ $(builddir)/varstate.o # Runtime library, written in C, for stage 2 compiler RTL_C_HEADERS = \ $(srcdir)/rtlincl/rtl.h \ $(srcdir)/rtl/internal.h RTL_INCLUDES = \ -I $(srcdir)/rtlincl \ -I $(srcdir)/rtl RTL_C_SOURCES = \ $(srcdir)/rtl/cli.c \ $(srcdir)/rtl/fail.c \ $(srcdir)/rtl/message.c \ $(srcdir)/rtl/string.c \ $(srcdir)/rtl/writer.c RTL_OBJECTS = \ $(builddir)/rtl/cli.o \ $(builddir)/rtl/fail.o \ $(builddir)/rtl/message.o \ $(builddir)/rtl/string.o \ $(builddir)/rtl/writer.o # Stage 2 compiler (real SLUL compiler compiled with stage 1 compiler) COMPILER_DIR = $(srcdir)/../compiler # TODO for now it is just some test code... COMPILER_SOURCES = \ $(srcdir)/../compiler/sources.index \ $(srcdir)/../compiler/misc.slul \ $(srcdir)/../compiler/SomeClass.slul \ $(srcdir)/../compiler/TheCommand.slul # TODO need to support multiple interface directories here. # or, copy all the interfaces into a single directory. INTERFACES_DIR = $(srcdir)/../stdlib/interfaces COMPILER_GEN = $(builddir)/gen/compiler.c STAGE1_TEST_ARGS = $(INTERFACES_DIR) $(COMPILER_DIR) $(COMPILER_GEN) STAGE2_GENERATED_C = \ $(COMPILER_GEN) STAGE2_INCLUDE = \ -I$(srcdir)/rtlincl STAGE2_OBJECTS = \ $(RTL_OBJECTS) \ $(builddir)/gen/compiler.o all: $(builddir)/stage2 $(OBJECTS): $(srcdir)/compiler.h out.o outcommon.o outdecl.o outexpr.o outstmt.o: $(srcdir)/out.h funccall.o intrange.o outdecl.o outexpr.o outstmt.o \ typechk.o typecompat.o varstate.o: $(srcdir)/semchk.h main.o parsedecl.o parseexpr.o parsemod.o parsespec.o parsestmt.o token.o: \ $(srcdir)/token.h $(RTL_OBJECTS): $(RTL_C_HEADERS) .SUFFIXES: .c .o # Note: the stage2 includes (rtlincl/ and rtl/) may only be used in actual # stage2 code and in the RTL code, not in any other places! .c.o: $(HOST_CC) $(HOST_CFLAGS) $(BASE_CFLAGS) $(STAGE2_INCLUDE) -c -o $@ $< $(builddir)/stage1: $(OBJECTS) $(HOST_CC) $(HOST_CFLAGS) $(HOST_LDFLAGS) -o $@ $(OBJECTS) $(builddir)/stage1-boundschecked: $(C_SOURCES) $(C_HEADERS) tcc -b -bt24 -o $@ $(C_SOURCES) $(builddir)/gen/compiler.c: $(builddir)/stage1 $(COMPILER_SOURCES) $(builddir)/stage1 $(INTERFACES_DIR) $(COMPILER_DIR) $(COMPILER_GEN) $(builddir)/stage2: $(STAGE2_OBJECTS) $(RTL_C_HEADERS) $(CC) $(CFLAGS) $(BASE_CFLAGS) $(STAGE2_DISABLED_WARNINGS) \ $(STAGE2_CFLAGS) $(LDFLAGS) \ $(STAGE2_INCLUDE) \ -o $@ \ $(STAGE2_OBJECTS) .PHONY: all check check-all check-boundschecked check-valgrind \ check-pmccabe clang-analyze clean gcc-analyzer \ cppcheck cppcheck-stage1 cppcheck-rtl \ sparse sparse-stage1 sparse-rtl \ gdb-stage1 longlines outdirs # TODO add a proper test once the stage2 compiler is done check: $(builddir)/stage2 check-all: check check-valgrind check-boundschecked clang-analyze \ gcc-analyzer cppcheck sparse \ check-tokens longlines check-valgrind: $(builddir)/stage1 valgrind --leak-check=yes -q $(builddir)/stage1 \ $(INTERFACES_DIR) $(COMPILER_DIR) $(builddir)/gen/compiler_vg.c check-boundschecked: $(builddir)/stage1-boundschecked TCC_BOUNDS_WARN_POINTER_ADD=1 $(builddir)/stage1-boundschecked \ $(INTERFACES_DIR) $(COMPILER_DIR) $(builddir)/gen/compiler_bchk.c clang-analyze: clang --analyze $(CLANG_ANALYZE_FLAGS) $(C_SOURCES) longlines: if grep -nE '^.{80,}' $(C_SOURCES) $(C_HEADERS) $(srcdir)/Makefile; then \ echo "error: Too long lines detected. Maximum is 80 characters" ;\ false ;\ else \ true ;\ fi check-tokens: grep -E '^ +CMP_KW' $(srcdir)/token.c | \ sed -E -e 's/^.*, "([^"]+)", T_KW_([a-zA-Z0-9_]+)\)/\1 \2/' | \ while read kwstr kwconst; do \ if [ "x$$kwstr" != "x$$kwconst" ]; then \ printf "Keyword str \"%s\" and enum \"%s\" mismatch\n" \ "$$kwstr" "$$kwconst" >&2; \ exit 1; \ fi; \ done gcc-analyzer: gcc -fanalyzer $(GCC_ANALYZER_FLAGS) $(C_SOURCES) cppcheck: cppcheck-stage1 cppcheck-rtl cppcheck-stage1: cppcheck $(CPPCHECK_FLAGS) -I $(srcdir) $(C_SOURCES) cppcheck-rtl: cppcheck $(CPPCHECK_FLAGS) $(RTL_INCLUDES) $(RTL_C_SOURCES) sparse: sparse-stage1 sparse-rtl sparse-stage1: sparse $(SPARSE_FLAGS) -I $(srcdir) $(C_SOURCES) $(SPARSE_GREP) sparse-rtl: sparse $(SPARSE_FLAGS) $(RTL_INCLUDES) $(RTL_C_SOURCES) $(SPARSE_GREP) check-pmccabe: pmccabe -c $(C_SOURCES) | sort -nr | head -n 20 gdb-stage1: $(builddir)/stage1 gdb -q --args $(builddir)/stage1 $(STAGE1_TEST_ARGS) clean: -$(RM_F) \ $(builddir)/stage1 \ $(builddir)/stage1-boundschecked \ $(builddir)/stage2 \ $(OBJECTS) \ $(STAGE2_GENERATED_C) \ $(STAGE2_OBJECTS) outdirs: $(MKDIR_P) $(builddir) $(builddir)/rtl $(builddir)/gen