diff --git a/.github/workflows/ci-test-valgrind/suppressions.txt b/.github/workflows/ci-test-valgrind/suppressions.txt index e37812556..b0e154921 100644 --- a/.github/workflows/ci-test-valgrind/suppressions.txt +++ b/.github/workflows/ci-test-valgrind/suppressions.txt @@ -43,3 +43,19 @@ fun:Builtins_CEntry_Return1_ArgvOnStack_NoBuiltinExit fun:Builtins_CompileLazy } +{ + + Memcheck:Value8 + fun:_ZN2v88internal18MacroAssemblerBase12BuiltinEntryENS0_7BuiltinE + fun:_ZN2v88internal14MacroAssembler11CallBuiltinENS0_7BuiltinE + fun:_ZZN2v88internal6maglev15MaglevAssembler32CheckAndEmitDeferredWriteBarrierILNS2_9StoreModeE1EEEvNS0_8RegisterENSt4__Cr11conditionalIXeqT_LS4_0EEiS5_E4typeES5_NS1_16RegisterSnapshotENS2_17ValueIsCompressedENS2_13ValueCanBeSmiEENKUlPS2_NS1_12ZoneLabelRefES5_S5_S5_SA_SB_E_clESD_SE_S5_S5_S5_SA_SB_ + fun:_ZZN2v88internal6maglev15MaglevAssembler32CheckAndEmitDeferredWriteBarrierILNS2_9StoreModeE1EEEvNS0_8RegisterENSt4__Cr11conditionalIXeqT_LS4_0EEiS5_E4typeES5_NS1_16RegisterSnapshotENS2_17ValueIsCompressedENS2_13ValueCanBeSmiEENUlPS2_NS1_12ZoneLabelRefES5_S5_S5_SA_SB_E_8__invokeESD_SE_S5_S5_S5_SA_SB_ + fun:_ZN2v88internal6maglev6detail20DeferredCodeInfoImplIZNS1_15MaglevAssembler32CheckAndEmitDeferredWriteBarrierILNS4_9StoreModeE1EEEvNS0_8RegisterENSt4__Cr11conditionalIXeqT_LS6_0EEiS7_E4typeES7_NS1_16RegisterSnapshotENS4_17ValueIsCompressedENS4_13ValueCanBeSmiEEUlPS4_NS1_12ZoneLabelRefES7_S7_S7_SC_SD_E_E8GenerateESF_ + fun:_ZN2v88internal6maglev19MaglevCodeGenerator8EmitCodeEv + fun:_ZN2v88internal6maglev19MaglevCodeGenerator8AssembleEv + fun:_ZN2v88internal6maglev14MaglevCompiler7CompileEPNS0_12LocalIsolateEPNS1_21MaglevCompilationInfoE + fun:_ZN2v88internal6maglev20MaglevCompilationJob14ExecuteJobImplEPNS0_16RuntimeCallStatsEPNS0_12LocalIsolateE + fun:_ZN2v88internal12_GLOBAL__N_113CompileMaglevEPNS0_7IsolateENS0_6HandleINS0_10JSFunctionEEENS0_15ConcurrencyModeENS0_14BytecodeOffsetENS1_21CompileResultBehaviorE + fun:_ZN2v88internal12_GLOBAL__N_121GetOrCompileOptimizedEPNS0_7IsolateENS0_6HandleINS0_10JSFunctionEEENS0_15ConcurrencyModeENS0_8CodeKindENS0_14BytecodeOffsetENS1_21CompileResultBehaviorE + fun:_ZN2v88internal8Compiler16CompileOptimizedEPNS0_7IsolateENS0_6HandleINS0_10JSFunctionEEENS0_15ConcurrencyModeENS0_8CodeKindE +} diff --git a/Cargo.lock b/Cargo.lock index a5f509190..40a0fe92f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -247,6 +247,14 @@ dependencies = [ "generic-array", ] +[[package]] +name = "build-your-own-js-snapshot" +version = "0.1.0" +dependencies = [ + "deno_core", + "tokio", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -555,7 +563,7 @@ dependencies = [ [[package]] name = "deno_core" -version = "0.306.0" +version = "0.311.0" dependencies = [ "anyhow", "bencher", @@ -610,6 +618,7 @@ dependencies = [ "prettyplease", "testing_macros", "tokio", + "url", ] [[package]] @@ -625,7 +634,7 @@ dependencies = [ [[package]] name = "deno_ops" -version = "0.182.0" +version = "0.187.0" dependencies = [ "pretty_assertions", "prettyplease", @@ -1842,7 +1851,7 @@ dependencies = [ [[package]] name = "serde_v8" -version = "0.215.0" +version = "0.220.0" dependencies = [ "bencher", "num-bigint", @@ -2729,9 +2738,9 @@ dependencies = [ [[package]] name = "v8" -version = "0.104.0" +version = "0.106.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "834593934c4b3693706bcbd24d4e2accf02a318a62efd2a4e5e33b5452074882" +checksum = "a381badc47c6f15acb5fe0b5b40234162349ed9d4e4fd7c83a7f5547c0fc69c5" dependencies = [ "bindgen", "bitflags", diff --git a/Cargo.toml b/Cargo.toml index 7802696ac..7100c7b41 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ resolver = "2" members = [ "core", + "core/examples/snapshot", "dcore", "ops", "ops/compile_test_runner", @@ -19,12 +20,12 @@ repository = "https://github.com/denoland/deno_core" [workspace.dependencies] # Local dependencies -deno_core = { version = "0.306.0", path = "./core" } -deno_ops = { version = "0.182.0", path = "./ops" } -serde_v8 = { version = "0.215.0", path = "./serde_v8" } +deno_core = { version = "0.311.0", path = "./core" } +deno_ops = { version = "0.187.0", path = "./ops" } +serde_v8 = { version = "0.220.0", path = "./serde_v8" } deno_core_testing = { path = "./testing" } -v8 = { version = "0.104.0", default-features = false } +v8 = { version = "0.106.0", default-features = false } deno_ast = { version = "=0.40.0", features = ["transpiling"] } deno_unsync = "0.4.0" deno_core_icudata = "0.0.73" diff --git a/core/01_core.js b/core/01_core.js index ea79449e1..5c1034359 100644 --- a/core/01_core.js +++ b/core/01_core.js @@ -260,24 +260,6 @@ return ObjectFromEntries(op_resources()); } - function metrics() { - // TODO(mmastrac): we should replace this with a newer API - return { - opsDispatched: 0, - opsDispatchedSync: 0, - opsDispatchedAsync: 0, - opsDispatchedAsyncUnref: 0, - opsCompleted: 0, - opsCompletedSync: 0, - opsCompletedAsync: 0, - opsCompletedAsyncUnref: 0, - bytesSentControl: 0, - bytesSentData: 0, - bytesReceived: 0, - ops: {}, - }; - } - let reportExceptionCallback = (error) => { op_dispatch_exception(error, false); }; @@ -313,7 +295,7 @@ }); } - // Some "extensions" rely on "BadResource", "Interrupted", "PermissionDenied" + // Some "extensions" rely on "BadResource", "Interrupted", "NotCapable" // errors in the JS code (eg. "deno_net") so they are provided in "Deno.core" // but later reexported on "Deno.errors" class BadResource extends Error { @@ -332,17 +314,17 @@ } const InterruptedPrototype = Interrupted.prototype; - class PermissionDenied extends Error { + class NotCapable extends Error { constructor(msg) { super(msg); - this.name = "PermissionDenied"; + this.name = "NotCapable"; } } - const PermissionDeniedPrototype = PermissionDenied.prototype; + const NotCapablePrototype = NotCapable.prototype; registerErrorClass("BadResource", BadResource); registerErrorClass("Interrupted", Interrupted); - registerErrorClass("PermissionDenied", PermissionDenied); + registerErrorClass("NotCapable", NotCapable); const promiseHooks = [ [], // init @@ -650,14 +632,13 @@ const core = ObjectAssign(globalThis.Deno.core, { internalRidSymbol: Symbol("Deno.internal.rid"), resources, - metrics, eventLoopTick, BadResource, BadResourcePrototype, Interrupted, InterruptedPrototype, - PermissionDenied, - PermissionDeniedPrototype, + NotCapable, + NotCapablePrototype, refOpPromise, unrefOpPromise, setReportExceptionCallback, diff --git a/core/Cargo.toml b/core/Cargo.toml index 4fd543a36..f74271e72 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "deno_core" -version = "0.306.0" +version = "0.311.0" authors.workspace = true edition.workspace = true license.workspace = true diff --git a/core/core.d.ts b/core/core.d.ts index c452f6b5b..bc57cb727 100644 --- a/core/core.d.ts +++ b/core/core.d.ts @@ -263,8 +263,8 @@ export namespace core { export const BadResourcePrototype: typeof BadResource.prototype; export class Interrupted extends Error {} export const InterruptedPrototype: typeof Interrupted.prototype; - export class PermissionDenied extends Error {} - export const PermissionDeniedPrototype: typeof PermissionDenied.prototype; + export class NotCapable extends Error {} + export const NotCapablePrototype: typeof NotCapable.prototype; function serialize( value: any, diff --git a/core/error.rs b/core/error.rs index 0e55e3a04..ec4ae1440 100644 --- a/core/error.rs +++ b/core/error.rs @@ -26,7 +26,7 @@ pub type AnyError = anyhow::Error; #[derive(Debug, thiserror::Error)] pub enum CoreError { #[error("Top-level await is not allowed in extensions")] - TLA(#[source] JsError), + TLA, #[error(transparent)] Js(#[from] JsError), #[error(transparent)] @@ -194,7 +194,7 @@ impl JsErrorClass for anyhow::Error { impl JsErrorClass for CoreError { fn get_class(&self) -> &'static str { match self { - CoreError::TLA(js_error) | CoreError::Js(js_error) => { + CoreError::Js(js_error) => { unreachable!("JsError's should not be reachable: {}", js_error) } CoreError::Io(err) => err.get_class(), @@ -207,7 +207,8 @@ impl JsErrorClass for CoreError { CoreError::DataError(err) => err.get_class(), CoreError::Other(err) => err.get_class(), CoreError::FutureCanceled(_) => "Interrupted", - CoreError::Parse(_) + CoreError::TLA + | CoreError::Parse(_) | CoreError::Execute(_) | CoreError::UnusedModules(_) | CoreError::NonEvaluatedModules(_) @@ -220,7 +221,7 @@ impl JsErrorClass for CoreError { fn get_message(&self) -> Cow<'static, str> { match self { - CoreError::TLA(js_error) | CoreError::Js(js_error) => { + CoreError::Js(js_error) => { unreachable!("JsError's should not be reachable: {}", js_error) } CoreError::Io(err) => err.get_message(), @@ -232,7 +233,8 @@ impl JsErrorClass for CoreError { CoreError::Module(err) => err.get_message(), CoreError::DataError(err) => err.get_message(), CoreError::Other(err) => err.get_message(), - CoreError::Parse(_) + CoreError::TLA + | CoreError::Parse(_) | CoreError::Execute(_) | CoreError::UnusedModules(_) | CoreError::NonEvaluatedModules(_) diff --git a/core/examples/snapshot/Cargo.toml b/core/examples/snapshot/Cargo.toml new file mode 100644 index 000000000..03a76ce86 --- /dev/null +++ b/core/examples/snapshot/Cargo.toml @@ -0,0 +1,19 @@ +# Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +# Note: Since Cargo "example" targets don't discover/use `build.rs` files, this +# example is a member of the root `deno_core` workspace. That means it will +# compile with `cargo build` in the root, so that this example/documentation +# stays in-sync with development. + +[package] +name = "build-your-own-js-snapshot" +version = "0.1.0" +edition = "2021" +build = "build.rs" + + +[dependencies] +deno_core.workspace = true +tokio.workspace = true + +[build-dependencies] +deno_core.workspace = true diff --git a/core/examples/snapshot/README.md b/core/examples/snapshot/README.md new file mode 100644 index 000000000..af7653259 --- /dev/null +++ b/core/examples/snapshot/README.md @@ -0,0 +1,32 @@ +# Snapshot Example + +This example roughly follows the blog post +[Roll Your Own JavaScript Runtime: Part 3][blog] to create a `JsRuntime` with an +embedded startup snapshot. + +That blog post and the two that preceded it were no longer accurate. By +including this example in the repository, it will continually be built, so it +will hopefully stay up-to-date. + +## Running + +The example can be run by changing to the `core/examples/snapshot` directory and +running `cargo run`. + +## Differences + +Differences from those blog posts: + +- The `create_snapshot()` API has changed in various ways. +- New API features for extensions: + - `#[op2]` ([read more][op2]) + - `extension!(...)` macro replaces `Extension::builder()` + - ESM-based extensions. + +Missing features vs. those blog posts: + +- Does not implement [TsModuleLoader], to keep this example more concise. + +[blog]: https://deno.com/blog/roll-your-own-javascript-runtime-pt3#creating-a-snapshot-in-buildrs +[op2]: https://github.com/denoland/deno_core/tree/main/ops/op2#readme +[TsModuleLoader]: https://deno.com/blog/roll-your-own-javascript-runtime-pt2#supporting-typescript diff --git a/core/examples/snapshot/build.rs b/core/examples/snapshot/build.rs new file mode 100644 index 000000000..1258f2a69 --- /dev/null +++ b/core/examples/snapshot/build.rs @@ -0,0 +1,42 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +use deno_core::{ + extension, + snapshot::{create_snapshot, CreateSnapshotOptions}, +}; +use std::path::PathBuf; +use std::{env, fs}; + +fn main() { + extension!( + runjs_extension, + // Must specify an entrypoint so that our module gets loaded while snapshotting: + esm_entry_point = "my:runtime", + esm = [ + dir "src", + "my:runtime" = "runtime.js", + ], + ); + + let options = CreateSnapshotOptions { + cargo_manifest_dir: env!("CARGO_MANIFEST_DIR"), + startup_snapshot: None, + extensions: vec![runjs_extension::init_ops_and_esm()], + with_runtime_cb: None, + skip_op_registration: false, + extension_transpiler: None, + }; + let warmup_script = None; + + let snapshot = + create_snapshot(options, warmup_script).expect("Error creating snapshot"); + + // Save the snapshot for use by our source code: + let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); + let file_path = out_dir.join("RUNJS_SNAPSHOT.bin"); + fs::write(file_path, snapshot.output).expect("Failed to write snapshot"); + + // Let cargo know that builds depend on these files: + for path in snapshot.files_loaded_during_snapshot { + println!("cargo:rerun-if-changed={}", path.display()); + } +} diff --git a/core/examples/snapshot/example.js b/core/examples/snapshot/example.js new file mode 100644 index 000000000..800fd3eba --- /dev/null +++ b/core/examples/snapshot/example.js @@ -0,0 +1,6 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +// Run this script with `cargo run`. + +import { callRust } from "my:runtime"; + +callRust("Hello from example.js"); diff --git a/core/examples/snapshot/src/main.rs b/core/examples/snapshot/src/main.rs new file mode 100644 index 000000000..83f865d0c --- /dev/null +++ b/core/examples/snapshot/src/main.rs @@ -0,0 +1,47 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +use std::{env::current_dir, rc::Rc}; + +use deno_core::{ + error::AnyError, extension, op2, FsModuleLoader, JsRuntime, + PollEventLoopOptions, RuntimeOptions, +}; + +fn main() { + let runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap(); + if let Err(error) = runtime.block_on(run_js("./example.js")) { + eprintln!("error: {}", error); + } +} + +#[op2(fast)] +fn op_call_rust(#[string] value: String) { + println!("Received this value from JS: {value}"); +} + +extension!(runjs_extension, ops = [op_call_rust,],); + +async fn run_js(file_path: &str) -> Result<(), AnyError> { + let cwd = current_dir()?; + let main_module = deno_core::resolve_path(file_path, &cwd)?; + + let mut js_runtime = JsRuntime::new(RuntimeOptions { + module_loader: Some(Rc::new(FsModuleLoader)), + startup_snapshot: Some(RUNTIME_SNAPSHOT), + extensions: vec![runjs_extension::init_ops()], + ..Default::default() + }); + + let mod_id = js_runtime.load_main_es_module(&main_module).await?; + let result = js_runtime.mod_evaluate(mod_id); + js_runtime + .run_event_loop(PollEventLoopOptions::default()) + .await?; + result.await +} + +// Load the snapshot generated by build.rs: +static RUNTIME_SNAPSHOT: &[u8] = + include_bytes!(concat!(env!("OUT_DIR"), "/RUNJS_SNAPSHOT.bin")); diff --git a/core/examples/snapshot/src/runtime.js b/core/examples/snapshot/src/runtime.js new file mode 100644 index 000000000..f5179e112 --- /dev/null +++ b/core/examples/snapshot/src/runtime.js @@ -0,0 +1,10 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +/** + * This module provides the JavaScript interface atop calls to the Rust ops. + */ + +// Minimal example, just passes arguments through to Rust: +export function callRust(stringValue) { + const { op_call_rust } = Deno.core.ops; + op_call_rust(stringValue); +} diff --git a/core/examples/snapshot/tests/output.rs b/core/examples/snapshot/tests/output.rs new file mode 100644 index 000000000..0a092d063 --- /dev/null +++ b/core/examples/snapshot/tests/output.rs @@ -0,0 +1,33 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +use core::str; +use std::process::{Command, Output}; + +#[test] +fn check_output() -> Result<(), Box> { + let output = capture_output()?; + + let err = str::from_utf8(&output.stderr)?; + assert_eq!(err, ""); + assert!(output.status.success()); + + let out = str::from_utf8(&output.stdout)?; + assert_eq!(out, "Received this value from JS: Hello from example.js\n"); + + Ok(()) +} + +/// NOTE!: This is NOT the preferred pattern to follow for testing binary crates! +/// See: +/// +/// However, we want to keep this example simple, so we're not going to create separate main.rs & lib.rs, or +/// add injectable outputs. We'll just run the binary and then capture its output. +fn capture_output() -> Result { + Command::new("cargo") + .args([ + "run", + "--release", // CI runs in --release mode, so re-use its cache. + "--quiet", // only capture the command's output. + ]) + .output() +} diff --git a/core/feature_checker.rs b/core/feature_checker.rs index 3a641c9a6..2350855b4 100644 --- a/core/feature_checker.rs +++ b/core/feature_checker.rs @@ -20,10 +20,6 @@ fn warn_legacy_flag(_feature: &str, _api_name: &str) {} pub struct FeatureChecker { features: BTreeSet<&'static str>, - // TODO(bartlomieju): remove once we migrate away from `--unstable` flag - // in the CLI. - legacy_unstable: bool, - warn_on_legacy_unstable: bool, exit_cb: ExitCb, warn_cb: WarnCb, } @@ -32,8 +28,6 @@ impl Default for FeatureChecker { fn default() -> Self { Self { features: Default::default(), - legacy_unstable: false, - warn_on_legacy_unstable: false, exit_cb: Box::new(exit), warn_cb: Box::new(warn_legacy_flag), } @@ -44,8 +38,6 @@ impl Debug for FeatureChecker { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("FeatureChecker") .field("features", &self.features) - .field("legacy_unstable", &self.legacy_unstable) - .field("warn_on_legacy_unstable", &self.warn_on_legacy_unstable) .finish() } } @@ -82,34 +74,6 @@ impl FeatureChecker { (self.exit_cb)(feature, api_name); } } - - #[inline(always)] - pub fn check_or_exit_with_legacy_fallback( - &self, - feature: &str, - api_name: &str, - ) { - if !self.features.contains(feature) { - if self.legacy_unstable { - if self.warn_on_legacy_unstable { - (self.warn_cb)(feature, api_name); - } - return; - } - - (self.exit_cb)(feature, api_name); - } - } - - // TODO(bartlomieju): remove this. - pub fn enable_legacy_unstable(&mut self) { - self.legacy_unstable = true; - } - - // TODO(bartlomieju): remove this. - pub fn warn_on_legacy_unstable(&mut self) { - self.warn_on_legacy_unstable = true; - } } #[cfg(test)] @@ -144,15 +108,5 @@ mod tests { assert_eq!(EXIT_COUNT.load(Ordering::Relaxed), 0); checker.check_or_exit("fizzbuzz", "foo"); assert_eq!(EXIT_COUNT.load(Ordering::Relaxed), 1); - - checker.enable_legacy_unstable(); - checker.check_or_exit_with_legacy_fallback("fizzbuzz", "foo"); - assert_eq!(EXIT_COUNT.load(Ordering::Relaxed), 1); - assert_eq!(WARN_COUNT.load(Ordering::Relaxed), 0); - - checker.warn_on_legacy_unstable(); - checker.check_or_exit_with_legacy_fallback("fizzbuzz", "foo"); - assert_eq!(EXIT_COUNT.load(Ordering::Relaxed), 1); - assert_eq!(WARN_COUNT.load(Ordering::Relaxed), 1); } } diff --git a/core/inspector.rs b/core/inspector.rs index 6757c09a8..60f8af29e 100644 --- a/core/inspector.rs +++ b/core/inspector.rs @@ -81,6 +81,8 @@ pub struct JsRuntimeInspector { waker: Arc, deregister_tx: Option>, is_dispatching_message: RefCell, + isolate_ptr: *mut v8::Isolate, + context: v8::Global, } impl Drop for JsRuntimeInspector { @@ -138,6 +140,16 @@ impl v8::inspector::V8InspectorClientImpl for JsRuntimeInspector { assert_eq!(context_group_id, JsRuntimeInspector::CONTEXT_GROUP_ID); self.flags.borrow_mut().waiting_for_session = false; } + + fn ensure_default_context_in_group( + &mut self, + context_group_id: i32, + ) -> Option> { + assert_eq!(context_group_id, JsRuntimeInspector::CONTEXT_GROUP_ID); + let isolate: &mut v8::Isolate = unsafe { &mut *self.isolate_ptr }; + let scope = &mut unsafe { v8::CallbackScope::new(isolate) }; + Some(v8::Local::new(scope, self.context.clone())) + } } impl JsRuntimeInspector { @@ -146,6 +158,7 @@ impl JsRuntimeInspector { const CONTEXT_GROUP_ID: i32 = 1; pub fn new( + isolate_ptr: *mut v8::Isolate, scope: &mut v8::HandleScope, context: v8::Local, is_main_runtime: bool, @@ -168,6 +181,8 @@ impl JsRuntimeInspector { waker, deregister_tx: None, is_dispatching_message: Default::default(), + isolate_ptr, + context: v8::Global::new(scope, context), })); let mut self_ = self__.borrow_mut(); self_.v8_inspector = Rc::new(RefCell::new( diff --git a/core/modules/map.rs b/core/modules/map.rs index d711a19f7..2bbd2b6fa 100644 --- a/core/modules/map.rs +++ b/core/modules/map.rs @@ -33,7 +33,6 @@ use crate::ModuleSpecifier; use futures::future::FutureExt; use futures::stream::FuturesUnordered; use futures::stream::StreamFuture; -use futures::task::noop_waker_ref; use futures::task::AtomicWaker; use futures::Future; use futures::StreamExt; @@ -111,6 +110,11 @@ struct DynImportModEvaluate { module: v8::Global, } +struct DynImportState { + resolver: v8::Global, + cped: v8::Global, +} + /// A collection of JS modules. pub(crate) struct ModuleMap { // Handling of futures for loading module sources @@ -119,8 +123,7 @@ pub(crate) struct ModuleMap { pub(crate) import_meta_resolve_cb: ImportMetaResolveCallback, exception_state: Rc, - dynamic_import_map: - RefCell>>, + dynamic_import_map: RefCell>, preparing_dynamic_imports: RefCell>>>, preparing_dynamic_imports_pending: Cell, @@ -931,42 +934,64 @@ impl ModuleMap { // Initiate loading of a module graph imported using `import()`. pub(crate) fn load_dynamic_import( self: Rc, + scope: &mut v8::HandleScope, specifier: &str, referrer: &str, requested_module_type: RequestedModuleType, resolver_handle: v8::Global, - ) { + cped_handle: v8::Global, + ) -> bool { + let resolve_result = + self.resolve(specifier, referrer, ResolutionKind::DynamicImport); + + if let Ok(module_specifier) = &resolve_result { + if let Some(id) = self + .data + .borrow() + .get_id(module_specifier.as_str(), &requested_module_type) + { + let module = self + .data + .borrow() + .get_handle(id) + .map(|handle| v8::Local::new(scope, handle)) + .expect("Dyn import module info not found"); + + if module.get_status() == v8::ModuleStatus::Evaluated { + let resolver = resolver_handle.open(scope); + let module_namespace = module.get_module_namespace(); + resolver.resolve(scope, module_namespace).unwrap(); + + return false; + } + } + } + let load = RecursiveModuleLoad::dynamic_import( specifier, referrer, - requested_module_type.clone(), + requested_module_type, self.clone(), ); - self - .dynamic_import_map - .borrow_mut() - .insert(load.id, resolver_handle); + self.dynamic_import_map.borrow_mut().insert( + load.id, + DynImportState { + resolver: resolver_handle, + cped: cped_handle, + }, + ); - let resolve_result = - self.resolve(specifier, referrer, ResolutionKind::DynamicImport); let fut = match resolve_result { - Ok(module_specifier) => { - if self - .data - .borrow() - .is_registered(module_specifier.as_str(), requested_module_type) - { - async move { (load.id, Ok(load)) }.boxed_local() - } else { - async move { (load.id, load.prepare().await.map(|()| load)) } - .boxed_local() - } - } + Ok(_) => async move { (load.id, load.prepare().await.map(|()| load)) } + .boxed_local(), Err(error) => async move { (load.id, Err(error)) }.boxed_local(), }; + self.preparing_dynamic_imports.borrow_mut().push(fut); self.preparing_dynamic_imports_pending.set(true); + + true } pub(crate) fn has_pending_dynamic_imports(&self) -> bool { @@ -1031,7 +1056,7 @@ impl ModuleMap { // This will be overridden in `exception_to_err_result()`. let exception = v8::undefined(tc_scope).into(); sender - .send(exception_to_err_result(tc_scope, exception, false, false)) + .send(exception_to_err_result(tc_scope, exception, true, false)) .expect("Failed to send module evaluation error."); } else { debug_assert!( @@ -1158,29 +1183,57 @@ impl ModuleMap { scope: &mut v8::HandleScope, id: ModuleId, ) -> Result<(), CoreError> { - let mut receiver = self.mod_evaluate(scope, id); - - // After evaluate_pending_module, if the module isn't fully evaluated - // and the resolver solved, it means the module or one of its imports - // uses TLA. - match receiver.poll_unpin(&mut Context::from_waker(noop_waker_ref())) { - Poll::Ready(result) => { - result.map_err(|e| CoreError::CouldNotExecute { - error: Box::new(e), - specifier: self.get_name_by_id(id).unwrap(), - })?; + let tc_scope = &mut v8::TryCatch::new(scope); + + let module = self + .get_handle(id) + .map(|handle| v8::Local::new(tc_scope, handle)) + .expect("ModuleInfo not found"); + let status = module.get_status(); + assert_eq!( + status, + v8::ModuleStatus::Instantiated, + "{} {} ({})", + if status == v8::ModuleStatus::Evaluated { + "Module already evaluated. Perhaps you've re-provided a module or extension that was already included in the snapshot?" + } else { + "Module not instantiated" + }, + self.get_name_by_id(id).unwrap(), + id, + ); + + if module.is_graph_async() { + return Err(CoreError::TLA); + } + + let Some(value) = module.evaluate(tc_scope) else { + let exception = tc_scope.exception().unwrap(); + return Err(JsError::from_v8_exception(tc_scope, exception).into()); + }; + + if let Some(exception) = tc_scope.exception() { + return Err(JsError::from_v8_exception(tc_scope, exception).into()); + } + + let status = module.get_status(); + debug_assert!( + status == v8::ModuleStatus::Evaluated + || status == v8::ModuleStatus::Errored + ); + let promise = v8::Local::::try_from(value) + .expect("Expected to get promise as module evaluation result"); + + match promise.state() { + PromiseState::Fulfilled => Ok(()), + PromiseState::Rejected => { + let err = promise.result(tc_scope); + Err(JsError::from_v8_exception(tc_scope, err).into()) } - Poll::Pending => { - // Find the TLA location and return it as an error - let messages = self.find_stalled_top_level_await(scope); - assert!(!messages.is_empty()); - let msg = v8::Local::new(scope, &messages[0]); - let js_error = JsError::from_v8_message(scope, msg); - return Err(CoreError::TLA(js_error)); + PromiseState::Pending => { + unreachable!() } } - - Ok(()) } fn dynamic_import_module_evaluate( @@ -1213,6 +1266,19 @@ impl ModuleMap { // https://github.com/denoland/deno/issues/4908 // https://v8.dev/features/top-level-await#module-execution-order let tc_scope = &mut v8::TryCatch::new(scope); + + { + let cped = self + .dynamic_import_map + .borrow() + .get(&load_id) + .unwrap() + .cped + .clone(); + let cped = v8::Local::new(tc_scope, cped); + tc_scope.set_continuation_preserved_embedder_data(cped); + } + let module = v8::Local::new(tc_scope, &module_handle); let maybe_value = module.evaluate(tc_scope); @@ -1332,7 +1398,8 @@ impl ModuleMap { .dynamic_import_map .borrow_mut() .remove(&id) - .expect("Invalid dynamic import id"); + .expect("Invalid dynamic import id") + .resolver; let resolver = resolver_handle.open(scope); let exception = v8::Local::new(scope, exception); @@ -1350,7 +1417,8 @@ impl ModuleMap { .dynamic_import_map .borrow_mut() .remove(&id) - .expect("Invalid dynamic import id"); + .expect("Invalid dynamic import id") + .resolver; let resolver = resolver_handle.open(scope); let module = self @@ -1578,6 +1646,18 @@ impl ModuleMap { } } + pub(crate) fn get_module<'s>( + &self, + scope: &mut v8::HandleScope<'s>, + module_id: ModuleId, + ) -> Option> { + self + .data + .borrow() + .get_handle(module_id) + .map(|g| v8::Local::new(scope, g)) + } + /// Returns the namespace object of a module. /// /// This is only available after module evaluation has completed. diff --git a/core/modules/module_map_data.rs b/core/modules/module_map_data.rs index b21da8c7a..cc2b84e22 100644 --- a/core/modules/module_map_data.rs +++ b/core/modules/module_map_data.rs @@ -217,16 +217,6 @@ impl ModuleMapData { } } - pub fn is_registered( - &self, - specifier: &str, - requested_module_type: impl AsRef, - ) -> bool { - self - .get_id(specifier, requested_module_type.as_ref()) - .is_some() - } - pub(crate) fn alias( &mut self, name: FastString, diff --git a/core/modules/tests.rs b/core/modules/tests.rs index 26d533013..e83d695c7 100644 --- a/core/modules/tests.rs +++ b/core/modules/tests.rs @@ -1036,12 +1036,12 @@ async fn dyn_import_ok() { runtime.poll_event_loop(cx, Default::default()), Poll::Ready(Ok(_)) )); - assert_eq!(loader.counts(), ModuleLoadEventCounts::new(7, 1, 1)); + assert_eq!(loader.counts(), ModuleLoadEventCounts::new(5, 1, 1)); assert!(matches!( runtime.poll_event_loop(cx, Default::default()), Poll::Ready(Ok(_)) )); - assert_eq!(loader.counts(), ModuleLoadEventCounts::new(7, 1, 1)); + assert_eq!(loader.counts(), ModuleLoadEventCounts::new(5, 1, 1)); Poll::Ready(()) }) .await; diff --git a/core/ops_builtin.rs b/core/ops_builtin.rs index 99537230b..a6adb014e 100644 --- a/core/ops_builtin.rs +++ b/core/ops_builtin.rs @@ -1,19 +1,25 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +use crate::error::exception_to_err_result; use crate::error::format_file_name; use crate::error::OpError; use crate::io::AdaptiveBufferStrategy; use crate::io::BufMutView; use crate::io::BufView; use crate::io::ResourceId; +use crate::modules::ModuleMap; use crate::op2; use crate::ops_builtin_types; use crate::ops_builtin_v8; +use crate::runtime::v8_static_strings; +use crate::runtime::JsRealm; use crate::CancelHandle; use crate::JsBuffer; +use crate::ModuleId; use crate::OpDecl; use crate::OpState; use crate::Resource; use bytes::BytesMut; +use futures::StreamExt; use serde_v8::ByteString; use std::cell::RefCell; use std::io::stderr; @@ -57,6 +63,7 @@ builtin_ops! { op_cancel_handle, op_encode_binary_string, op_is_terminal, + op_import_sync, ops_builtin_types::op_is_any_array_buffer, ops_builtin_types::op_is_arguments_object, ops_builtin_types::op_is_array_buffer, @@ -419,3 +426,126 @@ fn op_is_terminal( let handle = state.resource_table.get_handle(rid)?; Ok(handle.is_terminal()) } + +async fn do_load_job<'s>( + scope: &mut v8::HandleScope<'s>, + module_map_rc: Rc, + specifier: &str, + code: Option, +) -> Result { + if let Some(code) = code { + module_map_rc + .new_es_module(scope, false, specifier.to_owned(), code, false, None) + .map_err(|e| e.into_any_error(scope, false, false))?; + } + + let mut load = ModuleMap::load_side(module_map_rc.clone(), specifier).await?; + + while let Some(load_result) = load.next().await { + let (request, info) = load_result?; + load + .register_and_recurse(scope, &request, info) + .map_err(|e| e.into_any_error(scope, false, false))?; + } + + let root_id = load.root_module_id.expect("Root module should be loaded"); + module_map_rc + .instantiate_module(scope, root_id) + .map_err(|e| { + let exception = v8::Local::new(scope, e); + exception_to_err_result::<()>(scope, exception, false, false).unwrap_err() + })?; + + Ok(root_id) +} + +/// Wrap module with another module that also exports `__esModule=true` in order +/// to maintain compat with node, which does this to maintain compat with babel. +fn wrap_module<'s>( + scope: &mut v8::HandleScope<'s>, + module: v8::Local<'s, v8::Module>, +) -> Option> { + const SOURCE: &str = " + export * from 'original'; + export {default} from 'original'; + export const __esModule = true;"; + + let source = v8::String::new(scope, SOURCE)?; + let origin = v8::ScriptOrigin::new( + scope, + source.into(), + 0, + 0, + false, + 0, + None, + true, + false, + true, + None, + ); + + let mut source = v8::script_compiler::Source::new(source, Some(&origin)); + let wrapper_module = v8::script_compiler::compile_module(scope, &mut source)?; + + let global_module = v8::Global::new(scope, module); + scope.set_slot(global_module); + fn resolve_callback<'s>( + context: v8::Local<'s, v8::Context>, + specifier: v8::Local<'s, v8::String>, + _: v8::Local<'s, v8::FixedArray>, + _: v8::Local<'s, v8::Module>, + ) -> Option> { + // SAFETY: It is safe to open a CallbackScope from a context in this callback. + let mut scope = unsafe { v8::CallbackScope::new(context) }; + debug_assert_eq!(specifier.to_rust_string_lossy(&mut scope), "original"); + let module = scope.remove_slot::>().unwrap(); + Some(v8::Local::new(&mut scope, module)) + } + wrapper_module.instantiate_module(scope, resolve_callback)?; + + wrapper_module.evaluate(scope)?; + + Some(module) +} + +#[op2(reentrant)] +fn op_import_sync<'s>( + scope: &mut v8::HandleScope<'s>, + #[string] specifier: &str, + #[string] code: Option, +) -> Result, Error> { + let module_map_rc = JsRealm::module_map_from(scope); + + // no js execution within block_on + let module_id = futures::executor::block_on(do_load_job( + scope, + module_map_rc.clone(), + specifier, + code, + ))?; + + module_map_rc.mod_evaluate_sync(scope, module_id)?; + + let module = module_map_rc + .get_module(scope, module_id) + .expect("Module must exist"); + let namespace = module.get_module_namespace().cast::(); + + let scope = &mut v8::TryCatch::new(scope); + + let default = v8_static_strings::DEFAULT.v8_string(scope); + let es_module = v8_static_strings::ESMODULE.v8_string(scope); + // If the module has a default export and no __esModule export, wrap it. + if namespace.has_own_property(scope, default.into()) == Some(true) + && namespace.has_own_property(scope, es_module.into()) == Some(false) + { + let Some(module) = wrap_module(scope, module) else { + let exception = scope.exception().unwrap(); + return exception_to_err_result(scope, exception, false, false); + }; + Ok(v8::Local::new(scope, module.get_module_namespace())) + } else { + Ok(v8::Local::new(scope, namespace).into()) + } +} diff --git a/core/ops_builtin_v8.rs b/core/ops_builtin_v8.rs index 4976f8fcc..18cabfa6d 100644 --- a/core/ops_builtin_v8.rs +++ b/core/ops_builtin_v8.rs @@ -430,7 +430,7 @@ struct SerializeDeserialize<'a> { impl<'a> v8::ValueSerializerImpl for SerializeDeserialize<'a> { #[allow(unused_variables)] fn throw_data_clone_error<'s>( - &mut self, + &self, scope: &mut v8::HandleScope<'s>, message: v8::Local<'s, v8::String>, ) { @@ -448,7 +448,7 @@ impl<'a> v8::ValueSerializerImpl for SerializeDeserialize<'a> { } fn get_shared_array_buffer_id<'s>( - &mut self, + &self, scope: &mut v8::HandleScope<'s>, shared_array_buffer: v8::Local<'s, v8::SharedArrayBuffer>, ) -> Option { @@ -466,7 +466,7 @@ impl<'a> v8::ValueSerializerImpl for SerializeDeserialize<'a> { } fn get_wasm_module_transfer_id( - &mut self, + &self, scope: &mut v8::HandleScope<'_>, module: v8::Local, ) -> Option { @@ -486,12 +486,12 @@ impl<'a> v8::ValueSerializerImpl for SerializeDeserialize<'a> { } } - fn has_custom_host_object(&mut self, _isolate: &mut v8::Isolate) -> bool { + fn has_custom_host_object(&self, _isolate: &mut v8::Isolate) -> bool { true } fn is_host_object<'s>( - &mut self, + &self, scope: &mut v8::HandleScope<'s>, object: v8::Local<'s, v8::Object>, ) -> Option { @@ -504,10 +504,10 @@ impl<'a> v8::ValueSerializerImpl for SerializeDeserialize<'a> { } fn write_host_object<'s>( - &mut self, + &self, scope: &mut v8::HandleScope<'s>, object: v8::Local<'s, v8::Object>, - value_serializer: &mut dyn v8::ValueSerializerHelper, + value_serializer: &dyn v8::ValueSerializerHelper, ) -> Option { if let Some(host_objects) = self.host_objects { for i in 0..host_objects.length() { @@ -526,7 +526,7 @@ impl<'a> v8::ValueSerializerImpl for SerializeDeserialize<'a> { impl<'a> v8::ValueDeserializerImpl for SerializeDeserialize<'a> { fn get_shared_array_buffer_from_id<'s>( - &mut self, + &self, scope: &mut v8::HandleScope<'s>, transfer_id: u32, ) -> Option> { @@ -545,7 +545,7 @@ impl<'a> v8::ValueDeserializerImpl for SerializeDeserialize<'a> { } fn get_wasm_module_from_id<'s>( - &mut self, + &self, scope: &mut v8::HandleScope<'s>, clone_id: u32, ) -> Option> { @@ -563,9 +563,9 @@ impl<'a> v8::ValueDeserializerImpl for SerializeDeserialize<'a> { } fn read_host_object<'s>( - &mut self, + &self, scope: &mut v8::HandleScope<'s>, - value_deserializer: &mut dyn v8::ValueDeserializerHelper, + value_deserializer: &dyn v8::ValueDeserializerHelper, ) -> Option> { if let Some(host_objects) = self.host_objects { let mut i = 0; @@ -630,8 +630,7 @@ pub fn op_serialize( for_storage, host_object_brand, }); - let mut value_serializer = - v8::ValueSerializer::new(scope, serialize_deserialize); + let value_serializer = v8::ValueSerializer::new(scope, serialize_deserialize); value_serializer.write_header(); if let Some(transferred_array_buffers) = transferred_array_buffers { @@ -719,7 +718,7 @@ pub fn op_deserialize<'a>( for_storage, host_object_brand: None, }); - let mut value_deserializer = + let value_deserializer = v8::ValueDeserializer::new(scope, serialize_deserialize, &zero_copy); let parsed_header = value_deserializer .read_header(scope.get_current_context()) diff --git a/core/runtime/bindings.rs b/core/runtime/bindings.rs index f53926473..368cb7f96 100644 --- a/core/runtime/bindings.rs +++ b/core/runtime/bindings.rs @@ -427,6 +427,8 @@ pub fn host_import_module_dynamically_callback<'s>( specifier: v8::Local<'s, v8::String>, import_attributes: v8::Local<'s, v8::FixedArray>, ) -> Option> { + let cped = scope.get_continuation_preserved_embedder_data(); + // NOTE(bartlomieju): will crash for non-UTF-8 specifier let specifier_str = specifier .to_string(scope) @@ -466,17 +468,24 @@ pub fn host_import_module_dynamically_callback<'s>( get_requested_module_type_from_attributes(&assertions); let resolver_handle = v8::Global::new(scope, resolver); + let cped_handle = v8::Global::new(scope, cped); { let state = JsRuntime::state_from(scope); let module_map_rc = JsRealm::module_map_from(scope); - ModuleMap::load_dynamic_import( + if !ModuleMap::load_dynamic_import( module_map_rc, + scope, &specifier_str, &referrer_name_str, requested_module_type, resolver_handle, - ); + cped_handle, + ) { + // Short-circuit if the module is already cached and we know it won't error. + return Some(promise); + } + state.notify_new_dynamic_import(); } // Map errors from module resolution (not JS errors from module execution) to diff --git a/core/runtime/jsruntime.rs b/core/runtime/jsruntime.rs index a4262c760..36688e730 100644 --- a/core/runtime/jsruntime.rs +++ b/core/runtime/jsruntime.rs @@ -146,6 +146,7 @@ pub(crate) struct InnerIsolateState { main_realm: ManuallyDrop, pub(crate) state: ManuallyDropRc, v8_isolate: ManuallyDrop, + v8_cpp_heap: ManuallyDrop>, } impl InnerIsolateState { @@ -182,18 +183,29 @@ impl InnerIsolateState { debug_assert_eq!(Rc::strong_count(&self.state), 1); } + pub fn cleanup_cpp_heap(&mut self) { + self.v8_isolate.detach_cpp_heap(); + self.v8_cpp_heap.terminate(); + unsafe { + ManuallyDrop::drop(&mut self.v8_cpp_heap); + } + } + pub fn prepare_for_snapshot(mut self) -> v8::OwnedIsolate { self.cleanup(); + // SAFETY: We're copying out of self and then immediately forgetting self - let (state, isolate) = unsafe { - ( - ManuallyDrop::take(&mut self.state.0), - ManuallyDrop::take(&mut self.v8_isolate), - ) - }; - std::mem::forget(self); - drop(state); - isolate + unsafe { + ManuallyDrop::drop(&mut self.state.0); + + self.cleanup_cpp_heap(); + + let isolate = ManuallyDrop::take(&mut self.v8_isolate); + + std::mem::forget(self); + + isolate + } } } @@ -203,6 +215,9 @@ impl Drop for InnerIsolateState { // SAFETY: We gotta drop these unsafe { ManuallyDrop::drop(&mut self.state.0); + + self.cleanup_cpp_heap(); + if self.will_snapshot { // Create the snapshot and just drop it. #[allow(clippy::print_stderr)] @@ -910,6 +925,8 @@ impl JsRuntime { maybe_startup_snapshot, external_refs_static, ); + let mut cpp_heap = setup::create_cpp_heap(); + isolate.attach_cpp_heap(&mut cpp_heap); if state_rc.import_assertions_support.has_warning() { isolate.add_message_listener_with_error_level( @@ -1011,7 +1028,12 @@ impl JsRuntime { } let inspector = if options.inspector { - Some(JsRuntimeInspector::new(scope, context, options.is_main)) + Some(JsRuntimeInspector::new( + isolate_ptr, + scope, + context, + options.is_main, + )) } else { None }; @@ -1083,6 +1105,7 @@ impl JsRuntime { main_realm: ManuallyDrop::new(main_realm), state: ManuallyDropRc(ManuallyDrop::new(state_rc)), v8_isolate: ManuallyDrop::new(isolate), + v8_cpp_heap: ManuallyDrop::new(cpp_heap), }, allocations: isolate_allocations, files_loaded_from_fs_during_snapshot: vec![], @@ -1671,6 +1694,7 @@ impl JsRuntime { } let context = self.main_context(); + let isolate_ptr = self.inner.v8_isolate.as_mut() as *mut _; let scope = &mut v8::HandleScope::with_context( self.inner.v8_isolate.as_mut(), context.clone(), @@ -1679,6 +1703,7 @@ impl JsRuntime { self.inner.state.has_inspector.set(true); **inspector = Some(JsRuntimeInspector::new( + isolate_ptr, scope, context, self.is_main_runtime, diff --git a/core/runtime/setup.rs b/core/runtime/setup.rs index 953e88c0c..098335e14 100644 --- a/core/runtime/setup.rs +++ b/core/runtime/setup.rs @@ -111,7 +111,7 @@ pub fn init_v8( }); } -fn create_cpp_heap() -> v8::UniqueRef { +pub fn create_cpp_heap() -> v8::UniqueRef { v8::cppgc::Heap::create( v8::V8::get_current_platform(), v8::cppgc::HeapCreateParams::default(), @@ -129,8 +129,7 @@ pub fn create_isolate( .embedder_wrapper_type_info_offsets( V8_WRAPPER_TYPE_INDEX, V8_WRAPPER_OBJECT_INDEX, - ) - .cpp_heap(create_cpp_heap()); + ); let mut isolate = if will_snapshot { snapshot::create_snapshot_creator( external_refs, diff --git a/core/runtime/snapshot.rs b/core/runtime/snapshot.rs index d0188d1f8..319f5b77f 100644 --- a/core/runtime/snapshot.rs +++ b/core/runtime/snapshot.rs @@ -28,7 +28,7 @@ pub(crate) struct V8Snapshot(pub(crate) &'static [u8]); pub(crate) fn deconstruct( slice: &'static [u8], -) -> (V8Snapshot, SerializableSnapshotSidecarData) { +) -> (V8Snapshot, SerializableSnapshotSidecarData<'static>) { let len = usize::from_le_bytes(slice[slice.len() - ULEN..].try_into().unwrap()); let data = SerializableSnapshotSidecarData::from_slice( @@ -109,22 +109,72 @@ impl SnapshotStoreDataStore { } } +/// Options for [`create_snapshot`]. +/// +/// See: [example][1]. +/// +/// [1]: https://github.com/denoland/deno_core/tree/main/core/examples/snapshot pub struct CreateSnapshotOptions { + /// The directory which Cargo will compile everything into. + /// + /// This should always be the CARGO_MANIFEST_DIR environment variable. pub cargo_manifest_dir: &'static str, + + /// An optional starting snapshot atop which to build this snapshot. + /// + /// Passed to: [`RuntimeOptions::startup_snapshot`] pub startup_snapshot: Option<&'static [u8]>, + + /// Passed to [`RuntimeOptions::skip_op_registration`] while initializing the snapshot runtime. pub skip_op_registration: bool, + + /// Extensions to include within the generated snapshot. + /// + /// Passed to [`RuntimeOptions::extensions`] pub extensions: Vec, + + /// An optional transpiler to modify the module source before inclusion in the snapshot. + /// + /// For example, this might transpile from TypeScript to JavaScript. + /// + /// Passed to: [`RuntimeOptions::extension_transpiler`] pub extension_transpiler: Option>, + + /// An optional callback to perform further modification of the runtime before + /// taking the snapshot. pub with_runtime_cb: Option>, } +/// See [`create_snapshot`] for usage overview. pub struct CreateSnapshotOutput { /// Any files marked as LoadedFromFsDuringSnapshot are collected here and should be /// printed as 'cargo:rerun-if-changed' lines from your build script. pub files_loaded_during_snapshot: Vec, + + /// The resulting snapshot file's bytes. pub output: Box<[u8]>, } +/// Create a snapshot of a JavaScript runtime, which may yield better startup +/// time. +/// +/// At a high level, the steps are: +/// +/// * In your project's `build.rs` file: +/// * Call `create_snapshot()` from your `build.rs` file. +/// * Output the resulting snapshot to a path, preferably in [OUT_DIR]. +/// * Make sure to print a `cargo:rerun-if-changed` line for each +/// [`CreateSnapshotOutput::files_loaded_during_snapshot`]. +/// * In your project's source: +/// * Load the bytes of the generated snapshot file +/// ([`include_bytes`] is useful here) +/// * Pass those bytes to [`deno_core::JsRuntime::new`] via +/// [`RuntimeOptions::startup_snapshot`] +/// +/// For a concrete example, see [core/examples/snapshot/][example]. +/// +/// [example]: https://github.com/denoland/deno_core/tree/main/core/examples/snapshot +/// [OUT_DIR]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts #[must_use = "The files listed by create_snapshot should be printed as 'cargo:rerun-if-changed' lines"] pub fn create_snapshot( create_snapshot_options: CreateSnapshotOptions, diff --git a/core/runtime/tests/misc.rs b/core/runtime/tests/misc.rs index d49aa76bf..c668921e1 100644 --- a/core/runtime/tests/misc.rs +++ b/core/runtime/tests/misc.rs @@ -1023,11 +1023,7 @@ async fn test_dynamic_import_module_error_stack() { #[tokio::test] #[should_panic( - expected = r#"Failed to initialize a JsRuntime: Top-level await is not allowed in extensions - -Caused by: - Top-level await promise never resolved - at mod:tla:3:11"# + expected = "Failed to initialize a JsRuntime: Top-level await is not allowed in synchronous evaluation" )] async fn tla_in_esm_extensions_panics() { #[op2(async)] diff --git a/core/runtime/v8_static_strings.rs b/core/runtime/v8_static_strings.rs index b106d9c76..b751033c8 100644 --- a/core/runtime/v8_static_strings.rs +++ b/core/runtime/v8_static_strings.rs @@ -19,6 +19,7 @@ v8_static_strings!( CONSTRUCTOR = "constructor", CORE = "core", DENO = "Deno", + DEFAULT = "default", DIRNAME = "dirname", ERR_MODULE_NOT_FOUND = "ERR_MODULE_NOT_FOUND", ERRORS = "errors", @@ -35,4 +36,5 @@ v8_static_strings!( URL = "url", WASM_INSTANTIATE = "wasmInstantiate", WEBASSEMBLY = "WebAssembly", + ESMODULE = "__esModule", ); diff --git a/ops/Cargo.toml b/ops/Cargo.toml index 2bf89c837..db57b791c 100644 --- a/ops/Cargo.toml +++ b/ops/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "deno_ops" -version = "0.182.0" +version = "0.187.0" authors.workspace = true edition.workspace = true license.workspace = true diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 85cb9e7bb..f19c7df47 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "1.80.0" +channel = "1.81.0" components = ["rustfmt", "clippy"] diff --git a/serde_v8/Cargo.toml b/serde_v8/Cargo.toml index 7fa3ab620..be3bbb3a5 100644 --- a/serde_v8/Cargo.toml +++ b/serde_v8/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "serde_v8" -version = "0.215.0" +version = "0.220.0" authors.workspace = true edition.workspace = true license.workspace = true diff --git a/serde_v8/utilities/Cargo.toml b/serde_v8/utilities/Cargo.toml index bc86ed533..3e5d948ce 100644 --- a/serde_v8/utilities/Cargo.toml +++ b/serde_v8/utilities/Cargo.toml @@ -4,6 +4,7 @@ name = "serde_v8_utilities" version = "0.3.0" publish = false +edition = "2021" [lib] path = "lib.rs" diff --git a/testing/Cargo.toml b/testing/Cargo.toml index fc37d19fa..11df31420 100644 --- a/testing/Cargo.toml +++ b/testing/Cargo.toml @@ -20,6 +20,7 @@ deno_core.workspace = true deno_core.features = ["unsafe_use_unprotected_platform", "snapshot_flags_eager_parse"] futures.workspace = true tokio.workspace = true +url.workspace = true [dev-dependencies] pretty_assertions.workspace = true diff --git a/testing/checkin/runner/extensions.rs b/testing/checkin/runner/extensions.rs index bdabaa56d..d9b97001e 100644 --- a/testing/checkin/runner/extensions.rs +++ b/testing/checkin/runner/extensions.rs @@ -25,6 +25,7 @@ deno_core::extension!( ops::op_nop_generic

, ops_io::op_pipe_create, ops_io::op_file_open, + ops_io::op_path_to_url, ops_async::op_task_submit, ops_async::op_async_yield, ops_async::op_async_barrier_create, diff --git a/testing/checkin/runner/ops_io.rs b/testing/checkin/runner/ops_io.rs index 4af65372e..aef71dfe2 100644 --- a/testing/checkin/runner/ops_io.rs +++ b/testing/checkin/runner/ops_io.rs @@ -105,3 +105,11 @@ pub async fn op_file_open( .add(FileResource::new(tokio_file)); Ok(rid) } + +#[op2] +#[string] +pub fn op_path_to_url(#[string] path: &str) -> Result { + let path = std::path::absolute(path)?; + let url = url::Url::from_file_path(path).unwrap(); + Ok(url.to_string()) +} diff --git a/testing/checkin/runner/ts_module_loader.rs b/testing/checkin/runner/ts_module_loader.rs index f37330126..199e861ce 100644 --- a/testing/checkin/runner/ts_module_loader.rs +++ b/testing/checkin/runner/ts_module_loader.rs @@ -66,7 +66,11 @@ impl ModuleLoader for TypescriptModuleLoader { } else { 0 }; - let path = root.join(Path::new(&module_specifier.path()[start..])); + let path = if module_specifier.scheme() == "file" { + module_specifier.to_file_path().unwrap() + } else { + root.join(Path::new(&module_specifier.path()[start..])) + }; if let RequestedModuleType::Other(type_) = requested_module_type { let bytes = fs::read(path)?; return Ok(ModuleSource::new( diff --git a/testing/integration/import_sync/async.js b/testing/integration/import_sync/async.js new file mode 100644 index 000000000..97ff717bb --- /dev/null +++ b/testing/integration/import_sync/async.js @@ -0,0 +1,4 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +await {}; +export const a = 1; diff --git a/testing/integration/import_sync/import_sync.out b/testing/integration/import_sync/import_sync.out new file mode 100644 index 000000000..b738e50a4 --- /dev/null +++ b/testing/integration/import_sync/import_sync.out @@ -0,0 +1,5 @@ +{ + "a": 1 +} +[ERR] Error: Top-level await is not allowed in synchronous evaluation +[ERR] at test:///integration/import_sync/import_sync.ts:8:1 diff --git a/testing/integration/import_sync/import_sync.ts b/testing/integration/import_sync/import_sync.ts new file mode 100644 index 000000000..d8ecc0a11 --- /dev/null +++ b/testing/integration/import_sync/import_sync.ts @@ -0,0 +1,8 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +const { op_import_sync, op_path_to_url } = Deno.core.ops; + +const resolve = (p: string) => op_path_to_url(p); + +console.log(op_import_sync(resolve("./integration/import_sync/sync.js"))); +op_import_sync(resolve("./integration/import_sync/async.js")); diff --git a/testing/integration/import_sync/sync.js b/testing/integration/import_sync/sync.js new file mode 100644 index 000000000..f466fc686 --- /dev/null +++ b/testing/integration/import_sync/sync.js @@ -0,0 +1,3 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +export const a = 1; diff --git a/testing/integration/ts_types/which.ts b/testing/integration/ts_types/which.ts index 656be46ef..9ddae5903 100644 --- a/testing/integration/ts_types/which.ts +++ b/testing/integration/ts_types/which.ts @@ -16,7 +16,7 @@ declare namespace Deno { function get(string); } namespace errors { - class PermissionDenied {} + class NotCapable {} } namespace build { const os: string; @@ -93,7 +93,7 @@ async function pathMatches( const result = await environment.stat(path); return result.isFile; } catch (err) { - if (err instanceof Deno.errors.PermissionDenied) { + if (err instanceof Deno.errors.NotCapable) { throw err; } return false; @@ -136,7 +136,7 @@ function pathMatchesSync( const result = environment.statSync(path); return result.isFile; } catch (err) { - if (err instanceof Deno.errors.PermissionDenied) { + if (err instanceof Deno.errors.NotCapable) { throw err; } return false; diff --git a/testing/lib.rs b/testing/lib.rs index 0a5f607e9..aa9bd6614 100644 --- a/testing/lib.rs +++ b/testing/lib.rs @@ -69,6 +69,7 @@ integration_test!( error_get_file_name, error_get_file_name_to_string, error_get_script_name_or_source_url, + import_sync, main_module_handler, module_types, pending_unref_op_tla,