Skip to content
Merged
16 changes: 10 additions & 6 deletions scripts/build/deps/mimalloc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

import type { Dependency, DirectBuild } from "../source.ts";

const MIMALLOC_COMMIT = "d078ad066752ea7fd06acb2323b7a90c49d7d8e4";
const MIMALLOC_COMMIT = "1803341d6241d8fa4b3f65fa68cb13a32ad92f04";

export const mimalloc: Dependency = {
name: "mimalloc",
Expand Down Expand Up @@ -54,11 +54,15 @@ export const mimalloc: Dependency = {
...(cfg.release && { MI_BUILD_RELEASE: true }),
};

// Disable Transparent Huge Pages. Measured impact:
// bun --eval 1: THP off = 30MB peak, THP on = 52MB peak
// http-hello.js: THP off = 52MB peak, THP on = 74MB peak
// THP trades memory for (sometimes) latency; for a JS runtime the
// memory cost isn't worth it. The cmake option only applies on Linux.
// Opt mimalloc's arenas out of Transparent Huge Pages. Only matters when
// /sys/kernel/mm/transparent_hugepage/enabled is `always` (RHEL, Amazon
// Linux, Arch); under `madvise` nothing in bun asks for huge pages anyway.
// Measured on an `always` box (release, x64):
// bun -e 1: THP off = 30MB peak, THP on = 54MB peak
// Bun.serve hello: THP off = 46MB rss, THP on = 68MB rss
// mimalloc does this per mapping (MADV_NOHUGEPAGE), not per process, so
// spawned children keep the system THP policy. JSC's reservations and
// bun_alloc's lazy arena opt out the same way on their side.
if (cfg.linux) defines.MI_DEFAULT_ALLOW_THP = 0;

// Skip prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, ...) after each mmap.
Expand Down
2 changes: 1 addition & 1 deletion scripts/build/deps/webkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* for local mode. Override via `--webkit-version=<hash>` to test a branch.
* From https://github.com/oven-sh/WebKit releases.
*/
export const WEBKIT_VERSION = "e6e37cda216c0292ae68c30c84a9dc8601d0fba5";
export const WEBKIT_VERSION = "ddea71318fec9b923465c7c45ded8fa713ca3251";

/**
* WebKit (JavaScriptCore) — the JS engine.
Expand Down
8 changes: 8 additions & 0 deletions src/bun_alloc/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1610,6 +1610,14 @@ fn bss_mmap_noreserve(len: usize) -> *mut u8 {
if p == libc::MAP_FAILED {
crate::out_of_memory();
}
// Under THP `enabled=always` the first write to each 2 MiB stretch would
// fault a whole huge page, turning this demand-faulted arena into ~4 MiB of
// RSS. Per-VMA opt-out (not `PR_SET_THP_DISABLE`, which children inherit).
// SAFETY: `p..p+len` is the mapping created above.
#[cfg(any(target_os = "linux", target_os = "android"))]
unsafe {
libc::madvise(p, len, libc::MADV_NOHUGEPAGE);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
// LSan only scans data/BSS, stacks, and malloc-tracked heap for live
// pointers. This anonymous mapping is none of those, so any `Box`/`Vec`
// whose owning pointer lives inside a `bss_*!` singleton (e.g. the
Expand Down
23 changes: 23 additions & 0 deletions test/js/bun/spawn/spawn.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
getMaxFD,
isBroken,
isDebug,
isLinux,
isMacOS,
isPosix,
isWindows,
Expand Down Expand Up @@ -1536,3 +1537,25 @@ describe("uid/gid", () => {
expect(thrown?.code).toBe("EPERM");
});
});

// The allocator opts its own mappings out of THP; it must not use
// prctl(PR_SET_THP_DISABLE), which children inherit across execve. Gate on our
// parent so an environment (or older bun) that disabled THP itself skips.
function thpEnabled(status: string) {
return status.match(/^THP_enabled:\s*(\d)/m)?.[1];
}
function parentThp() {
if (!isLinux) return undefined;
try {
return thpEnabled(readFileSync(`/proc/${process.ppid}/status`, "utf8"));
} catch {
return undefined; // hidepid mount: cannot tell, skip
}
}
Comment thread
claude[bot] marked this conversation as resolved.
it.if(parentThp() === "1")("spawned children keep the system THP policy", async () => {
await using proc = spawn({ cmd: ["cat", "/proc/self/status"], stdout: "pipe", stderr: "inherit" });
const [stdout, exitCode] = await Promise.all([proc.stdout.text(), proc.exited]);
expect(thpEnabled(stdout)).toBe("1");
expect(thpEnabled(readFileSync("/proc/self/status", "utf8"))).toBe("1");
expect(exitCode).toBe(0);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
});
2 changes: 1 addition & 1 deletion test/js/node/process/process.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ it("process.versions", () => {
const expectedVersions = {
boringssl: "1a41b9025c2c0a37edd07ff10f6944f03e028522",
libarchive: "ded82291ab41d5e355831b96b0e1ff49e24d8939",
mimalloc: "d078ad066752ea7fd06acb2323b7a90c49d7d8e4",
mimalloc: "1803341d6241d8fa4b3f65fa68cb13a32ad92f04",
picohttpparser: "066d2b1e9ab820703db0837a7255d92d30f0c9f5",
zlib: "12731092979c6d07f42da27da673a9f6c7b13586",
tinycc: "05f0fafaa3be31e31d7b4b5c17dc60f62c991171",
Expand Down
Loading