Commit 88ec31414c9 for nodejs
commit 88ec31414c9e308b91b19028a8090689d927c8be
Author: Joyee Cheung <joyeec9h3@gmail.com>
Date: Mon Oct 5 13:25:07 2026 +0200
Revert "deps,lib,tools: remove jitless and lite modes"
Newer version of V8 no longer supports
--allow-overwriting-for-next-flag and there's no way to
override the preset flag now. Revert the commit to allow
users to opt-in into the flags if still necessary.
This reverts commit 7e39b8781aa25d4c3477f5add9d31a00b50e2802.
Signed-off-by: Joyee Cheung <joyeec9h3@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/66516
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
diff --git a/BUILDING.md b/BUILDING.md
index 795019c77db..6fc93ca2ad0 100644
--- a/BUILDING.md
+++ b/BUILDING.md
@@ -98,9 +98,6 @@ and libc version. The table below lists the support tier for each supported
combination. A list of [supported compile toolchains](#supported-toolchains) is
also supplied for tier 1 platforms.
-Node.js requires WebAssembly for built-in functionality, including `fetch()` and
-TypeScript support. JIT-less execution and V8 lite mode are not supported.
-
**For production applications, run Node.js on supported platforms only (Tier 1 or 2).**
Node.js does not support a platform version if a vendor has expired support
diff --git a/configure.py b/configure.py
index eec2250da6b..e769236cebe 100755
--- a/configure.py
+++ b/configure.py
@@ -1224,6 +1224,14 @@ parser.add_argument('--v8-with-dchecks',
default=False,
help='compile V8 with debug checks and runtime debugging features enabled')
+parser.add_argument('--v8-lite-mode',
+ action='store_true',
+ dest='v8_lite_mode',
+ default=False,
+ help='compile V8 in lite mode for constrained environments (lowers V8 '+
+ 'memory footprint, but also implies no just-in-time compilation ' +
+ 'support, thus much slower execution)')
+
parser.add_argument('--v8-enable-object-print',
action='store_true',
dest='v8_enable_object_print',
@@ -1310,10 +1318,6 @@ parser.add_argument('--use-ccache-win',
(options, args) = parser.parse_known_args()
-# Reject the removed option before forwarding unknown arguments to GYP.
-if any(arg.split('=', 1)[0] == '--v8-lite-mode' for arg in args):
- parser.error('--v8-lite-mode is no longer supported; Node.js requires WebAssembly')
-
# Expand ~ in the install prefix now, it gets written to multiple files.
options.prefix = str(Path(options.prefix or '').expanduser())
@@ -2227,8 +2231,9 @@ def configure_library(lib, output, pkgname=None):
def configure_v8(o, configs):
set_configuration_variable(configs, 'v8_enable_v8_checks', release=0, debug=1)
- o['variables']['v8_enable_webassembly'] = 1
+ o['variables']['v8_enable_webassembly'] = 0 if options.v8_lite_mode else 1
o['variables']['v8_enable_javascript_promise_hooks'] = 1
+ o['variables']['v8_enable_lite_mode'] = 1 if options.v8_lite_mode else 0
is_gdbjit_supported_arch = (
'x64' in o['variables']['target_arch'] or
'ia32' in o['variables']['target_arch'] or
diff --git a/doc/api/cli.md b/doc/api/cli.md
index d51eef7e2ac..4e17d81169d 100644
--- a/doc/api/cli.md
+++ b/doc/api/cli.md
@@ -2196,6 +2196,19 @@ added: v0.7.7
Opens the REPL even if stdin does not appear to be a terminal.
+### `--jitless`
+
+<!-- YAML
+added: v12.0.0
+-->
+
+> Stability: 1 - Experimental. This flag is inherited from V8 and is subject to
+> change upstream.
+
+Disable [runtime allocation of executable memory][jitless]. This may be
+required on some platforms for security reasons. It can also reduce attack
+surface on other platforms, but the performance impact may be severe.
+
### `--localstorage-file=file`
<!-- YAML
@@ -4398,6 +4411,7 @@ V8 options that are allowed are:
* `--enable-etw-stack-walking`
* `--expose-gc`
* `--interpreted-frames-native-stack`
+* `--jitless`
* `--max-heap-size`
* `--max-old-space-size`
* `--max-semi-space-size`
@@ -4768,6 +4782,8 @@ documented here:
### `--interpreted-frames-native-stack`
+### `--jitless`
+
### `--max-heap-size`
Specifies the maximum heap size (in megabytes) for the process.
@@ -4960,6 +4976,7 @@ node --stack-trace-limit=12 -p -e "Error.stackTraceLimit" # prints 12
[environment_variables]: #environment-variables-1
[filtering tests by name]: test.md#filtering-tests-by-name
[global setup and teardown]: test.md#global-setup-and-teardown
+[jitless]: https://v8.dev/blog/jitless
[libuv threadpool documentation]: https://docs.libuv.org/en/latest/threadpool.html
[module compile cache]: module.md#module-compile-cache
[preloading asynchronous module customization hooks]: module.md#registration-of-asynchronous-customization-hooks
diff --git a/doc/api/errors.md b/doc/api/errors.md
index 2838eb5e65b..11292b60b5d 100644
--- a/doc/api/errors.md
+++ b/doc/api/errors.md
@@ -3645,6 +3645,14 @@ The WASI instance has already started.
The WASI instance has not been started.
+<a id="ERR_WEBASSEMBLY_NOT_SUPPORTED"></a>
+
+### `ERR_WEBASSEMBLY_NOT_SUPPORTED`
+
+A feature requiring WebAssembly was used, but WebAssembly is not supported or
+has been disabled in the current environment (for example, when running with
+`--jitless`).
+
<a id="ERR_WEBASSEMBLY_RESPONSE"></a>
### `ERR_WEBASSEMBLY_RESPONSE`
@@ -4480,18 +4488,6 @@ The linker function returned a module for which linking has failed.
The module must be successfully linked before instantiation.
-<a id="ERR_WEBASSEMBLY_NOT_SUPPORTED"></a>
-
-### `ERR_WEBASSEMBLY_NOT_SUPPORTED`
-
-<!-- YAML
-removed: REPLACEME
--->
-
-A feature requiring WebAssembly was used in an environment where WebAssembly
-was unavailable. Node.js now requires WebAssembly and no longer supports
-JIT-less execution.
-
<a id="ERR_WORKER_UNSUPPORTED_EXTENSION"></a>
### `ERR_WORKER_UNSUPPORTED_EXTENSION`
diff --git a/doc/node.1 b/doc/node.1
index 5db586dddd1..70488dff26c 100644
--- a/doc/node.1
+++ b/doc/node.1
@@ -1175,6 +1175,11 @@ See V8 Inspector integration for Node.js for further explanation on Node.js debu
.It Fl i , Fl -interactive
Opens the REPL even if stdin does not appear to be a terminal.
.
+.It Fl -jitless
+Disable runtime allocation of executable memory. This may be
+required on some platforms for security reasons. It can also reduce attack
+surface on other platforms, but the performance impact may be severe.
+.
.It Fl -localstorage-file Ns = Ns Ar file
The file used to store \fBlocalStorage\fR data. If the file does not exist, it is
created the first time \fBlocalStorage\fR is accessed. The same file may be shared
@@ -2523,6 +2528,8 @@ V8 options that are allowed are:
.It
\fB--interpreted-frames-native-stack\fR
.It
+\fB--jitless\fR
+.It
\fB--max-heap-size\fR
.It
\fB--max-old-space-size\fR
diff --git a/lib/eslint.config_partial.mjs b/lib/eslint.config_partial.mjs
index 59b13b6a07d..9240b00ac70 100644
--- a/lib/eslint.config_partial.mjs
+++ b/lib/eslint.config_partial.mjs
@@ -298,8 +298,8 @@ export default [
name: 'URLSearchParams',
message: "Use `const { URLSearchParams } = require('internal/url');` instead of the global.",
},
- // WebAssembly is unavailable during snapshot building and must be
- // accessed lazily at runtime.
+ // WebAssembly is not available in primordials because it can be
+ // disabled with --jitless CLI flag.
{
name: 'WebAssembly',
message: 'Use `const { WebAssembly } = globalThis;` instead of the global.',
diff --git a/lib/internal/errors.js b/lib/internal/errors.js
index e7ef5aeba96..0e80f682817 100644
--- a/lib/internal/errors.js
+++ b/lib/internal/errors.js
@@ -1981,6 +1981,9 @@ E('ERR_VM_MODULE_NOT_MODULE',
'Provided module is not an instance of Module', Error);
E('ERR_VM_MODULE_STATUS', 'Module status %s', Error);
E('ERR_WASI_ALREADY_STARTED', 'WASI instance has already started', Error);
+E('ERR_WEBASSEMBLY_NOT_SUPPORTED',
+ 'WebAssembly is not supported in this environment, but is required for %s',
+ Error);
E('ERR_WEBASSEMBLY_RESPONSE', 'WebAssembly response %s', TypeError);
E('ERR_WORKER_HANDLE_NOT_TRANSFERABLE',
'%s cannot be transferred in its current state; it must be a freshly ' +
diff --git a/lib/internal/freeze_intrinsics.js b/lib/internal/freeze_intrinsics.js
index 9f1dbae7c9a..5dd425cb093 100644
--- a/lib/internal/freeze_intrinsics.js
+++ b/lib/internal/freeze_intrinsics.js
@@ -363,16 +363,18 @@ module.exports = function() {
ArrayPrototypePush(intrinsics, SharedArrayBuffer);
}
- ArrayPrototypePush(intrinsicPrototypes,
- WebAssembly.Module.prototype,
- WebAssembly.Instance.prototype,
- WebAssembly.Table.prototype,
- WebAssembly.Memory.prototype,
- WebAssembly.CompileError.prototype,
- WebAssembly.LinkError.prototype,
- WebAssembly.RuntimeError.prototype,
- );
- ArrayPrototypePush(intrinsics, WebAssembly);
+ if (typeof WebAssembly !== 'undefined') {
+ ArrayPrototypePush(intrinsicPrototypes,
+ WebAssembly.Module.prototype,
+ WebAssembly.Instance.prototype,
+ WebAssembly.Table.prototype,
+ WebAssembly.Memory.prototype,
+ WebAssembly.CompileError.prototype,
+ WebAssembly.LinkError.prototype,
+ WebAssembly.RuntimeError.prototype,
+ );
+ ArrayPrototypePush(intrinsics, WebAssembly);
+ }
if (typeof Intl !== 'undefined') {
ArrayPrototypePush(intrinsicPrototypes,
diff --git a/lib/internal/util.js b/lib/internal/util.js
index 54600a50b6d..2f72e636ab9 100644
--- a/lib/internal/util.js
+++ b/lib/internal/util.js
@@ -45,6 +45,7 @@ const {
SymbolPrototypeGetDescription,
SymbolReplace,
SymbolSplit,
+ globalThis,
} = primordials;
const {
@@ -52,6 +53,7 @@ const {
ERR_NO_CRYPTO,
ERR_NO_TYPESCRIPT,
ERR_UNKNOWN_SIGNAL,
+ ERR_WEBASSEMBLY_NOT_SUPPORTED,
},
isErrorStackTraceLimitWritable,
overrideStackTrace,
@@ -243,6 +245,8 @@ function assertCrypto() {
function assertTypeScript() {
if (noTypeScript)
throw new ERR_NO_TYPESCRIPT();
+ if (globalThis.WebAssembly === undefined)
+ throw new ERR_WEBASSEMBLY_NOT_SUPPORTED('TypeScript');
}
/**
diff --git a/src/node.cc b/src/node.cc
index ff4cb4b6ac5..d0b5310626c 100644
--- a/src/node.cc
+++ b/src/node.cc
@@ -48,10 +48,6 @@
#include "node_version.h"
#include "permission/env_permission.h"
-#if defined(V8_JITLESS) || defined(V8_LITE_MODE)
-#error Node.js does not support interpreter-only V8 builds.
-#endif
-
#if HAVE_OPENSSL
#include "ncrypto.h"
#if OPENSSL_VERSION_MAJOR >= 3
@@ -1112,12 +1108,6 @@ static ExitCode InitializeNodeWithArgsInternal(
allow_env_sources.Finish();
- // Node.js requires WebAssembly for built-in functionality. Override these
- // modes after all option sources, before V8 applies their implications.
- // Explicitly allow overriding even when contradiction checks are enabled.
- V8::SetFlagsFromString("--allow-overwriting-for-next-flag --no-lite-mode "
- "--allow-overwriting-for-next-flag --no-jitless");
-
// Every option source has now been parsed, so cross-source option
// constraints can finally be validated.
CheckGlobalBenchOptions(errors);
diff --git a/src/node_options.cc b/src/node_options.cc
index 911bb999ed2..c46b3baa775 100644
--- a/src/node_options.cc
+++ b/src/node_options.cc
@@ -1563,6 +1563,10 @@ PerIsolateOptionsParser::PerIsolateOptionsParser(
"disallow eval and friends",
V8Option{},
kAllowedInEnvvar);
+ AddOption("--jitless",
+ "disable runtime allocation of executable memory",
+ V8Option{},
+ kAllowedInEnvvar);
AddOption("--report-uncaught-exception",
"generate diagnostic report on uncaught exceptions",
BOOL_FIELD(report_uncaught_exception),
diff --git a/src/node_v8.cc b/src/node_v8.cc
index 4dc948113fb..14b74f4b6d4 100644
--- a/src/node_v8.cc
+++ b/src/node_v8.cc
@@ -272,10 +272,6 @@ void SetFlagsFromString(const FunctionCallbackInfo<Value>& args) {
CHECK(args[0]->IsString());
Utf8Value flags(args.GetIsolate(), args[0]);
V8::SetFlagsFromString(flags.out(), flags.length());
- // Runtime flags must not re-enable modes that disable required WebAssembly
- // support, even when contradiction checks are enabled.
- V8::SetFlagsFromString("--allow-overwriting-for-next-flag --no-lite-mode "
- "--allow-overwriting-for-next-flag --no-jitless");
}
void StartCpuProfile(const FunctionCallbackInfo<Value>& args) {
diff --git a/test/doctool/test-doc-api-json.mjs b/test/doctool/test-doc-api-json.mjs
index 3496652a819..ff063e018d0 100644
--- a/test/doctool/test-doc-api-json.mjs
+++ b/test/doctool/test-doc-api-json.mjs
@@ -159,4 +159,4 @@ for await (const dirent of await fs.opendir(new URL('../../out/doc/api/', import
}
assert.strictEqual(numberOfDeprecatedSections, 49); // Increase this number every time a new API is deprecated.
-assert.strictEqual(numberOfRemovedAPIs, 47); // Increase this number every time a section is marked as removed.
+assert.strictEqual(numberOfRemovedAPIs, 46); // Increase this number every time a section is marked as removed.
diff --git a/test/es-module/test-import-cjs-jitless.mjs b/test/es-module/test-import-cjs-jitless.mjs
new file mode 100644
index 00000000000..39f10491fa2
--- /dev/null
+++ b/test/es-module/test-import-cjs-jitless.mjs
@@ -0,0 +1,6 @@
+// Flags: --jitless
+
+// Tests that importing a CJS module works in JIT-less mode (i.e. falling back to the
+// JS parser if WASM is not available).
+import '../common/index.mjs';
+import '../fixtures/empty.cjs';
diff --git a/test/es-module/test-typescript.mjs b/test/es-module/test-typescript.mjs
index 9efe10ad61b..fd58d1d990a 100644
--- a/test/es-module/test-typescript.mjs
+++ b/test/es-module/test-typescript.mjs
@@ -318,3 +318,14 @@ test('execute invalid TypeScript syntax', async () => {
assert.strictEqual(result.stdout, '');
assert.strictEqual(result.code, 1);
});
+
+test('expect error when executing a TypeScript file with --jitless', async () => {
+ const result = await spawnPromisified(process.execPath, [
+ '--jitless',
+ fixtures.path('typescript/ts/test-typescript.ts'),
+ ]);
+
+ assert.match(result.stderr, /ERR_WEBASSEMBLY_NOT_SUPPORTED/);
+ assert.match(result.stderr, /WebAssembly is not supported in this environment, but is required for TypeScript/);
+ assert.strictEqual(result.code, 1);
+});
diff --git a/test/parallel/test-cli-no-jitless.js b/test/parallel/test-cli-no-jitless.js
deleted file mode 100644
index 96555e7c88f..00000000000
--- a/test/parallel/test-cli-no-jitless.js
+++ /dev/null
@@ -1,90 +0,0 @@
-'use strict';
-
-const common = require('../common');
-const assert = require('assert');
-const { spawnSync } = require('child_process');
-
-// Interpreter-only modes are overridden to preserve Node's WebAssembly
-// requirement, including alternate spellings and the runtime flag API.
-const wasm = `
- const assert = require('assert');
- assert.strictEqual(typeof WebAssembly, 'object');
- const bytes = Uint8Array.of(0, 97, 115, 109, 1, 0, 0, 0);
- new WebAssembly.Instance(new WebAssembly.Module(bytes));
-`;
-
-for (const flag of [
- '--jitless', '--lite-mode', '--lite_mode', '-jitless', '-lite_mode',
- '--no-jitless', '--no-lite-mode',
-]) {
- const result = spawnSync(process.execPath, [flag, '-e', wasm], { encoding: 'utf8' });
- assert.strictEqual(result.status, 0, result.stderr);
- assert.strictEqual(result.signal, null);
- assert.strictEqual(result.stderr, '');
-
- const runtime = spawnSync(process.execPath, ['-e', `
- require('v8').setFlagsFromString(${JSON.stringify(flag)});
- ${wasm}
- // A new isolate must also retain WebAssembly after changing runtime flags.
- new (require('worker_threads').Worker)(${JSON.stringify(wasm)}, { eval: true });
- `], { encoding: 'utf8' });
- assert.strictEqual(runtime.status, 0, runtime.stderr);
- assert.strictEqual(runtime.stderr, '');
-}
-
-// V8 boolean flags reject explicit values, even for overridden modes.
-for (const flag of ['--jitless=true', '--lite_mode=true']) {
- const result = spawnSync(process.execPath, [flag, '-e', wasm], { encoding: 'utf8' });
- assert.strictEqual(result.status, 9, result.stderr);
- assert.strictEqual(result.signal, null);
- assert.match(result.stderr, /illegal value for flag .* of type bool/);
-
- // The runtime API reports invalid syntax without throwing, and Node must
- // still override any flags V8 changed before reporting the error.
- const runtime = spawnSync(process.execPath, ['-e', `
- require('v8').setFlagsFromString(${JSON.stringify(flag)});
- ${wasm}
- new (require('worker_threads').Worker)(${JSON.stringify(wasm)}, { eval: true });
- `], { encoding: 'utf8' });
- assert.strictEqual(runtime.status, 0, runtime.stderr);
- assert.strictEqual(runtime.signal, null);
- assert.match(runtime.stderr, /illegal value for flag .* of type bool/);
-}
-
-// Node's overrides must not be treated as contradictory user-supplied flags.
-for (const flag of ['--jitless', '--lite-mode']) {
- const result = spawnSync(process.execPath, [
- '--abort-on-contradictory-flags', flag, '-e', wasm,
- ], { encoding: 'utf8' });
- assert.strictEqual(result.status, 0, result.stderr);
- assert.strictEqual(result.signal, null);
- assert.strictEqual(result.stderr, '');
-}
-
-const options = spawnSync(process.execPath, ['--v8-options'], { encoding: 'utf8' });
-assert.strictEqual(options.status, 0, options.stderr);
-assert.match(options.stdout, /--jitless\b/);
-assert.match(options.stdout, /--lite-mode\b/);
-
-for (const flag of ['--jitless', '--lite-mode', '--lite_mode']) {
- assert.strictEqual(process.allowedNodeEnvironmentFlags.has(flag), false);
- if (!process.config.variables.node_without_node_options) {
- const result = spawnSync(process.execPath, ['-e', ''], {
- encoding: 'utf8',
- env: { ...process.env, NODE_OPTIONS: flag },
- });
- assert.strictEqual(result.status, 9, result.stderr);
- assert.match(result.stderr, /is not allowed in NODE_OPTIONS/);
- }
-}
-
-// Workers must inherit a runtime with working WebAssembly as well.
-const { Worker } = require('worker_threads');
-const worker = new Worker(`
- const { parentPort } = require('worker_threads');
- const bytes = Uint8Array.of(0, 97, 115, 109, 1, 0, 0, 0);
- new WebAssembly.Instance(new WebAssembly.Module(bytes));
- parentPort.postMessage(typeof WebAssembly);
-`, { eval: true });
-worker.on('message', common.mustCall((value) => assert.strictEqual(value, 'object')));
-worker.on('exit', common.mustCall((code) => assert.strictEqual(code, 0)));
diff --git a/test/parallel/test-cli-node-options.js b/test/parallel/test-cli-node-options.js
index f6832ab0c79..375c46c11ab 100644
--- a/test/parallel/test-cli-node-options.js
+++ b/test/parallel/test-cli-node-options.js
@@ -76,6 +76,7 @@ if (common.hasCrypto) {
expect('--abort_on-uncaught_exception', 'B\n');
expect('--disallow-code-generation-from-strings', 'B\n');
expect('--expose-gc', 'B\n');
+expect('--jitless', 'B\n');
expect('--max-old-space-size=0', 'B\n');
expect('--max-semi-space-size=0', 'B\n');
expect('--stack-trace-limit=100',
diff --git a/test/parallel/test-freeze-intrinsics.js b/test/parallel/test-freeze-intrinsics.js
index e96d2c7832b..b3a2503d1d5 100644
--- a/test/parallel/test-freeze-intrinsics.js
+++ b/test/parallel/test-freeze-intrinsics.js
@@ -1,11 +1,8 @@
-// Flags: --frozen-intrinsics
+// Flags: --frozen-intrinsics --jitless
'use strict';
require('../common');
const assert = require('assert');
-assert(Object.isFrozen(WebAssembly));
-assert(Object.isFrozen(WebAssembly.Module.prototype));
-
assert.throws(
() => Object.defineProperty = 'asdf',
TypeError
diff --git a/test/parallel/test-internal-webidl-buffer-source.js b/test/parallel/test-internal-webidl-buffer-source.js
index 1c50880f49f..8e81f42a945 100644
--- a/test/parallel/test-internal-webidl-buffer-source.js
+++ b/test/parallel/test-internal-webidl-buffer-source.js
@@ -326,7 +326,9 @@ test('Shared buffer growability checks do not read JavaScript properties', () =>
}
});
-test('Shared WebAssembly buffer growability is checked per buffer', () => {
+test('Shared WebAssembly buffer growability is checked per buffer', {
+ skip: typeof WebAssembly === 'undefined',
+}, () => {
const memory = new WebAssembly.Memory({ initial: 1, maximum: 2, shared: true });
for (const [buffer, growable] of [
[memory.buffer, false],
diff --git a/tools/v8_gypfiles/features.gypi b/tools/v8_gypfiles/features.gypi
index 04ad6339194..10c8f4c9d78 100644
--- a/tools/v8_gypfiles/features.gypi
+++ b/tools/v8_gypfiles/features.gypi
@@ -309,6 +309,11 @@
# This option can be on unconditionally.
'v8_enable_temporal_systemicu%': 1,
+ # Lite mode disables a number of performance optimizations to reduce memory
+ # at the cost of performance.
+ # Sets --DV8_LITE_MODE.
+ 'v8_enable_lite_mode%': 0,
+
# Enable the Turbofan compiler.
# Sets -dV8_ENABLE_TURBOFAN
'v8_enable_turbofan%': 1,
@@ -346,6 +351,9 @@
['v8_enable_future==1', {
'defines': ['V8_ENABLE_FUTURE',],
}],
+ ['v8_enable_lite_mode==1', {
+ 'defines': ['V8_LITE_MODE',],
+ }],
['v8_enable_gdbjit==1', {
'defines': ['ENABLE_GDB_JIT_INTERFACE',],
}],