kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit f2fa4890b0f5a8f2789856784b845889b4692c18
parent a292100f526e3ee83fd6f4e76eee1eb3a4694559
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Thu,  4 Jun 2026 13:57:00 -0700

freebsd: cross-compile target, runtime, and link support

Make `kit cc --target=<arch>-freebsd` actually produce FreeBSD ELF for
aarch64/x64/rv64:

- target.c: parse the `freebsd` OS token (with version suffix, e.g.
  freebsd15.0) -> KIT_OS_FREEBSD/ELF. It previously fell through to
  freestanding.
- runtime.c: add {x86_64,aarch64,riscv64}-freebsd runtime variants (ELF,
  mirroring the Linux runtime) so cc finds libkit_rt.
- obj/elf: read ELF COMDAT groups -- record an SHT_GROUP section's
  signature symbol as absolute-defined instead of orphaning it into a
  phantom undef. FreeBSD's crt1.o brands the binary via a `.freebsd.note`
  COMDAT group. Also map STB_GNU_UNIQUE to a global binding.
- hosted.c: link FreeBSD 15's libsys (split out of libc) when present.

Linking a static hosted executable still hits the libc/libsys weak-alias
archive cycle (undefined `openat`); see doc/plan/FREEBSD.md.

test-elf/test-link/test-ar remain green.

Diffstat:
Mdriver/lib/hosted.c | 21+++++++++++++++++++++
Mdriver/lib/runtime.c | 15+++++++++++++++
Mdriver/lib/target.c | 13+++++++++++++
Msrc/obj/elf/elf.h | 5+++++
Msrc/obj/elf/read.c | 15+++++++++++++++
5 files changed, 69 insertions(+), 0 deletions(-)

diff --git a/driver/lib/hosted.c b/driver/lib/hosted.c @@ -439,11 +439,32 @@ static int hosted_resolve_freebsd(const DriverHostedRequest* req, DRIVER_HOSTED_MAX_AFTER, req, dirs, "libc.a", DRIVER_HOSTED_INPUT_ARCHIVE) != 0) return 1; + /* FreeBSD 15 split the raw syscall stubs out of libc into libsys; link it + * after libc when the sysroot provides it (pre-15 roots won't have it). + * libc.a and libsys.a are mutually recursive (libc calls the syscall + * stubs in libsys; libsys's stubs call back into libc), so re-list libc.a + * after libsys.a -- kit resolves each archive against the inputs before + * it, so the second occurrence picks up the back-references libsys + * introduces. (Equivalent to GNU ld's `--start-group libc libsys`.) */ + if (hosted_libdir_has(req->env, dirs, "libsys.a") && + (hosted_add_required_search(plan->after, &plan->nafter, + DRIVER_HOSTED_MAX_AFTER, req, dirs, + "libsys.a", DRIVER_HOSTED_INPUT_ARCHIVE) != + 0 || + hosted_add_required_search(plan->after, &plan->nafter, + DRIVER_HOSTED_MAX_AFTER, req, dirs, "libc.a", + DRIVER_HOSTED_INPUT_ARCHIVE) != 0)) + return 1; } else { if (hosted_add_required_search(plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, req, dirs, "libc.so.7", DRIVER_HOSTED_INPUT_DSO) != 0) return 1; + if (hosted_libdir_has(req->env, dirs, "libsys.so.7") && + hosted_add_required_search(plan->after, &plan->nafter, + DRIVER_HOSTED_MAX_AFTER, req, dirs, + "libsys.so.7", DRIVER_HOSTED_INPUT_DSO) != 0) + return 1; } if (hosted_add_required_search(plan->final, &plan->nfinal, DRIVER_HOSTED_MAX_FINAL, req, dirs, "crtn.o", diff --git a/driver/lib/runtime.c b/driver/lib/runtime.c @@ -124,6 +124,13 @@ static const RuntimeVariant kRtVariants[] = { "lib/include/lp64_le", 1, 0, kRtSrcX64, (uint32_t)(sizeof(kRtSrcX64) / sizeof(kRtSrcX64[0])), KIT_FLOAT_ABI_DEFAULT, NULL, NULL}, + /* FreeBSD ELF runtime mirrors the Linux ELF runtime per-arch: the + * compiler-rt helpers and coroutine asm are ABI/ELF-based, not kernel + * based, so the same source set and data model apply. */ + {"x86_64-freebsd", KIT_ARCH_X86_64, KIT_OS_FREEBSD, KIT_OBJ_ELF, 8, 8, + "lib/include/lp64_le", 1, 0, kRtSrcX64, + (uint32_t)(sizeof(kRtSrcX64) / sizeof(kRtSrcX64[0])), + KIT_FLOAT_ABI_DEFAULT, NULL, NULL}, {"x86_64-apple-darwin", KIT_ARCH_X86_64, KIT_OS_MACOS, KIT_OBJ_MACHO, 8, 8, "lib/include/lp64_le", 1, 0, kRtSrcX64, (uint32_t)(sizeof(kRtSrcX64) / sizeof(kRtSrcX64[0])), @@ -136,6 +143,10 @@ static const RuntimeVariant kRtVariants[] = { "lib/include/lp64_le", 1, 1, kRtSrcAarch64Linux, (uint32_t)(sizeof(kRtSrcAarch64Linux) / sizeof(kRtSrcAarch64Linux[0])), KIT_FLOAT_ABI_DEFAULT, NULL, NULL}, + {"aarch64-freebsd", KIT_ARCH_ARM_64, KIT_OS_FREEBSD, KIT_OBJ_ELF, 8, 8, + "lib/include/lp64_le", 1, 1, kRtSrcAarch64Linux, + (uint32_t)(sizeof(kRtSrcAarch64Linux) / sizeof(kRtSrcAarch64Linux[0])), + KIT_FLOAT_ABI_DEFAULT, NULL, NULL}, {"aarch64-apple-darwin", KIT_ARCH_ARM_64, KIT_OS_MACOS, KIT_OBJ_MACHO, 8, 8, "lib/include/lp64_le", 1, 0, kRtSrcAarch64Darwin, (uint32_t)(sizeof(kRtSrcAarch64Darwin) / sizeof(kRtSrcAarch64Darwin[0])), @@ -151,6 +162,10 @@ static const RuntimeVariant kRtVariants[] = { "lib/include/lp64_le", 1, 0, kRtSrcRv64Linux, (uint32_t)(sizeof(kRtSrcRv64Linux) / sizeof(kRtSrcRv64Linux[0])), KIT_FLOAT_ABI_DEFAULT, NULL, NULL}, + {"riscv64-freebsd", KIT_ARCH_RV64, KIT_OS_FREEBSD, KIT_OBJ_ELF, 8, 8, + "lib/include/lp64_le", 1, 0, kRtSrcRv64Linux, + (uint32_t)(sizeof(kRtSrcRv64Linux) / sizeof(kRtSrcRv64Linux[0])), + KIT_FLOAT_ABI_DEFAULT, NULL, NULL}, {"riscv64-elf", KIT_ARCH_RV64, KIT_OS_FREESTANDING, KIT_OBJ_ELF, 8, 8, "lib/include/lp64_le", 1, 0, kRtSrcRv64Elf, (uint32_t)(sizeof(kRtSrcRv64Elf) / sizeof(kRtSrcRv64Elf[0])), diff --git a/driver/lib/target.c b/driver/lib/target.c @@ -14,6 +14,13 @@ static int triple_tok_eq(const char* s, size_t n, const char* lit) { return n == l && memcmp(s, lit, n) == 0; } +/* Prefix match for OS tokens that carry a trailing version, e.g. clang emits + * "freebsd15.0" / "freebsd14" rather than a bare "freebsd". */ +static int triple_tok_prefix(const char* s, size_t n, const char* lit) { + size_t l = kit_slice_cstr(lit).len; + return n >= l && memcmp(s, lit, l) == 0; +} + KitPic driver_default_pic(KitObjFmt obj, KitOSKind os) { /* WASM has no PIC/PIE concept; freestanding targets have no dynamic * loader to apply load-time relocations. Everything else is hosted and @@ -319,6 +326,12 @@ int driver_target_from_triple(const char* triple, KitTargetSpec* out) { os_set = 1; break; } + if (triple_tok_prefix(parts[i], plen[i], "freebsd")) { + t.os = KIT_OS_FREEBSD; + t.obj = KIT_OBJ_ELF; + os_set = 1; + break; + } if (triple_tok_eq(parts[i], plen[i], "wasi")) { t.os = KIT_OS_WASI; t.obj = KIT_OBJ_WASM; diff --git a/src/obj/elf/elf.h b/src/obj/elf/elf.h @@ -115,6 +115,11 @@ #define STB_LOCAL 0 #define STB_GLOBAL 1 #define STB_WEAK 2 +/* GNU extension: a global symbol that the dynamic loader keeps unique across + * the whole process (used for C++ inline statics, and by FreeBSD's crt for the + * ABI-brand note symbol). For static-link resolution it behaves as a global + * definition. */ +#define STB_GNU_UNIQUE 10 #define STT_NOTYPE 0 #define STT_OBJECT 1 diff --git a/src/obj/elf/read.c b/src/obj/elf/read.c @@ -145,6 +145,10 @@ static u16 elf_kind_from_name(const char* name, u32 nlen, u64 sh_flags, static u16 elf_bind_to_obj(u32 b) { switch (b) { case STB_GLOBAL: + case STB_GNU_UNIQUE: + /* GNU-unique is a global with extra runtime uniqueness semantics; for + * link-time resolution it is an ordinary global definition. FreeBSD's + * crt1.o brands the binary with a GNU-unique `.freebsd.note*` symbol. */ return SB_GLOBAL; case STB_WEAK: return SB_WEAK; @@ -715,6 +719,17 @@ ObjBuilder* read_elf(Compiler* c, const char* name, const u8* data, sec_id = OBJ_SEC_NONE; value = st_value; if (st_shndx == SHN_COMMON) cmnalign = st_value; + } else if (st_shndx < e_shnum && shdrs[st_shndx].sh_type == SHT_GROUP) { + /* A COMDAT group's signature symbol is defined in its SHT_GROUP + * section, which we consume into an ObjGroup and never keep as an + * obj section (so elf_to_obj is OBJ_SEC_NONE for it). The symbol just + * names the group; it is not a data location and is never a reloc + * target. Record it as an absolute defined symbol so it doesn't look + * like a phantom undefined reference -- FreeBSD's crt1.o brands the + * binary with such a symbol (.freebsd.note*). */ + sec_id = OBJ_SEC_NONE; + value = st_value; + kind = SK_ABS; } else if (st_shndx < e_shnum) { sec_id = elf_to_obj[st_shndx]; value = st_value;