This was an unplanned feature I worked on mostly a month ago on a Saturday for fun. Happy to answer any questions
To get it out the door, ended up adding some patches to TinyCC to support .framework on macOS and fix a few things with dlopen and include paths. Also added support for parsing the deprecated attribute used in lots of Darwin headers. C parsers seem a lot simpler than JavaScript which is nice
Awesome work on this, it looks like a game changer!
From my (admittedly limited) knowledge on this space, it seems like this is a straight upgrade over WASM, with the only limiting factor being that your stack is limited to Bun + C. Are there any downsides of this feature when compared with the alternatives mentioned at the start of the article? There are some tradeoffs listed at the bottom but I'm not sure how napi/WASM perform in those aspects either.
There is something I don't understand about something. Why are individual solutions sought instead of nodes with an existing and large community? Why don't you pass bun's properties to Node?
You mean why not implement these things directly in node instead of a separate runtime? There's significant differences between runtimes. Node uses V8 while Bun uses JavascriptCore. Node uses C++ and Bun uses Zig. I'd guess it would be very difficult to land these changes in Node.
But Node now will have some catch up to do. It is already adding support for running Typescript without transpiling it to js first and that's because Bun and Deno provided it first and people saw it was a good idea. Maybe Node will add these things in the future.
I ran some benchmarks and got about a 10% improvement by porting a simple function used in autorouting to C. With the subfunction ported to C, Bun was still ~15% slower than node (v8). As the article hints at, you probably need to port fairly large subfunctions to see major performance gains. Results:
Bun: 6.7410ms
Bun FFI w/ C: 6.0413ms
Node: 5.1307ms
C only: 4.3ms (+- 1ms)
I'm generally very bullish on Bun and was very happy with the DX for this C api. Great work to Jarred and the team!!!
I wonder about the security of this. Are programs executed in some kind of jail? Is there a limit to certain features, e.g., opening a socket to somewhere?
> However, for system libraries, WebAssembly's isolated memory model comes with serious tradeoffs.
> Isolation means no system calls
WebAssembly can only access functions the runtime exposes to it. Usually, that's JavaScript.
Without digging into the code I'm going to assume (guess) that this feature did not take the main value-prop of the WASM model.
Afaik, this is explicitly against the isolation that WASM imposes [1]
> Modules must declare all accessible functions and their associated types at load time, even when dynamic linking is used. This allows implicit enforcement of control-flow integrity (CFI) through structured control-flow.
I seem to remember a WASI developer talk that discussed syscalls here, but I can't remember the specifics. The gist was basically along the lines of, "syscalls are a level of privilege that should not be cart-blanche accessible to all programs at all times"
It's worth noting that similar functionality is what allowed PHP to be useful well beyond its original capabilities when companies hit scale and needed more performance out of it. Companies would just write a C module for their critical path code, and load it in as a PHP extension.
An article from Facebook in 2010 about them writing and using a custom C extension for PHP, that was probably the precursor to JSX:
I would say that the problem really is there. Dealing with native dependencies and addons was almost always a pain as the article describes (and not just from developer perspective), so anything that helps there is really appreciated.
Not sure what you mean by the right tools in this context.
First of all, this is for C, and most extensions are written in C++ or Rust nowadays.
Secondly, the right tools are having Python, the C and C++ compiler, node-gyp and cmake.js installed, and actually understand how they work.
But what do I know, nowadays folks use C and C++ as scripting languages putting a full library into a single header file to avoid learning how to use the compiler and linker.
To get it out the door, ended up adding some patches to TinyCC to support .framework on macOS and fix a few things with dlopen and include paths. Also added support for parsing the deprecated attribute used in lots of Darwin headers. C parsers seem a lot simpler than JavaScript which is nice
From my (admittedly limited) knowledge on this space, it seems like this is a straight upgrade over WASM, with the only limiting factor being that your stack is limited to Bun + C. Are there any downsides of this feature when compared with the alternatives mentioned at the start of the article? There are some tradeoffs listed at the bottom but I'm not sure how napi/WASM perform in those aspects either.
But Node now will have some catch up to do. It is already adding support for running Typescript without transpiling it to js first and that's because Bun and Deno provided it first and people saw it was a good idea. Maybe Node will add these things in the future.
Bun: 6.7410ms Bun FFI w/ C: 6.0413ms Node: 5.1307ms C only: 4.3ms (+- 1ms)
I'm generally very bullish on Bun and was very happy with the DX for this C api. Great work to Jarred and the team!!!
benchmark code: https://github.com/tscircuit/bun-ffi-benchmarking
> However, for system libraries, WebAssembly's isolated memory model comes with serious tradeoffs.
> Isolation means no system calls WebAssembly can only access functions the runtime exposes to it. Usually, that's JavaScript.
Without digging into the code I'm going to assume (guess) that this feature did not take the main value-prop of the WASM model.
Afaik, this is explicitly against the isolation that WASM imposes [1]
> Modules must declare all accessible functions and their associated types at load time, even when dynamic linking is used. This allows implicit enforcement of control-flow integrity (CFI) through structured control-flow.
I seem to remember a WASI developer talk that discussed syscalls here, but I can't remember the specifics. The gist was basically along the lines of, "syscalls are a level of privilege that should not be cart-blanche accessible to all programs at all times"
It's worth noting that similar functionality is what allowed PHP to be useful well beyond its original capabilities when companies hit scale and needed more performance out of it. Companies would just write a C module for their critical path code, and load it in as a PHP extension.
An article from Facebook in 2010 about them writing and using a custom C extension for PHP, that was probably the precursor to JSX:
https://www.facebook.com/notes/10158791323777200/
An article from Zend in 2011 about how to write a PHP extension in C:
http://web.archive.org/web/20110222035803/http://devzone.zen...
https://www.cappuccino.dev/
Anyone that cares about compiling C code should be skilled enough to actually use the right tools in first place.
Not sure what you mean by the right tools in this context.
Secondly, the right tools are having Python, the C and C++ compiler, node-gyp and cmake.js installed, and actually understand how they work.
But what do I know, nowadays folks use C and C++ as scripting languages putting a full library into a single header file to avoid learning how to use the compiler and linker.
https://github.com/holepunchto/bare-abort or https://github.com/holepunchto/bare-buffer