# zstd.wasm
#
# Makefile to use with emscripten
# See https://emscripten.org/docs/getting_started/downloads.html
# for installation instructions.
#
# This Makefile assumes you have loaded emscripten's environment.
# (On Windows, you may need to execute emsdk_env.bat or encmdprompt.bat ahead)
#
# Build(Debug)
# > emmake make BUILD_TYPE=Debug
#
# Build(Release)
# > emmake make BUILD_TYPE=Release
#
# Build(Release) with a Brotli-compressed wasm artifact
# > emmake make BUILD_TYPE=Release BROTLI=1
# Optional: BROTLI_QUALITY=0..11 (default: 11), BROTLI_CMD=/path/to/brotli
#
# Debugging WebAssembly with modern tools
# See https://developer.chrome.com/blog/wasm-debugging-2020/
#
# Build-Output:object:
# - build/debug|release
# Build-Output:target:
# - ./web/debug
# - ./web/release
#

# Auto-sets parallel jobs to CPU core count
ifeq ($(OS),Windows_NT)
	MAKEFLAGS += -j$(if $(NUMBER_OF_PROCESSORS),$(NUMBER_OF_PROCESSORS),1)
else
	MAKEFLAGS += -j$(shell nproc 2>/dev/null || sysctl -n hw.logicalcpu 2>/dev/null || echo 1)
endif

################################################################
# Portable shell helpers (cmd.exe vs POSIX)

ifeq ($(OS),Windows_NT)
	SHELL := cmd
	.SHELLFLAGS := /C
	npath = $(subst /,\\,$(1))
define MKDIR
	if not exist "$(call npath,$(1))" mkdir "$(call npath,$(1))"
endef
	RM_DIR = rmdir /S /Q
else
	npath = $(1)
define MKDIR
	mkdir -p $(1)
endef
	RM_DIR = rm -rf
endif

################################################################

CC = emcc
CXX = em++

APP = uni-zstd.js
PRE_JS = wasm_binding-pre.js

BROTLI ?= 0
BROTLI_CMD ?= brotli
BROTLI_QUALITY ?= 11
PTHREAD_POOL_SIZE ?= 4

ifneq ($(filter $(BROTLI),0 1),$(BROTLI))
$(error BROTLI must be 0 or 1)
endif

# Debug/Release
# Note: allow utility targets like 'clean' to run without requiring BUILD_TYPE
# Fallback to a dummy value so variables that reference BUILD_TYPE_OUTPUT don't break on 'make clean'
ifneq (clean,$(firstword $(MAKECMDGOALS)))
	BUILD_TYPE_OUTPUT := $(BUILD_TYPE:Debug=debug)
	BUILD_TYPE_OUTPUT := $(BUILD_TYPE_OUTPUT:Release=release)
	ifneq ($(BUILD_TYPE_OUTPUT), debug)
		ifneq ($(BUILD_TYPE_OUTPUT), release)
			$(error BUILD_TYPE variable is not set)
		endif
	endif
else
	BUILD_TYPE_OUTPUT := debug
endif

ROOT = .
OUTPUT = $(ROOT)/build/$(BUILD_TYPE_OUTPUT)
TARGET = $(ROOT)/web/$(BUILD_TYPE_OUTPUT)/$(APP)
WASM_TARGET = $(TARGET:.js=.wasm)
BROTLI_TARGET = $(WASM_TARGET).br

# Keep this list in sync with zstd_sources in binding.gyp. The platform-specific
# N-API/JNI bindings are replaced by wasm_binding.cc for Emscripten.
ZSTD_SOURCES := \
	lib/common/debug.c \
	lib/common/entropy_common.c \
	lib/common/fse_decompress.c \
	lib/compress/fse_compress.c \
	lib/compress/huf_compress.c \
	lib/decompress/huf_decompress.c \
	lib/common/pool.c \
	lib/common/threading.c \
	lib/common/xxhash.c \
	lib/common/zstd_common.c \
	lib/common/error_private.c \
	lib/compress/hist.c \
	lib/compress/zstd_compress.c \
	lib/compress/zstd_compress_literals.c \
	lib/compress/zstd_compress_sequences.c \
	lib/compress/zstd_compress_superblock.c \
	lib/compress/zstd_double_fast.c \
	lib/compress/zstd_fast.c \
	lib/compress/zstd_lazy.c \
	lib/compress/zstd_ldm.c \
	lib/compress/zstd_opt.c \
	lib/compress/zstd_preSplit.c \
	lib/compress/zstdmt_compress.c \
	lib/decompress/zstd_ddict.c \
	lib/decompress/zstd_decompress.c \
	lib/decompress/zstd_decompress_block.c

CXX_SOURCES := zstd_codec.cc wasm_binding.cc
SOURCES := $(ZSTD_SOURCES) $(CXX_SOURCES)
OBJECTS := $(patsubst %.c,$(OUTPUT)/%.o,$(ZSTD_SOURCES)) \
	$(patsubst %.cc,$(OUTPUT)/%.o,$(CXX_SOURCES))
DEPENDENCIES := $(OBJECTS:.o=.d)

CPP_FLAGS += -Ilib
CPP_FLAGS += -DXXH_NAMESPACE=ZSTD_
CPP_FLAGS += -DZSTD_MULTITHREAD
CPP_FLAGS += -DZSTD_DISABLE_ASM
# CPP_FLAGS += -DUASM_ZSTD_MAX_WORKERS=$(PTHREAD_POOL_SIZE)

# COMMON_FLAGS += -pthread
COMMON_FLAGS += -msimd128
COMMON_FLAGS += -MMD -MP

C_FLAGS += $(CPP_FLAGS) $(COMMON_FLAGS)
CXX_FLAGS += $(CPP_FLAGS) $(COMMON_FLAGS) -std=c++20 -fexceptions

LD_FLAGS += --no-entry
LD_FLAGS += -lembind
# LD_FLAGS += -pthread
LD_FLAGS += -s PTHREAD_POOL_SIZE=$(PTHREAD_POOL_SIZE)
LD_FLAGS += -msimd128
LD_FLAGS += -fexceptions
LD_FLAGS += -s WASM=1
LD_FLAGS += -s ALLOW_MEMORY_GROWTH=1
LD_FLAGS += -s EXPORTED_FUNCTIONS='["_malloc","_free"]'
LD_FLAGS += -s DYNAMIC_EXECUTION=0

ifeq ($(BUILD_TYPE),Debug)
	LD_FLAGS += -s ASSERTIONS=1
	LD_FLAGS += -s SAFE_HEAP=1
endif

# ("EM_FLAGS" options gets added to compile and link flags.)
ifeq ($(BUILD_TYPE),Debug)
	EM_FLAGS += -O0 -g
else ifeq ($(BUILD_TYPE),Release)
	EM_FLAGS += -O3
else
endif

# Added "EM_FLAGS" to both compile and link flags
C_FLAGS += $(EM_FLAGS)
CXX_FLAGS += $(EM_FLAGS)
LD_FLAGS += $(EM_FLAGS)

JS_FLAGS += -s MODULARIZE=1
JS_FLAGS += -s EXPORT_ES6=1
JS_FLAGS += -s ENVIRONMENT=web
JS_FLAGS += -s EXPORT_NAME=createUniZstdModule
# JS_FLAGS += --pre-js $(PRE_JS)

.PHONY: build clean
build: $(TARGET) $(if $(filter 1,$(BROTLI)),$(BROTLI_TARGET))
	@echo Build complete for $(TARGET)
	$(if $(filter 1,$(BROTLI)),@echo Brotli artifact: $(BROTLI_TARGET))

$(TARGET): $(OBJECTS) $(PRE_JS)
	@$(call MKDIR,$(@D))
	$(CXX) -o $@ $(OBJECTS) $(LD_FLAGS) $(JS_FLAGS)

$(BROTLI_TARGET): $(TARGET)
	$(BROTLI_CMD) -q $(BROTLI_QUALITY) -f -o $@ $(WASM_TARGET)

$(OUTPUT)/%.o: %.c Makefile
	@$(call MKDIR,$(@D))
	$(CC) $(C_FLAGS) -c $< -o $@

$(OUTPUT)/%.o: %.cc Makefile
	@$(call MKDIR,$(@D))
	$(CXX) $(CXX_FLAGS) -c $< -o $@

clean:
	-$(RM_DIR) "$(call npath,$(ROOT)/build)"
	-$(RM_DIR) "$(call npath,$(ROOT)/web/debug)"
	-$(RM_DIR) "$(call npath,$(ROOT)/web/release)"

-include $(DEPENDENCIES)