diff --git a/BUILDING.md b/BUILDING.md
index 6fc93ca2ad0e..795019c77db9 100644
--- a/BUILDING.md
+++ b/BUILDING.md
@@ -98,6 +98,9 @@ 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 e769236cebe8..eec2250da6b1 100755
--- a/configure.py
+++ b/configure.py
@@ -1224,14 +1224,6 @@
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',
@@ -1318,6 +1310,10 @@
(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())
@@ -2231,9 +2227,8 @@ 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'] = 0 if options.v8_lite_mode else 1
+ o['variables']['v8_enable_webassembly'] = 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 b57714a0bfa2..5d1e5e2b6da1 100644
--- a/doc/api/cli.md
+++ b/doc/api/cli.md
@@ -2196,19 +2196,6 @@ added: v0.7.7
Opens the REPL even if stdin does not appear to be a terminal.
-### `--jitless`
-
-
-
-> 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`
+
+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.
+
### `ERR_WORKER_UNSUPPORTED_EXTENSION`
diff --git a/doc/node.1 b/doc/node.1
index 36a7cc720440..2a4d702820b2 100644
--- a/doc/node.1
+++ b/doc/node.1
@@ -1175,11 +1175,6 @@ 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
@@ -2536,8 +2531,6 @@ 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 9240b00ac709..59b13b6a07da 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 not available in primordials because it can be
- // disabled with --jitless CLI flag.
+ // WebAssembly is unavailable during snapshot building and must be
+ // accessed lazily at runtime.
{
name: 'WebAssembly',
message: 'Use `const { WebAssembly } = globalThis;` instead of the global.',
diff --git a/lib/internal/errors.js b/lib/internal/errors.js
index 0e80f682817d..e7ef5aeba96f 100644
--- a/lib/internal/errors.js
+++ b/lib/internal/errors.js
@@ -1981,9 +1981,6 @@ 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 5dd425cb0934..9f1dbae7c9a5 100644
--- a/lib/internal/freeze_intrinsics.js
+++ b/lib/internal/freeze_intrinsics.js
@@ -363,18 +363,16 @@ module.exports = function() {
ArrayPrototypePush(intrinsics, SharedArrayBuffer);
}
- 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);
- }
+ 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 2f72e636ab90..54600a50b6d7 100644
--- a/lib/internal/util.js
+++ b/lib/internal/util.js
@@ -45,7 +45,6 @@ const {
SymbolPrototypeGetDescription,
SymbolReplace,
SymbolSplit,
- globalThis,
} = primordials;
const {
@@ -53,7 +52,6 @@ const {
ERR_NO_CRYPTO,
ERR_NO_TYPESCRIPT,
ERR_UNKNOWN_SIGNAL,
- ERR_WEBASSEMBLY_NOT_SUPPORTED,
},
isErrorStackTraceLimitWritable,
overrideStackTrace,
@@ -245,8 +243,6 @@ 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 d0b5310626c3..ff4cb4b6ac58 100644
--- a/src/node.cc
+++ b/src/node.cc
@@ -48,6 +48,10 @@
#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
@@ -1108,6 +1112,12 @@ 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 917f6a4f47cc..d43bf3b8fd1a 100644
--- a/src/node_options.cc
+++ b/src/node_options.cc
@@ -1568,10 +1568,6 @@ 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 14b74f4b6d42..4dc948113fbc 100644
--- a/src/node_v8.cc
+++ b/src/node_v8.cc
@@ -272,6 +272,10 @@ void SetFlagsFromString(const FunctionCallbackInfo& 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& args) {
diff --git a/test/doctool/test-doc-api-json.mjs b/test/doctool/test-doc-api-json.mjs
index ff063e018d0e..3496652a819b 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, 46); // Increase this number every time a section is marked as removed.
+assert.strictEqual(numberOfRemovedAPIs, 47); // 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
deleted file mode 100644
index 39f10491fa28..000000000000
--- a/test/es-module/test-import-cjs-jitless.mjs
+++ /dev/null
@@ -1,6 +0,0 @@
-// 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 fd58d1d990a5..9efe10ad61bf 100644
--- a/test/es-module/test-typescript.mjs
+++ b/test/es-module/test-typescript.mjs
@@ -318,14 +318,3 @@ 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
new file mode 100644
index 000000000000..96555e7c88f9
--- /dev/null
+++ b/test/parallel/test-cli-no-jitless.js
@@ -0,0 +1,90 @@
+'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 375c46c11ab7..f6832ab0c79b 100644
--- a/test/parallel/test-cli-node-options.js
+++ b/test/parallel/test-cli-node-options.js
@@ -76,7 +76,6 @@ 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 b3a2503d1d5b..e96d2c7832be 100644
--- a/test/parallel/test-freeze-intrinsics.js
+++ b/test/parallel/test-freeze-intrinsics.js
@@ -1,8 +1,11 @@
-// Flags: --frozen-intrinsics --jitless
+// Flags: --frozen-intrinsics
'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 8e81f42a9456..1c50880f49fa 100644
--- a/test/parallel/test-internal-webidl-buffer-source.js
+++ b/test/parallel/test-internal-webidl-buffer-source.js
@@ -326,9 +326,7 @@ test('Shared buffer growability checks do not read JavaScript properties', () =>
}
});
-test('Shared WebAssembly buffer growability is checked per buffer', {
- skip: typeof WebAssembly === 'undefined',
-}, () => {
+test('Shared WebAssembly buffer growability is checked per buffer', () => {
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 95717992bcab..5bf6b6f5de41 100644
--- a/tools/v8_gypfiles/features.gypi
+++ b/tools/v8_gypfiles/features.gypi
@@ -309,11 +309,6 @@
# 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,
@@ -351,9 +346,6 @@
['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',],
}],