CC = clang++-17
CFLAGS_DEFINES ?= -DSEED=923 -DUPDATE_LIMIT=3000

ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))

CPPFLAGS_PART-THAT-SHOULD-BE-FAST := $(CFLAGS_DEFINES) -m64 -Wall -std=c++17 -ffp-model=fast -mrecip=none -fno-exceptions -fno-unwind-tables -fno-asynchronous-unwind-tables -fno-threadsafe-statics -Wno-unknown-escape-sequence -Wno-unused-variable -Wno-unneeded-internal-declaration -Wno-unused-but-set-variable -Wno-format 

ifdef COREI7
$(info COREI7 defined)
CPPFLAGS_PART-THAT-SHOULD-BE-FAST += -march=corei7
else
ifdef ZEN2
$(info ZEN2 defined)
CPPFLAGS_PART-THAT-SHOULD-BE-FAST += -march=znver2
else
CPPFLAGS_PART-THAT-SHOULD-BE-FAST += -march=native -mtune=native
$(info native used)
endif
endif

CPPFLAGS_PART-THAT-CAN-BE-SLOW    := $(CPPFLAGS_PART-THAT-SHOULD-BE-FAST)
CPPFLAGS_PART-THAT-CAN-BE-SLOW    += -Os -fdata-sections -ffunction-sections
CPPFLAGS_PART-THAT-SHOULD-BE-FAST += -O3 -fdata-sections -ffunction-sections
CPPFLAGS_PART-THAT-IS-COLD        := $(CFLAGS_DEFINES) -m64 -Wall -std=c++17 -ffp-model=fast -mrecip=none -fno-exceptions -fno-unwind-tables -fno-asynchronous-unwind-tables -fno-threadsafe-statics -Wno-unknown-escape-sequence -Wno-unused-variable -Wno-unneeded-internal-declaration -Wno-unused-but-set-variable -Wno-format -Oz -fdata-sections -ffunction-sections

ifdef COREI7
CPPFLAGS_PART-THAT-IS-COLD += -march=corei7
else
ifdef ZEN2
CPPFLAGS_PART-THAT-IS-COLD += -march=znver2
else
CPPFLAGS_PART-THAT-IS-COLD += -march=native -mtune=native
endif
endif

ifdef BLOBV2
$(info BLOBV2 defined)
CPPFLAGS_PART-THAT-IS-COLD += -DBLOBV2
endif

# IDHOIST: '>delta' id-line hoist in phda9's encode_txt_wit (encode side only;
# the decode side is always-on and runtime-adaptive, so flag-off binaries
# still restore IDHOIST streams). phda9_preprocess.h reaches the cmix binary
# only through runner.cpp => COLD, same pattern as BLOBV2.
ifdef IDHOIST
$(info IDHOIST defined)
CPPFLAGS_PART-THAT-IS-COLD += -DIDHOIST
endif

# GrammarMatch prediction channel (GRAMMAR_MATCH_DESIGN.md): make GRAMMARMATCH=1
# ODR/layout rule (same as MIXER_LIMIT below): predictor.h grows an
# optional<GrammarMatch> member and context-manager.h grows grammar_state_
# (GM_MIXER). runner.cpp (COLD)
# stack-constructs Predictor and preprocessor.h (SLOW) includes predictor.h,
# so ALL GM_* defines must reach ALL three parts or the COLD/SLOW view of
# Predictor is smaller than the FAST ctor writes (stack corruption).
# grammar-match.cpp itself still compiles only in the FAST part.
ifdef GRAMMARMATCH
$(info GRAMMARMATCH defined)
CPPFLAGS_PART-THAT-SHOULD-BE-FAST += -DGRAMMARMATCH
CPPFLAGS_PART-THAT-CAN-BE-SLOW    += -DGRAMMARMATCH
CPPFLAGS_PART-THAT-IS-COLD        += -DGRAMMARMATCH
FAST_EXTRA_SRC := src/models/grammar-match.cpp
FAST_EXTRA_OBJ := grammar-match.o

# Stage-3 gates (all require GRAMMARMATCH):
# GM_ARM=<1|2|3>: ECHO arm threshold (default 2 = Stage-2 behavior)
ifdef GM_ARM
$(info GM_ARM=$(GM_ARM))
CPPFLAGS_PART-THAT-SHOULD-BE-FAST += -DGM_ARM=$(GM_ARM)
CPPFLAGS_PART-THAT-CAN-BE-SLOW    += -DGM_ARM=$(GM_ARM)
CPPFLAGS_PART-THAT-IS-COLD        += -DGM_ARM=$(GM_ARM)
endif
# GM_MIXER=1: layer-0 mixer keyed on the channel state (Stage-4 amplifier)
ifdef GM_MIXER
$(info GM_MIXER defined)
CPPFLAGS_PART-THAT-SHOULD-BE-FAST += -DGM_MIXER
CPPFLAGS_PART-THAT-CAN-BE-SLOW    += -DGM_MIXER
CPPFLAGS_PART-THAT-IS-COLD        += -DGM_MIXER
endif
# ⚠ GM family gates add DATA MEMBERS to class GrammarMatch, so the header
# rule above applies: ALL THREE flag groups, no exceptions (this block
# originally shipped FAST-only = the same stack-corruption UB the 6b57f74
# audit fixed for GRAMMARMATCH; caught 2026-07-08 before any Stage-4 arm ran).
# GM_REVTS=1: regime-1 revision timestamp reconstruction (kFamilyRevTs)
ifdef GM_REVTS
$(info GM_REVTS defined)
CPPFLAGS_PART-THAT-SHOULD-BE-FAST += -DGM_REVTS
CPPFLAGS_PART-THAT-CAN-BE-SLOW    += -DGM_REVTS
CPPFLAGS_PART-THAT-IS-COLD        += -DGM_REVTS
endif
endif

# PRETRAIN_LSTM=1|2: the ACTUAL p14 warm-start lever — stock Pretrain warms
# only the CM stack; the LSTM has never been pretrained anywhere in the cmix
# lineage. 1 = PPMD+LSTM byte path added to Pretrain (mirrors Perceive's
# byte-boundary block); 2 = LSTM without the PPMD feed (diagnostic).
# Decode-symmetric (same binary replays it). Logic-only; 3-group rule.
ifdef PRETRAIN_LSTM
$(info PRETRAIN_LSTM=$(PRETRAIN_LSTM))
CPPFLAGS_PART-THAT-SHOULD-BE-FAST += -DPRETRAIN_LSTM=$(PRETRAIN_LSTM)
CPPFLAGS_PART-THAT-CAN-BE-SLOW    += -DPRETRAIN_LSTM=$(PRETRAIN_LSTM)
CPPFLAGS_PART-THAT-IS-COLD        += -DPRETRAIN_LSTM=$(PRETRAIN_LSTM)
endif

ifdef ORDPCXT
$(info ORDPCXT defined)
CPPFLAGS_PART-THAT-SHOULD-BE-FAST += -DORDPCXT
endif

# F-B8: restore fx2's LSTM->fxcm wiring (make LSTMPR=1)
ifdef LSTMPR
$(info LSTMPR defined)
CPPFLAGS_PART-THAT-SHOULD-BE-FAST += -DLSTMPR
endif

# F-B8: lstmex selector. LSTMEX=1 = extra mxA[18] mixer; LSTMEX=2 = replace mxA[9].cxt with fx2's formula
ifdef LSTMEX
$(info LSTMEX=$(LSTMEX) defined)
CPPFLAGS_PART-THAT-SHOULD-BE-FAST += -DLSTMEX=$(LSTMEX)
endif

# F-B2/G6: restore fx2's 7 SSCM inputs (make SCMA7=1)
ifdef SCMA7
$(info SCMA7 defined)
CPPFLAGS_PART-THAT-SHOULD-BE-FAST += -DSCMA7
endif

ifdef G2MEM
$(info G2MEM defined)
CPPFLAGS_PART-THAT-SHOULD-BE-FAST += -DG2MEM
CPPFLAGS_PART-THAT-CAN-BE-SLOW    += -DG2MEM
CPPFLAGS_PART-THAT-IS-COLD        += -DG2MEM
endif

# F-A6/G4: generic-mixer context-cap arms (mixer.h has the memory math).
# Arms: (a) make MIXER_LIMIT=40000  (b) make MIXER_LIMIT_BIG=65536
#       (c) make MIXER_LIMIT=2000   (+ MIXER_CAP_STATS=1 for saturation report)
# NOTE: mixer.h/predictor.h are also included by runner.cpp (COLD) and
# preprocessor.h (SLOW), and Predictor embeds Mixer by value — the macros
# change class layout, so they must reach ALL three parts (ODR).
ifdef MIXER_LIMIT
$(info MIXER_LIMIT=$(MIXER_LIMIT) defined)
CPPFLAGS_PART-THAT-SHOULD-BE-FAST += -DMIXER_LIMIT=$(MIXER_LIMIT)
CPPFLAGS_PART-THAT-CAN-BE-SLOW    += -DMIXER_LIMIT=$(MIXER_LIMIT)
CPPFLAGS_PART-THAT-IS-COLD        += -DMIXER_LIMIT=$(MIXER_LIMIT)
endif

ifdef MIXER_LIMIT_BIG
$(info MIXER_LIMIT_BIG=$(MIXER_LIMIT_BIG) defined)
CPPFLAGS_PART-THAT-SHOULD-BE-FAST += -DMIXER_LIMIT_BIG=$(MIXER_LIMIT_BIG)
CPPFLAGS_PART-THAT-CAN-BE-SLOW    += -DMIXER_LIMIT_BIG=$(MIXER_LIMIT_BIG)
CPPFLAGS_PART-THAT-IS-COLD        += -DMIXER_LIMIT_BIG=$(MIXER_LIMIT_BIG)
endif

ifdef MIXER_CAP_STATS
$(info MIXER_CAP_STATS defined)
CPPFLAGS_PART-THAT-SHOULD-BE-FAST += -DMIXER_CAP_STATS
CPPFLAGS_PART-THAT-CAN-BE-SLOW    += -DMIXER_CAP_STATS
CPPFLAGS_PART-THAT-IS-COLD        += -DMIXER_CAP_STATS
endif

# ---- b51 SMAP_SAT (2026-07-20) ----------------------------------------------
# GRAMMARMATCH-pattern block: top-level, the define reaches ALL THREE flag
# groups (the ODR rule -- predictor.h reaches SLOW via preprocessor.h and
# COLD via runner.cpp).
# SMAP_SAT=1: THE FIX sized by SMAP_WRAPCOUNT (b46 measured, 10MB dict-mode:
# cm3=8, all other counters 0 — ContextMap3's ts[] accumulator genuinely
# wraps in production). Makes the ContextMap3::update ts[] add SATURATING at
# that ONE site only: when p0 += (y<<18) - (p0>>14) would carry out of the
# U32 (stock: prediction crashes from ~max to ~0 and relearns), clamp ts[]
# to 0xFFFFFFFF instead. Arithmetic is byte-for-byte the stock statement in
# every non-overflow case — semantics differ ONLY where stock would wrap.
# StateMap/StateMap1/StationaryMap update sites are untouched (counters 0).
# Independent of SMAP_WRAPCOUNT: both may be on; the cm3 counter then counts
# would-have-wrapped (= clamped) events. NOTE the counters are NOT comparable
# across arms: stock wraps once and restarts near 0, SAT holds the ceiling,
# so every further 1-update during the pure run re-triggers the clamp —
# cm3(SAT) >= cm3(stock). fxcmv1.cpp is FAST-only, three groups per the rule.
ifdef SMAP_SAT
$(info SMAP_SAT defined)
CPPFLAGS_PART-THAT-SHOULD-BE-FAST += -DSMAP_SAT
CPPFLAGS_PART-THAT-CAN-BE-SLOW    += -DSMAP_SAT
CPPFLAGS_PART-THAT-IS-COLD        += -DSMAP_SAT
endif
# ------------------------------------------------------------------------------

# ---- Mixer/SSE numerics-audit value knobs (2026-07-12) ----------------------
# Every knob defaults (via #ifndef in the source) to the stock constant, so
# any subset can be armed and the rest stay token-identical. None change class
# layout, but per the ODR rule they all go to ALL THREE flag groups. Docs:
# mixer.cpp (decay schedule), mixer.h (MIXER_WINIT), predictor.cpp AddMixers
# (LR knobs), sse.cpp (Shelwien-stage knobs), fxcmv1.cpp above Mixer1 (FXCM_*).
#   make MIXER_DECAY_FINAL=0.1f MIXER_DECAY_T3=50000000 ... etc.
NUMERIC_AUDIT_KNOBS := MIXER_DECAY_FINAL MIXER_WINIT MIXER_L1_LR \
    SSE_QUANT SSE_X1WR SSE_X2WR SSE_SM6WRB SSE_SM7WRB \
    FXCM_UPERR_DIV FXCM_MX_PSHIFT FXCM_WINIT FXCM_ELIM_LO \
    FXCM_RATE_BASE FXCM_RATE_T1 FXCM_RATE_T2
# Top-level foreach (NOT nested in any feature ifdef — the PRETRAIN rule);
# each defined knob K adds -DK=$(K) to FAST+SLOW+COLD, exactly like the
# GRAMMARMATCH-pattern blocks above, just generated.
define NUMERIC_KNOB_template
ifdef $(1)
$$(info $(1)=$$($(1)) defined)
CPPFLAGS_PART-THAT-SHOULD-BE-FAST += -D$(1)=$$($(1))
CPPFLAGS_PART-THAT-CAN-BE-SLOW    += -D$(1)=$$($(1))
CPPFLAGS_PART-THAT-IS-COLD        += -D$(1)=$$($(1))
endif
endef
$(foreach knob,$(NUMERIC_AUDIT_KNOBS),$(eval $(call NUMERIC_KNOB_template,$(knob))))
# ------------------------------------------------------------------------------

# Quantized LSTM gate-matvec forward pass (make LSTM_QUANT=8|16):
# 8 = int8 (AVX512-VNNI when available, exact AVX2 fallback), 16 = int16
# (AVX2 vpmaddwd + i64 accumulation). Must reach every part: runner.cpp
# (COLD) includes predictor.h -> lstm.h, and the flag changes LstmLayer's
# layout (ODR). LSTM_QUANT_VERIFY=1 adds a runtime scalar crosscheck of the
# SIMD kernels (VM gate tool; slow).
ifdef LSTM_QUANT
$(info LSTM_QUANT=$(LSTM_QUANT) defined)
CPPFLAGS_PART-THAT-SHOULD-BE-FAST += -DLSTM_QUANT=$(LSTM_QUANT)
CPPFLAGS_PART-THAT-CAN-BE-SLOW    += -DLSTM_QUANT=$(LSTM_QUANT)
CPPFLAGS_PART-THAT-IS-COLD        += -DLSTM_QUANT=$(LSTM_QUANT)
ifdef LSTM_QUANT_VERIFY
$(info LSTM_QUANT_VERIFY defined)
CPPFLAGS_PART-THAT-SHOULD-BE-FAST += -DLSTM_QUANT_VERIFY
CPPFLAGS_PART-THAT-CAN-BE-SLOW    += -DLSTM_QUANT_VERIFY
CPPFLAGS_PART-THAT-IS-COLD        += -DLSTM_QUANT_VERIFY
endif
endif

LFLAGS := -m64 -fuse-ld=lld -Wl,--gc-sections -std=c++17
# b53: lld identical-code-fold + linker -O2 (b40 proved -c output identity for
# icf+O2 vs stock on this binary; +2,136 S banked). LFLAGS is := with no env
# passthrough and build_and_construct_comp.sh word-splits MAKE_VARS (no safe
# way to carry a spaced LFLAGS override), so the fold rides in the tree.
LFLAGS += -Wl,--icf=all -Wl,-O2


prof_gen: CPPFLAGS_PART-THAT-CAN-BE-SLOW    += -fprofile-generate=$(ROOT_DIR)/pgo_data
prof_gen: CPPFLAGS_PART-THAT-SHOULD-BE-FAST += -fprofile-generate=$(ROOT_DIR)/pgo_data
prof_gen: CPPFLAGS_PART-THAT-IS-COLD        += -fprofile-generate=$(ROOT_DIR)/pgo_data
prof_gen: LFLAGS                            += -fprofile-generate=$(ROOT_DIR)/pgo_data
prof_gen: clean cmix

prof_use: CPPFLAGS_PART-THAT-CAN-BE-SLOW    += -fprofile-use=$(ROOT_DIR)/pgo_data
prof_use: CPPFLAGS_PART-THAT-SHOULD-BE-FAST += -fprofile-use=$(ROOT_DIR)/pgo_data -flto
prof_use: CPPFLAGS_PART-THAT-IS-COLD        += -fprofile-use=$(ROOT_DIR)/pgo_data
prof_use: LFLAGS                            += -fprofile-use=$(ROOT_DIR)/pgo_data -flto
prof_use: clean cmix


slow: src/preprocess/preprocessor.cpp src/preprocess/preprocessor.h src/preprocess/dictionary.cpp src/preprocess/dictionary.h
	$(CC) $(CPPFLAGS_PART-THAT-CAN-BE-SLOW) src/preprocess/preprocessor.cpp src/preprocess/dictionary.cpp -c 

cold: src/r1_reorder_transform.cpp src/r1_reorder_transform.h src/runner.cpp
	$(CC) $(CPPFLAGS_PART-THAT-IS-COLD) src/r1_reorder_transform.cpp $(COLD_EXTRA_SRC) src/runner.cpp -c

fast: src/coder/decoder.cpp src/coder/decoder.h src/coder/encoder.cpp src/coder/encoder.h src/context-manager.cpp src/context-manager.h src/contexts/bit-context.cpp src/contexts/bit-context.h src/contexts/bracket-context.cpp src/contexts/bracket-context.h src/contexts/combined-context.cpp src/contexts/combined-context.h src/contexts/context-hash.cpp src/contexts/context-hash.h src/contexts/context.h src/contexts/indirect-hash.cpp src/contexts/indirect-hash.h src/contexts/interval-hash.cpp src/contexts/interval-hash.h src/contexts/interval.cpp src/contexts/interval.h src/contexts/sparse.cpp src/contexts/sparse.h  src/models/bracket.cpp src/models/bracket.h src/models/byte-model.cpp src/models/byte-model.h src/models/direct-hash.cpp src/models/direct-hash.h src/models/direct.cpp src/models/direct.h src/models/indirect.cpp src/models/indirect.h src/models/match.cpp src/models/match.h src/models/grammar-match.cpp src/models/grammar-match.h src/models/model.h src/models/fxcmv1.h src/models/ppmd.cpp src/models/ppmd.h src/states/nonstationary.cpp src/states/nonstationary.h src/states/run-map.cpp src/states/run-map.h src/states/state.h src/mixer/byte-mixer.cpp src/mixer/byte-mixer.h src/mixer/lstm-layer.h src/mixer/lstm.h src/mixer/mixer-input.cpp src/mixer/mixer-input.h src/mixer/mixer.cpp src/mixer/mixer.h src/mixer/sigmoid.cpp src/mixer/sigmoid.h src/mixer/sse.cpp src/mixer/sse.h src/predictor.h src/predictor.cpp
	$(CC) $(CPPFLAGS_PART-THAT-SHOULD-BE-FAST) src/coder/decoder.cpp src/coder/encoder.cpp src/context-manager.cpp src/contexts/bit-context.cpp src/contexts/bracket-context.cpp src/contexts/combined-context.cpp src/contexts/context-hash.cpp src/contexts/indirect-hash.cpp src/contexts/interval-hash.cpp src/contexts/interval.cpp src/contexts/sparse.cpp src/models/bracket.cpp src/models/byte-model.cpp src/models/direct-hash.cpp src/models/direct.cpp src/models/indirect.cpp src/models/match.cpp src/models/fxcmv1.cpp src/models/ppmd.cpp src/states/nonstationary.cpp src/states/run-map.cpp src/mixer/byte-mixer.cpp src/mixer/mixer-input.cpp src/mixer/mixer.cpp src/mixer/sigmoid.cpp src/mixer/sse.cpp $(FAST_EXTRA_SRC) -c src/predictor.cpp

cmix: fast slow cold
	$(CC) $(LFLAGS) bit-context.o bracket-context.o bracket.o byte-mixer.o byte-model.o combined-context.o context-hash.o context-manager.o decoder.o dictionary.o direct-hash.o direct.o encoder.o indirect-hash.o indirect.o interval-hash.o interval.o match.o mixer-input.o mixer.o nonstationary.o fxcmv1.o ppmd.o predictor.o preprocessor.o r1_reorder_transform.o $(COLD_EXTRA_OBJ) $(FAST_EXTRA_OBJ) run-map.o runner.o sigmoid.o sparse.o sse.o -s -o cmix
	rm -f *.o

remap: src/readalike_prepr/article_remap.cpp
	$(CC) src/readalike_prepr/article_remap.cpp -o remap

clean:
	rm -f *.o
	rm -f cmix
	rm -f remap

all: cmix remap
