From 19e32bd79302caa5ba3c870a7284bd93d2b97cb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 25 Mar 2026 13:33:42 +0100 Subject: [PATCH 1/2] move V8 patch management from denoland/v8 into rusty_v8 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously V8 patches were managed in a separate denoland/v8 repo with its own autoroll script and CI job. This required coordinating two repos and two cron jobs just to apply patches on top of upstream V8. Now everything lives in rusty_v8: - patches/ directory contains the V8 patch files - tools/auto_update_v8.ts fetches upstream V8 directly, applies patches, pushes to the denoland/v8 fork, and updates rusty_v8 deps — all in one step - Single cron job instead of two To add a new patch: drop a .patch file in patches/ To bump V8 version: change V8_VERSION at the top of auto_update_v8.ts Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/update-v8.yml | 2 +- ...st-visibility-workaround-in-BUILD.gn.patch | 54 +++++++ ...le-Silicon-when-mprotect-fails-expec.patch | 43 ++++++ tools/auto_update_v8.ts | 133 +++++++++++++----- 4 files changed, 198 insertions(+), 34 deletions(-) create mode 100644 patches/0001-Remove-googletest-visibility-workaround-in-BUILD.gn.patch create mode 100644 patches/0002-Fix-crash-on-Apple-Silicon-when-mprotect-fails-expec.patch diff --git a/.github/workflows/update-v8.yml b/.github/workflows/update-v8.yml index c3927da812..4dac697976 100644 --- a/.github/workflows/update-v8.yml +++ b/.github/workflows/update-v8.yml @@ -2,7 +2,7 @@ name: Update V8 on: schedule: - - cron: "1 10 * * *" # this is 1 hour after the autoroll in denoland/v8 + - cron: "0 10 * * *" workflow_dispatch: permissions: write-all diff --git a/patches/0001-Remove-googletest-visibility-workaround-in-BUILD.gn.patch b/patches/0001-Remove-googletest-visibility-workaround-in-BUILD.gn.patch new file mode 100644 index 0000000000..2eab675050 --- /dev/null +++ b/patches/0001-Remove-googletest-visibility-workaround-in-BUILD.gn.patch @@ -0,0 +1,54 @@ +From fe0790450d0c8668e859d125e952bd3687267adb Mon Sep 17 00:00:00 2001 +From: Divy Srivastava +Date: Tue, 5 Dec 2023 09:35:57 +0530 +Subject: [PATCH] Remove googletest visibility workaround in BUILD.gn + +--- + third_party/googletest/BUILD.gn | 18 ------------------ + 1 file changed, 18 deletions(-) + +diff --git a/third_party/googletest/BUILD.gn b/third_party/googletest/BUILD.gn +index 1cf84b3..0918a49 100644 +--- a/third_party/googletest/BUILD.gn ++++ b/third_party/googletest/BUILD.gn +@@ -2,8 +2,6 @@ + # Use of this source code is governed by a BSD-style license that can be + # found in the LICENSE file. + +-import("../../gni/v8.gni") +- + config("gtest_config") { + visibility = [ ":*" ] # gmock also shares this config. + +@@ -91,14 +89,6 @@ source_set("gtest") { + configs -= [ "//build/config/compiler:chromium_code" ] + configs += [ "//build/config/compiler:no_chromium_code" ] + +- # V8-only workaround for http://crbug.com/chromium/1191946. Ensures that +- # googletest is compiled with the same visibility such as the rest of V8, see +- # https://source.chromium.org/chromium/chromium/src/+/master:v8/gni/v8.gni +- if ((is_posix || is_fuchsia) && (v8_enable_backtrace || v8_monolithic)) { +- configs -= [ "//build/config/gcc:symbol_visibility_hidden" ] +- configs += [ "//build/config/gcc:symbol_visibility_default" ] +- } +- + deps = [] + + if (is_fuchsia) { +@@ -143,14 +133,6 @@ source_set("gmock") { + "src/googlemock/src/gmock.cc", + ] + +- # V8-only workaround for http://crbug.com/chromium/1191946. Ensures that +- # googletest is compiled with the same visibility such as the rest of V8, see +- # https://source.chromium.org/chromium/chromium/src/+/master:v8/gni/v8.gni +- if ((is_posix || is_fuchsia) && (v8_enable_backtrace || v8_monolithic)) { +- configs -= [ "//build/config/gcc:symbol_visibility_hidden" ] +- configs += [ "//build/config/gcc:symbol_visibility_default" ] +- } +- + public_configs = [ + ":gmock_config", + ":gtest_config", +-- +2.37.1 (Apple Git-137.1) diff --git a/patches/0002-Fix-crash-on-Apple-Silicon-when-mprotect-fails-expec.patch b/patches/0002-Fix-crash-on-Apple-Silicon-when-mprotect-fails-expec.patch new file mode 100644 index 0000000000..9267f842ac --- /dev/null +++ b/patches/0002-Fix-crash-on-Apple-Silicon-when-mprotect-fails-expec.patch @@ -0,0 +1,43 @@ +From 183144148764c4288d63639ff0a7f46e18203fa7 Mon Sep 17 00:00:00 2001 +From: Bert Belder +Date: Wed, 25 May 2022 20:40:04 +0200 +Subject: [PATCH 3/4] Fix crash on Apple Silicon when mprotect() fails + expectedly + +--- + src/base/platform/platform-posix.cc | 12 ++++++------ + 1 file changed, 6 insertions(+), 6 deletions(-) + +diff --git a/src/base/platform/platform-posix.cc b/src/base/platform/platform-posix.cc +index da42cda6c4..f29d754e77 100644 +--- a/src/base/platform/platform-posix.cc ++++ b/src/base/platform/platform-posix.cc +@@ -479,12 +479,6 @@ bool OS::SetPermissions(void* address, size_t size, MemoryPermission access) { + int prot = GetProtectionFromMemoryPermission(access); + int ret = mprotect(address, size, prot); + +- // Setting permissions can fail if the limit of VMAs is exceeded. +- // Any failure that's not OOM likely indicates a bug in the caller (e.g. +- // using an invalid mapping) so attempt to catch that here to facilitate +- // debugging of these failures. +- if (ret != 0) CHECK_EQ(ENOMEM, errno); +- + // MacOS 11.2 on Apple Silicon refuses to switch permissions from + // rwx to none. Just use madvise instead. + #if defined(V8_OS_DARWIN) +@@ -494,6 +488,12 @@ bool OS::SetPermissions(void* address, size_t size, MemoryPermission access) { + } + #endif + ++ // Setting permissions can fail if the limit of VMAs is exceeded. ++ // Any failure that's not OOM likely indicates a bug in the caller (e.g. ++ // using an invalid mapping) so attempt to catch that here to facilitate ++ // debugging of these failures. ++ if (ret != 0) CHECK_EQ(ENOMEM, errno); ++ + if (ret == 0 && access == OS::MemoryPermission::kNoAccess) { + // This is advisory; ignore errors and continue execution. + USE(DiscardSystemPages(address, size)); + +-- +2.37.3 diff --git a/tools/auto_update_v8.ts b/tools/auto_update_v8.ts index 3322333049..138bcc14bf 100644 --- a/tools/auto_update_v8.ts +++ b/tools/auto_update_v8.ts @@ -1,66 +1,133 @@ -const V8_TRACKING_BRANCH = "14.7-lkgr-denoland"; +// V8 version to track. Update this when bumping to a new major V8 version. +const V8_VERSION = "14.7"; + +const V8_UPSTREAM = "https://chromium.googlesource.com/v8/v8.git"; +const V8_FORK = "https://github.com/denoland/v8.git"; +const UPSTREAM_LKGR = `${V8_VERSION}-lkgr`; +const DENOLAND_LKGR = `${V8_VERSION}-lkgr-denoland`; const AUTOROLL_BRANCH = "autoroll"; -function extractVersion() { +async function run( + cmd: string, + args: string[], + cwd?: string, +): Promise { + console.log("$", cmd, ...args); + const proc = new Deno.Command(cmd, { args, cwd, stdout: "piped" }); + const output = await proc.output(); + if (!output.success) { + console.error(`Failed to run ${cmd} ${args.join(" ")}`); + Deno.exit(1); + } + return output.stdout; +} + +function extractVersion(path = "./v8/include/v8-version.h"): string { const MAJOR_PREFIX = "#define V8_MAJOR_VERSION "; const MINOR_PREFIX = "#define V8_MINOR_VERSION "; const BUILD_PREFIX = "#define V8_BUILD_NUMBER "; const PATCH_PREFIX = "#define V8_PATCH_LEVEL "; - const versionDotH = Deno.readTextFileSync("./v8/include/v8-version.h"); + const versionDotH = Deno.readTextFileSync(path); const lines = versionDotH.split("\n"); - const major = parseInt(lines.find((s) => s.startsWith(MAJOR_PREFIX))! - .substring(MAJOR_PREFIX.length)); - const minor = parseInt(lines.find((s) => s.startsWith(MINOR_PREFIX))! - .substring(MINOR_PREFIX.length)); - const build = parseInt(lines.find((s) => s.startsWith(BUILD_PREFIX))! - .substring(BUILD_PREFIX.length)); - const patch = parseInt(lines.find((s) => s.startsWith(PATCH_PREFIX))! - .substring(PATCH_PREFIX.length)); + const major = parseInt( + lines.find((s) => s.startsWith(MAJOR_PREFIX))!.substring( + MAJOR_PREFIX.length, + ), + ); + const minor = parseInt( + lines.find((s) => s.startsWith(MINOR_PREFIX))!.substring( + MINOR_PREFIX.length, + ), + ); + const build = parseInt( + lines.find((s) => s.startsWith(BUILD_PREFIX))!.substring( + BUILD_PREFIX.length, + ), + ); + const patch = parseInt( + lines.find((s) => s.startsWith(PATCH_PREFIX))!.substring( + PATCH_PREFIX.length, + ), + ); return `${major}.${minor}.${build}.${patch}`; } +// Start from origin/main await run("git", ["checkout", "origin/main"]); await run("git", ["submodule", "update", "--init", "--recursive", "v8"]); const currentVersion = extractVersion(); console.log(`Starting auto update. Currently on ${currentVersion}`); -async function run( - cmd: string, - args: string[], - cwd?: string, -): Promise { - console.log("$", cmd, ...args); - const proc = new Deno.Command(cmd, { args, cwd }); - const output = await proc.output(); - if (!output.success) { - console.error(`Failed to run ${cmd} ${args.join(" ")}`); - Deno.exit(1); - } - return output.stdout; +// -- Step 1: Update the denoland/v8 fork -- +// Ensure upstream remote exists in the v8 submodule +const remotes = new TextDecoder().decode( + await run("git", ["remote"], "./v8"), +); +if (!remotes.split("\n").includes("upstream")) { + await run("git", ["remote", "add", "upstream", V8_UPSTREAM], "./v8"); +} +if (!remotes.split("\n").includes("denoland")) { + await run("git", ["remote", "add", "denoland", V8_FORK], "./v8"); } -// Update v8 submodule -await run("git", ["fetch", `origin`, V8_TRACKING_BRANCH], "./v8"); -await run("git", ["checkout", `origin/${V8_TRACKING_BRANCH}`], "./v8"); +// Fetch upstream lkgr branch +await run("git", ["fetch", "upstream", UPSTREAM_LKGR], "./v8"); +// Create the denoland branch from upstream +await run( + "git", + ["checkout", "-B", DENOLAND_LKGR, `upstream/${UPSTREAM_LKGR}`], + "./v8", +); + +// Apply patches +const patches = [...Deno.readDirSync("./patches")] + .filter((e) => e.name.endsWith(".patch")) + .map((e) => e.name) + .sort(); + +for (const patch of patches) { + const patchPath = `${Deno.cwd()}/patches/${patch}`; + console.log(`Applying patch ${patch}`); + await run("git", ["am", "-3", patchPath], "./v8"); +} + +// Check if version changed const newVersion = extractVersion(); -if (currentVersion == newVersion) { +if (currentVersion === newVersion) { console.log(`No new version available. Staying on ${newVersion}`); Deno.exit(0); } console.log(`Updated to version ${newVersion}`); +// Push the patched branch to the denoland/v8 fork +console.log("Pushing patched V8 branch to denoland/v8 fork."); +await run( + "git", + ["push", "--force", "denoland", DENOLAND_LKGR], + "./v8", +); + +// Create and push a tag +const commit = new TextDecoder().decode( + await run("git", ["rev-parse", "HEAD"], "./v8"), +).trim(); +const tag = `${newVersion}-denoland-${commit.slice(0, 20)}`; +console.log(`Creating tag ${tag}`); +await run("git", ["tag", tag], "./v8"); +await run("git", ["push", "denoland", tag], "./v8"); + +// -- Step 2: Update rusty_v8 -- + // Update V8 dependencies const depsOutput = await run("python", ["tools/update_deps.py"]); -const depNames = new TextDecoder().decode(depsOutput).split("\n").filter((x) => - x.length > 0 -).at(-1)!.split( - ",", -); +const depNames = new TextDecoder().decode(depsOutput).split("\n").filter(( + x, +) => x.length > 0).at(-1)!.split(","); // Update version in readme let readme = Deno.readTextFileSync("README.md"); From fee98b1e8e367ea17eebc2af10df507a00c55eba Mon Sep 17 00:00:00 2001 From: Leo Kettmeir Date: Wed, 6 May 2026 16:22:34 +0200 Subject: [PATCH 2/2] add V8_TLS_USED_IN_LIBRARY patch --- ...S_USED_IN_LIBRARY-to-internal_config.patch | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 patches/0003-Apply-V8_TLS_USED_IN_LIBRARY-to-internal_config.patch diff --git a/patches/0003-Apply-V8_TLS_USED_IN_LIBRARY-to-internal_config.patch b/patches/0003-Apply-V8_TLS_USED_IN_LIBRARY-to-internal_config.patch new file mode 100644 index 0000000000..d97ec06ae9 --- /dev/null +++ b/patches/0003-Apply-V8_TLS_USED_IN_LIBRARY-to-internal_config.patch @@ -0,0 +1,51 @@ +From ee882db8c7ac05e1b022d0384fa5dd2ead7178b4 Mon Sep 17 00:00:00 2001 +From: Leo Kettmeir +Date: Thu, 23 Apr 2026 17:31:21 +0200 +Subject: [PATCH] Apply V8_TLS_USED_IN_LIBRARY define to V8 internal_config + +V8 already declares the `v8_monolithic_for_shared_library` GN arg, but +the only place it adds the `V8_TLS_USED_IN_LIBRARY` define is the +`:features` config -- which is consumed only by external embedders, not +by the `internal_config` that V8's own `.cc` files use. As a result, V8 +internal sources (where the `thread_local Isolate*` etc. live) keep +their `local-exec` TLS model and emit `R_X86_64_TPOFF32` relocations +that fail to link into a downstream cdylib/.so on Linux. + +Add the same define to `internal_config` under the same arg, and drop +the redundant `v8_monolithic &&` part of the existing `:features` +condition (the arg already implies its scope). +--- + BUILD.gn | 10 +++++++++- + 1 file changed, 9 insertions(+), 1 deletion(-) + +diff --git a/BUILD.gn b/BUILD.gn +index a5c7d847781..75e45b2429b 100644 +--- a/BUILD.gn ++++ b/BUILD.gn +@@ -897,6 +897,14 @@ config("internal_config") { + defines += [ "BUILDING_V8_SHARED" ] + } + ++ # When the resulting V8 archive will be linked into a shared library ++ # downstream (e.g. a cdylib), thread-locals defined in V8 internal sources ++ # must use a TLS model compatible with -fPIC (not "local-exec"). This ++ # define switches them via src/common/thread-local-storage.h. ++ if (v8_monolithic_for_shared_library) { ++ defines += [ "V8_TLS_USED_IN_LIBRARY" ] ++ } ++ + if (v8_current_cpu == "riscv64" || v8_current_cpu == "riscv32") { + if (!is_clang) { + libs = [ "atomic" ] +@@ -1229,7 +1237,7 @@ config("features") { + defines += [ "CPPGC_ALLOW_ALLOCATIONS_IN_PREFINALIZERS" ] + } + +- if (v8_monolithic && v8_monolithic_for_shared_library) { ++ if (v8_monolithic_for_shared_library) { + defines += [ "V8_TLS_USED_IN_LIBRARY" ] + } + +-- +2.53.0 +