1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
|
# 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 <llvm-c/Core.h>\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_SAVE=1 $(MAKE) 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
|