commit 0fa2b1d65c38f0213f6f5a2df8d73ee2e3d4553c
parent feb396e0fd9c03228accaafe31af0b6f1c8d6a7b
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Wed, 10 Jun 2026 10:14:55 -0700
fix(c): emit asm-label rename as verbatim symbol, not re-mangled
An asm label (`extern T f(...) __asm__("name")`) is the verbatim linker
symbol name and must bypass C name mangling. decl_emit_cg_sym routed the
label back through kit_cg_c_linkage_name, which prepends the Mach-O leading
underscore. The Darwin headers spell that underscore themselves (e.g.
realpath: __asm("_realpath$DARWIN_EXTSN")), so the result was doubled
(__realpath$DARWIN_EXTSN) and unresolvable against libSystem.
Surfaced only in the stage2/3 self-build: host clang compiles driver/env/
macos.c correctly, so only kit-compiling-macos.c hit it. realpath is the
sole asm-labeled symbol kit references on arm64 macOS, so it was latent
since the realpath use landed (19bc7463).
Use the asm label verbatim; only mangle the plain source name. No-op on
ELF (mangler was already identity there). Adds a host-independent driver
regression test that targets Mach-O and asserts the verbatim symbol.
Diffstat:
2 files changed, 34 insertions(+), 2 deletions(-)
diff --git a/lang/c/decl/decl.c b/lang/c/decl/decl.c
@@ -93,8 +93,15 @@ static ObjSymId decl_emit_cg_sym(DeclTable* t, const Decl* slot) {
decl.kind = (slot->type && slot->type->kind == TY_FUNC) ? KIT_CG_DECL_FUNC
: KIT_CG_DECL_OBJECT;
decl.display_name = slot->name;
- decl.linkage_name =
- kit_cg_c_linkage_name(t->c, slot->asm_name ? slot->asm_name : slot->name);
+ /* An asm label (`extern T f(...) __asm__("name")`) is the verbatim linker
+ * symbol name: it bypasses C name mangling, including the Mach-O leading
+ * underscore. The Darwin system headers spell the underscore themselves
+ * (e.g. realpath's `__asm("_realpath$DARWIN_EXTSN")`), so routing the label
+ * back through kit_cg_c_linkage_name would double it. Only mangle the plain
+ * source name. */
+ decl.linkage_name = slot->asm_name
+ ? slot->asm_name
+ : kit_cg_c_linkage_name(t->c, slot->name);
decl.type = type_cg_id_in_pool(t->c, t->pool, slot->type);
decl.sym = decl_sym_attrs(slot);
if (decl.kind == KIT_CG_DECL_FUNC) {
diff --git a/test/driver/run.sh b/test/driver/run.sh
@@ -264,6 +264,31 @@ SRC
run_ok "cc-implicit-freestanding-headers" "$KIT" cc -target aarch64-linux \
-c "$work/implicit-header.c" -o "$work/implicit-header.o"
+# ---- asm-label rename emits the verbatim symbol (no doubled underscore) ----
+# `extern f __asm__("_name")` must produce the linker symbol "_name" exactly.
+# The Darwin headers spell the Mach-O leading underscore themselves (e.g.
+# realpath's __asm("_realpath$DARWIN_EXTSN")), so the C frontend must use the
+# asm label verbatim instead of re-running it through the underscore mangler.
+# Regression: doubling the underscore (__realpath$DARWIN_EXTSN) broke the
+# stage2 self-build link against libSystem. Always target Mach-O so the check
+# is host-independent.
+cat > "$work/asm-rename.c" <<'SRC'
+extern int probe(void) __asm__("_kit_rename_target");
+int caller(void) { return probe(); }
+SRC
+if "$KIT" cc -target arm64-apple-macos -c "$work/asm-rename.c" \
+ -o "$work/asm-rename.o" 2> "$work/asm-rename.err" &&
+ "$KIT" nm "$work/asm-rename.o" 2> "$work/asm-rename-nm.err" \
+ | grep -qE '[[:space:]]_kit_rename_target$' &&
+ ! "$KIT" nm "$work/asm-rename.o" 2>/dev/null | grep -q '__kit_rename_target'; then
+ ok "cc-asm-label-rename-verbatim-symbol"
+else
+ { cat "$work/asm-rename.err" 2>/dev/null
+ "$KIT" nm "$work/asm-rename.o" 2>/dev/null | sed 's/^/nm: /'; } \
+ > "$work/asm-rename.diag"
+ not_ok "cc-asm-label-rename-verbatim-symbol" "$work/asm-rename.diag"
+fi
+
# ---- runtime auto-build + link via nm (aarch64) ----
mkdir -p "$work/rt-support/rt"
cp -R "$repo_root/rt/include" "$work/rt-support/rt/include"