use std::{cell::RefCell, mem::MaybeUninit};
use boa_string::JsString;
use dynify::Dynify;
use super::VaryingOperand;
use crate::{
Context, JsError, JsObject, JsResult, JsValue, NativeFunction,
builtins::{Promise, promise::PromiseCapability},
error::JsNativeError,
job::NativeAsyncJob,
module::{ModuleKind, Referrer},
object::FunctionObjectBuilder,
vm::opcode::Operation,
};
#[derive(Debug, Clone, Copy)]
pub(crate) struct CallEval;
impl CallEval {
#[inline(always)]
pub(super) fn operation(
(argument_count, scope_index): (VaryingOperand, VaryingOperand),
context: &mut Context,
) -> JsResult<()> {
let func = context
.vm
.stack
.calling_convention_get_function(argument_count.into());
let Some(object) = func.as_object() else {
return Err(JsNativeError::typ()
.with_message("not a callable function")
.into());
};
let eval = context.intrinsics().objects().eval();
if JsObject::equals(&object, &eval) {
let arguments = context
.vm
.stack
.calling_convention_pop_arguments(argument_count.into());
let _func = context.vm.stack.pop();
let _this = context.vm.stack.pop();
if let Some(x) = arguments.first() {
let strict = context.vm.frame().code_block.strict();
let scope = context
.vm
.frame()
.code_block()
.constant_scope(scope_index.into());
let result = crate::builtins::eval::Eval::perform_eval(
x,
true,
Some(scope),
strict,
context,
)?;
context.vm.stack.push(result);
} else {
context.vm.stack.push(JsValue::undefined());
}
return Ok(());
}
object.__call__(argument_count.into()).resolve(context)?;
Ok(())
}
}
impl Operation for CallEval {
const NAME: &'static str = "CallEval";
const INSTRUCTION: &'static str = "INST - CallEval";
const COST: u8 = 5;
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct CallEvalSpread;
impl CallEvalSpread {
#[inline(always)]
pub(super) fn operation(index: VaryingOperand, context: &mut Context) -> JsResult<()> {
let arguments_array = context.vm.stack.pop();
let arguments_array_object = arguments_array
.as_object()
.expect("arguments array in call spread function must be an object");
let arguments = arguments_array_object
.borrow()
.properties()
.to_dense_indexed_properties()
.expect("arguments array in call spread function must be dense");
let func = context.vm.stack.calling_convention_get_function(0);
let Some(object) = func.as_object() else {
return Err(JsNativeError::typ()
.with_message("not a callable function")
.into());
};
let eval = context.intrinsics().objects().eval();
if JsObject::equals(&object, &eval) {
let _func = context.vm.stack.pop();
let _this = context.vm.stack.pop();
if let Some(x) = arguments.first() {
let strict = context.vm.frame().code_block.strict();
let scope = context.vm.frame().code_block().constant_scope(index.into());
let result = crate::builtins::eval::Eval::perform_eval(
x,
true,
Some(scope),
strict,
context,
)?;
context.vm.stack.push(result);
} else {
context.vm.stack.push(JsValue::undefined());
}
return Ok(());
}
let argument_count = arguments.len();
context
.vm
.stack
.calling_convention_push_arguments(&arguments);
object.__call__(argument_count).resolve(context)?;
Ok(())
}
}
impl Operation for CallEvalSpread {
const NAME: &'static str = "CallEvalSpread";
const INSTRUCTION: &'static str = "INST - CallEvalSpread";
const COST: u8 = 5;
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct Call;
impl Call {
#[inline(always)]
pub(super) fn operation(argument_count: VaryingOperand, context: &mut Context) -> JsResult<()> {
let func = context
.vm
.stack
.calling_convention_get_function(argument_count.into());
let Some(object) = func.as_object() else {
return Err(Self::handle_not_callable());
};
object.__call__(argument_count.into()).resolve(context)?;
Ok(())
}
#[cold]
#[inline(never)]
fn handle_not_callable() -> JsError {
JsNativeError::typ()
.with_message("not a callable function")
.into()
}
}
impl Operation for Call {
const NAME: &'static str = "Call";
const INSTRUCTION: &'static str = "INST - Call";
const COST: u8 = 3;
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct CallSpread;
impl CallSpread {
#[inline(always)]
pub(super) fn operation((): (), context: &mut Context) -> JsResult<()> {
let arguments_array = context.vm.stack.pop();
let arguments_array_object = arguments_array
.as_object()
.expect("arguments array in call spread function must be an object");
let arguments = arguments_array_object
.borrow()
.properties()
.to_dense_indexed_properties()
.expect("arguments array in call spread function must be dense");
let argument_count = arguments.len();
context
.vm
.stack
.calling_convention_push_arguments(&arguments);
let func = context
.vm
.stack
.calling_convention_get_function(argument_count);
let Some(object) = func.as_object() else {
return Err(JsNativeError::typ()
.with_message("not a callable function")
.into());
};
object.__call__(argument_count).resolve(context)?;
Ok(())
}
}
impl Operation for CallSpread {
const NAME: &'static str = "CallSpread";
const INSTRUCTION: &'static str = "INST - CallSpread";
const COST: u8 = 3;
}
async fn load_dyn_import(
referrer: Referrer,
specifier: JsString,
cap: PromiseCapability,
context: &RefCell<&mut Context>,
) {
let loader = context.borrow().module_loader();
let fut = loader.load_imported_module(referrer.clone(), specifier.clone(), context);
let mut stack = [MaybeUninit::<u8>::uninit(); 16];
let mut heap = Vec::<MaybeUninit<u8>>::new();
let completion = fut.init2(&mut stack, &mut heap).await;
let module = match completion {
Err(err) => {
let err = err.to_opaque(&mut context.borrow_mut());
cap.reject()
.call(&JsValue::undefined(), &[err], &mut context.borrow_mut())
.expect("default `reject` function cannot throw");
return;
}
Ok(m) => m,
};
match referrer {
Referrer::Module(module) => {
let ModuleKind::SourceText(src) = module.kind() else {
panic!("referrer cannot be a synthetic module");
};
let mut loaded_modules = src.loaded_modules().borrow_mut();
let entry = loaded_modules
.entry(specifier)
.or_insert_with(|| module.clone());
debug_assert_eq!(&module, entry);
}
Referrer::Realm(realm) => {
let mut loaded_modules = realm.loaded_modules().borrow_mut();
let entry = loaded_modules
.entry(specifier)
.or_insert_with(|| module.clone());
debug_assert_eq!(&module, entry);
}
Referrer::Script(script) => {
let mut loaded_modules = script.loaded_modules().borrow_mut();
let entry = loaded_modules
.entry(specifier)
.or_insert_with(|| module.clone());
debug_assert_eq!(&module, entry);
}
}
let load = module.load(&mut context.borrow_mut());
let on_rejected = FunctionObjectBuilder::new(
context.borrow().realm(),
NativeFunction::from_copy_closure_with_captures(
|_, args, cap, context| {
cap.reject()
.call(&JsValue::undefined(), args, context)
.expect("default `reject` function cannot throw");
Ok(JsValue::undefined())
},
cap.clone(),
),
)
.build();
let link_evaluate = FunctionObjectBuilder::new(
context.borrow().realm(),
NativeFunction::from_copy_closure_with_captures(
|_, _, (module, cap, on_rejected), context| {
if let Err(e) = module.link(context) {
let e = e.to_opaque(context);
cap.reject()
.call(&JsValue::undefined(), &[e], context)
.expect("default `reject` function cannot throw");
return Ok(JsValue::undefined());
}
let evaluate = module.evaluate(context);
let fulfill = FunctionObjectBuilder::new(
context.realm(),
NativeFunction::from_copy_closure_with_captures(
|_, _, (module, cap), context| {
let namespace = module.namespace(context);
cap.resolve()
.call(&JsValue::undefined(), &[namespace.into()], context)
.expect("default `resolve` function cannot throw");
Ok(JsValue::undefined())
},
(module.clone(), cap.clone()),
),
)
.build();
Promise::perform_promise_then(
&evaluate,
Some(fulfill),
Some(on_rejected.clone()),
None,
context,
);
Ok(JsValue::undefined())
},
(module.clone(), cap.clone(), on_rejected.clone()),
),
)
.build();
Promise::perform_promise_then(
&load,
Some(link_evaluate),
Some(on_rejected),
None,
&mut context.borrow_mut(),
);
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct ImportCall;
impl ImportCall {
#[inline(always)]
pub(super) fn operation(value: VaryingOperand, context: &mut Context) -> JsResult<()> {
let referrer = context
.get_active_script_or_module()
.map_or_else(|| Referrer::Realm(context.realm().clone()), Into::into);
let arg = context.vm.get_register(value.into()).clone();
let cap = PromiseCapability::new(
&context.intrinsics().constructors().promise().constructor(),
context,
)
.expect("operation cannot fail for the %Promise% intrinsic");
let promise = cap.promise().clone();
match arg.to_string(context) {
Err(err) => {
let err = err.to_opaque(context);
cap.reject().call(&JsValue::undefined(), &[err], context)?;
}
Ok(specifier) => {
let job = NativeAsyncJob::with_realm(
async move |context| {
load_dyn_import(referrer, specifier, cap, context).await;
Ok(JsValue::undefined())
},
context.realm().clone(),
);
context.enqueue_job(job.into());
}
}
context.vm.set_register(value.into(), promise.into());
Ok(())
}
}
impl Operation for ImportCall {
const NAME: &'static str = "ImportCall";
const INSTRUCTION: &'static str = "INST - ImportCall";
const COST: u8 = 15;
}