ekp/ekp_c/Makefile
Kinneyzhang 74a780ce95 refactor!: remove the experimental C tokenization path; harden the module
The self-contained C path (ekp-c-break-lines, ekp-c-load-hyphenator,
ekp-c-hyphenate, ekp-c-set-spacing, ekp_paragraph.c, ekp_hyphen.c,
~1500 lines) was never used by ekp.el, diverged semantically from the
real pipeline (no kinsoku, no protrusion, no two-pass emergency), and
contained an exploitable heap overflow reachable from Lisp:
ekp_para_create sized its box array as box_count * 2, but a long word
hyphenates into arbitrarily many syllable boxes, overflowing the
calloc'd buffer.  Deleting the path deletes the bug class.

Hardening of the live path:

- thread pool: sized from the machine's core count instead of a
  hardcoded 8; created lazily on the first multi-paragraph batch
  (single-paragraph users never start worker threads); a full queue
  now blocks the submitter until a worker makes room — tasks were
  silently dropped before, degrading the batch to the Elisp fallback
  exactly when parallelism mattered most.
- unified failure gate: a partial allocation used to silently drop
  kinsoku, hyphenation or protrusion data and continue with a subtly
  different layout; any allocation failure or pending Lisp signal
  (non-local exit from a bad element type) now fails the whole call,
  and ekp.el falls back to the Elisp engine.  The Elisp bridge wraps
  both C entry points in condition-case, and a whole-batch nil no
  longer crashes the per-paragraph loop.
- integer safety: every extracted pixel value is clamped to int32
  instead of silently wrapping.
- EKP_INFINITY (the unreachable-state sentinel) is now a real
  infinity: extremely degenerate paragraphs could legitimately
  accumulate demerits past the old 1e10 constant, making C consider
  reachable states dead and diverge from the Elisp engine.

BREAKING: the four experimental module functions are gone; rebuild
with make -C ekp_c clean all (version gate unchanged at 1.5).

92 ERT green; fuzz 300/300 byte-identical across engines.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 01:49:25 +08:00

121 lines
3.0 KiB
Makefile

# Makefile for EKP C dynamic module
#
# Copyright (C) 2024-2026 Kinney Zhang
# SPDX-License-Identifier: GPL-3.0-or-later
#
# Builds ekp.so (or ekp.dylib on macOS) for Emacs dynamic loading.
# Uses pthread for multi-threading, optimizes for native CPU.
# Detect OS
ifeq ($(OS),Windows_NT)
UNAME := Windows
else
UNAME := $(shell uname 2>/dev/null || echo Unknown)
endif
# Compiler settings
# Default to gcc on Windows if cc is not found
ifeq ($(UNAME), Windows)
CC = gcc
# Point EMACS_ROOT at your Emacs installation (the directory that
# contains include/emacs-module.h), e.g.:
# make EMACS_ROOT=C:/path/to/emacs-30.2
EMACS_ROOT ?=
ifeq ($(EMACS_ROOT),)
$(warning EMACS_ROOT is not set; emacs-module.h may not be found. \
Set it to your Emacs installation directory.)
endif
EMACS ?= emacs.exe
else
CC ?= cc
EMACS ?= emacs
endif
CFLAGS := -std=c11 -Wall -Wextra -Wpedantic -O3 -fPIC
CFLAGS += -march=native -flto
CFLAGS += -D_POSIX_C_SOURCE=200809L
# Debug build
ifdef DEBUG
CFLAGS := -std=c11 -Wall -Wextra -Wpedantic -O0 -g -fPIC
CFLAGS += -fsanitize=address,undefined
endif
# Platform-specific settings
ifeq ($(UNAME), Darwin)
# macOS
MODULE_EXT := dylib
LDFLAGS := -shared -lpthread
# Find Emacs headers (check Emacs.app, homebrew, /usr/local)
EMACS_INCLUDE := $(shell find /Applications/Emacs.app/Contents/Resources/include /opt/homebrew /usr/local -name emacs-module.h -exec dirname {} \; 2>/dev/null | head -1)
ifdef EMACS_INCLUDE
CFLAGS += -I$(EMACS_INCLUDE)
endif
else ifeq ($(UNAME), Linux)
# Linux
MODULE_EXT := so
LDFLAGS := -shared -lpthread -lm
else ifeq ($(UNAME), Windows)
# Windows (MinGW)
MODULE_EXT := dll
LDFLAGS := -shared -lpthread
CFLAGS += -I$(EMACS_ROOT)/include
else
# Fallback / Unknown
MODULE_EXT := so
LDFLAGS := -shared -lpthread
endif
# Source files
SRCS := ekp.c ekp_kp.c ekp_thread_pool.c
OBJS := $(SRCS:.c=.o)
# Output
MODULE := ekp.$(MODULE_EXT)
# Targets
.PHONY: all clean install test
all: $(MODULE)
$(MODULE): $(OBJS)
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
@rm -f $(OBJS)
@echo "Built $@"
%.o: %.c ekp_module.h
$(CC) $(CFLAGS) -c -o $@ $<
clean:
rm -f $(OBJS) $(MODULE)
# Install to Emacs load path (customize DESTDIR as needed)
DESTDIR ?= $(HOME)/.emacs.d/modules
install: $(MODULE)
@mkdir -p $(DESTDIR)
cp $(MODULE) $(DESTDIR)/
@echo "Installed to $(DESTDIR)/$(MODULE)"
# Test with Emacs
test: $(MODULE)
$(EMACS) -Q --batch \
-L . \
--eval '(module-load (expand-file-name "./$(MODULE)"))' \
--eval '(message "ekp-c version: %s" (ekp-c-version))' \
--eval '(ekp-c-init)' \
--eval '(message "ekp-c initialized with %d threads" (ekp-c-thread-count))' \
--eval '(ekp-c-cleanup)' \
--eval '(message "Test passed!")'
# Show compiler info
info:
@echo "CC: $(CC)"
@echo "CFLAGS: $(CFLAGS)"
@echo "LDFLAGS: $(LDFLAGS)"
@echo "MODULE: $(MODULE)"
@echo "UNAME: $(UNAME)"
@echo "EMACS: $(EMACS)"
ifdef EMACS_INCLUDE
@echo "EMACS_INCLUDE: $(EMACS_INCLUDE)"
endif