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

Implement dynamic imports #2932

Merged
merged 12 commits into from
May 22, 2023
65 changes: 65 additions & 0 deletions boa_ast/src/expression/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,68 @@ impl VisitWith for SuperCall {
ControlFlow::Continue(())
}
}

/// The import() syntax, commonly called dynamic import, is a function-like expression that allows
/// loading an ECMAScript module asynchronously and dynamically into a potentially non-module
/// environment.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-ImportCall
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct ImportCall {
arg: Box<Expression>,
}

impl ImportCall {
/// Creates a new `ImportCall` AST node.
pub fn new<A>(arg: A) -> Self
where
A: Into<Expression>,
{
Self {
arg: Box::new(arg.into()),
}
}

/// Retrieves the single argument of the import call.
#[must_use]
pub const fn argument(&self) -> &Expression {
&self.arg
}
}

impl ToInternedString for ImportCall {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
format!("import({})", self.arg.to_interned_string(interner))
}
}

impl From<ImportCall> for Expression {
#[inline]
fn from(call: ImportCall) -> Self {
Self::ImportCall(call)
}
}

impl VisitWith for ImportCall {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_expression(&self.arg)
}

fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_expression_mut(&mut self.arg)
}
}
9 changes: 7 additions & 2 deletions boa_ast/src/expression/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ mod tagged_template;
mod r#yield;

use crate::visitor::{VisitWith, Visitor, VisitorMut};
pub use call::{Call, SuperCall};
pub use call::{Call, ImportCall, SuperCall};
pub use identifier::{Identifier, RESERVED_IDENTIFIERS_STRICT};
pub use new::New;
pub use optional::{Optional, OptionalOperation, OptionalOperationKind};
Expand Down Expand Up @@ -121,10 +121,12 @@ pub enum Expression {
/// See [`SuperCall`].
SuperCall(SuperCall),

/// See [`ImportCall`].
ImportCall(ImportCall),

/// See [`Optional`].
Optional(Optional),

// TODO: Import calls
/// See [`TaggedTemplate`].
TaggedTemplate(TaggedTemplate),

Expand Down Expand Up @@ -192,6 +194,7 @@ impl Expression {
Self::New(new) => new.to_interned_string(interner),
Self::Call(call) => call.to_interned_string(interner),
Self::SuperCall(supc) => supc.to_interned_string(interner),
Self::ImportCall(impc) => impc.to_interned_string(interner),
Self::Optional(opt) => opt.to_interned_string(interner),
Self::NewTarget => "new.target".to_owned(),
Self::TaggedTemplate(tag) => tag.to_interned_string(interner),
Expand Down Expand Up @@ -300,6 +303,7 @@ impl VisitWith for Expression {
Self::New(n) => visitor.visit_new(n),
Self::Call(c) => visitor.visit_call(c),
Self::SuperCall(sc) => visitor.visit_super_call(sc),
Self::ImportCall(ic) => visitor.visit_import_call(ic),
Self::Optional(opt) => visitor.visit_optional(opt),
Self::TaggedTemplate(tt) => visitor.visit_tagged_template(tt),
Self::Assign(a) => visitor.visit_assign(a),
Expand Down Expand Up @@ -341,6 +345,7 @@ impl VisitWith for Expression {
Self::New(n) => visitor.visit_new_mut(n),
Self::Call(c) => visitor.visit_call_mut(c),
Self::SuperCall(sc) => visitor.visit_super_call_mut(sc),
Self::ImportCall(ic) => visitor.visit_import_call_mut(ic),
Self::Optional(opt) => visitor.visit_optional_mut(opt),
Self::TaggedTemplate(tt) => visitor.visit_tagged_template_mut(tt),
Self::Assign(a) => visitor.visit_assign_mut(a),
Expand Down
7 changes: 6 additions & 1 deletion boa_ast/src/visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use crate::{
assign::{Assign, AssignTarget},
Binary, BinaryInPrivate, Conditional, Unary, Update,
},
Await, Call, Expression, Identifier, New, Optional, OptionalOperation,
Await, Call, Expression, Identifier, ImportCall, New, Optional, OptionalOperation,
OptionalOperationKind, Parenthesized, Spread, SuperCall, TaggedTemplate, Yield,
},
function::{
Expand Down Expand Up @@ -164,6 +164,7 @@ node_ref! {
New,
Call,
SuperCall,
ImportCall,
Optional,
TaggedTemplate,
Assign,
Expand Down Expand Up @@ -263,6 +264,7 @@ pub trait Visitor<'ast>: Sized {
define_visit!(visit_new, New);
define_visit!(visit_call, Call);
define_visit!(visit_super_call, SuperCall);
define_visit!(visit_import_call, ImportCall);
define_visit!(visit_optional, Optional);
define_visit!(visit_tagged_template, TaggedTemplate);
define_visit!(visit_assign, Assign);
Expand Down Expand Up @@ -359,6 +361,7 @@ pub trait Visitor<'ast>: Sized {
NodeRef::New(n) => self.visit_new(n),
NodeRef::Call(n) => self.visit_call(n),
NodeRef::SuperCall(n) => self.visit_super_call(n),
NodeRef::ImportCall(n) => self.visit_import_call(n),
NodeRef::Optional(n) => self.visit_optional(n),
NodeRef::TaggedTemplate(n) => self.visit_tagged_template(n),
NodeRef::Assign(n) => self.visit_assign(n),
Expand Down Expand Up @@ -460,6 +463,7 @@ pub trait VisitorMut<'ast>: Sized {
define_visit_mut!(visit_new_mut, New);
define_visit_mut!(visit_call_mut, Call);
define_visit_mut!(visit_super_call_mut, SuperCall);
define_visit_mut!(visit_import_call_mut, ImportCall);
define_visit_mut!(visit_optional_mut, Optional);
define_visit_mut!(visit_tagged_template_mut, TaggedTemplate);
define_visit_mut!(visit_assign_mut, Assign);
Expand Down Expand Up @@ -556,6 +560,7 @@ pub trait VisitorMut<'ast>: Sized {
NodeRefMut::New(n) => self.visit_new_mut(n),
NodeRefMut::Call(n) => self.visit_call_mut(n),
NodeRefMut::SuperCall(n) => self.visit_super_call_mut(n),
NodeRefMut::ImportCall(n) => self.visit_import_call_mut(n),
NodeRefMut::Optional(n) => self.visit_optional_mut(n),
NodeRefMut::TaggedTemplate(n) => self.visit_tagged_template_mut(n),
NodeRefMut::Assign(n) => self.visit_assign_mut(n),
Expand Down
9 changes: 5 additions & 4 deletions boa_cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ use boa_engine::{
module::{Module, ModuleLoader, SimpleModuleLoader},
optimizer::OptimizerOptions,
property::Attribute,
script::Script,
vm::flowgraph::{Direction, Graph},
Context, JsNativeError, JsResult, Source,
};
Expand Down Expand Up @@ -263,8 +264,8 @@ fn generate_flowgraph(
format: FlowgraphFormat,
direction: Option<FlowgraphDirection>,
) -> JsResult<String> {
let ast = context.parse_script(Source::from_bytes(src))?;
let code = context.compile_script(&ast)?;
let script = Script::parse(Source::from_bytes(src), None, context)?;
let code = script.codeblock(context)?;

let direction = match direction {
Some(FlowgraphDirection::TopToBottom) | None => Direction::TopToBottom,
Expand Down Expand Up @@ -331,7 +332,7 @@ fn evaluate_files(
Err(err) => eprintln!("Uncaught {err}"),
}
} else {
match context.eval_script(Source::from_bytes(&buffer)) {
match context.eval(Source::from_bytes(&buffer)) {
Ok(v) => println!("{}", v.display()),
Err(v) => eprintln!("Uncaught {v}"),
}
Expand Down Expand Up @@ -425,7 +426,7 @@ fn main() -> Result<(), io::Error> {
Err(v) => eprintln!("Uncaught {v}"),
}
} else {
match context.eval_script(Source::from_bytes(line.trim_end())) {
match context.eval(Source::from_bytes(line.trim_end())) {
Ok(v) => {
println!("{}", v.display());
}
Expand Down
32 changes: 23 additions & 9 deletions boa_engine/benches/full.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use boa_engine::{
context::DefaultHooks, object::shape::RootShape, optimizer::OptimizerOptions, realm::Realm,
Context, Source,
script::Script, Context, Source,
};
use criterion::{criterion_group, criterion_main, Criterion};
use std::hint::black_box;
Expand Down Expand Up @@ -33,7 +33,13 @@ macro_rules! full_benchmarks {
context.set_optimizer_options(OptimizerOptions::empty());

c.bench_function(concat!($id, " (Parser)"), move |b| {
b.iter(|| context.parse_script(black_box(Source::from_bytes(CODE))))
b.iter(|| {
Script::parse(
black_box(Source::from_bytes(CODE)),
None,
&mut context,
).unwrap()
})
});
}
)*
Expand All @@ -42,15 +48,19 @@ macro_rules! full_benchmarks {
$(
{
static CODE: &str = include_str!(concat!("bench_scripts/", stringify!($name), ".js"));
let mut context = Context::default();
let context = &mut Context::default();

// Disable optimizations
context.set_optimizer_options(OptimizerOptions::empty());

let statement_list = context.parse_script(Source::from_bytes(CODE)).expect("parsing failed");
let script = Script::parse(
black_box(Source::from_bytes(CODE)),
None,
context,
).unwrap();
c.bench_function(concat!($id, " (Compiler)"), move |b| {
b.iter(|| {
context.compile_script(black_box(&statement_list))
script.codeblock(context).unwrap()
})
});
}
Expand All @@ -60,16 +70,20 @@ macro_rules! full_benchmarks {
$(
{
static CODE: &str = include_str!(concat!("bench_scripts/", stringify!($name), ".js"));
let mut context = Context::default();
let context = &mut Context::default();

// Disable optimizations
context.set_optimizer_options(OptimizerOptions::empty());

let statement_list = context.parse_script(Source::from_bytes(CODE)).expect("parsing failed");
let code_block = context.compile_script(&statement_list).unwrap();
let script = Script::parse(
black_box(Source::from_bytes(CODE)),
None,
context,
).unwrap();
script.codeblock(context).unwrap();
c.bench_function(concat!($id, " (Execution)"), move |b| {
b.iter(|| {
context.execute(black_box(code_block.clone())).unwrap()
script.evaluate(context).unwrap();
})
});
}
Expand Down
17 changes: 14 additions & 3 deletions boa_engine/src/builtins/eval/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,14 @@
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval

use crate::{
builtins::BuiltInObject, bytecompiler::ByteCompiler, context::intrinsics::Intrinsics,
environments::Environment, error::JsNativeError, object::JsObject, realm::Realm, vm::Opcode,
builtins::BuiltInObject,
bytecompiler::ByteCompiler,
context::intrinsics::Intrinsics,
environments::Environment,
error::JsNativeError,
object::JsObject,
realm::Realm,
vm::{CallFrame, Opcode},
Context, JsArgs, JsResult, JsString, JsValue,
};
use boa_ast::operations::{contains, contains_arguments, ContainsSymbol};
Expand Down Expand Up @@ -246,6 +252,11 @@ impl Eval {
context.vm.environments.extend_outer_function_environment();
}

context.execute(code_block)
context.vm.push_frame(CallFrame::new(code_block));
context.realm().resize_global_env();
let record = context.run();
context.vm.pop_frame();

record.consume()
}
}
33 changes: 28 additions & 5 deletions boa_engine/src/builtins/function/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use crate::{
string::utf16,
symbol::JsSymbol,
value::IntegerOrInfinity,
vm::CodeBlock,
vm::{ActiveRunnable, CodeBlock},
Context, JsArgs, JsResult, JsString, JsValue,
};
use boa_ast::{
Expand Down Expand Up @@ -177,6 +177,9 @@ pub(crate) enum FunctionKind {

/// The class object that this function is associated with.
class_object: Option<JsObject>,

/// The `[[ScriptOrModule]]` internal slot.
script_or_module: Option<ActiveRunnable>,
},

/// A bytecode async function.
Expand All @@ -192,6 +195,9 @@ pub(crate) enum FunctionKind {

/// The class object that this function is associated with.
class_object: Option<JsObject>,

/// The `[[ScriptOrModule]]` internal slot.
script_or_module: Option<ActiveRunnable>,
},

/// A bytecode generator function.
Expand All @@ -207,6 +213,9 @@ pub(crate) enum FunctionKind {

/// The class object that this function is associated with.
class_object: Option<JsObject>,

/// The `[[ScriptOrModule]]` internal slot.
script_or_module: Option<ActiveRunnable>,
},

/// A bytecode async generator function.
Expand All @@ -222,6 +231,9 @@ pub(crate) enum FunctionKind {

/// The class object that this function is associated with.
class_object: Option<JsObject>,

/// The `[[ScriptOrModule]]` internal slot.
script_or_module: Option<ActiveRunnable>,
},
}

Expand Down Expand Up @@ -256,7 +268,16 @@ unsafe impl Trace for FunctionKind {
custom_trace! {this, {
match this {
Self::Native { function, .. } => {mark(function)}
Self::Ordinary { code, environments, home_object, fields, private_methods, class_object, .. } => {
Self::Ordinary {
code,
environments,
home_object,
fields,
private_methods,
class_object,
script_or_module,
..
} => {
mark(code);
mark(environments);
mark(home_object);
Expand All @@ -267,14 +288,16 @@ unsafe impl Trace for FunctionKind {
mark(elem);
}
mark(class_object);
mark(script_or_module);
}
Self::Async { code, environments, home_object, class_object }
| Self::Generator { code, environments, home_object, class_object}
| Self::AsyncGenerator { code, environments, home_object, class_object} => {
Self::Async { code, environments, home_object, class_object, script_or_module }
| Self::Generator { code, environments, home_object, class_object, script_or_module}
| Self::AsyncGenerator { code, environments, home_object, class_object, script_or_module} => {
mark(code);
mark(environments);
mark(home_object);
mark(class_object);
mark(script_or_module);
}
}
}}
Expand Down
Loading