Support compiling and running C from JavaScript - #13403
Conversation
|
amazing proof of concept. i still think the header parsing issue is a solvable skill issue and that should be attempted before making this the api we use. then the api would simply be would be cool to explore later letting this be statically analyzable with a note about your benchmark
would have to look at both files but your demo is possibly just re-encoding the metadata. this is good to show call overhead, but good video encoding take seconds or minutes, not milliseconds; i don't see ffmpeg via ffi over cli a notable use case. the opinionated command i use for encoding mp4+H.264 doesn't really care about encode time, but more so file size and quality: ( |
|
so sorry, i hit the wrong button |
|
I’m curious, is there perhaps a method to specify compiler, and is there any possibility building as a shared library can be added? |
|
does this use native types -- as in can I circumvent javascript's goofy floating point precision issues? |
…s, and misc crates (#39585) Fresh PR for the part of #39561 that still applies on current main, as requested there. Net -1,851 lines (71 files, +49 / -1,900). The TextCodec section of #39561 is gone: #39485 deleted the WebKit codecs outright. Everything else from that PR is carried over unchanged (it was reviewed there, all threads resolved), plus one new group: orphaned headers. No file here is touched by a dead-code PR that is still open. Every removal has no reference left in `src/`, `packages/`, `scripts/`, `test/` or the regenerated `build/debug/codegen/`, and the tree builds without it. For Rust, C++ and headers that build is the reference check: a definition with a caller, or a header with an includer, does not compile. ### Orphaned headers (-1,169, new in this PR) Nothing includes any of these (checked by include name and by plain text across the tree, then by the build): * `src/jsc/bindings/webcore/`: `EventSender.h`, `JSDOMBindingInternalsBuiltins.h`, `JSDOMConstructorNotCallable.h`, `JSDOMConvertVariadic.h`, `JSDOMConvertXPathNSResolver.h`, `JSDOMConvertScheduledAction.h`, `BroadcastChannelIdentifier.h`, `PortIdentifier.h`, `WebSocketIdentifier.h`. WebKit-derived headers whose includers were removed by earlier cleanups. * The empty stubs earlier sweeps left in place of deleted headers (their own comment says to remove them once possible): `MessagePortChannel.h`, `MessagePortChannelProvider.h`, `MessagePortChannelProviderImpl.h`, `MessagePortChannelRegistry.h`, `MessagePortIdentifier.h`, `BroadcastChannelRegistry.h`, `JSDOMBuiltinConstructorBase.h`, `JSDOMConvertSerializedScriptValue.h`. The live registry is `BunBroadcastChannelRegistry.h`. * `src/jsc/bindings/objects.h`: 254 lines, every one of them commented out. * `src/jsc/bindings/ZigLazyStaticFunctions.h` and `ZigLazyStaticFunctions-inlines.h`: marked "GENERATED FILE", but no generator emits them and nothing includes them. The `*__put` helpers they described live in the checked-in `ZigGeneratedCode.cpp`, and since #36903 those install plain host functions. The comments in `ffi/FFIObject.rs`, `ffi/mod.rs` and `host_fn.rs` that pointed at the deleted header now say that instead. * `src/runtime/ffi/ffi-stdatomic.h`: added with `cc()` in #13403 but never part of the embedded header list (`ffi_body.rs` embeds stdbool, stdarg, stdnoreturn, stdalign, tgmath, stddef), so `bun:ffi` users never saw it. ### bun_install (-150) * `PackageInstaller.rs`: `LazyPackageDestinationDir`. The only value ever passed to `get_dir()` was the `Dir` variant, so `Owned`, the `NodeModulesPath` arm (sole reader of its `#[allow(dead_code)]` field), `Closed` and the error branch were unreachable; the else-branch built a second value only to `close()` it. The EACCES handler reads `destination_dir.fd()` directly. `pkg_dependencies` was written twice and never read. * `dependency.rs`: `DependencyExt::is_less_than` / `cmp` are byte-identical copies of the inherent `Dependency::is_less_than` / `cmp` in `install_types/resolver_hooks.rs`, and inherent associated functions win for every `Dependency::cmp(..)` path in the tree (no UFCS call exists). `DependencyExt::is_aliased`, `VersionExt::zeroed`, the two-argument `VersionExt::clone_in` (every `clone_in` call is the three-argument `Dependency` one) and the `ValueExt` trait it alone used, plus the crate-root re-export. * `error.rs`: `Error::DeviceBusy`, `BrokenPipe`, `Invalid`; the `From` impls only produce `Sys(errno)` and `WriteFailed`, so the two match arms naming them were dead too. `repository.rs`: the `Term::Stopped` arm (variant removed below). ### bun_boringssl_sys (-60) The file documents itself as the subset of symbols Bun's Rust crates consume. Removed the ones none does: `SHA512_CTX`, `RIPEMD160_CTX`, `RIPEMD160_DIGEST_LENGTH`, the `SHA384_*` / `SHA512_*` / `SHA512_256_*` / `RIPEMD160_*` functions (`bun_sha_hmac` drives these through EVP), `EVP_md5_sha1`, `SSL_CTX_get_ex_data`, `SSL_CTX_set_cipher_list`, `ERR_peek_error`, the `SSL_CIPHER` handle with `SSL_get_current_cipher` / `SSL_CIPHER_standard_name` / `SSL_CIPHER_get_name` / `SSL_get_version`, `X509_STORE_free`, `i2d_SSL_SESSION` / `d2i_SSL_SESSION` (`socket/tls_socket_functions.rs` declares and uses its own copies of that last group). ### Other C++ (-70) * `BunJSCModule.h`: the `@begin BunJSCModuleTable` lut source (never generated; the module registers with `putNativeFn`) and the `USE(BMALLOC_MEMORY_FOOTPRINT_API)` branch (macro defined nowhere, so only the `jsNull()` body was ever compiled). Dropping the stray `// clang-format off` needed one spacing fix. * `ErrorCode.cpp/.h`: the `CRYPTO_INVALID_KEY_OBJECT_TYPE(JSValue, ...)` overload; all 9 callers pass a `CryptoKeyType` (an `enum class`, not convertible to `JSValue`). * `headers-handwritten.h` + `node_process.rs`: `Bun__versions_uws` / `Bun__versions_usockets`; `BunProcess.cpp` reads versions from the generated header. ### Misc Rust (-400) * `valkey_jsc`: the file-local `UnwrapOrOom` trait, replaced by `bun_core::UnwrapOrOom`; `Default` for `Command` and `Args`. (The unused `close_subscription_ctx` from #39561 was removed on main by #39530 in the meantime.) * `sql_jsc/mysql`: the `_options` / `options_buf` fields, the `init` parameters feeding them and the JS-argument conversion; `_options` was never read and `options_buf` was always an empty box. JS call shape unchanged. `Default` for `SSLRequest`, `MySQLStatement`, `Raw`. * `crypto/CryptoHasher.rs`: `StaticHasher::HAS_ENGINE`, `true` in the only implementation, and the null-engine branches behind it. `test/js/node/crypto/crypto.test.ts` now checks the one-shot `Hash.hash()` outputs against `node:crypto`, the streaming digest and a sentinel-filled caller buffer (review requests from #39561). * `zlib.classes.ts` + `node_zlib_binding.rs`: the `dictionary` cached-value slot; the bytes have been copied into the native context since cd1ad59 and no accessor call remains. * `node_fs_stat_watcher.rs` and `timer/TimeoutObject.rs`: hand-written accessor modules that duplicated `generated_classes::js_StatWatcher` and `bun_jsc::generated::JSTimeout`, now imports. * `bun_spawn`: `Term::Stopped` (`run()` never builds it), the `spawn_sys` compat re-export, the `IoCounters` / `WinRusage` / `WinTimeval` / `FdT` re-exports (and the `spawn_sys/Cargo.toml` comment that named them), `Default` for `NewQueue`. * Manual impls with no user: `Default` for `bun_io::Request`, `PipeReadScratch`, `HTTPResponseMetadata`, `InternalStateFlags`, `RwLock<T>`, `Link<T>`, `SocketAddress`, shell `ast::Group`, `ast::If`, `WorkerPipe` (and its empty `Drop`), `PackQueueItem`, `ParseArgumentsCfg`; `Display` for shell `IO` / `InKind` / `OutKind` and for `SmolList` (no element type implements `Debug`); `PartialEq<Error> for ScanError` with the `IntoStaticStr` derive that served it; `From<Tag> for &str` in `which_npm_client.rs`. * `test/internal/source-lints/dead-code-escape-limits.json` regenerated: `PackageInstaller.rs` no longer has escapes. This is the one check whose result the PR changes (it fails against the base sources with this inventory and passes here). No other test can fail before and pass after a removal of uncalled code, and `test/internal/source-lints/CLAUDE.md` asks for no dead-symbol tests. ### Verification * `bun bd` builds and links with everything above removed (the header deletions were built separately first, so an includer would have failed that build). * `bun run rust:check-all`: clean on all 12 target triples. * `bun bd test`: `node/crypto/crypto.test.ts`, `bun/ffi/ffi.test.js`, `node/zlib/zlib.test.js`, `fs.watchFile`, `node/timers` (981 pass), `cli/install` bun-add + bun-pack, `bun/shell` brace + parse (all pass). On the previous base the same Rust diff also passed the encoding, shell interpreter, parallel runner and source-lint suites, plus hand-run Bun.SQL against MariaDB and Bun.RedisClient against redis. * `web/workers/worker.test.ts`: three "terminate() races" cases fail in this container's debug build while a 12-target cargo check was running (worker startup did not happen within the 30 ms / 5 s the tests allow); the same cases pass here on a release build that contains the same Rust and C++ changes, and nothing in this PR is reachable from worker code (the MessagePort and BroadcastChannel files removed are empty or never-included headers). ### Probably dead, left alone * `headers.h` still declares thirteen `*__fastpath` functions (`FFI__ptr__fastpath`, `Reader__*__fastpath`) that have had no definition since #36903 removed the DOMJIT paths; left out only because touching `headers.h` rebuilds most of the C++. * From #39561, unchanged: the three react_compiler lint validators whose result `pipeline.rs` discards, the never-constructed logger types in `compile_result.rs`, the test-only outbound half of the h2 engine, `bun_shim_impl::read_without_launch`, the `#[cfg(not(windows))]` stubs inside the windows-only named-pipe modules, `DependencyToEnqueue::Pending`, `misctools/cold-jsc-start.cpp` (no build rule since #21863) and `completions/spec.yaml`. <!-- robobun:evidence:begin --> --- **[review]** gate passed · iteration 3 · 71 files touched <details><summary>fails on main (without fix)</summary> ```console ASAN without fix: BUILD FAILED (no junit output) $ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/node/crypto/crypto.test.ts ninja: Entering directory `/workspace/bun/build/debug' [1/181] gen ErrorCode+*.h [2/181] gen bake.{client,server,error}.js -> bake.client.js, bake.server.js, bake.error.js [3/181] gen JSBuffer.lut.h Generating /workspace/bun/build/debug/codegen/JSBuffer.lut.h from /workspace/bun/src/jsc/bindings/JSBuffer.cpp [4/181] gen generated_host_exports.rs generated_host_exports.rs: 92 exports (host=3, lazy=10, generic=79, rust=0); 241 extern-C blocks audited [5/181] gen ZigGeneratedClasses.{cpp,h,rs} Found 2 classes from /workspace/bun/src/jsc/resolve_message.classes.ts - ResolveMessage (15 fields) - BuildMessage (10 fields) Found 1 classes from /workspace/bun/src/runtime/api/Archive.classes.ts - Archive (4 fields, 1 class fields) Found 2 classes from /workspace/bun/src/runtime/api/BunObject.classes.ts - ResourceUsage (8 fields) - Subprocess (20 fields) Found 1 classes from /workspace/bun/src/runtime/api/cron.classes.ts - CronJob (5 fields) Found 3 classes from /workspace/bun/src/runtime/api/filesys ... (truncated) release without fix: all passed bun test v1.4.0-canary.1 (6fcf13c) test/js/node/crypto/crypto.test.ts: (pass) CryptoHasher > CryptoHasher.algorithms [0.08ms] (pass) CryptoHasher > new CryptoHasher blake2b256 [1.14ms] (pass) CryptoHasher > CryptoHasher.hash blake2b256 [0.04ms] (pass) CryptoHasher > new CryptoHasher blake2b256 multi-part [0.04ms] (pass) CryptoHasher > new CryptoHasher blake2b256 to Buffer [0.05ms] (pass) CryptoHasher > new CryptoHasher blake2b512 [0.01ms] (pass) CryptoHasher > CryptoHasher.hash blake2b512 (pass) CryptoHasher > new CryptoHasher blake2b512 multi-part (pass) CryptoHasher > new CryptoHasher blake2b512 to Buffer (pass) CryptoHasher > new CryptoHasher blake2s256 [0.01ms] (pass) CryptoHasher > CryptoHasher.hash blake2s256 (pass) CryptoHasher > new CryptoHasher blake2s256 multi-part (pass) CryptoHasher > new CryptoHasher blake2s256 to Buffer (pass) CryptoHasher > new CryptoHasher md4 (pass) CryptoHasher > CryptoHasher.hash md4 (pass) CryptoHasher > new CryptoHasher md4 multi-part (pass) CryptoHasher > new CryptoHasher md4 to Buffer (pass) CryptoHasher > new CryptoHasher md5 (pass) CryptoHasher > CryptoHasher.hash md5 (pass) CryptoHasher > new CryptoHasher md5 multi-part ... (truncated) ``` </details> <details><summary>passes on PR (with fix)</summary> ```console ASAN with fix: all passed $ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/node/crypto/crypto.test.ts bun test v1.4.0 (8326d1b) test/js/node/crypto/crypto.test.ts: (pass) CryptoHasher > CryptoHasher.algorithms [2.82ms] (pass) CryptoHasher > new CryptoHasher blake2b256 [3.70ms] (pass) CryptoHasher > CryptoHasher.hash blake2b256 [2.35ms] (pass) CryptoHasher > new CryptoHasher blake2b256 multi-part [2.37ms] (pass) CryptoHasher > new CryptoHasher blake2b256 to Buffer [3.36ms] (pass) CryptoHasher > new CryptoHasher blake2b512 [1.62ms] (pass) CryptoHasher > CryptoHasher.hash blake2b512 [0.74ms] (pass) CryptoHasher > new CryptoHasher blake2b512 multi-part [1.00ms] (pass) CryptoHasher > new CryptoHasher blake2b512 to Buffer [0.97ms] (pass) CryptoHasher > new CryptoHasher blake2s256 [0.86ms] (pass) CryptoHasher > CryptoHasher.hash blake2s256 [0.38ms] (pass) CryptoHasher > new CryptoHasher blake2s256 multi-part [0.56ms] (pass) CryptoHasher > new CryptoHasher blake2s256 to Buffer [1.40ms] (pass) CryptoHasher > new CryptoHasher md4 [0.76ms] (pass) CryptoHasher > CryptoHasher.hash md4 [0.43ms] (pass) CryptoHash ... (truncated) release with fix: all passed $ bun scripts/build.ts --profile=release [configured] bun-profile → bun (stripped) in 666ms (unchanged) ninja: Entering directory `/workspace/bun/build/release' [1/141] gen ErrorCode+*.h [2/141] gen bake.{client,server,error}.js -> bake.client.js, bake.server.js, bake.error.js [3/141] gen JSBuffer.lut.h Generating /workspace/bun/build/release/codegen/JSBuffer.lut.h from /workspace/bun/src/jsc/bindings/JSBuffer.cpp [4/141] gen generated_host_exports.rs generated_host_exports.rs: 92 exports (host=3, lazy=10, generic=79, rust=0); 241 extern-C blocks audited [5/141] gen ZigGeneratedClasses.{cpp,h,rs} Found 2 classes from /workspace/bun/src/jsc/resolve_message.classes.ts - ResolveMessage (15 fields) - BuildMessage (10 fields) Found 1 classes from /workspace/bun/src/runtime/api/Archive.classes.ts - Archive (4 fields, 1 class fields) Found 2 classes from /workspace/bun/src/runtime/api/BunObject.classes.ts - ResourceUsage (8 fields) - Subprocess (20 fields) Found 1 classes from /workspace/bun/src/runtime/api/cron.classes.ts - CronJob (5 fields) Found 3 classes from /workspace/bun/src/runtime/api/filesystem_router.classes.ts - FileSystemRouter (5 fields) - ... (truncated) ``` </details> <details><summary>diff hotspot</summary> ``` src/boringssl_sys/boringssl.rs | 69 ------ src/http/InternalState.rs | 7 - src/http/lib.rs | 15 +- src/install/PackageInstaller.rs | 90 +------- .../PackageManager/PackageManagerResolution.rs | 3 +- src/install/dependency.rs | 99 -------- src/install/error.rs | 9 - src/install/hoisted_install.rs | 2 - src/install/lib.rs | 2 +- src/install/lockfile.rs | 2 +- src/install/repository.rs | 1 - src/io/lib.rs | 10 - src/io/pipe_read_scratch.rs | 6 - src/jsc/bindings/ErrorCode.cpp | 14 -- src/jsc/bindings/ErrorCode.h | 1 - src/jsc/bindings/ZigLazyStaticFunctions-inlines.h | 33 --- src/jsc/bindings/ZigLazyStaticFunctions.h | 21 -- src/jsc/bindings/headers-handwritten.h | 5 - src/jsc/bindings/objects.h | 254 --------------------- .../bindings/webcore/BroadcastChannelIdentifier.h | 35 --- .../bindings/webcore/BroadcastChannelRegistry.h | 5 - src/jsc/bindings/webcore/EventSender.h | 116 ---------- .../webcore/JSDOMBindingInternalsBuiltins.h | 159 ------------- .../bindings/webcore/JSDOMBuiltinConstructorBase.h | 2 - .../bindings/webcore/JSDOMConstructorNotCallable.h | 85 ------- .../bindings/webcore/JSDOMConvertScheduledAction.h | 53 ----- .../webcore/JSDOMConvertSerializedScriptValue.h | 2 - src/jsc/bindings/webcore/JSDOMConvertVariadic.h | 74 ------ .../bindings/webcore/JSDOMConvertXPathNSResolver.h | 55 ----- src/jsc/bindings/webcore/MessagePortChannel.h | 5 - .../bindings/webcore/MessagePortChannelProvider.h | 5 - .../webcore/MessagePortChannelProvi ... (truncated) ``` </details> **gate history** · 2 passed · 0 rejected · iteration 3 <details><summary>evidence per changed file</summary> ``` file reads edits tests src/boringssl_sys/boringssl.rs 0 0 0 src/http/InternalState.rs 0 0 0 src/http/lib.rs 0 0 0 src/install/PackageInstaller.rs 5 0 0 src/install/PackageManager/PackageManagerResolution.rs 0 0 0 src/install/dependency.rs 0 0 0 src/install/error.rs 0 0 0 src/install/hoisted_install.rs 0 0 0 src/install/lib.rs 0 0 0 src/install/lockfile.rs 0 0 0 src/install/repository.rs 0 0 0 src/io/lib.rs 0 0 0 src/io/pipe_read_scratch.rs 0 0 0 src/jsc/bindings/ErrorCode.cpp 0 0 0 src/jsc/bindings/ErrorCode.h 0 0 0 src/jsc/bindings/ZigLazyStaticFunctions-inlines.h 0 0 0 (+ 55 more files) ``` </details> <!-- robobun:evidence:end -->
What does this PR do?
This adds a
ccfunction inbun:ffiwhich directly exposes TinyCC (a C compiler) and lets you run the code.An example program below converts a file to .mp4 using ffmpeg's API. Converting a 20 MB .mov file to .mp4 this method takes about 13ms per invocation, but
ffmpeg -i node-1.mov -c:v copy node-1.mp4takes 34ms, likely due to start time overhead.script:
ffmpeg cli:
C (this was generated by claude)
This program below which includes 144 header files compiles in about 30ms in a debug build on an M1.
How did you verify your code works?