Skip to content

Commit

Permalink
refactor: remove tsc snapshot (#27987)
Browse files Browse the repository at this point in the history
Removes the TSC snapshot to unblock the V8 upgrade (which enables shared
RO heap, and is incompatible with multiple snapshots).


this compresses the sources and declaration files as well, which leads
to a roughly 4.2MB reduction in binary size.

this currently adds about 80ms to deno check times, but code cache isn't
wired up for the extension code (namely `00_typescript.js`) yet

```
❯ hyperfine --warmup 5 -p "rm -rf ~/Library/Caches/deno/check_cache_v2" "../../deno/target/release-lite/deno check main.ts" "deno check main.ts"
Benchmark 1: ../../deno/target/release-lite/deno check main.ts
  Time (mean ± σ):     184.2 ms ±   2.2 ms    [User: 378.3 ms, System: 48.2 ms]
  Range (min … max):   181.5 ms … 189.9 ms    15 runs

Benchmark 2: deno check main.ts
  Time (mean ± σ):     107.4 ms ±   1.2 ms    [User: 155.3 ms, System: 23.9 ms]
  Range (min … max):   105.3 ms … 109.6 ms    26 runs

Summary
  deno check main.ts ran
    1.72 ± 0.03 times faster than ../../deno/target/release-lite/deno check main.ts
```

---------

Co-authored-by: Bartek Iwańczuk <[email protected]>
  • Loading branch information
nathanwhit and bartlomieju authored Feb 10, 2025
1 parent 7bd210f commit 174e496
Show file tree
Hide file tree
Showing 8 changed files with 601 additions and 582 deletions.
489 changes: 173 additions & 316 deletions cli/build.rs

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions cli/lsp/language_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -606,7 +606,7 @@ impl Inner {
ts_server.clone(),
diagnostics_state.clone(),
);
let assets = Assets::new(ts_server.clone());
let assets = Assets::new();
let initial_cwd = std::env::current_dir().unwrap_or_else(|_| {
panic!("Could not resolve current working directory")
});
Expand Down Expand Up @@ -913,7 +913,6 @@ impl Inner {
.await?;
self.ts_fixable_diagnostics = fixable_diagnostics;
}
self.assets.initialize(self.snapshot()).await;

self.performance.measure(mark);
Ok(InitializeResult {
Expand Down
127 changes: 28 additions & 99 deletions cli/lsp/tsc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1399,7 +1399,7 @@ fn new_assets_map() -> Arc<Mutex<AssetsMap>> {
.map(|(k, v)| {
let url_str = format!("asset:///{k}");
let specifier = resolve_url(&url_str).unwrap();
let asset = AssetDocument::new(specifier.clone(), v);
let asset = AssetDocument::new(specifier.clone(), v.get());
(specifier, asset)
})
.collect::<AssetsMap>();
Expand Down Expand Up @@ -1430,29 +1430,16 @@ impl AssetsSnapshot {
/// multiple threads without needing to worry about race conditions.
#[derive(Debug, Clone)]
pub struct Assets {
ts_server: Arc<TsServer>,
assets: Arc<Mutex<AssetsMap>>,
}

impl Assets {
pub fn new(ts_server: Arc<TsServer>) -> Self {
pub fn new() -> Self {
Self {
ts_server,
assets: new_assets_map(),
}
}

/// Initializes with the assets in the isolate.
pub async fn initialize(&self, state_snapshot: Arc<StateSnapshot>) {
let assets = get_isolate_assets(&self.ts_server, state_snapshot).await;
let mut assets_map = self.assets.lock();
for asset in assets {
if !assets_map.contains_key(asset.specifier()) {
assets_map.insert(asset.specifier().clone(), asset);
}
}
}

pub fn snapshot(&self) -> AssetsSnapshot {
// it's ok to not make a complete copy for snapshotting purposes
// because assets are static
Expand All @@ -1477,39 +1464,6 @@ impl Assets {
}
}

/// Get all the assets stored in the tsc isolate.
async fn get_isolate_assets(
ts_server: &TsServer,
state_snapshot: Arc<StateSnapshot>,
) -> Vec<AssetDocument> {
let req = TscRequest::GetAssets;
let res: Value = ts_server
.request(state_snapshot, req, None, &Default::default())
.await
.unwrap();
let response_assets = match res {
Value::Array(value) => value,
_ => unreachable!(),
};
let mut assets = Vec::with_capacity(response_assets.len());

for asset in response_assets {
let mut obj = match asset {
Value::Object(obj) => obj,
_ => unreachable!(),
};
let specifier_str = obj.get("specifier").unwrap().as_str().unwrap();
let specifier = ModuleSpecifier::parse(specifier_str).unwrap();
let text = match obj.remove("text").unwrap() {
Value::String(text) => text,
_ => unreachable!(),
};
assets.push(AssetDocument::new(specifier, text));
}

assets
}

fn get_tag_body_text(
tag: &JsDocTagInfo,
language_server: &language_server::Inner,
Expand Down Expand Up @@ -4547,6 +4501,21 @@ fn op_is_node_file(state: &mut OpState, #[string] path: String) -> bool {
r
}

#[op2]
#[serde]
fn op_libs() -> Vec<String> {
let mut out =
Vec::with_capacity(crate::tsc::LAZILY_LOADED_STATIC_ASSETS.len());
for key in crate::tsc::LAZILY_LOADED_STATIC_ASSETS.keys() {
let lib = key
.replace("lib.", "")
.replace(".d.ts", "")
.replace("deno_", "deno.");
out.push(lib);
}
out
}

#[derive(Debug, thiserror::Error, deno_error::JsError)]
enum LoadError {
#[error("{0}")]
Expand Down Expand Up @@ -4947,13 +4916,13 @@ fn run_tsc_thread(
// supplied snapshot is an isolate that contains the TypeScript language
// server.
let mut tsc_runtime = JsRuntime::new(RuntimeOptions {
extensions: vec![deno_tsc::init_ops(
extensions: vec![deno_tsc::init_ops_and_esm(
performance,
specifier_map,
request_rx,
)],
create_params: create_isolate_create_params(),
startup_snapshot: Some(tsc::compiler_snapshot()),
startup_snapshot: None,
inspector: has_inspector_server,
..Default::default()
});
Expand Down Expand Up @@ -5035,6 +5004,7 @@ deno_core::extension!(deno_tsc,
op_script_version,
op_project_version,
op_poll_requests,
op_libs,
],
options = {
performance: Arc<Performance>,
Expand All @@ -5049,6 +5019,14 @@ deno_core::extension!(deno_tsc,
options.request_rx,
));
},
customizer = |ext: &mut deno_core::Extension| {
use deno_core::ExtensionFileSource;
ext.esm_files.to_mut().push(ExtensionFileSource::new_computed("ext:deno_tsc/99_main_compiler.js", crate::tsc::MAIN_COMPILER_SOURCE.get().into()));
ext.esm_files.to_mut().push(ExtensionFileSource::new_computed("ext:deno_tsc/97_ts_host.js", crate::tsc::TS_HOST_SOURCE.get().into()));
ext.esm_files.to_mut().push(ExtensionFileSource::new_computed("ext:deno_tsc/98_lsp.js", crate::tsc::LSP_SOURCE.get().into()));
ext.js_files.to_mut().push(ExtensionFileSource::new_computed("ext:deno_cli_tsc/00_typescript.js", crate::tsc::TYPESCRIPT_SOURCE.get().into()));
ext.esm_entry_point = Some("ext:deno_tsc/99_main_compiler.js");
}
);

#[derive(Debug, Clone, Deserialize_repr, Serialize_repr)]
Expand Down Expand Up @@ -5428,7 +5406,6 @@ pub struct JsNull;
#[derive(Debug, Clone, Serialize)]
pub enum TscRequest {
GetDiagnostics((Vec<String>, usize)),
GetAssets,

CleanupSemanticCache,
// https://github.com/denoland/deno/blob/v1.37.1/cli/tsc/dts/typescript.d.ts#L6230
Expand Down Expand Up @@ -5634,7 +5611,6 @@ impl TscRequest {
("provideInlayHints", Some(serde_v8::to_v8(scope, args)?))
}
TscRequest::CleanupSemanticCache => ("cleanupSemanticCache", None),
TscRequest::GetAssets => ("$getAssets", None),
};

Ok(args)
Expand Down Expand Up @@ -5679,7 +5655,6 @@ impl TscRequest {
TscRequest::GetSignatureHelpItems(_) => "getSignatureHelpItems",
TscRequest::GetNavigateToItems(_) => "getNavigateToItems",
TscRequest::ProvideInlayHints(_) => "provideInlayHints",
TscRequest::GetAssets => "$getAssets",
}
}
}
Expand Down Expand Up @@ -6065,52 +6040,6 @@ mod tests {
);
}

#[tokio::test]
async fn test_request_assets() {
let (_, ts_server, snapshot, _) = setup(json!({}), &[]).await;
let assets = get_isolate_assets(&ts_server, snapshot).await;
let mut asset_names = assets
.iter()
.map(|a| {
a.specifier()
.to_string()
.replace("asset:///lib.", "")
.replace(".d.ts", "")
})
.collect::<Vec<_>>();
let mut expected_asset_names: Vec<String> = serde_json::from_str(
include_str!(concat!(env!("OUT_DIR"), "/lib_file_names.json")),
)
.unwrap();
asset_names.sort();

// if this test fails, update build.rs
expected_asset_names.sort();
assert_eq!(asset_names, expected_asset_names);

// get some notification when the size of the assets grows
let mut total_size = 0;
for asset in &assets {
total_size += asset.text().len();
}
assert!(total_size > 0);
#[allow(clippy::print_stderr)]
// currently as of TS 5.7, it's 3MB
if total_size > 3_500_000 {
let mut sizes = Vec::new();
for asset in &assets {
sizes.push((asset.specifier(), asset.text().len()));
}
sizes.sort_by_cached_key(|(_, size)| *size);
sizes.reverse();
for (specifier, size) in &sizes {
eprintln!("{}: {}", specifier, size);
}
eprintln!("Total size: {}", total_size);
panic!("Assets were quite large.");
}
}

#[tokio::test]
async fn test_modify_sources() {
let (temp_dir, ts_server, snapshot, cache) = setup(
Expand Down
14 changes: 0 additions & 14 deletions cli/tsc/97_ts_host.js
Original file line number Diff line number Diff line change
Expand Up @@ -855,17 +855,3 @@ const setTypesNodeIgnorableNames = new Set([
"WritableStreamDefaultWriter",
]);
ts.deno.setTypesNodeIgnorableNames(setTypesNodeIgnorableNames);

export function getAssets() {
/** @type {{ specifier: string; text: string; }[]} */
const assets = [];
for (const sourceFile of SOURCE_FILE_CACHE.values()) {
if (sourceFile.fileName.startsWith(ASSETS_URL_PREFIX)) {
assets.push({
specifier: sourceFile.fileName,
text: sourceFile.text,
});
}
}
return assets;
}
4 changes: 0 additions & 4 deletions cli/tsc/98_lsp.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
error,
filterMapDiagnostic,
fromTypeScriptDiagnostics,
getAssets,
getCreateSourceFileOptions,
host,
IS_NODE_SOURCE_FILE_CACHE,
Expand Down Expand Up @@ -446,9 +445,6 @@ function serverRequest(id, method, args, scope, maybeChange) {
ts.getSupportedCodeFixes(),
);
}
case "$getAssets": {
return respond(id, getAssets());
}
case "$getDiagnostics": {
const projectVersion = args[1];
// there's a possibility that we receive a change notification
Expand Down
32 changes: 9 additions & 23 deletions cli/tsc/99_main_compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,10 @@
delete Object.prototype.__proto__;

import {
assert,
AssertionError,
ASSETS_URL_PREFIX,
debug,
filterMapDiagnostic,
fromTypeScriptDiagnostics,
getAssets,
host,
setLogDebug,
} from "./97_ts_host.js";
Expand Down Expand Up @@ -214,31 +211,20 @@ function exec({ config, debug: debugFlag, rootNames, localOnly }) {
debug("<<< exec stop");
}

globalThis.snapshot = function (libs) {
for (const lib of libs) {
const specifier = `lib.${lib}.d.ts`;
// we are using internal APIs here to "inject" our custom libraries into
// tsc, so things like `"lib": [ "deno.ns" ]` are supported.
if (!ts.libs.includes(lib)) {
ts.libs.push(lib);
ts.libMap.set(lib, `lib.${lib}.d.ts`);
}
// we are caching in memory common type libraries that will be re-used by
// tsc on when the snapshot is restored
assert(
!!host.getSourceFile(
`${ASSETS_URL_PREFIX}${specifier}`,
ts.ScriptTarget.ESNext,
),
`failed to load '${ASSETS_URL_PREFIX}${specifier}'`,
);
const libs = ops.op_libs();
for (const lib of libs) {
const specifier = `lib.${lib}.d.ts`;
// we are using internal APIs here to "inject" our custom libraries into
// tsc, so things like `"lib": [ "deno.ns" ]` are supported.
if (!ts.libs.includes(lib)) {
ts.libs.push(lib);
ts.libMap.set(lib, specifier);
}
};
}

// exposes the functions that are called by `tsc::exec()` when type
// checking TypeScript.
globalThis.exec = exec;
globalThis.getAssets = getAssets;

// exposes the functions that are called when the compiler is used as a
// language service.
Expand Down
Loading

0 comments on commit 174e496

Please sign in to comment.