perf(linux): add memfd optimizations and typed flags (#25597)

## Summary

- Add `MemfdFlags` enum to replace raw integer flags for `memfd_create`,
providing semantic clarity for different use cases (`executable`,
`non_executable`, `cross_process`)
- Add support for `MFD_EXEC` and `MFD_NOEXEC_SEAL` flags (Linux 6.3+)
with automatic fallback to older kernel flags when `EINVAL` is returned
- Use memfd + `/proc/self/fd/{fd}` path for loading embedded `.node`
files in standalone builds, avoiding disk writes entirely on Linux

## Test plan

- [ ] Verify standalone builds with embedded `.node` files work on Linux
- [ ] Verify fallback works on older kernels (pre-6.3)
- [ ] Verify subprocess stdio memfd still works correctly

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Jarred Sumner
2025-12-19 23:18:21 -08:00
committed by GitHub
parent fa983247b2
commit c08ffadf56
8 changed files with 109 additions and 23 deletions

View File

@@ -132,7 +132,7 @@ pub fn create(bytes: []const u8) bun.sys.Maybe(bun.webcore.Blob.Store.Bytes) {
const label = std.fmt.bufPrintZ(&label_buf, "memfd-num-{d}", .{memfd_counter.fetchAdd(1, .monotonic)}) catch "";
// Using huge pages was slower.
const fd = switch (bun.sys.memfd_create(label, std.os.linux.MFD.CLOEXEC)) {
const fd = switch (bun.sys.memfd_create(label, .non_executable)) {
.err => |err| return .{ .err = bun.sys.Error.fromCode(err.getErrno(), .open) },
.result => |fd| fd,
};

View File

@@ -30,19 +30,45 @@ pub fn resetArena(this: *ModuleLoader, jsc_vm: *VirtualMachine) void {
}
}
pub fn resolveEmbeddedFile(vm: *VirtualMachine, input_path: []const u8, extname: []const u8) ?[]const u8 {
fn resolveEmbeddedNodeFileViaMemfd(file: *bun.StandaloneModuleGraph.File, path_buffer: *bun.PathBuffer, fd: *i32) ![]const u8 {
var label_buf: [128]u8 = undefined;
const count = struct {
pub var counter = std.atomic.Value(u32).init(0);
pub fn get() u32 {
return counter.fetchAdd(1, .seq_cst);
}
}.get();
const label = std.fmt.bufPrintZ(&label_buf, "node-addon-{d}", .{count}) catch "";
const memfd = try bun.sys.memfd_create(label, .executable).unwrap();
errdefer memfd.close();
fd.* = @intCast(memfd.cast());
errdefer fd.* = -1;
try bun.sys.ftruncate(memfd, @intCast(file.contents.len)).unwrap();
try bun.sys.File.writeAll(.{ .handle = memfd }, file.contents).unwrap();
return try std.fmt.bufPrint(path_buffer, "/proc/self/fd/{d}", .{memfd.cast()});
}
pub fn resolveEmbeddedFile(vm: *VirtualMachine, path_buf: *bun.PathBuffer, linux_memfd: *i32, input_path: []const u8, extname: []const u8) ?[]const u8 {
if (input_path.len == 0) return null;
var graph = vm.standalone_module_graph orelse return null;
const file = graph.find(input_path) orelse return null;
if (comptime Environment.isLinux) {
// TODO: use /proc/fd/12346 instead! Avoid the copy!
// Best-effort: use memfd to avoid hitting the disk
if (resolveEmbeddedNodeFileViaMemfd(file, path_buf, linux_memfd)) |path| {
return path;
} else |_| {
// fall back to temp file
}
}
// atomically write to a tmpfile and then move it to the final destination
var tmpname_buf: bun.PathBuffer = undefined;
const tmpfilename = bun.fs.FileSystem.tmpname(extname, &tmpname_buf, bun.hash(file.name)) catch return null;
const tmpname_buf = bun.path_buffer_pool.get();
defer bun.path_buffer_pool.put(tmpname_buf);
const tmpfilename = bun.fs.FileSystem.tmpname(extname, tmpname_buf, bun.hash(file.name)) catch return null;
const tmpdir: bun.FD = .fromStdDir(bun.fs.FileSystem.instance.tmpdir() catch return null);
// First we open the tmpfile, to avoid any other work in the event of failure.
@@ -50,7 +76,7 @@ pub fn resolveEmbeddedFile(vm: *VirtualMachine, input_path: []const u8, extname:
defer tmpfile.fd.close();
switch (bun.api.node.fs.NodeFS.writeFileWithPathBuffer(
&tmpname_buf, // not used
tmpname_buf, // not used
.{
.data = .{
@@ -66,7 +92,7 @@ pub fn resolveEmbeddedFile(vm: *VirtualMachine, input_path: []const u8, extname:
},
else => {},
}
return bun.path.joinAbs(bun.fs.FileSystem.instance.fs.tmpdirPath(), .auto, tmpfilename);
return bun.path.joinAbsStringBuf(bun.fs.FileSystem.instance.fs.tmpdirPath(), path_buf, &[_]string{tmpfilename}, .auto);
}
pub export fn Bun__getDefaultLoader(global: *JSGlobalObject, str: *const bun.String) api.Loader {
@@ -1300,12 +1326,14 @@ pub const FetchFlags = enum {
};
/// Support embedded .node files
export fn Bun__resolveEmbeddedNodeFile(vm: *VirtualMachine, in_out_str: *bun.String) bool {
export fn Bun__resolveEmbeddedNodeFile(vm: *VirtualMachine, in_out_str: *bun.String, linux_memfd_fd_to_close: *i32) bool {
if (vm.standalone_module_graph == null) return false;
const input_path = in_out_str.toUTF8(bun.default_allocator);
defer input_path.deinit();
const result = ModuleLoader.resolveEmbeddedFile(vm, input_path.slice(), "node") orelse return false;
const path_buffer = bun.path_buffer_pool.get();
defer bun.path_buffer_pool.put(path_buffer);
const result = ModuleLoader.resolveEmbeddedFile(vm, path_buffer, linux_memfd_fd_to_close, input_path.slice(), "node") orelse return false;
in_out_str.* = bun.String.cloneUTF8(result);
return true;
}

View File

@@ -1346,7 +1346,7 @@ pub fn spawnProcessPosix(
else => "spawn_stdio_generic",
};
const fd = bun.sys.memfd_create(label, 0).unwrap() catch break :use_memfd;
const fd = bun.sys.memfd_create(label, .cross_process).unwrap() catch break :use_memfd;
to_close_on_error.append(fd) catch {};
to_set_cloexec.append(fd) catch {};

View File

@@ -100,7 +100,7 @@ pub const Stdio = union(enum) {
else => "spawn_stdio_memory_file",
};
const fd = bun.sys.memfd_create(label, 0).unwrap() catch return false;
const fd = bun.sys.memfd_create(label, .cross_process).unwrap() catch return false;
var remain = this.byteSlice();

View File

@@ -1001,10 +1001,23 @@ pub const FFI = struct {
if (object_value.isEmptyOrUndefinedOrNull()) return invalidOptionsArg(global);
const object = object_value.getObject() orelse return invalidOptionsArg(global);
var filepath_buf: bun.PathBuffer = undefined;
var filepath_buf = bun.path_buffer_pool.get();
defer bun.path_buffer_pool.put(filepath_buf);
var linux_memfd_to_close: i32 = -1;
defer {
if (Environment.isLinux) {
if (linux_memfd_to_close != -1) {
_ = bun.FD.fromSystem(linux_memfd_to_close).close();
}
} else {
bun.debugAssert(linux_memfd_to_close == -1);
}
}
const name = brk: {
if (jsc.ModuleLoader.resolveEmbeddedFile(
vm,
filepath_buf,
&linux_memfd_to_close,
name_slice.slice(),
switch (Environment.os) {
.linux => "so",
@@ -1013,7 +1026,6 @@ pub const FFI = struct {
.wasm => @compileError("TODO"),
},
)) |resolved| {
@memcpy(filepath_buf[0..resolved.len], resolved);
filepath_buf[resolved.len] = 0;
break :brk filepath_buf[0..resolved.len];
}

View File

@@ -296,7 +296,7 @@ JSC_DEFINE_CUSTOM_SETTER(Process_defaultSetter, (JSC::JSGlobalObject * globalObj
return true;
}
extern "C" bool Bun__resolveEmbeddedNodeFile(void*, BunString*);
extern "C" bool Bun__resolveEmbeddedNodeFile(void*, BunString*, int32_t*);
#if OS(WINDOWS)
extern "C" HMODULE Bun__LoadLibraryBunString(BunString*);
#endif
@@ -434,6 +434,7 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionDlopen, (JSC::JSGlobalObject * globalOb
}
CString utf8;
int32_t linuxMemfdToClose = -1;
// Support embedded .node files
// See StandaloneModuleGraph.zig for what this "$bunfs" thing is
@@ -445,8 +446,8 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionDlopen, (JSC::JSGlobalObject * globalOb
bool deleteAfter = false;
if (filename.startsWith(StandaloneModuleGraph__base_path)) {
BunString bunStr = Bun::toString(filename);
if (Bun__resolveEmbeddedNodeFile(globalObject->bunVM(), &bunStr)) {
filename = bunStr.toWTFString(BunString::ZeroCopy);
if (Bun__resolveEmbeddedNodeFile(globalObject->bunVM(), &bunStr, &linuxMemfdToClose)) {
filename = bunStr.transferToWTFString();
deleteAfter = !filename.startsWith("/proc/"_s);
}
}
@@ -481,11 +482,20 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionDlopen, (JSC::JSGlobalObject * globalOb
delete[] dupeZ;
}
}
ASSERT(linuxMemfdToClose == -1);
#else
if (deleteAfter) {
deleteAfter = false;
Bun__unlink(utf8.data(), utf8.length());
}
#if OS(LINUX)
if (linuxMemfdToClose != -1) {
close(linuxMemfdToClose);
linuxMemfdToClose = -1;
}
#else
ASSERT(linuxMemfdToClose == -1);
#endif
#endif
};

View File

@@ -221,7 +221,7 @@ pub fn createMemfdForTesting(globalObject: *jsc.JSGlobalObject, callFrame: *jsc.
}
const size = arguments.ptr[0].toInt64();
switch (bun.sys.memfd_create("my_memfd", std.os.linux.MFD.CLOEXEC)) {
switch (bun.sys.memfd_create("my_memfd", .non_executable)) {
.result => |fd| {
_ = bun.sys.ftruncate(fd, size);
return jsc.JSValue.jsNumber(fd.cast());

View File

@@ -2971,15 +2971,51 @@ pub fn munmap(memory: []align(page_size_min) const u8) Maybe(void) {
} else return .success;
}
pub fn memfd_create(name: [:0]const u8, flags: u32) Maybe(bun.FileDescriptor) {
pub const MemfdFlags = enum(u32) {
// Recent Linux kernel versions require MFD_EXEC.
executable = MFD_EXEC | MFD_ALLOW_SEALING | MFD_CLOEXEC,
non_executable = MFD_NOEXEC_SEAL | MFD_ALLOW_SEALING | MFD_CLOEXEC,
cross_process = MFD_NOEXEC_SEAL,
pub fn olderKernelFlag(this: MemfdFlags) u32 {
return switch (this) {
.non_executable, .executable => MFD_CLOEXEC,
.cross_process => 0,
};
}
const MFD_NOEXEC_SEAL: u32 = 0x0008;
const MFD_EXEC: u32 = 0x0010;
const MFD_CLOEXEC: u32 = std.os.linux.MFD.CLOEXEC;
const MFD_ALLOW_SEALING: u32 = std.os.linux.MFD.ALLOW_SEALING;
};
pub fn memfd_create(name: [:0]const u8, flags_: MemfdFlags) Maybe(bun.FileDescriptor) {
if (comptime !Environment.isLinux) @compileError("linux only!");
var flags: u32 = @intFromEnum(flags_);
while (true) {
const rc = std.os.linux.memfd_create(name, flags);
log("memfd_create({s}, {s}) = {d}", .{ name, @tagName(flags_), rc });
const rc = std.os.linux.memfd_create(name, flags);
if (Maybe(bun.FileDescriptor).errnoSys(rc, .memfd_create)) |err| {
switch (err.getErrno()) {
.INTR => continue,
.INVAL => {
// MFD_EXEC / MFD_NOEXEC_SEAL require Linux 6.3.
if (@intFromEnum(flags_) == flags) {
flags = flags_.olderKernelFlag();
log("memfd_create retrying without exec/noexec flag, using {d}", .{flags});
continue;
}
},
else => {},
}
log("memfd_create({s}, {d}) = {d}", .{ name, flags, rc });
return err;
}
return Maybe(bun.FileDescriptor).errnoSys(rc, .memfd_create) orelse
.{ .result = .fromNative(@intCast(rc)) };
return .{ .result = .fromNative(@intCast(rc)) };
}
unreachable;
}
pub fn setPipeCapacityOnLinux(fd: bun.FileDescriptor, capacity: usize) Maybe(usize) {