From: Fabien Romano Subject: lang/node, wip v8-wasm support for yarn berry To: Volker Schlecht , ports@openbsd.org Date: Thu, 2 Jul 2026 09:36:55 +0100 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 -- Fabien Romano 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 1 Jul 2026 22:27:54 -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_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 1 Jul 2026 19:25:50 -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 ++#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(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( + 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 1 Jul 2026 19:24:30 -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 WasmContinuationO + wasm::JumpBuffer::StackState state, DirectHandle 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 result = #!/bin/sh set -u PREFIX=${PREFIX:-/} NODE_BIN=${NODE_BIN:-$PREFIX/bin/node} COREPACK_BIN=${COREPACK_BIN:-$PREFIX/bin/corepack} ROOT=${ROOT:-/tmp/corepack-berry-install/run} COREPACK_HOME=${COREPACK_HOME:-/tmp/corepack-berry-install/corepack} ITERATIONS=${ITERATIONS:-1000} YARN_VERSION=${YARN_VERSION:-4.17.0} STACK_TRACE_LIMIT=${STACK_TRACE_LIMIT:-10000} rm -rf "$ROOT" mkdir -p "$ROOT/work" "$ROOT/logs" "$COREPACK_HOME" export PATH="$(dirname "$NODE_BIN"):$PATH" export HOME="$ROOT/home" export XDG_CACHE_HOME="$ROOT/xdg-cache" export XDG_CONFIG_HOME="$ROOT/xdg-config" export XDG_DATA_HOME="$ROOT/xdg-data" export COREPACK_HOME="$COREPACK_HOME" export npm_config_cache="$ROOT/npm-cache" export YARN_ENABLE_GLOBAL_CACHE=0 export YARN_ENABLE_IMMUTABLE_INSTALLS=0 export YARN_ENABLE_TELEMETRY=0 export YARN_NPM_REGISTRY_SERVER="https://registry.yarnpkg.com" export NODE_OPTIONS="--stack-trace-limit=$STACK_TRACE_LIMIT" export CI=true write_project() { rm -rf "$ROOT/work" "$ROOT/home" "$ROOT/xdg-cache" "$ROOT/xdg-config" \ "$ROOT/xdg-data" "$ROOT/npm-cache" mkdir -p "$ROOT/work" "$ROOT/home" "$ROOT/xdg-cache" "$ROOT/xdg-config" \ "$ROOT/xdg-data" "$ROOT/npm-cache" cat > "$ROOT/work/package.json" < "$ROOT/work/.yarnrc.yml" </dev/null 2>&1) || true n=1 non_target_failures=0 while [ "$n" -le "$ITERATIONS" ]; do write_project log="$ROOT/logs/iter-$n.log" { printf '[%s] iter=%s node=%s corepack=%s yarn=%s node_options=%s\n' \ "$(date +%H:%M:%S)" "$n" "$NODE_BIN" "$COREPACK_BIN" "$YARN_VERSION" "$NODE_OPTIONS" "$NODE_BIN" -v "$COREPACK_BIN" --version (cd "$ROOT/work" && "$COREPACK_BIN" "yarn@$YARN_VERSION" --version) (cd "$ROOT/work" && "$COREPACK_BIN" "yarn@$YARN_VERSION" install --mode=skip-build) } > "$log" 2>&1 status=$? if is_target_stack "$log"; then print_target "$n" "$log" exit 1 fi if [ "$status" -ne 0 ]; then non_target_failures=$((non_target_failures + 1)) echo "non-target failure iteration $n status=$status log=$log" sed -n '1,120p' "$log" if [ "$non_target_failures" -ge 10 ]; then echo "too many non-target failures ($non_target_failures), stopping" exit "$status" fi fi if [ $((n % 50)) -eq 0 ]; then echo "completed $n / $ITERATIONS" fi n=$((n + 1)) done echo "OK $ITERATIONS iterations"