-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
perf: use emit from swc instead of tsc (#15118)
- Loading branch information
Showing
28 changed files
with
663 additions
and
524 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,215 @@ | ||
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. | ||
|
||
use std::path::Path; | ||
|
||
use deno_ast::ModuleSpecifier; | ||
use deno_core::anyhow::Context; | ||
use deno_core::error::AnyError; | ||
use deno_runtime::deno_webstorage::rusqlite::params; | ||
use deno_runtime::deno_webstorage::rusqlite::Connection; | ||
|
||
use super::common::run_sqlite_pragma; | ||
|
||
/// The cache used to tell whether type checking should occur again. | ||
/// | ||
/// This simply stores a hash of the inputs of each successful type check | ||
/// and only clears them out when changing CLI versions. | ||
pub struct TypeCheckCache { | ||
conn: Connection, | ||
} | ||
|
||
impl TypeCheckCache { | ||
pub fn new(db_file_path: &Path) -> Result<Self, AnyError> { | ||
let conn = Connection::open(db_file_path).with_context(|| { | ||
format!( | ||
concat!( | ||
"Error opening type checking cache at {} -- ", | ||
"Perhaps it's corrupt. Maybe try deleting it." | ||
), | ||
db_file_path.display() | ||
) | ||
})?; | ||
Self::from_connection(conn, crate::version::deno()) | ||
} | ||
|
||
fn from_connection( | ||
conn: Connection, | ||
cli_version: String, | ||
) -> Result<Self, AnyError> { | ||
run_sqlite_pragma(&conn)?; | ||
create_tables(&conn, cli_version)?; | ||
|
||
Ok(Self { conn }) | ||
} | ||
|
||
pub fn has_check_hash(&self, hash: u64) -> bool { | ||
match self.hash_check_hash_result(hash) { | ||
Ok(val) => val, | ||
Err(err) => { | ||
if cfg!(debug_assertions) { | ||
panic!("Error retrieving hash: {}", err); | ||
} else { | ||
log::debug!("Error retrieving hash: {}", err); | ||
// fail silently when not debugging | ||
false | ||
} | ||
} | ||
} | ||
} | ||
|
||
fn hash_check_hash_result(&self, hash: u64) -> Result<bool, AnyError> { | ||
let query = "SELECT * FROM checkcache WHERE check_hash=?1 LIMIT 1"; | ||
let mut stmt = self.conn.prepare_cached(query)?; | ||
Ok(stmt.exists(params![hash.to_string()])?) | ||
} | ||
|
||
pub fn add_check_hash(&self, check_hash: u64) { | ||
if let Err(err) = self.add_check_hash_result(check_hash) { | ||
if cfg!(debug_assertions) { | ||
panic!("Error saving check hash: {}", err); | ||
} else { | ||
log::debug!("Error saving check hash: {}", err); | ||
} | ||
} | ||
} | ||
|
||
fn add_check_hash_result(&self, check_hash: u64) -> Result<(), AnyError> { | ||
let sql = " | ||
INSERT OR REPLACE INTO | ||
checkcache (check_hash) | ||
VALUES | ||
(?1)"; | ||
let mut stmt = self.conn.prepare_cached(sql)?; | ||
stmt.execute(params![&check_hash.to_string(),])?; | ||
Ok(()) | ||
} | ||
|
||
pub fn get_tsbuildinfo(&self, specifier: &ModuleSpecifier) -> Option<String> { | ||
let mut stmt = self | ||
.conn | ||
.prepare_cached("SELECT text FROM tsbuildinfo WHERE specifier=?1 LIMIT 1") | ||
.ok()?; | ||
let mut rows = stmt.query(params![specifier.to_string()]).ok()?; | ||
let row = rows.next().ok().flatten()?; | ||
|
||
row.get(0).ok() | ||
} | ||
|
||
pub fn set_tsbuildinfo(&self, specifier: &ModuleSpecifier, text: &str) { | ||
if let Err(err) = self.set_tsbuildinfo_result(specifier, text) { | ||
// should never error here, but if it ever does don't fail | ||
if cfg!(debug_assertions) { | ||
panic!("Error saving tsbuildinfo: {}", err); | ||
} else { | ||
log::debug!("Error saving tsbuildinfo: {}", err); | ||
} | ||
} | ||
} | ||
|
||
fn set_tsbuildinfo_result( | ||
&self, | ||
specifier: &ModuleSpecifier, | ||
text: &str, | ||
) -> Result<(), AnyError> { | ||
let mut stmt = self.conn.prepare_cached( | ||
"INSERT OR REPLACE INTO tsbuildinfo (specifier, text) VALUES (?1, ?2)", | ||
)?; | ||
stmt.execute(params![specifier.to_string(), text])?; | ||
Ok(()) | ||
} | ||
} | ||
|
||
fn create_tables( | ||
conn: &Connection, | ||
cli_version: String, | ||
) -> Result<(), AnyError> { | ||
// INT doesn't store up to u64, so use TEXT | ||
conn.execute( | ||
"CREATE TABLE IF NOT EXISTS checkcache ( | ||
check_hash TEXT PRIMARY KEY | ||
)", | ||
[], | ||
)?; | ||
conn.execute( | ||
"CREATE TABLE IF NOT EXISTS tsbuildinfo ( | ||
specifier TEXT PRIMARY KEY, | ||
text TEXT NOT NULL | ||
)", | ||
[], | ||
)?; | ||
conn.execute( | ||
"CREATE TABLE IF NOT EXISTS info ( | ||
key TEXT PRIMARY KEY, | ||
value TEXT NOT NULL | ||
)", | ||
[], | ||
)?; | ||
|
||
// delete the cache when the CLI version changes | ||
let data_cli_version: Option<String> = conn | ||
.query_row( | ||
"SELECT value FROM info WHERE key='CLI_VERSION' LIMIT 1", | ||
[], | ||
|row| row.get(0), | ||
) | ||
.ok(); | ||
if data_cli_version != Some(cli_version.to_string()) { | ||
conn.execute("DELETE FROM checkcache", params![])?; | ||
conn.execute("DELETE FROM tsbuildinfo", params![])?; | ||
let mut stmt = conn | ||
.prepare("INSERT OR REPLACE INTO info (key, value) VALUES (?1, ?2)")?; | ||
stmt.execute(params!["CLI_VERSION", &cli_version])?; | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use super::*; | ||
|
||
#[test] | ||
pub fn check_cache_general_use() { | ||
let conn = Connection::open_in_memory().unwrap(); | ||
let cache = | ||
TypeCheckCache::from_connection(conn, "1.0.0".to_string()).unwrap(); | ||
|
||
assert!(!cache.has_check_hash(1)); | ||
cache.add_check_hash(1); | ||
assert!(cache.has_check_hash(1)); | ||
assert!(!cache.has_check_hash(2)); | ||
|
||
let specifier1 = ModuleSpecifier::parse("file:///test.json").unwrap(); | ||
assert_eq!(cache.get_tsbuildinfo(&specifier1), None); | ||
cache.set_tsbuildinfo(&specifier1, "test"); | ||
assert_eq!(cache.get_tsbuildinfo(&specifier1), Some("test".to_string())); | ||
|
||
// try changing the cli version (should clear) | ||
let conn = cache.conn; | ||
let cache = | ||
TypeCheckCache::from_connection(conn, "2.0.0".to_string()).unwrap(); | ||
assert!(!cache.has_check_hash(1)); | ||
cache.add_check_hash(1); | ||
assert!(cache.has_check_hash(1)); | ||
assert_eq!(cache.get_tsbuildinfo(&specifier1), None); | ||
cache.set_tsbuildinfo(&specifier1, "test"); | ||
assert_eq!(cache.get_tsbuildinfo(&specifier1), Some("test".to_string())); | ||
|
||
// recreating the cache should not remove the data because the CLI version and state hash is the same | ||
let conn = cache.conn; | ||
let cache = | ||
TypeCheckCache::from_connection(conn, "2.0.0".to_string()).unwrap(); | ||
assert!(cache.has_check_hash(1)); | ||
assert!(!cache.has_check_hash(2)); | ||
assert_eq!(cache.get_tsbuildinfo(&specifier1), Some("test".to_string())); | ||
|
||
// adding when already exists should not cause issue | ||
cache.add_check_hash(1); | ||
assert!(cache.has_check_hash(1)); | ||
cache.set_tsbuildinfo(&specifier1, "other"); | ||
assert_eq!( | ||
cache.get_tsbuildinfo(&specifier1), | ||
Some("other".to_string()) | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. | ||
|
||
use deno_core::error::AnyError; | ||
use deno_runtime::deno_webstorage::rusqlite::Connection; | ||
|
||
/// Very fast non-cryptographically secure hash. | ||
pub fn fast_insecure_hash(bytes: &[u8]) -> u64 { | ||
use std::hash::Hasher; | ||
use twox_hash::XxHash64; | ||
|
||
let mut hasher = XxHash64::default(); | ||
hasher.write(bytes); | ||
hasher.finish() | ||
} | ||
|
||
/// Runs the common sqlite pragma. | ||
pub fn run_sqlite_pragma(conn: &Connection) -> Result<(), AnyError> { | ||
// Enable write-ahead-logging and tweak some other stuff | ||
let initial_pragmas = " | ||
-- enable write-ahead-logging mode | ||
PRAGMA journal_mode=WAL; | ||
PRAGMA synchronous=NORMAL; | ||
PRAGMA temp_store=memory; | ||
PRAGMA page_size=4096; | ||
PRAGMA mmap_size=6000000; | ||
PRAGMA optimize; | ||
"; | ||
|
||
conn.execute_batch(initial_pragmas)?; | ||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. | ||
|
||
use deno_ast::ModuleSpecifier; | ||
use deno_core::error::AnyError; | ||
|
||
use super::CacheType; | ||
use super::Cacher; | ||
|
||
/// Emit cache for a single file. | ||
#[derive(Debug, Clone, PartialEq)] | ||
pub struct SpecifierEmitCacheData { | ||
pub source_hash: String, | ||
pub text: String, | ||
pub map: Option<String>, | ||
} | ||
|
||
pub trait EmitCache { | ||
/// Gets the emit data from the cache. | ||
fn get_emit_data( | ||
&self, | ||
specifier: &ModuleSpecifier, | ||
) -> Option<SpecifierEmitCacheData>; | ||
/// Sets the emit data in the cache. | ||
fn set_emit_data( | ||
&self, | ||
specifier: ModuleSpecifier, | ||
data: SpecifierEmitCacheData, | ||
) -> Result<(), AnyError>; | ||
/// Gets the stored hash of the source of the provider specifier | ||
/// to tell if the emit is out of sync with the source. | ||
/// TODO(13302): this is actually not reliable and should be removed | ||
/// once switching to an sqlite db | ||
fn get_source_hash(&self, specifier: &ModuleSpecifier) -> Option<String>; | ||
/// Gets the emitted JavaScript of the TypeScript source. | ||
/// TODO(13302): remove this once switching to an sqlite db | ||
fn get_emit_text(&self, specifier: &ModuleSpecifier) -> Option<String>; | ||
} | ||
|
||
impl<T: Cacher> EmitCache for T { | ||
fn get_emit_data( | ||
&self, | ||
specifier: &ModuleSpecifier, | ||
) -> Option<SpecifierEmitCacheData> { | ||
Some(SpecifierEmitCacheData { | ||
source_hash: self.get_source_hash(specifier)?, | ||
text: self.get_emit_text(specifier)?, | ||
map: self.get(CacheType::SourceMap, specifier), | ||
}) | ||
} | ||
|
||
fn get_source_hash(&self, specifier: &ModuleSpecifier) -> Option<String> { | ||
self.get(CacheType::Version, specifier) | ||
} | ||
|
||
fn get_emit_text(&self, specifier: &ModuleSpecifier) -> Option<String> { | ||
self.get(CacheType::Emit, specifier) | ||
} | ||
|
||
fn set_emit_data( | ||
&self, | ||
specifier: ModuleSpecifier, | ||
data: SpecifierEmitCacheData, | ||
) -> Result<(), AnyError> { | ||
self.set(CacheType::Version, &specifier, data.source_hash)?; | ||
self.set(CacheType::Emit, &specifier, data.text)?; | ||
if let Some(map) = data.map { | ||
self.set(CacheType::SourceMap, &specifier, map)?; | ||
} | ||
Ok(()) | ||
} | ||
} |
Oops, something went wrong.