# Makefile de BLApy.
#
# Filosofía de flags (mismo criterio documentado en bla_gemm.c y en el
# README, sección de diseño): -O3 SIEMPRE, pero SIN -march=native por
# defecto -- la detección de SIMD es en runtime (ver bla_simd.c), así
# que un binario compilado acá funciona igual en cualquier CPU x86_64
# donde se instale después, no solo en la máquina donde se compiló.
# Esto es intencional y calca la misma decisión que numerx documenta
# en su propio setup.py.
#
# Si querés exprimir el último 5-10% en TU máquina puntual (a costa de
# portabilidad), corré: make native
#
# -fopenmp -- AÑADIDO EN 0.0.5, OBLIGATORIO (no hay build sin OpenMP en
# esta versión): bla_gemm/bla_gemm_tn/bla_gemm_nt paralelizan el loop
# ic con #pragma omp (ver bla_gemm.c). Sin -fopenmp en CFLAGS, el
# compilador ignora los #pragma omp silenciosamente (el código sigue
# siendo válido C11 sin ellos -- son comentarios especiales para el
# compilador, no sintaxis nueva) y el resultado compilaría igual pero
# de un solo hilo; con -fopenmp en CFLAGS pero SIN -fopenmp en
# LDLIBS/link, en cambio, el link falla (necesita linkear contra
# libgomp) -- por eso va en ambos lados acá, no alcanza con uno solo.
CC := gcc
CFLAGS := -O3 -Wall -Wextra -std=c11 -fopenmp
LDLIBS := -lm -fopenmp

LIB_SRCS := bla_gemm.c bla_microkernel.c bla_level1.c bla_level2.c bla_simd.c \
            bla_level1_f32.c bla_gemm_f32.c bla_microkernel_f32.c bla_level2_f32.c
LIB_OBJS := $(LIB_SRCS:.c=.o)

.PHONY: all test bench clean native lib test-arm64

all: lib test

lib: libbla.so

libbla.so: $(LIB_SRCS) bla.h bla_internal.h bla_microkernel.h bla_microkernel_f32.h
	$(CC) $(CFLAGS) -fPIC -shared $(LIB_SRCS) $(LDLIBS) -o $@
	cp $@ python/$@

native: CFLAGS += -march=native
native: clean lib
	@echo "Compilado con -march=native -- este .so es específico de ESTA CPU, no lo distribuyas."

test: test_level1 test_gemv test_ger test_gemm test_gemm_trans test_level1_f32 test_gemm_f32 test_gemv_f32 test_sger test_gemm_trans_f32 test_gemm_thread_stress test_sgemm_thread_stress test_level2_sym_tri test_level2_sym_tri_f32 test_level3_sym_tri test_level3_sym_tri_f32
	@echo "--- nivel 1 ---"
	./test_level1
	@echo "--- gemv (nivel 2) ---"
	./test_gemv
	@echo "--- ger (nivel 2) ---"
	./test_ger
	@echo "--- symv/trmv/trsv (nivel 2, matrices simétricas/triangulares, AÑADIDO EN 0.0.6) ---"
	./test_level2_sym_tri
	@echo "--- gemm (nivel 3) ---"
	./test_gemm
	@echo "--- gemm_tn / gemm_nt (nivel 3, añadido en 0.0.2) ---"
	./test_gemm_trans
	@echo "--- symm/trmm/trsm/syrk (nivel 3, matrices simétricas/triangulares, AÑADIDO EN 0.0.6) ---"
	./test_level3_sym_tri
	@echo "--- sdot/saxpy/sscal/snrm2/sasum/sswap/scopy/isamax/srot, float32 nivel 1 (sdot en 0.0.3, saxpy/sscal en 0.0.4, snrm2/sasum en 0.0.5, sswap/scopy/isamax/srot en 0.0.6) ---"
	./test_level1_f32
	@echo "--- sgemm, float32 nivel 3 (añadido en 0.0.3) ---"
	./test_gemm_f32
	@echo "--- sgemv, float32 nivel 2 (añadido en 0.0.4) ---"
	./test_gemv_f32
	@echo "--- sger, float32 nivel 2 (AÑADIDO EN 0.0.5, completa el nivel 2 float32) ---"
	./test_sger
	@echo "--- ssymv/strmv/strsv, float32 nivel 2 (AÑADIDO EN 0.0.6) ---"
	./test_level2_sym_tri_f32
	@echo "--- sgemm_tn / sgemm_nt, float32 nivel 3 (AÑADIDO EN 0.0.5, completa las variantes transpuestas) ---"
	./test_gemm_trans_f32
	@echo "--- ssymm/strmm/strsm/ssyrk, float32 nivel 3 (AÑADIDO EN 0.0.6) ---"
	./test_level3_sym_tri_f32
	@echo "--- estrés de paralelización OpenMP, gemm double (AÑADIDO EN 0.0.5) ---"
	./test_gemm_thread_stress
	@echo "--- estrés de paralelización OpenMP, sgemm float32 (AÑADIDO EN 0.0.5) ---"
	./test_sgemm_thread_stress

test_level1: tests/test_level1.c bla_level1.c bla_simd.c bla.h
	$(CC) $(CFLAGS) -I. tests/test_level1.c bla_level1.c bla_simd.c $(LDLIBS) -o $@

test_gemv: tests/test_gemv.c bla_level2.c bla_level1.c bla_simd.c bla.h
	$(CC) $(CFLAGS) -I. tests/test_gemv.c bla_level2.c bla_level1.c bla_simd.c $(LDLIBS) -o $@

test_ger: tests/test_ger.c bla_level2.c bla_level1.c bla_simd.c bla.h
	$(CC) $(CFLAGS) -I. tests/test_ger.c bla_level2.c bla_level1.c bla_simd.c $(LDLIBS) -o $@

test_level2_sym_tri: tests/test_level2_sym_tri.c bla_level2.c bla_level1.c bla_simd.c bla.h
	$(CC) $(CFLAGS) -I. tests/test_level2_sym_tri.c bla_level2.c bla_level1.c bla_simd.c $(LDLIBS) -o $@

test_gemm: tests/test_gemm.c bla_gemm.c bla_microkernel.c bla_simd.c bla.h
	$(CC) $(CFLAGS) -I. tests/test_gemm.c bla_gemm.c bla_microkernel.c bla_simd.c $(LDLIBS) -o $@

test_level3_sym_tri: tests/test_level3_sym_tri.c bla_gemm.c bla_microkernel.c bla_simd.c bla.h
	$(CC) $(CFLAGS) -I. tests/test_level3_sym_tri.c bla_gemm.c bla_microkernel.c bla_simd.c $(LDLIBS) -o $@

test_gemm_thread_stress: tests/test_gemm_thread_stress.c bla_gemm.c bla_microkernel.c bla_simd.c bla.h
	$(CC) $(CFLAGS) -I. tests/test_gemm_thread_stress.c bla_gemm.c bla_microkernel.c bla_simd.c $(LDLIBS) -o $@

test_gemm_trans: tests/test_gemm_trans.c bla_gemm.c bla_microkernel.c bla_simd.c bla.h
	$(CC) $(CFLAGS) -I. tests/test_gemm_trans.c bla_gemm.c bla_microkernel.c bla_simd.c $(LDLIBS) -o $@

test_level1_f32: tests/test_level1_f32.c bla_level1_f32.c bla_simd.c bla.h
	$(CC) $(CFLAGS) -I. tests/test_level1_f32.c bla_level1_f32.c bla_simd.c $(LDLIBS) -o $@

test_gemm_f32: tests/test_gemm_f32.c bla_gemm_f32.c bla_microkernel_f32.c bla_simd.c bla.h
	$(CC) $(CFLAGS) -I. tests/test_gemm_f32.c bla_gemm_f32.c bla_microkernel_f32.c bla_simd.c $(LDLIBS) -o $@

test_level3_sym_tri_f32: tests/test_level3_sym_tri_f32.c bla_gemm_f32.c bla_microkernel_f32.c bla_simd.c bla.h
	$(CC) $(CFLAGS) -I. tests/test_level3_sym_tri_f32.c bla_gemm_f32.c bla_microkernel_f32.c bla_simd.c $(LDLIBS) -o $@

test_sgemm_thread_stress: tests/test_sgemm_thread_stress.c bla_gemm_f32.c bla_microkernel_f32.c bla_simd.c bla.h
	$(CC) $(CFLAGS) -I. tests/test_sgemm_thread_stress.c bla_gemm_f32.c bla_microkernel_f32.c bla_simd.c $(LDLIBS) -o $@

test_gemv_f32: tests/test_gemv_f32.c bla_level2_f32.c bla_level1_f32.c bla_simd.c bla.h
	$(CC) $(CFLAGS) -I. tests/test_gemv_f32.c bla_level2_f32.c bla_level1_f32.c bla_simd.c $(LDLIBS) -o $@

test_gemm_trans_f32: tests/test_gemm_trans_f32.c bla_gemm_f32.c bla_microkernel_f32.c bla_simd.c bla.h
	$(CC) $(CFLAGS) -I. tests/test_gemm_trans_f32.c bla_gemm_f32.c bla_microkernel_f32.c bla_simd.c $(LDLIBS) -o $@

test_sger: tests/test_sger.c bla_level2_f32.c bla_level1_f32.c bla_simd.c bla.h
	$(CC) $(CFLAGS) -I. tests/test_sger.c bla_level2_f32.c bla_level1_f32.c bla_simd.c $(LDLIBS) -o $@

test_level2_sym_tri_f32: tests/test_level2_sym_tri_f32.c bla_level2_f32.c bla_level1_f32.c bla_simd.c bla.h
	$(CC) $(CFLAGS) -I. tests/test_level2_sym_tri_f32.c bla_level2_f32.c bla_level1_f32.c bla_simd.c $(LDLIBS) -o $@

# Corre la misma suite de tests, pero en un binario ARM64 real (vía
# cross-compiler + QEMU user-mode) -- ver tests/run_arm64_tests.sh
# para el detalle y requisitos. Uso:
#   make test-arm64 ARM_TOOLCHAIN=/ruta/al/toolchain ARM_QEMU=/ruta/a/qemu-aarch64
test-arm64:
	tests/run_arm64_tests.sh "$(ARM_TOOLCHAIN)" "$(ARM_QEMU)"

bench: lib
	python3 bench/compare_numerx.py

# Mide bla_gemm forzando cada nivel SIMD (avx512/avx2/sse2/none), sin
# necesitar hardware distinto -- ver bench/bla_simd_forced.c. Este
# binario usa bla_gemm.c/bla_microkernel.c/bla_level1.c/bla_level2.c
# REALES (el mismo código que libbla.so), solo con la detección de
# CPU reemplazada por la variable de entorno BLA_FORCE_SIMD.
bench-forced: bench/bench_forced.c bench/bla_simd_forced.c bla_gemm.c bla_microkernel.c bla_level1.c bla_level2.c
	$(CC) $(CFLAGS) -I. bench/bench_forced.c bla_gemm.c bla_microkernel.c bla_level1.c bla_level2.c bench/bla_simd_forced.c $(LDLIBS) -o $@
	@echo "--- avx512 ---"; BLA_FORCE_SIMD=avx512 ./$@
	@echo "--- avx2 ---";   BLA_FORCE_SIMD=avx2   ./$@
	@echo "--- sse2 ---";   BLA_FORCE_SIMD=sse2   ./$@
	@echo "--- none ---";   BLA_FORCE_SIMD=none   ./$@

clean:
	rm -f *.o *.so python/*.so test_level1 test_gemv test_ger test_gemm test_gemm_trans test_level1_f32 test_gemm_f32 test_gemv_f32 test_sger test_gemm_trans_f32 test_gemm_thread_stress test_sgemm_thread_stress test_level2_sym_tri test_level2_sym_tri_f32 test_level3_sym_tri test_level3_sym_tri_f32 bench-forced
