diff --git a/lib/internal/bootstrap/cache.js b/lib/internal/bootstrap/cache.js index 41159d650bc375..ff6c4422c1148c 100644 --- a/lib/internal/bootstrap/cache.js +++ b/lib/internal/bootstrap/cache.js @@ -8,20 +8,13 @@ const { NativeModule } = require('internal/bootstrap/loaders'); +const { + source, + compileCodeCache +} = internalBinding('native_module'); const { hasTracing } = process.binding('config'); -function getCodeCache(id) { - const cached = NativeModule.getCached(id); - if (cached && (cached.loaded || cached.loading)) { - return cached.script.createCachedData(); - } - - // The script has not been compiled and run - NativeModule.require(id); - return getCodeCache(id); -} - -const depsModule = Object.keys(NativeModule._source).filter( +const depsModule = Object.keys(source).filter( (key) => NativeModule.isDepsModule(key) || key.startsWith('internal/deps') ); @@ -75,17 +68,10 @@ if (!process.versions.openssl) { } module.exports = { - cachableBuiltins: Object.keys(NativeModule._source).filter( + cachableBuiltins: Object.keys(source).filter( (key) => !cannotUseCache.includes(key) ), - builtinSource: Object.assign({}, NativeModule._source), - getCodeCache, - getSource: NativeModule.getSource, - codeCache: internalBinding('code_cache'), - compiledWithoutCache: NativeModule.compiledWithoutCache, - compiledWithCache: NativeModule.compiledWithCache, - nativeModuleWrap(script) { - return NativeModule.wrap(script); - }, + getSource(id) { return source[id]; }, + getCodeCache: compileCodeCache, cannotUseCache }; diff --git a/lib/internal/bootstrap/loaders.js b/lib/internal/bootstrap/loaders.js index 1879c0154c3179..78e1180dde2af2 100644 --- a/lib/internal/bootstrap/loaders.js +++ b/lib/internal/bootstrap/loaders.js @@ -149,7 +149,6 @@ // Create this WeakMap in js-land because V8 has no C++ API for WeakMap internalBinding('module_wrap').callbackMap = new WeakMap(); - const { ContextifyScript } = internalBinding('contextify'); // Set up NativeModule function NativeModule(id) { @@ -160,7 +159,6 @@ this.exportKeys = undefined; this.loaded = false; this.loading = false; - this.script = null; // The ContextifyScript of the module } NativeModule._source = getInternalBinding('natives'); @@ -168,12 +166,6 @@ const config = getBinding('config'); - const codeCache = getInternalBinding('code_cache'); - const codeCacheHash = getInternalBinding('code_cache_hash'); - const sourceHash = getInternalBinding('natives_hash'); - const compiledWithoutCache = NativeModule.compiledWithoutCache = []; - const compiledWithCache = NativeModule.compiledWithCache = []; - // Think of this as module.exports in this file even though it is not // written in CommonJS style. const loaderExports = { internalBinding, NativeModule }; @@ -332,67 +324,18 @@ this.exports = new Proxy(this.exports, handler); }; + const { compileFunction } = getInternalBinding('native_module'); NativeModule.prototype.compile = function() { const id = this.id; - let source = NativeModule.getSource(id); - source = NativeModule.wrap(source); this.loading = true; try { - // Currently V8 only checks that the length of the source code is the - // same as the code used to generate the hash, so we add an additional - // check here: - // 1. During compile time, when generating node_javascript.cc and - // node_code_cache.cc, we compute and include the hash of the - // (unwrapped) JavaScript source in both. - // 2. At runtime, we check that the hash of the code being compiled - // and the hash of the code used to generate the cache - // (inside the wrapper) is the same. - // This is based on the assumptions: - // 1. `internalBinding('code_cache_hash')` must be in sync with - // `internalBinding('code_cache')` (same C++ file) - // 2. `internalBinding('natives_hash')` must be in sync with - // `internalBinding('natives')` (same C++ file) - // 3. If `internalBinding('natives_hash')` is in sync with - // `internalBinding('natives_hash')`, then the (unwrapped) - // code used to generate `internalBinding('code_cache')` - // should be in sync with the (unwrapped) code in - // `internalBinding('natives')` - // There will be, however, false positives if the wrapper used - // to generate the cache is different from the one used at run time, - // and the length of the wrapper somehow stays the same. - // But that should be rare and can be eased once we make the - // two bootstrappers cached and checked as well. - const cache = codeCacheHash[id] && - (codeCacheHash[id] === sourceHash[id]) ? codeCache[id] : undefined; - - // (code, filename, lineOffset, columnOffset - // cachedData, produceCachedData, parsingContext) - const script = new ContextifyScript( - source, this.filename, 0, 0, - cache, false, undefined - ); - - // This will be used to create code cache in tools/generate_code_cache.js - this.script = script; - - // One of these conditions may be false when any of the inputs - // of the `node_js2c` target in node.gyp is modified. - // FIXME(joyeecheung): Figure out how to resolve the dependency issue. - // When the code cache was introduced we were at a point where refactoring - // node.gyp may not be worth the effort. - if (!cache || script.cachedDataRejected) { - compiledWithoutCache.push(this.id); - } else { - compiledWithCache.push(this.id); - } - - // Arguments: timeout, displayErrors, breakOnSigint - const fn = script.runInThisContext(-1, true, false); const requireFn = this.id.startsWith('internal/deps/') ? NativeModule.requireForDeps : NativeModule.require; + + const fn = compileFunction(id); fn(this.exports, requireFn, this, process, internalBinding); if (config.experimentalModules && !NativeModule.isInternal(this.id)) { diff --git a/node.gyp b/node.gyp index 83bd412dea6c9d..5b68281de45790 100644 --- a/node.gyp +++ b/node.gyp @@ -416,6 +416,8 @@ 'src/node_javascript.h', 'src/node_messaging.h', 'src/node_mutex.h', + 'src/node_native_module.h', + 'src/node_native_module.cc', 'src/node_options.h', 'src/node_options-inl.h', 'src/node_perf.h', diff --git a/src/env.h b/src/env.h index 01f686833eab24..3ed76ac49d4b02 100644 --- a/src/env.h +++ b/src/env.h @@ -120,248 +120,258 @@ constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2; // Strings are per-isolate primitives but Environment proxies them // for the sake of convenience. Strings should be ASCII-only. -#define PER_ISOLATE_STRING_PROPERTIES(V) \ - V(address_string, "address") \ - V(aliases_string, "aliases") \ - V(args_string, "args") \ - V(async_ids_stack_string, "async_ids_stack") \ - V(buffer_string, "buffer") \ - V(bytes_parsed_string, "bytesParsed") \ - V(bytes_read_string, "bytesRead") \ - V(bytes_written_string, "bytesWritten") \ - V(cached_data_string, "cachedData") \ - V(cached_data_produced_string, "cachedDataProduced") \ - V(cached_data_rejected_string, "cachedDataRejected") \ - V(change_string, "change") \ - V(channel_string, "channel") \ - V(chunks_sent_since_last_write_string, "chunksSentSinceLastWrite") \ - V(constants_string, "constants") \ - V(oncertcb_string, "oncertcb") \ - V(code_string, "code") \ - V(cwd_string, "cwd") \ - V(dest_string, "dest") \ - V(destroyed_string, "destroyed") \ - V(detached_string, "detached") \ - V(dns_a_string, "A") \ - V(dns_aaaa_string, "AAAA") \ - V(dns_cname_string, "CNAME") \ - V(dns_mx_string, "MX") \ - V(dns_naptr_string, "NAPTR") \ - V(dns_ns_string, "NS") \ - V(dns_ptr_string, "PTR") \ - V(dns_soa_string, "SOA") \ - V(dns_srv_string, "SRV") \ - V(dns_txt_string, "TXT") \ - V(duration_string, "duration") \ - V(emit_warning_string, "emitWarning") \ - V(exchange_string, "exchange") \ - V(encoding_string, "encoding") \ - V(entries_string, "entries") \ - V(entry_type_string, "entryType") \ - V(env_pairs_string, "envPairs") \ - V(env_var_settings_string, "envVarSettings") \ - V(errno_string, "errno") \ - V(error_string, "error") \ - V(exit_code_string, "exitCode") \ - V(expire_string, "expire") \ - V(exponent_string, "exponent") \ - V(exports_string, "exports") \ - V(ext_key_usage_string, "ext_key_usage") \ - V(external_stream_string, "_externalStream") \ - V(family_string, "family") \ - V(fatal_exception_string, "_fatalException") \ - V(fd_string, "fd") \ - V(file_string, "file") \ - V(fingerprint_string, "fingerprint") \ - V(fingerprint256_string, "fingerprint256") \ - V(flags_string, "flags") \ - V(fragment_string, "fragment") \ - V(get_data_clone_error_string, "_getDataCloneError") \ - V(get_shared_array_buffer_id_string, "_getSharedArrayBufferId") \ - V(gid_string, "gid") \ - V(handle_string, "handle") \ - V(help_text_string, "helpText") \ - V(homedir_string, "homedir") \ - V(host_string, "host") \ - V(hostmaster_string, "hostmaster") \ - V(ignore_string, "ignore") \ - V(infoaccess_string, "infoAccess") \ - V(inherit_string, "inherit") \ - V(input_string, "input") \ - V(internal_string, "internal") \ - V(ipv4_string, "IPv4") \ - V(ipv6_string, "IPv6") \ - V(isclosing_string, "isClosing") \ - V(issuer_string, "issuer") \ - V(issuercert_string, "issuerCertificate") \ - V(kill_signal_string, "killSignal") \ - V(kind_string, "kind") \ - V(mac_string, "mac") \ - V(main_string, "main") \ - V(max_buffer_string, "maxBuffer") \ - V(message_string, "message") \ - V(message_port_string, "messagePort") \ - V(message_port_constructor_string, "MessagePort") \ - V(minttl_string, "minttl") \ - V(modulus_string, "modulus") \ - V(name_string, "name") \ - V(netmask_string, "netmask") \ - V(nsname_string, "nsname") \ - V(ocsp_request_string, "OCSPRequest") \ - V(onaltsvc_string, "onaltsvc") \ - V(onchange_string, "onchange") \ - V(onclienthello_string, "onclienthello") \ - V(oncomplete_string, "oncomplete") \ - V(onconnection_string, "onconnection") \ - V(ondone_string, "ondone") \ - V(onerror_string, "onerror") \ - V(onexit_string, "onexit") \ - V(onframeerror_string, "onframeerror") \ - V(ongetpadding_string, "ongetpadding") \ - V(onhandshakedone_string, "onhandshakedone") \ - V(onhandshakestart_string, "onhandshakestart") \ - V(onheaders_string, "onheaders") \ - V(onmessage_string, "onmessage") \ - V(onnewsession_string, "onnewsession") \ - V(onocspresponse_string, "onocspresponse") \ - V(ongoawaydata_string, "ongoawaydata") \ - V(onorigin_string, "onorigin") \ - V(onpriority_string, "onpriority") \ - V(onread_string, "onread") \ - V(onreadstart_string, "onreadstart") \ - V(onreadstop_string, "onreadstop") \ - V(onping_string, "onping") \ - V(onsettings_string, "onsettings") \ - V(onshutdown_string, "onshutdown") \ - V(onsignal_string, "onsignal") \ - V(onstreamclose_string, "onstreamclose") \ - V(ontrailers_string, "ontrailers") \ - V(onunpipe_string, "onunpipe") \ - V(onwrite_string, "onwrite") \ - V(openssl_error_stack, "opensslErrorStack") \ - V(options_string, "options") \ - V(output_string, "output") \ - V(order_string, "order") \ - V(parse_error_string, "Parse Error") \ - V(password_string, "password") \ - V(path_string, "path") \ - V(pending_handle_string, "pendingHandle") \ - V(pid_string, "pid") \ - V(pipe_string, "pipe") \ - V(pipe_target_string, "pipeTarget") \ - V(pipe_source_string, "pipeSource") \ - V(port_string, "port") \ - V(port1_string, "port1") \ - V(port2_string, "port2") \ - V(preference_string, "preference") \ - V(priority_string, "priority") \ - V(promise_string, "promise") \ - V(pubkey_string, "pubkey") \ - V(query_string, "query") \ - V(raw_string, "raw") \ - V(read_host_object_string, "_readHostObject") \ - V(readable_string, "readable") \ - V(reason_string, "reason") \ - V(refresh_string, "refresh") \ - V(regexp_string, "regexp") \ - V(rename_string, "rename") \ - V(replacement_string, "replacement") \ - V(retry_string, "retry") \ - V(scheme_string, "scheme") \ - V(serial_string, "serial") \ - V(scopeid_string, "scopeid") \ - V(serial_number_string, "serialNumber") \ - V(service_string, "service") \ - V(servername_string, "servername") \ - V(session_id_string, "sessionId") \ - V(shell_string, "shell") \ - V(signal_string, "signal") \ - V(sink_string, "sink") \ - V(size_string, "size") \ - V(sni_context_err_string, "Invalid SNI context") \ - V(sni_context_string, "sni_context") \ - V(source_string, "source") \ - V(stack_string, "stack") \ - V(start_time_string, "startTime") \ - V(status_string, "status") \ - V(stdio_string, "stdio") \ - V(subject_string, "subject") \ - V(subjectaltname_string, "subjectaltname") \ - V(syscall_string, "syscall") \ - V(thread_id_string, "threadId") \ - V(ticketkeycallback_string, "onticketkeycallback") \ - V(timeout_string, "timeout") \ - V(tls_ticket_string, "tlsTicket") \ - V(ttl_string, "ttl") \ - V(type_string, "type") \ - V(uid_string, "uid") \ - V(unknown_string, "<unknown>") \ - V(url_string, "url") \ - V(username_string, "username") \ - V(valid_from_string, "valid_from") \ - V(valid_to_string, "valid_to") \ - V(value_string, "value") \ - V(verify_error_string, "verifyError") \ - V(version_string, "version") \ - V(weight_string, "weight") \ - V(windows_hide_string, "windowsHide") \ - V(windows_verbatim_arguments_string, "windowsVerbatimArguments") \ - V(wrap_string, "wrap") \ - V(writable_string, "writable") \ - V(write_host_object_string, "_writeHostObject") \ - V(write_queue_size_string, "writeQueueSize") \ - V(x_forwarded_string, "x-forwarded-for") \ +#define PER_ISOLATE_STRING_PROPERTIES(V) \ + V(address_string, "address") \ + V(aliases_string, "aliases") \ + V(args_string, "args") \ + V(async_ids_stack_string, "async_ids_stack") \ + V(buffer_string, "buffer") \ + V(bytes_parsed_string, "bytesParsed") \ + V(bytes_read_string, "bytesRead") \ + V(bytes_written_string, "bytesWritten") \ + V(cached_data_string, "cachedData") \ + V(cached_data_produced_string, "cachedDataProduced") \ + V(cached_data_rejected_string, "cachedDataRejected") \ + V(change_string, "change") \ + V(channel_string, "channel") \ + V(chunks_sent_since_last_write_string, "chunksSentSinceLastWrite") \ + V(constants_string, "constants") \ + V(oncertcb_string, "oncertcb") \ + V(code_string, "code") \ + V(cwd_string, "cwd") \ + V(dest_string, "dest") \ + V(destroyed_string, "destroyed") \ + V(detached_string, "detached") \ + V(dns_a_string, "A") \ + V(dns_aaaa_string, "AAAA") \ + V(dns_cname_string, "CNAME") \ + V(dns_mx_string, "MX") \ + V(dns_naptr_string, "NAPTR") \ + V(dns_ns_string, "NS") \ + V(dns_ptr_string, "PTR") \ + V(dns_soa_string, "SOA") \ + V(dns_srv_string, "SRV") \ + V(dns_txt_string, "TXT") \ + V(duration_string, "duration") \ + V(emit_warning_string, "emitWarning") \ + V(exchange_string, "exchange") \ + V(encoding_string, "encoding") \ + V(entries_string, "entries") \ + V(entry_type_string, "entryType") \ + V(env_pairs_string, "envPairs") \ + V(env_var_settings_string, "envVarSettings") \ + V(errno_string, "errno") \ + V(error_string, "error") \ + V(exit_code_string, "exitCode") \ + V(expire_string, "expire") \ + V(exponent_string, "exponent") \ + V(exports_string, "exports") \ + V(ext_key_usage_string, "ext_key_usage") \ + V(external_stream_string, "_externalStream") \ + V(family_string, "family") \ + V(fatal_exception_string, "_fatalException") \ + V(fd_string, "fd") \ + V(file_string, "file") \ + V(fingerprint_string, "fingerprint") \ + V(fingerprint256_string, "fingerprint256") \ + V(flags_string, "flags") \ + V(fragment_string, "fragment") \ + V(get_data_clone_error_string, "_getDataCloneError") \ + V(get_shared_array_buffer_id_string, "_getSharedArrayBufferId") \ + V(gid_string, "gid") \ + V(handle_string, "handle") \ + V(help_text_string, "helpText") \ + V(homedir_string, "homedir") \ + V(host_string, "host") \ + V(hostmaster_string, "hostmaster") \ + V(ignore_string, "ignore") \ + V(infoaccess_string, "infoAccess") \ + V(inherit_string, "inherit") \ + V(input_string, "input") \ + V(internal_string, "internal") \ + V(internal_binding_string, "internalBinding") \ + V(ipv4_string, "IPv4") \ + V(ipv6_string, "IPv6") \ + V(isclosing_string, "isClosing") \ + V(issuer_string, "issuer") \ + V(issuercert_string, "issuerCertificate") \ + V(kill_signal_string, "killSignal") \ + V(kind_string, "kind") \ + V(mac_string, "mac") \ + V(main_string, "main") \ + V(max_buffer_string, "maxBuffer") \ + V(message_string, "message") \ + V(message_port_string, "messagePort") \ + V(message_port_constructor_string, "MessagePort") \ + V(minttl_string, "minttl") \ + V(module_string, "module") \ + V(modulus_string, "modulus") \ + V(name_string, "name") \ + V(netmask_string, "netmask") \ + V(nsname_string, "nsname") \ + V(ocsp_request_string, "OCSPRequest") \ + V(onaltsvc_string, "onaltsvc") \ + V(onchange_string, "onchange") \ + V(onclienthello_string, "onclienthello") \ + V(oncomplete_string, "oncomplete") \ + V(onconnection_string, "onconnection") \ + V(ondone_string, "ondone") \ + V(onerror_string, "onerror") \ + V(onexit_string, "onexit") \ + V(onframeerror_string, "onframeerror") \ + V(ongetpadding_string, "ongetpadding") \ + V(onhandshakedone_string, "onhandshakedone") \ + V(onhandshakestart_string, "onhandshakestart") \ + V(onheaders_string, "onheaders") \ + V(onmessage_string, "onmessage") \ + V(onnewsession_string, "onnewsession") \ + V(onocspresponse_string, "onocspresponse") \ + V(ongoawaydata_string, "ongoawaydata") \ + V(onorigin_string, "onorigin") \ + V(onpriority_string, "onpriority") \ + V(onread_string, "onread") \ + V(onreadstart_string, "onreadstart") \ + V(onreadstop_string, "onreadstop") \ + V(onping_string, "onping") \ + V(onsettings_string, "onsettings") \ + V(onshutdown_string, "onshutdown") \ + V(onsignal_string, "onsignal") \ + V(onstreamclose_string, "onstreamclose") \ + V(ontrailers_string, "ontrailers") \ + V(onunpipe_string, "onunpipe") \ + V(onwrite_string, "onwrite") \ + V(openssl_error_stack, "opensslErrorStack") \ + V(options_string, "options") \ + V(output_string, "output") \ + V(order_string, "order") \ + V(parse_error_string, "Parse Error") \ + V(password_string, "password") \ + V(path_string, "path") \ + V(pending_handle_string, "pendingHandle") \ + V(pid_string, "pid") \ + V(pipe_string, "pipe") \ + V(pipe_target_string, "pipeTarget") \ + V(pipe_source_string, "pipeSource") \ + V(port_string, "port") \ + V(port1_string, "port1") \ + V(port2_string, "port2") \ + V(preference_string, "preference") \ + V(priority_string, "priority") \ + V(process_string, "process") \ + V(promise_string, "promise") \ + V(pubkey_string, "pubkey") \ + V(query_string, "query") \ + V(raw_string, "raw") \ + V(read_host_object_string, "_readHostObject") \ + V(readable_string, "readable") \ + V(reason_string, "reason") \ + V(refresh_string, "refresh") \ + V(regexp_string, "regexp") \ + V(rename_string, "rename") \ + V(replacement_string, "replacement") \ + V(require_string, "require") \ + V(retry_string, "retry") \ + V(scheme_string, "scheme") \ + V(serial_string, "serial") \ + V(scopeid_string, "scopeid") \ + V(serial_number_string, "serialNumber") \ + V(service_string, "service") \ + V(servername_string, "servername") \ + V(session_id_string, "sessionId") \ + V(shell_string, "shell") \ + V(signal_string, "signal") \ + V(sink_string, "sink") \ + V(size_string, "size") \ + V(sni_context_err_string, "Invalid SNI context") \ + V(sni_context_string, "sni_context") \ + V(source_string, "source") \ + V(stack_string, "stack") \ + V(start_time_string, "startTime") \ + V(status_string, "status") \ + V(stdio_string, "stdio") \ + V(subject_string, "subject") \ + V(subjectaltname_string, "subjectaltname") \ + V(syscall_string, "syscall") \ + V(thread_id_string, "threadId") \ + V(ticketkeycallback_string, "onticketkeycallback") \ + V(timeout_string, "timeout") \ + V(tls_ticket_string, "tlsTicket") \ + V(ttl_string, "ttl") \ + V(type_string, "type") \ + V(uid_string, "uid") \ + V(unknown_string, "<unknown>") \ + V(url_string, "url") \ + V(username_string, "username") \ + V(valid_from_string, "valid_from") \ + V(valid_to_string, "valid_to") \ + V(value_string, "value") \ + V(verify_error_string, "verifyError") \ + V(version_string, "version") \ + V(weight_string, "weight") \ + V(windows_hide_string, "windowsHide") \ + V(windows_verbatim_arguments_string, "windowsVerbatimArguments") \ + V(wrap_string, "wrap") \ + V(writable_string, "writable") \ + V(write_host_object_string, "_writeHostObject") \ + V(write_queue_size_string, "writeQueueSize") \ + V(x_forwarded_string, "x-forwarded-for") \ V(zero_return_string, "ZERO_RETURN") -#define ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V) \ - V(as_external, v8::External) \ - V(async_hooks_after_function, v8::Function) \ - V(async_hooks_before_function, v8::Function) \ - V(async_hooks_binding, v8::Object) \ - V(async_hooks_destroy_function, v8::Function) \ - V(async_hooks_init_function, v8::Function) \ - V(async_hooks_promise_resolve_function, v8::Function) \ - V(async_wrap_object_ctor_template, v8::FunctionTemplate) \ - V(async_wrap_ctor_template, v8::FunctionTemplate) \ - V(buffer_prototype_object, v8::Object) \ - V(context, v8::Context) \ - V(domain_callback, v8::Function) \ - V(domexception_function, v8::Function) \ - V(fdclose_constructor_template, v8::ObjectTemplate) \ - V(fd_constructor_template, v8::ObjectTemplate) \ - V(filehandlereadwrap_template, v8::ObjectTemplate) \ - V(fsreqpromise_constructor_template, v8::ObjectTemplate) \ - V(fs_use_promises_symbol, v8::Symbol) \ - V(handle_wrap_ctor_template, v8::FunctionTemplate) \ - V(host_import_module_dynamically_callback, v8::Function) \ - V(host_initialize_import_meta_object_callback, v8::Function) \ - V(http2ping_constructor_template, v8::ObjectTemplate) \ - V(http2settings_constructor_template, v8::ObjectTemplate) \ - V(http2stream_constructor_template, v8::ObjectTemplate) \ - V(immediate_callback_function, v8::Function) \ - V(inspector_console_api_object, v8::Object) \ - V(libuv_stream_wrap_ctor_template, v8::FunctionTemplate) \ - V(message_port, v8::Object) \ - V(message_port_constructor_template, v8::FunctionTemplate) \ - V(pipe_constructor_template, v8::FunctionTemplate) \ - V(performance_entry_callback, v8::Function) \ - V(performance_entry_template, v8::Function) \ - V(process_object, v8::Object) \ - V(promise_handler_function, v8::Function) \ - V(promise_wrap_template, v8::ObjectTemplate) \ - V(sab_lifetimepartner_constructor_template, v8::FunctionTemplate) \ - V(script_context_constructor_template, v8::FunctionTemplate) \ - V(script_data_constructor_function, v8::Function) \ - V(secure_context_constructor_template, v8::FunctionTemplate) \ - V(shutdown_wrap_template, v8::ObjectTemplate) \ - V(tcp_constructor_template, v8::FunctionTemplate) \ - V(tick_callback_function, v8::Function) \ - V(timers_callback_function, v8::Function) \ - V(tls_wrap_constructor_function, v8::Function) \ - V(trace_category_state_function, v8::Function) \ - V(tty_constructor_template, v8::FunctionTemplate) \ - V(udp_constructor_function, v8::Function) \ - V(url_constructor_function, v8::Function) \ +#define ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V) \ + V(as_external, v8::External) \ + V(async_hooks_after_function, v8::Function) \ + V(async_hooks_before_function, v8::Function) \ + V(async_hooks_binding, v8::Object) \ + V(async_hooks_destroy_function, v8::Function) \ + V(async_hooks_init_function, v8::Function) \ + V(async_hooks_promise_resolve_function, v8::Function) \ + V(async_wrap_object_ctor_template, v8::FunctionTemplate) \ + V(async_wrap_ctor_template, v8::FunctionTemplate) \ + V(buffer_prototype_object, v8::Object) \ + V(context, v8::Context) \ + V(domain_callback, v8::Function) \ + V(domexception_function, v8::Function) \ + V(fdclose_constructor_template, v8::ObjectTemplate) \ + V(fd_constructor_template, v8::ObjectTemplate) \ + V(filehandlereadwrap_template, v8::ObjectTemplate) \ + V(fsreqpromise_constructor_template, v8::ObjectTemplate) \ + V(fs_use_promises_symbol, v8::Symbol) \ + V(handle_wrap_ctor_template, v8::FunctionTemplate) \ + V(host_import_module_dynamically_callback, v8::Function) \ + V(host_initialize_import_meta_object_callback, v8::Function) \ + V(http2ping_constructor_template, v8::ObjectTemplate) \ + V(http2settings_constructor_template, v8::ObjectTemplate) \ + V(http2stream_constructor_template, v8::ObjectTemplate) \ + V(immediate_callback_function, v8::Function) \ + V(inspector_console_api_object, v8::Object) \ + V(libuv_stream_wrap_ctor_template, v8::FunctionTemplate) \ + V(message_port, v8::Object) \ + V(message_port_constructor_template, v8::FunctionTemplate) \ + V(native_modules_code_cache, v8::Object) \ + V(native_modules_code_cache_hash, v8::Object) \ + V(native_modules_source, v8::Object) \ + V(native_modules_source_hash, v8::Object) \ + V(native_modules_with_cache, v8::Set) \ + V(native_modules_without_cache, v8::Set) \ + V(pipe_constructor_template, v8::FunctionTemplate) \ + V(performance_entry_callback, v8::Function) \ + V(performance_entry_template, v8::Function) \ + V(process_object, v8::Object) \ + V(promise_handler_function, v8::Function) \ + V(promise_wrap_template, v8::ObjectTemplate) \ + V(sab_lifetimepartner_constructor_template, v8::FunctionTemplate) \ + V(script_context_constructor_template, v8::FunctionTemplate) \ + V(script_data_constructor_function, v8::Function) \ + V(secure_context_constructor_template, v8::FunctionTemplate) \ + V(shutdown_wrap_template, v8::ObjectTemplate) \ + V(tcp_constructor_template, v8::FunctionTemplate) \ + V(tick_callback_function, v8::Function) \ + V(timers_callback_function, v8::Function) \ + V(tls_wrap_constructor_function, v8::Function) \ + V(trace_category_state_function, v8::Function) \ + V(tty_constructor_template, v8::FunctionTemplate) \ + V(udp_constructor_function, v8::Function) \ + V(url_constructor_function, v8::Function) \ V(write_wrap_template, v8::ObjectTemplate) class Environment; diff --git a/src/node.cc b/src/node.cc index bfc50983911f2d..726a174fa60666 100644 --- a/src/node.cc +++ b/src/node.cc @@ -25,10 +25,9 @@ #include "node_errors.h" #include "node_internals.h" #include "node_javascript.h" -#include "node_code_cache.h" +#include "node_native_module.h" +#include "node_perf.h" #include "node_platform.h" -#include "node_version.h" -#include "node_internals.h" #include "node_revert.h" #include "node_version.h" #include "tracing/traced_value.h" @@ -131,6 +130,7 @@ typedef int mode_t; namespace node { +using native_module::NativeModule; using options_parser::kAllowedInEnvironment; using options_parser::kDisallowedInEnvironment; using v8::Array; @@ -775,6 +775,7 @@ static MaybeLocal<Value> ExecuteString(Environment* env, try_catch.SetVerbose(false); ScriptOrigin origin(filename); + MaybeLocal<Script> script = Script::Compile(env->context(), source, &origin); if (script.IsEmpty()) { @@ -1222,19 +1223,7 @@ static void GetInternalBinding(const FunctionCallbackInfo<Value>& args) { DefineConstants(env->isolate(), exports); } else if (!strcmp(*module_v, "natives")) { exports = Object::New(env->isolate()); - DefineJavaScript(env, exports); - } else if (!strcmp(*module_v, "code_cache")) { - // internalBinding('code_cache') - exports = Object::New(env->isolate()); - DefineCodeCache(env, exports); - } else if (!strcmp(*module_v, "code_cache_hash")) { - // internalBinding('code_cache_hash') - exports = Object::New(env->isolate()); - DefineCodeCacheHash(env, exports); - } else if (!strcmp(*module_v, "natives_hash")) { - // internalBinding('natives_hash') - exports = Object::New(env->isolate()); - DefineJavaScriptHash(env, exports); + NativeModule::GetNatives(env, exports); } else { return ThrowIfNoSuchModule(env, *module_v); } @@ -1772,6 +1761,8 @@ void LoadEnvironment(Environment* env) { // lib/internal/bootstrap/node.js, each included as a static C string // defined in node_javascript.h, generated in node_javascript.cc by // node_js2c. + + // TODO(joyeecheung): use NativeModule::Compile Local<String> loaders_name = FIXED_ONE_BYTE_STRING(env->isolate(), "internal/bootstrap/loaders.js"); MaybeLocal<Function> loaders_bootstrapper = @@ -1831,6 +1822,8 @@ void LoadEnvironment(Environment* env) { env->options()->debug_options->break_node_first_line) }; + NativeModule::LoadBindings(env); + // Bootstrap internal loaders Local<Value> bootstrapped_loaders; if (!ExecuteBootstrapper(env, loaders_bootstrapper.ToLocalChecked(), @@ -2484,6 +2477,8 @@ Local<Context> NewContext(Isolate* isolate, { // Run lib/internal/per_context.js Context::Scope context_scope(context); + + // TODO(joyeecheung): use NativeModule::Compile Local<String> per_context = NodePerContextSource(isolate); ScriptCompiler::Source per_context_src(per_context, nullptr); Local<Script> s = ScriptCompiler::Compile( diff --git a/src/node_code_cache.h b/src/node_code_cache.h index db8012286172a9..8054279d55ba96 100644 --- a/src/node_code_cache.h +++ b/src/node_code_cache.h @@ -7,6 +7,8 @@ namespace node { +extern const bool native_module_has_code_cache; + void DefineCodeCache(Environment* env, v8::Local<v8::Object> target); void DefineCodeCacheHash(Environment* env, v8::Local<v8::Object> target); diff --git a/src/node_code_cache_stub.cc b/src/node_code_cache_stub.cc index 35780e13f026b0..2fb86c5bf769f5 100644 --- a/src/node_code_cache_stub.cc +++ b/src/node_code_cache_stub.cc @@ -5,6 +5,9 @@ // The stub here is used when configure is run without `--code-cache-path` namespace node { + +const bool native_module_has_code_cache = false; + void DefineCodeCache(Environment* env, v8::Local<v8::Object> target) { // When we do not produce code cache for builtin modules, // `internalBinding('code_cache')` returns an empty object diff --git a/src/node_internals.h b/src/node_internals.h index b026a968430808..b6cd628086dcb2 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -118,6 +118,7 @@ struct sockaddr; V(js_stream) \ V(messaging) \ V(module_wrap) \ + V(native_module) \ V(options) \ V(os) \ V(performance) \ diff --git a/src/node_native_module.cc b/src/node_native_module.cc new file mode 100644 index 00000000000000..ea7fe9189a6857 --- /dev/null +++ b/src/node_native_module.cc @@ -0,0 +1,326 @@ +#include "node_native_module.h" +#include "node_code_cache.h" +#include "node_errors.h" +#include "node_javascript.h" + +namespace node { +namespace native_module { + +using v8::Array; +using v8::ArrayBuffer; +using v8::Context; +using v8::EscapableHandleScope; +using v8::Function; +using v8::FunctionCallbackInfo; +using v8::HandleScope; +using v8::Integer; +using v8::IntegrityLevel; +using v8::Isolate; +using v8::Local; +using v8::Maybe; +using v8::MaybeLocal; +using v8::Object; +using v8::Script; +using v8::ScriptCompiler; +using v8::ScriptOrigin; +using v8::Set; +using v8::String; +using v8::TryCatch; +using v8::Uint8Array; +using v8::Value; + +void NativeModule::GetNatives(Environment* env, Local<Object> exports) { + DefineJavaScript(env, exports); +} + +void NativeModule::LoadBindings(Environment* env) { + // TODO(joyeecheung): put the static values into a + // std::map<std::string, const uint8_t*> instead of a v8::Object, + // because here they are only looked up from the C++ side + // (except in process.binding('natives') which we don't use) + // so there is little value to put them in a v8::Object upfront. + // Moreover, a std::map lookup should be faster than a lookup on + // an V8 Object in dictionary mode. + Isolate* isolate = env->isolate(); + Local<Context> context = env->context(); + Local<Value> null = Null(isolate); + + Local<Object> native_modules_source = Object::New(isolate); + CHECK(native_modules_source->SetPrototype(context, null).FromJust()); + DefineJavaScript(env, native_modules_source); + native_modules_source->SetIntegrityLevel(context, IntegrityLevel::kFrozen) + .FromJust(); + env->set_native_modules_source(native_modules_source); + + Local<Object> native_modules_source_hash = Object::New(isolate); + CHECK(native_modules_source_hash->SetPrototype(context, null).FromJust()); + DefineJavaScriptHash(env, native_modules_source_hash); + native_modules_source_hash + ->SetIntegrityLevel(context, IntegrityLevel::kFrozen) + .FromJust(); + env->set_native_modules_source_hash(native_modules_source_hash); + + Local<Object> native_modules_code_cache = Object::New(isolate); + CHECK(native_modules_code_cache->SetPrototype(context, null).FromJust()); + DefineCodeCache(env, native_modules_code_cache); + native_modules_code_cache->SetIntegrityLevel(context, IntegrityLevel::kFrozen) + .FromJust(); + env->set_native_modules_code_cache(native_modules_code_cache); + + Local<Object> native_modules_code_cache_hash = Object::New(isolate); + CHECK(native_modules_code_cache_hash->SetPrototype(context, null).FromJust()); + DefineCodeCacheHash(env, native_modules_code_cache_hash); + native_modules_code_cache_hash + ->SetIntegrityLevel(context, IntegrityLevel::kFrozen) + .FromJust(); + env->set_native_modules_code_cache_hash(native_modules_code_cache_hash); + + env->set_native_modules_with_cache(Set::New(isolate)); + env->set_native_modules_without_cache(Set::New(isolate)); +} + +void NativeModule::CompileCodeCache(const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + CHECK(args[0]->IsString()); + Local<String> id = args[0].As<String>(); + + Local<Value> result = CompileAsModule(env, id, true); + if (!result.IsEmpty()) { + args.GetReturnValue().Set(result); + } +} + +void NativeModule::CompileFunction(const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + + CHECK(args[0]->IsString()); + Local<String> id = args[0].As<String>(); + + Local<Value> result = CompileAsModule(env, id, false); + if (!result.IsEmpty()) { + args.GetReturnValue().Set(result); + } +} + +Local<Value> NativeModule::CompileAsModule(Environment* env, + Local<String> id, + bool produce_code_cache) { + Local<String> parameters[] = {env->exports_string(), + env->require_string(), + env->module_string(), + env->process_string(), + env->internal_binding_string()}; + + return Compile( + env, id, parameters, arraysize(parameters), produce_code_cache); +} + +// Currently V8 only checks that the length of the source code is the +// same as the code used to generate the hash, so we add an additional +// check here: +// 1. During compile time, when generating node_javascript.cc and +// node_code_cache.cc, we compute and include the hash of the +// JavaScript source in both. +// 2. At runtime, we check that the hash of the code being compiled +// and the hash of the code used to generate the cache +// (without the parameters) is the same. +// This is based on the assumptions: +// 1. `code_cache_hash` must be in sync with `code_cache` +// (both defined in node_code_cache.cc) +// 2. `source_hash` must be in sync with `source` +// (both defined in node_javascript.cc) +// 3. If `source_hash` is in sync with `code_cache_hash`, +// then the source code used to generate `code_cache` +// should be in sync with the source code in `source` +// The only variable left, then, are the parameters passed to the +// CompileFunctionInContext. If the parameters used generate the cache +// is different from the one used to compile modules at run time, then +// there could be false postivies, but that should be rare and should fail +// early in the bootstrap process so it should be easy to detect and fix. + +// Returns nullptr if there is no code cache corresponding to the id +ScriptCompiler::CachedData* GetCachedData(Environment* env, Local<String> id) { + HandleScope scope(env->isolate()); + Local<Context> context = env->context(); + + Local<Value> result = + env->native_modules_code_cache()->Get(context, id).ToLocalChecked(); + // This could be false if the module cannot be cached somehow. + // See lib/internal/bootstrap/cache.js on the modules that cannot be cached + if (result->IsUndefined()) { + return nullptr; + } + + CHECK(result->IsUint8Array()); + Local<Uint8Array> code_cache = result.As<Uint8Array>(); + + result = + env->native_modules_code_cache_hash()->Get(context, id).ToLocalChecked(); + CHECK(result->IsString()); + Local<String> code_cache_hash = result.As<String>(); + + result = + env->native_modules_source_hash()->Get(context, id).ToLocalChecked(); + CHECK(result->IsString()); + Local<String> source_hash = result.As<String>(); + + // It may fail when any of the inputs of the `node_js2c` target in + // node.gyp is modified but the tools/generate_code_cache.js + // is not re run. + // FIXME(joyeecheung): Figure out how to resolve the dependency issue. + // When the code cache was introduced we were at a point where refactoring + // node.gyp may not be worth the effort. + CHECK(code_cache_hash->StrictEquals(source_hash)); + + ArrayBuffer::Contents contents = code_cache->Buffer()->GetContents(); + uint8_t* data = static_cast<uint8_t*>(contents.Data()); + return new ScriptCompiler::CachedData(data + code_cache->ByteOffset(), + code_cache->ByteLength()); +} + +// Returns Local<Function> of the compiled module if produce_code_cache +// is false (we are only compiling the function). +// Otherwise return a Local<Object> containing the cache. +Local<Value> NativeModule::Compile(Environment* env, + Local<String> id, + Local<String> parameters[], + size_t parameters_count, + bool produce_code_cache) { + EscapableHandleScope scope(env->isolate()); + Local<Context> context = env->context(); + Isolate* isolate = env->isolate(); + + Local<Value> result = + env->native_modules_source()->Get(context, id).ToLocalChecked(); + CHECK(result->IsString()); + Local<String> source = result.As<String>(); + + Local<String> filename = + String::Concat(isolate, id, FIXED_ONE_BYTE_STRING(isolate, ".js")); + Local<Integer> line_offset = Integer::New(isolate, 0); + Local<Integer> column_offset = Integer::New(isolate, 0); + ScriptOrigin origin(filename, line_offset, column_offset); + + bool use_cache = false; + ScriptCompiler::CachedData* cached_data = nullptr; + + // 1. We won't even check the existence of the cache if the binary is not + // built with them. + // 2. If we are generating code cache for tools/general_code_cache.js, we + // are not going to use any cache ourselves. + if (native_module_has_code_cache && !produce_code_cache) { + cached_data = GetCachedData(env, id); + if (cached_data != nullptr) { + use_cache = true; + } + } + + ScriptCompiler::Source script_source(source, origin, cached_data); + + ScriptCompiler::CompileOptions options; + if (produce_code_cache) { + options = ScriptCompiler::kEagerCompile; + } else if (use_cache) { + options = ScriptCompiler::kConsumeCodeCache; + } else { + options = ScriptCompiler::kNoCompileOptions; + } + + MaybeLocal<Function> maybe_fun = + ScriptCompiler::CompileFunctionInContext(context, + &script_source, + parameters_count, + parameters, + 0, + nullptr, + options); + + TryCatch try_catch(isolate); + Local<Function> fun; + // This could fail when there are early errors in the native modules, + // e.g. the syntax errors + if (maybe_fun.IsEmpty() || !maybe_fun.ToLocal(&fun)) { + DecorateErrorStack(env, try_catch); + try_catch.ReThrow(); + return scope.Escape(Local<Value>()); + } + + if (use_cache) { + // If the cache is rejected, something must be wrong with the build + // and we should just crash. + CHECK(!script_source.GetCachedData()->rejected); + if (env->native_modules_with_cache()->Add(context, id).IsEmpty()) { + return scope.Escape(Local<Value>()); + } + } else { + if (env->native_modules_without_cache()->Add(context, id).IsEmpty()) { + return scope.Escape(Local<Value>()); + } + } + + if (produce_code_cache) { + std::unique_ptr<ScriptCompiler::CachedData> cached_data( + ScriptCompiler::CreateCodeCacheForFunction(fun)); + CHECK_NE(cached_data, nullptr); + char* data = + reinterpret_cast<char*>(const_cast<uint8_t*>(cached_data->data)); + + // Since we have no API to create a buffer from a new'ed pointer, + // we will need to copy it - but this code path is only run by the + // tooling that generates the code cache to be bundled in the binary + // so it should be fine. + Local<Object> buf = + Buffer::Copy(env, data, cached_data->length).ToLocalChecked(); + return scope.Escape(buf); + } else { + return scope.Escape(fun); + } +} + +void Initialize(Local<Object> target, + Local<Value> unused, + Local<Context> context) { + Environment* env = Environment::GetCurrent(context); + + target + ->Set(context, + FIXED_ONE_BYTE_STRING(env->isolate(), "source"), + env->native_modules_source()) + .FromJust(); + target + ->Set(context, + FIXED_ONE_BYTE_STRING(env->isolate(), "sourceHash"), + env->native_modules_source_hash()) + .FromJust(); + target + ->Set(context, + FIXED_ONE_BYTE_STRING(env->isolate(), "codeCache"), + env->native_modules_code_cache()) + .FromJust(); + target + ->Set(context, + FIXED_ONE_BYTE_STRING(env->isolate(), "codeCacheHash"), + env->native_modules_code_cache_hash()) + .FromJust(); + target + ->Set(context, + FIXED_ONE_BYTE_STRING(env->isolate(), "compiledWithCache"), + env->native_modules_with_cache()) + .FromJust(); + target + ->Set(context, + FIXED_ONE_BYTE_STRING(env->isolate(), "compiledWithoutCache"), + env->native_modules_without_cache()) + .FromJust(); + + env->SetMethod(target, "compileFunction", NativeModule::CompileFunction); + env->SetMethod(target, "compileCodeCache", NativeModule::CompileCodeCache); + // internalBinding('native_module') should be frozen + target->SetIntegrityLevel(context, IntegrityLevel::kFrozen).FromJust(); +} + +} // namespace native_module +} // namespace node + +NODE_MODULE_CONTEXT_AWARE_INTERNAL(native_module, + node::native_module::Initialize) diff --git a/src/node_native_module.h b/src/node_native_module.h new file mode 100644 index 00000000000000..c4ffbfb0cdf94d --- /dev/null +++ b/src/node_native_module.h @@ -0,0 +1,41 @@ +#ifndef SRC_NODE_NATIVE_MODULE_H_ +#define SRC_NODE_NATIVE_MODULE_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "node_internals.h" + +namespace node { +namespace native_module { + +// The native (C++) side of the native module compilation. + +class NativeModule { + public: + // For legacy process.binding('natives') which is mutable + static void GetNatives(Environment* env, v8::Local<v8::Object> exports); + // Loads the static JavaScript source code and the cache into Environment + static void LoadBindings(Environment* env); + // Compile code cache for a specific native module + static void CompileCodeCache(const v8::FunctionCallbackInfo<v8::Value>& args); + // Compile a specific native module as a function + static void CompileFunction(const v8::FunctionCallbackInfo<v8::Value>& args); + + private: + static v8::Local<v8::Value> CompileAsModule(Environment* env, + v8::Local<v8::String> id, + bool produce_code_cache); + // TODO(joyeecheung): make this public and reuse it to compile bootstrappers + static v8::Local<v8::Value> Compile(Environment* env, + v8::Local<v8::String> id, + v8::Local<v8::String> parameters[], + size_t parameters_count, + bool produce_code_cache); +}; + +} // namespace native_module +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_NODE_NATIVE_MODULE_H_ diff --git a/test/code-cache/test-code-cache.js b/test/code-cache/test-code-cache.js index c1bfa80f4342f3..994facff1996c2 100644 --- a/test/code-cache/test-code-cache.js +++ b/test/code-cache/test-code-cache.js @@ -7,17 +7,28 @@ require('../common'); const assert = require('assert'); -const { - types: { - isUint8Array - } -} = require('util'); const { cachableBuiltins, - codeCache, - compiledWithCache, - compiledWithoutCache + cannotUseCache } = require('internal/bootstrap/cache'); +const { + isMainThread +} = require('worker_threads'); + +const { + internalBinding +} = require('internal/test/binding'); +const { + compiledWithoutCache, + compiledWithCache +} = internalBinding('native_module'); + +for (const key of cachableBuiltins) { + if (!isMainThread && key === 'trace_events') { + continue; // Cannot load trace_events in workers + } + require(key); +} const loadedModules = process.moduleLoadList .filter((m) => m.startsWith('NativeModule')) @@ -26,29 +37,23 @@ const loadedModules = process.moduleLoadList // The binary is not configured with code cache, verifies that the builtins // are all compiled without cache and we are doing the bookkeeping right. if (process.config.variables.node_code_cache_path === undefined) { - assert.deepStrictEqual(compiledWithCache, []); - assert.notStrictEqual(compiledWithoutCache.length, 0); - - for (const key of loadedModules) { - assert(compiledWithoutCache.includes(key), - `"${key}" should not have been compiled with code cache`); - } - + console.log('The binary is not configured with code cache'); + assert.deepStrictEqual(compiledWithCache, new Set()); + assert.deepStrictEqual(compiledWithoutCache, new Set(loadedModules)); } else { - // The binary is configured with code cache. + console.log('The binary is configured with code cache'); assert.strictEqual( typeof process.config.variables.node_code_cache_path, 'string' ); - assert.deepStrictEqual(compiledWithoutCache, []); for (const key of loadedModules) { - assert(compiledWithCache.includes(key), - `"${key}" should've been compiled with code cache`); - } - - for (const key of cachableBuiltins) { - assert(isUint8Array(codeCache[key]) && codeCache[key].length > 0, - `Code cache for "${key}" should've been generated`); + if (cannotUseCache.includes(key)) { + assert(compiledWithoutCache.has(key), + `"${key}" should've been compiled without code cache`); + } else { + assert(compiledWithCache.has(key), + `"${key}" should've been compiled with code cache`); + } } } diff --git a/tools/generate_code_cache.js b/tools/generate_code_cache.js index 35411bc5af182a..9609d77c2afca9 100644 --- a/tools/generate_code_cache.js +++ b/tools/generate_code_cache.js @@ -8,8 +8,8 @@ // of `configure`. const { - getCodeCache, getSource, + getCodeCache, cachableBuiltins } = require('internal/bootstrap/cache'); @@ -118,6 +118,8 @@ namespace node { ${cacheDefinitions.join('\n\n')} +const bool native_module_has_code_cache = true; + // The target here will be returned as \`internalBinding('code_cache')\` void DefineCodeCache(Environment* env, v8::Local<v8::Object> target) { v8::Isolate* isolate = env->isolate();