diff options
| author | Matt Mastracci <matthew@mastracci.com> | 2023-03-21 16:33:12 -0600 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-03-21 22:33:12 +0000 |
| commit | 0b4770fa7daf274ab01923fb09fd604aeb27e417 (patch) | |
| tree | bee10a226239c2787caa87944a5f80783048d9f9 /core | |
| parent | 253b556e6f430012c3094d47838fe397fa588028 (diff) | |
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.
Diffstat (limited to 'core')
| -rw-r--r-- | core/examples/eval_js_value.rs | 2 | ||||
| -rw-r--r-- | core/examples/ts_module_loader.rs | 2 | ||||
| -rw-r--r-- | core/extensions.rs | 10 | ||||
| -rw-r--r-- | core/lib.rs | 3 | ||||
| -rw-r--r-- | core/modules.rs | 331 | ||||
| -rw-r--r-- | core/runtime.rs | 99 |
6 files changed, 356 insertions, 91 deletions
diff --git a/core/examples/eval_js_value.rs b/core/examples/eval_js_value.rs index 6990abb85..e5b823a09 100644 --- a/core/examples/eval_js_value.rs +++ b/core/examples/eval_js_value.rs @@ -26,7 +26,7 @@ fn main() { fn eval( context: &mut JsRuntime, - code: &str, + code: &'static str, ) -> Result<serde_json::Value, String> { let res = context.execute_script("<anon>", code); match res { diff --git a/core/examples/ts_module_loader.rs b/core/examples/ts_module_loader.rs index c7097fc91..4a38073ab 100644 --- a/core/examples/ts_module_loader.rs +++ b/core/examples/ts_module_loader.rs @@ -82,7 +82,7 @@ impl ModuleLoader for TypescriptModuleLoader { code }; let module = ModuleSource { - code: code.into_bytes().into_boxed_slice(), + code: code.into(), module_type, module_url_specified: module_specifier.to_string(), module_url_found: module_specifier.to_string(), diff --git a/core/extensions.rs b/core/extensions.rs index 2a578429b..94c4a2a79 100644 --- a/core/extensions.rs +++ b/core/extensions.rs @@ -1,4 +1,5 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +use crate::modules::ModuleCode; use crate::OpState; use anyhow::Context as _; use anyhow::Error; @@ -23,13 +24,12 @@ pub enum ExtensionFileSourceCode { } impl ExtensionFileSourceCode { - pub fn load(&self) -> Result<String, Error> { + pub fn load(&self) -> Result<ModuleCode, Error> { match self { - ExtensionFileSourceCode::IncludedInBinary(code) => Ok(code.to_string()), + ExtensionFileSourceCode::IncludedInBinary(code) => Ok((*code).into()), ExtensionFileSourceCode::LoadedFromFsDuringSnapshot(path) => { - let msg = format!("Failed to read \"{}\"", path.display()); - let code = std::fs::read_to_string(path).context(msg)?; - Ok(code) + let msg = || format!("Failed to read \"{}\"", path.display()); + Ok(std::fs::read_to_string(path).with_context(msg)?.into()) } } } diff --git a/core/lib.rs b/core/lib.rs index 7751be6e8..ba0a026bc 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -77,6 +77,7 @@ pub use crate::module_specifier::ModuleSpecifier; pub use crate::modules::ExtModuleLoader; pub use crate::modules::ExtModuleLoaderCb; pub use crate::modules::FsModuleLoader; +pub use crate::modules::ModuleCode; pub use crate::modules::ModuleId; pub use crate::modules::ModuleLoader; pub use crate::modules::ModuleSource; @@ -144,7 +145,7 @@ pub mod _ops { #[macro_export] macro_rules! located_script_name { () => { - format!("[ext:{}:{}:{}]", std::file!(), std::line!(), std::column!()); + concat!("[ext:{}:{}:{}]", std::file!(), std::line!(), std::column!()); }; } diff --git a/core/modules.rs b/core/modules.rs index 2d80071aa..78efdedfd 100644 --- a/core/modules.rs +++ b/core/modules.rs @@ -10,6 +10,7 @@ use crate::snapshot_util::SnapshottedData; use crate::JsRuntime; use crate::OpState; use anyhow::Error; +use core::panic; use futures::future::FutureExt; use futures::stream::FuturesUnordered; use futures::stream::Stream; @@ -18,6 +19,7 @@ use futures::stream::TryStreamExt; use log::debug; use serde::Deserialize; use serde::Serialize; +use std::borrow::Cow; use std::cell::RefCell; use std::collections::HashMap; use std::collections::HashSet; @@ -25,6 +27,7 @@ use std::collections::VecDeque; use std::future::Future; use std::pin::Pin; use std::rc::Rc; +use std::sync::Arc; use std::task::Context; 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 // of things throughout the codebase otherwise we may end up requesting // 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 code: Box<[u8]>, + pub code: ModuleCode, pub module_type: ModuleType, pub module_url_specified: 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 = dyn Future<Output = (ModuleLoadId, Result<RecursiveModuleLoad, 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 /// transpile sources before passing to V8. pub type ExtModuleLoaderCb = - Box<dyn Fn(&ExtensionFileSource) -> Result<String, Error>>; + Box<dyn Fn(&ExtensionFileSource) -> Result<ModuleCode, Error>>; pub struct ExtModuleLoader { module_loader: Rc<dyn ModuleLoader>, @@ -448,7 +592,7 @@ impl ModuleLoader for ExtModuleLoader { return async move { let code = result?; let source = ModuleSource { - code: code.into_bytes().into_boxed_slice(), + code, module_type: ModuleType::JavaScript, module_url_specified: specifier.clone(), module_url_found: specifier.clone(), @@ -529,7 +673,7 @@ impl ModuleLoader for FsModuleLoader { let code = std::fs::read(path)?; let module = ModuleSource { - code: code.into_boxed_slice(), + code: code.into(), module_type, module_url_specified: module_specifier.to_string(), module_url_found: module_specifier.to_string(), @@ -1002,6 +1146,32 @@ pub(crate) enum ModuleError { 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. pub(crate) struct ModuleMap { // 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, scope: &mut v8::HandleScope, - name: &str, - source: &[u8], + name: N, + source: &ModuleCode, ) -> 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( scope, - strip_bom(source), + strip_bom(source.as_bytes()), v8::NewStringType::Normal, ) .unwrap(); @@ -1364,25 +1572,47 @@ impl ModuleMap { let value_handle = v8::Global::<v8::Value>::new(tc_scope, parsed_json); self.json_value_store.insert(handle.clone(), value_handle); - let id = - self.create_module_info(name, ModuleType::Json, handle, false, vec![]); + let id = self.create_module_info( + name.as_ref(), + ModuleType::Json, + handle, + false, + vec![], + ); Ok(id) } - // Create and compile an ES module. - pub(crate) fn new_es_module( + /// Create and compile an ES module. Generic interface that can receive either a `&'static str` or a string with a lifetime. Prefer + /// 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, scope: &mut v8::HandleScope, main: bool, - name: &str, - source: &[u8], + name: N, + source: &ModuleCode, is_dynamic_import: bool, ) -> Result<ModuleId, ModuleError> { - let name_str = v8::String::new(scope, name).unwrap(); - let source_str = - v8::String::new_from_utf8(scope, source, v8::NewStringType::Normal) - .unwrap(); + // Manual monomorphization (TODO: replace w/momo) + self.new_es_module_inner( + scope, + 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 source = v8::script_compiler::Source::new(source_str, Some(&origin)); @@ -1432,7 +1662,7 @@ impl ModuleMap { self.snapshot_loaded_and_not_snapshotting, self.loader.clone(), &import_specifier, - name, + name.as_ref(), if is_dynamic_import { ResolutionKind::DynamicImport } else { @@ -1456,7 +1686,7 @@ impl ModuleMap { if let Some(main_module) = maybe_main_module { return Err(ModuleError::Other(generic_error( format!("Trying to create \"main\" module ({:?}), when one already exists ({:?})", - name, + name.as_ref(), main_module.name, )))); } @@ -1464,7 +1694,7 @@ impl ModuleMap { let handle = v8::Global::<v8::Module>::new(tc_scope, module); let id = self.create_module_info( - name, + name.as_ref(), ModuleType::JavaScript, handle, main, @@ -1846,7 +2076,7 @@ import "/a.js"; } match mock_source_code(&inner.url) { 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_url_specified: inner.url.clone(), module_url_found: src.1.to_owned(), @@ -2043,12 +2273,13 @@ import "/a.js"; scope, true, &specifier_a, - br#" + &br#" import { b } from './b.js' if (b() != 'b') throw Error(); let control = 42; Deno.core.ops.op_test(control); - "#, + "# + .into(), false, ) .unwrap(); @@ -2068,7 +2299,7 @@ import "/a.js"; scope, false, "file:///b.js", - b"export function b() { return 'b' }", + &b"export function b() { return 'b' }".into(), false, ) .unwrap(); @@ -2153,11 +2384,12 @@ import "/a.js"; scope, true, &specifier_a, - br#" + &br#" import jsonData from './b.json' assert {type: "json"}; assert(jsonData.a == "b"); assert(jsonData.c.d == 10); - "#, + "# + .into(), false, ) .unwrap(); @@ -2175,7 +2407,7 @@ import "/a.js"; .new_json_module( scope, "file:///b.json", - b"{\"a\": \"b\", \"c\": {\"d\": 10}}", + &b"{\"a\": \"b\", \"c\": {\"d\": 10}}".into(), ) .unwrap(); let imports = module_map.get_requested_modules(mod_b).unwrap(); @@ -2285,9 +2517,7 @@ import "/a.js"; let info = ModuleSource { module_url_specified: specifier.to_string(), module_url_found: specifier.to_string(), - code: b"export function b() { return 'b' }" - .to_vec() - .into_boxed_slice(), + code: b"export function b() { return 'b' }".into(), module_type: ModuleType::JavaScript, }; async move { Ok(info) }.boxed() @@ -2427,7 +2657,7 @@ import "/a.js"; let info = ModuleSource { module_url_specified: 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, }; 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. let spec = resolve_url("file:///main_with_code.js").unwrap(); 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(); 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 { module_url_specified: "file:///main_module.js".to_string(), module_url_found: "file:///main_module.js".to_string(), - code: b"if (!import.meta.main) throw Error();" - .to_vec() - .into_boxed_slice(), + code: b"if (!import.meta.main) throw Error();".into(), module_type: ModuleType::JavaScript, }), "file:///side_module.js" => Ok(ModuleSource { module_url_specified: "file:///side_module.js".to_string(), module_url_found: "file:///side_module.js".to_string(), - code: b"if (import.meta.main) throw Error();" - .to_vec() - .into_boxed_slice(), + code: b"if (import.meta.main) throw Error();".into(), module_type: ModuleType::JavaScript, }), _ => 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. let spec = resolve_url("file:///main_with_code.js").unwrap(); 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(); 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. let spec = resolve_url("file:///main_with_code.js").unwrap(); 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(); 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()) ); } + + #[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()); + } } diff --git a/core/runtime.rs b/core/runtime.rs index 7b6dfba92..f9f2e5523 100644 --- a/core/runtime.rs +++ b/core/runtime.rs @@ -9,6 +9,7 @@ use crate::extensions::OpEventLoopFn; use crate::inspector::JsRuntimeInspector; use crate::module_specifier::ModuleSpecifier; use crate::modules::ExtModuleLoaderCb; +use crate::modules::ModuleCode; use crate::modules::ModuleError; use crate::modules::ModuleId; use crate::modules::ModuleLoadId; @@ -50,6 +51,8 @@ use std::sync::Mutex; use std::sync::Once; use std::task::Context; use std::task::Poll; +use v8::HandleScope; +use v8::Local; use v8::OwnedIsolate; type PendingOpFuture = OpCall<(RealmIdx, PromiseId, OpId, OpResult)>; @@ -748,7 +751,7 @@ impl JsRuntime { realm.execute_script( self.v8_isolate(), 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 /// 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" /// - "<anon>" @@ -911,10 +914,10 @@ impl JsRuntime { /// The same `name` value can be used for multiple executions. /// /// `Error` can usually be downcast to `JsError`. - pub fn execute_script( + pub fn execute_script<S: Into<ModuleCode>>( &mut self, - name: &str, - source_code: &str, + name: &'static str, + source_code: S, ) -> Result<v8::Global<v8::Value>, Error> { self .global_realm() @@ -2056,21 +2059,15 @@ impl JsRuntime { pub async fn load_main_module( &mut self, specifier: &ModuleSpecifier, - code: Option<String>, + code: Option<ModuleCode>, ) -> Result<ModuleId, Error> { let module_map_rc = Self::module_map(self.v8_isolate()); if let Some(code) = code { let scope = &mut self.handle_scope(); + // true for main module module_map_rc .borrow_mut() - .new_es_module( - scope, - // main module - true, - specifier.as_str(), - code.as_bytes(), - false, - ) + .new_es_module(scope, true, specifier, &code, false) .map_err(|e| match e { ModuleError::Exception(exception) => { let exception = v8::Local::new(scope, exception); @@ -2116,21 +2113,15 @@ impl JsRuntime { pub async fn load_side_module( &mut self, specifier: &ModuleSpecifier, - code: Option<String>, + code: Option<ModuleCode>, ) -> Result<ModuleId, Error> { let module_map_rc = Self::module_map(self.v8_isolate()); if let Some(code) = code { let scope = &mut self.handle_scope(); + // false for side module (not main module) module_map_rc .borrow_mut() - .new_es_module( - scope, - // not main module - false, - specifier.as_str(), - code.as_bytes(), - false, - ) + .new_es_module(scope, false, specifier, &code, false) .map_err(|e| match e { ModuleError::Exception(exception) => { let exception = v8::Local::new(scope, exception); @@ -2476,6 +2467,21 @@ impl JsRealm { 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 /// realm's context. /// @@ -2488,16 +2494,28 @@ impl JsRealm { /// The same `name` value can be used for multiple executions. /// /// `Error` can usually be downcast to `JsError`. - pub fn execute_script( + pub fn execute_script<S: Into<ModuleCode>>( &self, isolate: &mut v8::Isolate, - name: &str, - source_code: &str, + name: &'static 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> { let scope = &mut self.handle_scope(isolate); - let source = v8::String::new(scope, source_code).unwrap(); - let name = v8::String::new(scope, name).unwrap(); + let source = Self::string_from_code(scope, &source_code).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 tc_scope = &mut v8::TryCatch::new(scope); @@ -3104,7 +3122,8 @@ pub mod tests { runtime .execute_script( "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(); if let Poll::Ready(Err(_)) = runtime.poll_event_loop(cx, false) { @@ -3320,7 +3339,7 @@ pub mod tests { export const a = "b"; export default 1 + 2; "# - .to_string(); + .into(); let module_id = futures::executor::block_on( runtime.load_main_module(&specifier, Some(source_code)), @@ -3490,7 +3509,8 @@ pub mod tests { import {{ f{prev} }} from "file:///{prev}.js"; export function f{i}() {{ return f{prev}() }} "# - ); + ) + .into(); let id = if main { futures::executor::block_on( @@ -3559,8 +3579,7 @@ pub mod tests { }); let specifier = crate::resolve_url("file:///0.js").unwrap(); - let source_code = - r#"export function f0() { return "hello world" }"#.to_string(); + let source_code = r#"export function f0() { return "hello world" }"#.into(); let id = futures::executor::block_on( runtime.load_side_module(&specifier, Some(source_code)), ) @@ -3620,7 +3639,7 @@ pub mod tests { return mod.f400() + " " + Deno.core.ops.op_test(); })();"# .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 scope = &mut runtime3.handle_scope(); @@ -4163,7 +4182,7 @@ Deno.core.opAsync("op_async_serialize_object_with_numbers_as_keys", { ) -> Pin<Box<ModuleSourceFuture>> { async move { 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_found: "file:///main.js".to_string(), 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 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( 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( runtime.v8_isolate(), "", - &format!( + format!( r#" 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})); "# - ), + ) ) .unwrap(); } @@ -4344,7 +4363,7 @@ Deno.core.opAsync("op_async_serialize_object_with_numbers_as_keys", { async move { Ok(ModuleSource { - code: source.as_bytes().to_vec().into_boxed_slice(), + code: source.into(), module_url_specified: "file:///main.js".to_string(), module_url_found: "file:///main.js".to_string(), module_type: ModuleType::JavaScript, @@ -4858,7 +4877,7 @@ Deno.core.opAsync("op_async_serialize_object_with_numbers_as_keys", { async move { Ok(ModuleSource { - code: source.as_bytes().to_vec().into_boxed_slice(), + code: source.into(), module_url_specified: "file:///main.js".to_string(), module_url_found: "file:///main.js".to_string(), module_type: ModuleType::JavaScript, |
