commit 556472c785d8cf94ad8f9f5c376ad127b39975d4
parent d79afab6903f07ccae1fe5b750fbaa371ea62599
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Mon, 25 May 2026 13:21:08 -0700
Add release build toggle
Diffstat:
| M | Makefile | | | 71 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------- |
| M | driver/nm.c | | | 62 | +++++++++++++++++++++++++++++++++++++++++++++++--------------- |
| M | test/test.mk | | | 7 | ++++--- |
3 files changed, 108 insertions(+), 32 deletions(-)
diff --git a/Makefile b/Makefile
@@ -3,18 +3,55 @@ AR = ar
LD = ld
BUILD_DIR ?= build
SYSROOT = $(shell xcrun --show-sdk-path)
-HOST_OPTFLAGS ?= -O1
+RELEASE ?= 0
+.DEFAULT_GOAL := all
+
+ifeq ($(RELEASE),1)
+HOST_OPTFLAGS ?= -O2
+HOST_MODE_CPPFLAGS = -DNDEBUG
+HOST_MODE_CFLAGS =
+HOST_MODE_LDFLAGS =
+else
+HOST_OPTFLAGS ?= -O0
+HOST_MODE_CPPFLAGS =
+HOST_MODE_CFLAGS = -g3 -fno-omit-frame-pointer -fno-optimize-sibling-calls \
+ -fsanitize=address,undefined \
+ -fno-sanitize-recover=address,undefined \
+ -fsanitize-address-use-after-scope
+HOST_MODE_LDFLAGS = -fsanitize=address,undefined
+ASAN_OPTIONS ?= halt_on_error=1:abort_on_error=1
+UBSAN_OPTIONS ?= halt_on_error=1:print_stacktrace=1
+export ASAN_OPTIONS UBSAN_OPTIONS
+endif
# -isysroot lives in its own var so stage/bootstrap recipes can override
# host SDK handling when cfree is used as the compiler.
HOST_SYSROOT_CFLAGS = -isysroot $(SYSROOT)
HOST_SYSROOT_LDFLAGS = -isysroot $(SYSROOT)
-CFLAGS_COMMON = $(HOST_OPTFLAGS) -std=c11 -Wpedantic -Wall -Wextra -Werror
+CFLAGS_COMMON = $(HOST_OPTFLAGS) $(HOST_MODE_CPPFLAGS) $(HOST_MODE_CFLAGS) \
+ -std=c11 -Wpedantic -Wall -Wextra -Werror
HOST_CFLAGS = $(CFLAGS_COMMON) $(HOST_SYSROOT_CFLAGS)
+HOST_LDFLAGS = $(HOST_SYSROOT_LDFLAGS) $(HOST_MODE_LDFLAGS)
DEPFLAGS = -MMD -MP
+BUILD_CONFIG = $(BUILD_DIR)/.build-config
+
+.PHONY: FORCE
+FORCE:
+
+$(BUILD_CONFIG): FORCE
+ @mkdir -p $(dir $@)
+ @{ \
+ printf '%s\n' 'RELEASE=$(RELEASE)'; \
+ printf '%s\n' 'HOST_OPTFLAGS=$(HOST_OPTFLAGS)'; \
+ printf '%s\n' 'HOST_MODE_CPPFLAGS=$(HOST_MODE_CPPFLAGS)'; \
+ printf '%s\n' 'HOST_MODE_CFLAGS=$(HOST_MODE_CFLAGS)'; \
+ printf '%s\n' 'HOST_MODE_LDFLAGS=$(HOST_MODE_LDFLAGS)'; \
+ } > $@.tmp
+ @if ! cmp -s $@.tmp $@; then mv $@.tmp $@; else rm -f $@.tmp; fi
+
# Freestanding objects must not see host SDK/libc headers. Homebrew clang can
# also inject a configured sysroot before command-line flags; use
# --no-default-config when the selected compiler supports it, but omit it for
@@ -177,7 +214,7 @@ bin: $(BIN)
# Replace the archive (`ar rcs` only adds/updates), so removing a .c file
# also removes its .o from the archive on the next build.
-$(LIB_RELOC_OBJ): $(LIB_OBJS)
+$(LIB_RELOC_OBJ): $(LIB_OBJS) $(BUILD_CONFIG)
@mkdir -p $(dir $@)
@rm -f $@
$(LD) -r -o $@ $(LIB_OBJS)
@@ -187,47 +224,47 @@ $(LIB_AR): $(LIB_RELOC_OBJ)
@rm -f $@
$(AR) rcs $@ $(LIB_RELOC_OBJ)
-$(BIN): $(DRIVER_OBJS) $(LIB_AR)
- $(CC) $(HOST_SYSROOT_LDFLAGS) -o $@ $(DRIVER_OBJS) $(LIB_AR)
+$(BIN): $(DRIVER_OBJS) $(LIB_AR) $(BUILD_CONFIG)
+ $(CC) $(HOST_LDFLAGS) -o $@ $(DRIVER_OBJS) $(LIB_AR)
-$(BUILD_DIR)/lib/%.o: src/%.c Makefile
+$(BUILD_DIR)/lib/%.o: src/%.c Makefile $(BUILD_CONFIG)
@mkdir -p $(dir $@)
$(CC) $(LIB_CFLAGS) $(DEPFLAGS) -c $< -o $@
# lang_registry.c is the one libcfree source that crosses into lang/*; it
# uses -Ilang so the frontend headers can be reached as "c/c.h" etc.
-$(BUILD_DIR)/lib/api/lang_registry.o: src/api/lang_registry.c Makefile
+$(BUILD_DIR)/lib/api/lang_registry.o: src/api/lang_registry.c Makefile $(BUILD_CONFIG)
@mkdir -p $(dir $@)
$(CC) $(LIB_CFLAGS) -Ilang $(DEPFLAGS) -c $< -o $@
-$(BUILD_DIR)/lang/cpp/%.o: lang/cpp/%.c Makefile
+$(BUILD_DIR)/lang/cpp/%.o: lang/cpp/%.c Makefile $(BUILD_CONFIG)
@mkdir -p $(dir $@)
$(CC) $(FREESTANDING_CFLAGS) -Iinclude -Ilang/cpp $(DEPFLAGS) -c $< -o $@
# The C frontend includes the lexer and preprocessor headers (pp/pp.h,
# lex/lex.h) which now live under lang/cpp/, and cpp_support.h is the
# shared substrate. So lang/c objects build with -Ilang/cpp -Ilang/c.
-$(BUILD_DIR)/lang/c/%.o: lang/c/%.c Makefile
+$(BUILD_DIR)/lang/c/%.o: lang/c/%.c Makefile $(BUILD_CONFIG)
@mkdir -p $(dir $@)
$(CC) $(FREESTANDING_CFLAGS) -Iinclude -Ilang/cpp -Ilang/c $(DEPFLAGS) -c $< -o $@
-$(BUILD_DIR)/lang/wasm/%.o: lang/wasm/%.c Makefile
+$(BUILD_DIR)/lang/wasm/%.o: lang/wasm/%.c Makefile $(BUILD_CONFIG)
@mkdir -p $(dir $@)
$(CC) $(FREESTANDING_CFLAGS) -Iinclude -Ilang/wasm $(DEPFLAGS) -c $< -o $@
-$(BUILD_DIR)/lib/%.o: src/%.S Makefile
+$(BUILD_DIR)/lib/%.o: src/%.S Makefile $(BUILD_CONFIG)
@mkdir -p $(dir $@)
$(CC) $(LIB_CFLAGS) $(DEPFLAGS) -c $< -o $@
-$(BUILD_DIR)/driver/%.o: driver/%.c Makefile
+$(BUILD_DIR)/driver/%.o: driver/%.c Makefile $(BUILD_CONFIG)
@mkdir -p $(dir $@)
$(CC) $(DRIVER_CFLAGS) $(DEPFLAGS) -c $< -o $@
-$(BUILD_DIR)/driver/env.o: driver/env.c Makefile
+$(BUILD_DIR)/driver/env.o: driver/env.c Makefile $(BUILD_CONFIG)
@mkdir -p $(dir $@)
$(CC) $(DRIVER_ENV_CFLAGS) $(DEPFLAGS) -c $< -o $@
-$(BUILD_DIR)/lang/toy/%.o: lang/toy/%.c Makefile
+$(BUILD_DIR)/lang/toy/%.o: lang/toy/%.c Makefile $(BUILD_CONFIG)
@mkdir -p $(dir $@)
$(CC) $(FREESTANDING_CFLAGS) -Iinclude -Ilang/toy $(DEPFLAGS) -c $< -o $@
@@ -252,14 +289,20 @@ bootstrap: $(BIN) rt
@for tool in $(BOOTSTRAP_TOOLS); do ln -sf cfree "$(BOOTSTRAP_STAGE1_DIR)/$$tool"; done
$(MAKE) lib bin \
BUILD_DIR='$(abspath $(BOOTSTRAP_STAGE2_DIR))' \
+ RELEASE='$(RELEASE)' \
HOST_OPTFLAGS='$(HOST_OPTFLAGS)' \
+ HOST_MODE_CFLAGS= \
+ HOST_MODE_LDFLAGS= \
CC='$(abspath $(BOOTSTRAP_STAGE1_DIR))/cc' \
AR='$(abspath $(BOOTSTRAP_STAGE1_DIR))/ar' \
LD='$(abspath $(BOOTSTRAP_STAGE1_DIR))/ld'
@for tool in $(BOOTSTRAP_TOOLS); do ln -sf cfree "$(BOOTSTRAP_STAGE2_DIR)/$$tool"; done
$(MAKE) lib bin \
BUILD_DIR='$(abspath $(BOOTSTRAP_STAGE3_DIR))' \
+ RELEASE='$(RELEASE)' \
HOST_OPTFLAGS='$(HOST_OPTFLAGS)' \
+ HOST_MODE_CFLAGS= \
+ HOST_MODE_LDFLAGS= \
CC='$(abspath $(BOOTSTRAP_STAGE2_DIR))/cc' \
AR='$(abspath $(BOOTSTRAP_STAGE2_DIR))/ar' \
LD='$(abspath $(BOOTSTRAP_STAGE2_DIR))/ld'
diff --git a/driver/nm.c b/driver/nm.c
@@ -96,16 +96,17 @@ static int nm_value_cmp(const void *a, const void *b) {
return nm_name_cmp(a, b);
}
-static void nm_append_sym(DriverEnv *env, NmSym **syms, uint32_t *n,
- uint32_t *cap, const CfreeObjSymInfo *si,
- const char *file_prefix, int *ptr_digits,
- CfreeTarget t) {
+static int nm_append_sym(DriverEnv *env, NmSym **syms, uint32_t *n,
+ uint32_t *cap, const CfreeObjSymInfo *si,
+ const char *file_prefix, int *ptr_digits,
+ CfreeTarget t) {
int d = (t.ptr_size == 4) ? 8 : 16;
+ char *name;
if (d > *ptr_digits) *ptr_digits = d;
if (*n >= *cap) {
uint32_t nc = *cap ? *cap * 2u : 64u;
NmSym *ns = (NmSym *)driver_alloc_zeroed(env, (size_t)nc * sizeof(NmSym));
- if (!ns) return;
+ if (!ns) return 1;
if (*syms) {
memcpy(ns, *syms, (size_t)(*n) * sizeof(NmSym));
driver_free(env, *syms, (size_t)(*cap) * sizeof(NmSym));
@@ -113,30 +114,40 @@ static void nm_append_sym(DriverEnv *env, NmSym **syms, uint32_t *n,
*syms = ns;
*cap = nc;
}
+ name = (char *)driver_alloc(env, si->name.len);
+ if (!name) return 1;
+ driver_memcpy(name, si->name.s, si->name.len);
NmSym *ds = &(*syms)[*n];
- ds->name = si->name;
+ ds->name.s = name;
+ ds->name.len = si->name.len;
ds->value = si->value;
ds->bind = si->bind;
ds->kind = si->kind;
ds->defined = (si->section != CFREE_SECTION_NONE);
ds->file_prefix = file_prefix;
(*n)++;
+ return 0;
}
-static void nm_collect_obj(CfreeObjFile *of, const NmOpts *opts,
- const char *file_prefix, NmSym **syms,
- uint32_t *n, uint32_t *cap, int *ptr_digits,
- DriverEnv *env) {
+static int nm_collect_obj(CfreeObjFile *of, const NmOpts *opts,
+ const char *file_prefix, NmSym **syms, uint32_t *n,
+ uint32_t *cap, int *ptr_digits, DriverEnv *env) {
CfreeTarget t = cfree_obj_target(of);
CfreeObjSymIter *it = NULL;
- if (cfree_obj_symiter_new(of, &it) != CFREE_OK) return;
+ int rc = 0;
+ if (cfree_obj_symiter_new(of, &it) != CFREE_OK) return 1;
for (;;) {
CfreeObjSymInfo si;
if (cfree_obj_symiter_next(it, &si) != CFREE_ITER_ITEM) break;
if (!nm_symbol_visible(&si, opts)) continue;
- nm_append_sym(env, syms, n, cap, &si, file_prefix, ptr_digits, t);
+ if (nm_append_sym(env, syms, n, cap, &si, file_prefix, ptr_digits, t) !=
+ 0) {
+ rc = 1;
+ break;
+ }
}
cfree_obj_symiter_free(it);
+ return rc;
}
static int nm_process_file(const CfreeContext *ctx, const CfreeSlice *input,
@@ -158,8 +169,14 @@ static int nm_process_file(const CfreeContext *ctx, const CfreeSlice *input,
mb.data = m.data;
mb.len = m.size;
if (cfree_obj_open(ctx, m.name, &mb, &of) == CFREE_OK) {
- nm_collect_obj(of, opts, NULL, syms, n, cap, ptr_digits, env);
+ int collect_rc =
+ nm_collect_obj(of, opts, NULL, syms, n, cap, ptr_digits, env);
cfree_obj_free(of);
+ if (collect_rc != 0) {
+ cfree_ar_iter_free(it);
+ driver_errf(NM_TOOL, "%s: out of memory while reading symbols", path);
+ return 1;
+ }
}
}
cfree_ar_iter_free(it);
@@ -170,12 +187,27 @@ static int nm_process_file(const CfreeContext *ctx, const CfreeSlice *input,
driver_errf(NM_TOOL, "%s: not a recognized object file", path);
return 1;
}
- nm_collect_obj(of, opts, NULL, syms, n, cap, ptr_digits, env);
+ if (nm_collect_obj(of, opts, NULL, syms, n, cap, ptr_digits, env) != 0) {
+ cfree_obj_free(of);
+ driver_errf(NM_TOOL, "%s: out of memory while reading symbols", path);
+ return 1;
+ }
cfree_obj_free(of);
}
return 0;
}
+static void nm_free_syms(DriverEnv *env, NmSym *syms, uint32_t nsyms,
+ uint32_t cap) {
+ uint32_t i;
+ if (!syms) return;
+ for (i = 0; i < nsyms; ++i) {
+ if (syms[i].name.s)
+ driver_free(env, (void *)syms[i].name.s, syms[i].name.len);
+ }
+ driver_free(env, syms, (size_t)cap * sizeof(NmSym));
+}
+
static void nm_sort_syms(NmSym *syms, uint32_t nsyms, const NmOpts *opts) {
uint32_t i, j;
if (opts->no_sort) return;
@@ -313,7 +345,7 @@ int driver_nm(int argc, char **argv) {
rc = 0;
done:
- if (syms) driver_free(&env, syms, (size_t)cap * sizeof(NmSym));
+ nm_free_syms(&env, syms, nsyms, cap);
driver_env_fini(&env);
return rc;
}
diff --git a/test/test.mk b/test/test.mk
@@ -252,9 +252,10 @@ test-rt-runtime: bin rt $(LINK_EXE_RUNNER)
# Declared as Make targets (not built by the run.sh scripts) so they pick
# up libcfree.a changes deterministically.
#
-# HARNESS_CFLAGS drops -Wpedantic; the runners cast cfree_jit_lookup's
-# void* to a function pointer, which pedantic rejects under C11.
-HARNESS_CFLAGS = -std=c11 -Wall -Wextra -Werror -isysroot $(SYSROOT) -Iinclude -Itest
+# HARNESS_CFLAGS drops -Wpedantic from the normal host flags; the runners cast
+# cfree_jit_lookup's void* to a function pointer, which pedantic rejects under
+# C11.
+HARNESS_CFLAGS = $(filter-out -Wpedantic,$(HOST_CFLAGS)) -Iinclude -Itest
ROUNDTRIP_BIN = build/test/cfree-roundtrip
ROUNDTRIP_BIN_MACHO = build/test/cfree-roundtrip-macho