summaryrefslogtreecommitdiff
path: root/src/GNUmakefile
blob: 3cfee6c753479a8e976c52764bea0dfd1716b606 (plain)
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
# Makefile (for typhoon/GNU-style make).  Recognized gmake variables:
#
#  DEBUG=1: make debug version (includes asserts and sanity checks)
#  TEST=1: include self-test code in the binary produced
#  ASM=1: create assembly code, does not link final binary
#  EVAL_DUMP=1: make a version that can dump eval breakdowns
#  EVAL_TIME=1: make a version that counts cycles spent in eval
#  PERF_COUNTERS=1: make version with perf counters enabled
#  MOVE_ORDERING_EVIDENCE_COLLECTION=1: instrument the move-ordering
#    evidence-bucket harnesses (countermove/move-pair hit tracking,
#    CM/MP evidence-bucket tries/FH -- see MOVE_ORDERING_IDEAS.md) --
#    a dedicated flag, independent of PERF_COUNTERS, since this
#    collection (per-move IS_SAME_MOVE checks + bucket lookups on every
#    quiet move scored) is its own cost, not bundled into the general
#    perf-counter set by default.
#  BOUNDS_CHECKING=1: make version with bounds checking enabled
#  MP=1: make version for multi-processor machine
#  DUMP_TREE=1: internal tree dumping version
#  OSX=1: build for an Intel-based Apple Mac (omit this for FreeBSD/Linux)
#  USE_READLINE=1: link against the GNU readline library
#  SIXTYFOUR=1: make an X64 binary
#  CROUTINES=1: use the C versions of the asm routines [slower]
#  GETATTACKS_BITBOARD=1: use the bbPieces/bbPawns-backed _GetAttacksBB
#    instead of the asm/CROUTINES GetAttacks -- see
#    board_representation/MIGRATION.md section 6
#  DISABLE_BITBOARD_MOVEGEN=1: opt back into the mailbox move
#    generator/escapes/ExposesCheck/IsAttacked implementations -- the
#    nine GENERATE_*_BITBOARD/EXPOSESCHECK_BITBOARD/ISATTACKED_BITBOARD
#    toggles (board_representation/MOVEGEN_MIGRATION.md) are on by
#    default as of the 2026-09-04 correctness/no-regression sweep
#    (perft, move-set comparison harness, sd10 curated-suite parity,
#    match_play.py). The mailbox code is still fully present and
#    compiled either way -- this flag only flips which side of each
#    macro-swap wins, it doesn't remove anything.
#  GPROF=1: instrument with -pg for gprof(1)-based profiling (see
#    PROFILE.md) -- root-free on both FreeBSD and Linux, unlike
#    hardware-counter-based profiling (pmcstat/perf). Requires a
#    `gmake clean` first/after, since -pg changes codegen for every
#    translation unit.
#  EVERYTHING=1: everything everything everything everything
#
# $Id$
#
CC 		= 	clang
CXX		=	clang++
NASM		=	yasm

# Default to a 64-bit build -- 32-bit clang isn't available in this
# environment anyway (see CLAUDE.md). Still overridable in principle
# (e.g. `gmake SIXTYFOUR=` won't un-set it since ?= only fires when
# unset, but this matches how every build in this session has actually
# been invoked).
SIXTYFOUR	?=	1
ifdef OSX
 LIBRARIES	=	-pthread -lc
 ifdef SIXTYFOUR
  $(warning you will need to use yasm for now to get x64 on OSX)
  NASMFLAGS	=	-f macho64 -dOSX -d_X64_
 else
  NASMFLAGS	=	-f macho -dOSX
 endif
else
 LIBRARIES	=	-pthread -lc -lstdc++ 
 ifdef SIXTYFOUR
  NASMFLAGS	=	-f elf64 -d__GNUC__
 else
  NASMFLAGS	=	-f elf -d__GNUC__
 endif
endif
RM		= 	/bin/rm

#
# Setup commandline based on make defines
#
ifdef ASM
 ifdef SIXTYFOUR
  PROFILE	=	-S -fverbose-asm -D_X64_
 else
  PROFILE	=	-S -fverbose-asm -D_X86_
 endif
else
 ifdef SIXTYFOUR
  PROFILE 	=	-D_X64_
 else
  PROFILE 	=	-D_X86_
 endif
endif

ifdef OSX
 PROFILE	+=	-DOSX
endif

ifdef GENETIC
 PROFILE	+=	-DGENETIC
endif

ifdef GPROF
 PROFILE	+=	-pg
endif

ifdef DEBUG
 PROFILE 	+=	-g -DDEBUG
 BINARY 	=	_typhoon
else
 PROFILE	+=	-g -O3 -ffast-math -finline-functions
 BINARY 	=	typhoon
endif

ifdef USE_READLINE
 LIBRARIES	+=	-lreadline
 PROFILE	+=	-DUSE_READLINE
endif

ifdef SIXTYFOUR
 # -mpopcnt: every x86-64 chip built since ~2008 (and this box's Ryzen
 # 9 3900X) has the POPCNT instruction, but clang won't emit it for
 # __builtin_popcountll without this -- without it, the builtin lowers
 # to a ~15-instruction SWAR bit-trick sequence instead (still faster
 # than the old CDECL-call CountBits, but not the ~1-3 cycle single
 # instruction the "fast" eval.c wrappers were assumed to get).
 PROFILE	+=	-m64 -mpopcnt
 NASMFLAGS	+=	-d_X64_
else
 PROFILE	+=	-m32
 NASMFLAGS	+=	-d_X86_
endif

ifdef CROUTINES
 PROFILE	+=	-DCROUTINES
endif

ifdef GETATTACKS_BITBOARD
 PROFILE	+=	-DGETATTACKS_BITBOARD
endif

ifndef DISABLE_BITBOARD_MOVEGEN
 PROFILE	+=	-DGENERATE_KNIGHT_BITBOARD -DGENERATE_KING_BITBOARD \
			-DGENERATE_ROOK_BITBOARD -DGENERATE_BISHOP_BITBOARD \
			-DGENERATE_QUEEN_BITBOARD -DGENERATE_PAWN_BITBOARD \
			-DGENERATE_ESCAPES_KING_BITBOARD -DGENERATE_ESCAPES_BLOCK_BITBOARD \
			-DEXPOSESCHECK_BITBOARD -DISATTACKED_BITBOARD \
			-DUNBLOCKED_BITBOARD
endif

ifdef EVERYTHING
PROFILE 	+=	-DEVAL_DUMP -DEVAL_TIME -DPERF_COUNTERS -DMP -DSMP -DTEST_NULL -DDUMP_TREE -fbounds-checking -DMOVE_ORDERING_EVIDENCE_COLLECTION
else
ifdef EVAL_DUMP
PROFILE		+= 	-DEVAL_DUMP
endif
ifdef EVAL_TIME
PROFILE		+= 	-DEVAL_TIME
endif
ifdef PERF_COUNTERS
PROFILE		+=	-DPERF_COUNTERS
endif
ifdef MOVE_ORDERING_EVIDENCE_COLLECTION
PROFILE		+=	-DMOVE_ORDERING_EVIDENCE_COLLECTION
endif
ifdef BOUNDS_CHECKING
PROFILE		+=	-fbounds-checking
endif
ifdef MP
PROFILE		+=	-DMP -DSMP
endif
ifdef TEST_NULL
PROFILE		+=	-DTEST_NULL
endif
ifdef DUMP_TREE
PROFILE 	+=  -DDUMP_TREE
endif
ifdef CALIBRATE_POSITIONAL
PROFILE		+=	-DCALIBRATE_POSITIONAL
endif
ifdef KING_DEFECT_DUMP
PROFILE		+=	-DKING_DEFECT_DUMP
endif
ifdef CALIBRATE_BASE_MARGIN
PROFILE		+=	-DCALIBRATE_BASE_MARGIN
endif
ifdef CALIBRATE_MARGIN_SAFETY
PROFILE		+=	-DCALIBRATE_MARGIN_SAFETY
endif
ifdef DUMP_RESIDUAL_SAMPLES
PROFILE		+=	-DDUMP_RESIDUAL_SAMPLES
endif
ifdef DIAG_NO_QSEARCH_FUTILITY
PROFILE		+=	-DDIAG_NO_QSEARCH_FUTILITY
endif
ifdef CALIBRATE_QSEARCH_FUTILITY
PROFILE		+=	-DCALIBRATE_QSEARCH_FUTILITY
endif
endif # EVERYTHING
# 2026-09-28: full-coverage (non-sampled) throwaway shadow re-search,
# purely to measure the "silent miss" failure mode a reduced move's own
# real re-search hook can't see: the extra-reduced score is < beta (no
# real re-search triggered) but removing just the EXTRA plies (i.e. the
# depth HEAD's own baseline -ONE_PLY reduction would have used) finds
# a fail-high. Every extra-reduced move gets this check within a build
# with this flag on -- doubles search cost for that subset, so
# calibration-only, never part of a normal or self-play build. Opt in
# explicitly, same as the other CALIBRATE_*/DUMP_* flags above.
ifdef JOINT_EXTRA_VERIFY
PROFILE		+=	-DJOINT_EXTRA_VERIFY
endif
# 2026-09-28: audit-only EFP disable, same technique this project used
# during the joint-table collection run's self-censoring fix (force the
# prune decision FALSE, keep computing/logging everything around it) --
# isolates the joint-table-driven LMR's own behavior from EFP's
# single-axis (gap/down only) margin-based pruning, which the
# JOINT_EXTRA_VERIFY silent-miss data implicates as the dominant source
# of the measured instability, not LMR's own reduction depth. Opt-in,
# calibration-only.
ifdef DISABLE_EFP
PROFILE		+=	-DDISABLE_EFP
endif
# 2026-09-28: Trace() the FEN/move/scores whenever JOINT_EXTRA_VERIFY's
# shadow search catches a silent miss -- for looking at real, concrete
# failing cases by hand (the methodology that actually cracked the
# hanging-material bug earlier this project, rather than continuing to
# guess which other search mechanism might explain the EFP-tier-window
# silent-miss spike). Requires JOINT_EXTRA_VERIFY. Calibration-only.
ifdef SILENT_MISS_TRACE
PROFILE		+=	-DSILENT_MISS_TRACE
endif
# 2026-09-29: alternate, maximally-simple extra-reduction policy for A/B
# comparison against the axis-gated joint-table version -- skips the
# dumb/gap/late table lookup entirely and just asks "is there enough
# pre-reduction depth headroom to spare (>= 6 plies)?" If so, apply the
# full extra reduction (R=3) uniformly, for ANY depth >= 6 (not just
# exactly 6 -- 7, 8, 9, ... all get the same treatment). Tests whether
# the axis-based table is pulling any real weight once a depth floor is
# already in place, or whether depth alone does just as well.
ifdef DEPTH_ONLY_EXTRA_REDUCTION
PROFILE		+=	-DDEPTH_ONLY_EXTRA_REDUCTION
endif
# 2026-09-29: neutralize searchsup.c's null-move "history-reduced
# bailout" branch (Tord Romstad idea) for our own shadow-verification
# Search() call specifically, by temporarily zeroing our own
# pi->iExtensionAmount around it -- see search.c's NEUTRALIZE_NULL_
# BAILOUT comment. Correct in the real search path, that branch
# fabricates a "please re-search me" placeholder score that our
# bespoke, wrapper-less shadow call can't interpret and misreads as a
# genuine fail-high. Build once WITHOUT this flag to measure how much
# of our silent-miss data is this artifact (see
# u64JointExtraVerifyNullBailoutSignature), once WITH it for the
# corrected rate.
# 2026-10-03: PRUNE_VERIFY -- shadow verification for the DEEP PRUNE gate
# (ShouldLatePrune) was removed (2026-10-14): it never actually compiled
# (fVerifyFlagged/uVerifyBand/etc. were used in search.c but never
# declared -- PRUNE_VERIFY is never defined by any path in this file, so
# nothing ever caught it). Dead, broken code, not a working diagnostic;
# removed rather than fixed since reconstructing its intended semantics
# from scratch would be inventing behavior, not restoring it.

# 2026-10-03: PRUNE_LIVE=<K> was removed (2026-10-14): PruneLivePredict/
# PRUNE_LIVE_SENTINEL (search.c) were never actually called from
# anywhere, under any flag -- the wiring to make PRUNE_LIVE=<K> actually
# prune a move was never written, so this flag has never done anything
# even when it compiled. It also didn't compile: PruneVerifyPredict
# (the function PruneLivePredict aliased to) returns
# g_uJointFHThousandths[...]'s element as ULONG, but that array is
# declared USHORT in data.c -- confirmed pre-existing, unrelated to
# PRUNE_VERIFY's own removal above. Dead on every axis; removed rather
# than fixed for the same reason as PRUNE_VERIFY.
# 2026-10-03: DISABLE_LMR -- GetLMRReduction returns 0 unconditionally,
# so no move is ever reduced. Exists for ONE experiment: what is LMR
# actually worth in this engine? The time-odds calibration established
# that TIME-bought depth is ~95 Elo/ply here, but nothing has ever
# measured whether REDUCTION-bought depth converts at that rate. Two
# hypotheses fit "deeper but no stronger": (1) missed refutations, i.e.
# the reduction is too aggressive and its errors cancel the gain --
# remedy is to constrain it; or (2) nominal depth under reduction is a
# debased currency that never carried a time-ply's worth of information
# -- in which case constraining it cannot help, since less reduction
# buys less depth at the same bad rate. Removing LMR entirely and
# pricing the loss against the 95 Elo/ply yardstick separates them.
# Measurement only; never a shipping configuration.
ifdef DISABLE_LMR
PROFILE		+=	-DDISABLE_LMR
endif

# 2026-10-13: override search.c's NULLMOVE_SKIP_FH_CEILING_THOUSANDTHS
# default (300, validated -- see search.c's own comment) from the
# command line for sweeping, e.g. `gmake ... NULLMOVE_SKIP_CEILING=1000`
# -- search.c's #define is guarded with #ifndef so this wins without
# editing the source.
ifdef NULLMOVE_SKIP_CEILING
PROFILE		+=	-DNULLMOVE_SKIP_FH_CEILING_THOUSANDTHS=$(NULLMOVE_SKIP_CEILING)
endif

# 2026-10-13: override movesup.c's SelectBestWithHistory skip-
# selection-sort threshold for sweeping, e.g.
# `gmake ... SKIP_SELECT_N=6`.
ifdef SKIP_SELECT_N
PROFILE		+=	-DSKIP_SELECT_AFTER_N_MOVES=$(SKIP_SELECT_N)
endif

ifdef NEUTRALIZE_NULL_BAILOUT
PROFILE		+=	-DNEUTRALIZE_NULL_BAILOUT
endif

# Short commit hash (plus a -dirty suffix if the working tree has
# uncommitted changes) baked into the binary so a --logfile trace can
# be tied back to the exact source state, not just a build timestamp --
# distinguishing rebuilds of the same day/commit during A/B testing was
# previously only possible by diffing binaries. Placed here, after all
# the PROFILE-overwriting `=` assignments above (not `+=`), so it
# survives into CFLAGS instead of being wiped out by them.
GIT_COMMIT	:=	$(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
# Scoped to this directory: an unscoped `git diff` has to stat (and,
# with a stale index, re-hash) every tracked file in the repo,
# including the multi-GB pgn/twic.pgn, on every make invocation.
GIT_DIRTY	:=	$(shell git diff --quiet -- . 2>/dev/null || echo -dirty)
# Kept out of PROFILE itself (not folded in via +=): PROFILE gets
# stringified whole into -DPROFILE="..." below for the "Make profile
# used" trace line, and this value's embedded quotes broke that outer
# string literal when it was included there.
# -flto: several functions (e.g. _RookAttacksBB/_BishopAttacksBB,
# generate.c) are declared FORCEINLINE but non-static, exposed
# specifically so other translation units (eval.c, searchsup.c, see.c)
# can call them -- __attribute__((always_inline)) only actually forces
# inlining within the TU that has the function body, so every
# cross-file call was paying for a real call/ret instead of inlining
# without this. Measured via the `bench` command (4 reps each,
# interleaved with a non-LTO baseline): a clean, reproducible ~6.6% NPS
# improvement with zero overlap between the two distributions (baseline
# 1643k-1661k nps, LTO 1752k-1768k nps) -- not noise. -mtune=native:
# tunes instruction scheduling/costs for the actual build machine
# rather than a generic x86-64 target; measured weaker and noisier
# (~1.5%, some overlap with baseline) but real and free (no
# portability cost the way -march=native's newer-ISA-extension
# dependency would have -- see the BMI2/PEXT-on-Zen2 caveat if anyone
# reaches for -march=native or PEXT-based magic bitboards later).
CFLAGS		=	-DPROFILE="\"$(PROFILE)\"" -DGIT_COMMIT=\"$(GIT_COMMIT)$(GIT_DIRTY)\" $(PROFILE) -Wall -Wno-nan-infinity-disabled -Ifathom -flto -mtune=native
HEADERS 	=	chess.h compiler.h
FATHOM_CFLAGS	=	-std=gnu99 -O2 -Ifathom $(filter -m32 -m64,$(PROFILE))

#
# ---> .o, not .c! <---
#
OBJS    =       main.o root.o search.o searchsup.o draw.o dynamic.o \
		hash.o eval.o x86.o pawnhash.o bitboard.o \
		generate.o see.o move.o movesup.o command.o script.o \
		input.o vars.o util.o unix.o gamelist.o mersenne.o \
		sig.o piece.o ics.o san.o fen.o book.o bench.o board.o \
		data.o probe.o fathom.o recogn.o list.o x64.o \
		nnuefeatures.o nnueaccum.o

ifdef TEST
# ---> .o, not .c! <---
#
OBJS    +=      testdraw.o testmove.o testfen.o testgenerate.o \
		testsan.o testics.o testeval.o testbitboard.o \
		testhash.o testsee.o testsearch.o testsup.o \
		testrecogn.o
PROFILE +=	-DTEST
else
ifdef EVAL_DUMP
OBJS	+=	testeval.o testsup.o
endif
endif
ifdef DUMP_TREE
OBJS	+=	dumptree.o
endif
ifdef MP
OBJS	+=	split.o
endif

.SUFFIXES:	.c .o
.SUFFIXES:	.cpp .o
.SUFFIXES:	.asm .o

.PHONY:		all
all:		$(BINARY)

$(BINARY):	$(OBJS)
		$(CXX) $(PROFILE) -flto -o $(BINARY) -Wall $(LIBRARIES) \
		$(OBJS)

%.o:        %.c $(HEADERS)
		$(CC) $(CFLAGS) -c $< -o $@

fathom.o:	fathom/tbprobe.c fathom/tbchess.c fathom/tbconfig.h fathom/tbprobe.h fathom/stdendian.h
		$(CC) $(FATHOM_CFLAGS) -c fathom/tbprobe.c -o $@

%.o:        %.cpp $(HEADERS)
		$(CXX) $(CFLAGS) -c -w $< -o $@

%.o:        %.asm $(HEADERS)
		$(NASM) $(NASMFLAGS) -o $@ $<

.PHONY:		release
release:
		$(MAKE) -j5 MP=1 GENETIC=1

.PHONY:		debug
debug:
		$(MAKE) -j5 DEBUG=1 PERF_COUNTERS=1 GENETIC=1

.PHONY:		clean
clean:
		$(RM) -f *.o *.core *~ *.gmon \#*\# typhoon _typhoon