Skip to content

Commit

Permalink
perf: use emit from swc instead of tsc (#15118)
Browse files Browse the repository at this point in the history
  • Loading branch information
dsherret authored Jul 12, 2022
1 parent 7610764 commit 0c87dd1
Show file tree
Hide file tree
Showing 28 changed files with 663 additions and 524 deletions.
215 changes: 215 additions & 0 deletions cli/cache/check.rs
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())
);
}
}
31 changes: 31 additions & 0 deletions cli/cache/common.rs
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(())
}
13 changes: 7 additions & 6 deletions cli/disk_cache.rs → cli/cache/disk_cache.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.

use crate::cache::CacheType;
use crate::cache::Cacher;
use crate::cache::EmitMetadata;
use crate::fs_util;
use crate::http_cache::url_to_filename;

use super::CacheType;
use super::Cacher;
use super::EmitMetadata;

use deno_ast::ModuleSpecifier;
use deno_core::error::AnyError;
use deno_core::serde_json;
use deno_core::url::{Host, Url};
use deno_core::url::Host;
use deno_core::url::Url;
use std::ffi::OsStr;
use std::fs;
use std::io;
Expand Down Expand Up @@ -184,7 +187,6 @@ impl Cacher for DiskCache {
let extension = match cache_type {
CacheType::Emit => "js",
CacheType::SourceMap => "js.map",
CacheType::TypeScriptBuildInfo => "buildinfo",
CacheType::Version => {
return self.get_emit_metadata(specifier).map(|d| d.version_hash)
}
Expand All @@ -206,7 +208,6 @@ impl Cacher for DiskCache {
let extension = match cache_type {
CacheType::Emit => "js",
CacheType::SourceMap => "js.map",
CacheType::TypeScriptBuildInfo => "buildinfo",
CacheType::Version => {
let data = if let Some(mut data) = self.get_emit_metadata(specifier) {
data.version_hash = value;
Expand Down
71 changes: 71 additions & 0 deletions cli/cache/emit.rs
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(())
}
}
Loading

0 comments on commit 0c87dd1

Please sign in to comment.