git: bdf56c40600a - main - www/node2{2,4,6}: Use bundled merve to avoid loading two simdutfs

From: Dave Cottlehuber <dch_at_FreeBSD.org>
Date: Sun, 18 Oct 2026 00:19:42 UTC
The branch main has been updated by dch:

URL: https://cgit.FreeBSD.org/ports/commit/?id=bdf56c40600a34c93ce4badad821d77ad8025535

commit bdf56c40600a34c93ce4badad821d77ad8025535
Author:     Dave Cottlehuber <dch@FreeBSD.org>
AuthorDate: 2026-10-17 21:33:46 +0000
Commit:     Dave Cottlehuber <dch@FreeBSD.org>
CommitDate: 2026-10-18 00:19:17 +0000

    www/node2{2,4,6}: Use bundled merve to avoid loading two simdutfs
    
    node dies with SIGBUS (signal 10) the first time it lexes the exports
    of a CommonJS module, e.g. early in any `npm install`, for example
    www/tailwindcss4 fails to build, reporting "*** Signal 10".
    
    Running lldb inside poudriere build, because we get a core dump:
    
      frame #0  libsimdutf.so.36  simdutf::detail::find(...) +77: jmpq *%rax
      frame #1  libmerve.so.1     lexer::CJSLexer::addReexport(...)
      ...
      frame #6  node              node::cjs_lexer::Parse(...)
    
      rax = 0x4444444443434343  ("CCCCDDDD", string data)
    
    This appears to be caused by having two different simdutf versions
    loaded into one process:
    
    - node is configured with --shared-merve but not --shared-simdutf, so
      it statically includes its own simdutf (7.7.0 in 26.10.0, from
      deps/v8/third_party/simdutf), and exports those symbols for native
      addons (`nm -D node | grep -c simdutf` = 686).
    - devel/merve is built with MERVE_USE_SIMDUTF=ON and links the system
      libsimdutf.so.36 from converters/simdutf (9.2.1).
    
    Both define simdutf::get_active_implementation() and friends. rtld
    searches the executable first, so references made from inside
    libsimdutf.so.36 can bind to node's 7.7.0 copy. The 9.2.1 code then
    reads a 7.7.0 implementation object with its own layout, loads string
    data where it expects a function pointer, and jumps to it.
    
    I tried dropping shared-merve from the port and corresponding
    LIB_DEPENDS, and now things work again.
    
    Alternatively both --shared-merve and --shared-simdutf could be used,
    but this may result in behavioural changes for consumers. This should
    probably be done in the next minor nodejs version bump.
    
    Approved by:    portmgr (fix blanket)
    MFH:            2026Q4
    PR:             299170
    Reported by:    poudriere
    Sponsored by:   SkunkWerks, GmbH
---
 www/node22/Makefile |  7 ++++++-
 www/node24/Makefile | 14 ++++++++++----
 www/node26/Makefile | 14 ++++++++++----
 3 files changed, 26 insertions(+), 9 deletions(-)

diff --git a/www/node22/Makefile b/www/node22/Makefile
index b335f838c430..e5fa7af1a437 100644
--- a/www/node22/Makefile
+++ b/www/node22/Makefile
@@ -1,7 +1,7 @@
 PORTNAME=	node
 PORTVERSION=	${NODEJS_PORTVERSION}
 DISTVERSIONPREFIX=	v
-PORTREVISION=	1
+PORTREVISION=	2
 CATEGORIES=	www
 MASTER_SITES=	https://nodejs.org/dist/v${PORTVERSION}/
 PKGNAMESUFFIX=	${PORTVERSION:R:R}
@@ -119,6 +119,11 @@ post-configure:
 
 post-install:
 	${STRIP_CMD} ${STAGEDIR}${PREFIX}/bin/node
+# Fail if two simdutf copies end up in one process (node exports its bundled
+# simdutf; a DSO linking the system libsimdutf.so, e.g. libmerve, then binds
+# to the wrong one and CJS export lexing SIGBUSes). Importing a CommonJS
+# module from ESM runs that lexer; stdin avoids a temp file.
+	${ECHO_CMD} 'exports.ok = 1' | ${STAGEDIR}${PREFIX}/bin/node --input-type=module -e 'import "/dev/stdin"'
 
 post-install-BUNDLED_SSL-on:
 	cd ${STAGEDIR}${PREFIX} && ${FIND} include/node/openssl/ -type f >> ${TMPPLIST}
diff --git a/www/node24/Makefile b/www/node24/Makefile
index 7daf88f8f17b..bcb88f6bcdac 100644
--- a/www/node24/Makefile
+++ b/www/node24/Makefile
@@ -1,7 +1,7 @@
 PORTNAME=	node
 PORTVERSION=	${NODEJS_PORTVERSION}
 DISTVERSIONPREFIX=	v
-PORTREVISION=	1
+PORTREVISION=	3
 CATEGORIES=	www
 MASTER_SITES=	https://nodejs.org/dist/v${PORTVERSION}/
 PKGNAMESUFFIX=	${PORTVERSION:R:R}
@@ -26,7 +26,6 @@ LIB_DEPENDS=	libada.so:devel/libada \
 		libhdr_histogram.so:graphics/hdr_histogram \
 		libicui18n.so:devel/icu \
 		libllhttp.so:www/llhttp \
-		libmerve.so:devel/merve \
 		libnbytes.so:www/nbytes \
 		libnghttp2.so:www/libnghttp2 \
 		libnghttp3.so:www/libnghttp3 \
@@ -40,6 +39,10 @@ RUN_DEPENDS=	corepack>=0:www/corepack
 
 USES=		compiler:c++20-lang gmake localbase pkgconfig python:build shebangfix tar:xz
 
+# merve stays bundled: devel/merve links the system libsimdutf, while node
+# bundles and exports its own simdutf. rtld binds libsimdutf's internal calls
+# to node's copy, the dispatch tables disagree, and CJS export lexing jumps
+# to garbage (SIGBUS). Revisit together with --shared-simdutf.
 CONFIGURE_ARGS=	--prefix=${PREFIX:S|^${DESTDIR}||} \
 		--shared-ada \
 		--shared-brotli \
@@ -48,7 +51,6 @@ CONFIGURE_ARGS=	--prefix=${PREFIX:S|^${DESTDIR}||} \
 		--shared-hdr-histogram \
 		--shared-http-parser \
 		--shared-libuv \
-		--shared-merve \
 		--shared-nbytes \
 		--shared-nghttp2 \
 		--shared-nghttp3 \
@@ -107,7 +109,6 @@ post-patch:
 	@${RM} -r ${WRKSRC}/deps/histogram/
 	@${RM} -r ${WRKSRC}/deps/icu-small/
 	@${RM} -r ${WRKSRC}/deps/llhttp/
-	@${RM} -r ${WRKSRC}/deps/merve/
 	@${RM} -r ${WRKSRC}/deps/nbytes/
 	@${RM} -r ${WRKSRC}/deps/nghttp2/
 	@${RM} -r ${WRKSRC}/deps/ngtcp2/
@@ -130,6 +131,11 @@ post-configure:
 
 post-install:
 	${STRIP_CMD} ${STAGEDIR}${PREFIX}/bin/node
+# Fail if two simdutf copies end up in one process (node exports its bundled
+# simdutf; a DSO linking the system libsimdutf.so, e.g. libmerve, then binds
+# to the wrong one and CJS export lexing SIGBUSes). Importing a CommonJS
+# module from ESM runs that lexer; stdin avoids a temp file.
+	${ECHO_CMD} 'exports.ok = 1' | ${STAGEDIR}${PREFIX}/bin/node --input-type=module -e 'import "/dev/stdin"'
 
 post-install-BUNDLED_SSL-on:
 	cd ${STAGEDIR}${PREFIX} && ${FIND} include/node/openssl/ -type f >> ${TMPPLIST}
diff --git a/www/node26/Makefile b/www/node26/Makefile
index 9005c32c007b..608fdb17926b 100644
--- a/www/node26/Makefile
+++ b/www/node26/Makefile
@@ -1,7 +1,7 @@
 PORTNAME=	node
 PORTVERSION=	${NODEJS_PORTVERSION}
 DISTVERSIONPREFIX=	v
-PORTREVISION=	1
+PORTREVISION=	3
 CATEGORIES=	www
 MASTER_SITES=	https://nodejs.org/dist/v${PORTVERSION}/
 PKGNAMESUFFIX=	${PORTVERSION:R:R}
@@ -29,7 +29,6 @@ LIB_DEPENDS=	libada.so:devel/libada \
 		libicui18n.so:devel/icu \
 		libLIEF.so:devel/lief \
 		libllhttp.so:www/llhttp \
-		libmerve.so:devel/merve \
 		libnbytes.so:www/nbytes \
 		libnghttp2.so:www/libnghttp2 \
 		libnghttp3.so:www/libnghttp3 \
@@ -43,6 +42,10 @@ RUN_DEPENDS=	corepack>=0:www/corepack
 
 USES=		compiler:c++20-lang gmake localbase pkgconfig python:build shebangfix tar:xz
 
+# merve stays bundled: devel/merve links the system libsimdutf, while node
+# bundles and exports its own simdutf. rtld binds libsimdutf's internal calls
+# to node's copy, the dispatch tables disagree, and CJS export lexing jumps
+# to garbage (SIGBUS). Revisit together with --shared-simdutf.
 CONFIGURE_ARGS=	--prefix=${PREFIX:S|^${DESTDIR}||} \
 		--shared-ada \
 		--shared-brotli \
@@ -54,7 +57,6 @@ CONFIGURE_ARGS=	--prefix=${PREFIX:S|^${DESTDIR}||} \
 		--shared-http-parser \
 		--shared-libuv \
 		--shared-lief \
-		--shared-merve \
 		--shared-nbytes \
 		--shared-nghttp2 \
 		--shared-nghttp3 \
@@ -112,7 +114,6 @@ post-patch:
 	@${RM} -r ${WRKSRC}/deps/icu-small/
 	@${RM} -r ${WRKSRC}/deps/LIEF/
 	@${RM} -r ${WRKSRC}/deps/llhttp/
-	@${RM} -r ${WRKSRC}/deps/merve/
 	@${RM} -r ${WRKSRC}/deps/nbytes/
 	@${RM} -r ${WRKSRC}/deps/nghttp2/
 	@${RM} -r ${WRKSRC}/deps/ngtcp2/
@@ -136,6 +137,11 @@ post-configure:
 
 post-install:
 	${STRIP_CMD} ${STAGEDIR}${PREFIX}/bin/node
+# Fail if two simdutf copies end up in one process (node exports its bundled
+# simdutf; a DSO linking the system libsimdutf.so, e.g. libmerve, then binds
+# to the wrong one and CJS export lexing SIGBUSes). Importing a CommonJS
+# module from ESM runs that lexer; stdin avoids a temp file.
+	${ECHO_CMD} 'exports.ok = 1' | ${STAGEDIR}${PREFIX}/bin/node --input-type=module -e 'import "/dev/stdin"'
 
 post-install-BUNDLED_SSL-on:
 	cd ${STAGEDIR}${PREFIX} && ${FIND} include/node/openssl/ -type f >> ${TMPPLIST}