mod buffered_random;
mod builtins;
mod error;
mod event_loop;
mod isolate_state;
mod js_loading;
mod module;
mod pool;
mod script;
mod sourcemap;
pub(crate) use isolate_state::IsolateState;
pub fn init(v8_flags: Option<Vec<String>>) {
static ICU_INIT: std::sync::Once = std::sync::Once::new();
ICU_INIT.call_once(|| {
let icu_data =
align_data::include_aligned!(align_data::Align16, "../third_party/icu/icudtl.dat");
let _ = v8::icu::set_common_data_77(icu_data);
v8::icu::set_default_locale("en_US");
});
let mut flags = v8_flags.unwrap_or_default();
let perf_flags = [
"--turbofan", "--opt", ];
for flag in &perf_flags {
if !flags
.iter()
.any(|f| f.starts_with(flag) || f.starts_with(&format!("--no-{}", &flag[2..])))
{
flags.push(flag.to_string());
}
}
flags.push("jstime".to_owned());
flags.rotate_right(1);
v8::V8::set_flags_from_command_line(flags);
static V8_INIT: std::sync::Once = std::sync::Once::new();
V8_INIT.call_once(|| {
let platform = v8::new_default_platform(0, false).make_shared();
v8::V8::initialize_platform(platform);
v8::V8::initialize();
});
}
#[derive(Default)]
pub struct Options {
pub snapshot: Option<&'static [u8]>,
taking_snapshot: bool,
pub process_argv: Vec<String>,
pub warmup_iterations: usize,
}
impl Options {
pub fn new(snapshot: Option<&'static [u8]>) -> Options {
Options {
snapshot,
taking_snapshot: false,
process_argv: Vec::new(),
warmup_iterations: 0,
}
}
pub fn with_process_argv(mut self, argv: Vec<String>) -> Self {
self.process_argv = argv;
self
}
pub fn with_warmup(mut self, iterations: usize) -> Self {
self.warmup_iterations = iterations;
self
}
}
#[allow(clippy::all)]
pub struct JSTime {
isolate: Option<v8::OwnedIsolate>,
taking_snapshot: bool,
warmup_iterations: usize,
}
impl JSTime {
pub fn new(options: Options) -> JSTime {
let mut create_params = v8::Isolate::create_params()
.external_references(builtins::get_external_references().into_vec().into())
.heap_limits(0, 1024 * 1024 * 1024); if let Some(snapshot) = options.snapshot {
create_params = create_params.snapshot_blob(snapshot.into());
}
let isolate = v8::Isolate::new(create_params);
JSTime::create(options, isolate)
}
pub fn create_snapshot(mut options: Options) -> Vec<u8> {
assert!(
options.snapshot.is_none(),
"Cannot pass snapshot data while creating snapshot"
);
options.taking_snapshot = true;
let external_refs = builtins::get_external_references();
let external_refs_cow: std::borrow::Cow<'static, [v8::ExternalReference]> =
std::borrow::Cow::Owned(external_refs.into_vec());
let mut isolate = v8::Isolate::snapshot_creator(Some(external_refs_cow), None);
isolate.set_host_initialize_import_meta_object_callback(
module::host_initialize_import_meta_object_callback,
);
isolate.set_host_import_module_dynamically_callback(
module::host_import_module_dynamically_callback,
);
let global_context = {
v8::scope!(let scope, &mut isolate);
let context = v8::Context::new(scope, Default::default());
let isolate_ref: &v8::Isolate = scope;
v8::Global::new(isolate_ref, context)
};
isolate.set_slot(IsolateState::new(global_context, options.process_argv));
{
let context = IsolateState::get(&mut isolate).borrow().context();
v8::scope!(let scope, &mut isolate);
let context_local = v8::Local::new(scope, context);
let scope = &mut v8::ContextScope::new(scope, context_local);
builtins::Builtins::create(scope);
scope.set_default_context(context_local);
}
IsolateState::get(&mut isolate).borrow_mut().drop_context();
match isolate.create_blob(v8::FunctionCodeHandling::Keep) {
Some(data) => data.to_vec(),
None => {
panic!("Unable to create snapshot");
}
}
}
fn create(options: Options, mut isolate: v8::OwnedIsolate) -> JSTime {
isolate.set_host_initialize_import_meta_object_callback(
module::host_initialize_import_meta_object_callback,
);
isolate.set_host_import_module_dynamically_callback(
module::host_import_module_dynamically_callback,
);
let global_context = {
v8::scope!(let scope, &mut isolate);
let context = v8::Context::new(scope, Default::default());
let isolate_ref: &v8::Isolate = scope;
v8::Global::new(isolate_ref, context)
};
isolate.set_slot(IsolateState::new(global_context, options.process_argv));
if options.snapshot.is_none() {
let context = IsolateState::get(&mut isolate).borrow().context();
v8::scope!(let scope, &mut isolate);
let context_local = v8::Local::new(scope, context);
let mut scope = v8::ContextScope::new(scope, context_local);
builtins::Builtins::create(&mut scope);
}
JSTime {
isolate: Some(isolate),
taking_snapshot: options.taking_snapshot,
warmup_iterations: options.warmup_iterations,
}
}
fn isolate(&mut self) -> &mut v8::Isolate {
match self.isolate.as_mut() {
Some(i) => i,
None => unsafe {
std::hint::unreachable_unchecked();
},
}
}
pub fn import(&mut self, filename: &str) -> Result<(), String> {
if self.warmup_iterations > 0 {
self.warmup_import(filename)?;
}
let result = {
let context = IsolateState::get(self.isolate()).borrow().context();
v8::scope!(let scope, self.isolate());
let context_local = v8::Local::new(scope, context);
let mut scope = v8::ContextScope::new(scope, context_local);
v8::tc_scope!(let tc, &mut scope);
let loader = module::Loader::new();
let mut cwd = std::env::current_dir().unwrap();
cwd.push("jstime");
let cwd = cwd.into_os_string().into_string().unwrap();
match loader.import(tc, &cwd, filename) {
Ok(_) => Ok(()),
Err(exception) => {
if tc.has_caught() {
Err(crate::error::format_exception(tc))
} else {
Err(crate::error::format_exception_value(tc, exception))
}
}
}
};
self.run_event_loop();
result
}
fn warmup_import(&mut self, filename: &str) -> Result<(), String> {
for _ in 0..self.warmup_iterations {
let context = IsolateState::get(self.isolate()).borrow().context();
v8::scope!(let scope, self.isolate());
let context_local = v8::Local::new(scope, context);
let mut scope = v8::ContextScope::new(scope, context_local);
v8::tc_scope!(let tc, &mut scope);
let loader = module::Loader::new();
let mut cwd = std::env::current_dir().unwrap();
cwd.push("jstime");
let cwd = cwd.into_os_string().into_string().unwrap();
match loader.import(tc, &cwd, filename) {
Ok(_) => {}
Err(exception) => {
if tc.has_caught() {
return Err(crate::error::format_exception(tc));
} else {
return Err(crate::error::format_exception_value(tc, exception));
}
}
}
}
Ok(())
}
pub fn run_script(&mut self, source: &str, filename: &str) -> Result<String, String> {
if self.warmup_iterations > 0 {
self.warmup_script(source, filename)?;
}
let result = self.run_script_no_event_loop(source, filename);
self.run_event_loop();
result
}
fn warmup_script(&mut self, source: &str, filename: &str) -> Result<(), String> {
for _ in 0..self.warmup_iterations {
let context = IsolateState::get(self.isolate()).borrow().context();
v8::scope!(let scope, self.isolate());
let context_local = v8::Local::new(scope, context);
let mut scope = v8::ContextScope::new(scope, context_local);
script::run(&mut scope, source, filename)?;
}
Ok(())
}
pub fn run_script_no_event_loop(
&mut self,
source: &str,
filename: &str,
) -> Result<String, String> {
let context = IsolateState::get(self.isolate()).borrow().context();
v8::scope!(let scope, self.isolate());
let context_local = v8::Local::new(scope, context);
let mut scope = v8::ContextScope::new(scope, context_local);
match script::run(&mut scope, source, filename) {
Ok(v) => {
let isolate: &v8::Isolate = &scope;
Ok(v.to_string(&scope).unwrap().to_rust_string_lossy(isolate))
}
Err(e) => Err(e),
}
}
pub fn tick_event_loop(&mut self) {
let context = IsolateState::get(self.isolate()).borrow().context();
v8::scope!(let scope, self.isolate());
let context_local = v8::Local::new(scope, context);
let mut scope = v8::ContextScope::new(scope, context_local);
let event_loop = event_loop::get_event_loop(&mut scope);
event_loop.borrow_mut().tick(&mut scope);
}
fn run_event_loop(&mut self) {
let context = IsolateState::get(self.isolate()).borrow().context();
v8::scope!(let scope, self.isolate());
let context_local = v8::Local::new(scope, context);
let mut scope = v8::ContextScope::new(scope, context_local);
let event_loop = event_loop::get_event_loop(&mut scope);
event_loop.borrow_mut().run(&mut scope);
}
pub fn get_global_names(&mut self) -> Vec<String> {
let script = r#"
(function() {
const names = [];
// Get own properties of globalThis
names.push(...Object.getOwnPropertyNames(globalThis));
// Sort and deduplicate
return [...new Set(names)].sort().join('\n');
})()
"#;
match self.run_script_no_event_loop(script, "__repl_get_globals__") {
Ok(result) => result
.lines()
.filter(|s| !s.is_empty())
.map(String::from)
.collect(),
Err(_) => Vec::new(),
}
}
pub fn get_property_names(&mut self, obj_expr: &str) -> Vec<String> {
let is_safe_expr = obj_expr
.chars()
.all(|c| c.is_alphanumeric() || c == '_' || c == '$' || c == '.');
if !is_safe_expr || obj_expr.is_empty() {
return Vec::new();
}
let script = format!(
r#"
(function() {{
try {{
const obj = {obj_expr};
if (obj === null || obj === undefined) {{
return '';
}}
const names = new Set();
// Walk the prototype chain to get all properties
let current = obj;
while (current !== null) {{
Object.getOwnPropertyNames(current).forEach(n => names.add(n));
current = Object.getPrototypeOf(current);
}}
// Also get symbol properties converted to strings (like [Symbol.iterator])
return [...names].sort().join('\n');
}} catch (e) {{
return '';
}}
}})()
"#
);
match self.run_script_no_event_loop(&script, "__repl_get_props__") {
Ok(result) => result
.lines()
.filter(|s| !s.is_empty())
.map(String::from)
.collect(),
Err(_) => Vec::new(),
}
}
}
impl Drop for JSTime {
fn drop(&mut self) {
if self.taking_snapshot {
std::mem::forget(self.isolate.take().unwrap())
}
}
}