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>
121 lines
3.0 KiB
Makefile
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
|