Index | Thread | Search

From:
Fabien Romano <fabienromano@gmail.com>
Subject:
Re: lang/node, wip v8-wasm support for yarn berry
To:
Volker Schlecht <openbsd-ports@schlecht.dev>, ports@openbsd.org
Date:
Mon, 6 Jul 2026 08:47:06 +0100

Download raw body.

Thread
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 =