perf(core) Reduce script name and script code copies (#18298)

Reduce the number of copies and allocations of script code by carrying
around ownership/reference information from creation time.

As an advantage, this allows us to maintain the identity of `&'static
str`-based scripts and use v8's external 1-byte strings (to avoid
incorrectly passing non-ASCII strings, debug `assert!`s gate all string
reference paths).

Benchmark results:

Perf improvements -- ~0.1 - 0.2ms faster, but should reduce garbage
w/external strings and reduces data copies overall. May also unlock some
more interesting optimizations in the future.

This requires adding some generics to functions, but manual
monomorphization has been applied (outer/inner function) to avoid code
bloat.
This commit is contained in:
Matt Mastracci 2023-03-21 16:33:12 -06:00 committed by GitHub
parent 253b556e6f
commit 0b4770fa7d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 457 additions and 176 deletions

View File

@ -103,7 +103,8 @@ pub fn bench_js_async_with(
opts.benching_inner opts.benching_inner
}; };
let looped = loop_code(inner_iters, src); let looped = loop_code(inner_iters, src);
let src = looped.as_ref(); // Get a &'static str by leaking -- this is fine because it's benchmarking code
let src = Box::leak(looped.into_boxed_str());
if is_profiling() { if is_profiling() {
for _ in 0..opts.profiling_outer { for _ in 0..opts.profiling_outer {
tokio_runtime.block_on(inner_async(src, &mut runtime)); tokio_runtime.block_on(inner_async(src, &mut runtime));
@ -115,7 +116,7 @@ pub fn bench_js_async_with(
} }
} }
async fn inner_async(src: &str, runtime: &mut JsRuntime) { async fn inner_async(src: &'static str, runtime: &mut JsRuntime) {
runtime.execute_script("inner_loop", src).unwrap(); runtime.execute_script("inner_loop", src).unwrap();
runtime.run_event_loop(false).await.unwrap(); runtime.run_event_loop(false).await.unwrap();
} }

View File

@ -5,6 +5,7 @@ use crate::cache::FastInsecureHasher;
use crate::cache::ParsedSourceCache; use crate::cache::ParsedSourceCache;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::ModuleCode;
use deno_core::ModuleSpecifier; use deno_core::ModuleSpecifier;
use deno_graph::MediaType; use deno_graph::MediaType;
use std::sync::Arc; use std::sync::Arc;
@ -27,11 +28,11 @@ pub fn emit_parsed_source(
source: &Arc<str>, source: &Arc<str>,
emit_options: &deno_ast::EmitOptions, emit_options: &deno_ast::EmitOptions,
emit_config_hash: u64, emit_config_hash: u64,
) -> Result<String, AnyError> { ) -> Result<ModuleCode, AnyError> {
let source_hash = get_source_hash(source, emit_config_hash); let source_hash = get_source_hash(source, emit_config_hash);
if let Some(emit_code) = emit_cache.get_emit_code(specifier, source_hash) { if let Some(emit_code) = emit_cache.get_emit_code(specifier, source_hash) {
Ok(emit_code) Ok(emit_code.into())
} else { } else {
// this will use a cached version if it exists // this will use a cached version if it exists
let parsed_source = parsed_source_cache.get_or_parse_module( let parsed_source = parsed_source_cache.get_or_parse_module(
@ -42,6 +43,6 @@ pub fn emit_parsed_source(
let transpiled_source = parsed_source.transpile(emit_options)?; let transpiled_source = parsed_source.transpile(emit_options)?;
debug_assert!(transpiled_source.source_map.is_none()); debug_assert!(transpiled_source.source_map.is_none());
emit_cache.set_emit_code(specifier, source_hash, &transpiled_source.text); emit_cache.set_emit_code(specifier, source_hash, &transpiled_source.text);
Ok(transpiled_source.text) Ok(transpiled_source.text.into())
} }
} }

View File

@ -2854,7 +2854,7 @@ fn start(runtime: &mut JsRuntime, debug: bool) -> Result<(), AnyError> {
let init_config = json!({ "debug": debug }); let init_config = json!({ "debug": debug });
let init_src = format!("globalThis.serverInit({init_config});"); let init_src = format!("globalThis.serverInit({init_config});");
runtime.execute_script(&located_script_name!(), &init_src)?; runtime.execute_script(located_script_name!(), init_src)?;
Ok(()) Ok(())
} }
@ -3442,7 +3442,7 @@ pub fn request(
}; };
let mark = performance.mark("request", Some(request_params.clone())); let mark = performance.mark("request", Some(request_params.clone()));
let request_src = format!("globalThis.serverRequest({request_params});"); let request_src = format!("globalThis.serverRequest({request_params});");
runtime.execute_script(&located_script_name!(), &request_src)?; runtime.execute_script(located_script_name!(), request_src)?;
let op_state = runtime.op_state(); let op_state = runtime.op_state();
let mut op_state = op_state.borrow_mut(); let mut op_state = op_state.borrow_mut();

View File

@ -14,6 +14,7 @@ use deno_core::error::AnyError;
use deno_core::futures::future::FutureExt; use deno_core::futures::future::FutureExt;
use deno_core::futures::Future; use deno_core::futures::Future;
use deno_core::resolve_url; use deno_core::resolve_url;
use deno_core::ModuleCode;
use deno_core::ModuleLoader; use deno_core::ModuleLoader;
use deno_core::ModuleSource; use deno_core::ModuleSource;
use deno_core::ModuleSpecifier; use deno_core::ModuleSpecifier;
@ -30,7 +31,7 @@ use std::rc::Rc;
use std::str; use std::str;
struct ModuleCodeSource { struct ModuleCodeSource {
pub code: String, pub code: ModuleCode,
pub found_url: ModuleSpecifier, pub found_url: ModuleSpecifier,
pub media_type: MediaType, pub media_type: MediaType,
} }
@ -91,7 +92,7 @@ impl CliModuleLoader {
specifier, specifier,
.. ..
})) => Ok(ModuleCodeSource { })) => Ok(ModuleCodeSource {
code: source.to_string(), code: source.into(),
found_url: specifier.clone(), found_url: specifier.clone(),
media_type: *media_type, media_type: *media_type,
}), }),
@ -101,13 +102,15 @@ impl CliModuleLoader {
specifier, specifier,
.. ..
})) => { })) => {
let code = match media_type { let code: ModuleCode = match media_type {
MediaType::JavaScript MediaType::JavaScript
| MediaType::Unknown | MediaType::Unknown
| MediaType::Cjs | MediaType::Cjs
| MediaType::Mjs | MediaType::Mjs
| MediaType::Json => source.to_string(), | MediaType::Json => source.into(),
MediaType::Dts | MediaType::Dcts | MediaType::Dmts => "".to_string(), MediaType::Dts | MediaType::Dcts | MediaType::Dmts => {
Default::default()
}
MediaType::TypeScript MediaType::TypeScript
| MediaType::Mts | MediaType::Mts
| MediaType::Cts | MediaType::Cts
@ -191,7 +194,7 @@ impl CliModuleLoader {
)? )?
}; };
ModuleCodeSource { ModuleCodeSource {
code, code: code.into(),
found_url: specifier.clone(), found_url: specifier.clone(),
media_type: MediaType::from_specifier(specifier), media_type: MediaType::from_specifier(specifier),
} }
@ -208,7 +211,7 @@ impl CliModuleLoader {
code_without_source_map(code_source.code) code_without_source_map(code_source.code)
}; };
Ok(ModuleSource { Ok(ModuleSource {
code: code.into_bytes().into_boxed_slice(), code,
module_url_specified: specifier.to_string(), module_url_specified: specifier.to_string(),
module_url_found: code_source.found_url.to_string(), module_url_found: code_source.found_url.to_string(),
module_type: match code_source.media_type { module_type: match code_source.media_type {

View File

@ -178,7 +178,7 @@ impl ModuleLoader for EmbeddedModuleLoader {
async move { async move {
if let Some((source, _)) = is_data_uri { if let Some((source, _)) = is_data_uri {
return Ok(deno_core::ModuleSource { return Ok(deno_core::ModuleSource {
code: source.into_bytes().into_boxed_slice(), code: source.into(),
module_type: deno_core::ModuleType::JavaScript, module_type: deno_core::ModuleType::JavaScript,
module_url_specified: module_specifier.to_string(), module_url_specified: module_specifier.to_string(),
module_url_found: module_specifier.to_string(), module_url_found: module_specifier.to_string(),
@ -192,7 +192,7 @@ impl ModuleLoader for EmbeddedModuleLoader {
.to_owned(); .to_owned();
Ok(deno_core::ModuleSource { Ok(deno_core::ModuleSource {
code: code.into_bytes().into_boxed_slice(), code: code.into(),
module_type: match module.kind { module_type: match module.kind {
eszip::ModuleKind::JavaScript => deno_core::ModuleType::JavaScript, eszip::ModuleKind::JavaScript => deno_core::ModuleType::JavaScript,
eszip::ModuleKind::Json => deno_core::ModuleType::Json, eszip::ModuleKind::Json => deno_core::ModuleType::Json,
@ -384,16 +384,16 @@ pub async fn run(
options, options,
); );
worker.execute_main_module(main_module).await?; worker.execute_main_module(main_module).await?;
worker.dispatch_load_event(&located_script_name!())?; worker.dispatch_load_event(located_script_name!())?;
loop { loop {
worker.run_event_loop(false).await?; worker.run_event_loop(false).await?;
if !worker.dispatch_beforeunload_event(&located_script_name!())? { if !worker.dispatch_beforeunload_event(located_script_name!())? {
break; break;
} }
} }
worker.dispatch_unload_event(&located_script_name!())?; worker.dispatch_unload_event(located_script_name!())?;
std::process::exit(0); std::process::exit(0);
} }

View File

@ -20,6 +20,7 @@ use deno_core::serde_json;
use deno_core::sourcemap::SourceMap; use deno_core::sourcemap::SourceMap;
use deno_core::url::Url; use deno_core::url::Url;
use deno_core::LocalInspectorSession; use deno_core::LocalInspectorSession;
use deno_core::ModuleCode;
use regex::Regex; use regex::Regex;
use std::fs; use std::fs;
use std::fs::File; use std::fs::File;
@ -170,16 +171,16 @@ struct CoverageReport {
fn generate_coverage_report( fn generate_coverage_report(
script_coverage: &ScriptCoverage, script_coverage: &ScriptCoverage,
script_source: &str, script_source: String,
maybe_source_map: &Option<Vec<u8>>, maybe_source_map: &Option<Vec<u8>>,
output: &Option<PathBuf>, output: &Option<PathBuf>,
) -> CoverageReport { ) -> CoverageReport {
let maybe_source_map = maybe_source_map let maybe_source_map = maybe_source_map
.as_ref() .as_ref()
.map(|source_map| SourceMap::from_slice(source_map).unwrap()); .map(|source_map| SourceMap::from_slice(source_map).unwrap());
let text_lines = TextLines::new(script_source); let text_lines = TextLines::new(&script_source);
let comment_ranges = deno_ast::lex(script_source, MediaType::JavaScript) let comment_ranges = deno_ast::lex(&script_source, MediaType::JavaScript)
.into_iter() .into_iter()
.filter(|item| { .filter(|item| {
matches!(item.inner, deno_ast::TokenOrComment::Comment { .. }) matches!(item.inner, deno_ast::TokenOrComment::Comment { .. })
@ -680,14 +681,14 @@ pub async fn cover_files(
})?; })?;
// Check if file was transpiled // Check if file was transpiled
let original_source = &file.source; let original_source = file.source.clone();
let transpiled_code = match file.media_type { let transpiled_code: ModuleCode = match file.media_type {
MediaType::JavaScript MediaType::JavaScript
| MediaType::Unknown | MediaType::Unknown
| MediaType::Cjs | MediaType::Cjs
| MediaType::Mjs | MediaType::Mjs
| MediaType::Json => file.source.as_ref().to_string(), | MediaType::Json => file.source.into(),
MediaType::Dts | MediaType::Dmts | MediaType::Dcts => "".to_string(), MediaType::Dts | MediaType::Dmts | MediaType::Dcts => Default::default(),
MediaType::TypeScript MediaType::TypeScript
| MediaType::Jsx | MediaType::Jsx
| MediaType::Mts | MediaType::Mts
@ -695,7 +696,7 @@ pub async fn cover_files(
| MediaType::Tsx => { | MediaType::Tsx => {
let source_hash = get_source_hash(&file.source, ps.emit_options_hash); let source_hash = get_source_hash(&file.source, ps.emit_options_hash);
match ps.emit_cache.get_emit_code(&file.specifier, source_hash) { match ps.emit_cache.get_emit_code(&file.specifier, source_hash) {
Some(code) => code, Some(code) => code.into(),
None => { None => {
return Err(anyhow!( return Err(anyhow!(
"Missing transpiled source code for: \"{}\". "Missing transpiled source code for: \"{}\".
@ -710,15 +711,16 @@ pub async fn cover_files(
} }
}; };
let source_map = source_map_from_code(&transpiled_code);
let coverage_report = generate_coverage_report( let coverage_report = generate_coverage_report(
&script_coverage, &script_coverage,
&transpiled_code, transpiled_code.take_as_string(),
&source_map_from_code(&transpiled_code), &source_map,
&out_mode, &out_mode,
); );
if !coverage_report.found_lines.is_empty() { if !coverage_report.found_lines.is_empty() {
reporter.report(&coverage_report, original_source)?; reporter.report(&coverage_report, &original_source)?;
} }
} }

View File

@ -824,9 +824,9 @@ pub fn exec(request: Request) -> Result<Response, AnyError> {
}); });
runtime runtime
.execute_script(&located_script_name!(), startup_source) .execute_script(located_script_name!(), startup_source)
.context("Could not properly start the compiler runtime.")?; .context("Could not properly start the compiler runtime.")?;
runtime.execute_script(&located_script_name!(), &exec_source)?; runtime.execute_script(located_script_name!(), exec_source)?;
let op_state = runtime.op_state(); let op_state = runtime.op_state();
let mut op_state = op_state.borrow_mut(); let mut op_state = op_state.borrow_mut();

View File

@ -1,5 +1,6 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
use deno_core::ModuleCode;
use encoding_rs::*; use encoding_rs::*;
use std::borrow::Cow; use std::borrow::Cow;
use std::io::Error; use std::io::Error;
@ -53,11 +54,12 @@ pub fn strip_bom(text: &str) -> &str {
} }
} }
static SOURCE_MAP_PREFIX: &str = static SOURCE_MAP_PREFIX: &[u8] =
"//# sourceMappingURL=data:application/json;base64,"; b"//# sourceMappingURL=data:application/json;base64,";
pub fn source_map_from_code(code: &str) -> Option<Vec<u8>> { pub fn source_map_from_code(code: &ModuleCode) -> Option<Vec<u8>> {
let last_line = code.rsplit(|u| u == '\n').next()?; let bytes = code.as_bytes();
let last_line = bytes.rsplit(|u| *u == b'\n').next()?;
if last_line.starts_with(SOURCE_MAP_PREFIX) { if last_line.starts_with(SOURCE_MAP_PREFIX) {
let input = last_line.split_at(SOURCE_MAP_PREFIX.len()).1; let input = last_line.split_at(SOURCE_MAP_PREFIX.len()).1;
let decoded_map = base64::decode(input) let decoded_map = base64::decode(input)
@ -68,17 +70,18 @@ pub fn source_map_from_code(code: &str) -> Option<Vec<u8>> {
} }
} }
pub fn code_without_source_map(mut code: String) -> String { /// Truncate the source code before the source map.
if let Some(last_line_index) = code.rfind('\n') { pub fn code_without_source_map(mut code: ModuleCode) -> ModuleCode {
if code[last_line_index + 1..].starts_with(SOURCE_MAP_PREFIX) { let bytes = code.as_bytes();
code.truncate(last_line_index + 1); for i in (0..bytes.len()).rev() {
code if bytes[i] == b'\n' {
} else { if bytes[i + 1..].starts_with(SOURCE_MAP_PREFIX) {
code code.truncate(i + 1);
}
return code;
} }
} else {
code
} }
code
} }
#[cfg(test)] #[cfg(test)]
@ -155,8 +158,11 @@ mod tests {
"\n", "\n",
); );
fn run_test(input: &str, output: &str) { fn run_test(input: &'static str, output: &'static str) {
assert_eq!(code_without_source_map(input.to_string()), output); assert_eq!(
code_without_source_map(input.into()).take_as_string(),
output
);
} }
} }
} }

View File

@ -78,7 +78,7 @@ impl CliMainWorker {
self.execute_main_module_possibly_with_npm().await?; self.execute_main_module_possibly_with_npm().await?;
} }
self.worker.dispatch_load_event(&located_script_name!())?; self.worker.dispatch_load_event(located_script_name!())?;
loop { loop {
self self
@ -87,13 +87,13 @@ impl CliMainWorker {
.await?; .await?;
if !self if !self
.worker .worker
.dispatch_beforeunload_event(&located_script_name!())? .dispatch_beforeunload_event(located_script_name!())?
{ {
break; break;
} }
} }
self.worker.dispatch_unload_event(&located_script_name!())?; self.worker.dispatch_unload_event(located_script_name!())?;
if let Some(coverage_collector) = maybe_coverage_collector.as_mut() { if let Some(coverage_collector) = maybe_coverage_collector.as_mut() {
self self
@ -129,7 +129,7 @@ impl CliMainWorker {
self self
.inner .inner
.worker .worker
.dispatch_load_event(&located_script_name!())?; .dispatch_load_event(located_script_name!())?;
self.pending_unload = true; self.pending_unload = true;
let result = loop { let result = loop {
@ -140,7 +140,7 @@ impl CliMainWorker {
match self match self
.inner .inner
.worker .worker
.dispatch_beforeunload_event(&located_script_name!()) .dispatch_beforeunload_event(located_script_name!())
{ {
Ok(default_prevented) if default_prevented => {} // continue loop Ok(default_prevented) if default_prevented => {} // continue loop
Ok(_) => break Ok(()), Ok(_) => break Ok(()),
@ -154,7 +154,7 @@ impl CliMainWorker {
self self
.inner .inner
.worker .worker
.dispatch_unload_event(&located_script_name!())?; .dispatch_unload_event(located_script_name!())?;
Ok(()) Ok(())
} }
@ -166,7 +166,7 @@ impl CliMainWorker {
let _ = self let _ = self
.inner .inner
.worker .worker
.dispatch_unload_event(&located_script_name!()); .dispatch_unload_event(located_script_name!());
} }
} }
} }
@ -185,7 +185,7 @@ impl CliMainWorker {
// failures. // failures.
if self.ps.options.trace_ops() { if self.ps.options.trace_ops() {
self.worker.js_runtime.execute_script( self.worker.js_runtime.execute_script(
&located_script_name!(), located_script_name!(),
"Deno[Deno.internal].core.enableOpCallTracing();", "Deno[Deno.internal].core.enableOpCallTracing();",
)?; )?;
} }
@ -200,19 +200,19 @@ impl CliMainWorker {
self.execute_side_module_possibly_with_npm().await?; self.execute_side_module_possibly_with_npm().await?;
} }
self.worker.dispatch_load_event(&located_script_name!())?; self.worker.dispatch_load_event(located_script_name!())?;
self.run_tests(&self.ps.options.shuffle_tests()).await?; self.run_tests(&self.ps.options.shuffle_tests()).await?;
loop { loop {
if !self if !self
.worker .worker
.dispatch_beforeunload_event(&located_script_name!())? .dispatch_beforeunload_event(located_script_name!())?
{ {
break; break;
} }
self.worker.run_event_loop(false).await?; self.worker.run_event_loop(false).await?;
} }
self.worker.dispatch_unload_event(&located_script_name!())?; self.worker.dispatch_unload_event(located_script_name!())?;
if let Some(coverage_collector) = maybe_coverage_collector.as_mut() { if let Some(coverage_collector) = maybe_coverage_collector.as_mut() {
self self
@ -230,7 +230,7 @@ impl CliMainWorker {
self.enable_test(); self.enable_test();
self.worker.execute_script( self.worker.execute_script(
&located_script_name!(), located_script_name!(),
"Deno[Deno.internal].core.enableOpCallTracing();", "Deno[Deno.internal].core.enableOpCallTracing();",
)?; )?;
@ -239,18 +239,18 @@ impl CliMainWorker {
self.execute_side_module_possibly_with_npm().await?; self.execute_side_module_possibly_with_npm().await?;
} }
self.worker.dispatch_load_event(&located_script_name!())?; self.worker.dispatch_load_event(located_script_name!())?;
self.run_tests(&None).await?; self.run_tests(&None).await?;
loop { loop {
if !self if !self
.worker .worker
.dispatch_beforeunload_event(&located_script_name!())? .dispatch_beforeunload_event(located_script_name!())?
{ {
break; break;
} }
self.worker.run_event_loop(false).await?; self.worker.run_event_loop(false).await?;
} }
self.worker.dispatch_unload_event(&located_script_name!())?; self.worker.dispatch_unload_event(located_script_name!())?;
Ok(()) Ok(())
} }
@ -260,18 +260,18 @@ impl CliMainWorker {
// We execute the module module as a side module so that import.meta.main is not set. // We execute the module module as a side module so that import.meta.main is not set.
self.execute_side_module_possibly_with_npm().await?; self.execute_side_module_possibly_with_npm().await?;
self.worker.dispatch_load_event(&located_script_name!())?; self.worker.dispatch_load_event(located_script_name!())?;
self.run_benchmarks().await?; self.run_benchmarks().await?;
loop { loop {
if !self if !self
.worker .worker
.dispatch_beforeunload_event(&located_script_name!())? .dispatch_beforeunload_event(located_script_name!())?
{ {
break; break;
} }
self.worker.run_event_loop(false).await?; self.worker.run_event_loop(false).await?;
} }
self.worker.dispatch_unload_event(&located_script_name!())?; self.worker.dispatch_unload_event(located_script_name!())?;
Ok(()) Ok(())
} }

View File

@ -26,7 +26,7 @@ fn main() {
fn eval( fn eval(
context: &mut JsRuntime, context: &mut JsRuntime,
code: &str, code: &'static str,
) -> Result<serde_json::Value, String> { ) -> Result<serde_json::Value, String> {
let res = context.execute_script("<anon>", code); let res = context.execute_script("<anon>", code);
match res { match res {

View File

@ -82,7 +82,7 @@ impl ModuleLoader for TypescriptModuleLoader {
code code
}; };
let module = ModuleSource { let module = ModuleSource {
code: code.into_bytes().into_boxed_slice(), code: code.into(),
module_type, module_type,
module_url_specified: module_specifier.to_string(), module_url_specified: module_specifier.to_string(),
module_url_found: module_specifier.to_string(), module_url_found: module_specifier.to_string(),

View File

@ -1,4 +1,5 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
use crate::modules::ModuleCode;
use crate::OpState; use crate::OpState;
use anyhow::Context as _; use anyhow::Context as _;
use anyhow::Error; use anyhow::Error;
@ -23,13 +24,12 @@ pub enum ExtensionFileSourceCode {
} }
impl ExtensionFileSourceCode { impl ExtensionFileSourceCode {
pub fn load(&self) -> Result<String, Error> { pub fn load(&self) -> Result<ModuleCode, Error> {
match self { match self {
ExtensionFileSourceCode::IncludedInBinary(code) => Ok(code.to_string()), ExtensionFileSourceCode::IncludedInBinary(code) => Ok((*code).into()),
ExtensionFileSourceCode::LoadedFromFsDuringSnapshot(path) => { ExtensionFileSourceCode::LoadedFromFsDuringSnapshot(path) => {
let msg = format!("Failed to read \"{}\"", path.display()); let msg = || format!("Failed to read \"{}\"", path.display());
let code = std::fs::read_to_string(path).context(msg)?; Ok(std::fs::read_to_string(path).with_context(msg)?.into())
Ok(code)
} }
} }
} }

View File

@ -77,6 +77,7 @@ pub use crate::module_specifier::ModuleSpecifier;
pub use crate::modules::ExtModuleLoader; pub use crate::modules::ExtModuleLoader;
pub use crate::modules::ExtModuleLoaderCb; pub use crate::modules::ExtModuleLoaderCb;
pub use crate::modules::FsModuleLoader; pub use crate::modules::FsModuleLoader;
pub use crate::modules::ModuleCode;
pub use crate::modules::ModuleId; pub use crate::modules::ModuleId;
pub use crate::modules::ModuleLoader; pub use crate::modules::ModuleLoader;
pub use crate::modules::ModuleSource; pub use crate::modules::ModuleSource;
@ -144,7 +145,7 @@ pub mod _ops {
#[macro_export] #[macro_export]
macro_rules! located_script_name { macro_rules! located_script_name {
() => { () => {
format!("[ext:{}:{}:{}]", std::file!(), std::line!(), std::column!()); concat!("[ext:{}:{}:{}]", std::file!(), std::line!(), std::column!());
}; };
} }

View File

@ -10,6 +10,7 @@ use crate::snapshot_util::SnapshottedData;
use crate::JsRuntime; use crate::JsRuntime;
use crate::OpState; use crate::OpState;
use anyhow::Error; use anyhow::Error;
use core::panic;
use futures::future::FutureExt; use futures::future::FutureExt;
use futures::stream::FuturesUnordered; use futures::stream::FuturesUnordered;
use futures::stream::Stream; use futures::stream::Stream;
@ -18,6 +19,7 @@ use futures::stream::TryStreamExt;
use log::debug; use log::debug;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
use std::borrow::Cow;
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::HashSet; use std::collections::HashSet;
@ -25,6 +27,7 @@ use std::collections::VecDeque;
use std::future::Future; use std::future::Future;
use std::pin::Pin; use std::pin::Pin;
use std::rc::Rc; use std::rc::Rc;
use std::sync::Arc;
use std::task::Context; use std::task::Context;
use std::task::Poll; use std::task::Poll;
@ -192,14 +195,155 @@ impl std::fmt::Display for ModuleType {
// that happened; not only first and final target. It would simplify a lot // that happened; not only first and final target. It would simplify a lot
// of things throughout the codebase otherwise we may end up requesting // of things throughout the codebase otherwise we may end up requesting
// intermediate redirects from file loader. // intermediate redirects from file loader.
#[derive(Debug, Clone, Eq, PartialEq)] // NOTE: This should _not_ be made #[derive(Clone)] unless we take some precautions to avoid excessive string copying.
#[derive(Debug)]
pub struct ModuleSource { pub struct ModuleSource {
pub code: Box<[u8]>, pub code: ModuleCode,
pub module_type: ModuleType, pub module_type: ModuleType,
pub module_url_specified: String, pub module_url_specified: String,
pub module_url_found: String, pub module_url_found: String,
} }
/// Module code can be sourced from strings or bytes that are either owned or borrowed. This enumeration allows us
/// to perform a minimal amount of cloning and format-shifting of the underlying data.
///
/// Note that any [`ModuleCode`] created from a `'static` byte array or string must contain ASCII characters.
///
/// Examples of ways to construct a [`ModuleCode`] object:
///
/// ```rust
/// # use deno_core::ModuleCode;
///
/// let code: ModuleCode = "a string".into();
/// let code: ModuleCode = b"a string".into();
/// ```
#[derive(Debug)]
pub enum ModuleCode {
/// Created from static data -- must be 100% 7-bit ASCII!
Static(&'static [u8]),
/// An owned chunk of data.
Owned(Vec<u8>),
/// Scripts loaded from the `deno_graph` infrastructure.
Arc(Arc<str>),
}
impl ModuleCode {
#[inline(always)]
pub fn as_bytes(&self) -> &[u8] {
match self {
Self::Static(b) => b,
Self::Owned(b) => b,
Self::Arc(s) => s.as_bytes(),
}
}
pub fn try_static_ascii(&self) -> Option<&'static [u8]> {
match self {
Self::Static(b) => Some(b),
_ => None,
}
}
/// Takes a [`ModuleCode`] value as an owned [`String`]. May be slow.
pub fn take_as_string(self) -> String {
match self {
Self::Static(b) => String::from_utf8(b.to_vec()).unwrap(),
Self::Owned(b) => String::from_utf8(b).unwrap(),
Self::Arc(s) => (*s).to_owned(),
}
}
/// Truncates a `ModuleCode`] value, possibly re-allocating or memcpy'ing. May be slow.
pub fn truncate(&mut self, index: usize) {
match self {
Self::Static(b) => *self = Self::Static(&b[..index]),
Self::Owned(b) => b.truncate(index),
// We can't do much if we have an Arc<str>, so we'll just take ownership of the truncated version
Self::Arc(s) => *self = s[..index].to_owned().into(),
}
}
}
impl Default for ModuleCode {
fn default() -> Self {
ModuleCode::Static(&[])
}
}
impl From<Arc<str>> for ModuleCode {
#[inline(always)]
fn from(value: Arc<str>) -> Self {
Self::Arc(value)
}
}
impl From<&Arc<str>> for ModuleCode {
#[inline(always)]
fn from(value: &Arc<str>) -> Self {
Self::Arc(value.clone())
}
}
impl From<Cow<'static, str>> for ModuleCode {
#[inline(always)]
fn from(value: Cow<'static, str>) -> Self {
match value {
Cow::Borrowed(b) => b.into(),
Cow::Owned(b) => b.into(),
}
}
}
impl From<Cow<'static, [u8]>> for ModuleCode {
#[inline(always)]
fn from(value: Cow<'static, [u8]>) -> Self {
match value {
Cow::Borrowed(b) => b.into(),
Cow::Owned(b) => b.into(),
}
}
}
impl From<&'static str> for ModuleCode {
#[inline(always)]
fn from(value: &'static str) -> Self {
assert!(value.is_ascii());
ModuleCode::Static(value.as_bytes())
}
}
impl From<String> for ModuleCode {
#[inline(always)]
fn from(value: String) -> Self {
value.into_bytes().into()
}
}
impl From<Vec<u8>> for ModuleCode {
#[inline(always)]
fn from(value: Vec<u8>) -> Self {
ModuleCode::Owned(value)
}
}
impl From<&'static [u8]> for ModuleCode {
#[inline(always)]
fn from(value: &'static [u8]) -> Self {
assert!(value.is_ascii());
ModuleCode::Static(value)
}
}
impl<const N: usize> From<&'static [u8; N]> for ModuleCode {
#[inline(always)]
fn from(value: &'static [u8; N]) -> Self {
assert!(value.is_ascii());
ModuleCode::Static(value)
}
}
pub(crate) type PrepareLoadFuture = pub(crate) type PrepareLoadFuture =
dyn Future<Output = (ModuleLoadId, Result<RecursiveModuleLoad, Error>)>; dyn Future<Output = (ModuleLoadId, Result<RecursiveModuleLoad, Error>)>;
pub type ModuleSourceFuture = dyn Future<Output = Result<ModuleSource, Error>>; pub type ModuleSourceFuture = dyn Future<Output = Result<ModuleSource, Error>>;
@ -323,7 +467,7 @@ pub(crate) fn resolve_helper(
/// Function that can be passed to the `ExtModuleLoader` that allows to /// Function that can be passed to the `ExtModuleLoader` that allows to
/// transpile sources before passing to V8. /// transpile sources before passing to V8.
pub type ExtModuleLoaderCb = pub type ExtModuleLoaderCb =
Box<dyn Fn(&ExtensionFileSource) -> Result<String, Error>>; Box<dyn Fn(&ExtensionFileSource) -> Result<ModuleCode, Error>>;
pub struct ExtModuleLoader { pub struct ExtModuleLoader {
module_loader: Rc<dyn ModuleLoader>, module_loader: Rc<dyn ModuleLoader>,
@ -448,7 +592,7 @@ impl ModuleLoader for ExtModuleLoader {
return async move { return async move {
let code = result?; let code = result?;
let source = ModuleSource { let source = ModuleSource {
code: code.into_bytes().into_boxed_slice(), code,
module_type: ModuleType::JavaScript, module_type: ModuleType::JavaScript,
module_url_specified: specifier.clone(), module_url_specified: specifier.clone(),
module_url_found: specifier.clone(), module_url_found: specifier.clone(),
@ -529,7 +673,7 @@ impl ModuleLoader for FsModuleLoader {
let code = std::fs::read(path)?; let code = std::fs::read(path)?;
let module = ModuleSource { let module = ModuleSource {
code: code.into_boxed_slice(), code: code.into(),
module_type, module_type,
module_url_specified: module_specifier.to_string(), module_url_specified: module_specifier.to_string(),
module_url_found: module_specifier.to_string(), module_url_found: module_specifier.to_string(),
@ -1002,6 +1146,32 @@ pub(crate) enum ModuleError {
Other(Error), Other(Error),
} }
pub enum ModuleName<'a> {
Static(&'static str),
NotStatic(&'a str),
}
impl<'a> ModuleName<'a> {
pub fn as_ref(&self) -> &'a str {
match self {
ModuleName::Static(s) => s,
ModuleName::NotStatic(s) => s,
}
}
}
impl<'a, S: AsRef<str>> From<&'a S> for ModuleName<'a> {
fn from(s: &'a S) -> Self {
Self::NotStatic(s.as_ref())
}
}
impl From<&'static str> for ModuleName<'static> {
fn from(value: &'static str) -> Self {
Self::Static(value)
}
}
/// A collection of JS modules. /// A collection of JS modules.
pub(crate) struct ModuleMap { pub(crate) struct ModuleMap {
// Handling of specifiers and v8 objects // Handling of specifiers and v8 objects
@ -1326,16 +1496,54 @@ impl ModuleMap {
} }
} }
fn new_json_module( fn string_from_code<'a>(
scope: &mut v8::HandleScope<'a>,
code: &ModuleCode,
) -> Option<v8::Local<'a, v8::String>> {
if let Some(code) = code.try_static_ascii() {
v8::String::new_external_onebyte_static(scope, code)
} else {
v8::String::new_from_utf8(
scope,
code.as_bytes(),
v8::NewStringType::Normal,
)
}
}
fn string_from_module_name<'a>(
scope: &mut v8::HandleScope<'a>,
name: &ModuleName,
) -> Option<v8::Local<'a, v8::String>> {
match name {
ModuleName::Static(s) => {
assert!(s.is_ascii());
v8::String::new_external_onebyte_static(scope, s.as_bytes())
}
ModuleName::NotStatic(s) => v8::String::new(scope, s),
}
}
fn new_json_module<'a, N: Into<ModuleName<'a>>>(
&mut self, &mut self,
scope: &mut v8::HandleScope, scope: &mut v8::HandleScope,
name: &str, name: N,
source: &[u8], source: &ModuleCode,
) -> Result<ModuleId, ModuleError> { ) -> Result<ModuleId, ModuleError> {
let name_str = v8::String::new(scope, name).unwrap(); // Manual monomorphization (TODO: replace w/momo)
self.new_json_module_inner(scope, name.into(), source)
}
fn new_json_module_inner(
&mut self,
scope: &mut v8::HandleScope,
name: ModuleName,
source: &ModuleCode,
) -> Result<ModuleId, ModuleError> {
let name_str = Self::string_from_module_name(scope, &name).unwrap();
let source_str = v8::String::new_from_utf8( let source_str = v8::String::new_from_utf8(
scope, scope,
strip_bom(source), strip_bom(source.as_bytes()),
v8::NewStringType::Normal, v8::NewStringType::Normal,
) )
.unwrap(); .unwrap();
@ -1364,25 +1572,47 @@ impl ModuleMap {
let value_handle = v8::Global::<v8::Value>::new(tc_scope, parsed_json); let value_handle = v8::Global::<v8::Value>::new(tc_scope, parsed_json);
self.json_value_store.insert(handle.clone(), value_handle); self.json_value_store.insert(handle.clone(), value_handle);
let id = let id = self.create_module_info(
self.create_module_info(name, ModuleType::Json, handle, false, vec![]); name.as_ref(),
ModuleType::Json,
handle,
false,
vec![],
);
Ok(id) Ok(id)
} }
// Create and compile an ES module. /// Create and compile an ES module. Generic interface that can receive either a `&'static str` or a string with a lifetime. Prefer
pub(crate) fn new_es_module( /// to pass `&'static str` as this allows us to use v8 external strings.
pub(crate) fn new_es_module<'a, N: Into<ModuleName<'a>>>(
&mut self, &mut self,
scope: &mut v8::HandleScope, scope: &mut v8::HandleScope,
main: bool, main: bool,
name: &str, name: N,
source: &[u8], source: &ModuleCode,
is_dynamic_import: bool, is_dynamic_import: bool,
) -> Result<ModuleId, ModuleError> { ) -> Result<ModuleId, ModuleError> {
let name_str = v8::String::new(scope, name).unwrap(); // Manual monomorphization (TODO: replace w/momo)
let source_str = self.new_es_module_inner(
v8::String::new_from_utf8(scope, source, v8::NewStringType::Normal) scope,
.unwrap(); main,
name.into(),
source,
is_dynamic_import,
)
}
fn new_es_module_inner(
&mut self,
scope: &mut v8::HandleScope,
main: bool,
name: ModuleName,
source: &ModuleCode,
is_dynamic_import: bool,
) -> Result<ModuleId, ModuleError> {
let name_str = Self::string_from_module_name(scope, &name).unwrap();
let source_str = Self::string_from_code(scope, source).unwrap();
let origin = bindings::module_origin(scope, name_str); let origin = bindings::module_origin(scope, name_str);
let source = v8::script_compiler::Source::new(source_str, Some(&origin)); let source = v8::script_compiler::Source::new(source_str, Some(&origin));
@ -1432,7 +1662,7 @@ impl ModuleMap {
self.snapshot_loaded_and_not_snapshotting, self.snapshot_loaded_and_not_snapshotting,
self.loader.clone(), self.loader.clone(),
&import_specifier, &import_specifier,
name, name.as_ref(),
if is_dynamic_import { if is_dynamic_import {
ResolutionKind::DynamicImport ResolutionKind::DynamicImport
} else { } else {
@ -1456,7 +1686,7 @@ impl ModuleMap {
if let Some(main_module) = maybe_main_module { if let Some(main_module) = maybe_main_module {
return Err(ModuleError::Other(generic_error( return Err(ModuleError::Other(generic_error(
format!("Trying to create \"main\" module ({:?}), when one already exists ({:?})", format!("Trying to create \"main\" module ({:?}), when one already exists ({:?})",
name, name.as_ref(),
main_module.name, main_module.name,
)))); ))));
} }
@ -1464,7 +1694,7 @@ impl ModuleMap {
let handle = v8::Global::<v8::Module>::new(tc_scope, module); let handle = v8::Global::<v8::Module>::new(tc_scope, module);
let id = self.create_module_info( let id = self.create_module_info(
name, name.as_ref(),
ModuleType::JavaScript, ModuleType::JavaScript,
handle, handle,
main, main,
@ -1846,7 +2076,7 @@ import "/a.js";
} }
match mock_source_code(&inner.url) { match mock_source_code(&inner.url) {
Some(src) => Poll::Ready(Ok(ModuleSource { Some(src) => Poll::Ready(Ok(ModuleSource {
code: src.0.as_bytes().to_vec().into_boxed_slice(), code: src.0.into(),
module_type: ModuleType::JavaScript, module_type: ModuleType::JavaScript,
module_url_specified: inner.url.clone(), module_url_specified: inner.url.clone(),
module_url_found: src.1.to_owned(), module_url_found: src.1.to_owned(),
@ -2043,12 +2273,13 @@ import "/a.js";
scope, scope,
true, true,
&specifier_a, &specifier_a,
br#" &br#"
import { b } from './b.js' import { b } from './b.js'
if (b() != 'b') throw Error(); if (b() != 'b') throw Error();
let control = 42; let control = 42;
Deno.core.ops.op_test(control); Deno.core.ops.op_test(control);
"#, "#
.into(),
false, false,
) )
.unwrap(); .unwrap();
@ -2068,7 +2299,7 @@ import "/a.js";
scope, scope,
false, false,
"file:///b.js", "file:///b.js",
b"export function b() { return 'b' }", &b"export function b() { return 'b' }".into(),
false, false,
) )
.unwrap(); .unwrap();
@ -2153,11 +2384,12 @@ import "/a.js";
scope, scope,
true, true,
&specifier_a, &specifier_a,
br#" &br#"
import jsonData from './b.json' assert {type: "json"}; import jsonData from './b.json' assert {type: "json"};
assert(jsonData.a == "b"); assert(jsonData.a == "b");
assert(jsonData.c.d == 10); assert(jsonData.c.d == 10);
"#, "#
.into(),
false, false,
) )
.unwrap(); .unwrap();
@ -2175,7 +2407,7 @@ import "/a.js";
.new_json_module( .new_json_module(
scope, scope,
"file:///b.json", "file:///b.json",
b"{\"a\": \"b\", \"c\": {\"d\": 10}}", &b"{\"a\": \"b\", \"c\": {\"d\": 10}}".into(),
) )
.unwrap(); .unwrap();
let imports = module_map.get_requested_modules(mod_b).unwrap(); let imports = module_map.get_requested_modules(mod_b).unwrap();
@ -2285,9 +2517,7 @@ import "/a.js";
let info = ModuleSource { let info = ModuleSource {
module_url_specified: specifier.to_string(), module_url_specified: specifier.to_string(),
module_url_found: specifier.to_string(), module_url_found: specifier.to_string(),
code: b"export function b() { return 'b' }" code: b"export function b() { return 'b' }".into(),
.to_vec()
.into_boxed_slice(),
module_type: ModuleType::JavaScript, module_type: ModuleType::JavaScript,
}; };
async move { Ok(info) }.boxed() async move { Ok(info) }.boxed()
@ -2427,7 +2657,7 @@ import "/a.js";
let info = ModuleSource { let info = ModuleSource {
module_url_specified: specifier.to_string(), module_url_specified: specifier.to_string(),
module_url_found: specifier.to_string(), module_url_found: specifier.to_string(),
code: code.as_bytes().to_vec().into_boxed_slice(), code: code.into(),
module_type: ModuleType::JavaScript, module_type: ModuleType::JavaScript,
}; };
async move { Ok(info) }.boxed() async move { Ok(info) }.boxed()
@ -2703,7 +2933,7 @@ if (import.meta.url != 'file:///main_with_code.js') throw Error();
// The behavior should be very similar to /a.js. // The behavior should be very similar to /a.js.
let spec = resolve_url("file:///main_with_code.js").unwrap(); let spec = resolve_url("file:///main_with_code.js").unwrap();
let main_id_fut = runtime let main_id_fut = runtime
.load_main_module(&spec, Some(MAIN_WITH_CODE_SRC.to_owned())) .load_main_module(&spec, Some(MAIN_WITH_CODE_SRC.into()))
.boxed_local(); .boxed_local();
let main_id = futures::executor::block_on(main_id_fut).unwrap(); let main_id = futures::executor::block_on(main_id_fut).unwrap();
@ -2795,17 +3025,13 @@ if (import.meta.url != 'file:///main_with_code.js') throw Error();
"file:///main_module.js" => Ok(ModuleSource { "file:///main_module.js" => Ok(ModuleSource {
module_url_specified: "file:///main_module.js".to_string(), module_url_specified: "file:///main_module.js".to_string(),
module_url_found: "file:///main_module.js".to_string(), module_url_found: "file:///main_module.js".to_string(),
code: b"if (!import.meta.main) throw Error();" code: b"if (!import.meta.main) throw Error();".into(),
.to_vec()
.into_boxed_slice(),
module_type: ModuleType::JavaScript, module_type: ModuleType::JavaScript,
}), }),
"file:///side_module.js" => Ok(ModuleSource { "file:///side_module.js" => Ok(ModuleSource {
module_url_specified: "file:///side_module.js".to_string(), module_url_specified: "file:///side_module.js".to_string(),
module_url_found: "file:///side_module.js".to_string(), module_url_found: "file:///side_module.js".to_string(),
code: b"if (import.meta.main) throw Error();" code: b"if (import.meta.main) throw Error();".into(),
.to_vec()
.into_boxed_slice(),
module_type: ModuleType::JavaScript, module_type: ModuleType::JavaScript,
}), }),
_ => unreachable!(), _ => unreachable!(),
@ -2866,7 +3092,7 @@ if (import.meta.url != 'file:///main_with_code.js') throw Error();
// The behavior should be very similar to /a.js. // The behavior should be very similar to /a.js.
let spec = resolve_url("file:///main_with_code.js").unwrap(); let spec = resolve_url("file:///main_with_code.js").unwrap();
let main_id_fut = runtime let main_id_fut = runtime
.load_main_module(&spec, Some(MAIN_WITH_CODE_SRC.to_owned())) .load_main_module(&spec, Some(MAIN_WITH_CODE_SRC.into()))
.boxed_local(); .boxed_local();
let main_id = futures::executor::block_on(main_id_fut).unwrap(); let main_id = futures::executor::block_on(main_id_fut).unwrap();
@ -2906,7 +3132,7 @@ if (import.meta.url != 'file:///main_with_code.js') throw Error();
// The behavior should be very similar to /a.js. // The behavior should be very similar to /a.js.
let spec = resolve_url("file:///main_with_code.js").unwrap(); let spec = resolve_url("file:///main_with_code.js").unwrap();
let main_id_fut = runtime let main_id_fut = runtime
.load_main_module(&spec, Some(MAIN_WITH_CODE_SRC.to_owned())) .load_main_module(&spec, Some(MAIN_WITH_CODE_SRC.into()))
.boxed_local(); .boxed_local();
let main_id = futures::executor::block_on(main_id_fut).unwrap(); let main_id = futures::executor::block_on(main_id_fut).unwrap();
@ -2976,4 +3202,23 @@ if (import.meta.url != 'file:///main_with_code.js') throw Error();
Some("Cannot load extension module from external code".to_string()) Some("Cannot load extension module from external code".to_string())
); );
} }
#[test]
fn code_truncate() {
let mut s = "123456".to_owned();
s.truncate(3);
let mut code: ModuleCode = "123456".into();
code.truncate(3);
assert_eq!(s, code.take_as_string());
let mut code: ModuleCode = "123456".to_owned().into();
code.truncate(3);
assert_eq!(s, code.take_as_string());
let arc_str: Arc<str> = "123456".into();
let mut code: ModuleCode = arc_str.into();
code.truncate(3);
assert_eq!(s, code.take_as_string());
}
} }

View File

@ -9,6 +9,7 @@ use crate::extensions::OpEventLoopFn;
use crate::inspector::JsRuntimeInspector; use crate::inspector::JsRuntimeInspector;
use crate::module_specifier::ModuleSpecifier; use crate::module_specifier::ModuleSpecifier;
use crate::modules::ExtModuleLoaderCb; use crate::modules::ExtModuleLoaderCb;
use crate::modules::ModuleCode;
use crate::modules::ModuleError; use crate::modules::ModuleError;
use crate::modules::ModuleId; use crate::modules::ModuleId;
use crate::modules::ModuleLoadId; use crate::modules::ModuleLoadId;
@ -50,6 +51,8 @@ use std::sync::Mutex;
use std::sync::Once; use std::sync::Once;
use std::task::Context; use std::task::Context;
use std::task::Poll; use std::task::Poll;
use v8::HandleScope;
use v8::Local;
use v8::OwnedIsolate; use v8::OwnedIsolate;
type PendingOpFuture = OpCall<(RealmIdx, PromiseId, OpId, OpResult)>; type PendingOpFuture = OpCall<(RealmIdx, PromiseId, OpId, OpResult)>;
@ -748,7 +751,7 @@ impl JsRuntime {
realm.execute_script( realm.execute_script(
self.v8_isolate(), self.v8_isolate(),
file_source.specifier, file_source.specifier,
&file_source.code.load()?, file_source.code.load()?,
)?; )?;
} }
} }
@ -902,7 +905,7 @@ impl JsRuntime {
/// The execution takes place on the current global context, so it is possible /// The execution takes place on the current global context, so it is possible
/// to maintain local JS state and invoke this method multiple times. /// to maintain local JS state and invoke this method multiple times.
/// ///
/// `name` can be a filepath or any other string, eg. /// `name` can be a filepath or any other string, but it is required to be 7-bit ASCII, eg.
/// ///
/// - "/some/file/path.js" /// - "/some/file/path.js"
/// - "<anon>" /// - "<anon>"
@ -911,10 +914,10 @@ impl JsRuntime {
/// The same `name` value can be used for multiple executions. /// The same `name` value can be used for multiple executions.
/// ///
/// `Error` can usually be downcast to `JsError`. /// `Error` can usually be downcast to `JsError`.
pub fn execute_script( pub fn execute_script<S: Into<ModuleCode>>(
&mut self, &mut self,
name: &str, name: &'static str,
source_code: &str, source_code: S,
) -> Result<v8::Global<v8::Value>, Error> { ) -> Result<v8::Global<v8::Value>, Error> {
self self
.global_realm() .global_realm()
@ -2056,21 +2059,15 @@ impl JsRuntime {
pub async fn load_main_module( pub async fn load_main_module(
&mut self, &mut self,
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
code: Option<String>, code: Option<ModuleCode>,
) -> Result<ModuleId, Error> { ) -> Result<ModuleId, Error> {
let module_map_rc = Self::module_map(self.v8_isolate()); let module_map_rc = Self::module_map(self.v8_isolate());
if let Some(code) = code { if let Some(code) = code {
let scope = &mut self.handle_scope(); let scope = &mut self.handle_scope();
// true for main module
module_map_rc module_map_rc
.borrow_mut() .borrow_mut()
.new_es_module( .new_es_module(scope, true, specifier, &code, false)
scope,
// main module
true,
specifier.as_str(),
code.as_bytes(),
false,
)
.map_err(|e| match e { .map_err(|e| match e {
ModuleError::Exception(exception) => { ModuleError::Exception(exception) => {
let exception = v8::Local::new(scope, exception); let exception = v8::Local::new(scope, exception);
@ -2116,21 +2113,15 @@ impl JsRuntime {
pub async fn load_side_module( pub async fn load_side_module(
&mut self, &mut self,
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
code: Option<String>, code: Option<ModuleCode>,
) -> Result<ModuleId, Error> { ) -> Result<ModuleId, Error> {
let module_map_rc = Self::module_map(self.v8_isolate()); let module_map_rc = Self::module_map(self.v8_isolate());
if let Some(code) = code { if let Some(code) = code {
let scope = &mut self.handle_scope(); let scope = &mut self.handle_scope();
// false for side module (not main module)
module_map_rc module_map_rc
.borrow_mut() .borrow_mut()
.new_es_module( .new_es_module(scope, false, specifier, &code, false)
scope,
// not main module
false,
specifier.as_str(),
code.as_bytes(),
false,
)
.map_err(|e| match e { .map_err(|e| match e {
ModuleError::Exception(exception) => { ModuleError::Exception(exception) => {
let exception = v8::Local::new(scope, exception); let exception = v8::Local::new(scope, exception);
@ -2476,6 +2467,21 @@ impl JsRealm {
self.0.open(scope).global(scope) self.0.open(scope).global(scope)
} }
fn string_from_code<'a>(
scope: &mut HandleScope<'a>,
code: &ModuleCode,
) -> Option<Local<'a, v8::String>> {
if let Some(code) = code.try_static_ascii() {
v8::String::new_external_onebyte_static(scope, code)
} else {
v8::String::new_from_utf8(
scope,
code.as_bytes(),
v8::NewStringType::Normal,
)
}
}
/// Executes traditional JavaScript code (traditional = not ES modules) in the /// Executes traditional JavaScript code (traditional = not ES modules) in the
/// realm's context. /// realm's context.
/// ///
@ -2488,16 +2494,28 @@ impl JsRealm {
/// The same `name` value can be used for multiple executions. /// The same `name` value can be used for multiple executions.
/// ///
/// `Error` can usually be downcast to `JsError`. /// `Error` can usually be downcast to `JsError`.
pub fn execute_script( pub fn execute_script<S: Into<ModuleCode>>(
&self, &self,
isolate: &mut v8::Isolate, isolate: &mut v8::Isolate,
name: &str, name: &'static str,
source_code: &str, source_code: S,
) -> Result<v8::Global<v8::Value>, Error> {
// Manual monomorphization (TODO: replace w/momo)
self.execute_script_inner(isolate, name, source_code.into())
}
fn execute_script_inner(
&self,
isolate: &mut v8::Isolate,
name: &'static str,
source_code: ModuleCode,
) -> Result<v8::Global<v8::Value>, Error> { ) -> Result<v8::Global<v8::Value>, Error> {
let scope = &mut self.handle_scope(isolate); let scope = &mut self.handle_scope(isolate);
let source = v8::String::new(scope, source_code).unwrap(); let source = Self::string_from_code(scope, &source_code).unwrap();
let name = v8::String::new(scope, name).unwrap(); assert!(name.is_ascii());
let name =
v8::String::new_external_onebyte_static(scope, name.as_bytes()).unwrap();
let origin = bindings::script_origin(scope, name); let origin = bindings::script_origin(scope, name);
let tc_scope = &mut v8::TryCatch::new(scope); let tc_scope = &mut v8::TryCatch::new(scope);
@ -3104,7 +3122,8 @@ pub mod tests {
runtime runtime
.execute_script( .execute_script(
"encode_decode_test.js", "encode_decode_test.js",
include_str!("encode_decode_test.js"), // Note: We make this to_owned because it contains non-ASCII chars
include_str!("encode_decode_test.js").to_owned(),
) )
.unwrap(); .unwrap();
if let Poll::Ready(Err(_)) = runtime.poll_event_loop(cx, false) { if let Poll::Ready(Err(_)) = runtime.poll_event_loop(cx, false) {
@ -3320,7 +3339,7 @@ pub mod tests {
export const a = "b"; export const a = "b";
export default 1 + 2; export default 1 + 2;
"# "#
.to_string(); .into();
let module_id = futures::executor::block_on( let module_id = futures::executor::block_on(
runtime.load_main_module(&specifier, Some(source_code)), runtime.load_main_module(&specifier, Some(source_code)),
@ -3490,7 +3509,8 @@ pub mod tests {
import {{ f{prev} }} from "file:///{prev}.js"; import {{ f{prev} }} from "file:///{prev}.js";
export function f{i}() {{ return f{prev}() }} export function f{i}() {{ return f{prev}() }}
"# "#
); )
.into();
let id = if main { let id = if main {
futures::executor::block_on( futures::executor::block_on(
@ -3559,8 +3579,7 @@ pub mod tests {
}); });
let specifier = crate::resolve_url("file:///0.js").unwrap(); let specifier = crate::resolve_url("file:///0.js").unwrap();
let source_code = let source_code = r#"export function f0() { return "hello world" }"#.into();
r#"export function f0() { return "hello world" }"#.to_string();
let id = futures::executor::block_on( let id = futures::executor::block_on(
runtime.load_side_module(&specifier, Some(source_code)), runtime.load_side_module(&specifier, Some(source_code)),
) )
@ -3620,7 +3639,7 @@ pub mod tests {
return mod.f400() + " " + Deno.core.ops.op_test(); return mod.f400() + " " + Deno.core.ops.op_test();
})();"# })();"#
.to_string(); .to_string();
let val = runtime3.execute_script(".", &source_code).unwrap(); let val = runtime3.execute_script(".", source_code).unwrap();
let val = futures::executor::block_on(runtime3.resolve_value(val)).unwrap(); let val = futures::executor::block_on(runtime3.resolve_value(val)).unwrap();
{ {
let scope = &mut runtime3.handle_scope(); let scope = &mut runtime3.handle_scope();
@ -4163,7 +4182,7 @@ Deno.core.opAsync("op_async_serialize_object_with_numbers_as_keys", {
) -> Pin<Box<ModuleSourceFuture>> { ) -> Pin<Box<ModuleSourceFuture>> {
async move { async move {
Ok(ModuleSource { Ok(ModuleSource {
code: b"console.log('hello world');".to_vec().into_boxed_slice(), code: b"console.log('hello world');".into(),
module_url_specified: "file:///main.js".to_string(), module_url_specified: "file:///main.js".to_string(),
module_url_found: "file:///main.js".to_string(), module_url_found: "file:///main.js".to_string(),
module_type: ModuleType::JavaScript, module_type: ModuleType::JavaScript,
@ -4180,7 +4199,7 @@ Deno.core.opAsync("op_async_serialize_object_with_numbers_as_keys", {
}); });
let specifier = crate::resolve_url("file:///main.js").unwrap(); let specifier = crate::resolve_url("file:///main.js").unwrap();
let source_code = "Deno.core.print('hello\\n')".to_string(); let source_code = "Deno.core.print('hello\\n')".into();
let module_id = futures::executor::block_on( let module_id = futures::executor::block_on(
runtime.load_main_module(&specifier, Some(source_code)), runtime.load_main_module(&specifier, Some(source_code)),
@ -4273,7 +4292,7 @@ Deno.core.opAsync("op_async_serialize_object_with_numbers_as_keys", {
.execute_script( .execute_script(
runtime.v8_isolate(), runtime.v8_isolate(),
"", "",
&format!( format!(
r#" r#"
globalThis.rejectValue = undefined; globalThis.rejectValue = undefined;
@ -4282,7 +4301,7 @@ Deno.core.opAsync("op_async_serialize_object_with_numbers_as_keys", {
}}); }});
Deno.core.opAsync("op_void_async").then(() => Promise.reject({number})); Deno.core.opAsync("op_void_async").then(() => Promise.reject({number}));
"# "#
), )
) )
.unwrap(); .unwrap();
} }
@ -4344,7 +4363,7 @@ Deno.core.opAsync("op_async_serialize_object_with_numbers_as_keys", {
async move { async move {
Ok(ModuleSource { Ok(ModuleSource {
code: source.as_bytes().to_vec().into_boxed_slice(), code: source.into(),
module_url_specified: "file:///main.js".to_string(), module_url_specified: "file:///main.js".to_string(),
module_url_found: "file:///main.js".to_string(), module_url_found: "file:///main.js".to_string(),
module_type: ModuleType::JavaScript, module_type: ModuleType::JavaScript,
@ -4858,7 +4877,7 @@ Deno.core.opAsync("op_async_serialize_object_with_numbers_as_keys", {
async move { async move {
Ok(ModuleSource { Ok(ModuleSource {
code: source.as_bytes().to_vec().into_boxed_slice(), code: source.into(),
module_url_specified: "file:///main.js".to_string(), module_url_specified: "file:///main.js".to_string(),
module_url_found: "file:///main.js".to_string(), module_url_found: "file:///main.js".to_string(),
module_type: ModuleType::JavaScript, module_type: ModuleType::JavaScript,

View File

@ -390,7 +390,7 @@ pub fn initialize_runtime(
} else { } else {
"undefined".to_string() "undefined".to_string()
}; };
let source_code = &format!( let source_code = format!(
r#"(function loadBuiltinNodeModules(nodeGlobalThisName, usesLocalNodeModulesDir, argv0) {{ r#"(function loadBuiltinNodeModules(nodeGlobalThisName, usesLocalNodeModulesDir, argv0) {{
Deno[Deno.internal].node.initialize( Deno[Deno.internal].node.initialize(
nodeGlobalThisName, nodeGlobalThisName,
@ -403,7 +403,7 @@ pub fn initialize_runtime(
argv0 argv0
); );
js_runtime.execute_script(&located_script_name!(), source_code)?; js_runtime.execute_script(located_script_name!(), source_code)?;
Ok(()) Ok(())
} }
@ -417,7 +417,7 @@ pub fn load_cjs_module(
text.replace('\\', r"\\").replace('\'', r"\'") text.replace('\\', r"\\").replace('\'', r"\'")
} }
let source_code = &format!( let source_code = format!(
r#"(function loadCjsModule(moduleName, isMain, inspectBrk) {{ r#"(function loadCjsModule(moduleName, isMain, inspectBrk) {{
Deno[Deno.internal].node.loadCjsModule(moduleName, isMain, inspectBrk); Deno[Deno.internal].node.loadCjsModule(moduleName, isMain, inspectBrk);
}})('{module}', {main}, {inspect_brk});"#, }})('{module}', {main}, {inspect_brk});"#,
@ -426,6 +426,6 @@ pub fn load_cjs_module(
inspect_brk = inspect_brk, inspect_brk = inspect_brk,
); );
js_runtime.execute_script(&located_script_name!(), source_code)?; js_runtime.execute_script(located_script_name!(), source_code)?;
Ok(()) Ok(())
} }

View File

@ -17,11 +17,12 @@ mod startup_snapshot {
use deno_core::snapshot_util::*; use deno_core::snapshot_util::*;
use deno_core::Extension; use deno_core::Extension;
use deno_core::ExtensionFileSource; use deno_core::ExtensionFileSource;
use deno_core::ModuleCode;
use std::path::Path; use std::path::Path;
fn transpile_ts_for_snapshotting( fn transpile_ts_for_snapshotting(
file_source: &ExtensionFileSource, file_source: &ExtensionFileSource,
) -> Result<String, AnyError> { ) -> Result<ModuleCode, AnyError> {
let media_type = MediaType::from_path(Path::new(&file_source.specifier)); let media_type = MediaType::from_path(Path::new(&file_source.specifier));
let should_transpile = match media_type { let should_transpile = match media_type {
@ -41,7 +42,7 @@ mod startup_snapshot {
let parsed = deno_ast::parse_module(ParseParams { let parsed = deno_ast::parse_module(ParseParams {
specifier: file_source.specifier.to_string(), specifier: file_source.specifier.to_string(),
text_info: SourceTextInfo::from_string(code), text_info: SourceTextInfo::from_string(code.take_as_string()),
media_type, media_type,
capture_tokens: false, capture_tokens: false,
scope_analysis: false, scope_analysis: false,
@ -53,7 +54,7 @@ mod startup_snapshot {
..Default::default() ..Default::default()
})?; })?;
Ok(transpiled_source.text) Ok(transpiled_source.text.into())
} }
#[derive(Clone)] #[derive(Clone)]

View File

@ -25,6 +25,7 @@ use deno_core::CompiledWasmModuleStore;
use deno_core::Extension; use deno_core::Extension;
use deno_core::GetErrorClassFn; use deno_core::GetErrorClassFn;
use deno_core::JsRuntime; use deno_core::JsRuntime;
use deno_core::ModuleCode;
use deno_core::ModuleId; use deno_core::ModuleId;
use deno_core::ModuleLoader; use deno_core::ModuleLoader;
use deno_core::ModuleSpecifier; use deno_core::ModuleSpecifier;
@ -575,16 +576,16 @@ impl WebWorker {
"#; "#;
let poll_for_messages_fn = self let poll_for_messages_fn = self
.js_runtime .js_runtime
.execute_script(&located_script_name!(), script) .execute_script(located_script_name!(), script)
.expect("Failed to execute worker bootstrap script"); .expect("Failed to execute worker bootstrap script");
self.poll_for_messages_fn = Some(poll_for_messages_fn); self.poll_for_messages_fn = Some(poll_for_messages_fn);
} }
/// See [JsRuntime::execute_script](deno_core::JsRuntime::execute_script) /// See [JsRuntime::execute_script](deno_core::JsRuntime::execute_script)
pub fn execute_script( pub fn execute_script<S: Into<ModuleCode>>(
&mut self, &mut self,
name: &str, name: &'static str,
source_code: &str, source_code: S,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
self.js_runtime.execute_script(name, source_code)?; self.js_runtime.execute_script(name, source_code)?;
Ok(()) Ok(())
@ -744,7 +745,7 @@ fn print_worker_error(
pub fn run_web_worker( pub fn run_web_worker(
worker: WebWorker, worker: WebWorker,
specifier: ModuleSpecifier, specifier: ModuleSpecifier,
maybe_source_code: Option<String>, mut maybe_source_code: Option<String>,
preload_module_cb: Arc<ops::worker_host::WorkerEventCb>, preload_module_cb: Arc<ops::worker_host::WorkerEventCb>,
pre_execute_module_cb: Arc<ops::worker_host::WorkerEventCb>, pre_execute_module_cb: Arc<ops::worker_host::WorkerEventCb>,
format_js_error_fn: Option<Arc<FormatJsErrorFn>>, format_js_error_fn: Option<Arc<FormatJsErrorFn>>,
@ -772,8 +773,8 @@ pub fn run_web_worker(
}; };
// Execute provided source code immediately // Execute provided source code immediately
let result = if let Some(source_code) = maybe_source_code { let result = if let Some(source_code) = maybe_source_code.take() {
let r = worker.execute_script(&located_script_name!(), &source_code); let r = worker.execute_script(located_script_name!(), source_code);
worker.start_polling_for_messages(); worker.start_polling_for_messages();
r r
} else { } else {

View File

@ -21,6 +21,7 @@ use deno_core::FsModuleLoader;
use deno_core::GetErrorClassFn; use deno_core::GetErrorClassFn;
use deno_core::JsRuntime; use deno_core::JsRuntime;
use deno_core::LocalInspectorSession; use deno_core::LocalInspectorSession;
use deno_core::ModuleCode;
use deno_core::ModuleId; use deno_core::ModuleId;
use deno_core::ModuleLoader; use deno_core::ModuleLoader;
use deno_core::ModuleSpecifier; use deno_core::ModuleSpecifier;
@ -364,10 +365,10 @@ impl MainWorker {
} }
/// See [JsRuntime::execute_script](deno_core::JsRuntime::execute_script) /// See [JsRuntime::execute_script](deno_core::JsRuntime::execute_script)
pub fn execute_script( pub fn execute_script<S: Into<ModuleCode>>(
&mut self, &mut self,
script_name: &str, script_name: &'static str,
source_code: &str, source_code: S,
) -> Result<v8::Global<v8::Value>, AnyError> { ) -> Result<v8::Global<v8::Value>, AnyError> {
self.js_runtime.execute_script(script_name, source_code) self.js_runtime.execute_script(script_name, source_code)
} }
@ -502,7 +503,7 @@ impl MainWorker {
/// Does not poll event loop, and thus not await any of the "load" event handlers. /// Does not poll event loop, and thus not await any of the "load" event handlers.
pub fn dispatch_load_event( pub fn dispatch_load_event(
&mut self, &mut self,
script_name: &str, script_name: &'static str,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
self.execute_script( self.execute_script(
script_name, script_name,
@ -519,7 +520,7 @@ impl MainWorker {
/// Does not poll event loop, and thus not await any of the "unload" event handlers. /// Does not poll event loop, and thus not await any of the "unload" event handlers.
pub fn dispatch_unload_event( pub fn dispatch_unload_event(
&mut self, &mut self,
script_name: &str, script_name: &'static str,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
self.execute_script( self.execute_script(
script_name, script_name,
@ -536,7 +537,7 @@ impl MainWorker {
/// running. /// running.
pub fn dispatch_beforeunload_event( pub fn dispatch_beforeunload_event(
&mut self, &mut self,
script_name: &str, script_name: &'static str,
) -> Result<bool, AnyError> { ) -> Result<bool, AnyError> {
let value = self.js_runtime.execute_script( let value = self.js_runtime.execute_script(
script_name, script_name,