summaryrefslogtreecommitdiff
path: root/runtime
diff options
context:
space:
mode:
authorIgor Zinkovsky <igor@deno.com>2024-04-17 07:19:55 -0700
committerGitHub <noreply@github.com>2024-04-17 07:19:55 -0700
commitb3d7df55357ea6fc6f5141b64a9638ddb39b0f63 (patch)
tree0ca14140c7e080ed3367a7352bbaf3ed5f7a0f48 /runtime
parent9acbf90b06bf79dd6e4cf2428b3566da009bed65 (diff)
perf: v8 code cache (#23081)
This PR enables V8 code cache for ES modules and for `require` scripts through `op_eval_context`. Code cache artifacts are transparently stored and fetched using sqlite db and are passed to V8. `--no-code-cache` can be used to disable. --------- Co-authored-by: Bartek IwaƄczuk <biwanczuk@gmail.com>
Diffstat (limited to 'runtime')
-rw-r--r--runtime/Cargo.toml1
-rw-r--r--runtime/code_cache.rs31
-rw-r--r--runtime/fs_util.rs74
-rw-r--r--runtime/lib.rs1
-rw-r--r--runtime/worker.rs65
5 files changed, 172 insertions, 0 deletions
diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml
index 97a6d1397..8028a570d 100644
--- a/runtime/Cargo.toml
+++ b/runtime/Cargo.toml
@@ -112,6 +112,7 @@ log.workspace = true
netif = "0.1.6"
notify.workspace = true
once_cell.workspace = true
+percent-encoding.workspace = true
regex.workspace = true
ring.workspace = true
rustyline = { workspace = true, features = ["custom-bindings"] }
diff --git a/runtime/code_cache.rs b/runtime/code_cache.rs
new file mode 100644
index 000000000..ccc070365
--- /dev/null
+++ b/runtime/code_cache.rs
@@ -0,0 +1,31 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+pub enum CodeCacheType {
+ EsModule,
+ Script,
+}
+
+impl CodeCacheType {
+ pub fn as_str(&self) -> &str {
+ match self {
+ Self::EsModule => "esmodule",
+ Self::Script => "script",
+ }
+ }
+}
+
+pub trait CodeCache: Send + Sync {
+ fn get_sync(
+ &self,
+ specifier: &str,
+ code_cache_type: CodeCacheType,
+ source_hash: &str,
+ ) -> Option<Vec<u8>>;
+ fn set_sync(
+ &self,
+ specifier: &str,
+ code_cache_type: CodeCacheType,
+ source_hash: &str,
+ data: &[u8],
+ );
+}
diff --git a/runtime/fs_util.rs b/runtime/fs_util.rs
index f7c006a91..09b107300 100644
--- a/runtime/fs_util.rs
+++ b/runtime/fs_util.rs
@@ -1,6 +1,8 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+use deno_ast::ModuleSpecifier;
use deno_core::anyhow::Context;
+use deno_core::error::uri_error;
use deno_core::error::AnyError;
pub use deno_core::normalize_path;
use std::path::Path;
@@ -18,6 +20,60 @@ pub fn resolve_from_cwd(path: &Path) -> Result<PathBuf, AnyError> {
}
}
+/// Attempts to convert a specifier to a file path. By default, uses the Url
+/// crate's `to_file_path()` method, but falls back to try and resolve unix-style
+/// paths on Windows.
+pub fn specifier_to_file_path(
+ specifier: &ModuleSpecifier,
+) -> Result<PathBuf, AnyError> {
+ let result = if specifier.scheme() != "file" {
+ Err(())
+ } else if cfg!(windows) {
+ match specifier.to_file_path() {
+ Ok(path) => Ok(path),
+ Err(()) => {
+ // This might be a unix-style path which is used in the tests even on Windows.
+ // Attempt to see if we can convert it to a `PathBuf`. This code should be removed
+ // once/if https://github.com/servo/rust-url/issues/730 is implemented.
+ if specifier.scheme() == "file"
+ && specifier.host().is_none()
+ && specifier.port().is_none()
+ && specifier.path_segments().is_some()
+ {
+ let path_str = specifier.path();
+ match String::from_utf8(
+ percent_encoding::percent_decode(path_str.as_bytes()).collect(),
+ ) {
+ Ok(path_str) => Ok(PathBuf::from(path_str)),
+ Err(_) => Err(()),
+ }
+ } else {
+ Err(())
+ }
+ }
+ }
+ } else {
+ specifier.to_file_path()
+ };
+ match result {
+ Ok(path) => Ok(path),
+ Err(()) => Err(uri_error(format!(
+ "Invalid file path.\n Specifier: {specifier}"
+ ))),
+ }
+}
+
+pub fn code_timestamp(specifier: &str) -> Result<u64, AnyError> {
+ let specifier = ModuleSpecifier::parse(specifier)?;
+ let path = specifier_to_file_path(&specifier)?;
+ #[allow(clippy::disallowed_methods)]
+ let timestamp = std::fs::metadata(path)?
+ .modified()?
+ .duration_since(std::time::UNIX_EPOCH)?
+ .as_millis() as u64;
+ Ok(timestamp)
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -69,4 +125,22 @@ mod tests {
let absolute_expected = cwd.join(expected);
assert_eq!(resolve_from_cwd(expected).unwrap(), absolute_expected);
}
+
+ #[test]
+ fn test_specifier_to_file_path() {
+ run_success_test("file:///", "/");
+ run_success_test("file:///test", "/test");
+ run_success_test("file:///dir/test/test.txt", "/dir/test/test.txt");
+ run_success_test(
+ "file:///dir/test%20test/test.txt",
+ "/dir/test test/test.txt",
+ );
+
+ fn run_success_test(specifier: &str, expected_path: &str) {
+ let result =
+ specifier_to_file_path(&ModuleSpecifier::parse(specifier).unwrap())
+ .unwrap();
+ assert_eq!(result, PathBuf::from(expected_path));
+ }
+ }
}
diff --git a/runtime/lib.rs b/runtime/lib.rs
index 72fa1cef8..f33e9b7e3 100644
--- a/runtime/lib.rs
+++ b/runtime/lib.rs
@@ -26,6 +26,7 @@ pub use deno_webidl;
pub use deno_websocket;
pub use deno_webstorage;
+pub mod code_cache;
pub mod errors;
pub mod fmt_errors;
pub mod fs_util;
diff --git a/runtime/worker.rs b/runtime/worker.rs
index 956fa2d32..ee6b256ff 100644
--- a/runtime/worker.rs
+++ b/runtime/worker.rs
@@ -1,4 +1,5 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+use std::borrow::Cow;
use std::collections::HashMap;
use std::rc::Rc;
use std::sync::atomic::AtomicBool;
@@ -41,6 +42,9 @@ use deno_tls::RootCertStoreProvider;
use deno_web::BlobStore;
use log::debug;
+use crate::code_cache::CodeCache;
+use crate::code_cache::CodeCacheType;
+use crate::fs_util::code_timestamp;
use crate::inspector_server::InspectorServer;
use crate::ops;
use crate::permissions::PermissionsContainer;
@@ -193,6 +197,9 @@ pub struct WorkerOptions {
pub compiled_wasm_module_store: Option<CompiledWasmModuleStore>,
pub stdio: Stdio,
pub feature_checker: Arc<FeatureChecker>,
+
+ /// V8 code cache for module and script source code.
+ pub v8_code_cache: Option<Arc<dyn CodeCache>>,
}
impl Default for WorkerOptions {
@@ -227,6 +234,7 @@ impl Default for WorkerOptions {
bootstrap: Default::default(),
stdio: Default::default(),
feature_checker: Default::default(),
+ v8_code_cache: Default::default(),
}
}
}
@@ -296,6 +304,51 @@ pub fn create_op_metrics(
(op_summary_metrics, op_metrics_factory_fn)
}
+fn get_code_cache(
+ code_cache: Arc<dyn CodeCache>,
+ specifier: &str,
+) -> Option<Vec<u8>> {
+ // Code hashes are not maintained for op_eval_context scripts. Instead we use
+ // the modified timestamp from the local file system.
+ if let Ok(code_timestamp) = code_timestamp(specifier) {
+ code_cache
+ .get_sync(
+ specifier,
+ CodeCacheType::Script,
+ code_timestamp.to_string().as_str(),
+ )
+ .inspect(|_| {
+ // This log line is also used by tests.
+ log::debug!(
+ "V8 code cache hit for script: {specifier}, [{code_timestamp}]"
+ );
+ })
+ } else {
+ None
+ }
+}
+
+fn set_code_cache(
+ code_cache: Arc<dyn CodeCache>,
+ specifier: &str,
+ data: &[u8],
+) {
+ // Code hashes are not maintained for op_eval_context scripts. Instead we use
+ // the modified timestamp from the local file system.
+ if let Ok(code_timestamp) = code_timestamp(specifier) {
+ // This log line is also used by tests.
+ log::debug!(
+ "Updating V8 code cache for script: {specifier}, [{code_timestamp}]",
+ );
+ code_cache.set_sync(
+ specifier,
+ CodeCacheType::Script,
+ code_timestamp.to_string().as_str(),
+ data,
+ );
+ }
+}
+
impl MainWorker {
pub fn bootstrap_from_options(
main_module: ModuleSpecifier,
@@ -495,6 +548,18 @@ impl MainWorker {
validate_import_attributes_cb: Some(Box::new(
validate_import_attributes_callback,
)),
+ enable_code_cache: options.v8_code_cache.is_some(),
+ eval_context_code_cache_cbs: options.v8_code_cache.map(|cache| {
+ let cache_clone = cache.clone();
+ (
+ Box::new(move |specifier: &str| {
+ Ok(get_code_cache(cache.clone(), specifier).map(Cow::Owned))
+ }) as Box<dyn Fn(&_) -> _>,
+ Box::new(move |specifier: &str, data: &[u8]| {
+ set_code_cache(cache_clone.clone(), specifier, data);
+ }) as Box<dyn Fn(&_, &_)>,
+ )
+ }),
..Default::default()
});