From 281ea42bdf9c20e8e8857d333625891192428b95 Mon Sep 17 00:00:00 2001 From: mfwolffe Date: Thu, 28 May 2026 14:35:30 -0400 Subject: [PATCH 1/2] feat(ffi): enable bun:ffi on FreeBSD TinyCC's vendored fork already has full FreeBSD ELF support (TARGETOS_FreeBSD, note sections, ld-elf.so.1 interp) but was excluded from the build. Remove the exclusion from three places: - scripts/build/config.ts: build-time tinycc flag - scripts/build/buildOptionsRs.ts: ENABLE_TINYCC constant - src/tcc_sys/tcc.rs: cfg-gated extern stubs Also add FreeBSD LLVM tool resolution (ports use clang21/lld21 naming and /usr/local paths) and wire up ld.lld/strip for the FreeBSD link step. Tested: bun build + bun:ffi dlopen() on FreeBSD 15.0 x86_64. --- scripts/build/buildOptionsRs.ts | 1 - scripts/build/config.ts | 7 +++---- scripts/build/deps/tinycc.ts | 1 + scripts/build/tools.ts | 13 ++++++++++--- src/tcc_sys/tcc.rs | 8 ++++---- 5 files changed, 18 insertions(+), 12 deletions(-) diff --git a/scripts/build/buildOptionsRs.ts b/scripts/build/buildOptionsRs.ts index a24fc16829b9..6225923a15f9 100644 --- a/scripts/build/buildOptionsRs.ts +++ b/scripts/build/buildOptionsRs.ts @@ -69,7 +69,6 @@ export function generateBuildOptionsRs(cfg: Config): string { "pub const ENABLE_TINYCC: bool = !cfg!(any(", ` all(windows, target_arch = "aarch64"),`, ` target_os = "android",`, - ` target_os = "freebsd",`, "));", "", ]; diff --git a/scripts/build/config.ts b/scripts/build/config.ts index 37e056083fbc..6d68df459c2f 100644 --- a/scripts/build/config.ts +++ b/scripts/build/config.ts @@ -849,10 +849,9 @@ export function resolveConfig(partial: PartialConfig, toolchain: Toolchain): Con // failure is loud ("cannot find -l:libatomic.a") and the fix is obvious. const staticLibatomic = partial.staticLibatomic ?? true; - // TinyCC: off on Windows ARM64 (not supported), Android (no upstream - // bionic support; FFI cc() falls back to dlopen-only), and FreeBSD - // (oven-sh/tinycc has no FreeBSD target). - const tinycc = partial.tinycc ?? !((windows && arm64) || abi === "android" || freebsd); + // TinyCC: off on Windows ARM64 (not supported) and Android (no upstream + // bionic support; FFI cc() falls back to dlopen-only). + const tinycc = partial.tinycc ?? !((windows && arm64) || abi === "android"); const valgrind = partial.valgrind ?? false; const fuzzilli = partial.fuzzilli ?? false; diff --git a/scripts/build/deps/tinycc.ts b/scripts/build/deps/tinycc.ts index 499a83bc16df..dbd752d51c4b 100644 --- a/scripts/build/deps/tinycc.ts +++ b/scripts/build/deps/tinycc.ts @@ -58,6 +58,7 @@ export const tinycc: Dependency = { // driver) so we leave it at the default. } if (cfg.windows) defines.CONFIG_WIN32 = true; + if (cfg.freebsd) defines.TARGETOS_FreeBSD = true; const spec: DirectBuild = { kind: "direct", diff --git a/scripts/build/tools.ts b/scripts/build/tools.ts index ff216313129c..f6630864278e 100644 --- a/scripts/build/tools.ts +++ b/scripts/build/tools.ts @@ -303,7 +303,7 @@ function llvmSearchPaths(os: OS, arch: Arch): string[] { paths.push("C:\\Program Files\\LLVM\\bin"); } - if (os === "linux" || os === "darwin") { + if (os === "linux" || os === "darwin" || os === "freebsd") { paths.push("/usr/lib/llvm/bin"); // Debian/Ubuntu-style suffixed paths paths.push(`/usr/lib/llvm-${LLVM_MAJOR}.${LLVM_MINOR}.0/bin`); @@ -312,6 +312,12 @@ function llvmSearchPaths(os: OS, arch: Arch): string[] { paths.push(`/usr/lib/llvm${LLVM_MAJOR}/bin`); } + if (os === "freebsd") { + // FreeBSD ports install to /usr/local with unsuffixed version numbers + paths.push(`/usr/local/llvm${LLVM_MAJOR}/bin`); + paths.push("/usr/local/bin"); + } + return paths; } @@ -325,6 +331,7 @@ function llvmNameVariants(name: string): string[] { `${name}-${LLVM_MAJOR}.${LLVM_MINOR}.0`, `${name}-${LLVM_MAJOR}.${LLVM_MINOR}`, `${name}-${LLVM_MAJOR}`, + `${name}${LLVM_MAJOR}`, // FreeBSD ports: clang21, lld21 ]; } @@ -482,7 +489,7 @@ export function resolveLlvmToolchain( let ld: string; if (msvcTarget) { ld = findLlvmTool("lld-link", paths, os, { checkVersion: false, required: true })?.path ?? ""; - } else if (os === "linux") { + } else if (os === "linux" || os === "freebsd") { ld = findLlvmTool("ld.lld", paths, os, { checkVersion: true, required: true })?.path ?? ""; } else { ld = ""; // darwin: unused @@ -502,7 +509,7 @@ export function resolveLlvmToolchain( // Mach-O, so darwin cross-compiles need it (resolveConfig swaps it in). let strip: string; let llvmStrip: string | undefined; - if (os === "linux") { + if (os === "linux" || os === "freebsd") { strip = findTool({ names: ["strip"], required: true, hint: "Install binutils for your distro" })?.path ?? ""; llvmStrip = findLlvmTool("llvm-strip", paths, os, { checkVersion: false, required: false })?.path; } else { diff --git a/src/tcc_sys/tcc.rs b/src/tcc_sys/tcc.rs index 66257f510d1f..2808eb374945 100644 --- a/src/tcc_sys/tcc.rs +++ b/src/tcc_sys/tcc.rs @@ -14,8 +14,8 @@ pub type TCCErrorFunc = Option = unsafe extern "C" fn(ctx: *mut Ctx, msg: *const c_char); // `libtcc.a` is only built where `cfg.tinycc` is true (`scripts/build/config.ts`): -// not Windows/aarch64 (TinyCC has no aarch64-pe-coff backend), not Android, not -// FreeBSD (the vendored fork doesn't support those targets). On those platforms +// not Windows/aarch64 (TinyCC has no aarch64-pe-coff backend), not Android. +// On those platforms // these `extern "C"` decls would be undefined at link. Zig's `comptime // !Environment.enable_tinycc` early-returns in `ffi.zig` keep the *Zig* // callers off the analysis graph, so the Zig externs never get emitted; Rust @@ -31,13 +31,13 @@ pub type ErrorFunc = unsafe extern "C" fn(ctx: *mut Ctx, msg: *const c_char // Keep this predicate in sync with `cfg.tinycc` in `scripts/build/config.ts`. macro_rules! tcc_externs { ($($(#[$attr:meta])* fn $name:ident($($arg:ident: $ty:ty),* $(,)?) $(-> $ret:ty)?;)*) => { - #[cfg(not(any(target_os = "android", target_os = "freebsd", all(windows, target_arch = "aarch64"))))] + #[cfg(not(any(target_os = "android", all(windows, target_arch = "aarch64"))))] // TODO(port): move to tcc_sys (already in *_sys crate — verify crate layout) unsafe extern "C" { $($(#[$attr])* fn $name($($arg: $ty),*) $(-> $ret)?;)* } $( - #[cfg(any(target_os = "android", target_os = "freebsd", all(windows, target_arch = "aarch64")))] + #[cfg(any(target_os = "android", all(windows, target_arch = "aarch64")))] #[allow(unused_variables, clippy::missing_safety_doc)] unsafe extern "C" fn $name($($arg: $ty),*) $(-> $ret)? { unreachable!(concat!( From 22b7074e0ba75c7f19379f75ae162160081af729 Mon Sep 17 00:00:00 2001 From: mfwolffe Date: Thu, 28 May 2026 15:08:56 -0400 Subject: [PATCH 2/2] fix(ffi): remove FreeBSD from tcc_delete stub in mod.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Missed a second cfg gate — tcc_delete in runtime/ffi/mod.rs had its own FreeBSD exclusion independent of tcc_sys. Remove it to match the tcc_sys change. Found by CodeRabbit review. --- src/runtime/ffi/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/runtime/ffi/mod.rs b/src/runtime/ffi/mod.rs index 940a6a5695bf..9525df2e33bd 100644 --- a/src/runtime/ffi/mod.rs +++ b/src/runtime/ffi/mod.rs @@ -122,7 +122,6 @@ mod TCC { // TODO(port): move to _sys #[cfg(not(any( target_os = "android", - target_os = "freebsd", all(windows, target_arch = "aarch64") )))] unsafe extern "C" { @@ -130,7 +129,6 @@ mod TCC { } #[cfg(any( target_os = "android", - target_os = "freebsd", all(windows, target_arch = "aarch64") ))] pub(super) unsafe fn tcc_delete(_s: *mut State) {