commit 0d2d302c47e1bb326de5dc23136b33bd37de160d
parent df8ad4cefccdc4ceab566808818f8ec5894e3545
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Wed, 3 Jun 2026 07:45:30 -0700
build: split Makefile into mk/ includes; fold rt build into Makefile
The top-level Makefile read as ~660 lines with the build spine buried in the
middle of source-selection and flag-derivation noise. Extract the non-core
pieces into focused mk/ includes so the Makefile reads top-to-bottom as
setup -> sources -> spine -> extras:
mk/flags.mk compiler/linker flag derivation + .build-config stamp
mk/lib_srcs.mk libkit source selection -> LIB_OBJS / LIB_DEPS
mk/driver_srcs.mk driver source selection -> DRIVER_OBJS / DRIVER_DEPS
mk/rt.mk runtime variant/source selection -> RT_OBJS_<variant>
mk/bootstrap.mk three-stage self-build verification
mk/dist.mk release tarball staging
mk/maint.mk format / compile-commands / bench-opt / clean
rt/Makefile is removed: its variant table + per-variant source/flag derivation
move to mk/rt.mk (selection), and its build rules fold into the top-level
Makefile alongside the lib/driver/vendor rules (selection vs rules, same split
as the rest of the build).
Other cleanups in the same pass:
- collapse the repetitive per-arch source-gate trios into an arch-feature-off
macro, and the driver per-tool / shared-lib gates into tool-cmd / need-any
- group the compile rules by source tree (src/ -> lang/ -> vendor/ -> driver/)
- drop the `ld -r` relocatable step; libkit.a is now a plain ar of LIB_OBJS,
which also retires the now-unused LD toolchain var and bootstrap LD= plumbing
- drop clean-rt (build/rt is under BUILD_DIR, already removed by `make clean`)
Resolved source lists and flags are byte-identical to before; lib/bin/rt build
clean and test-driver + test-smoke-rv64 pass.
Diffstat:
| M | Makefile | | | 684 | ++++++++++++------------------------------------------------------------------- |
| A | mk/bootstrap.mk | | | 67 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | mk/dist.mk | | | 21 | +++++++++++++++++++++ |
| A | mk/driver_srcs.mk | | | 63 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | mk/flags.mk | | | 82 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | mk/lib_srcs.mk | | | 259 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | mk/maint.mk | | | 36 | ++++++++++++++++++++++++++++++++++++ |
| A | mk/rt.mk | | | 246 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| D | rt/Makefile | | | 284 | ------------------------------------------------------------------------------- |
9 files changed, 878 insertions(+), 864 deletions(-)
diff --git a/Makefile b/Makefile
@@ -1,6 +1,5 @@
CC = clang
AR = ar
-LD = ld
RELEASE ?= 0
ifeq ($(RELEASE),1)
BUILD_DIR ?= build/release
@@ -8,509 +7,74 @@ else
BUILD_DIR ?= build
endif
-# Host detection, driver/env source selection, host SDK -isysroot wiring.
-# Everything that branches on HOST_OS / HOST_ARCH lives in env.mk; the rest
-# of this Makefile only reads the variables it produces.
-include mk/env.mk
-
.DEFAULT_GOAL := all
-ifeq ($(RELEASE),1)
-HOST_OPTFLAGS ?= -O2
-HOST_MODE_CPPFLAGS = -DNDEBUG
-HOST_MODE_CFLAGS = -ffunction-sections -fdata-sections
-ifeq ($(HOST_OS),darwin)
-HOST_MODE_LDFLAGS = -Wl,-dead_strip -Wl,-S
-else
-HOST_MODE_LDFLAGS = -Wl,--gc-sections -Wl,-S
-endif
-else
-HOST_OPTFLAGS ?= -O0
-HOST_MODE_CPPFLAGS =
-HOST_MODE_CFLAGS = -g3 -fno-omit-frame-pointer -fno-optimize-sibling-calls \
- -fsanitize=address,undefined \
- -fno-sanitize-recover=address,undefined \
- -fsanitize-address-use-after-scope
-HOST_MODE_LDFLAGS = -fsanitize=address,undefined
-ASAN_OPTIONS ?= halt_on_error=1:abort_on_error=1
-UBSAN_OPTIONS ?= halt_on_error=1:print_stacktrace=1
-export ASAN_OPTIONS UBSAN_OPTIONS
-endif
-
-# HOST_SYSROOT_{C,LD}FLAGS come from env.mk: a `-isysroot <path>` pair on
-# Darwin, empty elsewhere. Stage/bootstrap recipes can clear these when
-# kit itself is the compiler.
-
-CFLAGS_COMMON = $(HOST_OPTFLAGS) $(HOST_MODE_CPPFLAGS) $(HOST_MODE_CFLAGS) \
- -std=c11 -Wpedantic -Wall -Wextra -Werror
-HOST_CFLAGS = $(CFLAGS_COMMON) $(HOST_SYSROOT_CFLAGS)
-HOST_LDFLAGS = $(HOST_SYSROOT_LDFLAGS) $(HOST_MODE_LDFLAGS)
-
-DEPFLAGS = -MMD -MP
-
-BUILD_CONFIG = $(BUILD_DIR)/.build-config
-
-.PHONY: FORCE
-FORCE:
-
-$(BUILD_CONFIG): FORCE
- @mkdir -p $(dir $@)
- @{ \
- printf '%s\n' 'RELEASE=$(RELEASE)'; \
- printf '%s\n' 'HOST_OPTFLAGS=$(HOST_OPTFLAGS)'; \
- printf '%s\n' 'HOST_MODE_CPPFLAGS=$(HOST_MODE_CPPFLAGS)'; \
- printf '%s\n' 'HOST_MODE_CFLAGS=$(HOST_MODE_CFLAGS)'; \
- printf '%s\n' 'HOST_MODE_LDFLAGS=$(HOST_MODE_LDFLAGS)'; \
- } > $@.tmp
- @if ! cmp -s $@.tmp $@; then mv $@.tmp $@; else rm -f $@.tmp; fi
-
-# Freestanding objects must not see host SDK/libc headers. Homebrew clang can
-# also inject a configured sysroot before command-line flags; use
-# --no-default-config when the selected compiler supports it, but omit it for
-# bootstrap stages where $(CC) is kit cc.
-FREESTANDING_CONFIG_CFLAGS = $(shell $(CC) --no-default-config -x c -E /dev/null >/dev/null 2>&1 && printf '%s' --no-default-config)
-FREESTANDING_CFLAGS = $(CFLAGS_COMMON) $(FREESTANDING_CONFIG_CFLAGS) -ffreestanding -nostdinc -Irt/include
-
-# libkit: written in C11 freestanding; sees both src/ (internal) and
-# include/ (its own public surface).
-LIB_VISIBILITY_CFLAGS = -fvisibility=hidden
-LIB_CFLAGS = $(FREESTANDING_CFLAGS) $(LIB_VISIBILITY_CFLAGS) -Iinclude -Isrc -Ivendor
-
-# Driver: mostly freestanding CLI binary. Sees only the public include/ tree —
-# that's what makes the driver the first consumer of libkit. -Ilang lets `cc`
-# reach the C frontend's public header ("c/c.h") for the JIT REPL; it
-# deliberately does NOT get -Isrc, so internal headers ("core/...", "link/...")
-# are unreachable from the driver.
-#
-# driver/env/ holds all hosted OS/libc adapter code and is compiled with
-# DRIVER_ENV_CFLAGS. The per-OS feature-test macros and the exact source
-# list (DRIVER_ENV_OS_CFLAGS / DRIVER_ENV_SRCS) come from mk/env.mk.
-DRIVER_CFLAGS = $(FREESTANDING_CFLAGS) -Iinclude -Ilang -Idriver -Idriver/lib
-DRIVER_ENV_CFLAGS = $(HOST_CFLAGS) -Iinclude -Ilang -Idriver -Idriver/lib
-TEST_HOST_CFLAGS = $(HOST_CFLAGS) -Iinclude -Ilang
-
+# Build setup, in dependency order. Each include owns one concern and only
+# reads the variables produced by the ones before it:
+# env.mk host detection, driver/env source selection, SDK sysroot
+# config.mk KIT_*_ENABLED component flags (mirrored from config.h)
+# flags.mk compiler/linker flag derivation + the .build-config stamp
+# lib_srcs.mk libkit source selection -> LIB_OBJS / LIB_DEPS
+# driver_srcs.mk driver source selection -> DRIVER_OBJS / DRIVER_DEPS
+# rt.mk runtime variant/source selection -> RT_OBJS_<variant>
+include mk/env.mk
include mk/config.mk
+include mk/flags.mk
+include mk/lib_srcs.mk
+include mk/driver_srcs.mk
+include mk/rt.mk
-# Core lib sources. Optional subsystems, backend directories, object format
-# directories, and ABI implementations are added below from their own groups.
-LIB_SRCS_ABI_CORE = src/abi/abi.c src/abi/registry.c
-LIB_SRCS_API_CORE = $(filter-out src/api/archive.c src/api/disasm.c \
- src/api/link.c src/api/cas.c src/api/package.c \
- src/api/compress.c src/api/stubs.c,$(wildcard src/api/*.c))
-LIB_SRCS_ARCH_CORE = $(filter-out src/arch/%_stubs.c,$(wildcard src/arch/*.c))
-LIB_SRCS_ASM_CORE = $(wildcard src/asm/*.c)
-LIB_SRCS_CG_CORE = $(wildcard src/cg/*.c)
-LIB_SRCS_CORE = $(wildcard src/core/*.c)
-LIB_SRCS_OBJ_CORE = $(filter-out src/obj/%_stubs.c,$(wildcard src/obj/*.c))
-LIB_SRCS_NONARCH = $(LIB_SRCS_ABI_CORE) \
- $(LIB_SRCS_API_CORE) \
- $(LIB_SRCS_ARCH_CORE) \
- $(LIB_SRCS_ASM_CORE) \
- $(LIB_SRCS_CG_CORE) \
- $(LIB_SRCS_CORE) \
- $(LIB_SRCS_OBJ_CORE)
-
-# These source lists use `:=` (not `=`) so each `find` runs exactly once at
-# parse time. With recursive `=`, every reference below (LIB_SRCS is rebuilt
-# and re-expanded many times) would re-spawn the `find`, turning ~20 finds into
-# hundreds on every `make` invocation.
-LIB_SRCS_ARCH_AA64 := $(shell find src/arch/aa64 -name '*.c' 2>/dev/null)
-LIB_SRCS_ARCH_X64 := $(shell find src/arch/x64 -name '*.c' 2>/dev/null)
-LIB_SRCS_ARCH_RV64 := $(shell find src/arch/rv64 -name '*.c' 2>/dev/null)
-LIB_SRCS_ARCH_WASM := $(shell find src/arch/wasm -name '*.c' 2>/dev/null)
-LIB_SRCS_ARCH_C_TARGET := $(shell find src/arch/c_target -name '*.c' 2>/dev/null)
-ifneq ($(KIT_OPT_ENABLED),1)
-LIB_SRCS_ARCH_AA64 := $(filter-out %/opt_coord.c,$(LIB_SRCS_ARCH_AA64))
-LIB_SRCS_ARCH_X64 := $(filter-out %/opt_coord.c,$(LIB_SRCS_ARCH_X64))
-LIB_SRCS_ARCH_RV64 := $(filter-out %/opt_coord.c,$(LIB_SRCS_ARCH_RV64))
-endif
-ifneq ($(KIT_DISASM_ENABLED),1)
-LIB_SRCS_ARCH_AA64 := $(filter-out %/disasm.c,$(LIB_SRCS_ARCH_AA64))
-LIB_SRCS_ARCH_X64 := $(filter-out %/disasm.c,$(LIB_SRCS_ARCH_X64))
-LIB_SRCS_ARCH_RV64 := $(filter-out %/disasm.c,$(LIB_SRCS_ARCH_RV64))
-LIB_SRCS_NONARCH += src/arch/disasm_stubs.c
-endif
-ifneq ($(KIT_LINK_ENABLED),1)
-LIB_SRCS_ARCH_AA64 := $(filter-out %/link.c,$(LIB_SRCS_ARCH_AA64))
-LIB_SRCS_ARCH_X64 := $(filter-out %/link.c,$(LIB_SRCS_ARCH_X64))
-LIB_SRCS_ARCH_RV64 := $(filter-out %/link.c,$(LIB_SRCS_ARCH_RV64))
-LIB_SRCS_NONARCH += src/arch/link_stubs.c
-endif
-ifneq ($(KIT_DBG_ENABLED),1)
-LIB_SRCS_ARCH_AA64 := $(filter-out %/dbg.c,$(LIB_SRCS_ARCH_AA64))
-LIB_SRCS_ARCH_X64 := $(filter-out %/dbg.c,$(LIB_SRCS_ARCH_X64))
-LIB_SRCS_ARCH_RV64 := $(filter-out %/dbg.c,$(LIB_SRCS_ARCH_RV64))
-LIB_SRCS_NONARCH += src/arch/dbg_stubs.c
-endif
-ifneq ($(KIT_EMU_ENABLED),1)
-LIB_SRCS_ARCH_RV64 := $(filter-out %/emu.c,$(LIB_SRCS_ARCH_RV64))
-LIB_SRCS_NONARCH += src/arch/emu_stubs.c
-endif
-ifneq ($(KIT_INTERP_ENABLED),1)
-LIB_SRCS_NONARCH += src/interp/interp_stubs.c
-endif
-
-LIB_SRCS_OBJ_ELF := $(shell find src/obj/elf -name '*.c' 2>/dev/null)
-LIB_SRCS_OBJ_MACHO := $(shell find src/obj/macho -name '*.c' 2>/dev/null)
-LIB_SRCS_OBJ_COFF := $(shell find src/obj/coff -name '*.c' 2>/dev/null)
-LIB_SRCS_OBJ_WASM := $(shell find src/obj/wasm -name '*.c' 2>/dev/null)
-ifneq ($(KIT_LINK_ENABLED),1)
-LIB_SRCS_OBJ_ELF := $(filter-out %/link.c %/link_dyn.c,$(LIB_SRCS_OBJ_ELF))
-LIB_SRCS_OBJ_MACHO := $(filter-out %/link.c,$(LIB_SRCS_OBJ_MACHO))
-LIB_SRCS_OBJ_COFF := $(filter-out %/link.c,$(LIB_SRCS_OBJ_COFF))
-LIB_SRCS_NONARCH += src/obj/link_stubs.c
-endif
-ifneq ($(KIT_EMU_ENABLED),1)
-LIB_SRCS_OBJ_ELF := $(filter-out %/emu_load.c,$(LIB_SRCS_OBJ_ELF))
-LIB_SRCS_NONARCH += src/obj/emu_stubs.c
-endif
-ifneq ($(KIT_AR_ENABLED),1)
-LIB_SRCS_OBJ_COFF := $(filter-out %/archive.c,$(LIB_SRCS_OBJ_COFF))
-LIB_SRCS_NONARCH += src/obj/archive_stubs.c
-endif
-
-LIB_SRCS_OPT := $(filter-out src/opt/pass_o2.c,$(shell find src/opt -name '*.c' 2>/dev/null))
-LIB_SRCS_INTERP := $(filter-out %/interp_stubs.c,$(shell find src/interp -name '*.c' 2>/dev/null))
-LIB_SRCS_WASM_CORE := $(shell find src/wasm -name '*.c' 2>/dev/null)
-LIB_SRCS_API_AR = src/api/archive.c
-LIB_SRCS_API_DISASM = src/api/disasm.c
-LIB_SRCS_API_LINK = src/api/link.c
-# Distribution subsystem (content store + signed packages). The cas layer
-# needs blake2b (-> monocypher); the pkg layer adds the crypto/container shims
-# and the second monocypher TU. The compression codecs (deflate + lz4 block)
-# are shared by pkg and the standalone compress API, so they live in their own
-# CODEC group pulled in once if either is enabled. The lz4 frame layer
-# (LIB_SRCS_DIST_COMPRESS) is needed only by the compress API. All vendored .c
-# (lz4.c, lz4frame.c's xxhash/lz4hc/lz4frame) are #included by their src/dist
-# shim (amalgamation), so they are NOT compiled standalone.
-LIB_SRCS_API_CAS = src/api/cas.c
-LIB_SRCS_API_PKG = src/api/package.c
-LIB_SRCS_API_COMPRESS = src/api/compress.c
-LIB_SRCS_DIST_CAS = src/dist/dist.c src/dist/blake2b.c src/dist/blob.c \
- src/dist/tree.c src/dist/cas.c
-LIB_SRCS_VENDOR_CAS = vendor/monocypher/monocypher.c
-LIB_SRCS_DIST_CODEC = src/dist/deflate.c src/dist/lz4.c
-LIB_SRCS_DIST_COMPRESS = src/dist/lz4frame.c
-LIB_SRCS_DIST_PKG = src/dist/b64.c src/dist/ed25519.c src/dist/minisig.c \
- src/dist/tar.c src/dist/kpkg.c src/dist/manifest.c \
- src/dist/trust.c
-LIB_SRCS_VENDOR_PKG = vendor/monocypher/monocypher-ed25519.c
-LIB_SRCS_DEBUG := $(shell find src/debug -name '*.c' 2>/dev/null)
-LIB_SRCS_DBG := $(shell find src/dbg -name '*.c' 2>/dev/null)
-LIB_SRCS_EMU := $(shell find src/emu -name '*.c' 2>/dev/null) \
- $(shell find src/os -name '*.c' 2>/dev/null)
-LIB_SRCS_LINK := $(shell find src/link -name '*.c' 2>/dev/null)
-ifneq ($(KIT_JIT_ENABLED),1)
-LIB_SRCS_LINK := $(filter-out %/link_jit.c,$(LIB_SRCS_LINK))
-endif
-LIB_SRCS_JIT_ASM := $(shell find src/jit -name '*.S' 2>/dev/null)
-
-LIB_SRC_ABI_AAPCS64 = src/abi/abi_aapcs64.c
-LIB_SRC_ABI_APPLE_ARM64 = src/abi/abi_apple_arm64.c
-LIB_SRC_ABI_AAPCS64_WINDOWS = src/abi/abi_aapcs64_windows.c
-LIB_SRC_ABI_SYSV_X64 = src/abi/abi_sysv_x64.c
-LIB_SRC_ABI_APPLE_X64 = src/abi/abi_apple_x64.c
-LIB_SRC_ABI_WIN64_X64 = src/abi/abi_win64_x64.c
-LIB_SRC_ABI_RV64 = src/abi/abi_rv64.c
-
-LIB_SRCS = $(LIB_SRCS_NONARCH)
-ifeq ($(KIT_OPT_ENABLED),1)
-LIB_SRCS += $(LIB_SRCS_OPT)
-endif
-ifeq ($(KIT_INTERP_ENABLED),1)
-LIB_SRCS += $(LIB_SRCS_INTERP)
-endif
-ifeq ($(KIT_AR_ENABLED),1)
-LIB_SRCS += $(LIB_SRCS_API_AR)
-endif
-ifeq ($(KIT_DISASM_ENABLED),1)
-LIB_SRCS += $(LIB_SRCS_API_DISASM)
-endif
-ifeq ($(KIT_DWARF_ENABLED),1)
-LIB_SRCS += $(LIB_SRCS_DEBUG)
-endif
-ifeq ($(KIT_LINK_ENABLED),1)
-LIB_SRCS += $(LIB_SRCS_API_LINK) $(LIB_SRCS_LINK)
-endif
-ifeq ($(KIT_DBG_ENABLED),1)
-LIB_SRCS += $(LIB_SRCS_DBG)
-endif
-ifeq ($(KIT_EMU_ENABLED),1)
-LIB_SRCS += $(LIB_SRCS_EMU)
-endif
-ifeq ($(KIT_CAS_ENABLED),1)
-LIB_SRCS += $(LIB_SRCS_API_CAS) $(LIB_SRCS_DIST_CAS) $(LIB_SRCS_VENDOR_CAS)
-endif
-# Shared compression codecs (deflate + lz4 block): pulled in once if either the
-# compress API or the pkg layer needs them.
-KIT_NEED_CODEC :=
-ifeq ($(KIT_COMPRESS_ENABLED),1)
-LIB_SRCS += $(LIB_SRCS_API_COMPRESS) $(LIB_SRCS_DIST_COMPRESS)
-KIT_NEED_CODEC := 1
-endif
-ifeq ($(KIT_PKG_ENABLED),1)
-LIB_SRCS += $(LIB_SRCS_API_PKG) $(LIB_SRCS_DIST_PKG) $(LIB_SRCS_VENDOR_PKG)
-KIT_NEED_CODEC := 1
-endif
-ifeq ($(KIT_NEED_CODEC),1)
-LIB_SRCS += $(LIB_SRCS_DIST_CODEC)
-endif
-ifeq ($(KIT_ARCH_AA64_ENABLED),1)
-LIB_SRCS += $(LIB_SRCS_ARCH_AA64)
-endif
-ifeq ($(KIT_ARCH_X64_ENABLED),1)
-LIB_SRCS += $(LIB_SRCS_ARCH_X64)
-endif
-ifeq ($(KIT_ARCH_RV64_ENABLED),1)
-LIB_SRCS += $(LIB_SRCS_ARCH_RV64)
-endif
-ifeq ($(KIT_ARCH_WASM_ENABLED),1)
-LIB_SRCS += $(LIB_SRCS_ARCH_WASM)
-endif
-ifeq ($(KIT_ARCH_C_TARGET_ENABLED),1)
-LIB_SRCS += $(LIB_SRCS_ARCH_C_TARGET)
-endif
-ifneq ($(filter 1,$(KIT_ARCH_WASM_ENABLED) $(KIT_OBJ_WASM_ENABLED) $(KIT_LANG_WASM_ENABLED)),)
-LIB_SRCS += $(LIB_SRCS_WASM_CORE)
-LIB_SRCS += $(LIB_SRCS_OBJ_WASM)
-endif
-ifeq ($(KIT_OBJ_ELF_ENABLED),1)
-LIB_SRCS += $(LIB_SRCS_OBJ_ELF)
-endif
-ifeq ($(KIT_OBJ_MACHO_ENABLED),1)
-LIB_SRCS += $(LIB_SRCS_OBJ_MACHO)
-endif
-ifeq ($(KIT_OBJ_COFF_ENABLED),1)
-LIB_SRCS += $(LIB_SRCS_OBJ_COFF)
-endif
-ifneq ($(filter 1,$(KIT_ARCH_AA64_ENABLED) $(KIT_ARCH_C_TARGET_ENABLED)),)
-ifneq ($(filter 1,$(KIT_OBJ_ELF_ENABLED) $(KIT_OBJ_MACHO_ENABLED) $(KIT_OBJ_COFF_ENABLED)),)
-LIB_SRCS += $(LIB_SRC_ABI_AAPCS64)
-endif
-ifeq ($(KIT_OBJ_MACHO_ENABLED),1)
-LIB_SRCS += $(LIB_SRC_ABI_APPLE_ARM64)
-endif
-ifeq ($(KIT_OBJ_COFF_ENABLED),1)
-LIB_SRCS += $(LIB_SRC_ABI_AAPCS64_WINDOWS)
-endif
-endif
-ifneq ($(filter 1,$(KIT_ARCH_X64_ENABLED) $(KIT_ARCH_C_TARGET_ENABLED)),)
-ifneq ($(filter 1,$(KIT_OBJ_ELF_ENABLED) $(KIT_OBJ_MACHO_ENABLED)),)
-LIB_SRCS += $(LIB_SRC_ABI_SYSV_X64)
-endif
-ifeq ($(KIT_OBJ_MACHO_ENABLED),1)
-LIB_SRCS += $(LIB_SRC_ABI_APPLE_X64)
-endif
-ifeq ($(KIT_OBJ_COFF_ENABLED),1)
-LIB_SRCS += $(LIB_SRC_ABI_WIN64_X64)
-endif
-endif
-ifneq ($(filter 1,$(KIT_ARCH_RV64_ENABLED) $(KIT_ARCH_C_TARGET_ENABLED)),)
-ifeq ($(KIT_OBJ_ELF_ENABLED),1)
-LIB_SRCS += $(LIB_SRC_ABI_RV64)
-endif
-endif
+LIB_AR = $(BUILD_DIR)/libkit.a
+BIN = $(BUILD_DIR)/kit
-# Per-frontend source sets. Each is gated by its KIT_LANG_*_ENABLED flag
-# from mk/config.mk so the matching `#if` in src/api/lang_registry.c and
-# the build agree on which frontends are compiled in.
-LANG_CPP_SRCS := $(shell find lang/cpp -name '*.c' 2>/dev/null)
-LANG_C_SRCS := $(shell find lang/c -name '*.c' 2>/dev/null)
-LANG_WASM_SRCS := $(shell find lang/wasm -name '*.c' 2>/dev/null)
-LANG_TOY_SRCS = $(wildcard lang/toy/*.c)
-
-LANG_OBJS =
-ifeq ($(KIT_LANG_CPP_ENABLED),1)
-LANG_OBJS += $(patsubst lang/cpp/%.c,$(BUILD_DIR)/lang/cpp/%.o,$(LANG_CPP_SRCS))
-endif
-ifeq ($(KIT_LANG_C_ENABLED),1)
-LANG_OBJS += $(patsubst lang/c/%.c,$(BUILD_DIR)/lang/c/%.o,$(LANG_C_SRCS))
-endif
-ifeq ($(KIT_LANG_WASM_ENABLED),1)
-LANG_OBJS += $(patsubst lang/wasm/%.c,$(BUILD_DIR)/lang/wasm/%.o,$(LANG_WASM_SRCS))
-endif
-ifeq ($(KIT_LANG_TOY_ENABLED),1)
-LANG_OBJS += $(patsubst lang/toy/%.c,$(BUILD_DIR)/lang/toy/%.o,$(LANG_TOY_SRCS))
-endif
+# ===========================================================================
+# Main build sequence: objects -> archive -> binary.
+# ===========================================================================
-LIB_ASMS =
-ifeq ($(KIT_JIT_ENABLED),1)
-LIB_ASMS += $(LIB_SRCS_JIT_ASM)
-endif
-LIB_OBJS = $(patsubst src/%.c,$(BUILD_DIR)/lib/%.o,$(filter src/%.c,$(LIB_SRCS))) \
- $(patsubst vendor/%.c,$(BUILD_DIR)/vendor/%.o,$(filter vendor/%.c,$(LIB_SRCS))) \
- $(LANG_OBJS) \
- $(patsubst src/%.S,$(BUILD_DIR)/lib/%.o,$(LIB_ASMS))
-LIB_DEPS = $(LIB_OBJS:.o=.d)
-LIB_RELOC_OBJ = $(BUILD_DIR)/libkit.o
-
-DRIVER_SRCS = driver/main.c driver/lib/target.c $(DRIVER_ENV_SRCS)
-DRIVER_TOOL_SRCS =
-ifeq ($(KIT_TOOL_CC_ENABLED),1)
-DRIVER_TOOL_SRCS += driver/cmd/cc.c
-endif
-ifeq ($(KIT_TOOL_CHECK_ENABLED),1)
-DRIVER_TOOL_SRCS += driver/cmd/cc.c
-endif
-ifeq ($(KIT_TOOL_COMPILE_ENABLED),1)
-DRIVER_TOOL_SRCS += driver/cmd/compile.c
-endif
-ifeq ($(KIT_TOOL_INSTALL_ENABLED),1)
-DRIVER_TOOL_SRCS += driver/cmd/install.c
-endif
-ifeq ($(KIT_TOOL_CPP_ENABLED),1)
-DRIVER_TOOL_SRCS += driver/cmd/cpp.c
-endif
-ifeq ($(KIT_TOOL_AS_ENABLED),1)
-DRIVER_TOOL_SRCS += driver/cmd/as.c
-endif
-ifeq ($(KIT_TOOL_LD_ENABLED),1)
-DRIVER_TOOL_SRCS += driver/cmd/ld.c
-endif
-ifeq ($(KIT_TOOL_AR_ENABLED),1)
-DRIVER_TOOL_SRCS += driver/cmd/ar.c
-endif
-ifeq ($(KIT_TOOL_RANLIB_ENABLED),1)
-DRIVER_TOOL_SRCS += driver/cmd/ranlib.c
-endif
-ifeq ($(KIT_TOOL_STRIP_ENABLED),1)
-DRIVER_TOOL_SRCS += driver/cmd/strip.c
-endif
-ifeq ($(KIT_TOOL_OBJCOPY_ENABLED),1)
-DRIVER_TOOL_SRCS += driver/cmd/objcopy.c
-endif
-ifeq ($(KIT_TOOL_OBJDUMP_ENABLED),1)
-DRIVER_TOOL_SRCS += driver/cmd/objdump.c
-endif
-ifeq ($(KIT_TOOL_DBG_ENABLED),1)
-DRIVER_TOOL_SRCS += driver/cmd/dbg.c
-endif
-ifeq ($(KIT_TOOL_RUN_ENABLED),1)
-DRIVER_TOOL_SRCS += driver/cmd/run.c
-endif
-ifeq ($(KIT_TOOL_EMU_ENABLED),1)
-DRIVER_TOOL_SRCS += driver/cmd/emu.c
-endif
-ifeq ($(KIT_TOOL_NM_ENABLED),1)
-DRIVER_TOOL_SRCS += driver/cmd/nm.c
-endif
-ifeq ($(KIT_TOOL_SIZE_ENABLED),1)
-DRIVER_TOOL_SRCS += driver/cmd/size.c
-endif
-ifeq ($(KIT_TOOL_ADDR2LINE_ENABLED),1)
-DRIVER_TOOL_SRCS += driver/cmd/addr2line.c
-endif
-ifeq ($(KIT_TOOL_STRINGS_ENABLED),1)
-DRIVER_TOOL_SRCS += driver/cmd/strings.c
-endif
-# The cas/pkg tools link libkit's public cas/package APIs (gated by
-# KIT_CAS_ENABLED / KIT_PKG_ENABLED, asserted by config_assert.c); the
-# dist implementation and vendored primitives live in the library now.
-ifneq ($(filter 1,$(KIT_TOOL_CAS_ENABLED) $(KIT_TOOL_PKG_ENABLED)),)
-DRIVER_TOOL_SRCS += driver/lib/dist_host.c
-endif
-ifeq ($(KIT_TOOL_CAS_ENABLED),1)
-DRIVER_TOOL_SRCS += driver/cmd/cas.c
-endif
-ifeq ($(KIT_TOOL_PKG_ENABLED),1)
-DRIVER_TOOL_SRCS += driver/cmd/pkg.c
-endif
-ifeq ($(KIT_TOOL_XXD_ENABLED),1)
-DRIVER_TOOL_SRCS += driver/cmd/xxd.c
-endif
-ifeq ($(KIT_TOOL_CMP_ENABLED),1)
-DRIVER_TOOL_SRCS += driver/cmd/cmp.c
-endif
-ifeq ($(KIT_TOOL_HASH_ENABLED),1)
-DRIVER_TOOL_SRCS += driver/cmd/hash.c
-endif
-ifeq ($(KIT_TOOL_COMPRESS_ENABLED),1)
-DRIVER_TOOL_SRCS += driver/cmd/compress.c
-endif
-ifeq ($(KIT_TOOL_DISAS_ENABLED),1)
-DRIVER_TOOL_SRCS += driver/cmd/disas.c
-endif
-ifeq ($(KIT_TOOL_MC_ENABLED),1)
-DRIVER_TOOL_SRCS += driver/cmd/mc.c
-endif
-DRIVER_SRCS += $(sort $(DRIVER_TOOL_SRCS))
-ifneq ($(filter 1,$(KIT_TOOL_CC_ENABLED) $(KIT_TOOL_CHECK_ENABLED) $(KIT_TOOL_CPP_ENABLED) $(KIT_TOOL_AS_ENABLED) $(KIT_TOOL_DBG_ENABLED) $(KIT_TOOL_RUN_ENABLED)),)
-DRIVER_SRCS += driver/lib/cflags.c
-endif
-ifneq ($(filter 1,$(KIT_TOOL_CC_ENABLED) $(KIT_TOOL_CHECK_ENABLED) $(KIT_TOOL_LD_ENABLED) $(KIT_TOOL_RUN_ENABLED)),)
-DRIVER_SRCS += driver/lib/lib_resolve.c
-endif
-ifneq ($(filter 1,$(KIT_TOOL_CC_ENABLED) $(KIT_TOOL_CHECK_ENABLED) $(KIT_TOOL_RUN_ENABLED)),)
-DRIVER_SRCS += driver/lib/hosted.c
-endif
-ifneq ($(filter 1,$(KIT_TOOL_CC_ENABLED) $(KIT_TOOL_CHECK_ENABLED) $(KIT_TOOL_LD_ENABLED)),)
-DRIVER_SRCS += driver/lib/runtime.c
-endif
-ifneq ($(filter 1,$(KIT_TOOL_AR_ENABLED) $(KIT_TOOL_RANLIB_ENABLED) $(KIT_TOOL_STRIP_ENABLED) $(KIT_TOOL_DBG_ENABLED) $(KIT_TOOL_RUN_ENABLED)),)
-DRIVER_SRCS += driver/lib/inputs.c
-endif
-ifneq ($(filter 1,$(KIT_TOOL_CC_ENABLED) $(KIT_TOOL_CHECK_ENABLED) $(KIT_TOOL_COMPILE_ENABLED)),)
-DRIVER_SRCS += driver/lib/compile_engine.c
-endif
-DRIVER_SRCS := $(sort $(DRIVER_SRCS))
-DRIVER_OBJS = $(patsubst driver/%.c,$(BUILD_DIR)/driver/%.o,$(DRIVER_SRCS))
-DRIVER_DEPS = $(DRIVER_OBJS:.o=.d)
-
-LIB_AR = $(BUILD_DIR)/libkit.a
-BIN = $(BUILD_DIR)/kit
-
-DIST_STAGING = build/dist/kit
-DIST_TARBALL = build/dist/kit.tar.gz
-
-.PHONY: \
- all \
- lib \
- bin \
- dist \
- format \
- compile-commands \
- clean \
- bootstrap \
- bootstrap-debug \
- bootstrap-release \
- _do-bootstrap \
- bench-opt
+.PHONY: all lib bin
all: lib bin
+lib: $(LIB_AR)
+
bin: $(BIN) $(BUILD_DIR)/support/rt
$(BUILD_DIR)/support/rt:
@mkdir -p $(dir $@)
ln -sfn $(abspath rt) $@
-lib: $(LIB_AR)
-
-# Replace the archive (`ar rcs` only adds/updates), so removing a .c file
-# also removes its .o from the archive on the next build.
-$(LIB_RELOC_OBJ): $(LIB_OBJS) $(BUILD_CONFIG)
- @mkdir -p $(dir $@)
- @rm -f $@
- $(LD) -r -o $@ $(LIB_OBJS)
-
-$(LIB_AR): $(LIB_RELOC_OBJ)
+# Rebuild the archive from scratch (rm first): `ar rcs` only adds/updates, so
+# recreating it ensures a removed .c file also drops its .o from the archive.
+$(LIB_AR): $(LIB_OBJS) $(BUILD_CONFIG)
@mkdir -p $(dir $@)
@rm -f $@
- $(AR) rcs $@ $(LIB_RELOC_OBJ)
+ $(AR) rcs $@ $(LIB_OBJS)
$(BIN): $(DRIVER_OBJS) $(LIB_AR) $(BUILD_CONFIG)
$(CC) $(HOST_LDFLAGS) -o $@ $(DRIVER_OBJS) $(LIB_AR) $(HOST_LDLIBS)
+# ---------------------------------------------------------------------------
+# Compile rules, grouped by source tree: src/ (libkit) -> lang/ (frontends) ->
+# vendor/ -> driver/. Each tree gets its own include scope; see mk/flags.mk for
+# the *_CFLAGS definitions and the rationale behind each tree's include set.
+# ---------------------------------------------------------------------------
+
+# --- src/: libkit core (C + asm), freestanding, hidden visibility -----------
$(BUILD_DIR)/lib/%.o: src/%.c Makefile $(BUILD_CONFIG)
@mkdir -p $(dir $@)
$(CC) $(LIB_CFLAGS) $(DEPFLAGS) -c $< -o $@
+$(BUILD_DIR)/lib/%.o: src/%.S Makefile $(BUILD_CONFIG)
+ @mkdir -p $(dir $@)
+ $(CC) $(LIB_CFLAGS) $(DEPFLAGS) -c $< -o $@
+
# lang_registry.c is the one libkit source that crosses into lang/*; it
# uses -Ilang so the frontend headers can be reached as "c/c.h" etc.
$(BUILD_DIR)/lib/api/lang_registry.o: src/api/lang_registry.c Makefile $(BUILD_CONFIG)
@mkdir -p $(dir $@)
$(CC) $(FREESTANDING_CFLAGS) $(LIB_VISIBILITY_CFLAGS) -Iinclude -Ilang -Isrc $(DEPFLAGS) -c $< -o $@
+# --- lang/: frontends, each with its own include set ------------------------
$(BUILD_DIR)/lang/cpp/%.o: lang/cpp/%.c Makefile $(BUILD_CONFIG)
@mkdir -p $(dir $@)
$(CC) $(FREESTANDING_CFLAGS) $(LIB_VISIBILITY_CFLAGS) -Iinclude -Ilang/cpp $(DEPFLAGS) -c $< -o $@
@@ -522,20 +86,26 @@ $(BUILD_DIR)/lang/c/%.o: lang/c/%.c Makefile $(BUILD_CONFIG)
@mkdir -p $(dir $@)
$(CC) $(FREESTANDING_CFLAGS) $(LIB_VISIBILITY_CFLAGS) -Iinclude -Ilang/cpp -Ilang/c $(DEPFLAGS) -c $< -o $@
+# The Wasm frontend is the one lang/* tree that reaches internal headers: it
+# includes "wasm/wasm.h" to share src/wasm/ (the module/encode/decode library)
+# with the Wasm backend, so it gets -Isrc. lang/c and lang/cpp deliberately do
+# not — they see only the public include/ surface.
$(BUILD_DIR)/lang/wasm/%.o: lang/wasm/%.c Makefile $(BUILD_CONFIG)
@mkdir -p $(dir $@)
$(CC) $(FREESTANDING_CFLAGS) $(LIB_VISIBILITY_CFLAGS) -Iinclude -Isrc -Ilang/wasm $(DEPFLAGS) -c $< -o $@
-$(BUILD_DIR)/lib/%.o: src/%.S Makefile $(BUILD_CONFIG)
+$(BUILD_DIR)/lang/toy/%.o: lang/toy/%.c Makefile $(BUILD_CONFIG)
@mkdir -p $(dir $@)
- $(CC) $(LIB_CFLAGS) $(DEPFLAGS) -c $< -o $@
+ $(CC) $(FREESTANDING_CFLAGS) $(LIB_VISIBILITY_CFLAGS) -Iinclude -Ilang/toy $(DEPFLAGS) -c $< -o $@
+# --- vendor/: third-party sources compiled into libkit ----------------------
# Vendored third-party sources (monocypher) compiled into libkit. Same
# freestanding flags as the rest of the library; symbols stay hidden.
$(BUILD_DIR)/vendor/%.o: vendor/%.c Makefile $(BUILD_CONFIG)
@mkdir -p $(dir $@)
$(CC) $(LIB_CFLAGS) $(DEPFLAGS) -c $< -o $@
+# --- driver/: hosted CLI; env/ is the OS/libc adapter sub-tree --------------
$(BUILD_DIR)/driver/%.o: driver/%.c Makefile $(BUILD_CONFIG)
@mkdir -p $(dir $@)
$(CC) $(DRIVER_CFLAGS) $(DEPFLAGS) -c $< -o $@
@@ -544,114 +114,68 @@ $(BUILD_DIR)/driver/env/%.o: driver/env/%.c Makefile $(BUILD_CONFIG)
@mkdir -p $(dir $@)
$(CC) $(DRIVER_ENV_CFLAGS) $(DRIVER_ENV_OS_CFLAGS) $(DEPFLAGS) -c $< -o $@
-$(BUILD_DIR)/lang/toy/%.o: lang/toy/%.c Makefile $(BUILD_CONFIG)
- @mkdir -p $(dir $@)
- $(CC) $(FREESTANDING_CFLAGS) $(LIB_VISIBILITY_CFLAGS) -Iinclude -Ilang/toy $(DEPFLAGS) -c $< -o $@
+# ===========================================================================
+# Runtime (libkit_rt.a): kit compiles and archives its own runtime, one
+# variant per target. The variant table and per-variant source/flag lists come
+# from mk/rt.mk; this section is the per-variant build rules expanded over it.
+# ===========================================================================
-include rt/Makefile
-
-# Bootstrap: build kit with the host compiler as stage 1, then rebuild it
-# twice through the normal Makefile using kit's busybox-style cc/ar/ld
-# symlinks. Stages 2 and 3 must be bitwise identical.
-BOOTSTRAP_DIR = $(BUILD_DIR)/bootstrap
-BOOTSTRAP_STAGE1_DIR = $(BOOTSTRAP_DIR)/stage1
-BOOTSTRAP_STAGE2_DIR = $(BOOTSTRAP_DIR)/stage2
-BOOTSTRAP_STAGE3_DIR = $(BOOTSTRAP_DIR)/stage3
-BOOTSTRAP_STAGE1_BIN = $(BOOTSTRAP_STAGE1_DIR)/kit
-BOOTSTRAP_STAGE2_BIN = $(BOOTSTRAP_STAGE2_DIR)/kit
-BOOTSTRAP_STAGE3_BIN = $(BOOTSTRAP_STAGE3_DIR)/kit
-BOOTSTRAP_TOOLS = cc ld ar ranlib as
-BOOTSTRAP_HOST_MODE_CFLAGS =
-BOOTSTRAP_HOST_MODE_LDFLAGS =
-BOOTSTRAP_STAMP = $(BOOTSTRAP_DIR)/.stamp
-BOOTSTRAP_RT_LIBS = $(addprefix $(RT_BUILD_DIR)/,$(addsuffix /libkit_rt.a,$(RT_DEFAULT_VARIANTS)))
-BOOTSTRAP_MAKEFILES = Makefile mk/config.mk rt/Makefile
+RT_CC ?= $(BIN) cc
+RT_AR ?= $(BIN) ar
+RT_AS ?= $(BIN) as
+RT_AS_COMPILE_FLAGS ?=
-ifeq ($(RELEASE),1)
-$(BOOTSTRAP_STAMP): HOST_OPTFLAGS = -O1
-$(BOOTSTRAP_STAMP): BOOTSTRAP_HOST_MODE_CFLAGS = $(HOST_MODE_CFLAGS)
-$(BOOTSTRAP_STAMP): BOOTSTRAP_HOST_MODE_LDFLAGS = -Wl,--gc-sections -Wl,-S
-endif
+.PHONY: rt rt-all-targets rt-no-native-target $(addprefix rt-,$(RT_VARIANTS))
+
+rt: $(if $(RT_DEFAULT_VARIANTS),$(addprefix rt-,$(RT_DEFAULT_VARIANTS)),rt-no-native-target)
+rt-all-targets: $(addprefix rt-,$(RT_VARIANTS))
-bootstrap: bootstrap-debug bootstrap-release
-
-bootstrap-debug:
- $(MAKE) RELEASE=0 BUILD_DIR='$(BUILD_DIR)/debug' _do-bootstrap
-
-bootstrap-release:
- $(MAKE) RELEASE=1 BUILD_DIR='$(BUILD_DIR)/release' _do-bootstrap
-
-_do-bootstrap: $(BOOTSTRAP_STAMP)
-
-$(BOOTSTRAP_STAMP): $(BIN) $(BOOTSTRAP_RT_LIBS) $(BOOTSTRAP_MAKEFILES)
- rm -rf $(BOOTSTRAP_DIR)
- @mkdir -p $(BOOTSTRAP_STAGE1_DIR)
- cp $(BIN) $(BOOTSTRAP_STAGE1_BIN)
- @for tool in $(BOOTSTRAP_TOOLS); do ln -sf kit "$(BOOTSTRAP_STAGE1_DIR)/$$tool"; done
- $(MAKE) lib bin \
- BUILD_DIR='$(abspath $(BOOTSTRAP_STAGE2_DIR))' \
- RELEASE='$(RELEASE)' \
- HOST_OPTFLAGS='$(HOST_OPTFLAGS)' \
- HOST_MODE_CFLAGS='$(BOOTSTRAP_HOST_MODE_CFLAGS)' \
- HOST_MODE_LDFLAGS='$(BOOTSTRAP_HOST_MODE_LDFLAGS)' \
- CC='$(abspath $(BOOTSTRAP_STAGE1_DIR))/cc' \
- AR='$(abspath $(BOOTSTRAP_STAGE1_DIR))/ar' \
- LD='$(abspath $(BOOTSTRAP_STAGE1_DIR))/ld'
- @for tool in $(BOOTSTRAP_TOOLS); do ln -sf kit "$(BOOTSTRAP_STAGE2_DIR)/$$tool"; done
- $(MAKE) lib bin \
- BUILD_DIR='$(abspath $(BOOTSTRAP_STAGE3_DIR))' \
- RELEASE='$(RELEASE)' \
- HOST_OPTFLAGS='$(HOST_OPTFLAGS)' \
- HOST_MODE_CFLAGS='$(BOOTSTRAP_HOST_MODE_CFLAGS)' \
- HOST_MODE_LDFLAGS='$(BOOTSTRAP_HOST_MODE_LDFLAGS)' \
- CC='$(abspath $(BOOTSTRAP_STAGE2_DIR))/cc' \
- AR='$(abspath $(BOOTSTRAP_STAGE2_DIR))/ar' \
- LD='$(abspath $(BOOTSTRAP_STAGE2_DIR))/ld'
- cmp $(BOOTSTRAP_STAGE2_BIN) $(BOOTSTRAP_STAGE3_BIN)
- shasum -a 256 $(BOOTSTRAP_STAGE2_BIN) $(BOOTSTRAP_STAGE3_BIN)
- @touch $@
-
-bench-opt:
- $(MAKE) RELEASE=1 bin
- @KIT='$(abspath build/release/kit)' bash scripts/opt_bench.sh
-
-dist:
- $(MAKE) RELEASE=1 bin
- @rm -rf $(DIST_STAGING)
- @mkdir -p $(DIST_STAGING)/support/rt $(DIST_STAGING)/lib
- cp build/release/kit $(DIST_STAGING)/kit
- cp build/release/libkit.a $(DIST_STAGING)/lib/libkit.a
- cp -r include $(DIST_STAGING)/include
- cp -r rt/include $(DIST_STAGING)/support/rt/include
- cp -r rt/lib $(DIST_STAGING)/support/rt/lib
- cd build/dist && tar czf kit.tar.gz kit
- @echo "dist: $(abspath $(DIST_TARBALL))"
-
-# Format only the .c/.h files changed in the working tree (staged, unstaged, or
-# new/untracked), restricted to the formatted roots and excluding test/pp. When
-# nothing has changed, fall back to formatting every source file.
-format:
- @files=$$( { git diff --name-only --diff-filter=ACMR HEAD -- '*.c' '*.h'; \
- git ls-files --others --exclude-standard -- '*.c' '*.h'; } 2>/dev/null \
- | grep -E '^(src|include|driver|lang|test|rt)/' \
- | grep -vE '^test/pp/' \
- | sort -u ); \
- if [ -n "$$files" ]; then \
- echo "$$files" | xargs clang-format -i --style=google; \
- echo "$$files" | sed 's/^/formatted /'; \
- else \
- echo "no changed sources; formatting all"; \
- find src include driver lang test rt -path test/pp -prune -o \( -name '*.c' -o -name '*.h' \) -print | xargs clang-format -i --style=google; \
- fi
-
-# Regenerate the clangd compilation database (build/compile_commands.json).
-# clangd auto-discovers it under build/; re-run after adding/removing sources
-# or after `make clean`.
-compile-commands:
- python3 scripts/gen_compile_commands.py
-
-clean:
- rm -rf $(BUILD_DIR)
+rt-no-native-target:
+ $(error unsupported native runtime target: HOST_UNAME=$(HOST_UNAME) RT_HOST_MACHINE=$(RT_HOST_MACHINE))
+
+define RT_VARIANT_rules
+rt-$(1): $$(RT_BUILD_DIR)/$(1)/libkit_rt.a
+
+# Regular (not order-only) dep on $(BIN): kit compiles and archives its own
+# runtime, so a codegen or `ar` change in the compiler must rebuild the rt.
+# The archive lists $(RT_OBJS) explicitly rather than $^ so the kit binary
+# (now a regular prereq) is not itself archived.
+#
+# Depend on mk/rt.mk + this Makefile too: the member list (RT_*_SRCS) lives in
+# mk/rt.mk and `ar rcs` only adds/updates members. When the list grows, an
+# existing archive whose objects happen to be newer would otherwise never
+# regenerate and would silently drop the new members (e.g. a missing
+# __clear_cache breaking links). With the makefiles as prereqs, any list edit
+# re-fires the `rm -f` + full re-archive below so the archive always matches
+# RT_OBJS.
+$$(RT_BUILD_DIR)/$(1)/libkit_rt.a: $$(RT_OBJS_$(1)) mk/rt.mk Makefile $$(BIN)
+ @mkdir -p $$(dir $$@)
+ @rm -f $$@
+ $$(RT_AR) rcs $$@ $$(RT_OBJS_$(1))
+
+$$(RT_BUILD_DIR)/$(1)/%.s.o: rt/lib/%.s $$(BIN)
+ @mkdir -p $$(dir $$@)
+ $$(RT_AS) $$(RT_ASFLAGS_$(1)) $$(RT_AS_COMPILE_FLAGS) $$< -o $$@
+
+$$(RT_BUILD_DIR)/$(1)/%.S.o: rt/lib/%.S $$(BIN)
+ @mkdir -p $$(dir $$@)
+ $$(RT_AS) $$(RT_ASFLAGS_$(1)) $$(RT_AS_COMPILE_FLAGS) $$< -o $$@
+
+$$(RT_BUILD_DIR)/$(1)/%.o: rt/lib/% $$(BIN)
+ @mkdir -p $$(dir $$@)
+ $$(RT_CC) $$(RT_CFLAGS_$(1)) -c $$< -o $$@
+
+$$(RT_BUILD_DIR)/$(1)/atomic/atomic_freestanding.c.o: rt/lib/atomic/atomic_common.inc
+endef
+
+$(foreach variant,$(RT_VARIANTS),$(eval $(call RT_VARIANT_rules,$(variant))))
+
+# ===========================================================================
+# Bootstrap, dist, and maintenance targets live in their own includes.
+# ===========================================================================
+include mk/bootstrap.mk
+include mk/dist.mk
+include mk/maint.mk
-include $(LIB_DEPS)
-include $(DRIVER_DEPS)
diff --git a/mk/bootstrap.mk b/mk/bootstrap.mk
@@ -0,0 +1,67 @@
+# mk/bootstrap.mk
+# ===========================================================================
+# Three-stage self-build verification. Included after mk/rt.mk because it
+# references RT_BUILD_DIR / RT_DEFAULT_VARIANTS.
+#
+# Build kit with the host compiler as stage 1, then rebuild it twice through
+# the normal Makefile using kit's busybox-style cc/ar/ld symlinks. Stages 2
+# and 3 must be bitwise identical.
+
+.PHONY: bootstrap bootstrap-debug bootstrap-release _do-bootstrap
+
+BOOTSTRAP_DIR = $(BUILD_DIR)/bootstrap
+BOOTSTRAP_STAGE1_DIR = $(BOOTSTRAP_DIR)/stage1
+BOOTSTRAP_STAGE2_DIR = $(BOOTSTRAP_DIR)/stage2
+BOOTSTRAP_STAGE3_DIR = $(BOOTSTRAP_DIR)/stage3
+BOOTSTRAP_STAGE1_BIN = $(BOOTSTRAP_STAGE1_DIR)/kit
+BOOTSTRAP_STAGE2_BIN = $(BOOTSTRAP_STAGE2_DIR)/kit
+BOOTSTRAP_STAGE3_BIN = $(BOOTSTRAP_STAGE3_DIR)/kit
+BOOTSTRAP_TOOLS = cc ld ar ranlib as
+BOOTSTRAP_HOST_MODE_CFLAGS =
+BOOTSTRAP_HOST_MODE_LDFLAGS =
+BOOTSTRAP_STAMP = $(BOOTSTRAP_DIR)/.stamp
+BOOTSTRAP_RT_LIBS = $(addprefix $(RT_BUILD_DIR)/,$(addsuffix /libkit_rt.a,$(RT_DEFAULT_VARIANTS)))
+BOOTSTRAP_MAKEFILES = Makefile mk/env.mk mk/config.mk mk/flags.mk \
+ mk/lib_srcs.mk mk/driver_srcs.mk mk/rt.mk
+
+ifeq ($(RELEASE),1)
+$(BOOTSTRAP_STAMP): HOST_OPTFLAGS = -O1
+$(BOOTSTRAP_STAMP): BOOTSTRAP_HOST_MODE_CFLAGS = $(HOST_MODE_CFLAGS)
+$(BOOTSTRAP_STAMP): BOOTSTRAP_HOST_MODE_LDFLAGS = -Wl,--gc-sections -Wl,-S
+endif
+
+bootstrap: bootstrap-debug bootstrap-release
+
+bootstrap-debug:
+ $(MAKE) RELEASE=0 BUILD_DIR='$(BUILD_DIR)/debug' _do-bootstrap
+
+bootstrap-release:
+ $(MAKE) RELEASE=1 BUILD_DIR='$(BUILD_DIR)/release' _do-bootstrap
+
+_do-bootstrap: $(BOOTSTRAP_STAMP)
+
+$(BOOTSTRAP_STAMP): $(BIN) $(BOOTSTRAP_RT_LIBS) $(BOOTSTRAP_MAKEFILES)
+ rm -rf $(BOOTSTRAP_DIR)
+ @mkdir -p $(BOOTSTRAP_STAGE1_DIR)
+ cp $(BIN) $(BOOTSTRAP_STAGE1_BIN)
+ @for tool in $(BOOTSTRAP_TOOLS); do ln -sf kit "$(BOOTSTRAP_STAGE1_DIR)/$$tool"; done
+ $(MAKE) lib bin \
+ BUILD_DIR='$(abspath $(BOOTSTRAP_STAGE2_DIR))' \
+ RELEASE='$(RELEASE)' \
+ HOST_OPTFLAGS='$(HOST_OPTFLAGS)' \
+ HOST_MODE_CFLAGS='$(BOOTSTRAP_HOST_MODE_CFLAGS)' \
+ HOST_MODE_LDFLAGS='$(BOOTSTRAP_HOST_MODE_LDFLAGS)' \
+ CC='$(abspath $(BOOTSTRAP_STAGE1_DIR))/cc' \
+ AR='$(abspath $(BOOTSTRAP_STAGE1_DIR))/ar'
+ @for tool in $(BOOTSTRAP_TOOLS); do ln -sf kit "$(BOOTSTRAP_STAGE2_DIR)/$$tool"; done
+ $(MAKE) lib bin \
+ BUILD_DIR='$(abspath $(BOOTSTRAP_STAGE3_DIR))' \
+ RELEASE='$(RELEASE)' \
+ HOST_OPTFLAGS='$(HOST_OPTFLAGS)' \
+ HOST_MODE_CFLAGS='$(BOOTSTRAP_HOST_MODE_CFLAGS)' \
+ HOST_MODE_LDFLAGS='$(BOOTSTRAP_HOST_MODE_LDFLAGS)' \
+ CC='$(abspath $(BOOTSTRAP_STAGE2_DIR))/cc' \
+ AR='$(abspath $(BOOTSTRAP_STAGE2_DIR))/ar'
+ cmp $(BOOTSTRAP_STAGE2_BIN) $(BOOTSTRAP_STAGE3_BIN)
+ shasum -a 256 $(BOOTSTRAP_STAGE2_BIN) $(BOOTSTRAP_STAGE3_BIN)
+ @touch $@
diff --git a/mk/dist.mk b/mk/dist.mk
@@ -0,0 +1,21 @@
+# mk/dist.mk
+# ===========================================================================
+# Release tarball staging: a self-contained kit/ tree (binary, libkit.a,
+# public headers, runtime sources) packaged as kit.tar.gz.
+
+.PHONY: dist
+
+DIST_STAGING = build/dist/kit
+DIST_TARBALL = build/dist/kit.tar.gz
+
+dist:
+ $(MAKE) RELEASE=1 bin
+ @rm -rf $(DIST_STAGING)
+ @mkdir -p $(DIST_STAGING)/support/rt $(DIST_STAGING)/lib
+ cp build/release/kit $(DIST_STAGING)/kit
+ cp build/release/libkit.a $(DIST_STAGING)/lib/libkit.a
+ cp -r include $(DIST_STAGING)/include
+ cp -r rt/include $(DIST_STAGING)/support/rt/include
+ cp -r rt/lib $(DIST_STAGING)/support/rt/lib
+ cd build/dist && tar czf kit.tar.gz kit
+ @echo "dist: $(abspath $(DIST_TARBALL))"
diff --git a/mk/driver_srcs.mk b/mk/driver_srcs.mk
@@ -0,0 +1,63 @@
+# mk/driver_srcs.mk
+# ===========================================================================
+# Driver source selection -> DRIVER_OBJS / DRIVER_DEPS.
+#
+# The tool set is gated by the KIT_TOOL_*_ENABLED flags (mk/config.mk), the
+# same flags main.c keys its dispatch table on. DRIVER_ENV_SRCS (the hosted
+# OS/libc adapter set) comes from mk/env.mk.
+
+# A subcommand TU: include driver/cmd/$(2).c when KIT_TOOL_$(1)_ENABLED is set.
+tool-cmd = $(if $(filter 1,$(KIT_TOOL_$(1)_ENABLED)),driver/cmd/$(2).c)
+# A shared driver/lib TU: include $(2) if ANY of the named tools is enabled.
+need-any = $(if $(filter 1,$(foreach f,$(1),$(KIT_TOOL_$(f)_ENABLED))),$(2))
+
+DRIVER_SRCS = driver/main.c driver/lib/target.c $(DRIVER_ENV_SRCS)
+
+# One entry per subcommand. cc.c serves both `cc` and `check`; the final
+# $(sort) below dedupes it. The cas/pkg tools additionally pull in
+# driver/lib/dist_host.c (handled with the shared-lib group below).
+DRIVER_TOOL_SRCS = \
+ $(call tool-cmd,CC,cc) \
+ $(call tool-cmd,CHECK,cc) \
+ $(call tool-cmd,COMPILE,compile) \
+ $(call tool-cmd,INSTALL,install) \
+ $(call tool-cmd,CPP,cpp) \
+ $(call tool-cmd,AS,as) \
+ $(call tool-cmd,LD,ld) \
+ $(call tool-cmd,AR,ar) \
+ $(call tool-cmd,RANLIB,ranlib) \
+ $(call tool-cmd,STRIP,strip) \
+ $(call tool-cmd,OBJCOPY,objcopy) \
+ $(call tool-cmd,OBJDUMP,objdump) \
+ $(call tool-cmd,DBG,dbg) \
+ $(call tool-cmd,RUN,run) \
+ $(call tool-cmd,EMU,emu) \
+ $(call tool-cmd,NM,nm) \
+ $(call tool-cmd,SIZE,size) \
+ $(call tool-cmd,ADDR2LINE,addr2line) \
+ $(call tool-cmd,STRINGS,strings) \
+ $(call tool-cmd,CAS,cas) \
+ $(call tool-cmd,PKG,pkg) \
+ $(call tool-cmd,XXD,xxd) \
+ $(call tool-cmd,CMP,cmp) \
+ $(call tool-cmd,HASH,hash) \
+ $(call tool-cmd,COMPRESS,compress) \
+ $(call tool-cmd,DISAS,disas) \
+ $(call tool-cmd,MC,mc)
+DRIVER_SRCS += $(sort $(DRIVER_TOOL_SRCS))
+
+# Shared driver/lib support, each compiled in when any of its consumer tools
+# is enabled. The cas/pkg tools link libkit's public cas/package APIs (gated
+# by KIT_CAS_ENABLED / KIT_PKG_ENABLED, asserted by config_assert.c); the dist
+# implementation and vendored primitives live in the library now.
+DRIVER_SRCS += $(call need-any,CC CHECK CPP AS DBG RUN,driver/lib/cflags.c)
+DRIVER_SRCS += $(call need-any,CC CHECK LD RUN,driver/lib/lib_resolve.c)
+DRIVER_SRCS += $(call need-any,CC CHECK RUN,driver/lib/hosted.c)
+DRIVER_SRCS += $(call need-any,CC CHECK LD,driver/lib/runtime.c)
+DRIVER_SRCS += $(call need-any,AR RANLIB STRIP DBG RUN,driver/lib/inputs.c)
+DRIVER_SRCS += $(call need-any,CC CHECK COMPILE,driver/lib/compile_engine.c)
+DRIVER_SRCS += $(call need-any,CAS PKG,driver/lib/dist_host.c)
+
+DRIVER_SRCS := $(sort $(DRIVER_SRCS))
+DRIVER_OBJS = $(patsubst driver/%.c,$(BUILD_DIR)/driver/%.o,$(DRIVER_SRCS))
+DRIVER_DEPS = $(DRIVER_OBJS:.o=.d)
diff --git a/mk/flags.mk b/mk/flags.mk
@@ -0,0 +1,82 @@
+# mk/flags.mk
+# ===========================================================================
+# Compiler/linker flag derivation and the .build-config stamp.
+#
+# Consumes HOST_OS and HOST_SYSROOT_{C,LD}FLAGS from mk/env.mk and the
+# RELEASE switch from the top-level Makefile; produces the *_CFLAGS /
+# *_LDFLAGS the compile rules splice in. Nothing here branches on the host
+# beyond the Darwin dead-strip choice.
+
+ifeq ($(RELEASE),1)
+HOST_OPTFLAGS ?= -O2
+HOST_MODE_CPPFLAGS = -DNDEBUG
+HOST_MODE_CFLAGS = -ffunction-sections -fdata-sections
+ifeq ($(HOST_OS),darwin)
+HOST_MODE_LDFLAGS = -Wl,-dead_strip -Wl,-S
+else
+HOST_MODE_LDFLAGS = -Wl,--gc-sections -Wl,-S
+endif
+else
+HOST_OPTFLAGS ?= -O0
+HOST_MODE_CPPFLAGS =
+HOST_MODE_CFLAGS = -g3 -fno-omit-frame-pointer -fno-optimize-sibling-calls \
+ -fsanitize=address,undefined \
+ -fno-sanitize-recover=address,undefined \
+ -fsanitize-address-use-after-scope
+HOST_MODE_LDFLAGS = -fsanitize=address,undefined
+ASAN_OPTIONS ?= halt_on_error=1:abort_on_error=1
+UBSAN_OPTIONS ?= halt_on_error=1:print_stacktrace=1
+export ASAN_OPTIONS UBSAN_OPTIONS
+endif
+
+# HOST_SYSROOT_{C,LD}FLAGS come from env.mk: a `-isysroot <path>` pair on
+# Darwin, empty elsewhere. Stage/bootstrap recipes can clear these when
+# kit itself is the compiler.
+
+CFLAGS_COMMON = $(HOST_OPTFLAGS) $(HOST_MODE_CPPFLAGS) $(HOST_MODE_CFLAGS) \
+ -std=c11 -Wpedantic -Wall -Wextra -Werror
+HOST_CFLAGS = $(CFLAGS_COMMON) $(HOST_SYSROOT_CFLAGS)
+HOST_LDFLAGS = $(HOST_SYSROOT_LDFLAGS) $(HOST_MODE_LDFLAGS)
+
+DEPFLAGS = -MMD -MP
+
+BUILD_CONFIG = $(BUILD_DIR)/.build-config
+
+.PHONY: FORCE
+FORCE:
+
+$(BUILD_CONFIG): FORCE
+ @mkdir -p $(dir $@)
+ @{ \
+ printf '%s\n' 'RELEASE=$(RELEASE)'; \
+ printf '%s\n' 'HOST_OPTFLAGS=$(HOST_OPTFLAGS)'; \
+ printf '%s\n' 'HOST_MODE_CPPFLAGS=$(HOST_MODE_CPPFLAGS)'; \
+ printf '%s\n' 'HOST_MODE_CFLAGS=$(HOST_MODE_CFLAGS)'; \
+ printf '%s\n' 'HOST_MODE_LDFLAGS=$(HOST_MODE_LDFLAGS)'; \
+ } > $@.tmp
+ @if ! cmp -s $@.tmp $@; then mv $@.tmp $@; else rm -f $@.tmp; fi
+
+# Freestanding objects must not see host SDK/libc headers. Homebrew clang can
+# also inject a configured sysroot before command-line flags; use
+# --no-default-config when the selected compiler supports it, but omit it for
+# bootstrap stages where $(CC) is kit cc.
+FREESTANDING_CONFIG_CFLAGS = $(shell $(CC) --no-default-config -x c -E /dev/null >/dev/null 2>&1 && printf '%s' --no-default-config)
+FREESTANDING_CFLAGS = $(CFLAGS_COMMON) $(FREESTANDING_CONFIG_CFLAGS) -ffreestanding -nostdinc -Irt/include
+
+# libkit: written in C11 freestanding; sees both src/ (internal) and
+# include/ (its own public surface).
+LIB_VISIBILITY_CFLAGS = -fvisibility=hidden
+LIB_CFLAGS = $(FREESTANDING_CFLAGS) $(LIB_VISIBILITY_CFLAGS) -Iinclude -Isrc -Ivendor
+
+# Driver: mostly freestanding CLI binary. Sees only the public include/ tree —
+# that's what makes the driver the first consumer of libkit. -Ilang lets `cc`
+# reach the C frontend's public header ("c/c.h") for the JIT REPL; it
+# deliberately does NOT get -Isrc, so internal headers ("core/...", "link/...")
+# are unreachable from the driver.
+#
+# driver/env/ holds all hosted OS/libc adapter code and is compiled with
+# DRIVER_ENV_CFLAGS. The per-OS feature-test macros and the exact source
+# list (DRIVER_ENV_OS_CFLAGS / DRIVER_ENV_SRCS) come from mk/env.mk.
+DRIVER_CFLAGS = $(FREESTANDING_CFLAGS) -Iinclude -Ilang -Idriver -Idriver/lib
+DRIVER_ENV_CFLAGS = $(HOST_CFLAGS) -Iinclude -Ilang -Idriver -Idriver/lib
+TEST_HOST_CFLAGS = $(HOST_CFLAGS) -Iinclude -Ilang
diff --git a/mk/lib_srcs.mk b/mk/lib_srcs.mk
@@ -0,0 +1,259 @@
+# mk/lib_srcs.mk
+# ===========================================================================
+# libkit source selection -> LIB_OBJS / LIB_DEPS.
+#
+# Consumes the KIT_*_ENABLED flags (mk/config.mk) and BUILD_DIR; mirrors the
+# `#if` gates in the corresponding sources so the build drops exactly what the
+# compile drops. The matching frontend `#if`s live in src/api/lang_registry.c.
+
+# When a native feature is disabled, drop its per-arch TU from all three
+# native backends and, where one exists, compile a stub in its place.
+# $(1) = TU basename (e.g. disasm.c) $(2) = stub source, or empty
+define arch-feature-off
+LIB_SRCS_ARCH_AA64 := $$(filter-out %/$(1),$$(LIB_SRCS_ARCH_AA64))
+LIB_SRCS_ARCH_X64 := $$(filter-out %/$(1),$$(LIB_SRCS_ARCH_X64))
+LIB_SRCS_ARCH_RV64 := $$(filter-out %/$(1),$$(LIB_SRCS_ARCH_RV64))
+LIB_SRCS_NONARCH += $(2)
+endef
+
+# Core lib sources. Optional subsystems, backend directories, object format
+# directories, and ABI implementations are added below from their own groups.
+LIB_SRCS_ABI_CORE = src/abi/abi.c src/abi/registry.c
+LIB_SRCS_API_CORE = $(filter-out src/api/archive.c src/api/disasm.c \
+ src/api/link.c src/api/cas.c src/api/package.c \
+ src/api/compress.c src/api/stubs.c,$(wildcard src/api/*.c))
+LIB_SRCS_ARCH_CORE = $(filter-out src/arch/%_stubs.c,$(wildcard src/arch/*.c))
+LIB_SRCS_ASM_CORE = $(wildcard src/asm/*.c)
+LIB_SRCS_CG_CORE = $(wildcard src/cg/*.c)
+LIB_SRCS_CORE = $(wildcard src/core/*.c)
+LIB_SRCS_OBJ_CORE = $(filter-out src/obj/%_stubs.c,$(wildcard src/obj/*.c))
+LIB_SRCS_NONARCH = $(LIB_SRCS_ABI_CORE) \
+ $(LIB_SRCS_API_CORE) \
+ $(LIB_SRCS_ARCH_CORE) \
+ $(LIB_SRCS_ASM_CORE) \
+ $(LIB_SRCS_CG_CORE) \
+ $(LIB_SRCS_CORE) \
+ $(LIB_SRCS_OBJ_CORE)
+
+# These source lists use `:=` (not `=`) so each `find` runs exactly once at
+# parse time. With recursive `=`, every reference below (LIB_SRCS is rebuilt
+# and re-expanded many times) would re-spawn the `find`, turning ~20 finds into
+# hundreds on every `make` invocation.
+LIB_SRCS_ARCH_AA64 := $(shell find src/arch/aa64 -name '*.c' 2>/dev/null)
+LIB_SRCS_ARCH_X64 := $(shell find src/arch/x64 -name '*.c' 2>/dev/null)
+LIB_SRCS_ARCH_RV64 := $(shell find src/arch/rv64 -name '*.c' 2>/dev/null)
+LIB_SRCS_ARCH_WASM := $(shell find src/arch/wasm -name '*.c' 2>/dev/null)
+LIB_SRCS_ARCH_C_TARGET := $(shell find src/arch/c_target -name '*.c' 2>/dev/null)
+ifneq ($(KIT_OPT_ENABLED),1)
+$(eval $(call arch-feature-off,opt_coord.c,))
+endif
+ifneq ($(KIT_DISASM_ENABLED),1)
+$(eval $(call arch-feature-off,disasm.c,src/arch/disasm_stubs.c))
+endif
+ifneq ($(KIT_LINK_ENABLED),1)
+$(eval $(call arch-feature-off,link.c,src/arch/link_stubs.c))
+endif
+ifneq ($(KIT_DBG_ENABLED),1)
+$(eval $(call arch-feature-off,dbg.c,src/arch/dbg_stubs.c))
+endif
+ifneq ($(KIT_EMU_ENABLED),1)
+LIB_SRCS_ARCH_RV64 := $(filter-out %/emu.c,$(LIB_SRCS_ARCH_RV64))
+LIB_SRCS_NONARCH += src/arch/emu_stubs.c
+endif
+ifneq ($(KIT_INTERP_ENABLED),1)
+LIB_SRCS_NONARCH += src/interp/interp_stubs.c
+endif
+
+LIB_SRCS_OBJ_ELF := $(shell find src/obj/elf -name '*.c' 2>/dev/null)
+LIB_SRCS_OBJ_MACHO := $(shell find src/obj/macho -name '*.c' 2>/dev/null)
+LIB_SRCS_OBJ_COFF := $(shell find src/obj/coff -name '*.c' 2>/dev/null)
+LIB_SRCS_OBJ_WASM := $(shell find src/obj/wasm -name '*.c' 2>/dev/null)
+ifneq ($(KIT_LINK_ENABLED),1)
+LIB_SRCS_OBJ_ELF := $(filter-out %/link.c %/link_dyn.c,$(LIB_SRCS_OBJ_ELF))
+LIB_SRCS_OBJ_MACHO := $(filter-out %/link.c,$(LIB_SRCS_OBJ_MACHO))
+LIB_SRCS_OBJ_COFF := $(filter-out %/link.c,$(LIB_SRCS_OBJ_COFF))
+LIB_SRCS_NONARCH += src/obj/link_stubs.c
+endif
+ifneq ($(KIT_EMU_ENABLED),1)
+LIB_SRCS_OBJ_ELF := $(filter-out %/emu_load.c,$(LIB_SRCS_OBJ_ELF))
+LIB_SRCS_NONARCH += src/obj/emu_stubs.c
+endif
+ifneq ($(KIT_AR_ENABLED),1)
+LIB_SRCS_OBJ_COFF := $(filter-out %/archive.c,$(LIB_SRCS_OBJ_COFF))
+LIB_SRCS_NONARCH += src/obj/archive_stubs.c
+endif
+
+LIB_SRCS_OPT := $(filter-out src/opt/pass_o2.c,$(shell find src/opt -name '*.c' 2>/dev/null))
+LIB_SRCS_INTERP := $(filter-out %/interp_stubs.c,$(shell find src/interp -name '*.c' 2>/dev/null))
+LIB_SRCS_WASM_CORE := $(shell find src/wasm -name '*.c' 2>/dev/null)
+LIB_SRCS_API_AR = src/api/archive.c
+LIB_SRCS_API_DISASM = src/api/disasm.c
+LIB_SRCS_API_LINK = src/api/link.c
+# Distribution subsystem (content store + signed packages). The cas layer
+# needs blake2b (-> monocypher); the pkg layer adds the crypto/container shims
+# and the second monocypher TU. The compression codecs (deflate + lz4 block)
+# are shared by pkg and the standalone compress API, so they live in their own
+# CODEC group pulled in once if either is enabled. The lz4 frame layer
+# (LIB_SRCS_DIST_COMPRESS) is needed only by the compress API. All vendored .c
+# (lz4.c, lz4frame.c's xxhash/lz4hc/lz4frame) are #included by their src/dist
+# shim (amalgamation), so they are NOT compiled standalone.
+LIB_SRCS_API_CAS = src/api/cas.c
+LIB_SRCS_API_PKG = src/api/package.c
+LIB_SRCS_API_COMPRESS = src/api/compress.c
+LIB_SRCS_DIST_CAS = src/dist/dist.c src/dist/blake2b.c src/dist/blob.c \
+ src/dist/tree.c src/dist/cas.c
+LIB_SRCS_VENDOR_CAS = vendor/monocypher/monocypher.c
+LIB_SRCS_DIST_CODEC = src/dist/deflate.c src/dist/lz4.c
+LIB_SRCS_DIST_COMPRESS = src/dist/lz4frame.c
+LIB_SRCS_DIST_PKG = src/dist/b64.c src/dist/ed25519.c src/dist/minisig.c \
+ src/dist/tar.c src/dist/kpkg.c src/dist/manifest.c \
+ src/dist/trust.c
+LIB_SRCS_VENDOR_PKG = vendor/monocypher/monocypher-ed25519.c
+LIB_SRCS_DEBUG := $(shell find src/debug -name '*.c' 2>/dev/null)
+LIB_SRCS_DBG := $(shell find src/dbg -name '*.c' 2>/dev/null)
+LIB_SRCS_EMU := $(shell find src/emu -name '*.c' 2>/dev/null) \
+ $(shell find src/os -name '*.c' 2>/dev/null)
+LIB_SRCS_LINK := $(shell find src/link -name '*.c' 2>/dev/null)
+ifneq ($(KIT_JIT_ENABLED),1)
+LIB_SRCS_LINK := $(filter-out %/link_jit.c,$(LIB_SRCS_LINK))
+endif
+LIB_SRCS_JIT_ASM := $(shell find src/jit -name '*.S' 2>/dev/null)
+
+LIB_SRC_ABI_AAPCS64 = src/abi/abi_aapcs64.c
+LIB_SRC_ABI_APPLE_ARM64 = src/abi/abi_apple_arm64.c
+LIB_SRC_ABI_AAPCS64_WINDOWS = src/abi/abi_aapcs64_windows.c
+LIB_SRC_ABI_SYSV_X64 = src/abi/abi_sysv_x64.c
+LIB_SRC_ABI_APPLE_X64 = src/abi/abi_apple_x64.c
+LIB_SRC_ABI_WIN64_X64 = src/abi/abi_win64_x64.c
+LIB_SRC_ABI_RV64 = src/abi/abi_rv64.c
+
+LIB_SRCS = $(LIB_SRCS_NONARCH)
+ifeq ($(KIT_OPT_ENABLED),1)
+LIB_SRCS += $(LIB_SRCS_OPT)
+endif
+ifeq ($(KIT_INTERP_ENABLED),1)
+LIB_SRCS += $(LIB_SRCS_INTERP)
+endif
+ifeq ($(KIT_AR_ENABLED),1)
+LIB_SRCS += $(LIB_SRCS_API_AR)
+endif
+ifeq ($(KIT_DISASM_ENABLED),1)
+LIB_SRCS += $(LIB_SRCS_API_DISASM)
+endif
+ifeq ($(KIT_DWARF_ENABLED),1)
+LIB_SRCS += $(LIB_SRCS_DEBUG)
+endif
+ifeq ($(KIT_LINK_ENABLED),1)
+LIB_SRCS += $(LIB_SRCS_API_LINK) $(LIB_SRCS_LINK)
+endif
+ifeq ($(KIT_DBG_ENABLED),1)
+LIB_SRCS += $(LIB_SRCS_DBG)
+endif
+ifeq ($(KIT_EMU_ENABLED),1)
+LIB_SRCS += $(LIB_SRCS_EMU)
+endif
+ifeq ($(KIT_CAS_ENABLED),1)
+LIB_SRCS += $(LIB_SRCS_API_CAS) $(LIB_SRCS_DIST_CAS) $(LIB_SRCS_VENDOR_CAS)
+endif
+# Shared compression codecs (deflate + lz4 block): pulled in once if either the
+# compress API or the pkg layer needs them.
+KIT_NEED_CODEC :=
+ifeq ($(KIT_COMPRESS_ENABLED),1)
+LIB_SRCS += $(LIB_SRCS_API_COMPRESS) $(LIB_SRCS_DIST_COMPRESS)
+KIT_NEED_CODEC := 1
+endif
+ifeq ($(KIT_PKG_ENABLED),1)
+LIB_SRCS += $(LIB_SRCS_API_PKG) $(LIB_SRCS_DIST_PKG) $(LIB_SRCS_VENDOR_PKG)
+KIT_NEED_CODEC := 1
+endif
+ifeq ($(KIT_NEED_CODEC),1)
+LIB_SRCS += $(LIB_SRCS_DIST_CODEC)
+endif
+ifeq ($(KIT_ARCH_AA64_ENABLED),1)
+LIB_SRCS += $(LIB_SRCS_ARCH_AA64)
+endif
+ifeq ($(KIT_ARCH_X64_ENABLED),1)
+LIB_SRCS += $(LIB_SRCS_ARCH_X64)
+endif
+ifeq ($(KIT_ARCH_RV64_ENABLED),1)
+LIB_SRCS += $(LIB_SRCS_ARCH_RV64)
+endif
+ifeq ($(KIT_ARCH_WASM_ENABLED),1)
+LIB_SRCS += $(LIB_SRCS_ARCH_WASM)
+endif
+ifeq ($(KIT_ARCH_C_TARGET_ENABLED),1)
+LIB_SRCS += $(LIB_SRCS_ARCH_C_TARGET)
+endif
+ifneq ($(filter 1,$(KIT_ARCH_WASM_ENABLED) $(KIT_OBJ_WASM_ENABLED) $(KIT_LANG_WASM_ENABLED)),)
+LIB_SRCS += $(LIB_SRCS_WASM_CORE)
+LIB_SRCS += $(LIB_SRCS_OBJ_WASM)
+endif
+ifeq ($(KIT_OBJ_ELF_ENABLED),1)
+LIB_SRCS += $(LIB_SRCS_OBJ_ELF)
+endif
+ifeq ($(KIT_OBJ_MACHO_ENABLED),1)
+LIB_SRCS += $(LIB_SRCS_OBJ_MACHO)
+endif
+ifeq ($(KIT_OBJ_COFF_ENABLED),1)
+LIB_SRCS += $(LIB_SRCS_OBJ_COFF)
+endif
+ifneq ($(filter 1,$(KIT_ARCH_AA64_ENABLED) $(KIT_ARCH_C_TARGET_ENABLED)),)
+ifneq ($(filter 1,$(KIT_OBJ_ELF_ENABLED) $(KIT_OBJ_MACHO_ENABLED) $(KIT_OBJ_COFF_ENABLED)),)
+LIB_SRCS += $(LIB_SRC_ABI_AAPCS64)
+endif
+ifeq ($(KIT_OBJ_MACHO_ENABLED),1)
+LIB_SRCS += $(LIB_SRC_ABI_APPLE_ARM64)
+endif
+ifeq ($(KIT_OBJ_COFF_ENABLED),1)
+LIB_SRCS += $(LIB_SRC_ABI_AAPCS64_WINDOWS)
+endif
+endif
+ifneq ($(filter 1,$(KIT_ARCH_X64_ENABLED) $(KIT_ARCH_C_TARGET_ENABLED)),)
+ifneq ($(filter 1,$(KIT_OBJ_ELF_ENABLED) $(KIT_OBJ_MACHO_ENABLED)),)
+LIB_SRCS += $(LIB_SRC_ABI_SYSV_X64)
+endif
+ifeq ($(KIT_OBJ_MACHO_ENABLED),1)
+LIB_SRCS += $(LIB_SRC_ABI_APPLE_X64)
+endif
+ifeq ($(KIT_OBJ_COFF_ENABLED),1)
+LIB_SRCS += $(LIB_SRC_ABI_WIN64_X64)
+endif
+endif
+ifneq ($(filter 1,$(KIT_ARCH_RV64_ENABLED) $(KIT_ARCH_C_TARGET_ENABLED)),)
+ifeq ($(KIT_OBJ_ELF_ENABLED),1)
+LIB_SRCS += $(LIB_SRC_ABI_RV64)
+endif
+endif
+
+# Per-frontend source sets. Each is gated by its KIT_LANG_*_ENABLED flag
+# from mk/config.mk so the matching `#if` in src/api/lang_registry.c and
+# the build agree on which frontends are compiled in.
+LANG_CPP_SRCS := $(shell find lang/cpp -name '*.c' 2>/dev/null)
+LANG_C_SRCS := $(shell find lang/c -name '*.c' 2>/dev/null)
+LANG_WASM_SRCS := $(shell find lang/wasm -name '*.c' 2>/dev/null)
+LANG_TOY_SRCS = $(wildcard lang/toy/*.c)
+
+LANG_OBJS =
+ifeq ($(KIT_LANG_CPP_ENABLED),1)
+LANG_OBJS += $(patsubst lang/cpp/%.c,$(BUILD_DIR)/lang/cpp/%.o,$(LANG_CPP_SRCS))
+endif
+ifeq ($(KIT_LANG_C_ENABLED),1)
+LANG_OBJS += $(patsubst lang/c/%.c,$(BUILD_DIR)/lang/c/%.o,$(LANG_C_SRCS))
+endif
+ifeq ($(KIT_LANG_WASM_ENABLED),1)
+LANG_OBJS += $(patsubst lang/wasm/%.c,$(BUILD_DIR)/lang/wasm/%.o,$(LANG_WASM_SRCS))
+endif
+ifeq ($(KIT_LANG_TOY_ENABLED),1)
+LANG_OBJS += $(patsubst lang/toy/%.c,$(BUILD_DIR)/lang/toy/%.o,$(LANG_TOY_SRCS))
+endif
+
+LIB_ASMS =
+ifeq ($(KIT_JIT_ENABLED),1)
+LIB_ASMS += $(LIB_SRCS_JIT_ASM)
+endif
+
+LIB_OBJS = $(patsubst src/%.c,$(BUILD_DIR)/lib/%.o,$(filter src/%.c,$(LIB_SRCS))) \
+ $(patsubst vendor/%.c,$(BUILD_DIR)/vendor/%.o,$(filter vendor/%.c,$(LIB_SRCS))) \
+ $(LANG_OBJS) \
+ $(patsubst src/%.S,$(BUILD_DIR)/lib/%.o,$(LIB_ASMS))
+LIB_DEPS = $(LIB_OBJS:.o=.d)
diff --git a/mk/maint.mk b/mk/maint.mk
@@ -0,0 +1,36 @@
+# mk/maint.mk
+# ===========================================================================
+# Developer maintenance targets: source formatting, the clangd compilation
+# database, optimizer benchmarking, and clean.
+
+.PHONY: format compile-commands bench-opt clean
+
+# Format only the .c/.h files changed in the working tree (staged, unstaged, or
+# new/untracked), restricted to the formatted roots and excluding test/pp. When
+# nothing has changed, fall back to formatting every source file.
+format:
+ @files=$$( { git diff --name-only --diff-filter=ACMR HEAD -- '*.c' '*.h'; \
+ git ls-files --others --exclude-standard -- '*.c' '*.h'; } 2>/dev/null \
+ | grep -E '^(src|include|driver|lang|test|rt)/' \
+ | grep -vE '^test/pp/' \
+ | sort -u ); \
+ if [ -n "$$files" ]; then \
+ echo "$$files" | xargs clang-format -i --style=google; \
+ echo "$$files" | sed 's/^/formatted /'; \
+ else \
+ echo "no changed sources; formatting all"; \
+ find src include driver lang test rt -path test/pp -prune -o \( -name '*.c' -o -name '*.h' \) -print | xargs clang-format -i --style=google; \
+ fi
+
+# Regenerate the clangd compilation database (build/compile_commands.json).
+# clangd auto-discovers it under build/; re-run after adding/removing sources
+# or after `make clean`.
+compile-commands:
+ python3 scripts/gen_compile_commands.py
+
+bench-opt:
+ $(MAKE) RELEASE=1 bin
+ @KIT='$(abspath build/release/kit)' bash scripts/opt_bench.sh
+
+clean:
+ rm -rf $(BUILD_DIR)
diff --git a/mk/rt.mk b/mk/rt.mk
@@ -0,0 +1,246 @@
+# mk/rt.mk
+# ===========================================================================
+# Runtime (libkit_rt.a) variant selection and per-variant source/flag
+# derivation -> RT_OBJS_<variant> / RT_CFLAGS_<variant> / RT_ASFLAGS_<variant>.
+#
+# The build rules that consume these live in the top-level Makefile. This file
+# is the data: the variant table, the host-native variant detection, the
+# feature->source/flag groups, and the template that expands one variant into
+# its concrete source/object/flag lists. Paths are root-relative.
+
+RT_BUILD_DIR ?= $(BUILD_DIR)/rt
+RT_COMMON_CFLAGS = -Werror
+RT_LIB_INCS = -Irt/lib/include/common -Irt/lib/impl
+
+RT_VARIANTS = \
+ x86_64-linux \
+ x86_64-apple-darwin \
+ aarch64-linux \
+ aarch64-apple-darwin \
+ riscv64-linux \
+ riscv64-elf \
+ riscv64-elf-save-restore \
+ aarch64-windows \
+ x86_64-pc-windows \
+ i386-linux \
+ wasm32 \
+ riscv32-elf \
+ riscv32-elf-save-restore \
+ arm-eabi-thumb2 \
+ arm-eabi-thumb1
+
+RT_HOST_MACHINE ?= $(shell uname -m 2>/dev/null || printf unknown)
+RT_NATIVE_VARIANT =
+ifeq ($(HOST_UNAME),Darwin)
+ifneq ($(filter arm64 aarch64,$(RT_HOST_MACHINE)),)
+RT_NATIVE_VARIANT = aarch64-apple-darwin
+else ifneq ($(filter x86_64 amd64,$(RT_HOST_MACHINE)),)
+RT_NATIVE_VARIANT = x86_64-apple-darwin
+endif
+else ifeq ($(HOST_UNAME),Linux)
+ifneq ($(filter x86_64 amd64,$(RT_HOST_MACHINE)),)
+RT_NATIVE_VARIANT = x86_64-linux
+else ifneq ($(filter arm64 aarch64,$(RT_HOST_MACHINE)),)
+RT_NATIVE_VARIANT = aarch64-linux
+else ifneq ($(filter riscv64,$(RT_HOST_MACHINE)),)
+RT_NATIVE_VARIANT = riscv64-linux
+else ifneq ($(filter i386 i486 i586 i686,$(RT_HOST_MACHINE)),)
+RT_NATIVE_VARIANT = i386-linux
+endif
+else ifneq ($(filter MINGW% MSYS% CYGWIN%,$(HOST_UNAME)),)
+ifneq ($(filter x86_64 amd64,$(RT_HOST_MACHINE)),)
+RT_NATIVE_VARIANT = x86_64-pc-windows
+else ifneq ($(filter arm64 aarch64,$(RT_HOST_MACHINE)),)
+RT_NATIVE_VARIANT = aarch64-windows
+endif
+endif
+
+RT_DEFAULT_VARIANTS ?= $(RT_NATIVE_VARIANT)
+
+# ---------- variant feature flags --------------------------------------------
+# TARGET - clang target triple
+# ABI - lp64 | ilp32 | llp64
+# INT128 - 0 | 1
+# CORO - x86_64 | x86_64_win | i386 | aarch64 |
+# arm32 | arm32_thumb1 | riscv32 | riscv64 | empty
+# LDBL128 - 1 enables binary128 long double support
+# SAVE_RESTORE - 1 enables RISC-V save-restore routines
+# AEABI - thumb2 | thumb1 | empty
+# ARCH_FLAGS - extra target-specific flags
+
+RT_x86_64-linux_TARGET = x86_64-linux-gnu
+RT_x86_64-linux_ABI = lp64
+RT_x86_64-linux_INT128 = 1
+RT_x86_64-linux_CORO = x86_64
+
+RT_x86_64-apple-darwin_TARGET = x86_64-apple-darwin
+RT_x86_64-apple-darwin_ABI = lp64
+RT_x86_64-apple-darwin_INT128 = 1
+RT_x86_64-apple-darwin_CORO = x86_64
+
+RT_aarch64-linux_TARGET = aarch64-linux-gnu
+RT_aarch64-linux_ABI = lp64
+RT_aarch64-linux_INT128 = 1
+RT_aarch64-linux_CORO = aarch64
+RT_aarch64-linux_LDBL128 = 1
+RT_EXTRA_SRCS_aarch64-linux = rt/lib/coro/aarch64_elf.s
+
+RT_aarch64-apple-darwin_TARGET = aarch64-apple-darwin
+RT_aarch64-apple-darwin_ABI = lp64
+RT_aarch64-apple-darwin_INT128 = 1
+RT_aarch64-apple-darwin_CORO = aarch64
+RT_EXTRA_SRCS_aarch64-apple-darwin = rt/lib/coro/aarch64_macho.s
+
+RT_aarch64-windows_TARGET = aarch64-w64-windows-gnu
+RT_aarch64-windows_ABI = llp64
+RT_aarch64-windows_INT128 = 1
+RT_aarch64-windows_CORO = aarch64
+RT_aarch64-windows_HOSTED = 1
+
+RT_riscv64-linux_TARGET = riscv64-linux-gnu
+RT_riscv64-linux_ABI = lp64
+RT_riscv64-linux_INT128 = 1
+RT_riscv64-linux_CORO = riscv64
+# RISC-V `long double` is IEEE-754 binary128 per the psABI; ship the quad
+# soft-float / __int128 runtime (fp_tf, fp_ti).
+RT_riscv64-linux_LDBL128 = 1
+RT_riscv64-linux_ARCH_FLAGS = -mabi=lp64d -march=rv64imafd
+
+RT_riscv64-elf_TARGET = riscv64-unknown-elf
+RT_riscv64-elf_ABI = lp64
+RT_riscv64-elf_INT128 = 1
+RT_riscv64-elf_CORO = riscv64
+RT_riscv64-elf_LDBL128 = 1
+RT_riscv64-elf_ARCH_FLAGS = -mabi=lp64 -march=rv64imafd
+
+RT_riscv64-elf-save-restore_TARGET = riscv64-unknown-elf
+RT_riscv64-elf-save-restore_ABI = lp64
+RT_riscv64-elf-save-restore_INT128 = 1
+RT_riscv64-elf-save-restore_CORO = riscv64
+RT_riscv64-elf-save-restore_SAVE_RESTORE = 1
+RT_riscv64-elf-save-restore_LDBL128 = 1
+RT_riscv64-elf-save-restore_ARCH_FLAGS = -mabi=lp64 -march=rv64imafd
+
+RT_x86_64-pc-windows_TARGET = x86_64-pc-windows-msvc
+RT_x86_64-pc-windows_ABI = llp64
+RT_x86_64-pc-windows_INT128 = 1
+RT_x86_64-pc-windows_CORO = x86_64_win
+RT_x86_64-pc-windows_HOSTED = 1
+
+RT_i386-linux_TARGET = i386-linux-gnu
+RT_i386-linux_ABI = ilp32
+RT_i386-linux_INT128 = 0
+RT_i386-linux_CORO = i386
+
+RT_wasm32_TARGET = wasm32-unknown-unknown
+RT_wasm32_ABI = ilp32
+RT_wasm32_INT128 = 0
+
+RT_riscv32-elf_TARGET = riscv32-unknown-elf
+RT_riscv32-elf_ABI = ilp32
+RT_riscv32-elf_INT128 = 0
+RT_riscv32-elf_CORO = riscv32
+RT_riscv32-elf_ARCH_FLAGS = -mabi=ilp32 -march=rv32imafd
+
+RT_riscv32-elf-save-restore_TARGET = riscv32-unknown-elf
+RT_riscv32-elf-save-restore_ABI = ilp32
+RT_riscv32-elf-save-restore_INT128 = 0
+RT_riscv32-elf-save-restore_CORO = riscv32
+RT_riscv32-elf-save-restore_SAVE_RESTORE = 1
+RT_riscv32-elf-save-restore_ARCH_FLAGS = -mabi=ilp32 -march=rv32imafd
+
+RT_arm-eabi-thumb2_TARGET = arm-none-eabi
+RT_arm-eabi-thumb2_ABI = ilp32
+RT_arm-eabi-thumb2_INT128 = 0
+RT_arm-eabi-thumb2_CORO = arm32
+RT_arm-eabi-thumb2_AEABI = thumb2
+
+RT_arm-eabi-thumb1_TARGET = arm-none-eabi
+RT_arm-eabi-thumb1_ABI = ilp32
+RT_arm-eabi-thumb1_INT128 = 0
+RT_arm-eabi-thumb1_CORO = arm32_thumb1
+RT_arm-eabi-thumb1_AEABI = thumb1
+
+# ---------- feature -> source/flag groups ------------------------------------
+RT_BASE_SRCS = \
+ rt/lib/assert/assert.c \
+ rt/lib/int/int.c \
+ rt/lib/int/si_div.c \
+ rt/lib/fp/fp.c \
+ rt/lib/mem/mem.c \
+ rt/lib/string/string.c \
+ rt/lib/stdlib/stdlib.c \
+ rt/lib/stdlib/qsort.c \
+ rt/lib/stdio/printf.c \
+ rt/lib/atomic/atomic_freestanding.c \
+ rt/lib/cache/clear_cache.c \
+ rt/lib/kit/ifunc_init.c
+
+RT_COMPILER_SRCS = \
+ rt/lib/int/int.c \
+ rt/lib/int/si_div.c \
+ rt/lib/fp/fp.c \
+ rt/lib/atomic/atomic_freestanding.c \
+ rt/lib/cache/clear_cache.c \
+ rt/lib/kit/ifunc_init.c
+
+RT_ABI_SRCS_lp64 = rt/lib/int64/int64.c
+RT_ABI_SRCS_llp64 = rt/lib/int64/int64.c
+RT_ABI_SRCS_ilp32 = rt/lib/int32/int32.c
+
+RT_ABI_INC_lp64 = -Irt/lib/include/lp64_le
+RT_ABI_INC_llp64 = -Irt/lib/include/llp64_le
+RT_ABI_INC_ilp32 = -Irt/lib/include/ilp32_le
+
+RT_CORO_SRCS_x86_64 = rt/lib/coro/x86_64.c rt/lib/coro/coro.c
+RT_CORO_SRCS_x86_64_win = rt/lib/coro/x86_64_win.c rt/lib/coro/coro.c
+RT_CORO_SRCS_i386 = rt/lib/coro/i386.c rt/lib/coro/coro.c
+RT_CORO_SRCS_aarch64 = rt/lib/coro/aarch64.c rt/lib/coro/coro.c
+RT_CORO_SRCS_arm32 = rt/lib/coro/arm32.c rt/lib/coro/coro.c
+RT_CORO_SRCS_arm32_thumb1 = rt/lib/coro/arm32_thumb1.c rt/lib/coro/coro.c
+RT_CORO_SRCS_riscv32 = rt/lib/coro/riscv32.c rt/lib/coro/coro.c
+RT_CORO_SRCS_riscv64 = rt/lib/coro/riscv64.c rt/lib/coro/coro.c
+
+RT_LDBL128_SRCS = rt/lib/fp_tf/fp_tf.c rt/lib/fp_ti/fp_ti.c
+RT_LDBL128_FLAGS = -Irt/lib/include/lp64_le_ldbl128 -DKITRT_LDBL128=1
+
+RT_SAVE_RESTORE_SRCS_lp64 = rt/lib/riscv/rv64.S
+RT_SAVE_RESTORE_SRCS_ilp32 = rt/lib/riscv/rv32.S
+RT_SAVE_RESTORE_FLAGS = -msave-restore
+
+RT_AEABI_SRCS_thumb2 = rt/lib/arm/aeabi_thumb2.S rt/lib/arm/aeabi.c
+RT_AEABI_SRCS_thumb1 = rt/lib/arm/aeabi_thumb1.S rt/lib/arm/aeabi.c
+RT_AEABI_FLAGS_thumb2 = -march=armv7-a -mthumb -mfloat-abi=soft
+RT_AEABI_FLAGS_thumb1 = -march=armv6-m -mthumb -mfloat-abi=soft
+
+# ---------- per-variant derivation -------------------------------------------
+# Expand one variant into its concrete source/object/flag lists. `:=` makes
+# each list concrete at eval time so the rule template in the top-level
+# Makefile can reference RT_OBJS_<variant> directly.
+define RT_VARIANT_srcs
+RT_SRCS_$(1) := \
+ $$(if $$(RT_$(1)_HOSTED),$$(RT_COMPILER_SRCS),$$(RT_BASE_SRCS)) \
+ $$(RT_ABI_SRCS_$$(RT_$(1)_ABI)) \
+ $$(RT_CORO_SRCS_$$(RT_$(1)_CORO)) \
+ $$(if $$(RT_$(1)_LDBL128),$$(RT_LDBL128_SRCS)) \
+ $$(if $$(RT_$(1)_SAVE_RESTORE),$$(RT_SAVE_RESTORE_SRCS_$$(RT_$(1)_ABI))) \
+ $$(RT_AEABI_SRCS_$$(RT_$(1)_AEABI)) \
+ $$(RT_EXTRA_SRCS_$(1))
+RT_CFLAGS_$(1) := \
+ $$(RT_COMMON_CFLAGS) $$(RT_LIB_INCS) \
+ -target $$(RT_$(1)_TARGET) \
+ -DHAS_INT128=$$(RT_$(1)_INT128) \
+ $$(RT_ABI_INC_$$(RT_$(1)_ABI)) \
+ $$(if $$(RT_$(1)_LDBL128),$$(RT_LDBL128_FLAGS)) \
+ $$(if $$(RT_$(1)_CORO),-Irt/include)
+RT_ASFLAGS_$(1) := \
+ -target $$(RT_$(1)_TARGET) \
+ -DHAS_INT128=$$(RT_$(1)_INT128) \
+ -D__ASSEMBLER__=1 \
+ $$(RT_ABI_INC_$$(RT_$(1)_ABI)) \
+ $$(if $$(RT_$(1)_LDBL128),$$(RT_LDBL128_FLAGS)) \
+ $$(if $$(RT_$(1)_CORO),-Irt/include)
+RT_OBJS_$(1) := $$(patsubst rt/lib/%,$$(RT_BUILD_DIR)/$(1)/%.o,$$(RT_SRCS_$(1)))
+endef
+
+$(foreach variant,$(RT_VARIANTS),$(eval $(call RT_VARIANT_srcs,$(variant))))
diff --git a/rt/Makefile b/rt/Makefile
@@ -1,284 +0,0 @@
-# Included by the repository-root Makefile. Paths are root-relative.
-
-RT_CC ?= $(BIN) cc
-RT_AR ?= $(BIN) ar
-RT_AS ?= $(BIN) as
-RT_AS_COMPILE_FLAGS ?=
-
-RT_BUILD_DIR ?= $(BUILD_DIR)/rt
-RT_COMMON_CFLAGS = -Werror
-RT_LIB_INCS = -Irt/lib/include/common -Irt/lib/impl
-
-RT_VARIANTS = \
- x86_64-linux \
- x86_64-apple-darwin \
- aarch64-linux \
- aarch64-apple-darwin \
- riscv64-linux \
- riscv64-elf \
- riscv64-elf-save-restore \
- aarch64-windows \
- x86_64-pc-windows \
- i386-linux \
- wasm32 \
- riscv32-elf \
- riscv32-elf-save-restore \
- arm-eabi-thumb2 \
- arm-eabi-thumb1
-
-RT_HOST_MACHINE ?= $(shell uname -m 2>/dev/null || printf unknown)
-RT_NATIVE_VARIANT =
-ifeq ($(HOST_UNAME),Darwin)
-ifneq ($(filter arm64 aarch64,$(RT_HOST_MACHINE)),)
-RT_NATIVE_VARIANT = aarch64-apple-darwin
-else ifneq ($(filter x86_64 amd64,$(RT_HOST_MACHINE)),)
-RT_NATIVE_VARIANT = x86_64-apple-darwin
-endif
-else ifeq ($(HOST_UNAME),Linux)
-ifneq ($(filter x86_64 amd64,$(RT_HOST_MACHINE)),)
-RT_NATIVE_VARIANT = x86_64-linux
-else ifneq ($(filter arm64 aarch64,$(RT_HOST_MACHINE)),)
-RT_NATIVE_VARIANT = aarch64-linux
-else ifneq ($(filter riscv64,$(RT_HOST_MACHINE)),)
-RT_NATIVE_VARIANT = riscv64-linux
-else ifneq ($(filter i386 i486 i586 i686,$(RT_HOST_MACHINE)),)
-RT_NATIVE_VARIANT = i386-linux
-endif
-else ifneq ($(filter MINGW% MSYS% CYGWIN%,$(HOST_UNAME)),)
-ifneq ($(filter x86_64 amd64,$(RT_HOST_MACHINE)),)
-RT_NATIVE_VARIANT = x86_64-pc-windows
-else ifneq ($(filter arm64 aarch64,$(RT_HOST_MACHINE)),)
-RT_NATIVE_VARIANT = aarch64-windows
-endif
-endif
-
-RT_DEFAULT_VARIANTS ?= $(RT_NATIVE_VARIANT)
-
-.PHONY: rt rt-all-targets rt-no-native-target $(addprefix rt-,$(RT_VARIANTS)) clean-rt
-
-rt: $(if $(RT_DEFAULT_VARIANTS),$(addprefix rt-,$(RT_DEFAULT_VARIANTS)),rt-no-native-target)
-rt-all-targets: $(addprefix rt-,$(RT_VARIANTS))
-
-rt-no-native-target:
- $(error unsupported native runtime target: HOST_UNAME=$(HOST_UNAME) RT_HOST_MACHINE=$(RT_HOST_MACHINE))
-
-# ---------- variant feature flags --------------------------------------------
-# TARGET - clang target triple
-# ABI - lp64 | ilp32 | llp64
-# INT128 - 0 | 1
-# CORO - x86_64 | x86_64_win | i386 | aarch64 |
-# arm32 | arm32_thumb1 | riscv32 | riscv64 | empty
-# LDBL128 - 1 enables binary128 long double support
-# SAVE_RESTORE - 1 enables RISC-V save-restore routines
-# AEABI - thumb2 | thumb1 | empty
-# ARCH_FLAGS - extra target-specific flags
-
-RT_x86_64-linux_TARGET = x86_64-linux-gnu
-RT_x86_64-linux_ABI = lp64
-RT_x86_64-linux_INT128 = 1
-RT_x86_64-linux_CORO = x86_64
-
-RT_x86_64-apple-darwin_TARGET = x86_64-apple-darwin
-RT_x86_64-apple-darwin_ABI = lp64
-RT_x86_64-apple-darwin_INT128 = 1
-RT_x86_64-apple-darwin_CORO = x86_64
-
-RT_aarch64-linux_TARGET = aarch64-linux-gnu
-RT_aarch64-linux_ABI = lp64
-RT_aarch64-linux_INT128 = 1
-RT_aarch64-linux_CORO = aarch64
-RT_aarch64-linux_LDBL128 = 1
-RT_EXTRA_SRCS_aarch64-linux = rt/lib/coro/aarch64_elf.s
-
-RT_aarch64-apple-darwin_TARGET = aarch64-apple-darwin
-RT_aarch64-apple-darwin_ABI = lp64
-RT_aarch64-apple-darwin_INT128 = 1
-RT_aarch64-apple-darwin_CORO = aarch64
-RT_EXTRA_SRCS_aarch64-apple-darwin = rt/lib/coro/aarch64_macho.s
-
-RT_aarch64-windows_TARGET = aarch64-w64-windows-gnu
-RT_aarch64-windows_ABI = llp64
-RT_aarch64-windows_INT128 = 1
-RT_aarch64-windows_CORO = aarch64
-RT_aarch64-windows_HOSTED = 1
-
-RT_riscv64-linux_TARGET = riscv64-linux-gnu
-RT_riscv64-linux_ABI = lp64
-RT_riscv64-linux_INT128 = 1
-RT_riscv64-linux_CORO = riscv64
-# RISC-V `long double` is IEEE-754 binary128 per the psABI; ship the quad
-# soft-float / __int128 runtime (fp_tf, fp_ti).
-RT_riscv64-linux_LDBL128 = 1
-RT_riscv64-linux_ARCH_FLAGS = -mabi=lp64d -march=rv64imafd
-
-RT_riscv64-elf_TARGET = riscv64-unknown-elf
-RT_riscv64-elf_ABI = lp64
-RT_riscv64-elf_INT128 = 1
-RT_riscv64-elf_CORO = riscv64
-RT_riscv64-elf_LDBL128 = 1
-RT_riscv64-elf_ARCH_FLAGS = -mabi=lp64 -march=rv64imafd
-
-RT_riscv64-elf-save-restore_TARGET = riscv64-unknown-elf
-RT_riscv64-elf-save-restore_ABI = lp64
-RT_riscv64-elf-save-restore_INT128 = 1
-RT_riscv64-elf-save-restore_CORO = riscv64
-RT_riscv64-elf-save-restore_SAVE_RESTORE = 1
-RT_riscv64-elf-save-restore_LDBL128 = 1
-RT_riscv64-elf-save-restore_ARCH_FLAGS = -mabi=lp64 -march=rv64imafd
-
-RT_x86_64-pc-windows_TARGET = x86_64-pc-windows-msvc
-RT_x86_64-pc-windows_ABI = llp64
-RT_x86_64-pc-windows_INT128 = 1
-RT_x86_64-pc-windows_CORO = x86_64_win
-RT_x86_64-pc-windows_HOSTED = 1
-
-RT_i386-linux_TARGET = i386-linux-gnu
-RT_i386-linux_ABI = ilp32
-RT_i386-linux_INT128 = 0
-RT_i386-linux_CORO = i386
-
-RT_wasm32_TARGET = wasm32-unknown-unknown
-RT_wasm32_ABI = ilp32
-RT_wasm32_INT128 = 0
-
-RT_riscv32-elf_TARGET = riscv32-unknown-elf
-RT_riscv32-elf_ABI = ilp32
-RT_riscv32-elf_INT128 = 0
-RT_riscv32-elf_CORO = riscv32
-RT_riscv32-elf_ARCH_FLAGS = -mabi=ilp32 -march=rv32imafd
-
-RT_riscv32-elf-save-restore_TARGET = riscv32-unknown-elf
-RT_riscv32-elf-save-restore_ABI = ilp32
-RT_riscv32-elf-save-restore_INT128 = 0
-RT_riscv32-elf-save-restore_CORO = riscv32
-RT_riscv32-elf-save-restore_SAVE_RESTORE = 1
-RT_riscv32-elf-save-restore_ARCH_FLAGS = -mabi=ilp32 -march=rv32imafd
-
-RT_arm-eabi-thumb2_TARGET = arm-none-eabi
-RT_arm-eabi-thumb2_ABI = ilp32
-RT_arm-eabi-thumb2_INT128 = 0
-RT_arm-eabi-thumb2_CORO = arm32
-RT_arm-eabi-thumb2_AEABI = thumb2
-
-RT_arm-eabi-thumb1_TARGET = arm-none-eabi
-RT_arm-eabi-thumb1_ABI = ilp32
-RT_arm-eabi-thumb1_INT128 = 0
-RT_arm-eabi-thumb1_CORO = arm32_thumb1
-RT_arm-eabi-thumb1_AEABI = thumb1
-
-# ---------- sources and flags derived from features --------------------------
-RT_BASE_SRCS = \
- rt/lib/assert/assert.c \
- rt/lib/int/int.c \
- rt/lib/int/si_div.c \
- rt/lib/fp/fp.c \
- rt/lib/mem/mem.c \
- rt/lib/string/string.c \
- rt/lib/stdlib/stdlib.c \
- rt/lib/stdlib/qsort.c \
- rt/lib/stdio/printf.c \
- rt/lib/atomic/atomic_freestanding.c \
- rt/lib/cache/clear_cache.c \
- rt/lib/kit/ifunc_init.c
-
-RT_COMPILER_SRCS = \
- rt/lib/int/int.c \
- rt/lib/int/si_div.c \
- rt/lib/fp/fp.c \
- rt/lib/atomic/atomic_freestanding.c \
- rt/lib/cache/clear_cache.c \
- rt/lib/kit/ifunc_init.c
-
-RT_ABI_SRCS_lp64 = rt/lib/int64/int64.c
-RT_ABI_SRCS_llp64 = rt/lib/int64/int64.c
-RT_ABI_SRCS_ilp32 = rt/lib/int32/int32.c
-
-RT_ABI_INC_lp64 = -Irt/lib/include/lp64_le
-RT_ABI_INC_llp64 = -Irt/lib/include/llp64_le
-RT_ABI_INC_ilp32 = -Irt/lib/include/ilp32_le
-
-RT_CORO_SRCS_x86_64 = rt/lib/coro/x86_64.c rt/lib/coro/coro.c
-RT_CORO_SRCS_x86_64_win = rt/lib/coro/x86_64_win.c rt/lib/coro/coro.c
-RT_CORO_SRCS_i386 = rt/lib/coro/i386.c rt/lib/coro/coro.c
-RT_CORO_SRCS_aarch64 = rt/lib/coro/aarch64.c rt/lib/coro/coro.c
-RT_CORO_SRCS_arm32 = rt/lib/coro/arm32.c rt/lib/coro/coro.c
-RT_CORO_SRCS_arm32_thumb1 = rt/lib/coro/arm32_thumb1.c rt/lib/coro/coro.c
-RT_CORO_SRCS_riscv32 = rt/lib/coro/riscv32.c rt/lib/coro/coro.c
-RT_CORO_SRCS_riscv64 = rt/lib/coro/riscv64.c rt/lib/coro/coro.c
-
-RT_LDBL128_SRCS = rt/lib/fp_tf/fp_tf.c rt/lib/fp_ti/fp_ti.c
-RT_LDBL128_FLAGS = -Irt/lib/include/lp64_le_ldbl128 -DKITRT_LDBL128=1
-
-RT_SAVE_RESTORE_SRCS_lp64 = rt/lib/riscv/rv64.S
-RT_SAVE_RESTORE_SRCS_ilp32 = rt/lib/riscv/rv32.S
-RT_SAVE_RESTORE_FLAGS = -msave-restore
-
-RT_AEABI_SRCS_thumb2 = rt/lib/arm/aeabi_thumb2.S rt/lib/arm/aeabi.c
-RT_AEABI_SRCS_thumb1 = rt/lib/arm/aeabi_thumb1.S rt/lib/arm/aeabi.c
-RT_AEABI_FLAGS_thumb2 = -march=armv7-a -mthumb -mfloat-abi=soft
-RT_AEABI_FLAGS_thumb1 = -march=armv6-m -mthumb -mfloat-abi=soft
-
-define RT_VARIANT_template
-RT_SRCS_$(1) := \
- $$(if $$(RT_$(1)_HOSTED),$$(RT_COMPILER_SRCS),$$(RT_BASE_SRCS)) \
- $$(RT_ABI_SRCS_$$(RT_$(1)_ABI)) \
- $$(RT_CORO_SRCS_$$(RT_$(1)_CORO)) \
- $$(if $$(RT_$(1)_LDBL128),$$(RT_LDBL128_SRCS)) \
- $$(if $$(RT_$(1)_SAVE_RESTORE),$$(RT_SAVE_RESTORE_SRCS_$$(RT_$(1)_ABI))) \
- $$(RT_AEABI_SRCS_$$(RT_$(1)_AEABI)) \
- $$(RT_EXTRA_SRCS_$(1))
-RT_CFLAGS_$(1) := \
- $$(RT_COMMON_CFLAGS) $$(RT_LIB_INCS) \
- -target $$(RT_$(1)_TARGET) \
- -DHAS_INT128=$$(RT_$(1)_INT128) \
- $$(RT_ABI_INC_$$(RT_$(1)_ABI)) \
- $$(if $$(RT_$(1)_LDBL128),$$(RT_LDBL128_FLAGS)) \
- $$(if $$(RT_$(1)_CORO),-Irt/include)
-RT_ASFLAGS_$(1) := \
- -target $$(RT_$(1)_TARGET) \
- -DHAS_INT128=$$(RT_$(1)_INT128) \
- -D__ASSEMBLER__=1 \
- $$(RT_ABI_INC_$$(RT_$(1)_ABI)) \
- $$(if $$(RT_$(1)_LDBL128),$$(RT_LDBL128_FLAGS)) \
- $$(if $$(RT_$(1)_CORO),-Irt/include)
-RT_OBJS_$(1) := $$(patsubst rt/lib/%,$$(RT_BUILD_DIR)/$(1)/%.o,$$(RT_SRCS_$(1)))
-
-rt-$(1): $$(RT_BUILD_DIR)/$(1)/libkit_rt.a
-
-# Regular (not order-only) dep on $(BIN): kit compiles and archives its own
-# runtime, so a codegen or `ar` change in the compiler must rebuild the rt.
-# The archive lists $(RT_OBJS) explicitly rather than $^ so the kit binary
-# (now a regular prereq) is not itself archived.
-#
-# Depend on rt/Makefile too: the member list (RT_*_SRCS) lives here, and
-# `ar rcs` only adds/updates members. When the list grows, an existing
-# archive whose objects happen to be newer would otherwise never regenerate
-# and would silently drop the new members (e.g. a missing __clear_cache
-# breaking links). With the makefile as a prereq, any list edit re-fires the
-# `rm -f` + full re-archive below so the archive always matches RT_OBJS.
-$$(RT_BUILD_DIR)/$(1)/libkit_rt.a: $$(RT_OBJS_$(1)) rt/Makefile $$(BIN)
- @mkdir -p $$(dir $$@)
- @rm -f $$@
- $$(RT_AR) rcs $$@ $$(RT_OBJS_$(1))
-
-$$(RT_BUILD_DIR)/$(1)/%.s.o: rt/lib/%.s $$(BIN)
- @mkdir -p $$(dir $$@)
- $$(RT_AS) $$(RT_ASFLAGS_$(1)) $$(RT_AS_COMPILE_FLAGS) $$< -o $$@
-
-$$(RT_BUILD_DIR)/$(1)/%.S.o: rt/lib/%.S $$(BIN)
- @mkdir -p $$(dir $$@)
- $$(RT_AS) $$(RT_ASFLAGS_$(1)) $$(RT_AS_COMPILE_FLAGS) $$< -o $$@
-
-$$(RT_BUILD_DIR)/$(1)/%.o: rt/lib/% $$(BIN)
- @mkdir -p $$(dir $$@)
- $$(RT_CC) $$(RT_CFLAGS_$(1)) -c $$< -o $$@
-
-$$(RT_BUILD_DIR)/$(1)/atomic/atomic_freestanding.c.o: rt/lib/atomic/atomic_common.inc
-endef
-
-$(foreach variant,$(RT_VARIANTS),$(eval $(call RT_VARIANT_template,$(variant))))
-
-clean: clean-rt
-
-clean-rt:
- rm -rf $(RT_BUILD_DIR)