# crpreload: CORE-MATH's 76 elementary functions under their C library
# names (crpreload.c). The CORE-MATH sources are crmvec's (../crmvec, the
# Makefile's CR list), so the two never drift; they are compiled with
# hidden visibility, so only the libm names are exported. CM is crmvec's
# directory: the parent in crmvec's repository (crpreload/), ../crmvec in the
# harness this is exported from.
#   make                 libcrpreload.so and the checks
#   make check           wiring, interposition, errno, flush-to-zero
#   make bench           ns per call against glibc
CM      ?= $(if $(wildcard ../crmvec.c),..,../crmvec)
CC      ?= gcc
CFLAGS  ?= -O2
FP      := -ffp-contract=off -frounding-math
# only the .c names: crmvec's Makefile may print a message while it is read (no MPFR 4.2), which once landed here
# as Makefile text and broke the conda build (2026-10-02)
CR      := $(addprefix $(CM)/,$(filter %.c,$(shell $(MAKE) -s -C $(CM) print-sources 2>/dev/null)))
OBJ     := $(patsubst $(CM)/%.c,obj/%.o,$(CR)) obj/crmvec-roundeven.o

ARCH    := $(shell $(CC) -dumpmachine | cut -d- -f1)
ifeq ($(ARCH),x86_64)
all: libcrpreload.so libcrpreload-generic.so libcrpreload-v3.so crpreload-check crpreload-bench
CHECKED := libcrpreload.so libcrpreload-generic.so libcrpreload-v3.so
else
# elsewhere (aarch64, 2026-10-03) one build: no x86-64-v3 copy to dispatch to, and no flush-to-zero table, so
# every call goes through crmvec-fpenv.h's guard (FPCR's FZ, FZ16 and FIZ off during the call)
all: libcrpreload.so crpreload-check crpreload-bench
CHECKED := libcrpreload.so
endif

obj/%.o: $(CM)/%.c
	@mkdir -p $(dir $@)
	$(CC) $(CFLAGS) $(FP) -fPIC -fvisibility=hidden -I$(dir $<) -c -o $@ $<

# crpreload-ftzsafe.h (generated by cfarm-ftzscan.sh) is used only for the
# objects it was proven on: their digest, in make's order, must be named in it
FTZSAFE = d="$(1) $$(cat $(sort $(2)) | sha256sum | cut -c1-64)"; f=; \
  if [ -f crpreload-ftzsafe.h ] && grep -qF "CRP_FTZSAFE_OBJECTS(\"$$d\")" crpreload-ftzsafe.h; then f=-DCRP_FTZSAFE=1; \
  else echo "$@: no flush-to-zero table for these objects ($$d): every call guarded"; fi
obj/crpreload.o: crpreload.c $(CM)/crmvec-fpenv.h $(OBJ) $(wildcard crpreload-ftzsafe.h)
	@mkdir -p obj
	@$(call FTZSAFE,generic,$(OBJ)); echo "$(CC) $(CFLAGS) $(FP) -fPIC -I$(CM) $$f -c -o $@ $<"; $(CC) $(CFLAGS) $(FP) -fPIC -I$(CM) $$f -c -o $@ $<

# the generic build alone (any x86-64)
libcrpreload-generic.so: obj/crpreload.o $(OBJ)
	$(CC) $(CFLAGS) -shared -Wl,-z,defs -Wl,-Bsymbolic-functions -Wl,-soname,libcrpreload.so $(LDFLAGS) -o $@ $^ -lm

ifneq ($(ARCH),x86_64)
libcrpreload.so: obj/crpreload.o $(OBJ)
	$(CC) $(CFLAGS) -shared -Wl,-z,defs -Wl,-Bsymbolic-functions -Wl,-soname,libcrpreload.so $(LDFLAGS) -o $@ $^ -lm
endif

# libcrpreload.so (2026-10-01): both builds, each libm name a trampoline to
# the x86-64-v3 code on a CPU with AVX2 and FMA, the generic code otherwise
# (mkdispatch.sh); the -generic and -v3 libraries force one, for the checks
ifeq ($(ARCH),x86_64)
libcrpreload.so: obj/crpreload.o objv3/crpreload.o $(OBJ) $(OBJV3) mkdispatch.sh
	bash ./mkdispatch.sh   # not ./: Nix's build sandbox has no /bin/bash for the #! line
	$(CC) -c -o objd/dispatch.o objd/dispatch.S
	$(CC) $(CFLAGS) -fPIC -c -o objd/pick.o objd/pick.c
	$(CC) $(CFLAGS) -shared -Wl,-z,defs -Wl,-Bsymbolic-functions -Wl,-soname,libcrpreload.so -Wl,--version-script=objd/exports.map $(LDFLAGS) -o $@ objd/crpreload-g.o $(OBJ) objd/crpreload-v3.o objd/v3/*.o objd/dispatch.o objd/pick.o -lm
endif

# the same library built for x86-64-v3 (AVX2, FMA inline instead of calls
# to glibc's fma); the results are the same, since fma is exact
OBJV3   := $(patsubst obj/%,objv3/%,$(OBJ))
objv3/%.o: $(CM)/%.c
	@mkdir -p $(dir $@)
	$(CC) $(CFLAGS) -march=x86-64-v3 $(FP) -fPIC -fvisibility=hidden -I$(dir $<) -c -o $@ $<
objv3/crpreload.o: crpreload.c $(CM)/crmvec-fpenv.h $(OBJV3) $(wildcard crpreload-ftzsafe.h)
	@mkdir -p objv3
	@$(call FTZSAFE,v3,$(OBJV3)); echo "$(CC) $(CFLAGS) -march=x86-64-v3 $(FP) -fPIC -I$(CM) $$f -c -o $@ $<"; $(CC) $(CFLAGS) -march=x86-64-v3 $(FP) -fPIC -I$(CM) $$f -c -o $@ $<
libcrpreload-v3.so: objv3/crpreload.o $(OBJV3)
	$(CC) $(CFLAGS) -shared -Wl,-z,defs -Wl,-Bsymbolic-functions -Wl,-soname,libcrpreload.so $(LDFLAGS) -o $@ $^ -lm

# the digest of the CORE-MATH objects each library is linked from, in the
# order make lists them: crpreload-ftzsafe.h is valid only for these bytes
objdigest: $(OBJ) $(OBJV3)
	@echo "generic $$(cat $(sort $(OBJ)) | sha256sum | cut -c1-64)"
	@echo "v3 $$(cat $(sort $(OBJV3)) | sha256sum | cut -c1-64)"
print-objects:
	@echo $(sort $(OBJ))
print-objects-v3:
	@echo $(sort $(OBJV3))

bench: $(CHECKED) crpreload-bench
	./crpreload-bench $(addprefix ./,$(filter-out libcrpreload-generic.so,$(CHECKED)))

# the checks call CORE-MATH directly (crmvec's libcrref.so) as the reference
crpreload-check: crpreload-check.c crpreload-list.h
	$(CC) -O2 $(FP) -fno-builtin -fopenmp -o $@ $< -ldl -lm
crpreload-bench: crpreload-bench.c crpreload-list.h
	$(CC) -O2 $(FP) -fno-builtin -o $@ $< -ldl -lm

check: all
	for l in $(CHECKED); do echo "== $$l"; ./crpreload-check ./$$l $(CM)/libcrref.so | tail -1; done

clean:
	rm -rf obj objv3 objd libcrpreload.so libcrpreload-generic.so libcrpreload-v3.so crpreload-check crpreload-bench
.PHONY: all check bench clean objdigest print-objects print-objects-v3
