diff --git a/scripts/build/deps/webkit.ts b/scripts/build/deps/webkit.ts index b7ca822369da..3d81fccf1b72 100644 --- a/scripts/build/deps/webkit.ts +++ b/scripts/build/deps/webkit.ts @@ -7,7 +7,7 @@ // -lto variants built with ThinLTO (per-module summaries for cross-language // importing), and the Windows ICU data table filtered + per-item zstd // compressed (lazily decompressed via bun_icu_decompress.cpp). -export const WEBKIT_VERSION = "4895f45dfbd0d1226c4d41799887bc0ecb9f341b"; +export const WEBKIT_VERSION = "autobuild-preview-pr-308-40ff52aa"; /** * WebKit (JavaScriptCore) — the JS engine. diff --git a/src/jsc/modules/BunJSCModule.h b/src/jsc/modules/BunJSCModule.h index deb683bb80d7..4633af86f315 100644 --- a/src/jsc/modules/BunJSCModule.h +++ b/src/jsc/modules/BunJSCModule.h @@ -799,7 +799,7 @@ JSC_DEFINE_HOST_FUNCTION(functionGenerateHeapSnapshotForDebugging, } scope.releaseAssertNoException(); - return JSValue::encode(JSONParse(globalObject, WTF::move(jsonString))); + RELEASE_AND_RETURN(scope, JSValue::encode(JSONParse(globalObject, WTF::move(jsonString)))); } JSC_DEFINE_HOST_FUNCTION(functionSerialize, diff --git a/test/js/bun/jsc/bun-jsc.test.ts b/test/js/bun/jsc/bun-jsc.test.ts index 13657cb8909c..ac961aa82cce 100644 --- a/test/js/bun/jsc/bun-jsc.test.ts +++ b/test/js/bun/jsc/bun-jsc.test.ts @@ -24,7 +24,8 @@ import { totalCompileTime, } from "bun:jsc"; import { describe, expect, it } from "bun:test"; -import { bunEnv, bunExe, isBuildKite, isWindows } from "harness"; +import { bunEnv, bunExe, isASAN, isBuildKite, isDebug, isWindows } from "harness"; +import path from "node:path"; describe("bun:jsc", () => { function count() { @@ -556,3 +557,28 @@ it("deserialize applies the same nesting depth limit to arrays as to objects", a const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); expect({ stdout, exitCode }).toEqual({ stdout: "rejected\n65\n", exitCode: 0 }); }); + +// oven-sh/WebKit#308: DFG Plan::m_mustHandleValues is weak, so objects live in +// the OSR-triggering frame are not JITWorkList-rooted for the life of a queued +// concurrent compile. +it( + "gc() does not root user objects from a concurrent DFG plan's OSR-entry snapshot", + async () => { + // http client/server drives enough functions to DFG at once that plans are + // still queued at gc(). One compiler thread so they queue instead of drain. + await using proc = Bun.spawn({ + cmd: [bunExe(), path.join(import.meta.dir, "dfg-plan-gc-fixture.js")], + env: { ...bunEnv, BUN_JSC_numberOfDFGCompilerThreads: "1", BUN_JSC_numberOfFTLCompilerThreads: "1" }, + stdout: "pipe", + stderr: "pipe", + }); + const [stdout, , exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + // The "alive" count is timing dependent (how many plans were queued at the + // first gc()), but no ClientRequest/IncomingMessage may be a JITWorkList root. + expect({ stdout: stdout.trim(), exitCode }).toEqual({ + stdout: expect.stringMatching(/^jitworklist-rooted=0 alive=\d+$/), + exitCode: 0, + }); + }, + isDebug || isASAN ? 30_000 : undefined, +); diff --git a/test/js/bun/jsc/dfg-plan-gc-fixture.js b/test/js/bun/jsc/dfg-plan-gc-fixture.js new file mode 100644 index 000000000000..15fcb55f0d34 --- /dev/null +++ b/test/js/bun/jsc/dfg-plan-gc-fixture.js @@ -0,0 +1,46 @@ +// oven-sh/WebKit#308: after one gc(), no ClientRequest/IncomingMessage may be a +// RootMarkReason::JITWorkList root while DFG plans are queued. +"use strict"; +const http = require("http"); +const jsc = require("bun:jsc"); +const N = 32; +let done = 0; +const refs = []; +const server = http + .createServer((req, res) => { + res.writeHead(200); + res.end("ok"); + }) + .listen(0, "127.0.0.1", () => { + for (let i = 0; i < N; i++) { + const req = http.get({ hostname: "127.0.0.1", port: server.address().port }, res => { + res.resume(); + res.on("end", () => done++); + }); + refs.push(new WeakRef(req)); + } + }); +setImmediate(function check() { + if (done < N) return setImmediate(check); + Bun.gc(true); + const alive = refs.filter(r => r.deref()).length; + // If nothing survived there cannot be a JITWorkList-rooted instance either; + // skip the (expensive under ASAN) heap snapshot. + let rooted = 0; + if (alive > 0) { + // Count ClientRequest / IncomingMessage instances that the debugging heap + // snapshot attributes directly to the JIT worklist. + const snap = jsc.generateHeapSnapshotForDebugging(); + const NF = 7; + const RF = 3; + const { nodes, nodeClassNames, roots, labels } = snap; + const classOf = new Map(); + for (let i = 0; i < nodes.length; i += NF) classOf.set(nodes[i], nodeClassNames[nodes[i + 2]]); + for (let i = 0; i < roots.length; i += RF) { + const cn = classOf.get(roots[i]); + if ((cn === "ClientRequest" || cn === "IncomingMessage") && labels[roots[i + 1]] === "JITWorkList") rooted++; + } + } + console.log("jitworklist-rooted=" + rooted + " alive=" + alive); + server.close(); +});