Repository: denoland/deno
Pull request: perf: pass transpiled module to deno_core as known string

When using transpile cache:

```
% hyperfine --warmup 4 "../deno/target/release/deno run -A main.ts" "../deno/target/release/deno_main run -A main.ts"
Benchmark 1: ../deno/target/release/deno run -A main.ts
  Time (mean ± σ):     101.8 ms ±   1.0 ms    [User: 82.7 ms, System: 24.8 ms]
  Range (min … max):    99.6 ms … 103.9 ms    28 runs
 
Benchmark 2: ../deno/target/release/deno_main run -A main.ts
  Time (mean ± σ):     107.4 ms ±   1.6 ms    [User: 86.9 ms, System: 25.9 ms]
  Range (min … max):   104.3 ms … 112.1 ms    27 runs
 
Summary
  ../deno/target/release/deno run -A main.ts ran
    1.05 ± 0.02 times faster than ../deno/target/release/deno_main run -A main.ts
```

No transpile cache:

```
% DENO_DIR=$pwd/deno_dir hyperfine --prepare "rm -rf deno_dir" --warmup 4 "../deno/target/release/deno run -A main.ts" "../deno/target/release/deno_main run -A main.ts"
Benchmark 1: ../deno/target/release/deno run -A main.ts
  Time (mean ± σ):      2.479 s ±  0.016 s    [User: 4.259 s, System: 0.132 s]
  Range (min … max):    2.458 s …  2.508 s    10 runs
 
Benchmark 2: ../deno/target/release/deno_main run -A main.ts
  Time (mean ± σ):      2.493 s ±  0.012 s    [User: 4.273 s, System: 0.134 s]
  Range (min … max):    2.478 s …  2.514 s    10 runs
 
Summary
  ../deno/target/release/deno run -A main.ts ran
    1.01 ± 0.01 times faster than ../deno/target/release/deno_main run -A main.ts
```

I noticed this when working on the deno compile changes because I had a regression in performance when just providing deno_core with bytes.

<details>
<summary>Files</summary>

typescript1.ts and typescript2.ts are two copies of the typescript compiler as typescript files (not JS files)

```ts
import { ts as ts1 } from "./typescript1.ts";
import { ts as ts2 } from "./typescript2.ts";

console.log(ts1.version);
console.log(ts2.version);

```
</details>

Changed files:
filename: cli/cache/emit.rs
patch: @@ -39,7 +39,7 @@ impl EmitCache {
     &self,
     specifier: &ModuleSpecifier,
     expected_source_hash: u64,
-  ) -> Option<Vec<u8>> {
+  ) -> Option<String> {
     let emit_filename = self.get_emit_filename(specifier)?;
     let bytes = self.disk_cache.get(&emit_filename).ok()?;
     self
@@ -100,7 +100,7 @@ impl EmitFileSerializer {
     &self,
     mut bytes: Vec<u8>,
     expected_source_hash: u64,
-  ) -> Option<Vec<u8>> {
+  ) -> Option<String> {
     let last_newline_index = bytes.iter().rposition(|&b| b == b'\n')?;
     let (content, last_line) = bytes.split_at(last_newline_index);
     let hashes = last_line.strip_prefix(LAST_LINE_PREFIX.as_bytes())?;
@@ -120,7 +120,7 @@ impl EmitFileSerializer {
 
     // everything looks good, truncate and return it
     bytes.truncate(content.len());
-    Some(bytes)
+    String::from_utf8(bytes).ok()
   }
 
   pub fn serialize(&self, code: &[u8], source_hash: u64) -> Vec<u8> {
@@ -170,8 +170,6 @@ mod test {
       },
       emit_failed_flag: Default::default(),
     };
-    let to_string =
-      |bytes: Vec<u8>| -> String { String::from_utf8(bytes).unwrap() };
 
     let specifier1 =
       ModuleSpecifier::from_file_path(temp_dir.path().join("file1.ts"))
@@ -188,13 +186,10 @@ mod test {
     assert_eq!(cache.get_emit_code(&specifier1, 5), None);
     // providing the correct source hash
     assert_eq!(
-      cache.get_emit_code(&specifier1, 10).map(to_string),
+      cache.get_emit_code(&specifier1, 10),
       Some(emit_code1.clone()),
     );
-    assert_eq!(
-      cache.get_emit_code(&specifier2, 2).map(to_string),
-      Some(emit_code2)
-    );
+    assert_eq!(cache.get_emit_code(&specifier2, 2), Some(emit_code2));
 
     // try changing the cli version (should not load previous ones)
     let cache = EmitCache {
@@ -215,18 +210,12 @@ mod test {
       },
       emit_failed_flag: Default::default(),
     };
-    assert_eq!(
-      cache.get_emit_code(&specifier1, 5).map(to_string),
-      Some(emit_code1)
-    );
+    assert_eq!(cache.get_emit_code(&specifier1, 5), Some(emit_code1));
 
     // adding when already exists should not cause issue
     let emit_code3 = "asdf".to_string();
     cache.set_emit_code(&specifier1, 20, emit_code3.as_bytes());
     assert_eq!(cache.get_emit_code(&specifier1, 5), None);
-    assert_eq!(
-      cache.get_emit_code(&specifier1, 20).map(to_string),
-      Some(emit_code3)
-    );
+    assert_eq!(cache.get_emit_code(&specifier1, 20), Some(emit_code3));
   }
 }

filename: cli/emit.rs
patch: @@ -13,7 +13,6 @@ use deno_core::error::AnyError;
 use deno_core::futures::stream::FuturesUnordered;
 use deno_core::futures::FutureExt;
 use deno_core::futures::StreamExt;
-use deno_core::ModuleCodeBytes;
 use deno_core::ModuleSpecifier;
 use deno_graph::MediaType;
 use deno_graph::Module;
@@ -94,7 +93,7 @@ impl Emitter {
     &self,
     specifier: &ModuleSpecifier,
     source: &str,
-  ) -> Option<Vec<u8>> {
+  ) -> Option<String> {
     let source_hash = self.get_source_hash(source);
     self.emit_cache.get_emit_code(specifier, source_hash)
   }
@@ -104,7 +103,7 @@ impl Emitter {
     specifier: &ModuleSpecifier,
     media_type: MediaType,
     source: &Arc<str>,
-  ) -> Result<ModuleCodeBytes, AnyError> {
+  ) -> Result<String, AnyError> {
     // Note: keep this in sync with the sync version below
     let helper = EmitParsedSourceHelper(self);
     match helper.pre_emit_parsed_source(specifier, source) {
@@ -113,7 +112,7 @@ impl Emitter {
         let parsed_source_cache = self.parsed_source_cache.clone();
         let transpile_and_emit_options =
           self.transpile_and_emit_options.clone();
-        let transpile_result = deno_core::unsync::spawn_blocking({
+        let transpiled_source = deno_core::unsync::spawn_blocking({
           let specifier = specifier.clone();
           let source = source.clone();
           move || -> Result<_, AnyError> {
@@ -129,11 +128,12 @@ impl Emitter {
         })
         .await
         .unwrap()?;
-        Ok(helper.post_emit_parsed_source(
+        helper.post_emit_parsed_source(
           specifier,
-          transpile_result,
+          &transpiled_source,
           source_hash,
-        ))
+        );
+        Ok(transpiled_source)
       }
     }
   }
@@ -143,25 +143,26 @@ impl Emitter {
     specifier: &ModuleSpecifier,
     media_type: MediaType,
     source: &Arc<str>,
-  ) -> Result<ModuleCodeBytes, AnyError> {
+  ) -> Result<String, AnyError> {
     // Note: keep this in sync with the async version above
     let helper = EmitParsedSourceHelper(self);
     match helper.pre_emit_parsed_source(specifier, source) {
       PreEmitResult::Cached(emitted_text) => Ok(emitted_text),
       PreEmitResult::NotCached { source_hash } => {
-        let transpile_result = EmitParsedSourceHelper::transpile(
+        let transpiled_source = EmitParsedSourceHelper::transpile(
           &self.parsed_source_cache,
           specifier,
           source.clone(),
           media_type,
           &self.transpile_and_emit_options.0,
           &self.transpile_and_emit_options.1,
         )?;
-        Ok(helper.post_emit_parsed_source(
+        helper.post_emit_parsed_source(
           specifier,
-          transpile_result,
+          &transpiled_source,
           source_hash,
-        ))
+        );
+        Ok(transpiled_source)
       }
     }
   }
@@ -227,7 +228,7 @@ impl Emitter {
 }
 
 enum PreEmitResult {
-  Cached(ModuleCodeBytes),
+  Cached(String),
   NotCached { source_hash: u64 },
 }
 
@@ -245,7 +246,7 @@ impl<'a> EmitParsedSourceHelper<'a> {
     if let Some(emit_code) =
       self.0.emit_cache.get_emit_code(specifier, source_hash)
     {
-      PreEmitResult::Cached(emit_code.into_boxed_slice().into())
+      PreEmitResult::Cached(emit_code)
     } else {
       PreEmitResult::NotCached { source_hash }
     }
@@ -258,21 +259,14 @@ impl<'a> EmitParsedSourceHelper<'a> {
     media_type: MediaType,
     transpile_options: &deno_ast::TranspileOptions,
     emit_options: &deno_ast::EmitOptions,
-  ) -> Result<TranspileResult, AnyError> {
+  ) -> Result<String, AnyError> {
     // nothing else needs the parsed source at this point, so remove from
     // the cache in order to not transpile owned
     let parsed_source = parsed_source_cache
       .remove_or_parse_module(specifier, source, media_type)?;
     ensure_no_import_assertion(&parsed_source)?;
-    Ok(parsed_source.transpile(transpile_options, emit_options)?)
-  }
-
-  pub fn post_emit_parsed_source(
-    &self,
-    specifier: &ModuleSpecifier,
-    transpile_result: TranspileResult,
-    source_hash: u64,
-  ) -> ModuleCodeBytes {
+    let transpile_result =
+      parsed_source.transpile(transpile_options, emit_options)?;
     let transpiled_source = match transpile_result {
       TranspileResult::Owned(source) => source,
       TranspileResult::Cloned(source) => {
@@ -281,12 +275,21 @@ impl<'a> EmitParsedSourceHelper<'a> {
       }
     };
     debug_assert!(transpiled_source.source_map.is_none());
+    let text = String::from_utf8(transpiled_source.source)?;
+    Ok(text)
+  }
+
+  pub fn post_emit_parsed_source(
+    &self,
+    specifier: &ModuleSpecifier,
+    transpiled_source: &str,
+    source_hash: u64,
+  ) {
     self.0.emit_cache.set_emit_code(
       specifier,
       source_hash,
-      &transpiled_source.source,
+      transpiled_source.as_bytes(),
     );
-    transpiled_source.source.into_boxed_slice().into()
   }
 }
 

filename: cli/module_loader.rs
patch: @@ -541,7 +541,8 @@ impl<TGraphContainer: ModuleGraphContainer>
         self.parsed_source_cache.free(specifier);
 
         Ok(Some(ModuleCodeStringSource {
-          code: ModuleSourceCode::Bytes(transpile_result),
+          // note: it's faster to provide a string if we know it's a string
+          code: ModuleSourceCode::String(transpile_result.into()),
           found_url: specifier.clone(),
           media_type,
         }))
@@ -571,7 +572,8 @@ impl<TGraphContainer: ModuleGraphContainer>
         self.parsed_source_cache.free(specifier);
 
         Ok(Some(ModuleCodeStringSource {
-          code: ModuleSourceCode::Bytes(transpile_result),
+          // note: it's faster to provide a string if we know it's a string
+          code: ModuleSourceCode::String(transpile_result.into()),
           found_url: specifier.clone(),
           media_type,
         }))

filename: cli/standalone/binary.rs
patch: @@ -613,7 +613,7 @@ impl<'a> DenoCompileBinaryWriter<'a> {
               .emitter
               .emit_parsed_source(&m.specifier, m.media_type, &m.source)
               .await?;
-            source.to_vec()
+            source.into_bytes()
           } else {
             m.source.as_bytes().to_vec()
           };

filename: cli/tools/coverage/mod.rs
patch: @@ -571,7 +571,7 @@ pub async fn cover_files(
       | MediaType::Cjs
       | MediaType::Mjs
       | MediaType::Json => None,
-      MediaType::Dts | MediaType::Dmts | MediaType::Dcts => Some(Vec::new()),
+      MediaType::Dts | MediaType::Dmts | MediaType::Dcts => Some(String::new()),
       MediaType::TypeScript
       | MediaType::Jsx
       | MediaType::Mts
@@ -593,8 +593,7 @@ pub async fn cover_files(
       }
     };
     let runtime_code: String = match transpiled_code {
-      Some(code) => String::from_utf8(code)
-        .with_context(|| format!("Failed decoding {}", file.specifier))?,
+      Some(code) => code,
       None => original_source.to_string(),
     };