forked from jolt-lang/jolt
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMakefile
More file actions
1352 lines (1186 loc) · 67.9 KB
/
Copy pathMakefile
File metadata and controls
1352 lines (1186 loc) · 67.9 KB
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
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# jolt — Clojure on Chez Scheme. Single substrate, no Janet.
#
# bin/jolt runs jolt directly off the checked-in seed (host/chez/seed/).
# `make build` builds the standalone binary, `make test` is the full gate, and
# `make remint` rebuilds the seed after a source change.
R := https://github.com/makeplus/makes
M ?= .cache/makes
# PINNED. Unpinned, this clone is whatever makeplus/makes HEAD is on the day of
# the build — including a release build, whose toolchain would then be decided by
# a repository this one records no version of. Bump deliberately.
V := d14cb578c2f04e6f9d00e01c7c4e416a9baf94e9
# Nix supplies a locked, source-only tree through M. Ordinary checkouts retain
# the Git clone so their pinned revision is verified and provisioned on demand.
$(shell [ -f '$M/init.mk' ] || git clone -q $R '$M')
# Fetch only when the pin is absent, so a normal build stays offline. A
# source-only Nix input has no .git directory, so there is nothing to fetch.
$(shell [ ! -d '$M/.git' ] || { git -C '$M' cat-file -e '$V^{commit}' 2>/dev/null || git -C '$M' fetch -q origin; git -C '$M' rev-parse -q --verify HEAD 2>/dev/null | grep -qx '$V' || git -C '$M' checkout -q '$V'; })
include $M/init.mk
# An explicit caller-selected Chez is authoritative. This preserves CI/release
# toolchains whose threading, libc floor, and native libraries are intentional.
#
# Same-or-newer system Chez: with nothing explicit selected, a Chez on PATH at
# or above JOLT-CHEZ-FLOOR is also used as-is — the system toolchain then
# builds and links everything, one self-consistent toolchain end to end, and
# nothing is downloaded. Older, broken, or absent, fall through to provisioning
# the pinned Chez + xPack GCC below. Set JOLT_SYSTEM_CHEZ= (empty) to always
# provision the pinned versions.
JOLT-CHEZ-FLOOR ?= 10.4.1
JOLT_SYSTEM_CHEZ ?= 1
JOLT-CHEZ := $(or $(CHEZ),$(CHEZSCHEME))
ifeq (,$(JOLT-CHEZ))
ifeq (1,$(JOLT_SYSTEM_CHEZ))
JOLT-CHEZ := $(shell \
for name in chez chezscheme scheme; do \
exe=$$(command -v $$name 2>/dev/null) || continue; \
test -x "$$exe" || continue; \
exe=$$(cd "$$(dirname "$$exe")" && pwd -P)/$$(basename "$$exe"); \
v=$$(printf '(display (scheme-version)) (newline)\n' | "$$exe" -q 2>/dev/null | tr -d '\r'); \
v=$$(printf '%s\n' "$$v" | awk '{print $$NF}'); \
ok=$$(awk -v v="$$v" -v f="$(JOLT-CHEZ-FLOOR)" 'BEGIN { \
split(v, V, "."); split(f, F, "."); \
for (i = 1; i <= 3; i = i + 1) { \
if (V[i] + 0 > F[i] + 0) { print 1; exit } \
if (V[i] + 0 < F[i] + 0) { exit } \
} \
print 1 }'); \
if [ "$$ok" = 1 ]; then printf '%s\n' "$$exe"; break; fi; \
done)
endif
endif
ifneq (,$(JOLT-CHEZ))
SHELL-DEPS += $(JOLT-CHEZ)
else
include $M/chezscheme.mk
JOLT-CHEZ := $(CHEZSCHEME)
endif
include $M/clean.mk
include $M/shell.mk
MAKES-CLEAN := \
build/ \
target/ \
MAKES-REALCLEAN := \
.jolt/ \
test/chez/*/.jolt/ \
test/chez/*/*/.jolt/ \
PREFIX ?= $(if $(IS-ROOT),/usr/local,$(HOME)/.local)
CHEZ ?= $(JOLT-CHEZ)
# Hand the selected Chez down to bin/jolt, so the targets that shell out to it
# run the same interpreter as the ones that call $(CHEZ) directly. When make
# provisions its own Chez (the one on PATH being a different version), bin/jolt's
# own PATH search would otherwise pick the other one.
export JOLT_CHEZ := $(CHEZ)
# A locally built Chez links its kernel against the bundled lz4 and zlib. Their
# static archives remain in the Makes build tree rather than the installed Chez
# prefix, so expose them when linking Jolt's standalone launcher.
ifneq (,$(CHEZSCHEME-LOCAL))
CHEZSCHEME-LIB-DIRS := \
$(LOCAL-TMP)/$(CHEZSCHEME-DIR)/pb/lz4/lib \
$(LOCAL-TMP)/$(CHEZSCHEME-DIR)/pb/zlib
export LIBRARY_PATH := $(subst $(space),:,$(strip $(CHEZSCHEME-LIB-DIRS)))$(if $(LIBRARY_PATH),:$(LIBRARY_PATH))
# The provisioned GCC built Chez, so the same driver links the standalone
# binary: gcc.mk puts the bundle's bin FIRST on the exported PATH, but the
# bundle ships no `cc`, so a bare cc falls through to the distro driver and
# pairs it with the bundle's older as/ld (#788: gcc 16 .base64 into pre-2.43
# gas). build.ss bld-cc reads JOLT_CC. A command-line JOLT_CC=... still wins.
export JOLT_CC := $(GCC)
endif
JOLT-TARGETS-NEEDING-DEPS := \
aotcacheperf aotcachesmoke aotfingerprint asynctimer buildlibsmoke buildsmoke \
aotcachepathsmoke compilepathsmoke contagion corpus cts dcerefs depssmoke depsunit devboot \
readscaling vecscaling pipescaling chunkscaling printscaling complexity ioscaling hotscaling applyscaling lazyscaling \
devbootsmoke devirt directlink ffi fibers fieldjoin fieldnum fieldread flarr fnform coreproc grenadine \
gateboot gatebootsmoke gosm hasheq httpsfetch infer inline inline-body irvalidate statlayout \
jolt jolt-debug jolt-release joltsmoke libconformance mandelbrot-num mathfl mvnhttp \
deadhost mirrordrift mirrordrift-regen regexdfacheck regexdfacheck-regen regexdfa \
narrow narrowhash numeric numwp oparity pic protoret printperf remint sbperf sci selfhost shakelocal \
traceemit vfaslceiling \
shakesmoke smoke staticnativesmoke stateimage test testbin transient unit unitcontext \
threadsafety values wp ci
# Only mark PHONY targets for names that have file system conflicts:
.PHONY: build install test ci gate-run-test gate-run-ci gate-status \
gambitcheck gambitkernel gambiteval gambitseed gambitweb gambitprofile \
gambitgen gambitgencheck gambitseedcheck grenadinecheck \
fibersbench dynbench \
fibersresidue
default:: build
# Honor an explicit Chez or install the pinned toolchain, initialize every
# vendored dependency, and enforce Jolt's threaded-runtime requirement.
deps: submodules $(JOLT-CHEZ)
@threaded=$$(printf '(write (threaded?)) (newline)\n' | \
'$(JOLT-CHEZ)' -q 2>/dev/null | tr -d '\r'); \
test "$$threaded" = '#t' || { \
echo "Jolt requires a threaded Chez Scheme ($(JOLT-CHEZ) reported $$threaded)" >&2; \
exit 1; \
}
submodules:
@if git submodule status --recursive | grep -q '^-'; then \
git submodule update --init --recursive; \
fi
# Every target that runs Chez directly or through bin/jolt waits until deps is
# complete. This keeps direct targets and parallel aggregate gates race-free.
$(JOLT-TARGETS-NEEDING-DEPS): | deps
build: testbin
install: build
install -d '$(PREFIX)/bin'
install -m 755 target/release/jolt '$(PREFIX)/bin/jolt'
# --- the gate ---------------------------------------------------------------
#
# A gate is only worth anything if an INCOMPLETE run cannot be read as a pass.
# Three things go wrong otherwise, and all three have happened:
#
# 1. make stops at the first failing target, so every target after it never
# runs. Re-running a hand-picked subset afterwards prints per-target
# success that looks exactly like the whole gate's.
# 2. `make test | tail` reports tail's status, and the log then ENDS on some
# passing target's output — the failure scrolled past.
# 3. `make -i` exits 0 even when targets failed.
#
# So: the gate runs as a sub-make, a verdict line is printed either way (the log
# can never end on a passing target), the exit status is preserved, and a receipt
# naming the covered tree is written ONLY on a complete pass. `make gate-status`
# answers "is this working tree gated?" — which is not something to remember.
CI-GATES := submodules values corpus unit documented grenadine mvnhttp readscaling compilescaling applyscaling lazyscaling vecscaling pipescaling chunkscaling printscaling complexity ioscaling hotscaling depssmoke taskssmoke scriptsmoke completionssmoke depscpcache depsunit \
smoke tracesmoke errorreport errorkinds buildsmoke buildlibsmoke staticnativesmoke sci scifunctional cts loaderconf ffi ffidupsym continuations stdlibfasl \
transient rrbprop rrbscaling stateimage infer wp devirt fieldread numwp fieldnum fieldjoin contagion \
hasheq narrowhash \
protoret pic narrow directlink directcall arraymap arraybacking unitcontext numeric oparity mathfl flarr \
fnform coreproc traceemit traceeval degradedbacktrace \
inline inline-body dcerefs shakelocal manifestcheck readmecheck portcheck mirrordrift regexdfacheck regexdfa deadhost adaptercheck hostprops statlayout lockcheck parkcheck shelloutcheck errnocheck irvalidate seeddefs devbootsmoke \
gatebootsmoke aotcachesmoke aotcachepathsmoke aotfingerprint vfaslceiling compilepathsmoke makefilesmoke versionsmoke \
systemstreams \
certify gambitcheck gambitgencheck gambitseedcheck gambitboot grenadinecheck fibers gosm asynctimer interruptnest threadsafety flow
TEST-GATES := submodules selfhost ci
GATE-RECEIPT := target/gate-receipt
# Every tracked and every untracked-but-not-ignored file, so adding a source file
# invalidates the receipt as surely as editing one does. Hash each file's NAME and
# content, over a SORTED list: `git ls-files -c -o` groups untracked separately, so
# concatenating in its order made the hash change when a file merely went from
# untracked to tracked — committing, which changes nothing, read as "not gated".
# The submodule paths list as directories, which the per-file hash cannot read;
# their pinned SHAs come from submodule status instead, so a submodule bump
# invalidates the receipt too.
GATE-FINGERPRINT = { git ls-files -z -c -o --exclude-standard \
| LC_ALL=C sort -z | xargs -0 $(GATE-SHA) 2>/dev/null; \
git submodule status --recursive 2>/dev/null; } \
| $(GATE-SHA) | cut -d' ' -f1
GATE-SHA = $$(command -v sha256sum >/dev/null 2>&1 && echo sha256sum || echo "shasum -a 256")
# -i ignores failures outright; -k runs on past them and leaves a later target's
# success as the last thing in the log. Both turn the gate into decoration. This
# has to refuse at PARSE time: -i ignores the failure of a recipe line, including
# a guard's own, so a guard inside the recipe is itself ignored and the gate runs.
# make groups SHORT flags into the first word of MAKEFLAGS with no leading dash
# ("ik"); long options stay separate words. Read the first word only when it is
# that group, or --no-print-directory matches a search for "i".
MAKE-SHORT-FLAGS := $(filter-out -%,$(firstword $(MAKEFLAGS)))
ifneq (,$(filter test ci gate-run-test gate-run-ci,$(MAKECMDGOALS)))
ifneq (,$(findstring i,$(MAKE-SHORT-FLAGS)))
$(error make -i ignores failures, so the gate cannot fail — drop -i)
endif
ifneq (,$(findstring k,$(MAKE-SHORT-FLAGS)))
$(error make -k runs past failures, so the log's last line is not the verdict — drop -k)
endif
endif
# $(1) = gate name, $(2) = target list.
# The `+` on the sub-make hands the jobserver down; without it the sub-make runs
# -j1 and a parallel gate silently serializes.
define run-gate
@rm -f '$(GATE-RECEIPT)'
+@if $(MAKE) --no-print-directory gate-run-$(1); then \
mkdir -p target; \
{ echo "gate: $(1)"; \
echo "tree: $$($(GATE-FINGERPRINT))"; \
echo "head: $$(git rev-parse HEAD 2>/dev/null || echo none)"; \
echo "targets: $(2)"; } > '$(GATE-RECEIPT)'; \
echo "OK: $(1) gate passed ($(words $(2)) targets)"; \
else \
st=$$?; \
echo "FAILED: $(1) gate did not complete — targets after the failure never ran,"; \
echo " so nothing here is evidence the rest of the gate passes."; \
exit $$st; \
fi
endef
# Full gate (dev machine). Includes the self-host byte-fixpoint, which only holds
# on the same Chez that minted the seed.
test:
$(call run-gate,test,$(TEST-GATES))
# CI gate: behavior only. The checked-in seed is a minted artifact (like a
# lockfile) — it RUNS correctly on any Chez, but `selfhost` rebuilds it and a
# different Chez version may emit byte-different (gensym/order) output, so the
# byte-fixpoint is a dev-machine check, not a CI one (jolt-8479).
ci:
$(call run-gate,ci,$(CI-GATES))
# The prerequisite-only targets the wrappers drive. Not meant to be run directly:
# they pass silently, which is the thing the wrappers exist to prevent.
gate-run-test: $(TEST-GATES)
gate-run-ci: $(CI-GATES)
# Is THIS working tree covered by a complete gate run? A subset run leaves the
# receipt absent (the wrapper clears it) and any edit since changes the tree hash.
gate-status:
@if [ ! -f '$(GATE-RECEIPT)' ]; then \
echo "NOT GATED: no receipt — run 'make test'"; exit 1; fi
@recorded=$$(sed -n 's/^tree: //p' '$(GATE-RECEIPT)'); \
current=$$($(GATE-FINGERPRINT)); \
if [ "$$recorded" != "$$current" ]; then \
echo "NOT GATED: tree changed since the last full gate run"; \
echo " receipt: $$(sed -n 's/^gate: //p' '$(GATE-RECEIPT)') at $$(sed -n 's/^head: //p' '$(GATE-RECEIPT)')"; \
exit 1; \
fi; \
echo "GATED: $$(sed -n 's/^gate: //p' '$(GATE-RECEIPT)') gate passed on this exact tree"
# Self-host fixpoint: bootstrap.ss rebuild == checked-in seed.
selfhost:
@sh host/chez/selfcheck.sh
# Value-model unit tests (nil/truthiness/collections on Chez).
values:
@$(CHEZ) --script test/chez/values-test.ss
# The hash engine's VALUES, pinned to JVM Clojure. hasheq.ss gets tuned for speed
# (its 32-bit leaf helpers are macros so they inline), and a tuning pass that
# changes a hash VALUE rather than its cost fails nothing until a hash crosses the
# JVM boundary — so the goldens are literal here, plus a flat-vs-layered mixer
# sweep over every length and char class.
hasheq:
@$(CHEZ) --script test/chez/hasheq-test.ss
# The same suites again with the hash engine's NARROW arms selected. hasheq.ss
# and collections.ss compute in the Java int window, which is fixnum on a 64-bit
# Chez and bignum on a 32-bit one (tpb32l, the pb/WASM build), so each operator
# has a generic exact-integer twin that only a 32-bit host would otherwise ever
# run. JOLT_NARROW_HASH=1 forces that arm at expand time here, which puts the
# generic twins under the JVM-pinned hash goldens, the value-model suite and the
# transient HAMT suite on ordinary hardware. Slower than `hasheq`: the knob is
# read during expansion, so gate-boot has to compile the preamble from source.
narrowhash:
@JOLT_NARROW_HASH=1 $(CHEZ) --script test/chez/hasheq-test.ss
@JOLT_NARROW_HASH=1 $(CHEZ) --script test/chez/values-test.ss
@JOLT_NARROW_HASH=1 $(CHEZ) --script test/chez/transient-test.ss
# Fibers R1 (epic jolt-nvpr.2): the fiber primitive + single-carrier scheduler
# behind the CONTRACT.txt coroutines tier. Correctness (round trip, completion,
# per-fiber raise isolation, round-robin order, deep-stack yield) plus the
# pinned numbers (spawn < 5us, switch < 100ns, per-fiber live < 8KB by R0's
# corrected absolute-live-bytes measurement). Detection-free, no jolt boot.
# fibers-state-test.ss is the R2 dynamic-slice gate (per-fiber bindings/ns/txn;
# loads rt.ss for the real thread parameters).
# fibers-chan-test.ss is the R3 waiter-protocol gate (fiber <! / >! over the
# existing channel handlers; loads rt.ss for the real async.ss channels).
# fibers-go-test.ss is the R4 gate (epic jolt-nvpr.5): go on fibers via the
# *go-backend* opt-in (sections 1-3 through the compiler), and alts! as a wait
# set (sections 4-7 through the host seam; the :default check loads the
# overlay).
# fibers-lock-test.ss is the object-monitor gate (jolt-3a87): `locking` and the
# bare monitor-enter/monitor-exit halves across a fiber switch. A monitor is the
# one lock in the runtime that wraps user code, so neither half of locks.ss's
# premise — short regions, never spanning a park — holds for it.
# async-io-thread-test.ss is the io-thread gate (jolt-579): core.async's third
# carrier. It runs with the pool pinned to ONE carrier, which is what makes "8
# bodies parked at the same time, all of them resuming" mean that a fiber released
# its carrier rather than held it.
fibers:
@$(CHEZ) --script test/chez/fibers-test.ss
@$(CHEZ) --script test/chez/fibers-state-test.ss
@$(CHEZ) --script test/chez/fibers-chan-test.ss
@$(CHEZ) --script test/chez/fibers-go-test.ss
@$(CHEZ) --script test/chez/fibers-pool-test.ss
@$(CHEZ) --script test/chez/fibers-io-test.ss
@$(CHEZ) --script test/chez/fibers-process-io-test.ss
@$(CHEZ) --script test/chez/fibers-sm-test.ss
@$(CHEZ) --script test/chez/fibers-preempt-test.ss
@$(CHEZ) --script test/chez/fibers-lock-test.ss
@$(CHEZ) --script test/chez/fibers-monitor-test.ss
@$(CHEZ) --script test/chez/async-io-thread-test.ss
# The one (timeout ms) timer thread (jolt-pe84): a timeout closes on its own
# deadline however far away the pending ones are, and the thread is forked once.
# Both were broken by the same three lines — a sleep that left the timer off its
# condition variable dropped the wake for a nearer deadline, and a fork guard
# cleared before an idle wait forked a second immortal timer per call.
asynctimer:
@$(CHEZ) --script test/chez/async-timer-test.ss
# A nested run-interruptible extent restores the enclosing polling timer after
# normal return, exception, or interruption, without sharing ownership between
# application threads.
interruptnest:
@$(CHEZ) --script test/chez/interrupt-nesting-test.ss
# The dynamic-var binding stack (jolt-3bo): lookup cost against binding DEPTH and
# against the number of vars in one frame, push/pop throughput, and the two
# workloads the trade-off is judged on — N nested fn literals (deep) and a real
# namespace compile (wide and shallow). Opt-in, NOT part of make ci.
dynbench:
@sh bench/dyn-binding/run.sh
# Fibers R6 (jolt-nvpr.7): the :thread vs :fiber benchmark harness. Opt-in and
# NOT part of the gate — benchmarks do not belong in CI. Runs each measurement
# phase as a subprocess and prints the comparison table; like aba.sh it is
# blind to dev mode (every phase runs the runtime from source).
fibersbench:
@sh bench/fibers/run.sh
# R0-residue probes (jolt-nvpr.10): the size of a fiber with REAL frames, and
# memory under fiber churn. Opt-in like fibersbench, NOT part of make ci.
fibersresidue:
@$(CHEZ) --script bench/fibers/residue.ss
@$(CHEZ) --script bench/fibers/churn.ss
# Corpus conformance vs JVM-sourced expecteds (allowlist + floor).
corpus:
@$(CHEZ) --script host/chez/run-corpus.ss
# Host-specific unit cases.
unit:
@$(CHEZ) --script host/chez/run-unit.ss
# The jolt half of the known-divergences :documented gate: every entry's :check
# must render exactly its recorded :jolt value, its :jvm and :jolt must differ,
# and an entry with no :check fails. certify.clj runs the JVM half against
# reference Clojure; this half needs no JVM, so it lives in `ci`.
# `make documented-record` prints what jolt currently answers, for recording a
# new entry. Run `make certify` for the JVM side through the pinned oracle.
documented:
@$(CHEZ) --script host/chez/run-documented.ss
documented-record:
@$(CHEZ) --script host/chez/run-documented.ss --record
# Real-CLI smoke over bin/jolt.
# The CLI and build gates spawn a jolt process per case; a prebuilt binary boots
# ~10x faster than script mode (0.14s vs 1.5s) and builds an app ~5x faster, so
# they take this as a prerequisite. JOLT_BIN=bin/jolt forces script mode.
#
# Rebuilt only when something it bakes in is newer than the binary. It used to
# rebuild unconditionally, which is free under `make -j ci` (one shared node in
# the graph) but charged every single-gate run 18s — enough to make `make
# buildlibsmoke` slower with the prerequisite than without it. The staleness
# check covers the same inputs build-jolt.ss embeds: the runtime .ss files, the
# install roots, and the launcher stub. JOLT_FORCE_TESTBIN=1 rebuilds anyway.
TESTBIN-INPUTS := host/chez jolt-core stdlib vendor/fs/src vendor/process/src vendor/grenadine/src vendor/grenadine-generated vendor/irregex
testbin:
@if [ -n "$${JOLT_FORCE_TESTBIN:-}" ] || [ ! -x target/release/jolt ] || \
[ -n "$$(find $(TESTBIN-INPUTS) -type f -newer target/release/jolt -print -quit 2>/dev/null)" ]; then \
$(CHEZ) --script host/chez/build-jolt.ss release target/release/jolt; \
else \
echo "testbin: target/release/jolt up to date"; \
fi
smoke: testbin
@JOLT_BIN="$${JOLT_BIN:-target/release/jolt}" sh host/chez/smoke.sh
# An escaping throw names the Clojure fn, file and line it came from — including
# when TCO erased the frame — and every exception class inherits the Throwable
# method surface.
tracesmoke: testbin
@JOLT_BIN="$${JOLT_BIN:-target/release/jolt}" sh host/chez/trace-smoke.sh
# What a user READS when jolt rejects their program: message, position, ex-data,
# trace and exit status, pinned per case as golden files under test/errors. Every
# other gate asserts that a bad program is rejected; this one asserts what the
# report then says. Regenerate deliberately with:
# sh host/chez/error-report-check.sh generate
errorreport: testbin
@JOLT_BIN="$${JOLT_BIN:-target/release/jolt}" sh host/chez/error-report-check.sh
# Every diagnostic kind the sources raise is registered in
# test/conformance/error-kinds.edn, and every registered kind is still raised.
# Both directions: an unregistered kind is an error with no documented meaning,
# and a registry entry nothing raises is a doc rotting into decoration.
errorkinds:
@sh host/chez/error-kinds-check.sh
# The IR schema validator (JOLT_IR_VALIDATE) reports no problems on real code.
irvalidate:
@sh host/chez/ir-validate-smoke.sh
# Every var the checked-in seed defines exists after the seed loads. The seed's
# forms are emitted guard-wrapped so the MINT can skip one that fails to compile
# (remint.sh fails on a nonzero skip count); the same guard is in the emitted
# text, so it equally swallows a form that raises when the seed LOADS, and that
# half went unchecked -- the var never appears and every read of the name gets
# the truthy unbound sentinel (jolt#879). Run twice: the two compiler trace flags
# are read in such defs, and each run pins them against its own environment, so
# neither "always on" nor "always off" passes both arms.
seeddefs:
@$(CHEZ) --script host/chez/run-seed-defs.ss
@JOLT_WP_TRACE=1 JOLT_IR_VALIDATE=1 $(CHEZ) --script host/chez/run-seed-defs.ss
# The build-driving gates take testbin for the same reason smoke and cts do,
# only more so: a `jolt build` costs ~2.5s through the prebuilt binary and
# ~12.5s through the source-mode driver, and buildsmoke alone drives 26 of them
# (343s -> 78s measured). Under `make -j ci` the one testbin build is shared
# with smoke/cts/aotcachesmoke. buildsmoke keeps an explicit bin/jolt build at
# the end so the source-mode driver stays gated; JOLT_BIN=bin/jolt forces the
# whole gate back to script mode.
# `jolt build` produces a working standalone binary.
buildsmoke: testbin
@JOLT_BIN="$${JOLT_BIN:-target/release/jolt}" sh host/chez/build-smoke.sh
# `jolt build --library` produces a shared object callable from C/C++/Rust.
buildlibsmoke: testbin
@JOLT_BIN="$${JOLT_BIN:-target/release/jolt}" sh host/chez/build-lib-smoke.sh
# `jolt build` cc-links a :jolt/native :static archive into the binary (the
# default), and --dynamic keeps the runtime load-shared-object path.
staticnativesmoke: testbin
@JOLT_BIN="$${JOLT_BIN:-target/release/jolt}" sh host/chez/static-native-smoke.sh
# Duplicate native symbol detection (issue #731): a declared :jolt/native that
# carries its own static copy of another's code — raygui linked against
# libraylib.a — used to go inert with no error. Pins that the footgun build is
# reported AND that a correctly linked one is not.
ffidupsym:
@sh host/chez/ffi-duplicate-symbol-smoke.sh
# OPT-IN: jolt.mvn-http cert-verifying HTTPS fetch against Central + Clojars.
# Not in `make test` — needs network + a working system OpenSSL.
httpsfetch:
@sh host/chez/https-fetch-smoke.sh
# OPT-IN: replay third-party libraries' own clojure.test suites and compare the
# tallies against test/conformance/libs/manifest.edn. Not in `make test` — needs
# the upstream library checkouts ($JOLT_CONFORMANCE_LIBS, default
# ../conformance-libraries), which are not vendored. Skips cleanly without them.
# `make libconformance LIBS="malli honeysql"` runs a subset.
# See test/conformance/libs/README.md.
libconformance: testbin
@JOLT_BIN="$${JOLT_BIN:-target/release/jolt}" \
JOLT_NO_USER_DEPS=1 target/release/jolt run test/conformance/libs/run.clj $(LIBS)
# jolt.mvn-http pure-function tests (URL/redirect/header/body parsing). No
# network, no OpenSSL — runs in the default gate.
mvnhttp:
@bin/jolt run test/mvn_http_test.clj
# Reading a source file form by form off a java.io reader must cost time LINEAR
# in the source. It was quadratic until v0.7.7 (37s to read clojure/core.clj,
# against the JVM's 0.06s) and nothing caught it, because the corpus rows about
# reading are all about the VALUES a read produces. Asserts the 1x-vs-4x ratio
# measured inside ONE process, so it judges the shape and not the machine.
# Takes the built binary: script mode would measure the same ratio far slower.
readscaling: testbin
@JOLT_NO_USER_DEPS=1 target/release/jolt run test/read_scaling_test.clj
# Compiling a namespace stays linear in its source, and a quoted form does not
# cost dramatically more than the construction it is. The second half is not
# implied by the first: a per-form cost regression is linear, just linear and
# slow, and one shipped green through the whole gate (see the file).
compilescaling: testbin
@JOLT_NO_USER_DEPS=1 target/release/jolt run test/compile_scaling_test.clj
# apply must stream a variadic's rest, not materialize it: guards the
# jolt-register-variadic! registration on the native + - * / min max and the
# comparison chains (host/chez/seq.ss). Without it (apply max (range)) realizes
# an unbounded seq until the process dies.
applyscaling: testbin
@JOLT_NO_USER_DEPS=1 target/release/jolt run test/apply_scaling_test.clj
# Lazy realization costs the same whether or not a thread has ever existed: a
# cell publishes its forced tail through one word and reads it lock-free, and the
# once-only mutex is borrowed for the force, never kept per cell. The ratio of one
# workload timed before and after a thread has existed, in ONE process, is the
# judge; a per-cell mutex reads ~5 there (every collection visits a million
# finalized objects), the claim design ~1.5. Also races eight walkers over
# shared unrealized seqs and checks every producer ran exactly once.
lazyscaling: testbin
@JOLT_NO_USER_DEPS=1 target/release/jolt run test/lazyseq_mt_scaling_test.clj
# (into vec vec) and subvec stay O(log n) through core — the raw pvec ops have
# rrbscaling; this catches core falling back to an element-by-element rebuild.
vecscaling: testbin
@JOLT_NO_USER_DEPS=1 target/release/jolt run test/vec_scaling_test.clj
# A piped-stream write costs O(1), not O(chunks-queued): the jpipe buffer was a
# plain list re-copied per write, so piping N chunks cost O(N^2) cons work under
# the pipe mutex (the ring.util.io/piped-input-stream path). 1x-vs-4x in-process
# ratio, like readscaling.
pipescaling: testbin
@JOLT_NO_USER_DEPS=1 target/release/jolt run test/pipe_scaling_test.clj
# chunk-append (the clojure.core chunk-builder API) costs O(1) amortized per
# item, not O(items-buffered) — filling a chunk-buffer was O(n^2) via a
# re-copied item list, and the cap argument was ignored.
chunkscaling: testbin
@JOLT_NO_USER_DEPS=1 target/release/jolt run test/chunk_scaling_test.clj
# Printing a collection costs O(printed length): jolt-str-join was a right-fold
# of string-append (each element re-copied the whole joined suffix), on every
# pr-str/str/prn of any collection, record, or sorted coll.
printscaling: testbin
@JOLT_NO_USER_DEPS=1 target/release/jolt run test/print_scaling_test.clj
# Operations that must not be linear in the collection's size: count/drop on a
# vector-backed seq, rseq, first on a sorted collection. Every one of these was
# O(n) here while the reference answers from the shape, and none of it is visible
# to a value test. Judged as a shape (n vs 4n), not an absolute time.
complexity: testbin
@JOLT_NO_USER_DEPS=1 target/release/jolt run test/complexity_test.clj
# Draining *in* / with-in-str by lines or forms costs O(input), not
# O(input x items): the IReader buffer atoms re-copied (and read re-parsed) the
# whole remaining input per item until the [string offset] cursor rework.
ioscaling: testbin
@JOLT_NO_USER_DEPS=1 target/release/jolt run test/io_scaling_test.clj
# The 2026-08 sweep's remaining hot-path shapes in one gate: split-with-limit,
# core.async timeout arming, ArrayDeque/StringTokenizer draining, ns-publics/
# refer var-table independence, set/intersection smaller-side walk.
hotscaling: testbin
@JOLT_NO_USER_DEPS=1 target/release/jolt run test/hotpath_scaling_test.clj
# deps.edn alias + CLI semantics (tools.deps args-map keys, -X/-T/-Sdeps, the
# user deps.edn chain, jar/git coordinates) through the real CLI, over local
# fixture projects in test/chez/deps-alias/. Offline.
depssmoke: testbin
@JOLT_BIN="$${JOLT_BIN:-target/release/jolt}" sh host/chez/deps-alias-smoke.sh
# bb.edn / deps.edn :tasks through the real CLI: babashka task semantics
# (:depends, :init, :requires, :enter/:leave, :private, :extra-paths/:extra-deps,
# the babashka.tasks API), the `tasks` listing, and exit-code propagation.
# Offline fixture projects in test/chez/tasks/.
taskssmoke: testbin
@JOLT_BIN="$${JOLT_BIN:-target/release/jolt}" sh host/chez/tasks-smoke.sh
# Running a FILE as a script through the real CLI, including a
# `#!/usr/bin/env jolt` shebang executed by the kernel: the shebang line as a
# comment, *command-line-args*, *file*, stdin, exit-code propagation, a project's
# roots, -f/--file, and which of a file, a command and a task wins a name.
# Offline, throwaway projects in a temp dir.
scriptsmoke: testbin
@JOLT_BIN="$${JOLT_BIN:-target/release/jolt}" sh host/chez/script-smoke.sh
# `jolt completions`: the name/doc lines a completing shell asks for, and the
# zsh/bash/fish snippets it installs — parsed by their own shells, and the bash
# one actually run against a project to see what it offers.
completionssmoke: testbin
@JOLT_BIN="$${JOLT_BIN:-target/release/jolt}" sh host/chez/completions-smoke.sh
# The resolved-roots cache (.jolt/cpcache): a warm run reuses a project's final
# dependency resolution instead of re-expanding the graph. Offline throwaway
# project in a temp dir; gates the cache key, invalidation, and dev posture.
depscpcache: testbin
@JOLT_BIN="$${JOLT_BIN:-target/release/jolt}" sh host/chez/deps-cpcache-smoke.sh
# Shared Grenadine dependency-expansion integration tests: exclusions, version
# selection, orphan cutting, and the Maven version comparator, driven through
# a fake coordinate type. The cases are ported from tools.deps. Offline.
depsunit:
@JOLT_NO_USER_DEPS=1 bin/jolt run test/deps_expand_test.clj
# Vendored Grenadine core plus Jolt's effective-POM adapter. Offline.
grenadine:
@JOLT_NO_USER_DEPS=1 bin/jolt run test/grenadine_test.clj
# Build jolt as a self-contained native binary into target/<profile>/jolt. The
# binary bundles the runtime, compiler, jolt-core + stdlib source, the Chez boots,
# and a launcher stub, so it runs AND compiles jolt apps with no Chez or cc on the
# machine. Built on a dev/CI host that HAS Chez + cc. release = optimize-level 3,
# no inspector info, compressed; debug = optimize-level 0 + inspector + debug info.
# JOLT_CROSS_TARGET (optional) cross-compiles jolt for another Chez machine — it is
# passed as build-jolt.ss's 3rd arg and needs $JOLT_TARGET_PACK (empty = native).
jolt-release:
@$(CHEZ) --script host/chez/build-jolt.ss release target/release/jolt $(JOLT_CROSS_TARGET)
jolt-debug:
@$(CHEZ) --script host/chez/build-jolt.ss debug target/debug/jolt
# Re-mint the seed first so the embedded compiler image is current, then both builds.
jolt: selfhost jolt-release jolt-debug
@echo "OK: target/release/jolt and target/debug/jolt built"
# Self-build smoke: the distributed jolt compiles an app with Chez + cc removed.
joltsmoke:
@sh host/chez/jolt-selfbuild-smoke.sh
# The embedded per-namespace stdlib fasls (R1): assert that a built jolt serves
# clojure.test (and jolt.time) from the compiled fasl blob rather than
# recompiling from source, that the aot-info line fires (non-vacuous), and that a
# real deftest + LocalDate expression run through the binary. Depends on
# jolt-release having run.
stdlibfasl: testbin
@sh host/chez/stdlib-fasl-smoke.sh
# SCI conformance: load borkdude/sci's source through jolt (floor-gated).
sci:
@$(CHEZ) --script host/chez/run-sci.ss
# A complementary functional gate: load SCI through Jolt's ordinary dependency
# path, then initialize and reuse real contexts. run-sci.ss remains the broad,
# intentionally lenient source-loading compatibility gate.
scifunctional: testbin
@JOLT_NO_USER_DEPS=1 target/release/jolt -Sdeps '{:deps {borkdude/sci {:local/root "vendor/sci"}}}' run test/chez/sci-functional-test.clj
# clojure-test-suite conformance: run the vendored jank-lang/clojure-test-suite
# per-namespace under jolt, gated on the per-namespace baseline
# (test/chez/cts-known-failures.txt).
cts: testbin
@JOLT_BIN="$${JOLT_BIN:-target/release/jolt}" bash host/chez/cts.sh
# The loader conformance suite: the twelve cases that specify jolt.loader (roots
# per context, isolation, delegation policy, unload). Baselined like certify —
# a case that regresses fails, and a case that starts passing fails until the
# baseline records it, so nothing green quietly goes red again.
#
# Through the BUILT binary, like cts and the other CLI gates: a loader that only
# works against the source tree is not a loader, and jolt.loader ships in the
# stdlib fasl, so the binary is the arrangement the cases have to hold under.
loaderconf: testbin
@JOLT_BIN="$${JOLT_BIN:-target/release/jolt}" sh host/chez/loaderconf.sh
# FFI: bind native functions (typed foreign-procedure), memory, and that a
# :blocking call is collect-safe (a parked thread doesn't pin the collector).
# The widths gate covers the exact scalar vocabulary across both halves of the
# API: runtime memory access and compiler-emitted procedures/callables, :bool
# included — the one type whose value, not just width, converts at the boundary.
# The layout gate compares declarative struct metadata and field access against
# C; the aggregate gate covers structs passed and returned by C value; the native
# error gate covers atomic errno/GetLastError capture and option composition.
# The arena gate covers the allocation-lifetime API (issue #799) and the rest of
# the babashka.ffi-compatible surface built on it — the four arena kinds and who
# closes each, arena-owned blocks/strings/callbacks/views, the pointer
# vocabulary, layout-shaped read and write, places, and the typed array moves.
ffi:
@$(CHEZ) --script test/chez/ffi-binding-test.ss
@sh test/chez/ffi-widths-test.sh "$(CHEZ)"
@sh test/chez/ffi-layout-test.sh "$(CHEZ)"
@sh test/chez/ffi-aggregate-test.sh "$(CHEZ)"
@bin/jolt run test/chez/jolt-ffi-scoped-test.clj
@bin/jolt run test/chez/jolt-ffi-arena-test.clj
@sh test/chez/ffi-native-error-test.sh "$(CHEZ)"
# Escape continuations (jolt.continuations, issue #736): the one-shot contract
# call-cc/letcc expose, what unwinds on an escape, that a park inside ONE fiber
# is not an ownership boundary, and the four misuses. The cross-fiber rows are
# why this gate exists — unguarded, invoking an escape captured on another
# fiber hangs the process rather than raising, so each of those rows runs on a
# watchdog thread and FAILS on a deadline instead of wedging the run.
continuations:
@bin/jolt run test/chez/continuations-test.clj
# clojure.core.async.flow: the graph end to end (start/pause/resume/ping/inject,
# the error channel, casts) and the j.u.c seams under it — deref of a Future,
# ExecutionException out of .get, instance? Executor, and the workload -> carrier
# mapping. The scale row is the one that would regress silently: :io processes
# run on fibers, so 200 of them do not need 200 threads.
flow:
@bin/jolt run test/chez/flow-test.clj
# Transients: mutable backing, snapshot on persistent!, and linear-time builds.
transient:
@$(CHEZ) --script test/chez/transient-test.ss
# RRB vector: catvec/slice against a list model with structural invariants
# (seeded sequences, seed printed on failure), and the n-vs-4n complexity
# ratio measured in one process.
rrbprop:
@$(CHEZ) --script test/chez/rrb-property-test.ss
rrbscaling:
@$(CHEZ) --script test/chez/rrb-scaling-test.ss
# State images: value-graph round-trip through jolt.image, plus the Chez fasl
# behaviour the format assumes (machine-independence, what fasl refuses).
stateimage:
@$(CHEZ) --script test/chez/state-image-test.ss
# Inference / success-type checking: drive jolt.passes.types directly and assert
# diagnostic counts + collected calls/escapes (the optimization pass the other
# gates don't exercise).
infer:
@$(CHEZ) --script host/chez/run-infer.ss
# Whole-program param-type fixpoint: record types flowing across fn boundaries
# (a callee's param picks up its callers' ctor return types), the foundation the
# bare-index field reads + protocol devirtualization build on.
wp:
@$(CHEZ) --script host/chez/run-wp.ss
# Protocol-call devirtualization: a monomorphic call resolves its impl by the
# inferred record tag (find-protocol-method) instead of routing through the
# protocol var; the result must match ordinary dispatch.
devirt:
@$(CHEZ) --script host/chez/run-devirt.ss
# Fibers R7 (jolt-nvpr.9): the CPS pass over a go body. Asserts WHICH
# representation each park site got (on the expansion, and on the cheap-park vs
# capture counters), that the fallbacks still work, and that both backends give
# the same values.
gosm:
@$(CHEZ) --script host/chez/run-gosm.ss
# The runtime's shared side-tables under concurrent access (jolt-3907). Scenario 1
# is a reproducer: with the hasheq caches shared instead of per-thread it faults
# inside the collector on most runs. test/chez/thread-tables.clj (smoke.sh) covers
# the same bug class through a core.async pipeline sweep.
threadsafety:
@$(CHEZ) --script test/chez/thread-safety-test.ss
# Native record field reads: a keyword lookup on a statically-known record reads
# the field by its declared slot (jrec-field-at) instead of jolt-get; the value
# must match, and a non-field key / default-arg form keeps the generic path.
fieldread:
@$(CHEZ) --script host/chez/run-fieldread.ss
# Inline method body field-read gate: when the optimize pipeline re-infers
# defrecord/deftype inline method bodies with the receiver typed, field reads
# must emit jrec-field-at (bare index) instead of jolt-get.
inline-body:
@$(CHEZ) --script host/chez/run-inline-body.ss
# DCE reference collection (dce.ss): an app form's refs must union an IR walk
# (:var/:the-var nodes) with a text scan of the emitted Scheme, so a macro-spliced
# (var-deref "ns" "nm") with no :var node still roots its target. Pins both halves.
dcerefs:
@$(CHEZ) --script host/chez/run-dce-refs.ss
# Hintless whole-program double inference: a fn whose every call site passes a
# flonum has its param typed :double by the closed-world fixpoint and unboxed to
# fl-ops with no ^double hint; an integer caller leaves it generic, an escaped fn
# keeps :any.
numwp:
@$(CHEZ) --script host/chez/run-numwp.ss
# Mandelbrot count-point hot loop: whole-program fixpoint must seed cr/ci as
# :double from caller type, and the numeric pass must emit fl-ops with zero
# jolt-n* generic arith in the double arithmetic path.
mandelbrot-num:
@$(CHEZ) --script host/chez/run-mandelbrot-num.ss
# Double record fields: a ^double-tagged field reads back as a flonum (coerced at
# construction and set!), so hintless arithmetic over those fields unboxes to fl-ops.
fieldnum:
@$(CHEZ) --script host/chez/run-fieldnum.ss
# Whole-program record field-type inference: wp-infer! joins the ctor-argument types
# across every (->Ctor ...) site to derive each field's type — all-flonum -> :double
# (reads unbox, protocol-method return concretizes, caller accumulator goes fl+);
# record-or-nil -> nilable record (guarded reads narrow to the direct accessor);
# conflicting/escaping/mutable/map-> -> :any. Portable hint-free code now reaches the
# same emission the ^double hint reaches above.
fieldjoin:
@$(CHEZ) --script host/chez/run-fieldjoin.ss
# Devirt-gated fl* contagion for :num record fields: a :num field read
# beside a proven :double operand contagion-coerces (exact->inexact) and lowers to
# fl* in a specialized clone resolved only at devirtualized call sites — recovering
# the win Option B gave up without touching the megamorphic/PIC regime. Pins the
# types contagion-specialize API (the invariant: contagion fires only beside a
# proven :double; a pure-:num body stays generic) and the runtime clone registry.
contagion:
@$(CHEZ) --script host/chez/run-contagion.ss
# Protocol-method return inference: a method whose impls all return the same record
# type has a monomorphic return, so a (method recv ..) call types as that record and
# a field read off the result bare-indexes; a disagreeing impl keeps the generic path.
protoret:
@$(CHEZ) --script host/chez/run-protoret.ss
# Protocol-dispatch polymorphic inline cache: a protocol call the inference tags
# :proto/:method but can't prove monomorphic emits a per-site cache keyed on the
# receiver's descriptor identity (eq? scan + a global epoch guard). Pins the
# emission, megamorphic correctness across record types, and that an extend-type at
# runtime invalidates the cache (the epoch bump) so the new impl is served.
pic:
@$(CHEZ) --script host/chez/run-pic.ss
# Under tracing, the tail-frame ring save/restore goes around calls that can push a
# rib and nowhere else. A site lowering to inline Chez primitives (a proven aget is
# one flvector-ref) can never reach a fn prologue, and wrapping it let-binds the
# result across the restore — which re-boxes an unboxed flonum and cost 19x on an
# array loop. Gates both directions: no wrapper on the primitive branches, wrapper
# still present on the ones that really apply a fn.
traceemit:
@$(CHEZ) --script host/chez/run-trace-emit.ss
# trace-r2: an eval-path (cache-miss) frame carries a source object, and its
# (source-name . offset) resolves to the original clj line via the eval marker
# registry; with tracing off the eval path registers nothing.
traceeval:
@$(CHEZ) --script host/chez/run-traceeval.ss
# PSL R6: with introspection suppressed (sa-introspect-enabled? #f), a throw
# still surfaces type+message while the walker entry points return empty and
# the backtrace renders without continuation frames.
degradedbacktrace:
@$(CHEZ) --script host/chez/run-degraded-backtrace.ss
# Nilable record types + flow-sensitive narrowing: a record-or-nil types as a nilable
# record (some?/nil? don't fold, so a runtime guard stays); inside (if (some? x) ..)
# the then-branch narrows x to non-nil, so its field reads bare-index and unbox.
narrow:
@$(CHEZ) --script host/chez/run-narrow.ss
# The direct call shapes of a --direct-link build: seed vars called through a
# load-bound root, the unhinted string/keyword interop guard, the unchecked
# family lowered to its helpers, case on interned constants (run-directcall.ss).
directcall:
@$(CHEZ) --script host/chez/run-directcall.ss
# Array-mode maps are one flat k/v slot vector (PersistentArrayMap), their
# transients a slot buffer, their seq views vector-backed (test/chez/arraymap-test.ss).
arraymap:
@$(CHEZ) --script test/chez/arraymap-test.ss
# Array backings: which Chez vector type each element kind stores its elements
# in (fxvector / bytevector / flvector / boxed vector), the fixnum-range
# widening, and that a boxed array of a typed kind still behaves.
arraybacking:
@$(CHEZ) --script test/chez/array-backing-test.ss
# Direct-linking emission: a closed-world build binds top-level app defs to jv$
# Scheme bindings and routes app->app calls/refs to them, skipping var-deref +
# jolt-invoke; ^:dynamic/^:redef and nested defs opt out.
directlink:
@$(CHEZ) --script test/chez/directlink-test.ss
# Unique anon-fn letrec names + source-form registration (R1): a user-ns anon
# literal registers jfn$<ns>$<def>$<n> -> {form, ns, free-names} and the live
# closure's inspector name must agree; system-ns closures stay unregistered.
fnform:
@$(CHEZ) --script test/chez/fnform-test.ss
# Every clojure.core fn must be nameable in value position: the state image
# writes a procedure as its var NAME, and a native that is set!-extended after
# its def-var! leaves a procedure nothing named — so values built from it stop
# being writable, silently. Swept from the var table, so a fn added later is
# covered without anyone remembering.
coreproc:
@$(CHEZ) --script test/chez/core-proc-name-test.ss
# Compilation-unit context: the emit-session state (mode flags, direct-link
# registries, ctor shapes, gensym, cache cells) is per-unit, so two units are
# isolated (reentrant) and a flag set under one never leaks into another.
unitcontext:
@$(CHEZ) --script test/chez/unit-context-test.ss
# Every numeric fast-path op at every arity it admits, derived from op-registry:
# the specialized form compiles, agrees with the generic path, and actually emits
# its specialization. op-registry names a proc per kind without saying what arity
# that proc takes, so this is what pins the two together.
oparity:
@$(CHEZ) --script test/chez/op-arity-test.ss
# Hint-directed fast arithmetic: ^double/^long param hints (and float literals)
# lower arithmetic to Chez fl*/fx* ops; un-hinted integer code stays generic.
numeric:
@$(CHEZ) --script test/chez/numeric-test.ss
# java.lang.Math over proven flonum operands lowers to the native Chez flonum op
# (flsqrt/flatan/…), result typed :double so flonum contagion holds; an untyped
# arg or an all-integer Math/abs stays the generic string-keyed host-static-call.
mathfl:
@$(CHEZ) --script host/chez/run-mathfl.ss
# (aget ^doubles a i): a primitive-array param hint lowers aget to an unboxed
# flvector-ref (jolt-flaget) typed :double, so surrounding arithmetic unboxes to
# fl+; an untyped aget stays the native jolt-nth.
flarr:
@$(CHEZ) --script host/chez/run-flarr.ss
# IR inlining: a small single-arity defn is spliced at call sites (under optimize
# + direct-link, closed-world guarantee), with ^double/^long entry/return
# coercions carried through via :coerce nodes.
inline:
@$(CHEZ) --script test/chez/inline-test.ss
# Tree-shake soundness: build example apps (incl. deps.edn git-lib apps) default vs
# --tree-shake and require identical output. Slow (two builds per app); not in the
# default gate. Skips without the examples repo / Chez kernel dev files.
shakesmoke: testbin
@JOLT_BIN="$${JOLT_BIN:-target/release/jolt}" sh host/chez/tree-shake-smoke.sh
# The no-git-dep tree-shake correctness fixtures only (ns-publics/defonce/
# data-reader apps under test/chez) — build in seconds, no examples repo needed,
# so they run in `make test`/ci. The git-dep apps stay in the manual shakesmoke.
shakelocal: testbin
@SHAKESMOKE_SCOPE=local JOLT_BIN="$${JOLT_BIN:-target/release/jolt}" sh host/chez/tree-shake-smoke.sh
# Runtime load-manifest drift guard: cli.ss (the live entry) and bootstrap.ss
# (the seed rebuilder's reduced set) hand-mirror build.ss's bld-runtime-manifest;
# this diffs them so a load added to one but not the other fails the gate.
manifestcheck:
@sh host/chez/manifest-check.sh
# README truncation guard. Tools that feed the README to a model cut it at a byte
# budget (15000 is a common default) with no marker, so anything past the cut is
# not "further down the page", it is absent. When "Differences from Clojure" sat
# at the 19KB mark a reader saw the java.* shims, never saw what they are not,
# and reasoned about JVM daemon threads for the rest of a task. So the section
# has to END inside the budget, not begin near it. 12000 leaves room for a 12KB
# cut and for the section to grow.
README-DIFF-CEILING := 12000
readmecheck:
@end=$$(LC_ALL=C awk '/^## Differences from Clojure$$/ {seen=1; next} \
seen && /^## / {print off; found=1; exit} \
{off += length($$0) + 1} \
END {if (!found) print (seen ? off : -1)}' README.md); \
if [ "$$end" -lt 0 ]; then \
echo "readmecheck: README.md has no '## Differences from Clojure' section" >&2; exit 1; fi; \
if [ "$$end" -gt $(README-DIFF-CEILING) ]; then \
echo "readmecheck: 'Differences from Clojure' ends at byte $$end, past the $(README-DIFF-CEILING) ceiling." >&2; \
echo " A truncating fetch would cut it. Move it earlier or shorten what precedes it;" >&2; \
echo " contributor-facing prose belongs in CONTRIBUTING.md." >&2; exit 1; fi; \
for f in CONTRIBUTING.md llms.txt; do \
[ -f "$$f" ] || { echo "readmecheck: $$f is missing (README links to it)" >&2; exit 1; }; \
done; \
echo "readmecheck: divergences end at byte $$end (ceiling $(README-DIFF-CEILING))"
# PSL R1 portability lint gate: fails when a blocklisted Chez-only identifier
# appears in a host file that is not allowlisted for it (and on stale allowlist
# lines, and on any line naming a non-target-owned file). Allowlist seeded from
# current reality; the R10 end state is the two target-owned files.
portcheck:
@sh host/chez/portability-check.sh
# The two hand-mirrored host file pairs (chez/rt.ss <-> gambit/rt-core.ss and the
# two hasheq.ss) must not drift: a procedure defined in BOTH has to be identical
# unless mirror-drift-allowlist.txt records the split as deliberate. Everything
# else the hosts share is generated and gated by its own generator check; these
# two pairs were kept in step by hand with nothing watching. Compares read
# DATA, so reformatting is not drift, and a stale allowlist line fails too.
mirrordrift:
@sh host/chez/mirror-drift-check.sh
mirrordrift-regen:
@sh host/chez/mirror-drift-check.sh --regen
# host/chez/regex-dfa.ss is a COPY of one vendored irregex procedure with two
# deliberate changes. Fails when the submodule's original has moved on, so a
# vendor bump cannot leave jolt shadowing a stale copy in silence.
regexdfacheck:
@sh host/chez/regex-dfa-check.sh
regexdfacheck-regen:
@sh host/chez/regex-dfa-check.sh --regen
# The DFA work budget itself (#945): the pattern that took ~5s / never finished
# takes the backtracker, a small one still gets a DFA, and the two engines agree
# on every match. Deterministic — which engine a pattern got — not a clock.
regexdfa:
@$(CHEZ) --script test/chez/regex-dfa-test.ss
# Top-level host procedures nothing calls. A definition whose last caller went
# away is still compiled into every binary and still copied forward into the
# Gambit half by gen-records.ss — four of the first batch were dead in BOTH
# copies for exactly that reason. Counts a name referenced when it appears as a
# token anywhere OUTSIDE a comment, string bodies included (the backend emits
# calls as text), or when an identifier inside a string literal is its stem and
# the tail is all digits: (str "jolt-ffi-varargs-proc" k) reaches proc0..proc3,
# and a gate without that rule would have deleted every varargs FFI binding.
deadhost:
@sh host/chez/dead-host-check.sh
census:
@sh host/chez/portability-check.sh --census