Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Let runtimes handle imports #349

Draft
wants to merge 14 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions nova_vm/src/ecmascript/builtins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,5 @@ pub use builtin_function::{
BuiltinFunctionArgs, BuiltinGetter, ConstructorFn, RegularFn as JsFunction, RegularFn,
};
pub(crate) use builtin_function::{BuiltinIntrinsic, BuiltinIntrinsicConstructor};
pub use control_abstraction_objects::*;
pub(crate) use ecmascript_function::*;
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ pub(crate) mod async_generator_objects;
pub(crate) mod generator_function_objects;
pub(crate) mod generator_objects;
pub(crate) mod iteration;
pub(crate) mod promise_objects;
pub mod promise_objects;
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

pub(crate) mod promise_abstract_operations;
pub(crate) mod promise_constructor;
pub(crate) mod promise_prototype;
pub mod promise_abstract_operations;
pub mod promise_constructor;
pub mod promise_prototype;
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

pub(crate) mod promise_capability_records;
pub(crate) mod promise_jobs;
pub(crate) mod promise_reaction_records;
pub(crate) mod promise_resolving_functions;
pub mod promise_capability_records;
pub mod promise_jobs;
pub mod promise_reaction_records;
pub mod promise_resolving_functions;
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ use super::promise_jobs::new_promise_resolve_thenable_job;
/// state is Fulfilled or Rejected. If true, it also counts as already resolved
/// if it's Pending but `is_resolved` is set to true.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct PromiseCapability {
pub struct PromiseCapability {
promise: Promise,
must_be_unresolved: bool,
}
Expand All @@ -47,18 +47,18 @@ impl PromiseCapability {
/// [27.2.1.5 NewPromiseCapability ( C )](https://tc39.es/ecma262/#sec-newpromisecapability)
/// NOTE: Our implementation doesn't take C as a parameter, since we don't
/// yet support promise subclassing.
pub(crate) fn new(agent: &mut Agent) -> Self {
pub fn new(agent: &mut Agent) -> Self {
Self::from_promise(agent.heap.create(PromiseHeapData::default()), true)
}

pub(crate) fn from_promise(promise: Promise, must_be_unresolved: bool) -> Self {
pub fn from_promise(promise: Promise, must_be_unresolved: bool) -> Self {
Self {
promise,
must_be_unresolved,
}
}

pub(crate) fn promise(&self) -> Promise {
pub fn promise(&self) -> Promise {
self.promise
}

Expand Down
27 changes: 25 additions & 2 deletions nova_vm/src/ecmascript/execution/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
//! - This is inspired by and/or copied from Kiesel engine:
//! Copyright (c) 2023-2024 Linus Groh

use oxc_ast::ast;

use super::{
environments::get_identifier_reference, EnvironmentIndex, ExecutionContext, Realm,
RealmIdentifier,
Expand All @@ -21,7 +23,7 @@ use crate::{
heap::CreateHeapData,
Heap,
};
use std::collections::HashMap;
use std::{any::Any, collections::HashMap};

#[derive(Debug, Default)]
pub struct Options {
Expand Down Expand Up @@ -122,6 +124,20 @@ pub trait HostHooks: std::fmt::Debug {
) {
// The default implementation of HostPromiseRejectionTracker is to return unused.
}

/// Handle import declarations.
///
/// Note: This will panic if not implemented manually.
fn import_module(&self, _import: &ast::ImportDeclaration<'_>, _agent: &mut Agent) {
unimplemented!()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably receive an &mut Executable or return something so the imported values can get injected.

}

/// Get access to the Host data, useful to share state between calls of built-in functions.
///
/// Note: This will panic if not implemented manually.
fn get_host_data(&self) -> &dyn Any {
unimplemented!()
}
}

/// ### [9.7 Agents](https://tc39.es/ecma262/#sec-agents)
Expand Down Expand Up @@ -182,13 +198,20 @@ impl Agent {
JsError(self.create_exception(kind, message))
}

pub(crate) fn running_execution_context(&self) -> &ExecutionContext {
pub fn running_execution_context(&self) -> &ExecutionContext {
self.execution_context_stack.last().unwrap()
}

pub(crate) fn running_execution_context_mut(&mut self) -> &mut ExecutionContext {
self.execution_context_stack.last_mut().unwrap()
}

/// Get access to the Host data, useful to share state between calls of built-in functions.
///
/// Note: This will panic if not implemented manually.
pub fn get_host_data(&self) -> &dyn Any {
self.host_hooks.get_host_data()
}
}

/// ### [9.4.1 GetActiveScriptOrModule ()](https://tc39.es/ecma262/#sec-getactivescriptormodule)
Expand Down
4 changes: 2 additions & 2 deletions nova_vm/src/ecmascript/execution/execution_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,12 @@ pub(crate) struct ECMAScriptCodeEvaluationState {
/// references to the running execution context in this specification denote
/// the running execution context of the surrounding agent.
#[derive(Debug)]
pub(crate) struct ExecutionContext {
pub struct ExecutionContext {
/// ### code evaluation state
///
/// Any state needed to perform, suspend, and resume evaluation of the code
/// associated with this execution context.
pub ecmascript_code: Option<ECMAScriptCodeEvaluationState>,
pub(crate) ecmascript_code: Option<ECMAScriptCodeEvaluationState>,

/// ### Function
///
Expand Down
2 changes: 1 addition & 1 deletion nova_vm/src/ecmascript/execution/realm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ pub struct Realm {
///
/// Field reserved for use by hosts that need to associate additional
/// information with a Realm Record.
pub(crate) host_defined: Option<&'static dyn Any>,
pub host_defined: Option<&'static dyn Any>,
}

unsafe impl Send for Realm {}
Expand Down
2 changes: 1 addition & 1 deletion nova_vm/src/ecmascript/scripts_and_modules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub mod module;
pub mod script;

#[derive(Debug, Clone, Copy)]
pub(crate) enum ScriptOrModule {
pub enum ScriptOrModule {
Script(ScriptIdentifier),
Module(Module),
}
Expand Down
4 changes: 2 additions & 2 deletions nova_vm/src/ecmascript/scripts_and_modules/script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ use std::{
pub type HostDefined = &'static mut dyn Any;

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct ScriptIdentifier(u32, PhantomData<Script>);
pub struct ScriptIdentifier(u32, PhantomData<Script>);

impl ScriptIdentifier {
/// Creates a script identififer from a usize.
Expand Down Expand Up @@ -145,7 +145,7 @@ pub struct Script {
///
/// Field reserved for use by host environments that need to associate
/// additional information with a script.
pub(crate) host_defined: Option<HostDefined>,
pub host_defined: Option<HostDefined>,

/// Source text of the script
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1176,8 +1176,8 @@ impl<'a> TopLevelLexicallyScopedDeclarations<'a> for Statement<'a> {
// Note: TopLevelLexicallScopedDeclarations should only be reached
// from Function body, Class static fields, and Script body. Module
// declarations should never be reached.
Statement::ImportDeclaration(_)
| Statement::ExportAllDeclaration(_)
Statement::ImportDeclaration(_) => {}
Statement::ExportAllDeclaration(_)
| Statement::ExportDefaultDeclaration(_)
| Statement::ExportNamedDeclaration(_) => unreachable!(),
}
Expand Down
6 changes: 3 additions & 3 deletions nova_vm/src/ecmascript/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ mod spec;

pub(crate) use language::*;
pub use language::{
bigint, BigInt, Function, HeapNumber, HeapString, InternalMethods, InternalSlots, IntoFunction,
IntoNumeric, IntoObject, IntoPrimitive, IntoValue, Number, Numeric, Object, OrdinaryObject,
Primitive, PropertyKey, String, Symbol, Value,
bigint, BigInt, Function, GlobalValue, HeapNumber, HeapString, InternalMethods, InternalSlots,
IntoFunction, IntoNumeric, IntoObject, IntoPrimitive, IntoValue, Number, Numeric, Object,
OrdinaryObject, Primitive, PropertyKey, String, Symbol, Value,
};
pub use spec::PropertyDescriptor;
pub(crate) use spec::*;
2 changes: 2 additions & 0 deletions nova_vm/src/ecmascript/types/language.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

pub mod bigint;
mod function;
mod global_value;
mod into_numeric;
mod into_primitive;
mod into_value;
Expand All @@ -20,6 +21,7 @@ pub(crate) use function::{
BoundFunctionHeapData, BuiltinFunctionHeapData, ECMAScriptFunctionHeapData,
};
pub use function::{Function, IntoFunction};
pub use global_value::GlobalValue;
pub use into_numeric::IntoNumeric;
pub use into_primitive::IntoPrimitive;
pub use into_value::IntoValue;
Expand Down
55 changes: 55 additions & 0 deletions nova_vm/src/ecmascript/types/language/global_value.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use crate::ecmascript::execution::Agent;

use super::Value;

/// Holds the position of a global value in the heap.
/// The main advantage of this is that the garbage collector will not move the elements
/// of the global values vector, making it possible to maintain a position of any value across
/// multiple calls of the garbage collector.
///
/// This might be useful to resolve promises asynchronously for example.
#[derive(Debug, PartialEq)]
pub struct GlobalValue(usize);

impl GlobalValue {
/// Register a value as global.
pub fn new(agent: &mut Agent, value: impl Into<Value>) -> Self {
let available_index = Self::find_available_index(agent);
agent
.heap
.globals
.insert(available_index, Some(value.into()));
Self(available_index)
}

/// Unregister this global value.
#[must_use]
pub fn take(&self, agent: &mut Agent) -> Value {
// Leave a `None` in the index and return the value
agent.heap.globals.get_mut(self.0).unwrap().take().unwrap()
}

/// Find an available index in the global values vector.
fn find_available_index(agent: &mut Agent) -> usize {
let mut available_index = 0;

loop {
// Index has been freed previously
if let Some(None) = agent.heap.globals.get(available_index) {
break;
}

// Global values vector is full, the capacity must increase
if available_index == agent.heap.globals.len() {
agent.heap.globals.push(None);
available_index = agent.heap.globals.len() - 1;
break;
}

// Advance the index
available_index += 1;
}

available_index
}
}
9 changes: 8 additions & 1 deletion nova_vm/src/engine/bytecode/executable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2705,6 +2705,13 @@ impl CompileEvaluation for ast::ContinueStatement<'_> {
}
}

impl CompileEvaluation for ast::ImportDeclaration<'_> {
fn compile(&self, ctx: &mut CompileContext) {
// TODO: Inject the imported values into the context
ctx.agent.host_hooks.import_module(self, ctx.agent)
}
}

impl CompileEvaluation for ast::Statement<'_> {
fn compile(&self, ctx: &mut CompileContext) {
match self {
Expand All @@ -2730,7 +2737,7 @@ impl CompileEvaluation for ast::Statement<'_> {
Statement::WithStatement(_) => todo!(),
Statement::ClassDeclaration(_) => todo!(),
Statement::UsingDeclaration(_) => todo!(),
Statement::ImportDeclaration(_) => todo!(),
Statement::ImportDeclaration(x) => x.compile(ctx),
Statement::ExportAllDeclaration(_) => todo!(),
Statement::ExportDefaultDeclaration(_) => todo!(),
Statement::ExportNamedDeclaration(_) => todo!(),
Expand Down
2 changes: 1 addition & 1 deletion nova_vm/src/heap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ pub struct Heap {
pub environments: Environments,
pub errors: Vec<Option<ErrorHeapData>>,
pub finalization_registrys: Vec<Option<FinalizationRegistryHeapData>>,
pub globals: Vec<Value>,
pub globals: Vec<Option<Value>>,
pub maps: Vec<Option<MapHeapData>>,
pub modules: Vec<Option<ModuleHeapData>>,
pub numbers: Vec<Option<NumberHeapData>>,
Expand Down
14 changes: 11 additions & 3 deletions nova_vm/src/heap/heap_gc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,17 @@ pub fn heap_gc(heap: &mut Heap) {
let mut bits = HeapBits::new(heap);
let mut queues = WorkQueues::new(heap);

heap.globals.iter().for_each(|&value| {
queues.push_value(value);
let mut last_filled_global_value = None;
heap.globals.iter().enumerate().for_each(|(i, &value)| {
if let Some(value) = value {
queues.push_value(value);
last_filled_global_value = Some(i);
}
});
// Remove as many `None` global values without moving any `Some(Value)` values.
if let Some(last_filled_global_value) = last_filled_global_value {
heap.globals.drain(last_filled_global_value + 1..);
}

while !queues.is_empty() {
let Heap {
Expand Down Expand Up @@ -960,7 +968,7 @@ fn test_heap_gc() {
assert!(heap.objects.is_empty());
let obj = Value::Object(heap.create_null_object(&[]));
println!("Object: {:#?}", obj);
heap.globals.push(obj);
heap.globals.push(Some(obj));
heap_gc(&mut heap);
println!("Objects: {:#?}", heap.objects);
assert_eq!(heap.objects.len(), 1);
Expand Down