TOOLCHAIN_PREFIX ?= riscv64-unknown-elf-
TOOLCHAIN_LIBS = -lgcc

ifeq ($(DEV_ENV_HAS_TOOLCHAIN),yes)
TOOLCHAIN_INCS=\
	-I/usr/riscv64-linux-gnu/include/c++/14 \
	-I/usr/riscv64-linux-gnu/include/c++/14/riscv64-linux-gnu \
	-I/usr/riscv64-linux-gnu/include
else
TOOLCHAIN_INCS=
endif

EMULATOR_SRC_DIR = ../src
THIRD_PARTY_DIR := ../third-party

ifeq ($(UNAME),Darwin)
HOST_CXX := clang++
HOST_CC := clang
else
HOST_CXX := g++
HOST_CC := gcc
endif
HOST_CFLAGS := -I$(EMULATOR_SRC_DIR)
HOST_CXXFLAGS := -std=c++23

CC := $(TOOLCHAIN_PREFIX)gcc
LD := $(TOOLCHAIN_PREFIX)ld
CXX := $(TOOLCHAIN_PREFIX)g++
OBJCOPY := $(TOOLCHAIN_PREFIX)objcopy
OBJDUMP := $(TOOLCHAIN_PREFIX)objdump

# Instructions supported by the microarchitecture interpreter (rv64ui)
SUPPORTED_UARCH_INSN := add|addi|addiw|addw|and|andi|auipc|beq|bge|bgeu|blt|bltu|bne|ebreak|ecall|fence|jal|jalr|lb|lbu|ld|lh|$\
	lhu|lui|lw|lwu|or|ori|sb|sd|sh|sll|slli|slliw|sllw|slt|slti|sltiu|sltu|sra|srai|sraiw|sraw|srl|srli|srliw|srlw|sub|$\
	subw|sw|xor|xori

OPTFLAGS=-O2 -g0
GC_SECTIONS=-Wl,--gc-sections

# -ffunction-sections + -fno-inline keep every function out-of-line in its own
# section so each uarch PC resolves to exactly one function/line. Without them,
# -O0 still folds duplicate template instantiations and produces overlapping
# symbol ranges, which makes addr2line misattribute PCs (e.g. crediting unrelated
# handlers) when merging uarch coverage. This binary is only used to collect PCs
# for coverage, never for proofs, so layout changes here are safe.
COV_OPTFLAGS=-O0 -g -DCODE_COVERAGE -Wno-stringop-overread -ffunction-sections -fno-inline -fno-inline-functions-called-once
COV_GC_SECTIONS=

# Asserts are always enabled by now, but in the far future we should disable them
# OPTFLAGS+=-DNDEBUG

# Flags to minimize undefined behavior
UBFLAGS := -fno-strict-aliasing -fno-strict-overflow -fno-delete-null-pointer-checks
WARNFLAGS := -Wall -Wextra -Wpedantic -Wno-array-bounds -Werror

BASEFLAGS := -march=rv64i -mabi=lp64 $(UBFLAGS) $(WARNFLAGS) \
	-DMICROARCHITECTURE=1 \
	-DNO_STD_VECTOR=1 \
	-DAVOID_NATIVE_UINT128_T=1 \
	-ffreestanding \
	-nostartfiles \
	-nostdlib \
	-fno-exceptions \
	-mstrict-align \
	-mcmodel=medany -static -fvisibility=hidden \
	-I. \
	-I$(THIRD_PARTY_DIR)/llvm-flang-uint128 \
	-I$(THIRD_PARTY_DIR) \
	-I$(EMULATOR_SRC_DIR) \
	$(UARCH_DEFS) \
	$(TOOLCHAIN_INCS)

CFLAGS := $(BASEFLAGS) $(GC_SECTIONS) $(OPTFLAGS)

CXXFLAGS := -std=c++23 -fno-rtti \
	-fno-threadsafe-statics \
	-fno-use-cxa-atexit

UARCH_SOURCES=\
	uarch-run.cpp \
	uarch-ecall.c \
	uarch-runtime.cpp

UARCH_THIRDPARTY_SOURCES_C=\
	$(THIRD_PARTY_DIR)/printf/printf.c

EMULATOR_SOURCES=\
	$(EMULATOR_SRC_DIR)/interpret.cpp \
	$(EMULATOR_SRC_DIR)/htif-address-range.cpp \
	$(EMULATOR_SRC_DIR)/plic-address-range.cpp \
	$(EMULATOR_SRC_DIR)/clint-address-range.cpp

COMPUTE_SOURCES=\
	compute-uarch-pristine-hash.cpp \
	$(EMULATOR_SRC_DIR)/back-merkle-tree.cpp \
	$(EMULATOR_SRC_DIR)/keccak-256-hasher.cpp \
	$(EMULATOR_SRC_DIR)/sha-256-hasher.cpp \
	$(EMULATOR_SRC_DIR)/is-pristine.cpp \
	uarch-pristine-ram.c

UARCH_OBJS = $(patsubst %.c,%.uarch_c.o,$(patsubst %.cpp,%.uarch_cpp.o,$(UARCH_SOURCES)))
UARCH_THIRDPARTY_OBJS = $(patsubst %.c,%.uarch_thirdparty_c.o,$(UARCH_THIRDPARTY_SOURCES_C))
EMULATOR_OBJS = $(patsubst %.c,%.emulator_c.o,$(patsubst %.cpp,%.emulator_cpp.o,$(EMULATOR_SOURCES)))
COMPUTE_OBJS = $(patsubst %.c,%.compute_c.o,$(patsubst %.cpp,%.compute_cpp.o,$(COMPUTE_SOURCES)))

LINTER_SOURCES=$(strip $(wildcard uarch-*.cpp))
CLANG_TIDY=clang-tidy
CLANG_TIDY_TARGETS=$(patsubst %.cpp,%.clang-tidy,$(LINTER_SOURCES))
CLANG_TIDY_FLAGS=--header-filter='^$(abspath ..)/'
CLANG_TIDY_WARNS=-Wthread-safety -Wglobal-constructors -Wno-unused-command-line-argument -Wno-format
CLANG_TIDY_CXXFLAGS=-target riscv64-linux-gnu -I../tools/rv64i-lp64-stubs

.PHONY: all clean validate-instruction-set

coverage?=no

ifeq ($(coverage),yes)
# Under coverage we also build the instrumented uarch and a matching pristine
# ram/hash pair generated from it. The coverage PC-collection test relinks the
# emulator with these so reset_uarch restores the instrumented binary (the one
# addr2line resolves PCs against) for every machine instruction. They are
# coverage-only and never used for proofs. The default pristine stays stock so
# the functional uarch tests keep running the fast optimized binary.
all: uarch-ram.bin uarch-ram-coverage.bin uarch-pristine-ram.c uarch-pristine-hash.c \
	uarch-pristine-ram-coverage.c uarch-pristine-hash-coverage.c validate-instruction-set
else
all: uarch-ram.bin uarch-pristine-ram.c uarch-pristine-hash.c validate-instruction-set
endif

compute-uarch-pristine-hash: $(COMPUTE_OBJS)
	$(HOST_CXX) $(HOST_CFLAGS) $(HOST_CXXFLAGS) -o $@ $^

uarch-pristine-hash.c: compute-uarch-pristine-hash
	./compute-uarch-pristine-hash > $@

uarch-pristine-ram.c: uarch-ram.bin
	@(echo '// This file is auto-generated and should not be modified'; \
		echo '#include <stddef.h>'; \
		xxd -i -n uarch_pristine_ram $<) > $@ || rm $@

# Coverage pristine ram/hash, generated from the instrumented uarch binary.
# Same symbol names as the stock pair, so relinking with these objects swaps the
# embedded pristine without recompiling any instrumented object.
COMPUTE_COVERAGE_OBJS = $(filter-out uarch-pristine-ram.compute_c.o,$(COMPUTE_OBJS)) \
	uarch-pristine-ram-coverage.compute_c.o

compute-uarch-pristine-hash-coverage: $(COMPUTE_COVERAGE_OBJS)
	$(HOST_CXX) $(HOST_CFLAGS) $(HOST_CXXFLAGS) -o $@ $^

uarch-pristine-hash-coverage.c: compute-uarch-pristine-hash-coverage
	./compute-uarch-pristine-hash-coverage > $@

uarch-pristine-ram-coverage.c: uarch-ram-coverage.bin
	@(echo '// This file is auto-generated and should not be modified'; \
		echo '#include <stddef.h>'; \
		xxd -i -n uarch_pristine_ram $<) > $@ || rm $@

uarch-ram.bin: uarch-ram.elf
	$(OBJCOPY) -S -O binary  $^ $@

uarch-ram.elf.insn.txt: uarch-ram.elf.objdump
	grep -oP '^\s*[0-9a-f]{4,8}\:\s+[0-9a-f]{4,8}\s+\K[a-z]\S+' $(^F) | sort -u > $@

uarch-ram.elf.objdump: uarch-ram.elf
	@$(OBJDUMP) -M no-aliases  -d $(^F) > $@

validate-instruction-set: uarch-ram.elf.insn.txt
	@grep  -v -w -E "$(SUPPORTED_UARCH_INSN)" $(<F);\
	if [ $$? -eq 0 ]; then\
		echo "Unsupported uarch instruction(s) detected. See objdump output in in uarch-ram.elf.objdump"; \
		false;\
	fi

uarch-ram.elf: $(EMULATOR_OBJS) $(UARCH_OBJS) $(UARCH_THIRDPARTY_OBJS) uarch-ram-entry.o uarch-ram.ld
	$(CXX) $(CFLAGS) $(CXXFLAGS) -Wl,-Tuarch-ram.ld  -o $@ $(EMULATOR_OBJS) $(UARCH_OBJS) $(UARCH_THIRDPARTY_OBJS) uarch-ram-entry.o $(TOOLCHAIN_LIBS)

uarch-ram-entry.o: uarch-ram-entry.S
	$(CC) $(CFLAGS) -c -o $@ $<

%.compute_cpp.o: %.cpp
	$(HOST_CXX) $(HOST_CFLAGS) $(HOST_CXXFLAGS) -c -o $@ $<

%.compute_c.o: %.c
	$(HOST_CC) $(HOST_CFLAGS) -c -o $@ $<

%.emulator_cpp.o: %.cpp
	$(CXX) $(CFLAGS) $(CXXFLAGS) -c -o $@ $<

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

%.uarch_cpp.o: %.cpp
	$(CXX) $(CFLAGS) $(CXXFLAGS) -c -o $@ $<

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

%.ld: %.ld.in
	$(CC) -o $(@F).tmp -x c $(CFLAGS) -E $(^F)
	grep -v '^#' $@.tmp > $@

%.clang-tidy: %.cpp
	$(CLANG_TIDY) $(CLANG_TIDY_FLAGS) $< -- $(CLANG_TIDY_CXXFLAGS) $(CFLAGS) $(CXXFLAGS) $(CLANG_TIDY_WARNS)
	@touch $@

lint: $(CLANG_TIDY_TARGETS)

# Coverage build: separate object files with -O0 -g and no gc-sections,
# for use with uarch PC coverage collection.
COV_CFLAGS := $(BASEFLAGS) $(COV_GC_SECTIONS) $(COV_OPTFLAGS)

COV_UARCH_OBJS = $(patsubst %.c,%.cov_c.o,$(patsubst %.cpp,%.cov_cpp.o,$(UARCH_SOURCES)))
COV_EMULATOR_OBJS = $(patsubst %.c,%.cov_c.o,$(patsubst %.cpp,%.cov_cpp.o,$(EMULATOR_SOURCES)))
COV_THIRDPARTY_OBJS = $(patsubst %.c,%.cov_c.o,$(UARCH_THIRDPARTY_SOURCES_C))

%.cov_cpp.o: %.cpp
	$(CXX) $(COV_CFLAGS) $(CXXFLAGS) -c -o $@ $<

%.cov_c.o: %.c
	$(CC) $(COV_CFLAGS) -c -o $@ $<

uarch-ram-coverage-entry.o: uarch-ram-entry.S
	$(CC) $(COV_CFLAGS) -c -o $@ $<

uarch-ram-coverage.elf: $(COV_EMULATOR_OBJS) $(COV_UARCH_OBJS) $(COV_THIRDPARTY_OBJS) uarch-ram-coverage-entry.o uarch-ram.ld
	$(CXX) $(COV_CFLAGS) $(CXXFLAGS) -Wl,-Tuarch-ram.ld -o $@ $(COV_EMULATOR_OBJS) $(COV_UARCH_OBJS) $(COV_THIRDPARTY_OBJS) uarch-ram-coverage-entry.o $(TOOLCHAIN_LIBS)

uarch-ram-coverage.bin: uarch-ram-coverage.elf
	$(OBJCOPY) -S -O binary $^ $@

clean-executables:
	@rm -f compute-uarch-pristine-hash compute-uarch-pristine-hash-coverage

clean-auto-generated:
	@rm -f uarch-pristine-hash.c uarch-pristine-ram.c \
		uarch-pristine-hash-coverage.c uarch-pristine-ram-coverage.c

clean: clean-executables clean-auto-generated
	@rm -f *.ld *.elf *.bin *.tmp *.o *.clang-tidy *.insn.txt *.objdump $(UARCH_OBJS) $(UARCH_THIRDPARTY_OBJS) $(EMULATOR_OBJS) $(COMPUTE_OBJS) $(COMPUTE_COVERAGE_OBJS) $(COV_UARCH_OBJS) $(COV_EMULATOR_OBJS) $(COV_THIRDPARTY_OBJS)
