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