Repository navigation
Expand file tree
/
Copy pathMakefile
More file actions
1946 lines (1716 loc) · 103 KB
/
Copy pathMakefile
File metadata and controls
1946 lines (1716 loc) · 103 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
# bionic (Android's libc) is not readable off a machine tag — Android builds as
# tarm64le, the same tag as glibc arm64 Linux — so ask the compiler, as
# build.ss's bld-bionic? does. makes' provisioning assumes glibc twice over
# (xPack GCC, Chez's hard-link install), so bionic provisions its own below.
JOLT-BIONIC := $(if $(findstring android,$(shell cc -dumpmachine 2>/dev/null)),1,)
ifneq (,$(JOLT-CHEZ))
SHELL-DEPS += $(JOLT-CHEZ)
else ifeq (1,$(JOLT-BIONIC))
$(eval $(call include-local))
# The pinned release, built with the host compiler and staged without Chez's
# hard-link install — host/chez/bionic-provision-chez.sh says why. The prefix
# is makes' own layout, so bin/jolt's .cache/local glob finds it.
CHEZSCHEME-VERSION ?= $(or $(shell sed -n 's/^CHEZSCHEME-VERSION ?= *//p' $M/chezscheme.mk | head -n1),10.4.1)
CHEZSCHEME-DIR := csv$(CHEZSCHEME-VERSION)
CHEZSCHEME-LOCAL := $(LOCAL-ROOT)/chezscheme-$(CHEZSCHEME-VERSION)
CHEZSCHEME := $(CHEZSCHEME-LOCAL)/bin/scheme
PETITE-CHEZSCHEME := $(CHEZSCHEME-LOCAL)/bin/petite
$(CHEZSCHEME):
$Q sh host/chez/bionic-provision-chez.sh '$(CHEZSCHEME-LOCAL)' '$(CHEZSCHEME-VERSION)'
SHELL-DEPS += $(CHEZSCHEME)
JOLT-CHEZ := $(CHEZSCHEME)
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 zipmemory lazyscaling \
devbootsmoke devirt directlink ffi fibers fiberspoll 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 libperf mandelbrot-num mathfl mvnhttp defmetacells staticsite gcpolicy stacklimit lazyretain \
deadhost recordshadow mirrordrift mirrordrift-regen regexdfacheck regexdfacheck-regen regexdfa regexanchor regexanchorprims regexanchorcheck regexsyntax \
hostarity narrow narrowhash numeric numwp oparity pic protoret printperf remint sbperf sci selfhost shakelocal \
traceemit vfaslceiling \
shakesmoke smoke sortedcoll staticnativesmoke stateimage test testbin transient unit unitcontext zlibregistersmoke zlibnativesmoke noexecsmoke \
threadsafety values wp ci
# Only mark PHONY targets for names that have file system conflicts:
.PHONY: build install test ci bionic-ci gate-run-test gate-run-ci gate-run-bionic-ci gate-status hooks attributioncheck noticecheck monitormodel \
gambitcheck gambitkernel gambiteval gambitseed gambitweb gambitprofile \
gambitgen gambitgencheck gambitseedcheck gambitunbound gambitunbound-regen \
gambitvars gambitvars-regen gambitstatics gambitstatics-regen gambittwins 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 recordinline corpus unit documented grenadine clishim mvnhttp readscaling gcpolicy stacklimit footprint lazyretain compilescaling applyscaling lazyscaling vecscaling pipescaling chunkscaling printscaling complexity ioscaling hotscaling fastpathratio depssmoke taskssmoke scriptsmoke exitwait completionssmoke depscpcache depsunit \
smoke tracesmoke errorreport errorkinds buildsmoke buildlibsmoke staticnativesmoke zlibregistersmoke sci scifunctional cts loaderconf ffi ffidupsym ffiloadfail continuations stdlibfasl zlibunit depsnounzip zlibnativesmoke zipmemory noexecsmoke \
transient sortedcoll rrbprop rrbscaling stateimage infer wp devirt fieldread numwp fieldnum fieldjoin contagion \
hasheq narrowhash \
protoret accfix pic narrow directlink directcall defmetacells staticsite arraymap arraybacking largebytesgc largefxgc unitcontext numeric oparity mathfl flarr \
fnform coreproc traceemit traceeval degradedbacktrace \
inline inline-body dcerefs shakelocal manifestcheck readmecheck portcheck mirrordrift regexdfacheck regexdfa regexanchor regexanchorprims regexanchorcheck regexreplace regexsyntax deadhost recordshadow adaptercheck hostprops normalizecheck hostregistry hostarity foreignhandles dispatchalloc hostsite regexmatcher winpath winplatform winparity statlayout lockcheck parkcheck shelloutcheck errnocheck irvalidate seeddefs devbootsmoke \
gatebootsmoke aotcachesmoke aotcachepathsmoke aotfingerprint vfaslceiling vfaslstrings buildscaling buildnatives buildsegments compilepathsmoke makefilesmoke versionsmoke attributioncheck noticecheck \
systemstreams userdirpaths utf8decode \
certify gambitcheck gambitkernel gambitgencheck gambitseedcheck gambitboot gambiteval gambitunbound gambitvars gambitstatics gambittwins gambitprofile grenadinecheck fibers fiberspoll gosm asynctimer interruptnest monitorescape windescape threadsafety cas 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 CI gate on bionic (Android/Termux, #943), which tests.yml runs inside
# termux/termux-docker through ci/termux-build.sh. It is now the CI gate: the
# ten gates that used to be skipped here (jolt-rcz9) all pass on bionic — the
# charset layer finds libiconv, the provisioned kernel is PIC, CTS matches its
# baseline, and the fixtures that asserted off-Android behavior branch on the
# platform.
bionic-ci:
$(call run-gate,bionic-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)
gate-run-bionic-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
# Record predicates/accessors/mutators/constructors are open-coded: the
# define-record-type in scheme-adapter-runtime.ss binds them as syntax over the
# constant rtd. The gate pins the expansion shape and the semantics kept.
recordinline:
@$(CHEZ) --script test/chez/record-inline-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.
# fibers-interrupt-test.ss is the interrupt gate: jolt.fibers/interrupt! raises
# a throwable in another fiber wherever it is -- spinning, parked on a channel or
# a deref, a CPS'd go body -- and an abandoned channel wait swallows no value.
# 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.
# The WSAPoll backend jolt.io-poller uses on Windows, run on POSIX: with
# JOLT_IO_POLLER=poll the poller is the poll(2) one, which is the same code over
# the same contract (native/c-poll is poll there and WSAPoll on Windows). So the
# Windows backend's parking, wake channel, cancel and timed waits are exercised
# by every POSIX gate run, not only on a Windows runner.
fiberspoll:
@JOLT_IO_POLLER=poll $(CHEZ) --script host/chez/cli.ss test/chez/poll-backend-active.clj
@JOLT_IO_POLLER=poll $(CHEZ) --script test/chez/fibers-io-test.ss
@out="$$(JOLT_IO_POLLER=poll $(CHEZ) --script host/chez/cli.ss test/chez/socket-test.clj 2>&1)"; \
if printf '%s' "$$out" | grep -q 'SOCKET-TEST OK'; then echo "fiberspoll: socket-test OK"; \
else printf '%s\n' "$$out" | tail -5; echo "fiberspoll: socket-test FAILED"; exit 1; fi
@JOLT_IO_POLLER=poll $(CHEZ) --script host/chez/cli.ss test/chez/win-parity-smoke.clj
@JOLT_IO_POLLER=poll $(CHEZ) --script host/chez/cli.ss test/chez/poller-registration.clj
@JOLT_IO_POLLER=poll $(CHEZ) --script host/chez/cli.ss test/chez/poller-retirement.clj
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/fibers-interrupt-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
# An interrupt (run-interruptible's escape) swept across every check point of a
# monitor enter/body/exit, Object.wait, and jolt-with-mutex never leaves the
# monitor or the mutex held, nor this thread's counted-lock depth raised.
monitorescape:
@$(CHEZ) --script test/chez/monitor-escape-test.ss
# The same sweep over the runtime's other winds and the back end's finally: an
# interrupt never skips a finally between its body and its cleanup, and never
# leaves a binding frame, *txn*, *out* or a lazy seq's claim behind.
windescape:
@$(CHEZ) --script test/chez/wind-escape-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/cli/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
# Every binary kind registers its linked zlib as jolt_z_* (java.util.zip binds
# those names) and none exports zlib's own names on Linux.
zlibregistersmoke: testbin
@JOLT_BIN="$${JOLT_BIN:-target/release/jolt}" sh host/chez/zlib-register-smoke.sh
# A zlib a built app loads through :jolt/native does not change the zlib
# java.util.zip uses.
zlibnativesmoke: testbin
@JOLT_BIN="$${JOLT_BIN:-target/release/jolt}" sh host/chez/zlib-native-smoke.sh
# The runtime runs no program off PATH to answer about its own process:
# (System/getenv) as a map, the environment a ProcessBuilder child inherits and
# os.version hold with PATH empty (they used to spawn `env -0`, `sw_vers` and
# `uname -r`). shelloutcheck is the static half of the same rule.
noexecsmoke: testbin
@JOLT_BIN="$${JOLT_BIN:-target/release/jolt}" sh host/chez/no-external-programs-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
# A native that is on disk but fails to load (its own dependency is missing)
# is reported as that, with the loader's reason — not as "not found" (#1127).
ffiloadfail:
@sh host/chez/ffi-load-failure-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)
# OPT-IN: time the same library suites per test on jolt and on JVM Clojure, and
# list the tests more than JOLT_LIBPERF_THRESHOLD (default 5) times slower on
# jolt. Needs the checkouts and a `clojure` CLI. Report in target/libperf/.
# `make libperf LIBS="malli honeysql"` runs a subset.
libperf: testbin
@JOLT_BIN="$${JOLT_BIN:-target/release/jolt}" \
JOLT_NO_USER_DEPS=1 target/release/jolt run test/conformance/libs/timing.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
# Lazy realization walks past what it has consumed in constant memory, as the
# JVM does (test/lazy_retention_test.clj): each case in its own process under a
# 256MB ceiling, so one that runs out cannot mask the next. A run of skips used
# to become a recursion pinning the whole run (lazy-bridge.ss, and ^:once thunks
# in backend emit-fn).
lazyretain: testbin
@t=test/lazy_retention_test.clj; j=target/release/jolt; fails=0; n=0; \
for c in $$(JOLT_NO_USER_DEPS=1 $$j run $$t); do \
n=$$((n + 1)); \
out=$$(JOLT_NO_USER_DEPS=1 JOLT_MAX_HEAP=256m timeout 60 $$j run $$t $$c 2>&1 | tail -1); \
case "$$out" in "$$c ok") ;; *) fails=$$((fails + 1)); echo "FAIL lazyretain: $$c: $${out:-timed out}";; esac; \
done; \
[ "$$fails" = 0 ] || exit 1; \
echo "lazyretain: $$n cases in constant memory under a 256MB heap"
# The stack depth limit (rt.ss jolt-install-stack-limit!): runaway recursion
# throws java.lang.StackOverflowError instead of growing the heap until the
# kernel kills the process, legitimately deep recursion still runs, and
# JOLT_STACK_LIMIT sets, disables (-1) or is refused by name. The Scheme half
# pins what a process cannot reach on every host (the default's rlimit
# precedence; macOS will not set RLIMIT_AS).
stacklimit: testbin
@$(CHEZ) --script test/chez/stack-limit-test.ss
@sh test/chez/stack-limit-smoke.sh target/release/jolt
# What a process holds beyond its data (test/chez/footprint-test.sh): no boot
# chunk pinned near-empty on a patched kernel (chez-patches/0002), the embedded
# boot arrays in a read-only section (madvise dirtied them in writable data), and
# a GC live baseline measured after a collection rather than over the boot's
# garbage.
footprint: testbin
@sh test/chez/footprint-test.sh target/release/jolt "$(CHEZ)"
# The nursery follows the collector's time share, bounded by the live set
# (rt.ss jolt-install-gc-policy!): a churning program grows it past the 16MB floor
# but not past its footprint bound (the fixed-cap policy grew the same loop's to
# 512MB+), a light one leaves it at the floor, and the JVM-named knobs take
# effect: JOLT_GC_TRIP_BYTES pins it, JOLT_MAX_NEW_SIZE caps it, JOLT_MAX_HEAP
# bounds the total heap (not only the live data) within the collector's working
# room -- 10%: Chez cannot compact in place, so a full collection over live data
# near the limit either copies (room for a second copy) or marks in place
# (fragments that cannot go back to the OS) --
# JOLT_MAX_RAM_PERCENTAGE sets the heap ceiling, and a bad value is refused by
# name. Each mode is its own process, since the policy is per process.
# Recipes run under bash -e (.cache/makes/init.mk), so a capture whose process is
# meant to fail takes `|| true` and the check after it decides; without it the
# recipe exits there with no FAIL line (macOS's make 3.81 ignores .SHELLFLAGS,
# which is why it only showed on Linux).
gcpolicy: testbin
@floor=16777216; t=test/gc_policy_test.clj; j=target/release/jolt; \
trip() { env JOLT_NO_USER_DEPS=1 "$$@" $$j run $$t churn 2>&1 | sed -n 's/^trip //p'; }; \
churn=$$(trip); \
light=$$(JOLT_NO_USER_DEPS=1 $$j run $$t light 2>&1 | sed -n 's/^trip //p'); \
pinned=$$(trip JOLT_GC_TRIP_BYTES=33554432); \
capped=$$(trip JOLT_MAX_NEW_SIZE=24m); \
echo "gcpolicy: churn $$churn, light $$light, pinned $$pinned, max-new-size 24m -> $$capped"; \
[ -n "$$churn" ] && [ "$$churn" -gt "$$floor" ] || { echo "FAIL gcpolicy: a churning program kept the $$floor floor"; exit 1; }; \
[ "$$churn" -lt 536870912 ] || { echo "FAIL gcpolicy: a ~40MB-live loop grew its nursery to $$churn, past the live-set bound"; exit 1; }; \
[ "$$light" = "$$floor" ] || { echo "FAIL gcpolicy: a light program left the floor ($$light)"; exit 1; }; \
[ "$$pinned" = "33554432" ] || { echo "FAIL gcpolicy: JOLT_GC_TRIP_BYTES did not pin the nursery ($$pinned)"; exit 1; }; \
[ -n "$$capped" ] && [ "$$capped" -le 25165824 ] || { echo "FAIL gcpolicy: JOLT_MAX_NEW_SIZE=24m did not cap the nursery ($$capped)"; exit 1; }; \
full=$$(JOLT_NO_USER_DEPS=1 $$j -e '(print (.maxMemory (Runtime/getRuntime)))'); \
tenth=$$(JOLT_NO_USER_DEPS=1 JOLT_MAX_RAM_PERCENTAGE=10 $$j -e '(print (.maxMemory (Runtime/getRuntime)))'); \
[ $$((tenth * 25 / 10)) -le $$((full + 1)) ] && [ $$((tenth * 25 / 10)) -ge $$((full - 4)) ] || { echo "FAIL gcpolicy: JOLT_MAX_RAM_PERCENTAGE=10 gave $$tenth against $$full at the default 25"; exit 1; }; \
out=$$(JOLT_NO_USER_DEPS=1 JOLT_GC_LOG=1 JOLT_MAX_HEAP=256m $$j run $$t ceiling 2>&1); \
set -- $$(printf '%s\n' "$$out" | sed -n 's/^peak //p'); pk=$$1; mx=$$3; \
after=$$(printf '%s\n' "$$out" | sed -n 's/^gc: .* total \([0-9]*\)MB .*/\1/p' | sort -n | tail -1); \
echo "gcpolicy: JOLT_MAX_HEAP=256m with ~100MB held -> total after collections $${after}MB, peak $$((pk / 1048576))MB, max $$((mx / 1048576))MB"; \
[ -n "$$after" ] && [ $$((after * 1048576 * 10)) -le $$((mx * 11)) ] || { echo "FAIL gcpolicy: a collection left the total heap at $${after}MB, more than 10% over JOLT_MAX_HEAP=256m: the ceiling bounds the TOTAL heap, as -Xmx does, within the collector's working room"; exit 1; }; \
[ -n "$$pk" ] && [ $$((pk * 10)) -le $$((mx * 11)) ] || { echo "FAIL gcpolicy: the total heap peaked at $$pk during collections, more than 10% over JOLT_MAX_HEAP=256m (v0.8.12 peaked 23% over)"; exit 1; }; \
fdir=$$(mktemp -d); \
set -- $$(cd "$$fdir" && JOLT_NO_USER_DEPS=1 JOLT_MAX_HEAP=256m "$(CURDIR)/$$j" run "$(CURDIR)/test/gc_ceiling_forced_test.clj" 2>&1 | sed -n 's/^peak //p'); pk=$$1; mx=$$3; rmdir "$$fdir"; \
echo "gcpolicy: a ceiling-forced full collection with ~140MB spread over the older generations -> peak $$(($${pk:-0} / 1048576))MB"; \
[ -n "$$pk" ] && [ $$((pk * 10)) -le $$((mx * 11)) ] || { echo "FAIL gcpolicy: the heap peaked at $${pk:-?} in a ceiling-forced full collection, more than 10% over JOLT_MAX_HEAP=256m: one collection of every generation holds its copies beside their sources (collect it a generation at a time; jolt-exoj)"; exit 1; }; \
set -- $$(JOLT_NO_USER_DEPS=1 $$j run $$t refresh 2>&1 | sed -n 's/^growth //p'); \
echo "gcpolicy: older generations' allowance with ~100MB held $$(($${1:-0} / 1048576))MB, after dropping it and System/gc $$(($${2:-0} / 1048576))MB"; \
[ -n "$${2:-}" ] && [ "$$1" -gt 67108864 ] && [ "$$2" -ge 67108864 ] && [ $$(($$2 * 2)) -lt "$$1" ] || { echo "FAIL gcpolicy: System/gc did not re-measure the live set the older generations' allowance is sized from"; exit 1; }; \
st=$$(JOLT_NO_USER_DEPS=1 $$j run $$t startup 2>&1 | sed -n 's/^trip //p'); \
echo "gcpolicy: a sub-millisecond first reading then cheap collections -> nursery $$st"; \
[ "$$st" = "$$floor" ] || { echo "FAIL gcpolicy: one collection right after startup grew the nursery to $${st:-?} (the share must weigh collections by their time, and wait for five)"; exit 1; }; \
set -- $$(JOLT_NO_USER_DEPS=1 JOLT_GC_TRIP_BYTES=16m JOLT_GC_LOG=1 $$j run $$t walk 2>&1 | sed -n 's/^gc: .* heap \([0-9]*\)MB .*/\1/p' | sort -n | sed -n '1p;$$p'); \
echo "gcpolicy: a lazy-seq walk's heap after each collection ranged $${1:-?}MB..$${2:-?}MB"; \
[ -n "$${2:-}" ] && [ $$(($$2 - $$1)) -le 4 ] || { echo "FAIL gcpolicy: the heap after a lazy-seq walk's collections climbed from $${1:-?}MB to $${2:-?}MB: a dead promoted cell is rooting the cells realized after it (nepotism; see sa-collect-young!)"; exit 1; }; \
oh=$$(JOLT_NO_USER_DEPS=1 JOLT_MAX_HEAP=1g JOLT_GC_TIME_LIMIT=0 JOLT_GC_HEAP_FREE_LIMIT=100 $$j run $$t churn 2>&1) || true; \
case "$$oh" in *OutOfMemoryError*"GC overhead limit exceeded"*) ;; *) echo "FAIL gcpolicy: with every collection over the limits, the GC overhead limit did not raise: $$(printf '%s' "$$oh" | head -2)"; exit 1;; esac; \
off=$$(JOLT_NO_USER_DEPS=1 JOLT_MAX_HEAP=1g JOLT_GC_TIME_LIMIT=0 JOLT_GC_HEAP_FREE_LIMIT=100 JOLT_GC_OVERHEAD_LIMIT=off $$j run $$t churn 2>&1 | sed -n 's/^trip //p'); \
[ -n "$$off" ] || { echo "FAIL gcpolicy: JOLT_GC_OVERHEAD_LIMIT=off did not turn the limit off"; exit 1; }; \
echo "gcpolicy: the GC overhead limit raises OutOfMemoryError, and JOLT_GC_OVERHEAD_LIMIT=off turns it off"; \
for bad in JOLT_GC_TIME_RATIO=0 JOLT_MAX_HEAP_FREE_RATIO=100 JOLT_MAX_RAM_PERCENTAGE=abc JOLT_NEW_SIZE=12q JOLT_GC_TIME_LIMIT=200; do \
msg=$$(env JOLT_NO_USER_DEPS=1 $$bad $$j -e '(println :ran)' 2>&1) || true; \
case "$$msg" in *"$${bad%%=*}"*"is not valid"*) ;; *) echo "FAIL gcpolicy: $$bad was not refused by name: $$msg"; exit 1;; esac; \
done; \
echo "gcpolicy: passed"
# 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.
# Both footprint gates below judge the collector's high-water mark against ONE
# collection trip, so the trip is pinned (JOLT_GC_TRIP_BYTES) rather than left to
# the collector policy (rt.ss jolt-install-gc-policy!): a nursery that grows during
# an allocation-heavy arm raises the floor the reading is judged against, and the
# arms that must differ read the same (what failed apply-scaling's control on CI).
applyscaling: testbin
@JOLT_NO_USER_DEPS=1 JOLT_GC_TRIP_BYTES=16777216 target/release/jolt run test/apply_scaling_test.clj
# Peak memory of the java.util.zip streams: 100 MB through GZIPOutputStream or
# GZIPInputStream peaks within 2 MB of 1 MB. It reads the live heap, not the
# collector's high-water mark applyscaling reads: that mark hides anything under
# one collection trip, and the ceiling here is 2 MB.
zipmemory: testbin
@JOLT_NO_USER_DEPS=1 JOLT_GC_TRIP_BYTES=16777216 target/release/jolt run test/zip_memory_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 constant-factor fast paths, each judged as a RATIO against a reference arm
# the same run measures: reading a form off a stream vs off a string, the
# memory-bounded char[] read vs slurping the file, and the literal-pattern
# recognition that keeps split/replace off the regex engine. Deliberately not a
# scaling gate — every regression here is linear with a terrible constant, which
# a 1x-vs-4x ratio cannot see (see the file's header).
fastpathratio: testbin
@JOLT_NO_USER_DEPS=1 target/release/jolt run test/fastpath_ratio_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
# The process ends when main returns only once every live non-daemon thread has
# finished, with the agent pools' idle holds, as a JVM running clojure.main does:
# each case's outcome was measured on JDK 21.
exitwait: testbin
@JOLT_BIN="$${JOLT_BIN:-target/release/jolt}" sh host/chez/exit-wait-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
# Maven dependencies load from their jars in place, with no unzip on PATH and
# no extraction (jolt issues #988 and #1005): a :mvn/version jar from an offline
# local repository resolves and loads, its resources answer jar: URLs, a jar
# that is not a whole archive fails loudly and caches nothing, and a damaged
# entry is refused where it is read.
depsnounzip: testbin
@JOLT_BIN="$${JOLT_BIN:-target/release/jolt}" sh host/chez/deps-no-unzip-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
# The jolt.cli surface over the vendored babashka.cli: that the re-export
# reaches the parser, the dispatcher and the help renderer, and that *exit-fn*
# (a dynamic var, which import-vars cannot re-export as a delegating fn) is
# deliberately NOT among them. The task runner's own use of babashka.cli is
# covered by taskssmoke. Offline.
clishim:
@JOLT_NO_USER_DEPS=1 bin/jolt run test/cli_shim_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 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.
# The foreign-thread gate covers the other side of the same collect-safety
# contract (issue #973): a callback arriving on the library's OWN thread while
# the caller is parked in an outbound call to that library, where a call that is
# not :blocking pins the collector the callback needs — and the two signatures
# the collect-safe convention cannot carry, which jolt must refuse in its own
# words rather than leave to Chez's expander.
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)"
@sh test/chez/ffi-foreign-thread-test.sh
@sh test/chez/ffi-gc-stall-test.sh
@sh test/chez/collect-safe-activation-test.sh "$(CHEZ)"
# zlib bindings (host/chez/java/zlib.ss): the z_stream layout, entry-point
# resolution, checksums, round trips, error codes, dictionaries, parameter
# changes, close, and the guardian drain.
zlibunit:
@$(CHEZ) --script test/chez/zlib-test.ss
# UTF-8 bytes -> text against java.nio's decoder (natives-str.ss
# utf8-bytes->string, host-static-classes.ss decode-bytevector): the named
# malformed rows, 2702 JVM-pinned random rows, and the soundness of the guard
# that keeps Chez's C decoder on the well-formed path.
utf8decode:
@$(CHEZ) --script test/chez/utf8-decode-test.ss
# 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
# Sorted colls: the lazy tree walk behind seq/rseq/keys/vals/subseq, and hash/=
# answered from the tree. Values pinned to the JVM; scaling pinned as allocation.
sortedcoll:
@$(CHEZ) --script test/chez/sorted-test.ss
# 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