Download raw body.
lang/node, wip v8-wasm support for yarn berry
On 7/4/26 13:18, Volker Schlecht wrote:
> Attached is your diff against 24.18.0 in -current. I don't see any wasm-related
> tests failing here, but the failure cases that you mentioned seem to be the same
> as or similar to random failures we're seeing while building the chromiums when
> running the wasm version of rollup.
To reproduce, you need amd64(V8_ENABLE_CET_IBT) and a specific memory layout.
When V8 patches a Wasm jump slot and has to fall back to the far jump table,
it generates twice endbr ; nop, it emit insn before checking relative offset.
V8 WASM uses jump tables; thus, it overwrites an other function call slot.
deps/v8/src/wasm/jump-table-assembler.h
// The jump table is the central dispatch point for all (direct and indirect)
// invocations in WebAssembly. It holds one slot per function in a module, with
// each slot containing a dispatch to the currently published {WasmCode} that
// corresponds to the function.
Fatal error in ../deps/v8/src/wasm/jump-table-assembler.h, line 163
Debug check failed: kJumpTableSlotSize == jtasm.pc_offset() (16 vs. 24).
Trace/BPT trap (core dumped)
static void PatchJumpTableSlot(WritableJumpTablePair& jump_table_pair,
Address jump_table_slot,
Address far_jump_table_slot, Address target) {
// First, try to patch the jump table slot.
JumpTableAssembler jtasm(jump_table_pair.jump_table(), jump_table_slot);
if (!jtasm.EmitJumpSlot(target)) {
// If that fails, we need to patch the far jump table slot, and then
// update the jump table slot to jump to this far jump table slot.
DCHECK_NE(kNullAddress, far_jump_table_slot);
JumpTableAssembler::PatchFarJumpSlot(jump_table_pair.far_jump_table(),
far_jump_table_slot, target);
CHECK(jtasm.EmitJumpSlot(far_jump_table_slot));
}
// We write nops here instead of skipping to avoid partial instructions in
// the jump table. Partial instructions can cause problems for the
// disassembler.
DCHECK_EQ(kJumpTableSlotSize, jtasm.pc_offset());
FlushInstructionCache(jump_table_slot, kJumpTableSlotSize);
}
bool JumpTableAssembler::EmitJumpSlot(Address target) {
#ifdef V8_ENABLE_CET_IBT
uint32_t endbr_insn = 0xfa1e0ff3;
uint32_t nop = 0x00401f0f;
emit<uint32_t>(endbr_insn, kRelaxedStore);
// Add a nop to ensure that the next block is 8 byte aligned.
emit<uint32_t>(nop, kRelaxedStore);
#endif
intptr_t displacement =
target - (pc_ + MacroAssembler::kIntraSegmentJmpInstrSize);
if (!is_int32(displacement)) return false;
...
}
EmitJumpSlot() shouldn't emit before return false.
You can compare with V8_TARGET_ARCH_ARM64 / V8_ENABLE_CONTROL_FLOW_INTEGRITY.
This got catched with dcheck enabled.
CONFIGURE_ARGS += --v8-with-dchecks
That's why I include a patch for
deps/v8/src/snapshot/embedded/platform-embedded-file-writer-generic.h
I'm still running with dcheck enabled, so I didn't actually test the final diff.
More tests welcome.
I will run both node and node-v8 tests with dchecks.
Maybe a separate commit related to dchecks only.
>
> I'll test drive that for a bit, but what these patches do, or how they might fix
> things is beyond my capabilities, so I hope that someone else will look over
> them as well, particularly if this might improve things for other ports
> including v8 (chromiums, deno, codex...)
There is also a regexp issue to fix.
>
> On 7/2/26 10:36 AM, Fabien Romano wrote:
>>
>> On 7/1/26 23:13, Volker Schlecht wrote:
>>> On 7/1/26 11:26 PM, Fabien Romano wrote:
>>>> I have a few wasm issues in node v8 for which I have patches.
>>>
>>> Please share, I'll be happy to include them in the port.
>>>
>>>
>>
>> Actually, I was gathering info in a draft email.
>> I still have to update system & ports on my side to confirm the issue.
>>
>> That's a first step for wasm. Hope the diff applies.
>> I manually remove a hack in Makefile.
>>
>> # node+v8/js-wasm randomly fool itself resulting in
>> # Range Error: Maximum call stack size exceeded
>> # XXX enabling pointer compression without enabling sandbox is unsupported by V8
>> # XXX further reduce v8 flags to narrow the code path change to audit (?)
>> CONFIGURE_ARGS+= --experimental-enable-pointer-compression \
>> --experimental-pointer-compression-shared-cage
>>
>> Using those two configure args, berry is stable (tested on 24.17).
>> By default, the v8 test build with pointer-compression and sandbox enabled...
>> so I blindly try to do the same in node except for sandbox (see configure.py).
>> I hit just one "Maximum call stack size exceeded" while building electron, once.
>> Since then, parcel, swc, electron all extract/install into pobj without issue.
>>
>> I could make a short reproducer... there is an AI one (repro.sh).
>> Open and configure it to your system.
>>
>> With an online install, with --stack-trace-limit=10000, I get:
>> RangeError: ansi-styles@npm:3.2.1: Maximum call stack size exceeded
>> at wasm-function[104]:0xbc29
>> at wasm-function[104]:0xbc9d
>> ... 7506 frames of wasm-function[104] ...
>> at wasm-function[188]:0x11867
>> at new PI (.../yarn.js:148:193125)
>> at new hs (.../yarn.js:148:199501)
>> at Object.Dit (.../yarn.js:191:212680)
>> at async e.fetchFromNetwork (.../yarn.js:693:3984)
>> at async e.fetchPackageFromCache (.../yarn.js:198:3374)
>> at async e.fetch (.../yarn.js:693:3337)
>>
>> I'm trying to understand how it derails in the libzip wasm embeded in berry.
>> AI tried to stress the same libzip function. It only reproduce inside berry.
>> The pointer-compression hack may be a hint for an expert who know v8 internals.
>>
>> It should be reproducible offline also (I'm trying an other repro atm).
>> If you create a package that uses a local .tgz dependency, install it with berry
>> (tested on corepack yarn 4.17.0), *without a cache*, it could fail.
>> The code path that triggers looks very specific to what berry is doing.
>> I didn't manage to get similar traces using node or v8 tests.
>>
>> I had another issue with regexp.
>> I work around it with --regexp-interpret-all in electron.
>> You can stress test node parallel/test-worker-heap-snapshot.js to reproduce.
>>
>> Program terminated with signal SIGTRAP, Trace/breakpoint trap.
>> #0 v8::internal::RegExpMacroAssemblerX64::CheckStackGuardState(...)
>>
>> I also add the below change while working on node v8 tests.
>> I just don't remember if this actually fixed a test.
>> OpenBSD has the same 512kB thread stack size by default.
>>
>> In deps/v8/src/base/platform/platform-posix.cc
>>
>> @@ -1258,7 +1266,7 @@ bool Thread::Start() {
>> if (result != 0) return false;
>> size_t stack_size = stack_size_;
>> if (stack_size == 0) {
>> -#if V8_OS_DARWIN
>> +#if V8_OS_DARWIN || V8_OS_OPENBSD
>> // Default on Mac OS X is 512kB -- bump up to 1MB
>> stack_size = 1 * 1024 * 1024;
>> #elif V8_OS_AIX
>>
>> The below patches may also apply to other V8 copies (chromium ...)
>> If I remember correctly, new V8 uses guard pages in StackSegment().
>> Maybe someone can give a look.
>> Otherwise, I may write a v8 port to run tests for this purpose.
>>
>> For node we have to use the embedded version for tests.
>> I joined a dirty node-v8-test port I made just to run tests (without gclient).
>> I don't think we should run node v8 test by default considering the complexity.
>>
>> In node-v8-test, you may want to adjust deps/v8/.gn, examples :
>>
>> # Disable pointer compression and sandbox like Node build
>> # while debugging OpenBSD Wasm stack failures.
>> v8_enable_pointer_compression = false
>> v8_enable_pointer_compression_shared_cage = false
>> v8_enable_31bit_smis_on_64bit_arch = false
>> v8_enable_external_code_space = false
>> v8_enable_sandbox = false
>>
>> # # debug
>> # enable_profiling = true
>> # symbol_level = 2
>> # v8_code_comments = true
>> # v8_enable_debugging_features = true
>> # v8_enable_disassembler = true
>> # v8_enable_object_print = true
>> # v8_enable_slow_dchecks = true
>> # v8_symbol_level = 2
>>
--
Index: Makefile
===================================================================
RCS file: /mnt/ext/cvs/ports/lang/node/Makefile,v
diff -u -p -r1.169 Makefile
--- Makefile 30 Jun 2026 18:46:22 -0000 1.169
+++ Makefile 5 Jul 2026 12:20:53 -0000
@@ -13,6 +13,7 @@ DIST_TUPLE = github qbit node-pledge 1.
DISTNAME = node-${NODE_VERSION}
PKGNAME = ${DISTNAME:S/v//g}
EPOCH = 0
+REVISION = 0
CATEGORIES = lang devel
Index: patches/patch-deps_v8_src_base_platform-posix_cc
===================================================================
RCS file: /mnt/ext/cvs/ports/lang/node/patches/patch-deps_v8_src_base_platform-posix_cc,v
diff -u -p -r1.8 patch-deps_v8_src_base_platform-posix_cc
--- patches/patch-deps_v8_src_base_platform-posix_cc 9 May 2026 13:26:47 -0000 1.8
+++ patches/patch-deps_v8_src_base_platform-posix_cc 5 Jul 2026 13:24:14 -0000
@@ -43,6 +43,15 @@ Index: deps/v8/src/base/platform/platfor
return true;
#else
// TODO(bbudge) Return true for all POSIX platforms.
+@@ -1258,7 +1266,7 @@ bool Thread::Start() {
+ if (result != 0) return false;
+ size_t stack_size = stack_size_;
+ if (stack_size == 0) {
+-#if V8_OS_DARWIN
++#if V8_OS_DARWIN || V8_OS_OPENBSD
+ // Default on Mac OS X is 512kB -- bump up to 1MB
+ stack_size = 1 * 1024 * 1024;
+ #elif V8_OS_AIX
@@ -1358,7 +1366,7 @@ void Thread::SetThreadLocal(LocalStorageKey key, void*
// keep this version in POSIX as most Linux-compatible derivatives will
// support it. MacOS and FreeBSD are different here.
Index: patches/patch-deps_v8_src_snapshot_embedded_platform-embedded-file-writer-generic_h
===================================================================
RCS file: patches/patch-deps_v8_src_snapshot_embedded_platform-embedded-file-writer-generic_h
diff -N patches/patch-deps_v8_src_snapshot_embedded_platform-embedded-file-writer-generic_h
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ patches/patch-deps_v8_src_snapshot_embedded_platform-embedded-file-writer-generic_h 5 Jul 2026 09:33:14 -0000
@@ -0,0 +1,13 @@
+Index: deps/v8/src/snapshot/embedded/platform-embedded-file-writer-generic.h
+--- deps/v8/src/snapshot/embedded/platform-embedded-file-writer-generic.h.orig
++++ deps/v8/src/snapshot/embedded/platform-embedded-file-writer-generic.h
+@@ -19,7 +19,8 @@ class PlatformEmbeddedFileWriterGeneric
+ : target_arch_(target_arch), target_os_(target_os) {
+ DCHECK(target_os_ == EmbeddedTargetOs::kChromeOS ||
+ target_os_ == EmbeddedTargetOs::kFuchsia ||
+- target_os_ == EmbeddedTargetOs::kGeneric);
++ target_os_ == EmbeddedTargetOs::kGeneric ||
++ target_os_ == EmbeddedTargetOs::kOpenBSD);
+ }
+
+ void SectionText() override;
Index: patches/patch-deps_v8_src_wasm_jump-table-assembler_cc
===================================================================
RCS file: patches/patch-deps_v8_src_wasm_jump-table-assembler_cc
diff -N patches/patch-deps_v8_src_wasm_jump-table-assembler_cc
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ patches/patch-deps_v8_src_wasm_jump-table-assembler_cc 5 Jul 2026 16:35:06 -0000
@@ -0,0 +1,54 @@
+Fatal error in ../deps/v8/src/wasm/jump-table-assembler.h, line 163
+Debug check failed: kJumpTableSlotSize == jtasm.pc_offset() (16 vs. 24).
+Trace/BPT trap (core dumped)
+
+This needs x64/amd64 with V8_ENABLE_CET_IBT and a code-space layout where the
+compiled Wasm target is outside the rel32 range of its primary jump-table slot.
+With CET, an x64 Wasm jump-table slot is 16 bytes:
+endbr64; nop; jmp rel32; int3 padding.
+
+The buggy path tries a direct jump first. EmitJumpSlot() writes endbr64; nop
+before checking rel32 reachability. If the target is too far, it returns false
+after advancing pc_ by 8 bytes. The far-jump fallback then emits another
+endbr64; nop and the final jmp starts at slot+16, overwriting the next slot.
+Keep EmitJumpSlot() side-effect-free on failure and compute the rel32
+displacement from the real jmp location, i.e. after the CET marker.
+
+Index: deps/v8/src/wasm/jump-table-assembler.cc
+--- deps/v8/src/wasm/jump-table-assembler.cc.orig
++++ deps/v8/src/wasm/jump-table-assembler.cc
+@@ -115,15 +115,14 @@ void JumpTableAssembler::EmitLazyCompileJumpSlot(uint3
+
+ bool JumpTableAssembler::EmitJumpSlot(Address target) {
+ #ifdef V8_ENABLE_CET_IBT
+- uint32_t endbr_insn = 0xfa1e0ff3;
+- uint32_t nop = 0x00401f0f;
+- emit<uint32_t>(endbr_insn, kRelaxedStore);
+- // Add a nop to ensure that the next block is 8 byte aligned.
+- emit<uint32_t>(nop, kRelaxedStore);
++ static constexpr int kCodeEntryMarkerSize = 2 * kInt32Size;
++#else
++ static constexpr int kCodeEntryMarkerSize = 0;
+ #endif
+
+ intptr_t displacement =
+- target - (pc_ + MacroAssembler::kIntraSegmentJmpInstrSize);
++ target - (pc_ + kCodeEntryMarkerSize +
++ MacroAssembler::kIntraSegmentJmpInstrSize);
+ if (!is_int32(displacement)) return false;
+
+ uint8_t inst[kJumpTableSlotSize] = {
+@@ -134,6 +133,13 @@ bool JumpTableAssembler::EmitJumpSlot(Address target)
+ memcpy(&inst[1], &displacement32, sizeof(int32_t));
+
+ // The jump table is updated live, so the write has to be atomic.
++#ifdef V8_ENABLE_CET_IBT
++ uint32_t endbr_insn = 0xfa1e0ff3;
++ uint32_t nop = 0x00401f0f;
++ emit<uint32_t>(endbr_insn, kRelaxedStore);
++ // Add a nop to ensure that the next block is 8 byte aligned.
++ emit<uint32_t>(nop, kRelaxedStore);
++#endif
+ emit<uint64_t>(*reinterpret_cast<uint64_t*>(inst), kRelaxedStore);
+
+ return true;
Index: patches/patch-deps_v8_src_wasm_stacks_cc
===================================================================
RCS file: patches/patch-deps_v8_src_wasm_stacks_cc
diff -N patches/patch-deps_v8_src_wasm_stacks_cc
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ patches/patch-deps_v8_src_wasm_stacks_cc 5 Jul 2026 16:18:52 -0000
@@ -0,0 +1,50 @@
+Index: deps/v8/src/wasm/stacks.cc
+--- deps/v8/src/wasm/stacks.cc.orig
++++ deps/v8/src/wasm/stacks.cc
+@@ -4,6 +4,10 @@
+
+ #include "src/wasm/stacks.h"
+
++#if V8_OS_OPENBSD
++#include <sys/mman.h>
++#endif
++
+ #include "src/base/platform/platform.h"
+ #include "src/execution/simulator.h"
+ #include "src/wasm/wasm-engine.h"
+@@ -66,9 +70,18 @@ StackMemory::StackSegment::StackSegment(size_t pages)
+ DCHECK_GE(pages, 1);
+ PageAllocator* allocator = GetPlatformPageAllocator();
+ size_ = pages * allocator->AllocatePageSize();
++#if V8_OS_OPENBSD
++ limit_ = static_cast<uint8_t*>(mmap(nullptr, size_, PROT_READ | PROT_WRITE,
++ MAP_PRIVATE | MAP_ANON | MAP_STACK, -1,
++ 0));
++ if (limit_ == MAP_FAILED) {
++ limit_ = nullptr;
++ }
++#else
+ limit_ = static_cast<uint8_t*>(
+ allocator->AllocatePages(nullptr, size_, allocator->AllocatePageSize(),
+ PageAllocator::kReadWrite));
++#endif
+ if (limit_ == nullptr) {
+ V8::FatalProcessOutOfMemory(nullptr,
+ "StackMemory::StackSegment::StackSegment");
+@@ -76,10 +89,16 @@ StackMemory::StackSegment::StackSegment(size_t pages)
+ }
+
+ StackMemory::StackSegment::~StackSegment() {
++#if V8_OS_OPENBSD
++ if (munmap(limit_, size_) != 0) {
++ V8::FatalProcessOutOfMemory(nullptr, "Release stack memory");
++ }
++#else
+ PageAllocator* allocator = GetPlatformPageAllocator();
+ if (!allocator->DecommitPages(limit_, size_)) {
+ V8::FatalProcessOutOfMemory(nullptr, "Decommit stack memory");
+ }
++#endif
+ }
+
+ bool StackMemory::Grow(Address current_fp) {
Index: patches/patch-deps_v8_src_wasm_wasm-objects_cc
===================================================================
RCS file: patches/patch-deps_v8_src_wasm_wasm-objects_cc
diff -N patches/patch-deps_v8_src_wasm_wasm-objects_cc
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ patches/patch-deps_v8_src_wasm_wasm-objects_cc 5 Jul 2026 16:19:05 -0000
@@ -0,0 +1,19 @@
+Index: deps/v8/src/wasm/wasm-objects.cc
+--- deps/v8/src/wasm/wasm-objects.cc.orig
++++ deps/v8/src/wasm/wasm-objects.cc
+@@ -2855,7 +2855,15 @@ DirectHandle<WasmContinuationObject> WasmContinuationO
+ wasm::JumpBuffer::StackState state, DirectHandle<HeapObject> parent,
+ AllocationType allocation_type) {
+ stack->jmpbuf()->stack_limit = stack->jslimit();
++#if V8_OS_OPENBSD
++ // OpenBSD MAP_STACK mappings cannot be entered with rsp exactly equal to the
++ // high end of the mapping. The first push from that value traps with
++ // SEGV_ACCERR, even though push would write below rsp. Keep the initial stack
++ // pointer strictly inside the mapped stack.
++ stack->jmpbuf()->sp = stack->base() - kSystemPointerSize;
++#else
+ stack->jmpbuf()->sp = stack->base();
++#endif
+ stack->jmpbuf()->fp = kNullAddress;
+ stack->jmpbuf()->state = state;
+ DirectHandle<WasmContinuationObject> result =
Index: Makefile
===================================================================
RCS file: /mnt/ext/cvs/ports/lang/node/Makefile,v
diff -u -p -r1.169 Makefile
--- Makefile 30 Jun 2026 18:46:22 -0000 1.169
+++ Makefile 5 Jul 2026 12:20:53 -0000
@@ -13,6 +13,7 @@ DIST_TUPLE = github qbit node-pledge 1.
DISTNAME = node-${NODE_VERSION}
PKGNAME = ${DISTNAME:S/v//g}
EPOCH = 0
+REVISION = 0
CATEGORIES = lang devel
Index: patches/patch-deps_v8_src_base_platform-posix_cc
===================================================================
RCS file: /mnt/ext/cvs/ports/lang/node/patches/patch-deps_v8_src_base_platform-posix_cc,v
diff -u -p -r1.8 patch-deps_v8_src_base_platform-posix_cc
--- patches/patch-deps_v8_src_base_platform-posix_cc 9 May 2026 13:26:47 -0000 1.8
+++ patches/patch-deps_v8_src_base_platform-posix_cc 5 Jul 2026 13:24:14 -0000
@@ -43,6 +43,15 @@ Index: deps/v8/src/base/platform/platfor
return true;
#else
// TODO(bbudge) Return true for all POSIX platforms.
+@@ -1258,7 +1266,7 @@ bool Thread::Start() {
+ if (result != 0) return false;
+ size_t stack_size = stack_size_;
+ if (stack_size == 0) {
+-#if V8_OS_DARWIN
++#if V8_OS_DARWIN || V8_OS_OPENBSD
+ // Default on Mac OS X is 512kB -- bump up to 1MB
+ stack_size = 1 * 1024 * 1024;
+ #elif V8_OS_AIX
@@ -1358,7 +1366,7 @@ void Thread::SetThreadLocal(LocalStorageKey key, void*
// keep this version in POSIX as most Linux-compatible derivatives will
// support it. MacOS and FreeBSD are different here.
Index: patches/patch-deps_v8_src_snapshot_embedded_platform-embedded-file-writer-generic_h
===================================================================
RCS file: patches/patch-deps_v8_src_snapshot_embedded_platform-embedded-file-writer-generic_h
diff -N patches/patch-deps_v8_src_snapshot_embedded_platform-embedded-file-writer-generic_h
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ patches/patch-deps_v8_src_snapshot_embedded_platform-embedded-file-writer-generic_h 5 Jul 2026 09:33:14 -0000
@@ -0,0 +1,13 @@
+Index: deps/v8/src/snapshot/embedded/platform-embedded-file-writer-generic.h
+--- deps/v8/src/snapshot/embedded/platform-embedded-file-writer-generic.h.orig
++++ deps/v8/src/snapshot/embedded/platform-embedded-file-writer-generic.h
+@@ -19,7 +19,8 @@ class PlatformEmbeddedFileWriterGeneric
+ : target_arch_(target_arch), target_os_(target_os) {
+ DCHECK(target_os_ == EmbeddedTargetOs::kChromeOS ||
+ target_os_ == EmbeddedTargetOs::kFuchsia ||
+- target_os_ == EmbeddedTargetOs::kGeneric);
++ target_os_ == EmbeddedTargetOs::kGeneric ||
++ target_os_ == EmbeddedTargetOs::kOpenBSD);
+ }
+
+ void SectionText() override;
Index: patches/patch-deps_v8_src_wasm_jump-table-assembler_cc
===================================================================
RCS file: patches/patch-deps_v8_src_wasm_jump-table-assembler_cc
diff -N patches/patch-deps_v8_src_wasm_jump-table-assembler_cc
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ patches/patch-deps_v8_src_wasm_jump-table-assembler_cc 5 Jul 2026 16:35:06 -0000
@@ -0,0 +1,54 @@
+Fatal error in ../deps/v8/src/wasm/jump-table-assembler.h, line 163
+Debug check failed: kJumpTableSlotSize == jtasm.pc_offset() (16 vs. 24).
+Trace/BPT trap (core dumped)
+
+This needs x64/amd64 with V8_ENABLE_CET_IBT and a code-space layout where the
+compiled Wasm target is outside the rel32 range of its primary jump-table slot.
+With CET, an x64 Wasm jump-table slot is 16 bytes:
+endbr64; nop; jmp rel32; int3 padding.
+
+The buggy path tries a direct jump first. EmitJumpSlot() writes endbr64; nop
+before checking rel32 reachability. If the target is too far, it returns false
+after advancing pc_ by 8 bytes. The far-jump fallback then emits another
+endbr64; nop and the final jmp starts at slot+16, overwriting the next slot.
+Keep EmitJumpSlot() side-effect-free on failure and compute the rel32
+displacement from the real jmp location, i.e. after the CET marker.
+
+Index: deps/v8/src/wasm/jump-table-assembler.cc
+--- deps/v8/src/wasm/jump-table-assembler.cc.orig
++++ deps/v8/src/wasm/jump-table-assembler.cc
+@@ -115,15 +115,14 @@ void JumpTableAssembler::EmitLazyCompileJumpSlot(uint3
+
+ bool JumpTableAssembler::EmitJumpSlot(Address target) {
+ #ifdef V8_ENABLE_CET_IBT
+- uint32_t endbr_insn = 0xfa1e0ff3;
+- uint32_t nop = 0x00401f0f;
+- emit<uint32_t>(endbr_insn, kRelaxedStore);
+- // Add a nop to ensure that the next block is 8 byte aligned.
+- emit<uint32_t>(nop, kRelaxedStore);
++ static constexpr int kCodeEntryMarkerSize = 2 * kInt32Size;
++#else
++ static constexpr int kCodeEntryMarkerSize = 0;
+ #endif
+
+ intptr_t displacement =
+- target - (pc_ + MacroAssembler::kIntraSegmentJmpInstrSize);
++ target - (pc_ + kCodeEntryMarkerSize +
++ MacroAssembler::kIntraSegmentJmpInstrSize);
+ if (!is_int32(displacement)) return false;
+
+ uint8_t inst[kJumpTableSlotSize] = {
+@@ -134,6 +133,13 @@ bool JumpTableAssembler::EmitJumpSlot(Address target)
+ memcpy(&inst[1], &displacement32, sizeof(int32_t));
+
+ // The jump table is updated live, so the write has to be atomic.
++#ifdef V8_ENABLE_CET_IBT
++ uint32_t endbr_insn = 0xfa1e0ff3;
++ uint32_t nop = 0x00401f0f;
++ emit<uint32_t>(endbr_insn, kRelaxedStore);
++ // Add a nop to ensure that the next block is 8 byte aligned.
++ emit<uint32_t>(nop, kRelaxedStore);
++#endif
+ emit<uint64_t>(*reinterpret_cast<uint64_t*>(inst), kRelaxedStore);
+
+ return true;
Index: patches/patch-deps_v8_src_wasm_stacks_cc
===================================================================
RCS file: patches/patch-deps_v8_src_wasm_stacks_cc
diff -N patches/patch-deps_v8_src_wasm_stacks_cc
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ patches/patch-deps_v8_src_wasm_stacks_cc 5 Jul 2026 16:18:52 -0000
@@ -0,0 +1,50 @@
+Index: deps/v8/src/wasm/stacks.cc
+--- deps/v8/src/wasm/stacks.cc.orig
++++ deps/v8/src/wasm/stacks.cc
+@@ -4,6 +4,10 @@
+
+ #include "src/wasm/stacks.h"
+
++#if V8_OS_OPENBSD
++#include <sys/mman.h>
++#endif
++
+ #include "src/base/platform/platform.h"
+ #include "src/execution/simulator.h"
+ #include "src/wasm/wasm-engine.h"
+@@ -66,9 +70,18 @@ StackMemory::StackSegment::StackSegment(size_t pages)
+ DCHECK_GE(pages, 1);
+ PageAllocator* allocator = GetPlatformPageAllocator();
+ size_ = pages * allocator->AllocatePageSize();
++#if V8_OS_OPENBSD
++ limit_ = static_cast<uint8_t*>(mmap(nullptr, size_, PROT_READ | PROT_WRITE,
++ MAP_PRIVATE | MAP_ANON | MAP_STACK, -1,
++ 0));
++ if (limit_ == MAP_FAILED) {
++ limit_ = nullptr;
++ }
++#else
+ limit_ = static_cast<uint8_t*>(
+ allocator->AllocatePages(nullptr, size_, allocator->AllocatePageSize(),
+ PageAllocator::kReadWrite));
++#endif
+ if (limit_ == nullptr) {
+ V8::FatalProcessOutOfMemory(nullptr,
+ "StackMemory::StackSegment::StackSegment");
+@@ -76,10 +89,16 @@ StackMemory::StackSegment::StackSegment(size_t pages)
+ }
+
+ StackMemory::StackSegment::~StackSegment() {
++#if V8_OS_OPENBSD
++ if (munmap(limit_, size_) != 0) {
++ V8::FatalProcessOutOfMemory(nullptr, "Release stack memory");
++ }
++#else
+ PageAllocator* allocator = GetPlatformPageAllocator();
+ if (!allocator->DecommitPages(limit_, size_)) {
+ V8::FatalProcessOutOfMemory(nullptr, "Decommit stack memory");
+ }
++#endif
+ }
+
+ bool StackMemory::Grow(Address current_fp) {
Index: patches/patch-deps_v8_src_wasm_wasm-objects_cc
===================================================================
RCS file: patches/patch-deps_v8_src_wasm_wasm-objects_cc
diff -N patches/patch-deps_v8_src_wasm_wasm-objects_cc
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ patches/patch-deps_v8_src_wasm_wasm-objects_cc 5 Jul 2026 16:19:05 -0000
@@ -0,0 +1,19 @@
+Index: deps/v8/src/wasm/wasm-objects.cc
+--- deps/v8/src/wasm/wasm-objects.cc.orig
++++ deps/v8/src/wasm/wasm-objects.cc
+@@ -2855,7 +2855,15 @@ DirectHandle<WasmContinuationObject> WasmContinuationO
+ wasm::JumpBuffer::StackState state, DirectHandle<HeapObject> parent,
+ AllocationType allocation_type) {
+ stack->jmpbuf()->stack_limit = stack->jslimit();
++#if V8_OS_OPENBSD
++ // OpenBSD MAP_STACK mappings cannot be entered with rsp exactly equal to the
++ // high end of the mapping. The first push from that value traps with
++ // SEGV_ACCERR, even though push would write below rsp. Keep the initial stack
++ // pointer strictly inside the mapped stack.
++ stack->jmpbuf()->sp = stack->base() - kSystemPointerSize;
++#else
+ stack->jmpbuf()->sp = stack->base();
++#endif
+ stack->jmpbuf()->fp = kNullAddress;
+ stack->jmpbuf()->state = state;
+ DirectHandle<WasmContinuationObject> result =
lang/node, wip v8-wasm support for yarn berry