use crate::engine::api::error::Error;
use crate::engine::api::error::ErrorKind;
use crate::engine::code::function::metadata::ClassInitializerKind;
use crate::engine::code::function::metadata::ClosureSource;
use crate::engine::code::function::metadata::ClosureVariable;
use crate::engine::code::function::metadata::ClosureVariableName;
use crate::engine::code::function::metadata::EvalCallerProfile;
use crate::engine::code::function::metadata::EvalCallerVariableTarget;
use crate::engine::code::function::metadata::EvalEnvironment;
use crate::engine::code::function::metadata::EvalKind;
use crate::engine::code::function::metadata::EvalRootBinding;
use crate::engine::code::function::metadata::FunctionKind as BytecodeFunctionKind;
use crate::engine::code::function::metadata::ParameterDefaultSource;
use crate::engine::compiler::EVAL_RET_LOCAL_NAME;
use crate::engine::compiler::MAX_LOCAL_VARIABLES;
use crate::engine::compiler::lexer::Span;
use crate::engine::compiler::model::bindings::BindingId;
use crate::engine::compiler::model::bindings::BindingKind;
use crate::engine::compiler::model::bindings::BindingStorage;
use crate::engine::compiler::model::bindings::IrBinding;
use crate::engine::compiler::model::bindings::IrEvalDeclaration;
use crate::engine::compiler::model::bindings::IrGlobalDeclaration;
use crate::engine::compiler::model::bindings::IrHoistedFunction;
use crate::engine::compiler::model::bindings::IrScopedFunction;
use crate::engine::compiler::model::bindings::ResolvedBinding;
use crate::engine::compiler::model::bindings::SyntheticLocal;
use crate::engine::compiler::model::bindings::SyntheticLocalKind;
use crate::engine::compiler::model::ir::FunctionId;
use crate::engine::compiler::model::ir::IrConstant;
use crate::engine::compiler::model::ir::IrOp;
use crate::engine::compiler::model::ir::SpannedIrOp;
use crate::engine::compiler::model::scope::IrScope;
use crate::engine::compiler::model::scope::ScopeId;
use crate::engine::compiler::model::scope::ScopeKind;
use crate::engine::compiler::module;
use crate::engine::compiler::names::NameId;
use crate::engine::compiler::names::NameTable;
use crate::engine::compiler::pseudo_binding::ACTIVE_FUNCTION_LOCAL_NAME;
use crate::engine::compiler::pseudo_binding::THIS_LOCAL_NAME;
use crate::engine::value::JsString;
use crate::engine::value::PrimitiveValue as Value;
use crate::source::SourceOffset;
use crate::source::text::SourceText;
use std::collections::HashMap;
use std::ops::Range;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(in crate::engine::compiler) struct ParentLink {
pub(in crate::engine::compiler) function: FunctionId,
pub(in crate::engine::compiler) definition_scope: ScopeId,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(in crate::engine::compiler) enum FunctionKind {
Script,
Module,
Eval(EvalKind),
Ordinary,
Method,
Arrow,
}
#[derive(Debug)]
pub(in crate::engine::compiler) struct IrParameterPatternBinding {
pub(in crate::engine::compiler) name: NameId,
pub(in crate::engine::compiler) parameter_local: u16,
pub(in crate::engine::compiler) body_local: Option<u16>,
pub(in crate::engine::compiler) declaration_span: Span,
}
#[derive(Debug)]
pub(in crate::engine::compiler) struct FunctionIr {
pub(in crate::engine::compiler) body_parsed: bool,
pub(in crate::engine::compiler) parent: Option<ParentLink>,
pub(in crate::engine::compiler) kind: FunctionKind,
pub(in crate::engine::compiler) execution_kind: BytecodeFunctionKind,
pub(in crate::engine::compiler) class_constructor: bool,
pub(in crate::engine::compiler) derived_class_constructor: bool,
pub(in crate::engine::compiler) class_initializer_kind: Option<ClassInitializerKind>,
pub(in crate::engine::compiler) class_private_brand: bool,
pub(in crate::engine::compiler) super_call_allowed: bool,
pub(in crate::engine::compiler) super_allowed: bool,
pub(in crate::engine::compiler) arguments_forbidden: bool,
pub(in crate::engine::compiler) source: FunctionSourceInfo,
pub(in crate::engine::compiler) function_name: Option<NameId>,
pub(in crate::engine::compiler) private_name_binding: bool,
pub(in crate::engine::compiler) function_name_local: Option<u16>,
pub(in crate::engine::compiler) arguments_local: Option<u16>,
pub(in crate::engine::compiler) home_object_local: Option<u16>,
pub(in crate::engine::compiler) active_function_local: Option<u16>,
pub(in crate::engine::compiler) this_local: Option<u16>,
pub(in crate::engine::compiler) new_target_local: Option<u16>,
pub(in crate::engine::compiler) eval_variable_object_local: Option<u16>,
pub(in crate::engine::compiler) arg_eval_variable_object_local: Option<u16>,
pub(in crate::engine::compiler) synthetic_parameter_arguments_local: Option<u16>,
pub(in crate::engine::compiler) needs_home_object: bool,
pub(in crate::engine::compiler) parameters: Vec<Option<NameId>>,
pub(in crate::engine::compiler) parameter_names: Vec<NameId>,
pub(in crate::engine::compiler) defined_argument_count: usize,
pub(in crate::engine::compiler) has_simple_parameter_list: bool,
pub(in crate::engine::compiler) rest_parameter: Option<u16>,
pub(in crate::engine::compiler) rest_pattern_start: Option<u16>,
pub(in crate::engine::compiler) parameter_scope: Option<ScopeId>,
pub(in crate::engine::compiler) parameter_locals: Vec<u16>,
pub(in crate::engine::compiler) parameter_local_reservation_count: Option<usize>,
pub(in crate::engine::compiler) parameter_argument_locals: Vec<Option<u16>>,
pub(in crate::engine::compiler) parameter_pattern_bindings: Vec<IrParameterPatternBinding>,
pub(in crate::engine::compiler) parameter_default_sources: Vec<ParameterDefaultSource>,
pub(in crate::engine::compiler) pattern_parameter_initialization: bool,
pub(in crate::engine::compiler) locals: Vec<NameId>,
pub(in crate::engine::compiler) scopes: Vec<IrScope>,
pub(in crate::engine::compiler) bindings: Vec<IrBinding>,
pub(in crate::engine::compiler) global_declarations: Vec<IrGlobalDeclaration>,
global_declaration_index: HashMap<NameId, FirstGlobalDeclarations>,
pub(in crate::engine::compiler) hoisted_functions: Vec<IrHoistedFunction>,
pub(in crate::engine::compiler) eval_declarations: Vec<IrEvalDeclaration>,
pub(in crate::engine::compiler) eval_declarations_installed: bool,
pub(in crate::engine::compiler) eval_redeclaration: Option<NameId>,
pub(in crate::engine::compiler) function_hoists_installed: bool,
pub(in crate::engine::compiler) pseudo_binding_prologues_installed: bool,
pub(in crate::engine::compiler) scoped_functions: Vec<IrScopedFunction>,
pub(in crate::engine::compiler) var_scope: ScopeId,
pub(in crate::engine::compiler) body_scope: ScopeId,
pub(in crate::engine::compiler) eval_ret_local: Option<u16>,
pub(in crate::engine::compiler) synthetic_locals: Vec<SyntheticLocal>,
pub(in crate::engine::compiler) ops: Vec<SpannedIrOp>,
pub(in crate::engine::compiler) operands: super::operands::IrOperands,
pub(in crate::engine::compiler) constants: Vec<IrConstant>,
pub(in crate::engine::compiler) string_constants: HashMap<JsString, u32>,
pub(in crate::engine::compiler) closure_variables: Vec<ClosureVariable>,
#[expect(clippy::box_collection)]
pub(in crate::engine::compiler) closure_index: Option<Box<HashMap<ClosureKey, u16>>>,
pub(in crate::engine::compiler) external_bindings: Vec<EvalRootBinding<JsString>>,
pub(in crate::engine::compiler) eval_caller_profile: EvalCallerProfile,
pub(in crate::engine::compiler) eval_environments: Vec<EvalEnvironment<JsString>>,
pub(in crate::engine::compiler) strict: bool,
}
#[derive(Clone, Debug)]
pub(in crate::engine::compiler) struct FunctionSourceInfo {
pub(in crate::engine::compiler) span: Span,
pub(in crate::engine::compiler) definition: SourceOffset,
pub(in crate::engine::compiler) range: Option<Range<SourceOffset>>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub(in crate::engine::compiler) struct SuperCapabilities {
pub(in crate::engine::compiler) super_call_allowed: bool,
pub(in crate::engine::compiler) super_allowed: bool,
}
impl SuperCapabilities {
pub(in crate::engine::compiler) const NONE: Self = Self {
super_call_allowed: false,
super_allowed: false,
};
pub(in crate::engine::compiler) const PROPERTY: Self = Self {
super_call_allowed: false,
super_allowed: true,
};
pub(in crate::engine::compiler) const CALL_AND_PROPERTY: Self = Self {
super_call_allowed: true,
super_allowed: true,
};
pub(in crate::engine::compiler) fn validated(self) -> Result<Self, Error> {
if self.super_call_allowed && !self.super_allowed {
return Err(Error::internal(
"function permits super() without SuperProperty",
));
}
Ok(self)
}
}
#[derive(Clone, Debug)]
pub(in crate::engine::compiler) struct FunctionIrOptions {
pub(in crate::engine::compiler) function_name: Option<NameId>,
pub(in crate::engine::compiler) private_name_binding: bool,
pub(in crate::engine::compiler) class_constructor: bool,
pub(in crate::engine::compiler) derived_class_constructor: bool,
pub(in crate::engine::compiler) parameters: Vec<Option<NameId>>,
pub(in crate::engine::compiler) defined_argument_count: usize,
pub(in crate::engine::compiler) has_simple_parameter_list: bool,
pub(in crate::engine::compiler) rest_parameter: Option<u16>,
pub(in crate::engine::compiler) strict: bool,
pub(in crate::engine::compiler) super_capabilities: SuperCapabilities,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(in crate::engine::compiler) enum ClosureKey {
Source(ClosureSource),
Named(ClosureSource, ClosureVariableName),
}
impl ClosureKey {
pub(in crate::engine::compiler) fn of(variable: &ClosureVariable) -> Self {
match variable.source {
source @ (ClosureSource::Global | ClosureSource::GlobalDeclaration) => {
Self::Named(source, variable.name)
}
source => Self::Source(source),
}
}
}
#[derive(Clone, Copy, Debug)]
struct FirstGlobalDeclarations {
any: u32,
normal: u32,
lexical: u32,
}
impl FirstGlobalDeclarations {
const NONE: u32 = u32::MAX;
}
impl FunctionIr {
pub(in crate::engine::compiler) fn new(
parent: Option<ParentLink>,
kind: FunctionKind,
source: FunctionSourceInfo,
options: FunctionIrOptions,
names: &mut NameTable,
) -> Result<Self, Error> {
let super_capabilities = options.super_capabilities.validated()?;
if options.derived_class_constructor
&& (!options.class_constructor
|| kind != FunctionKind::Method
|| super_capabilities != SuperCapabilities::CALL_AND_PROPERTY)
{
return Err(Error::internal("derived constructor metadata is malformed"));
}
let parameter_count = options.parameters.len();
if options.defined_argument_count > options.parameters.len()
|| (options.has_simple_parameter_list
&& (options.defined_argument_count != options.parameters.len()
|| options.rest_parameter.is_some()
|| options.parameters.iter().any(Option::is_none)))
|| options.rest_parameter.is_some_and(|rest| {
usize::from(rest) + 1 != options.parameters.len()
|| options.defined_argument_count != usize::from(rest)
|| options.has_simple_parameter_list
|| options.parameters[usize::from(rest)].is_none()
})
{
return Err(Error::internal("formal parameter metadata is malformed"));
}
let (locals, eval_ret_local, synthetic_locals) =
if matches!(kind, FunctionKind::Script | FunctionKind::Eval(_)) {
(
vec![names.intern(EVAL_RET_LOCAL_NAME)],
Some(0),
vec![SyntheticLocal {
index: 0,
kind: SyntheticLocalKind::EvalCompletion,
}],
)
} else {
(Vec::new(), None, Vec::new())
};
let function_root = ScopeId(0);
let body = ScopeId(1);
let scopes = vec![
IrScope {
parent: None,
kind: ScopeKind::FunctionRoot,
is_parameter_initializer: false,
bindings: Vec::new(),
bindings_by_name: Default::default(),
},
IrScope {
parent: Some(function_root),
kind: if matches!(
kind,
FunctionKind::Script | FunctionKind::Module | FunctionKind::Eval(_)
) {
ScopeKind::ProgramBody
} else {
ScopeKind::FunctionBody
},
is_parameter_initializer: false,
bindings: Vec::new(),
bindings_by_name: Default::default(),
},
];
let var_scope = function_root;
let ops = if matches!(
kind,
FunctionKind::Ordinary
| FunctionKind::Method
| FunctionKind::Arrow
| FunctionKind::Eval(_)
) {
vec![SpannedIrOp {
op: IrOp::EnterScope(body),
pc_site: None,
}]
} else {
Vec::new()
};
let mut function = Self {
body_parsed: false,
parent,
kind,
execution_kind: BytecodeFunctionKind::Normal,
class_constructor: options.class_constructor,
derived_class_constructor: options.derived_class_constructor,
class_initializer_kind: None,
class_private_brand: false,
super_call_allowed: super_capabilities.super_call_allowed,
super_allowed: super_capabilities.super_allowed,
arguments_forbidden: false,
source,
function_name: options.function_name,
private_name_binding: options.private_name_binding,
function_name_local: None,
arguments_local: None,
home_object_local: None,
active_function_local: None,
this_local: None,
new_target_local: None,
eval_variable_object_local: None,
arg_eval_variable_object_local: None,
synthetic_parameter_arguments_local: None,
needs_home_object: false,
parameters: options.parameters,
parameter_names: Vec::new(),
defined_argument_count: options.defined_argument_count,
has_simple_parameter_list: options.has_simple_parameter_list,
rest_parameter: options.rest_parameter,
rest_pattern_start: None,
parameter_scope: None,
parameter_locals: Vec::new(),
parameter_local_reservation_count: None,
parameter_argument_locals: vec![None; parameter_count],
parameter_pattern_bindings: Vec::new(),
parameter_default_sources: Vec::new(),
pattern_parameter_initialization: false,
locals,
scopes,
bindings: Vec::new(),
global_declarations: Vec::new(),
global_declaration_index: HashMap::new(),
hoisted_functions: Vec::new(),
eval_declarations: Vec::new(),
eval_declarations_installed: false,
eval_redeclaration: None,
function_hoists_installed: false,
pseudo_binding_prologues_installed: false,
scoped_functions: Vec::new(),
var_scope,
body_scope: body,
eval_ret_local,
synthetic_locals,
ops,
operands: Default::default(),
constants: Vec::new(),
string_constants: HashMap::new(),
closure_variables: Vec::new(),
closure_index: None,
external_bindings: Vec::new(),
eval_caller_profile: EvalCallerProfile {
scope_kinds: Box::new([]),
variable_target: EvalCallerVariableTarget::Global,
},
eval_environments: Vec::new(),
strict: options.strict,
};
for (index, name) in function.parameters.clone().into_iter().enumerate() {
let Some(name) = name else {
continue;
};
let index = u16::try_from(index)
.map_err(|_| Error::new(ErrorKind::JsInternal, "too many arguments"))?;
function.parameter_names.push(name);
function.add_binding(
function.var_scope,
function.var_scope,
name,
BindingStorage::Argument(index),
BindingKind::Normal,
None,
);
}
Ok(function)
}
pub(in crate::engine::compiler) fn allocate_derived_constructor_pseudo_bindings(
&mut self,
names: &mut NameTable,
) -> Result<(), Error> {
if !self.derived_class_constructor
|| !self.class_constructor
|| self.kind != FunctionKind::Method
|| self.active_function_local.is_some()
|| self.this_local.is_some()
{
return Err(Error::internal(
"derived constructor pseudo bindings were allocated in an invalid phase",
));
}
if self.locals.len().saturating_add(2) > MAX_LOCAL_VARIABLES {
return Err(Error::new(
ErrorKind::JsInternal,
"too many local variables",
));
}
let active_function = u16::try_from(self.locals.len())
.map_err(|_| Error::new(ErrorKind::JsInternal, "too many local variables"))?;
let active_function_name = names.intern(ACTIVE_FUNCTION_LOCAL_NAME);
self.locals.push(active_function_name);
self.active_function_local = Some(active_function);
self.add_binding(
self.var_scope,
self.var_scope,
active_function_name,
BindingStorage::Local(active_function),
BindingKind::Normal,
None,
);
let this = u16::try_from(self.locals.len())
.map_err(|_| Error::new(ErrorKind::JsInternal, "too many local variables"))?;
let this_name = names.intern(THIS_LOCAL_NAME);
self.locals.push(this_name);
self.this_local = Some(this);
self.add_binding(
self.var_scope,
self.var_scope,
this_name,
BindingStorage::Local(this),
BindingKind::Lexical { is_const: false },
None,
);
Ok(())
}
pub(in crate::engine::compiler) fn append_constant(
&mut self,
constant: IrConstant,
) -> Result<u32, Error> {
let index = u32::try_from(self.constants.len())
.map_err(|_| Error::new(ErrorKind::JsInternal, "out of memory"))?;
if let IrConstant::Primitive(Value::String(value)) = &constant {
self.string_constants.entry(value.clone()).or_insert(index);
}
self.constants.push(constant);
Ok(index)
}
pub(in crate::engine::compiler) fn add_binding(
&mut self,
storage_scope: ScopeId,
declaration_scope: ScopeId,
name: NameId,
storage: BindingStorage,
kind: BindingKind,
declaration_span: Option<Span>,
) -> BindingId {
let binding = BindingId(self.bindings.len());
self.bindings.push(IrBinding {
name,
storage_scope,
declaration_scope,
storage,
kind,
is_scoped_function: false,
is_scoped_generator: false,
is_catch_parameter: false,
declaration_span,
});
self.scopes[storage_scope.0].bindings.push(binding);
self.scopes[storage_scope.0].index_binding(name, binding, &self.bindings);
binding
}
pub(in crate::engine::compiler) fn add_synthetic_local(
&mut self,
kind: SyntheticLocalKind,
names: &mut NameTable,
) -> Result<u16, Error> {
if self.locals.len() >= MAX_LOCAL_VARIABLES {
return Err(Error::new(
ErrorKind::JsInternal,
"too many local variables",
));
}
let index = u16::try_from(self.locals.len())
.map_err(|_| Error::new(ErrorKind::JsInternal, "too many local variables"))?;
self.locals.push(names.intern(kind.name()));
self.synthetic_locals.push(SyntheticLocal { index, kind });
Ok(index)
}
pub(in crate::engine::compiler) fn binding_in_scope(
&self,
scope: ScopeId,
name: NameId,
) -> Option<&IrBinding> {
self.binding_id_in_scope(scope, name)
.map(|binding| &self.bindings[binding.0])
}
pub(in crate::engine::compiler) fn binding_id_in_scope(
&self,
scope: ScopeId,
name: NameId,
) -> Option<BindingId> {
self.scopes[scope.0].binding_named(name, &self.bindings)
}
pub(in crate::engine::compiler) fn rebuild_scope_name_index(&mut self, scope: ScopeId) {
self.scopes[scope.0].rebuild_name_index(&self.bindings);
}
pub(in crate::engine::compiler) fn binding_id_from_scope(
&self,
mut scope: ScopeId,
name: NameId,
) -> Option<(ScopeId, BindingId)> {
loop {
if let Some(binding) = self.binding_id_in_scope(scope, name) {
return Some((scope, binding));
}
scope = self.scopes[scope.0].parent?;
}
}
pub(in crate::engine::compiler) fn first_global_declaration_is_normal(
&self,
name: NameId,
) -> bool {
self.first_global_declaration(name)
.is_some_and(|declaration| !declaration.is_lexical)
}
pub(in crate::engine::compiler) fn push_global_declaration(
&mut self,
declaration: IrGlobalDeclaration,
) {
let index = self.global_declarations.len() as u32;
let first = self
.global_declaration_index
.entry(declaration.name)
.or_insert(FirstGlobalDeclarations {
any: index,
normal: FirstGlobalDeclarations::NONE,
lexical: FirstGlobalDeclarations::NONE,
});
let kind = if declaration.is_lexical {
&mut first.lexical
} else {
&mut first.normal
};
if *kind == FirstGlobalDeclarations::NONE {
*kind = index;
}
self.global_declarations.push(declaration);
}
pub(in crate::engine::compiler) fn first_global_declaration(
&self,
name: NameId,
) -> Option<&IrGlobalDeclaration> {
let first = self.global_declaration_index.get(&name)?;
self.global_declarations.get(first.any as usize)
}
pub(in crate::engine::compiler) fn first_global_declaration_of(
&self,
name: NameId,
is_lexical: bool,
) -> Option<&IrGlobalDeclaration> {
let first = self.global_declaration_index.get(&name)?;
let index = if is_lexical {
first.lexical
} else {
first.normal
};
self.global_declarations.get(index as usize)
}
pub(in crate::engine::compiler) fn binding_from_scope(
&self,
mut scope: ScopeId,
name: NameId,
) -> Option<ResolvedBinding> {
loop {
if let Some(binding) = self.binding_in_scope(scope, name) {
return Some(ResolvedBinding {
storage: binding.storage,
kind: binding.kind,
});
}
scope = self.scopes[scope.0].parent?;
}
}
pub(in crate::engine::compiler) fn scope_is_within(
&self,
mut scope: ScopeId,
ancestor: ScopeId,
) -> bool {
loop {
if scope == ancestor {
return true;
}
let Some(parent) = self.scopes[scope.0].parent else {
return false;
};
scope = parent;
}
}
}
#[derive(Debug)]
pub(in crate::engine::compiler) struct FunctionTree {
#[expect(clippy::vec_box)]
pub(in crate::engine::compiler) functions: Vec<Box<FunctionIr>>,
pub(in crate::engine::compiler) names: NameTable,
pub(in crate::engine::compiler) source: SourceText,
pub(in crate::engine::compiler) filename: JsString,
pub(in crate::engine::compiler) module: Option<module::IrModule>,
pub(in crate::engine::compiler) pending_unsupported: Option<Error>,
}