# tinyvm and zc. `make` builds the tools, the interpreter, the microcore and the # language server into build/bin/. everything built or generated goes under # build/; the tree holds sources only, and `make clean` (rm -rf build) returns it # to a fresh checkout. # # make everything below that is a program # make test the whole suite (tests/test.sh) # make check the minimal suite, for iterating (tests/test.sh -q) # make test JOBS=4 the same, four sections at a time # make parity compile the test programs with zc0 and with the self-hosted # compiler and compare what they print (src/tools/parity.sh) # make xref who writes and who reads each global of the compiler # (src/tools/xref.sh): build/xref/report.txt and report.json; # make xref XREF_ARGS=--check compares with tests/xref.baseline # make bench time the benchmark programs -> build/bench.txt # make bench-save ... and keep the result as tests/bench.baseline # make bench-check compare with tests/bench.baseline (or make bench CHECK=1) # make genome build/core/genome.bin from src/, with the fixpoint check # make LLVM=1 on x86_64, build zy with the llvm backend instead of the x86-64 jit # make NOLLVM=1 on other hosts, leave out the llvm backend (in by default when a # usable llvm-config is on PATH, or LLVM_CONFIG names one) # make build/bin/zyi the interpreter alone (zy has a jit on arm64 and on x86-64 linux) # make lsp build/bin/zclsp, the language server # make web build/web/: the page, its script and zc.wasm # make js build/js/: the javascript vm, its layout and genome # make run PROG=examples/hello.zc # compile one program into a run-only image on a memory # map sized to it (src/tools/automap.sh) -> build/run/zymr # make vsix the vscode extension, server and all # make clean remove build/ # # the cores take their genome in with c23 #embed (src/core/genome.h), so CC has # to be clang 19 or gcc 15 or later. src/tools/findcc.sh picks one when CC is # not given on the command line or in the environment. ifeq ($(origin CC),default) CC := $(shell sh src/tools/findcc.sh || echo cc) endif export CC CFLAGS ?= -O2 -w -fwrapv ifeq ($(OS),Windows_NT) EXE := .exe IO := src/core/io_win.c PORT := src/micro/port_win.c else EXE := IO := src/core/io_linux.c PORT := src/micro/port_linux.c CFLAGS += -pthread MICRO_THREADS := -DZY_THREADS endif B := build BIN := $(B)/bin JS := $(B)/js WEB := $(B)/web # the default memory map, generated from the const blocks in the zc sources MAP := $(B)/core/map # hand-written core headers, the generated map, and where #embed finds the genome image COREINC := -Isrc/core -I$(MAP) --embed-dir=$(B)/core CORE_H := src/core/genome.h src/core/fops.h src/core/bops.h src/core/stack.h $(MAP)/memmap.h # the sources the compiler genome is built from, and the files it embeds # the compiler is the files named in src/compiler/parts, in that order ZC_PARTS := $(addprefix src/compiler/,$(shell cat src/compiler/parts)) ZC_SRC := src/runtime/rt.zc src/core/core.zc $(ZC_PARTS) ZC_DEPS := $(ZC_SRC) src/compiler/parts src/lib/std.zc src/lib/icon.zc src/tools/build.zc TOOLS := $(BIN)/zc0$(EXE) $(BIN)/mkgenome$(EXE) $(BIN)/genmap$(EXE) PROGS := $(BIN)/zy$(EXE) $(BIN)/zym$(EXE) $(BIN)/zclsp$(EXE) all: $(TOOLS) $(PROGS) $(BIN): mkdir -p $@ # genmap needs no map of its own; everything else is built against the one it writes $(BIN)/genmap$(EXE): src/tools/genmap.c | $(BIN) $(CC) $(CFLAGS) src/tools/genmap.c -o $@ $(MAP)/memmap.h: $(BIN)/genmap$(EXE) src/runtime/rt.zc src/compiler/parts $(ZC_PARTS) mkdir -p $(B)/core $(BIN)/genmap$(EXE) --preset default --check -o $(MAP) >$(B)/core/genmap.log 2>&1 || { cat $(B)/core/genmap.log; exit 1; } $(BIN)/zc0$(EXE): src/tools/zc0.c src/core/fops.h src/core/bops.h $(MAP)/memmap.h | $(BIN) $(CC) -O1 -w -fwrapv src/tools/zc0.c -Isrc/core -I$(MAP) -o $@ $(BIN)/mkgenome$(EXE): src/tools/mkgenome.c $(MAP)/memmap.h | $(BIN) $(CC) -O1 -w -fwrapv src/tools/mkgenome.c -I$(MAP) -o $@ # bootstrap: zc0 compiles the compiler to g0, g0 compiles it to g1, g1 to g2. # g1 and g2 must be identical (the fixpoint), and g1 becomes build/core/genome.bin. # each stage's core embeds the image beside it, so no stage reads another's. genome: $(B)/core/genome.bin $(B)/core/genome.bin: $(ZC_DEPS) $(BIN)/zc0$(EXE) $(BIN)/mkgenome$(EXE) src/core/core.c $(IO) $(CORE_H) mkdir -p $(B)/s0 $(B)/s1 $(B)/core cat $(ZC_SRC) | $(BIN)/zc0$(EXE) > $(B)/s0/g0.bin $(BIN)/mkgenome$(EXE) < $(B)/s0/g0.bin > $(B)/s0/genome.bin $(CC) $(CFLAGS) -DJTYPE=int src/core/core.c $(IO) -Isrc/core -I$(MAP) --embed-dir=$(B)/s0 -o $(B)/s0/zy0$(EXE) (cat src/tools/build.zc; printf '`'; cat $(ZC_SRC)) | $(B)/s0/zy0$(EXE) > $(B)/s1/g1.bin $(BIN)/mkgenome$(EXE) < $(B)/s1/g1.bin > $(B)/s1/genome.bin $(CC) $(CFLAGS) -DJTYPE=int src/core/core.c $(IO) -Isrc/core -I$(MAP) --embed-dir=$(B)/s1 -o $(B)/s1/zy1$(EXE) (cat src/tools/build.zc; printf '`'; cat $(ZC_SRC)) | $(B)/s1/zy1$(EXE) > $(B)/s1/g2.bin cmp $(B)/s1/g1.bin $(B)/s1/g2.bin cp $(B)/s1/genome.bin $@ @echo "fixpoint: $$(( $$(wc -c < $@) / 8 )) words, $@ updated" # the llvm backend (-DZYLLVM) in zy. -DZYLLVM replaces the x86-64 jit, so on x86_64 hosts # (uname -m) it is built only when LLVM=1 is given; on other hosts it is in whenever an llvm # is found, and NOLLVM=1 leaves it out. the llvm is LLVM_CONFIG when given, else llvm-config, # else llvm-config-19 on PATH; it is used only when llvm-c/Core.h exists, --libs is not # empty and a tiny program links against it (llvm-config can exist without headers or libs). # otherwise the build goes on without it and prints a one-line note. HOST_ARCH := $(shell uname -m 2>/dev/null) ifeq ($(HOST_ARCH),x86_64) WANT_LLVM := $(if $(filter 1,$(LLVM)),1,) else WANT_LLVM := $(if $(filter 1,$(NOLLVM)),,1) endif ifeq ($(WANT_LLVM),1) ifeq ($(origin LLVM_CONFIG),undefined) LLVM_CONFIG := $(firstword $(shell command -v llvm-config llvm-config-19 2>/dev/null)) endif ifneq ($(LLVM_CONFIG),) ifneq ($(wildcard $(shell $(LLVM_CONFIG) --includedir 2>/dev/null)/llvm-c/Core.h),) llvm_try_libs := $(shell $(LLVM_CONFIG) --libs 2>/dev/null) ifneq ($(strip $(llvm_try_libs)),) llvm_try_cflags := $(shell $(LLVM_CONFIG) --cflags 2>/dev/null) llvm_try_ldflags := $(shell $(LLVM_CONFIG) --ldflags 2>/dev/null) llvm_link_ok := $(shell d=$$(mktemp -d) && printf '\043include \nint main(void){LLVMContextDispose(LLVMContextCreate());return 0;}\n' > $$d/t.c && $(CC) $(llvm_try_cflags) $$d/t.c -o $$d/t $(llvm_try_ldflags) $(llvm_try_libs) >/dev/null 2>&1 && echo yes; rm -rf $$d) ifeq ($(llvm_link_ok),yes) LLVM_CFLAGS := -DZYLLVM $(llvm_try_cflags) LLVM_LIBS := $(llvm_try_ldflags) $(llvm_try_libs) endif endif endif endif ifeq ($(LLVM_CFLAGS),) $(info note: no usable llvm (llvm-config with headers, libs and a test link), building zy without the llvm backend) endif endif # the mode zy was built in; rewritten only when it changes, so switching rebuilds zy LLVM_MODE := $(if $(LLVM_CFLAGS),llvm,nollvm) ifneq ($(MAKECMDGOALS),clean) $(shell mkdir -p $(B); [ "$$(cat $(B)/llvm.mode 2>/dev/null)" = "$(LLVM_MODE)" ] || echo $(LLVM_MODE) > $(B)/llvm.mode) endif # the vm core with the compiler genome inside: a jit on arm64 and x86-64 linux, else the # interpreter; plus the llvm backend when found (above) $(BIN)/zy$(EXE): src/core/core.c $(IO) $(B)/core/genome.bin $(CORE_H) $(B)/llvm.mode | $(BIN) $(CC) $(CFLAGS) -DJTYPE=int $(LLVM_CFLAGS) src/core/core.c $(IO) $(COREINC) -o $@ $(LLVM_LIBS) # the interpreter alone, even on a host with a jit: make build/bin/zyi, to compare against zy $(BIN)/zyi$(EXE): src/core/core.c $(IO) $(B)/core/genome.bin $(CORE_H) | $(BIN) $(CC) $(CFLAGS) -DZYINTERP -DJTYPE=int src/core/core.c $(IO) $(COREINC) -o $@ # the portable microcore $(BIN)/zym$(EXE): src/micro/micro.c src/micro/micro_port.h $(PORT) $(B)/core/genome.bin $(CORE_H) | $(BIN) $(CC) $(CFLAGS) $(MICRO_THREADS) src/micro/micro.c $(PORT) $(COREINC) -Isrc/micro -o $@ # the language server is the compiler in check mode plus src/lsp/server.zc, on a map # sized to it LSP_DEPS := src/core/core.c src/core/io_linux.c $(filter-out $(MAP)/memmap.h,$(CORE_H)) $(ZC_DEPS) src/lsp/server.zc \ src/tools/zc0.c src/tools/genmap.c src/tools/mkgenome.c src/tools/automap.sh src/tools/mklsp.sh lsp: $(BIN)/zclsp$(EXE) $(BIN)/zclsp$(EXE): $(LSP_DEPS) | $(BIN) bash src/tools/mklsp.sh $(B)/lsp cp $(B)/lsp/zclsp$(EXE) $@ # the javascript vm: tinyvm.js loads genome.bin from beside itself js: $(JS)/tinyvm.js $(JS)/layout.js $(JS)/genome.bin $(JS): mkdir -p $@ $(JS)/genome.bin: $(B)/core/genome.bin | $(JS) cp $< $@ $(JS)/layout.js: $(MAP)/memmap.h src/js/mklayout.js src/js/mapvalues.js | $(JS) node src/js/mklayout.js --map $(MAP)/memmap.h --out $(JS) $(JS)/tinyvm.js: src/js/tinyvm.ts src/js/layout.d.ts | $(JS) tsc src/js/tinyvm.ts --target es2020 --lib es2020,dom --module commonjs --outDir $(JS) # the language server image for the javascript vm, and the wasm server lspimage: $(JS)/lsp.bin $(JS)/lsp.bin: $(BIN)/zclsp$(EXE) src/js/extract_lsp.js src/js/mapvalues.js | $(JS) node src/js/extract_lsp.js # the vscode extension, with the server and its image inside it vsix: bash src/tools/mkvsix.sh # the browser compiler: the page, its loader and the examples it offers beside the wasm build WEB_EXAMPLES := $(patsubst examples/%,$(WEB)/examples/%,$(wildcard examples/*.zc)) web: $(WEB)/zc.wasm $(WEB)/index.html $(WEB)/zc.js $(WEB_EXAMPLES) $(WEB)/examples/%.zc: examples/%.zc mkdir -p $(WEB)/examples cp $< $@ $(WEB)/zc.wasm: src/micro/micro.c src/micro/micro_port.h src/web/port_wasm.c src/web/build.sh $(B)/core/genome.bin $(CORE_H) bash src/web/build.sh $@ $(WEB)/index.html $(WEB)/zc.js: $(WEB)/%: src/web/% mkdir -p $(WEB) cp $< $@ # one program as a run-only image: the runtime, the program, and a map exactly # big enough for them run: @test -n "$(PROG)" || { echo "make run PROG=examples/hello.zc"; exit 2; } bash src/tools/automap.sh run $(B)/run src/runtime/run.zc $(PROG) $(CC) $(CFLAGS) $(MICRO_THREADS) src/micro/micro.c $(PORT) -Isrc/core -Isrc/micro -I$(B)/run --embed-dir=$(B)/run -o $(B)/run/zymr$(EXE) @echo "built $(B)/run/zymr$(EXE)" # the two compilers on the same programs: whatever zc0 accepts must print the same # under both. work files go to build/parity/. parity: $(BIN)/zc0$(EXE) $(BIN)/mkgenome$(EXE) $(BIN)/zy$(EXE) bash src/tools/parity.sh # the compiler's globals, by writer and reader; the language server supplies the exact # scoping. XREF_ARGS passes options: --check (ci), --update (rewrite the baseline). xref: $(BIN)/zclsp$(EXE) bash src/tools/xref.sh $(XREF_ARGS) # benchmarks (tests/bench/suite.py) on build/bin/zy: build/bench.txt, ms per repetition per # program. bench-save makes it the committed baseline, tests/bench.baseline; bench-check # (or make bench CHECK=1) fails when a column is over 10% and over 0.1 ms slower than the # baseline. interp rows only with INTERP=1. bench-selftest checks the verdicts, no timing. bench: $(BIN)/zy$(EXE) ifeq ($(CHECK),1) INTERP=$(INTERP) python3 tests/bench/suite.py check else INTERP=$(INTERP) python3 tests/bench/suite.py run endif bench-save: bench cp $(B)/bench.txt tests/bench.baseline @echo "bench: wrote tests/bench.baseline" bench-check: $(BIN)/zy$(EXE) INTERP=$(INTERP) python3 tests/bench/suite.py check bench-selftest: INTERP=$(INTERP) python3 tests/bench/suite.py selftest # JOBS sets how many sections run at once: make test JOBS=4 test: bash tests/test.sh -j $(or $(JOBS),1) # the minimal suite: bootstrap, fixpoint, interpreter and lsp checks check: bash tests/test.sh -q -j $(or $(JOBS),2) clean: rm -rf $(B) .PHONY: bench bench-save bench-selftest bench-check all lsp genome js lspimage vsix web run parity xref test check clean