Commit 7e39b8781aa for nodejs

commit 7e39b8781aa25d4c3477f5add9d31a00b50e2802
Author: Paolo Insogna <paolo@cowtech.it>
Date:   Sun Oct 4 15:25:43 2026 +0200

    deps,lib,tools: remove jitless and lite modes

    Signed-off-by: Paolo Insogna <paolo@cowtech.it>
    PR-URL: https://github.com/nodejs/node/pull/66459
    Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
    Reviewed-By: Robert Nagy <ronagy@icloud.com>
    Reviewed-By: Matteo Collina <matteo.collina@gmail.com>

diff --git a/BUILDING.md b/BUILDING.md
index 6fc93ca2ad0..795019c77db 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 e769236cebe..eec2250da6b 100755
--- a/configure.py
+++ b/configure.py
@@ -1224,14 +1224,6 @@ 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',
@@ -1318,6 +1310,10 @@ 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())

@@ -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 4e17d81169d..d51eef7e2ac 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`
-
-<!-- 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
@@ -4411,7 +4398,6 @@ 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`
@@ -4782,8 +4768,6 @@ documented here:

 ### `--interpreted-frames-native-stack`

-### `--jitless`
-
 ### `--max-heap-size`

 Specifies the maximum heap size (in megabytes) for the process.
@@ -4976,7 +4960,6 @@ 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 11292b60b5d..2838eb5e65b 100644
--- a/doc/api/errors.md
+++ b/doc/api/errors.md
@@ -3645,14 +3645,6 @@ 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`
@@ -4488,6 +4480,18 @@ 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 70488dff26c..5db586dddd1 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
@@ -2528,8 +2523,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 9240b00ac70..59b13b6a07d 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 0e80f682817..e7ef5aeba96 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 5dd425cb093..9f1dbae7c9a 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 2f72e636ab9..54600a50b6d 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 d0b5310626c..ff4cb4b6ac5 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 72c8a9a2aef..668b92695a5 100644
--- a/src/node_options.cc
+++ b/src/node_options.cc
@@ -1563,10 +1563,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 14b74f4b6d4..4dc948113fb 100644
--- a/src/node_v8.cc
+++ b/src/node_v8.cc
@@ -272,6 +272,10 @@ 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 ff063e018d0..3496652a819 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 39f10491fa2..00000000000
--- 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 fd58d1d990a..9efe10ad61b 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 00000000000..96555e7c88f
--- /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 375c46c11ab..f6832ab0c79 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 b3a2503d1d5..e96d2c7832b 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 8e81f42a945..1c50880f49f 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 95717992bca..5bf6b6f5de4 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',],
       }],