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

Attachment: node-v8-test.tgz
Description: application/compressed-tar

#!/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" <<JSON
{
  "name": "corepack-berry-install-repro",
  "private": true,
  "packageManager": "yarn@$YARN_VERSION",
  "dependencies": {
    "chai-as-promised": "7.1.2",
    "chalk": "2.4.2",
    "call-bind-apply-helpers": "1.0.2",
    "call-bound": "1.0.4",
    "camelcase": "6.3.0",
    "ccount": "2.0.1",
    "callsites": "3.1.0"
  }
}
JSON
        cat > "$ROOT/work/.yarnrc.yml" <<YAML
cacheFolder: $ROOT/work/.yarn/cache
globalFolder: $ROOT/work/.yarn/global
enableGlobalCache: false
enableMirror: false
compressionLevel: 0
nodeLinker: node-modules
taskPoolMode: workers
taskPoolConcurrency: 1
YAML
}

is_target_stack() {
        rg -q 'RangeError: .*Maximum call stack size exceeded|Maximum call 
stack size exceeded' "$1"
}

print_target() {
        iter=$1
        log=$2
        echo "FAIL target stack iteration $iter log=$log"
        echo "summary:"
        for pattern in \
            'Maximum call stack size exceeded' \
            'memory access out of bounds' \
            'wasm-function\[104\]' \
            'wasm-function\[56\]' \
            'wasm-function\[9\]' \
            'makeInterface' \
            'new ZipFS' \
            'convertToZipWorker'
        do
                printf '  %s=' "$pattern"
                rg -c "$pattern" "$log" || true
        done
        echo "first 120 lines:"
        sed -n '1,120p' "$log"
        echo "last 120 lines:"
        tail -120 "$log"
}

write_project
(cd "$ROOT/work" && "$COREPACK_BIN" "yarn@$YARN_VERSION" --version >/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"

Reply via email to