# SQLite loadable extension around the portable pg_sift sources.
#   nice make -j4                builds build/sift.so (load with .load build/sift); SIFT_VERSION=x.y.z overrides the version in VERSION
#   make test                    ./test.sh
#   make col-smoke              compiles src/portable-col.list (the column engine) on this host, links it with the portable core and runs
#                               tests/col_smoke.c: unit encode/parse round trip. The column engine is not in sift.so yet (docs/COL-PORTS.md)
#   make isa-check               no POPCNT or vector instruction outside the AVX2/AVX-512 objects (the baseline runs on any x86-64)
#   make SIMD=none B=build-scalar   no vector code at all (the vector levels dispatch to the scalar tables): the compile-time scalar build
#   make asan | tsan | tsan-run  sanitizer builds (build-asan/, build-tsan/); tsan-run also runs the C stress test on the TSan build
#   make span-test               the unit and adversarial tests of src/core/span.h (docs/SAFETY.md); make span-test-asan under ASan+UBSan;
#                                make SIMD=none B=build-scalar span-test with the vector-free flags
#   make cross-aarch64           compiles everything for aarch64 with clang (no execution)
# The sources of ../src listed in portable.list are compiled unmodified, against the host implementation in host/ (they include
# only src/core/host.h, which selects it with -DSIFT_HOST_SQLITE), except one mechanical change: bm25/prefix.c's one entry point is renamed so that init.c can serialize it.
SRC := ../src
include $(SRC)/areas.mk
B ?= build
# The release version: the repository tag without the "v" (scripts/package-sqlite.sh checks that VERSION matches the tag)
SIFT_VERSION ?= $(shell cat VERSION)
CC ?= cc
OPT ?= -O2
ARCH := $(firstword $(subst -, ,$(shell $(CC) -dumpmachine)))
SIMD ?= auto
# SIFT_ISA: the x86-64 baseline, SIMD kernels opt in per function (see src/core/cpu.h); -ffp-contract=off keeps scores bit-identical.
# Other architectures have no vector kernels (SIMD=none): the vector levels dispatch to the scalar tables, and the x86 CPU probes
# of bm25/bitmap.c answer "no"
ifeq ($(ARCH),x86_64)
ISA := -march=x86-64 -mtune=generic
else
SIMD := none
CPUPROBE := '-D__builtin_cpu_init()=' '-D__builtin_cpu_supports(x)=0'
endif
SIMD_SRC := bm25/bitmap_avx2 bm25/bitmap_avx512 bm25/kernels_avx2 bm25/kernels_avx512
ifeq ($(SIMD),none)
EXTRA += -DSQ_SIMD_NONE
ALIASES := -Dsift_kern_avx2=sift_kern_scalar -Dsift_kern_avx512=sift_kern_scalar -Dsift_kernels_avx2=sift_kernels_scalar \
	-Dsift_kernels_avx2_nopdep=sift_kernels_scalar -Dsift_kernels_avx512=sift_kernels_scalar $(CPUPROBE)
endif
CFLAGS += $(OPT) -g -ffp-contract=off -fwrapv -fno-strict-aliasing -fPIC -Wall -Wno-misleading-indentation \
	-Wno-unused-function -Wno-declaration-after-statement -MMD -D_GNU_SOURCE '-DSQ_VERSION="$(SIFT_VERSION)"' $(ISA) $(EXTRA) -DSIFT_HOST_SQLITE -Ihost $(SIFT_INC) -I.
LDLIBS := -lm -lpthread

# the portable core, one list for every host and for the check (scripts/check-portable.sh); the vector files only when there are vector kernels
PORTABLE := $(filter-out $(if $(filter none,$(SIMD)),$(SIMD_SRC)),$(basename $(filter %.c,$(shell grep -v '^[\#]' $(SRC)/portable.list))))
OWN := host container mb spool regex regex_parse regex_exec host_stubs store dir stream stubs build merge vtab content cursor write auxfn auxfn_facets pscan init
OBJS := $(addprefix $(B)/,$(addsuffix .o,$(PORTABLE) $(OWN)))

all: $(B)/sift.so

$(B)/sift.so: $(OBJS)
	$(CC) -shared $(EXTRA) -o $@ $(OBJS) $(LDLIBS)

$(B)/bm25/bitmap.o: $(SRC)/bm25/bitmap.c | $(B)
	@mkdir -p $(@D)
	$(CC) $(CFLAGS) $(ALIASES) -c -o $@ $<

# bm25/prefix.c keeps a process-wide cache of wide-prefix unions; init.c wraps the entry point in a mutex
$(B)/bm25/prefix.o: $(SRC)/bm25/prefix.c | $(B)
	@mkdir -p $(@D)
	$(CC) $(CFLAGS) -Dsift_prefix_union=sq_real_sift_prefix_union -c -o $@ $<

# SIMD=none: the dispatch tables of the vector levels are the scalar ones (the vector files are not built)
$(B)/bm25/kernels.o: $(SRC)/bm25/kernels.c | $(B)
	@mkdir -p $(@D)
	$(CC) $(CFLAGS) $(ALIASES) -c -o $@ $<

# sift_version() reports it: rebuild init.o when it changes
$(B)/gen/version: FORCE | $(B)/gen
	@echo '$(SIFT_VERSION)' | cmp -s - $@ || echo '$(SIFT_VERSION)' > $@
$(B)/init.o: $(B)/gen/version
FORCE:

$(B)/%.o: $(SRC)/%.c | $(B)
	@mkdir -p $(@D)
	$(CC) $(CFLAGS) -c -o $@ $<

$(B)/%.o: %.c | $(B)
	$(CC) $(CFLAGS) -c -o $@ $<

$(B)/%.o: host/%.c | $(B)
	$(CC) $(CFLAGS) -c -o $@ $<

$(B) $(B)/gen:
	mkdir -p $@

check test: all
	./test.sh

# The fallback path must run on any x86-64: outside the AVX2 and AVX-512 objects there is no POPCNT or 256/512-bit instruction, and
# nothing links libgcc's __popcountdi2 (a distribution may build it with POPCNT). Release builds run this (scripts/package-sqlite.sh).
isa-check: all
	@sh ../scripts/port-isa-check.sh $(B)/sift.so $(B)/*.o $(B)/*/*.o

# The C stress test: N threads, each with a connection, querying while one writes. The extension is linked in and
# registered with sqlite3_auto_extension.
$(B)/stress: tests/stress.c $(OBJS) | $(B)
	$(CC) $(CFLAGS) -o $@ tests/stress.c $(OBJS) $(LDLIBS) -lsqlite3

# the column engine of src/portable-col.list, unmodified, against this host; SIMD=none leaves out its vector files and aliases their tables
COLSRC := $(basename $(filter %.c,$(shell grep -v '^[\#]' $(SRC)/portable-col.list)))
COLSRC := $(filter-out $(if $(filter none,$(SIMD)),$(foreach s,$(COLSRC),$(if $(filter %_avx2 %_avx512,$(s)),$(s)))),$(COLSRC))
COLOBJS := $(addprefix $(B)/,$(addsuffix .o,$(COLSRC)))
COLALIASES := $(if $(filter none,$(SIMD)),$(foreach l,avx2 avx512,-Dsift_col_kernels_$(l)=sift_col_kernels_scalar -Dsift_cc_kernels_$(l)=sift_cc_kernels_scalar \
	-Dsift_shred_kernels_$(l)=sift_shred_kernels_scalar -Dsift_aggkern_$(l)=sift_aggkern_scalar))
$(COLOBJS): $(B)/%.o: $(SRC)/%.c | $(B)
	@mkdir -p $(@D)
	$(CC) $(CFLAGS) $(COLALIASES) -c -o $@ $<

$(B)/col-smoke: tests/col_smoke.c $(COLOBJS) $(OBJS) | $(B)
	$(CC) $(CFLAGS) -o $@ tests/col_smoke.c $(COLOBJS) $(OBJS) $(LDLIBS)

col-smoke: $(B)/col-smoke
	$(B)/col-smoke

# the checked reader of the portable core: C library only, no host, no database
$(B)/span_test: tests/span_test.c $(SRC)/core/span.h $(SRC)/core/varint.h | $(B)
	$(CC) $(CFLAGS) -Wextra -Wno-sign-compare -Wno-unused-parameter -o $@ tests/span_test.c

span-test: $(B)/span_test
	$(B)/span_test

# the multiply-fold mixers: their outputs are pinned (group output order depends on them)
$(B)/hash_test: tests/hash_test.c $(SRC)/core/hash.h | $(B)
	$(CC) $(CFLAGS) -Wextra -o $@ tests/hash_test.c

hash-test: $(B)/hash_test
	$(B)/hash_test

span-test-asan:
	$(MAKE) B=$(B)-asan EXTRA="-fsanitize=address,undefined -fno-omit-frame-pointer -fno-sanitize-recover=all" OPT=-O1 $(B)-asan/span_test
	$(B)-asan/span_test

asan:
	$(MAKE) B=build-asan EXTRA="-fsanitize=address,undefined -fno-omit-frame-pointer -fno-sanitize-recover=undefined -fno-sanitize=nonnull-attribute" OPT=-O1 build-asan/sift.so build-asan/stress

tsan:
	$(MAKE) B=build-tsan EXTRA="-fsanitize=thread -fno-omit-frame-pointer" OPT=-O1 build-tsan/stress

# the stress test on the TSan build: separate connections, one shared connection, with and without parallel scans
tsan-run: tsan
	for a in "-m multi -r 4" "-m serialized -r 4" "-c -r 4" "-m multi -r 3 -p 3" "-c -r 3 -p 3"; do \
		TSAN_OPTIONS="suppressions=$(CURDIR)/tests/tsan.supp exitcode=66" build-tsan/stress -s 10 $$a || exit 1; done

# Without an aarch64 sysroot the build machine's x86-64 glibc headers stand in: bits/floatn.h is skipped and bits/wordsize.h and bits/pthreadtypes-arch.h are
# replaced by LP64 ones (generated), so only the types and the code are checked, nothing is linked or run.
cross-aarch64:
	mkdir -p build-aarch64/gen/inc/bits
	printf '#define __WORDSIZE 64\n#define __WORDSIZE_TIME64_COMPAT32 1\n#define __SYSCALL_WORDSIZE 64\n' > build-aarch64/gen/inc/bits/wordsize.h
	sed 's/__attribute__ ((__regparm__ (1)))//' /usr/include/bits/pthreadtypes-arch.h > build-aarch64/gen/inc/bits/pthreadtypes-arch.h
	$(MAKE) B=build-aarch64 CC="clang --target=aarch64-linux-gnu" EXTRA="-fsyntax-only -Wno-unused-command-line-argument -D_BITS_FLOATN_H -isystem build-aarch64/gen/inc" cross-objs

cross-objs: $(OBJS)

clean:
	rm -rf build build-asan build-tsan build-scalar build-aarch64

-include $(wildcard $(B)/*.d $(B)/*/*.d)

.PHONY: FORCE all check test col-smoke isa-check clean asan tsan tsan-run cross-aarch64 cross-objs span-test span-test-asan hash-test
