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',], }],