summaryrefslogtreecommitdiff
path: root/cli
diff options
context:
space:
mode:
Diffstat (limited to 'cli')
-rw-r--r--cli/args/flags.rs7
-rw-r--r--cli/cache/code_cache.rs10
-rw-r--r--cli/standalone/binary.rs14
-rw-r--r--cli/standalone/code_cache.rs514
-rw-r--r--cli/standalone/mod.rs89
-rw-r--r--cli/worker.rs15
6 files changed, 638 insertions, 11 deletions
diff --git a/cli/args/flags.rs b/cli/args/flags.rs
index bd6b30e41..39db12b5f 100644
--- a/cli/args/flags.rs
+++ b/cli/args/flags.rs
@@ -1939,6 +1939,7 @@ On the first invocation with deno will download the proper binary and cache it i
])
.help_heading(COMPILE_HEADING),
)
+ .arg(no_code_cache_arg())
.arg(
Arg::new("no-terminal")
.long("no-terminal")
@@ -4431,6 +4432,8 @@ fn compile_parse(
};
ext_arg_parse(flags, matches);
+ flags.code_cache_enabled = !matches.get_flag("no-code-cache");
+
flags.subcommand = DenoSubcommand::Compile(CompileFlags {
source_file,
output,
@@ -10040,6 +10043,7 @@ mod tests {
include: vec![]
}),
type_check_mode: TypeCheckMode::Local,
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -10048,7 +10052,7 @@ mod tests {
#[test]
fn compile_with_flags() {
#[rustfmt::skip]
- let r = flags_from_vec(svec!["deno", "compile", "--import-map", "import_map.json", "--no-remote", "--config", "tsconfig.json", "--no-check", "--unsafely-ignore-certificate-errors", "--reload", "--lock", "lock.json", "--cert", "example.crt", "--cached-only", "--location", "https:foo", "--allow-read", "--allow-net", "--v8-flags=--help", "--seed", "1", "--no-terminal", "--icon", "favicon.ico", "--output", "colors", "--env=.example.env", "https://examples.deno.land/color-logging.ts", "foo", "bar", "-p", "8080"]);
+ let r = flags_from_vec(svec!["deno", "compile", "--import-map", "import_map.json", "--no-code-cache", "--no-remote", "--config", "tsconfig.json", "--no-check", "--unsafely-ignore-certificate-errors", "--reload", "--lock", "lock.json", "--cert", "example.crt", "--cached-only", "--location", "https:foo", "--allow-read", "--allow-net", "--v8-flags=--help", "--seed", "1", "--no-terminal", "--icon", "favicon.ico", "--output", "colors", "--env=.example.env", "https://examples.deno.land/color-logging.ts", "foo", "bar", "-p", "8080"]);
assert_eq!(
r.unwrap(),
Flags {
@@ -10064,6 +10068,7 @@ mod tests {
}),
import_map_path: Some("import_map.json".to_string()),
no_remote: true,
+ code_cache_enabled: false,
config_flag: ConfigFlag::Path("tsconfig.json".to_owned()),
type_check_mode: TypeCheckMode::None,
reload: true,
diff --git a/cli/cache/code_cache.rs b/cli/cache/code_cache.rs
index abcd0d46a..b1d9ae757 100644
--- a/cli/cache/code_cache.rs
+++ b/cli/cache/code_cache.rs
@@ -1,10 +1,14 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+use std::sync::Arc;
+
use deno_ast::ModuleSpecifier;
use deno_core::error::AnyError;
use deno_runtime::code_cache;
use deno_runtime::deno_webstorage::rusqlite::params;
+use crate::worker::CliCodeCache;
+
use super::cache_db::CacheDB;
use super::cache_db::CacheDBConfiguration;
use super::cache_db::CacheDBHash;
@@ -82,6 +86,12 @@ impl CodeCache {
}
}
+impl CliCodeCache for CodeCache {
+ fn as_code_cache(self: Arc<Self>) -> Arc<dyn code_cache::CodeCache> {
+ self
+ }
+}
+
impl code_cache::CodeCache for CodeCache {
fn get_sync(
&self,
diff --git a/cli/standalone/binary.rs b/cli/standalone/binary.rs
index 3efd8ee14..37753bafc 100644
--- a/cli/standalone/binary.rs
+++ b/cli/standalone/binary.rs
@@ -64,6 +64,7 @@ use crate::args::NpmInstallDepsProvider;
use crate::args::PermissionFlags;
use crate::args::UnstableConfig;
use crate::cache::DenoDir;
+use crate::cache::FastInsecureHasher;
use crate::emit::Emitter;
use crate::file_fetcher::FileFetcher;
use crate::http_util::HttpClientProvider;
@@ -174,6 +175,7 @@ pub struct SerializedWorkspaceResolver {
pub struct Metadata {
pub argv: Vec<String>,
pub seed: Option<u64>,
+ pub code_cache_key: Option<u64>,
pub permissions: PermissionFlags,
pub location: Option<Url>,
pub v8_flags: Vec<String>,
@@ -604,10 +606,21 @@ impl<'a> DenoCompileBinaryWriter<'a> {
VfsBuilder::new(root_path.clone())?
};
let mut remote_modules_store = RemoteModulesStoreBuilder::default();
+ let mut code_cache_key_hasher = if cli_options.code_cache_enabled() {
+ Some(FastInsecureHasher::new_deno_versioned())
+ } else {
+ None
+ };
for module in graph.modules() {
if module.specifier().scheme() == "data" {
continue; // don't store data urls as an entry as they're in the code
}
+ if let Some(hasher) = &mut code_cache_key_hasher {
+ if let Some(source) = module.source() {
+ hasher.write(module.specifier().as_str().as_bytes());
+ hasher.write(source.as_bytes());
+ }
+ }
let (maybe_source, media_type) = match module {
deno_graph::Module::Js(m) => {
let source = if m.media_type.is_emittable() {
@@ -675,6 +688,7 @@ impl<'a> DenoCompileBinaryWriter<'a> {
let metadata = Metadata {
argv: compile_flags.args.clone(),
seed: cli_options.seed(),
+ code_cache_key: code_cache_key_hasher.map(|h| h.finish()),
location: cli_options.location_flag().clone(),
permissions: cli_options.permission_flags().clone(),
v8_flags: cli_options.v8_flags().clone(),
diff --git a/cli/standalone/code_cache.rs b/cli/standalone/code_cache.rs
new file mode 100644
index 000000000..25b490544
--- /dev/null
+++ b/cli/standalone/code_cache.rs
@@ -0,0 +1,514 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+use std::collections::BTreeMap;
+use std::collections::HashMap;
+use std::io::BufReader;
+use std::io::BufWriter;
+use std::io::Read;
+use std::io::Write;
+use std::path::Path;
+use std::path::PathBuf;
+use std::sync::Arc;
+
+use deno_ast::ModuleSpecifier;
+use deno_core::anyhow::bail;
+use deno_core::error::AnyError;
+use deno_core::parking_lot::Mutex;
+use deno_core::unsync::sync::AtomicFlag;
+use deno_runtime::code_cache::CodeCache;
+use deno_runtime::code_cache::CodeCacheType;
+
+use crate::cache::FastInsecureHasher;
+use crate::util::path::get_atomic_file_path;
+use crate::worker::CliCodeCache;
+
+enum CodeCacheStrategy {
+ FirstRun(FirstRunCodeCacheStrategy),
+ SubsequentRun(SubsequentRunCodeCacheStrategy),
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct DenoCompileCodeCacheEntry {
+ pub source_hash: u64,
+ pub data: Vec<u8>,
+}
+
+pub struct DenoCompileCodeCache {
+ strategy: CodeCacheStrategy,
+}
+
+impl DenoCompileCodeCache {
+ pub fn new(file_path: PathBuf, cache_key: u64) -> Self {
+ // attempt to deserialize the cache data
+ match deserialize(&file_path, cache_key) {
+ Ok(data) => {
+ log::debug!("Loaded {} code cache entries", data.len());
+ Self {
+ strategy: CodeCacheStrategy::SubsequentRun(
+ SubsequentRunCodeCacheStrategy {
+ is_finished: AtomicFlag::lowered(),
+ data: Mutex::new(data),
+ },
+ ),
+ }
+ }
+ Err(err) => {
+ log::debug!("Failed to deserialize code cache: {:#}", err);
+ Self {
+ strategy: CodeCacheStrategy::FirstRun(FirstRunCodeCacheStrategy {
+ cache_key,
+ file_path,
+ is_finished: AtomicFlag::lowered(),
+ data: Mutex::new(FirstRunCodeCacheData {
+ cache: HashMap::new(),
+ add_count: 0,
+ }),
+ }),
+ }
+ }
+ }
+ }
+}
+
+impl CodeCache for DenoCompileCodeCache {
+ fn get_sync(
+ &self,
+ specifier: &ModuleSpecifier,
+ code_cache_type: CodeCacheType,
+ source_hash: u64,
+ ) -> Option<Vec<u8>> {
+ match &self.strategy {
+ CodeCacheStrategy::FirstRun(strategy) => {
+ if !strategy.is_finished.is_raised() {
+ // we keep track of how many times the cache is requested
+ // then serialize the cache when we get that number of
+ // "set" calls
+ strategy.data.lock().add_count += 1;
+ }
+ None
+ }
+ CodeCacheStrategy::SubsequentRun(strategy) => {
+ if strategy.is_finished.is_raised() {
+ return None;
+ }
+ strategy.take_from_cache(specifier, code_cache_type, source_hash)
+ }
+ }
+ }
+
+ fn set_sync(
+ &self,
+ specifier: ModuleSpecifier,
+ code_cache_type: CodeCacheType,
+ source_hash: u64,
+ bytes: &[u8],
+ ) {
+ match &self.strategy {
+ CodeCacheStrategy::FirstRun(strategy) => {
+ if strategy.is_finished.is_raised() {
+ return;
+ }
+
+ let data_to_serialize = {
+ let mut data = strategy.data.lock();
+ data.cache.insert(
+ (specifier.to_string(), code_cache_type),
+ DenoCompileCodeCacheEntry {
+ source_hash,
+ data: bytes.to_vec(),
+ },
+ );
+ if data.add_count != 0 {
+ data.add_count -= 1;
+ }
+ if data.add_count == 0 {
+ // don't allow using the cache anymore
+ strategy.is_finished.raise();
+ if data.cache.is_empty() {
+ None
+ } else {
+ Some(std::mem::take(&mut data.cache))
+ }
+ } else {
+ None
+ }
+ };
+ if let Some(cache_data) = &data_to_serialize {
+ strategy.write_cache_data(cache_data);
+ }
+ }
+ CodeCacheStrategy::SubsequentRun(_) => {
+ // do nothing
+ }
+ }
+ }
+}
+
+impl CliCodeCache for DenoCompileCodeCache {
+ fn enabled(&self) -> bool {
+ match &self.strategy {
+ CodeCacheStrategy::FirstRun(strategy) => {
+ !strategy.is_finished.is_raised()
+ }
+ CodeCacheStrategy::SubsequentRun(strategy) => {
+ !strategy.is_finished.is_raised()
+ }
+ }
+ }
+
+ fn as_code_cache(self: Arc<Self>) -> Arc<dyn CodeCache> {
+ self
+ }
+}
+
+type CodeCacheKey = (String, CodeCacheType);
+
+struct FirstRunCodeCacheData {
+ cache: HashMap<CodeCacheKey, DenoCompileCodeCacheEntry>,
+ add_count: usize,
+}
+
+struct FirstRunCodeCacheStrategy {
+ cache_key: u64,
+ file_path: PathBuf,
+ is_finished: AtomicFlag,
+ data: Mutex<FirstRunCodeCacheData>,
+}
+
+impl FirstRunCodeCacheStrategy {
+ fn write_cache_data(
+ &self,
+ cache_data: &HashMap<CodeCacheKey, DenoCompileCodeCacheEntry>,
+ ) {
+ let count = cache_data.len();
+ let temp_file = get_atomic_file_path(&self.file_path);
+ match serialize(&temp_file, self.cache_key, cache_data) {
+ Ok(()) => {
+ if let Err(err) = std::fs::rename(&temp_file, &self.file_path) {
+ log::debug!("Failed to rename code cache: {}", err);
+ } else {
+ log::debug!("Serialized {} code cache entries", count);
+ }
+ }
+ Err(err) => {
+ let _ = std::fs::remove_file(&temp_file);
+ log::debug!("Failed to serialize code cache: {}", err);
+ }
+ }
+ }
+}
+
+struct SubsequentRunCodeCacheStrategy {
+ is_finished: AtomicFlag,
+ data: Mutex<HashMap<CodeCacheKey, DenoCompileCodeCacheEntry>>,
+}
+
+impl SubsequentRunCodeCacheStrategy {
+ fn take_from_cache(
+ &self,
+ specifier: &ModuleSpecifier,
+ code_cache_type: CodeCacheType,
+ source_hash: u64,
+ ) -> Option<Vec<u8>> {
+ let mut data = self.data.lock();
+ // todo(dsherret): how to avoid the clone here?
+ let entry = data.remove(&(specifier.to_string(), code_cache_type))?;
+ if entry.source_hash != source_hash {
+ return None;
+ }
+ if data.is_empty() {
+ self.is_finished.raise();
+ }
+ Some(entry.data)
+ }
+}
+
+/// File format:
+/// - <header>
+/// - <cache key>
+/// - <u32: number of entries>
+/// - <[entry length]> - u64 * number of entries
+/// - <[entry]>
+/// - <[u8]: entry data>
+/// - <String: specifier>
+/// - <u8>: code cache type
+/// - <u32: specifier length>
+/// - <u64: source hash>
+/// - <u64: entry data hash>
+fn serialize(
+ file_path: &Path,
+ cache_key: u64,
+ cache: &HashMap<CodeCacheKey, DenoCompileCodeCacheEntry>,
+) -> Result<(), AnyError> {
+ let cache_file = std::fs::OpenOptions::new()
+ .create(true)
+ .truncate(true)
+ .write(true)
+ .open(file_path)?;
+ let mut writer = BufWriter::new(cache_file);
+ serialize_with_writer(&mut writer, cache_key, cache)
+}
+
+fn serialize_with_writer<T: Write>(
+ writer: &mut BufWriter<T>,
+ cache_key: u64,
+ cache: &HashMap<CodeCacheKey, DenoCompileCodeCacheEntry>,
+) -> Result<(), AnyError> {
+ // header
+ writer.write_all(&cache_key.to_le_bytes())?;
+ writer.write_all(&(cache.len() as u32).to_le_bytes())?;
+ // lengths of each entry
+ for ((specifier, _), entry) in cache {
+ let len: u64 =
+ entry.data.len() as u64 + specifier.len() as u64 + 1 + 4 + 8 + 8;
+ writer.write_all(&len.to_le_bytes())?;
+ }
+ // entries
+ for ((specifier, code_cache_type), entry) in cache {
+ writer.write_all(&entry.data)?;
+ writer.write_all(&[match code_cache_type {
+ CodeCacheType::EsModule => 0,
+ CodeCacheType::Script => 1,
+ }])?;
+ writer.write_all(specifier.as_bytes())?;
+ writer.write_all(&(specifier.len() as u32).to_le_bytes())?;
+ writer.write_all(&entry.source_hash.to_le_bytes())?;
+ let hash: u64 = FastInsecureHasher::new_without_deno_version()
+ .write(&entry.data)
+ .finish();
+ writer.write_all(&hash.to_le_bytes())?;
+ }
+
+ writer.flush()?;
+
+ Ok(())
+}
+
+fn deserialize(
+ file_path: &Path,
+ expected_cache_key: u64,
+) -> Result<HashMap<CodeCacheKey, DenoCompileCodeCacheEntry>, AnyError> {
+ let cache_file = std::fs::File::open(file_path)?;
+ let mut reader = BufReader::new(cache_file);
+ deserialize_with_reader(&mut reader, expected_cache_key)
+}
+
+fn deserialize_with_reader<T: Read>(
+ reader: &mut BufReader<T>,
+ expected_cache_key: u64,
+) -> Result<HashMap<CodeCacheKey, DenoCompileCodeCacheEntry>, AnyError> {
+ // it's very important to use this below so that a corrupt cache file
+ // doesn't cause a memory allocation error
+ fn new_vec_sized<T: Clone>(
+ capacity: usize,
+ default_value: T,
+ ) -> Result<Vec<T>, AnyError> {
+ let mut vec = Vec::new();
+ vec.try_reserve(capacity)?;
+ vec.resize(capacity, default_value);
+ Ok(vec)
+ }
+
+ fn try_subtract(a: usize, b: usize) -> Result<usize, AnyError> {
+ if a < b {
+ bail!("Integer underflow");
+ }
+ Ok(a - b)
+ }
+
+ let mut header_bytes = vec![0; 8 + 4];
+ reader.read_exact(&mut header_bytes)?;
+ let actual_cache_key = u64::from_le_bytes(header_bytes[..8].try_into()?);
+ if actual_cache_key != expected_cache_key {
+ // cache bust
+ bail!("Cache key mismatch");
+ }
+ let len = u32::from_le_bytes(header_bytes[8..].try_into()?) as usize;
+ // read the lengths for each entry found in the file
+ let entry_len_bytes_capacity = len * 8;
+ let mut entry_len_bytes = new_vec_sized(entry_len_bytes_capacity, 0)?;
+ reader.read_exact(&mut entry_len_bytes)?;
+ let mut lengths = Vec::new();
+ lengths.try_reserve(len)?;
+ for i in 0..len {
+ let pos = i * 8;
+ lengths.push(
+ u64::from_le_bytes(entry_len_bytes[pos..pos + 8].try_into()?) as usize,
+ );
+ }
+
+ let mut map = HashMap::new();
+ map.try_reserve(len)?;
+ for len in lengths {
+ let mut buffer = new_vec_sized(len, 0)?;
+ reader.read_exact(&mut buffer)?;
+ let entry_data_hash_start_pos = try_subtract(buffer.len(), 8)?;
+ let expected_entry_data_hash =
+ u64::from_le_bytes(buffer[entry_data_hash_start_pos..].try_into()?);
+ let source_hash_start_pos = try_subtract(entry_data_hash_start_pos, 8)?;
+ let source_hash = u64::from_le_bytes(
+ buffer[source_hash_start_pos..entry_data_hash_start_pos].try_into()?,
+ );
+ let specifier_end_pos = try_subtract(source_hash_start_pos, 4)?;
+ let specifier_len = u32::from_le_bytes(
+ buffer[specifier_end_pos..source_hash_start_pos].try_into()?,
+ ) as usize;
+ let specifier_start_pos = try_subtract(specifier_end_pos, specifier_len)?;
+ let specifier = String::from_utf8(
+ buffer[specifier_start_pos..specifier_end_pos].to_vec(),
+ )?;
+ let code_cache_type_pos = try_subtract(specifier_start_pos, 1)?;
+ let code_cache_type = match buffer[code_cache_type_pos] {
+ 0 => CodeCacheType::EsModule,
+ 1 => CodeCacheType::Script,
+ _ => bail!("Invalid code cache type"),
+ };
+ buffer.truncate(code_cache_type_pos);
+ let actual_entry_data_hash: u64 =
+ FastInsecureHasher::new_without_deno_version()
+ .write(&buffer)
+ .finish();
+ if expected_entry_data_hash != actual_entry_data_hash {
+ bail!("Hash mismatch.")
+ }
+ map.insert(
+ (specifier, code_cache_type),
+ DenoCompileCodeCacheEntry {
+ source_hash,
+ data: buffer,
+ },
+ );
+ }
+
+ Ok(map)
+}
+
+#[cfg(test)]
+mod test {
+ use test_util::TempDir;
+
+ use super::*;
+ use std::fs::File;
+
+ #[test]
+ fn serialize_deserialize() {
+ let cache_key = 123456;
+ let cache = {
+ let mut cache = HashMap::new();
+ cache.insert(
+ ("specifier1".to_string(), CodeCacheType::EsModule),
+ DenoCompileCodeCacheEntry {
+ source_hash: 1,
+ data: vec![1, 2, 3],
+ },
+ );
+ cache.insert(
+ ("specifier2".to_string(), CodeCacheType::EsModule),
+ DenoCompileCodeCacheEntry {
+ source_hash: 2,
+ data: vec![4, 5, 6],
+ },
+ );
+ cache.insert(
+ ("specifier2".to_string(), CodeCacheType::Script),
+ DenoCompileCodeCacheEntry {
+ source_hash: 2,
+ data: vec![6, 5, 1],
+ },
+ );
+ cache
+ };
+ let mut buffer = Vec::new();
+ serialize_with_writer(&mut BufWriter::new(&mut buffer), cache_key, &cache)
+ .unwrap();
+ let deserialized =
+ deserialize_with_reader(&mut BufReader::new(&buffer[..]), cache_key)
+ .unwrap();
+ assert_eq!(cache, deserialized);
+ }
+
+ #[test]
+ fn serialize_deserialize_empty() {
+ let cache_key = 1234;
+ let cache = HashMap::new();
+ let mut buffer = Vec::new();
+ serialize_with_writer(&mut BufWriter::new(&mut buffer), cache_key, &cache)
+ .unwrap();
+ let deserialized =
+ deserialize_with_reader(&mut BufReader::new(&buffer[..]), cache_key)
+ .unwrap();
+ assert_eq!(cache, deserialized);
+ }
+
+ #[test]
+ fn serialize_deserialize_corrupt() {
+ let buffer = "corrupttestingtestingtesting".as_bytes().to_vec();
+ let err = deserialize_with_reader(&mut BufReader::new(&buffer[..]), 1234)
+ .unwrap_err();
+ assert_eq!(err.to_string(), "Cache key mismatch");
+ }
+
+ #[test]
+ fn code_cache() {
+ let temp_dir = TempDir::new();
+ let file_path = temp_dir.path().join("cache.bin").to_path_buf();
+ let url1 = ModuleSpecifier::parse("https://deno.land/example1.js").unwrap();
+ let url2 = ModuleSpecifier::parse("https://deno.land/example2.js").unwrap();
+ // first run
+ {
+ let code_cache = DenoCompileCodeCache::new(file_path.clone(), 1234);
+ assert!(code_cache
+ .get_sync(&url1, CodeCacheType::EsModule, 0)
+ .is_none());
+ assert!(code_cache
+ .get_sync(&url2, CodeCacheType::EsModule, 1)
+ .is_none());
+ assert!(code_cache.enabled());
+ code_cache.set_sync(url1.clone(), CodeCacheType::EsModule, 0, &[1, 2, 3]);
+ assert!(code_cache.enabled());
+ assert!(!file_path.exists());
+ code_cache.set_sync(url2.clone(), CodeCacheType::EsModule, 1, &[2, 1, 3]);
+ assert!(file_path.exists()); // now the new code cache exists
+ assert!(!code_cache.enabled()); // no longer enabled
+ }
+ // second run
+ {
+ let code_cache = DenoCompileCodeCache::new(file_path.clone(), 1234);
+ assert!(code_cache.enabled());
+ let result1 = code_cache
+ .get_sync(&url1, CodeCacheType::EsModule, 0)
+ .unwrap();
+ assert!(code_cache.enabled());
+ let result2 = code_cache
+ .get_sync(&url2, CodeCacheType::EsModule, 1)
+ .unwrap();
+ assert!(!code_cache.enabled()); // no longer enabled
+ assert_eq!(result1, vec![1, 2, 3]);
+ assert_eq!(result2, vec![2, 1, 3]);
+ }
+
+ // new cache key first run
+ {
+ let code_cache = DenoCompileCodeCache::new(file_path.clone(), 54321);
+ assert!(code_cache
+ .get_sync(&url1, CodeCacheType::EsModule, 0)
+ .is_none());
+ assert!(code_cache
+ .get_sync(&url2, CodeCacheType::EsModule, 1)
+ .is_none());
+ code_cache.set_sync(url1.clone(), CodeCacheType::EsModule, 0, &[2, 2, 3]);
+ code_cache.set_sync(url2.clone(), CodeCacheType::EsModule, 1, &[3, 2, 3]);
+ }
+ // new cache key second run
+ {
+ let code_cache = DenoCompileCodeCache::new(file_path.clone(), 54321);
+ let result1 = code_cache
+ .get_sync(&url1, CodeCacheType::EsModule, 0)
+ .unwrap();
+ assert_eq!(result1, vec![2, 2, 3]);
+ assert!(code_cache
+ .get_sync(&url2, CodeCacheType::EsModule, 5) // different hash will cause none
+ .is_none());
+ }
+ }
+}
diff --git a/cli/standalone/mod.rs b/cli/standalone/mod.rs
index e3449c152..b9f0b1d5b 100644
--- a/cli/standalone/mod.rs
+++ b/cli/standalone/mod.rs
@@ -7,6 +7,7 @@
use binary::StandaloneData;
use binary::StandaloneModules;
+use code_cache::DenoCompileCodeCache;
use deno_ast::MediaType;
use deno_cache_dir::npm::NpmCacheDir;
use deno_config::workspace::MappedResolution;
@@ -17,6 +18,7 @@ use deno_core::anyhow::Context;
use deno_core::error::generic_error;
use deno_core::error::type_error;
use deno_core::error::AnyError;
+use deno_core::futures::future::LocalBoxFuture;
use deno_core::futures::FutureExt;
use deno_core::v8_set_flags;
use deno_core::FastString;
@@ -27,6 +29,7 @@ use deno_core::ModuleSpecifier;
use deno_core::ModuleType;
use deno_core::RequestedModuleType;
use deno_core::ResolutionKind;
+use deno_core::SourceCodeCacheInfo;
use deno_npm::npm_rc::ResolvedNpmRc;
use deno_package_json::PackageJsonDepValue;
use deno_resolver::npm::NpmReqResolverOptions;
@@ -64,6 +67,7 @@ use crate::args::StorageKeyResolver;
use crate::cache::Caches;
use crate::cache::DenoCacheEnvFsAdapter;
use crate::cache::DenoDirProvider;
+use crate::cache::FastInsecureHasher;
use crate::cache::NodeAnalysisCache;
use crate::cache::RealDenoCacheEnv;
use crate::http_util::HttpClientProvider;
@@ -86,12 +90,14 @@ use crate::resolver::NpmModuleLoader;
use crate::util::progress_bar::ProgressBar;
use crate::util::progress_bar::ProgressBarStyle;
use crate::util::v8::construct_v8_flags;
+use crate::worker::CliCodeCache;
use crate::worker::CliMainWorkerFactory;
use crate::worker::CliMainWorkerOptions;
use crate::worker::CreateModuleLoaderResult;
use crate::worker::ModuleLoaderFactory;
pub mod binary;
+mod code_cache;
mod file_system;
mod serialization;
mod virtual_fs;
@@ -113,6 +119,35 @@ struct SharedModuleLoaderState {
npm_req_resolver: Arc<CliNpmReqResolver>,
npm_resolver: Arc<dyn CliNpmResolver>,
workspace_resolver: WorkspaceResolver,
+ code_cache: Option<Arc<dyn CliCodeCache>>,
+}
+
+impl SharedModuleLoaderState {
+ fn get_code_cache(
+ &self,
+ specifier: &ModuleSpecifier,
+ source: &[u8],
+ ) -> Option<SourceCodeCacheInfo> {
+ let Some(code_cache) = &self.code_cache else {
+ return None;
+ };
+ if !code_cache.enabled() {
+ return None;
+ }
+ // deno version is already included in the root cache key
+ let hash = FastInsecureHasher::new_without_deno_version()
+ .write_hashable(source)
+ .finish();
+ let data = code_cache.get_sync(
+ specifier,
+ deno_runtime::code_cache::CodeCacheType::EsModule,
+ hash,
+ );
+ Some(SourceCodeCacheInfo {
+ hash,
+ data: data.map(Cow::Owned),
+ })
+ }
}
#[derive(Clone)]
@@ -329,14 +364,19 @@ impl ModuleLoader for EmbeddedModuleLoader {
}
if self.shared.node_resolver.in_npm_package(original_specifier) {
- let npm_module_loader = self.shared.npm_module_loader.clone();
+ let shared = self.shared.clone();
let original_specifier = original_specifier.clone();
let maybe_referrer = maybe_referrer.cloned();
return deno_core::ModuleLoadResponse::Async(
async move {
- let code_source = npm_module_loader
+ let code_source = shared
+ .npm_module_loader
.load(&original_specifier, maybe_referrer.as_ref())
.await?;
+ let code_cache_entry = shared.get_code_cache(
+ &code_source.found_url,
+ code_source.code.as_bytes(),
+ );
Ok(deno_core::ModuleSource::new_with_redirect(
match code_source.media_type {
MediaType::Json => ModuleType::Json,
@@ -345,7 +385,7 @@ impl ModuleLoader for EmbeddedModuleLoader {
code_source.code,
&original_specifier,
&code_source.found_url,
- None,
+ code_cache_entry,
))
}
.boxed_local(),
@@ -398,25 +438,30 @@ impl ModuleLoader for EmbeddedModuleLoader {
ModuleSourceCode::String(FastString::from_static(source))
}
};
+ let code_cache_entry = shared
+ .get_code_cache(&module_specifier, module_source.as_bytes());
Ok(deno_core::ModuleSource::new_with_redirect(
module_type,
module_source,
&original_specifier,
&module_specifier,
- None,
+ code_cache_entry,
))
}
.boxed_local(),
)
} else {
let module_source = module_source.into_for_v8();
+ let code_cache_entry = self
+ .shared
+ .get_code_cache(module_specifier, module_source.as_bytes());
deno_core::ModuleLoadResponse::Sync(Ok(
deno_core::ModuleSource::new_with_redirect(
module_type,
module_source,
original_specifier,
module_specifier,
- None,
+ code_cache_entry,
),
))
}
@@ -429,6 +474,23 @@ impl ModuleLoader for EmbeddedModuleLoader {
))),
}
}
+
+ fn code_cache_ready(
+ &self,
+ specifier: ModuleSpecifier,
+ source_hash: u64,
+ code_cache_data: &[u8],
+ ) -> LocalBoxFuture<'static, ()> {
+ if let Some(code_cache) = &self.shared.code_cache {
+ code_cache.set_sync(
+ specifier,
+ deno_runtime::code_cache::CodeCacheType::EsModule,
+ source_hash,
+ code_cache_data,
+ );
+ }
+ std::future::ready(()).boxed_local()
+ }
}
impl NodeRequireLoader for EmbeddedModuleLoader {
@@ -739,6 +801,19 @@ pub async fn run(data: StandaloneData) -> Result<i32, AnyError> {
metadata.workspace_resolver.pkg_json_resolution,
)
};
+ let code_cache = match metadata.code_cache_key {
+ Some(code_cache_key) => Some(Arc::new(DenoCompileCodeCache::new(
+ root_path.with_file_name(format!(
+ "{}.cache",
+ root_path.file_name().unwrap().to_string_lossy()
+ )),
+ code_cache_key,
+ )) as Arc<dyn CliCodeCache>),
+ None => {
+ log::debug!("Code cache disabled.");
+ None
+ }
+ };
let module_loader_factory = StandaloneModuleLoaderFactory {
shared: Arc::new(SharedModuleLoaderState {
cjs_tracker: cjs_tracker.clone(),
@@ -751,6 +826,7 @@ pub async fn run(data: StandaloneData) -> Result<i32, AnyError> {
fs.clone(),
node_code_translator,
)),
+ code_cache: code_cache.clone(),
npm_resolver: npm_resolver.clone(),
workspace_resolver,
npm_req_resolver,
@@ -792,8 +868,7 @@ pub async fn run(data: StandaloneData) -> Result<i32, AnyError> {
});
let worker_factory = CliMainWorkerFactory::new(
Arc::new(BlobStore::default()),
- // Code cache is not supported for standalone binary yet.
- None,
+ code_cache,
feature_checker,
fs,
None,
diff --git a/cli/worker.rs b/cli/worker.rs
index 24397b6bf..3b09714d5 100644
--- a/cli/worker.rs
+++ b/cli/worker.rs
@@ -83,6 +83,15 @@ pub trait HmrRunner: Send + Sync {
async fn run(&mut self) -> Result<(), AnyError>;
}
+pub trait CliCodeCache: code_cache::CodeCache {
+ /// Gets if the code cache is still enabled.
+ fn enabled(&self) -> bool {
+ true
+ }
+
+ fn as_code_cache(self: Arc<Self>) -> Arc<dyn code_cache::CodeCache>;
+}
+
#[async_trait::async_trait(?Send)]
pub trait CoverageCollector: Send + Sync {
async fn start_collecting(&mut self) -> Result<(), AnyError>;
@@ -127,7 +136,7 @@ pub struct CliMainWorkerOptions {
struct SharedWorkerState {
blob_store: Arc<BlobStore>,
broadcast_channel: InMemoryBroadcastChannel,
- code_cache: Option<Arc<dyn code_cache::CodeCache>>,
+ code_cache: Option<Arc<dyn CliCodeCache>>,
compiled_wasm_module_store: CompiledWasmModuleStore,
feature_checker: Arc<FeatureChecker>,
fs: Arc<dyn deno_fs::FileSystem>,
@@ -393,7 +402,7 @@ impl CliMainWorkerFactory {
#[allow(clippy::too_many_arguments)]
pub fn new(
blob_store: Arc<BlobStore>,
- code_cache: Option<Arc<dyn code_cache::CodeCache>>,
+ code_cache: Option<Arc<dyn CliCodeCache>>,
feature_checker: Arc<FeatureChecker>,
fs: Arc<dyn deno_fs::FileSystem>,
maybe_file_watcher_communicator: Option<Arc<WatcherCommunicator>>,
@@ -554,7 +563,7 @@ impl CliMainWorkerFactory {
),
feature_checker,
permissions,
- v8_code_cache: shared.code_cache.clone(),
+ v8_code_cache: shared.code_cache.clone().map(|c| c.as_code_cache()),
};
let options = WorkerOptions {