1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-01-21 21:50:00 -05:00

perf(core): Cache source lookups (#14816)

Keep a cache for source maps and source lines. 

We sort of already had a cache argument for source map lookup 
functions but we just passed an empty map instead of storing it. 

Extended it to cache source line lookups as well and plugged it 
into runtime state.
This commit is contained in:
Nayeem Rahman 2022-06-20 13:42:20 +01:00 committed by GitHub
parent 94d369ebc6
commit 79b42808a4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 147 additions and 152 deletions

View file

@ -8,8 +8,6 @@ use deno_core::error::format_file_name;
use deno_core::error::JsError;
use deno_core::error::JsStackFrame;
const SOURCE_ABBREV_THRESHOLD: usize = 150;
// Keep in sync with `/core/error.js`.
pub fn format_location(frame: &JsStackFrame) -> String {
let _internal = frame
@ -115,8 +113,7 @@ fn format_maybe_source_line(
let source_line = source_line.unwrap();
// sometimes source_line gets set with an empty string, which then outputs
// an empty source line when displayed, so need just short circuit here.
// Also short-circuit on error line too long.
if source_line.is_empty() || source_line.len() > SOURCE_ABBREV_THRESHOLD {
if source_line.is_empty() {
return "".to_string();
}
if source_line.contains("Couldn't format source line: ") {

View file

@ -694,11 +694,9 @@ impl SourceMapGetter for ProcState {
_ => return None,
}
if let Some((code, maybe_map)) = self.get_emit(&specifier) {
let code = String::from_utf8(code).unwrap();
source_map_from_code(code).or(maybe_map)
source_map_from_code(&code).or(maybe_map)
} else if let Ok(source) = self.load(specifier, None, false) {
let code = String::from_utf8(source.code.to_vec()).unwrap();
source_map_from_code(code)
source_map_from_code(&source.code)
} else {
None
}
@ -756,24 +754,17 @@ pub fn import_map_from_text(
Ok(result.import_map)
}
fn source_map_from_code(code: String) -> Option<Vec<u8>> {
let lines: Vec<&str> = code.split('\n').collect();
if let Some(last_line) = lines.last() {
if last_line
.starts_with("//# sourceMappingURL=data:application/json;base64,")
{
let input = last_line.trim_start_matches(
"//# sourceMappingURL=data:application/json;base64,",
);
fn source_map_from_code(code: &[u8]) -> Option<Vec<u8>> {
static PREFIX: &[u8] = b"//# sourceMappingURL=data:application/json;base64,";
let last_line = code.rsplitn(2, |u| u == &b'\n').next().unwrap();
if last_line.starts_with(PREFIX) {
let input = last_line.split_at(PREFIX.len()).1;
let decoded_map = base64::decode(input)
.expect("Unable to decode source map from emitted file.");
Some(decoded_map)
} else {
None
}
} else {
None
}
}
#[derive(Debug)]

View file

@ -2,10 +2,10 @@
use crate::runtime::JsRuntime;
use crate::source_map::apply_source_map;
use crate::source_map::get_source_line;
use crate::url::Url;
use anyhow::Error;
use std::borrow::Cow;
use std::collections::HashMap;
use std::collections::HashSet;
use std::fmt;
use std::fmt::Debug;
@ -192,6 +192,10 @@ impl JsError {
let msg = v8::Exception::create_message(scope, exception);
let mut exception_message = None;
// Nest this state borrow. A mutable borrow can occur when accessing `stack`
// in this outer scope, invoking `Error.prepareStackTrace()` which calls
// `op_apply_source_map`.
{
let state_rc = JsRuntime::state(scope);
let state = state_rc.borrow();
if let Some(format_exception_cb) = &state.js_format_exception_cb {
@ -204,6 +208,7 @@ impl JsError {
}
}
}
}
if is_instance_of_error(scope, exception) {
// The exception is a JS Error object.
@ -255,8 +260,11 @@ impl JsError {
None => vec![],
};
let mut source_line = None;
let mut source_line_frame_index = None;
{
let state_rc = JsRuntime::state(scope);
let state = state_rc.borrow();
let state = &mut *state_rc.borrow_mut();
// When the stack frame array is empty, but the source location given by
// (script_resource_name, line_number, start_column + 1) exists, this is
@ -269,18 +277,19 @@ impl JsError {
.map(|v| v.to_rust_string_lossy(scope));
let line_number: Option<i64> =
msg.get_line_number(scope).and_then(|v| v.try_into().ok());
let column_number: Option<i64> = msg.get_start_column().try_into().ok();
let column_number: Option<i64> =
msg.get_start_column().try_into().ok();
if let (Some(f), Some(l), Some(c)) =
(script_resource_name, line_number, column_number)
{
// V8's column numbers are 0-based, we want 1-based.
let c = c + 1;
if let Some(source_map_getter) = &state.source_map_getter {
let (f, l, c, _) = apply_source_map(
let (f, l, c) = apply_source_map(
f,
l,
c,
&mut HashMap::new(),
&mut state.source_map_cache,
source_map_getter.as_ref(),
);
frames =
@ -292,8 +301,6 @@ impl JsError {
}
}
let mut source_line = None;
let mut source_line_frame_index = None;
if let Some(source_map_getter) = &state.source_map_getter {
for (i, frame) in frames.iter().enumerate() {
if let (Some(file_name), Some(line_number)) =
@ -301,8 +308,12 @@ impl JsError {
{
if !file_name.trim_start_matches('[').starts_with("deno:") {
// Source lookup expects a 0-based line number, ours are 1-based.
source_line = source_map_getter
.get_source_line(file_name, (line_number - 1) as usize);
source_line = get_source_line(
file_name,
line_number,
&mut state.source_map_cache,
source_map_getter.as_ref(),
);
source_line_frame_index = Some(i);
break;
}
@ -318,6 +329,7 @@ impl JsError {
}
}
}
}
// Read an array of stored errors, this is only defined for `AggregateError`
let aggregated_errors = get_property(scope, exception, "errors");

View file

@ -752,14 +752,14 @@ fn op_apply_source_map(
location: Location,
) -> Result<Location, Error> {
let state_rc = JsRuntime::state(scope);
let state = state_rc.borrow();
let state = &mut *state_rc.borrow_mut();
if let Some(source_map_getter) = &state.source_map_getter {
let mut location = location;
let (f, l, c, _) = apply_source_map_(
let (f, l, c) = apply_source_map_(
location.file_name,
location.line_number.into(),
location.column_number.into(),
&mut Default::default(),
&mut state.source_map_cache,
source_map_getter.as_ref(),
);
location.file_name = f;

View file

@ -17,6 +17,7 @@ use crate::modules::NoopModuleLoader;
use crate::op_void_async;
use crate::op_void_sync;
use crate::ops::*;
use crate::source_map::SourceMapCache;
use crate::source_map::SourceMapGetter;
use crate::Extension;
use crate::OpMiddlewareFn;
@ -163,6 +164,7 @@ pub(crate) struct JsRuntimeState {
/// of the event loop.
dyn_module_evaluate_idle_counter: u32,
pub(crate) source_map_getter: Option<Box<dyn SourceMapGetter>>,
pub(crate) source_map_cache: SourceMapCache,
pub(crate) pending_ops: FuturesUnordered<PendingOpFuture>,
pub(crate) unrefed_ops: HashSet<i32>,
pub(crate) have_unpolled_ops: bool,
@ -391,6 +393,7 @@ impl JsRuntime {
has_tick_scheduled: false,
js_wasm_streaming_cb: None,
source_map_getter: options.source_map_getter,
source_map_cache: Default::default(),
pending_ops: FuturesUnordered::new(),
unrefed_ops: HashSet::new(),
shared_array_buffer_store: options.shared_array_buffer_store,

View file

@ -17,29 +17,34 @@ pub trait SourceMapGetter {
) -> Option<String>;
}
/// Cached filename lookups. The key can be None if a previous lookup failed to
/// find a SourceMap.
pub type CachedMaps = HashMap<String, Option<SourceMap>>;
#[derive(Debug, Default)]
pub struct SourceMapCache {
maps: HashMap<String, Option<SourceMap>>,
source_lines: HashMap<(String, i64), Option<String>>,
}
pub fn apply_source_map<G: SourceMapGetter + ?Sized>(
file_name: String,
line_number: i64,
column_number: i64,
mappings_map: &mut CachedMaps,
cache: &mut SourceMapCache,
getter: &G,
) -> (String, i64, i64, Option<String>) {
) -> (String, i64, i64) {
// Lookup expects 0-based line and column numbers, but ours are 1-based.
let line_number = line_number - 1;
let column_number = column_number - 1;
let default_pos = (file_name.clone(), line_number, column_number, None);
let maybe_source_map = get_mappings(&file_name, mappings_map, getter);
let (file_name, line_number, column_number, source_line) =
match maybe_source_map {
let default_pos = (file_name.clone(), line_number, column_number);
let maybe_source_map =
cache.maps.entry(file_name.clone()).or_insert_with(|| {
getter
.get_source_map(&file_name)
.and_then(|raw_source_map| SourceMap::from_slice(&raw_source_map).ok())
});
let (file_name, line_number, column_number) = match maybe_source_map {
None => default_pos,
Some(source_map) => {
match source_map.lookup_token(line_number as u32, column_number as u32)
{
match source_map.lookup_token(line_number as u32, column_number as u32) {
None => default_pos,
Some(token) => match token.get_source() {
None => default_pos,
@ -53,47 +58,34 @@ pub fn apply_source_map<G: SourceMapGetter + ?Sized>(
Ok(m) => m.to_string(),
Err(_) => file_name,
};
let source_line =
if let Some(source_view) = token.get_source_view() {
source_view
.get_line(token.get_src_line())
.map(|s| s.to_string())
} else {
None
};
(
file_name,
i64::from(token.get_src_line()),
i64::from(token.get_src_col()),
source_line,
)
}
},
}
}
};
let source_line = source_line
.or_else(|| getter.get_source_line(&file_name, line_number as usize));
(file_name, line_number + 1, column_number + 1, source_line)
(file_name, line_number + 1, column_number + 1)
}
fn get_mappings<'a, G: SourceMapGetter + ?Sized>(
file_name: &str,
mappings_map: &'a mut CachedMaps,
getter: &G,
) -> &'a Option<SourceMap> {
mappings_map
.entry(file_name.to_string())
.or_insert_with(|| parse_map_string(file_name, getter))
}
const MAX_SOURCE_LINE_LENGTH: usize = 150;
// TODO(kitsonk) parsed source maps should probably be cached in state in
// the module meta data.
fn parse_map_string<G: SourceMapGetter + ?Sized>(
pub fn get_source_line<G: SourceMapGetter + ?Sized>(
file_name: &str,
line_number: i64,
cache: &mut SourceMapCache,
getter: &G,
) -> Option<SourceMap> {
getter
.get_source_map(file_name)
.and_then(|raw_source_map| SourceMap::from_slice(&raw_source_map).ok())
) -> Option<String> {
cache
.source_lines
.entry((file_name.to_string(), line_number))
.or_insert_with(|| {
// Source lookup expects a 0-based line number, ours are 1-based.
let s = getter.get_source_line(file_name, (line_number - 1) as usize);
s.filter(|s| s.len() <= MAX_SOURCE_LINE_LENGTH)
})
.clone()
}