#![allow(clippy::too_many_lines)]
use std::cell::RefCell;
use std::collections::HashSet;
use std::rc::Rc;
use smallvec::{SmallVec, smallvec};
use crate::builtins::string::{string_char_at, utf16_len};
use crate::objects::map::PropertyAttributes;
use crate::objects::property_map::{INTERNAL_PROTO_PROPERTY_KEY, PropertyMap, recycle_object_rc};
use super::{
ACTIVE_DEBUGGER, CallArgs, FAST_ARRAY_METHOD_KEY, GlobalEnv, INLINE_BYTECODE_THRESHOLD,
Interpreter, InterpreterFrame, MegamorphicIcEntry, OSR_LOOP_THRESHOLD, ProtoLoadIc,
TURBOFAN_OSR_LOOP_THRESHOLD, abstract_eq, acquire_frame, bigint_pow, clone_shared_global_env,
collect_args, concat_rc_strs, constant_pool_jump_delta, constant_to_value,
construct_builtin_result, decode_string_constant, dispatch_call_property, dispatch_call_value,
dispatch_call_with_this, dispatch_getter, dispatch_setter, err_bad_operand,
error_message_from_value, extract_context, fast_array_method_name, fast_array_method_target,
find_handler, fn_props_get, fn_props_set, has_prototype_in_chain, is_js_receiver, js_add,
js_less_than, keyed_load, keyed_store, make_construct_this, maybe_cache_construct_boilerplate,
maybe_compile_baseline, maybe_compile_maglev, maybe_compile_turbofan, normalize_async_iterator,
number_to_jsvalue, plain_object_to_array_items, populate_self_name, proto_lookup,
proto_lookup_cached_resolution, proto_lookup_chain_depth, resolve_construct_proto,
resolve_jump, restore_closure_context, run_callee_pooled, set_function_name_if_missing,
set_pending_exception, settle_async_iterator_result, strict_eq, to_array_index, to_bigint,
to_property_key, try_execute_best_jit, try_fast_named_property_lookup,
try_inline_small_function, try_proto_lookup_rc, walk_context_chain,
};
use crate::builtins::error::{ErrorKind, JsError, pop_call_frame, push_call_frame};
use crate::builtins::proxy::{
proxy_construct, proxy_delete_property, proxy_has, proxy_set_with_receiver,
};
use crate::bytecode::bytecode_array::{
BytecodeArray, ConstantPoolEntry, HandlerTableEntry, MAGLEV_TIERING_THRESHOLD,
TIERING_THRESHOLD, TURBOFAN_TIERING_THRESHOLD,
};
use crate::bytecode::bytecodes::{Instruction, Opcode, Operand};
use crate::error::{StatorError, StatorResult};
use crate::objects::js_object::JsObject;
use crate::objects::value::{
GeneratorResumeMode, GeneratorState, GeneratorStatus, GeneratorStep, JsContext, JsValue,
NativeIterator,
};
struct StackStr {
buf: [u8; 11],
len: u8,
}
impl StackStr {
#[inline(always)]
fn as_str(&self) -> &str {
unsafe { std::str::from_utf8_unchecked(&self.buf[..self.len as usize]) }
}
}
#[inline(always)]
fn itoa_stack(mut n: u32) -> StackStr {
let mut s = StackStr {
buf: [0u8; 11],
len: 0,
};
if n == 0 {
s.buf[0] = b'0';
s.len = 1;
return s;
}
let mut i = 0usize;
while n > 0 {
s.buf[i] = b'0' + (n % 10) as u8;
n /= 10;
i += 1;
}
s.len = i as u8;
s.buf[..i].reverse();
s
}
pub(super) enum DispatchAction {
Continue,
Return(JsValue),
TailCall,
}
#[allow(dead_code)]
fn js_object_to_plain_value(obj: JsObject) -> JsValue {
let mut props = PropertyMap::new();
for key in obj.own_property_keys() {
if let Some((value, _attrs)) = obj.get_own_property_descriptor(&key) {
props.insert(key, value);
}
}
JsValue::PlainObject(Rc::new(RefCell::new(props)))
}
pub(super) struct DispatchContext<'a> {
pub frame: &'a mut InterpreterFrame,
pub instructions: &'a [Instruction],
pub byte_offsets: &'a [usize],
pub jump_targets: &'a [Option<usize>],
#[allow(dead_code)]
pub handler_table: &'a [HandlerTableEntry],
}
const PRIVATE_STORAGE_PREFIX: &str = ".private.";
const PRIVATE_BRAND_PREFIX: &str = ".private.brand.";
const PRIVATE_KIND_PREFIX: &str = ".private.kind.";
const POLYMORPHIC_LOAD_CACHE_CAP: usize = 8;
#[inline]
fn resolve_cached_jump(ctx: &DispatchContext, opcode_name: &str) -> StatorResult<usize> {
let current_instr_index = ctx.frame.pc.checked_sub(1).ok_or_else(|| {
StatorError::Internal(format!(
"{opcode_name} executed before program counter advanced"
))
})?;
ctx.jump_targets
.get(current_instr_index)
.copied()
.flatten()
.ok_or_else(|| {
StatorError::Internal(format!(
"missing cached jump target for {opcode_name} at instruction {current_instr_index}"
))
})
}
#[inline(always)]
fn is_private_storage_key(key: &str) -> bool {
key.starts_with(PRIVATE_STORAGE_PREFIX)
&& !key.starts_with(PRIVATE_BRAND_PREFIX)
&& !key.starts_with(PRIVATE_KIND_PREFIX)
}
fn private_brand_key(storage_key: &str) -> String {
storage_key.replacen(PRIVATE_STORAGE_PREFIX, PRIVATE_BRAND_PREFIX, 1)
}
fn private_kind_key(storage_key: &str) -> String {
storage_key.replacen(PRIVATE_STORAGE_PREFIX, PRIVATE_KIND_PREFIX, 1)
}
fn private_getter_key(storage_key: &str) -> String {
format!("__get_{storage_key}__")
}
fn private_setter_key(storage_key: &str) -> String {
format!("__set_{storage_key}__")
}
fn private_display_name(storage_key: &str) -> String {
format!("#{}", storage_key.rsplit('.').next().unwrap_or(storage_key))
}
fn private_access_error(action: &str, storage_key: &str) -> StatorError {
StatorError::TypeError(format!(
"Cannot {action} private member {} from an object whose class did not declare it",
private_display_name(storage_key)
))
}
fn own_private_element_exists(map: &PropertyMap, storage_key: &str) -> bool {
map.contains_key(storage_key)
|| (map.has_accessors
&& (map.contains_key(&private_getter_key(storage_key))
|| map.contains_key(&private_setter_key(storage_key))))
|| map.contains_key(&private_kind_key(storage_key))
}
fn private_kind_marker(obj: &JsValue, storage_key: &str) -> Option<String> {
match proto_lookup(obj, &private_kind_key(storage_key)) {
JsValue::String(kind) => Some(kind.to_string()),
_ => None,
}
}
#[inline(always)]
fn invalidate_plain_object_caches(ctx: &mut DispatchContext, map: &Rc<RefCell<PropertyMap>>) {
let map_ptr = Rc::as_ptr(map) as usize;
if let Some(cache) = &mut ctx.frame.mono_load_cache {
cache.invalidate_layout(map_ptr);
}
if let Some(cache) = &mut ctx.frame.poly_load_cache {
cache.retain(|_, entries| {
entries.retain(|(ptr, _)| *ptr != map_ptr);
!entries.is_empty()
});
}
}
#[inline(always)]
fn mono_load_probe(frame: &InterpreterFrame, slot: u32) -> Option<(usize, usize)> {
frame
.mono_load_cache
.as_ref()
.and_then(|cache| cache.probe(slot))
}
fn load_private_named_property(obj: &JsValue, storage_key: &str) -> StatorResult<JsValue> {
let JsValue::PlainObject(map) = obj else {
return Err(private_access_error("read", storage_key));
};
let has_brand = map.borrow().contains_key(&private_brand_key(storage_key));
if has_brand {
if private_kind_marker(obj, storage_key).is_none() {
return Err(private_access_error("read", storage_key));
}
return Ok(proto_lookup(obj, storage_key));
}
let getter_key = private_getter_key(storage_key);
let setter_key = private_setter_key(storage_key);
let kind_key = private_kind_key(storage_key);
let borrow = map.borrow();
if let Some(getter) = borrow.get(&getter_key).cloned() {
drop(borrow);
return dispatch_getter(&getter, obj);
}
if let Some(value) = borrow.get(storage_key).cloned() {
return Ok(value);
}
let has_setter = borrow.contains_key(&setter_key);
let kind = match borrow.get(&kind_key) {
Some(JsValue::String(kind)) => Some(kind.to_string()),
_ => None,
};
drop(borrow);
match kind.as_deref() {
Some("accessor") if has_setter => Ok(JsValue::Undefined),
_ => Err(private_access_error("read", storage_key)),
}
}
fn store_private_named_property(
ctx: &mut DispatchContext,
obj: &JsValue,
storage_key: &str,
value: JsValue,
) -> StatorResult<()> {
let JsValue::PlainObject(map) = obj else {
return Err(private_access_error("write to", storage_key));
};
let has_brand = map.borrow().contains_key(&private_brand_key(storage_key));
if has_brand {
match private_kind_marker(obj, storage_key).as_deref() {
Some("field") => {
if map.borrow().contains_key(storage_key) {
map.borrow_mut().insert(storage_key.to_string(), value);
invalidate_plain_object_caches(ctx, map);
return Ok(());
}
}
Some("accessor") => {
let setter = proto_lookup(obj, &private_setter_key(storage_key));
if !matches!(setter, JsValue::Undefined) {
dispatch_setter(&setter, obj, value)?;
ctx.frame.global_cache_invalidate();
return Ok(());
}
}
_ => {}
}
return Err(private_access_error("write to", storage_key));
}
let kind_key = private_kind_key(storage_key);
let setter_key = private_setter_key(storage_key);
let kind = {
let borrow = map.borrow();
match borrow.get(&kind_key) {
Some(JsValue::String(kind)) => Some(kind.to_string()),
_ => None,
}
};
match kind.as_deref() {
Some("field") => {
if map.borrow().contains_key(storage_key) {
map.borrow_mut().insert(storage_key.to_string(), value);
invalidate_plain_object_caches(ctx, map);
Ok(())
} else {
Err(private_access_error("write to", storage_key))
}
}
Some("accessor") => {
let setter = map.borrow().get(&setter_key).cloned();
if let Some(setter) = setter {
dispatch_setter(&setter, obj, value)?;
ctx.frame.global_cache_invalidate();
Ok(())
} else {
Err(private_access_error("write to", storage_key))
}
}
_ => Err(private_access_error("write to", storage_key)),
}
}
fn private_named_property_attrs(prop_name: &str) -> Option<PropertyAttributes> {
if !prop_name.starts_with(PRIVATE_STORAGE_PREFIX) {
return None;
}
Some(
if prop_name.starts_with(PRIVATE_BRAND_PREFIX) || prop_name.starts_with(PRIVATE_KIND_PREFIX)
{
PropertyAttributes::CONFIGURABLE
} else {
PropertyAttributes::WRITABLE | PropertyAttributes::CONFIGURABLE
},
)
}
pub(super) type OpcodeHandler =
fn(&mut DispatchContext, &Instruction) -> StatorResult<DispatchAction>;
const OPCODE_COUNT: usize = Opcode::Nop as usize + 1;
#[inline]
fn handle_lda_zero(
ctx: &mut DispatchContext,
_instr: &Instruction,
) -> StatorResult<DispatchAction> {
ctx.frame.accumulator = JsValue::Smi(0);
Ok(DispatchAction::Continue)
}
#[inline]
fn handle_nop(_ctx: &mut DispatchContext, _instr: &Instruction) -> StatorResult<DispatchAction> {
Ok(DispatchAction::Continue)
}
#[inline(always)]
fn handle_lda_smi(ctx: &mut DispatchContext, instr: &Instruction) -> StatorResult<DispatchAction> {
let Operand::Immediate(v) = *instr.operand(0) else {
return Err(err_bad_operand("LdaSmi", 0));
};
ctx.frame.accumulator = JsValue::Smi(v);
Ok(DispatchAction::Continue)
}
#[inline(always)]
fn handle_lda_smi_star(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Immediate(v) = *instr.operand(0) else {
return Err(err_bad_operand("LdaSmiStar", 0));
};
let Operand::Register(dst) = *instr.operand(1) else {
return Err(err_bad_operand("LdaSmiStar", 1));
};
let value = JsValue::Smi(v);
ctx.frame.accumulator = value.cheap_clone();
ctx.frame.write_reg(dst, value)?;
Ok(DispatchAction::Continue)
}
#[inline]
fn handle_lda_undefined(
ctx: &mut DispatchContext,
_instr: &Instruction,
) -> StatorResult<DispatchAction> {
ctx.frame.accumulator = JsValue::Undefined;
Ok(DispatchAction::Continue)
}
#[inline]
fn handle_lda_the_hole(
ctx: &mut DispatchContext,
_instr: &Instruction,
) -> StatorResult<DispatchAction> {
ctx.frame.accumulator = JsValue::TheHole;
Ok(DispatchAction::Continue)
}
#[inline]
fn handle_lda_null(
ctx: &mut DispatchContext,
_instr: &Instruction,
) -> StatorResult<DispatchAction> {
ctx.frame.accumulator = JsValue::Null;
Ok(DispatchAction::Continue)
}
#[inline]
fn handle_lda_true(
ctx: &mut DispatchContext,
_instr: &Instruction,
) -> StatorResult<DispatchAction> {
ctx.frame.accumulator = JsValue::Boolean(true);
Ok(DispatchAction::Continue)
}
#[inline]
fn handle_lda_false(
ctx: &mut DispatchContext,
_instr: &Instruction,
) -> StatorResult<DispatchAction> {
ctx.frame.accumulator = JsValue::Boolean(false);
Ok(DispatchAction::Continue)
}
#[inline]
fn handle_lda_constant(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::ConstantPoolIdx(idx) = *instr.operand(0) else {
return Err(err_bad_operand("LdaConstant", 0));
};
let entry =
ctx.frame.bytecode_array.get_constant(idx).ok_or_else(|| {
StatorError::Internal(format!("constant pool index {idx} out of bounds"))
})?;
ctx.frame.accumulator = constant_to_value(entry);
Ok(DispatchAction::Continue)
}
#[inline(always)]
fn handle_ldar(ctx: &mut DispatchContext, instr: &Instruction) -> StatorResult<DispatchAction> {
let Operand::Register(v) = *instr.operand(0) else {
return Err(err_bad_operand("Ldar", 0));
};
ctx.frame.accumulator = ctx.frame.read_reg(v)?.cheap_clone();
Ok(DispatchAction::Continue)
}
#[inline(always)]
fn handle_ldar_add_star(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(src) = *instr.operand(0) else {
return Err(err_bad_operand("LdarAddStar", 0));
};
let Operand::Register(add_reg) = *instr.operand(1) else {
return Err(err_bad_operand("LdarAddStar", 1));
};
let Operand::Register(dst) = *instr.operand(2) else {
return Err(err_bad_operand("LdarAddStar", 2));
};
let Operand::FeedbackSlot(_slot) = *instr.operand(3) else {
return Err(err_bad_operand("LdarAddStar", 3));
};
let lhs = ctx.frame.read_reg(src)?.cheap_clone();
let rhs = ctx.frame.read_reg(add_reg)?;
if let (JsValue::Smi(a), JsValue::Smi(b)) = (&lhs, rhs)
&& let Some(result) = a.checked_add(*b)
{
let value = JsValue::Smi(result);
ctx.frame.accumulator = value.cheap_clone();
ctx.frame.write_reg(dst, value)?;
return Ok(DispatchAction::Continue);
}
ctx.frame.accumulator = js_add(&lhs, rhs)?;
ctx.frame
.write_reg(dst, ctx.frame.accumulator.cheap_clone())?;
Ok(DispatchAction::Continue)
}
#[inline(always)]
fn handle_ldar_sub_star(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(src) = *instr.operand(0) else {
return Err(err_bad_operand("LdarSubStar", 0));
};
let Operand::Register(sub_reg) = *instr.operand(1) else {
return Err(err_bad_operand("LdarSubStar", 1));
};
let Operand::Register(dst) = *instr.operand(2) else {
return Err(err_bad_operand("LdarSubStar", 2));
};
let Operand::FeedbackSlot(_slot) = *instr.operand(3) else {
return Err(err_bad_operand("LdarSubStar", 3));
};
let lhs = ctx.frame.read_reg(src)?.cheap_clone();
let rhs = ctx.frame.read_reg(sub_reg)?;
if let (JsValue::Smi(a), JsValue::Smi(b)) = (&lhs, rhs)
&& let Some(result) = a.checked_sub(*b)
{
let value = JsValue::Smi(result);
ctx.frame.accumulator = value.cheap_clone();
ctx.frame.write_reg(dst, value)?;
return Ok(DispatchAction::Continue);
}
if lhs.is_bigint() || rhs.is_bigint() {
let left = to_bigint(&lhs)?;
let right = to_bigint(rhs)?;
ctx.frame.accumulator = JsValue::BigInt(Box::new(left.wrapping_sub(right)));
} else {
let lhs_n = lhs.to_number()?;
let rhs_n = rhs.to_number()?;
ctx.frame.accumulator = number_to_jsvalue(lhs_n - rhs_n);
}
ctx.frame
.write_reg(dst, ctx.frame.accumulator.cheap_clone())?;
Ok(DispatchAction::Continue)
}
#[inline(always)]
fn handle_ldar_mul_star(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(src) = *instr.operand(0) else {
return Err(err_bad_operand("LdarMulStar", 0));
};
let Operand::Register(mul_reg) = *instr.operand(1) else {
return Err(err_bad_operand("LdarMulStar", 1));
};
let Operand::Register(dst) = *instr.operand(2) else {
return Err(err_bad_operand("LdarMulStar", 2));
};
let Operand::FeedbackSlot(_slot) = *instr.operand(3) else {
return Err(err_bad_operand("LdarMulStar", 3));
};
let lhs = ctx.frame.read_reg(src)?.cheap_clone();
let rhs = ctx.frame.read_reg(mul_reg)?;
if let Some(value) = JsValue::try_mul_smi(&lhs, rhs) {
ctx.frame.accumulator = value.cheap_clone();
ctx.frame.write_reg(dst, value)?;
return Ok(DispatchAction::Continue);
}
if let (Some(a), Some(b)) = (lhs.try_as_heap_number(), rhs.try_as_heap_number()) {
let value = number_to_jsvalue(a * b);
ctx.frame.accumulator = value.cheap_clone();
ctx.frame.write_reg(dst, value)?;
return Ok(DispatchAction::Continue);
}
if lhs.is_bigint() || rhs.is_bigint() {
let left = to_bigint(&lhs)?;
let right = to_bigint(rhs)?;
ctx.frame.accumulator = JsValue::BigInt(Box::new(left.wrapping_mul(right)));
} else {
let lhs_n = lhs.to_number()?;
let rhs_n = rhs.to_number()?;
ctx.frame.accumulator = number_to_jsvalue(lhs_n * rhs_n);
}
ctx.frame
.write_reg(dst, ctx.frame.accumulator.cheap_clone())?;
Ok(DispatchAction::Continue)
}
#[inline(always)]
fn handle_star(ctx: &mut DispatchContext, instr: &Instruction) -> StatorResult<DispatchAction> {
let Operand::Register(v) = *instr.operand(0) else {
return Err(err_bad_operand("Star", 0));
};
let val = ctx.frame.accumulator.cheap_clone();
let idx = ctx.frame.reg_index(v)?;
let old = std::mem::replace(&mut ctx.frame.registers[idx], val);
if let JsValue::PlainObject(rc) = old {
recycle_object_rc(rc);
}
Ok(DispatchAction::Continue)
}
#[inline]
fn handle_mov(ctx: &mut DispatchContext, instr: &Instruction) -> StatorResult<DispatchAction> {
let Operand::Register(src) = *instr.operand(0) else {
return Err(err_bad_operand("Mov", 0));
};
let Operand::Register(dst) = *instr.operand(1) else {
return Err(err_bad_operand("Mov", 1));
};
ctx.frame.copy_reg(src, dst)?;
Ok(DispatchAction::Continue)
}
#[inline(always)]
fn handle_add(ctx: &mut DispatchContext, instr: &Instruction) -> StatorResult<DispatchAction> {
let Operand::Register(v) = *instr.operand(0) else {
return Err(err_bad_operand("Add", 0));
};
let rhs = ctx.frame.read_reg(v)?;
if let Some(result) = JsValue::try_add_smi(&ctx.frame.accumulator, rhs) {
ctx.frame.accumulator = result;
return Ok(DispatchAction::Continue);
}
if let (Some(a), Some(b)) = (
ctx.frame.accumulator.try_as_heap_number(),
rhs.try_as_heap_number(),
) {
ctx.frame.accumulator = number_to_jsvalue(a + b);
return Ok(DispatchAction::Continue);
}
if let JsValue::String(ref a) = ctx.frame.accumulator
&& let JsValue::String(b) = rhs
{
let total = a.len().saturating_add(b.len());
if total > crate::builtins::string::MAX_STRING_LEN {
return Err(crate::error::StatorError::RangeError(
"Invalid string length".into(),
));
}
ctx.frame.accumulator = concat_rc_strs(a, b);
return Ok(DispatchAction::Continue);
}
ctx.frame.accumulator = js_add(&ctx.frame.accumulator, rhs)?;
Ok(DispatchAction::Continue)
}
#[inline(always)]
fn handle_sub(ctx: &mut DispatchContext, instr: &Instruction) -> StatorResult<DispatchAction> {
let Operand::Register(v) = *instr.operand(0) else {
return Err(err_bad_operand("Sub", 0));
};
let rhs = ctx.frame.read_reg(v)?;
if let Some(result) = JsValue::try_sub_smi(&ctx.frame.accumulator, rhs) {
ctx.frame.accumulator = result;
return Ok(DispatchAction::Continue);
}
if let (Some(a), Some(b)) = (
ctx.frame.accumulator.try_as_heap_number(),
rhs.try_as_heap_number(),
) {
ctx.frame.accumulator = number_to_jsvalue(a - b);
return Ok(DispatchAction::Continue);
}
if ctx.frame.accumulator.is_bigint() || rhs.is_bigint() {
let l = to_bigint(&ctx.frame.accumulator)?;
let r = to_bigint(rhs)?;
ctx.frame.accumulator = JsValue::BigInt(Box::new(l.wrapping_sub(r)));
} else {
let lhs_n = ctx.frame.accumulator.to_number()?;
let rhs_n = rhs.to_number()?;
ctx.frame.accumulator = number_to_jsvalue(lhs_n - rhs_n);
}
Ok(DispatchAction::Continue)
}
#[inline(always)]
fn handle_mul(ctx: &mut DispatchContext, instr: &Instruction) -> StatorResult<DispatchAction> {
let Operand::Register(v) = *instr.operand(0) else {
return Err(err_bad_operand("Mul", 0));
};
let rhs = ctx.frame.read_reg(v)?;
if let Some(result) = JsValue::try_mul_smi(&ctx.frame.accumulator, rhs) {
ctx.frame.accumulator = result;
return Ok(DispatchAction::Continue);
}
if let (Some(a), Some(b)) = (
ctx.frame.accumulator.try_as_heap_number(),
rhs.try_as_heap_number(),
) {
ctx.frame.accumulator = number_to_jsvalue(a * b);
return Ok(DispatchAction::Continue);
}
if ctx.frame.accumulator.is_bigint() || rhs.is_bigint() {
let l = to_bigint(&ctx.frame.accumulator)?;
let r = to_bigint(rhs)?;
ctx.frame.accumulator = JsValue::BigInt(Box::new(l.wrapping_mul(r)));
} else {
let lhs_n = ctx.frame.accumulator.to_number()?;
let rhs_n = rhs.to_number()?;
ctx.frame.accumulator = number_to_jsvalue(lhs_n * rhs_n);
}
Ok(DispatchAction::Continue)
}
fn handle_div(ctx: &mut DispatchContext, instr: &Instruction) -> StatorResult<DispatchAction> {
let Operand::Register(v) = *instr.operand(0) else {
return Err(err_bad_operand("Div", 0));
};
let rhs = ctx.frame.read_reg(v)?;
if let (Some(a), Some(b)) = (ctx.frame.accumulator.try_as_smi(), rhs.try_as_smi()) {
if b != 0 && a % b == 0 {
ctx.frame.accumulator = JsValue::Smi(a / b);
return Ok(DispatchAction::Continue);
}
ctx.frame.accumulator = number_to_jsvalue(a as f64 / b as f64);
return Ok(DispatchAction::Continue);
}
if let (Some(a), Some(b)) = (
ctx.frame.accumulator.try_as_heap_number(),
rhs.try_as_heap_number(),
) {
ctx.frame.accumulator = number_to_jsvalue(a / b);
return Ok(DispatchAction::Continue);
}
if ctx.frame.accumulator.is_bigint() || rhs.is_bigint() {
let l = to_bigint(&ctx.frame.accumulator)?;
let r = to_bigint(rhs)?;
if r == 0 {
return Err(StatorError::RangeError("Division by zero".to_string()));
}
ctx.frame.accumulator = JsValue::BigInt(Box::new(l / r));
} else {
let lhs_n = ctx.frame.accumulator.to_number()?;
let rhs_n = rhs.to_number()?;
ctx.frame.accumulator = number_to_jsvalue(lhs_n / rhs_n);
}
Ok(DispatchAction::Continue)
}
fn handle_mod(ctx: &mut DispatchContext, instr: &Instruction) -> StatorResult<DispatchAction> {
let Operand::Register(v) = *instr.operand(0) else {
return Err(err_bad_operand("Mod", 0));
};
let rhs = ctx.frame.read_reg(v)?;
if let (Some(a), Some(b)) = (ctx.frame.accumulator.try_as_smi(), rhs.try_as_smi()) {
if b != 0 {
let rem = a % b;
if rem == 0 && a < 0 {
ctx.frame.accumulator = JsValue::HeapNumber(-0.0);
} else {
ctx.frame.accumulator = JsValue::Smi(rem);
}
} else {
ctx.frame.accumulator = JsValue::HeapNumber(f64::NAN);
}
return Ok(DispatchAction::Continue);
}
if let (Some(a), Some(b)) = (
ctx.frame.accumulator.try_as_heap_number(),
rhs.try_as_heap_number(),
) {
ctx.frame.accumulator = number_to_jsvalue(a % b);
return Ok(DispatchAction::Continue);
}
if ctx.frame.accumulator.is_bigint() || rhs.is_bigint() {
let l = to_bigint(&ctx.frame.accumulator)?;
let r = to_bigint(rhs)?;
if r == 0 {
return Err(StatorError::RangeError("Division by zero".to_string()));
}
ctx.frame.accumulator = JsValue::BigInt(Box::new(l % r));
} else {
let lhs_n = ctx.frame.accumulator.to_number()?;
let rhs_n = rhs.to_number()?;
ctx.frame.accumulator = number_to_jsvalue(lhs_n % rhs_n);
}
Ok(DispatchAction::Continue)
}
fn handle_exp(ctx: &mut DispatchContext, instr: &Instruction) -> StatorResult<DispatchAction> {
let Operand::Register(v) = *instr.operand(0) else {
return Err(err_bad_operand("Exp", 0));
};
let rhs = ctx.frame.read_reg(v)?;
if let JsValue::Smi(a) = ctx.frame.accumulator
&& let JsValue::Smi(b) = rhs
{
ctx.frame.accumulator = number_to_jsvalue((a as f64).powf(*b as f64));
return Ok(DispatchAction::Continue);
}
if ctx.frame.accumulator.is_bigint() || rhs.is_bigint() {
let l = to_bigint(&ctx.frame.accumulator)?;
let r = to_bigint(rhs)?;
if r < 0 {
return Err(StatorError::RangeError(
"Exponent must be positive".to_string(),
));
}
ctx.frame.accumulator = JsValue::BigInt(Box::new(bigint_pow(l, r as u32)));
} else {
let lhs_n = ctx.frame.accumulator.to_number()?;
let rhs_n = rhs.to_number()?;
ctx.frame.accumulator = number_to_jsvalue(lhs_n.powf(rhs_n));
}
Ok(DispatchAction::Continue)
}
#[inline(always)]
fn handle_bitwise_or(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(v) = *instr.operand(0) else {
return Err(err_bad_operand("BitwiseOr", 0));
};
let rhs = ctx.frame.read_reg(v)?;
if let JsValue::Smi(a) = ctx.frame.accumulator
&& let JsValue::Smi(b) = rhs
{
ctx.frame.accumulator = JsValue::Smi(a | *b);
return Ok(DispatchAction::Continue);
}
if ctx.frame.accumulator.is_bigint() || rhs.is_bigint() {
let l = to_bigint(&ctx.frame.accumulator)?;
let r = to_bigint(rhs)?;
ctx.frame.accumulator = JsValue::BigInt(Box::new(l | r));
} else {
let lhs = ctx.frame.accumulator.to_int32()?;
let rhs_i = rhs.to_int32()?;
ctx.frame.accumulator = JsValue::Smi(lhs | rhs_i);
}
Ok(DispatchAction::Continue)
}
fn handle_bitwise_xor(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(v) = *instr.operand(0) else {
return Err(err_bad_operand("BitwiseXor", 0));
};
let rhs = ctx.frame.read_reg(v)?;
if let JsValue::Smi(a) = ctx.frame.accumulator
&& let JsValue::Smi(b) = rhs
{
ctx.frame.accumulator = JsValue::Smi(a ^ *b);
return Ok(DispatchAction::Continue);
}
if ctx.frame.accumulator.is_bigint() || rhs.is_bigint() {
let l = to_bigint(&ctx.frame.accumulator)?;
let r = to_bigint(rhs)?;
ctx.frame.accumulator = JsValue::BigInt(Box::new(l ^ r));
} else {
let lhs = ctx.frame.accumulator.to_int32()?;
let rhs_i = rhs.to_int32()?;
ctx.frame.accumulator = JsValue::Smi(lhs ^ rhs_i);
}
Ok(DispatchAction::Continue)
}
#[inline(always)]
fn handle_bitwise_and(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(v) = *instr.operand(0) else {
return Err(err_bad_operand("BitwiseAnd", 0));
};
let rhs = ctx.frame.read_reg(v)?;
if let JsValue::Smi(a) = ctx.frame.accumulator
&& let JsValue::Smi(b) = rhs
{
ctx.frame.accumulator = JsValue::Smi(a & *b);
return Ok(DispatchAction::Continue);
}
if ctx.frame.accumulator.is_bigint() || rhs.is_bigint() {
let l = to_bigint(&ctx.frame.accumulator)?;
let r = to_bigint(rhs)?;
ctx.frame.accumulator = JsValue::BigInt(Box::new(l & r));
} else {
let lhs = ctx.frame.accumulator.to_int32()?;
let rhs_i = rhs.to_int32()?;
ctx.frame.accumulator = JsValue::Smi(lhs & rhs_i);
}
Ok(DispatchAction::Continue)
}
fn handle_shift_left(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(v) = *instr.operand(0) else {
return Err(err_bad_operand("ShiftLeft", 0));
};
let rhs = ctx.frame.read_reg(v)?;
if let JsValue::Smi(a) = ctx.frame.accumulator
&& let JsValue::Smi(b) = rhs
{
let shift = (*b as u32) & 0x1f;
ctx.frame.accumulator = JsValue::Smi(a << shift);
return Ok(DispatchAction::Continue);
}
if ctx.frame.accumulator.is_bigint() || rhs.is_bigint() {
let l = to_bigint(&ctx.frame.accumulator)?;
let r = to_bigint(rhs)?;
ctx.frame.accumulator = JsValue::BigInt(Box::new(l.wrapping_shl(r as u32)));
} else {
let lhs = ctx.frame.accumulator.to_int32()?;
let shift = rhs.to_uint32()? & 0x1f;
ctx.frame.accumulator = JsValue::Smi(lhs << shift);
}
Ok(DispatchAction::Continue)
}
fn handle_shift_right(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(v) = *instr.operand(0) else {
return Err(err_bad_operand("ShiftRight", 0));
};
let rhs = ctx.frame.read_reg(v)?;
if let JsValue::Smi(a) = ctx.frame.accumulator
&& let JsValue::Smi(b) = rhs
{
let shift = (*b as u32) & 0x1f;
ctx.frame.accumulator = JsValue::Smi(a >> shift);
return Ok(DispatchAction::Continue);
}
if ctx.frame.accumulator.is_bigint() || rhs.is_bigint() {
let l = to_bigint(&ctx.frame.accumulator)?;
let r = to_bigint(rhs)?;
ctx.frame.accumulator = JsValue::BigInt(Box::new(l.wrapping_shr(r as u32)));
} else {
let lhs = ctx.frame.accumulator.to_int32()?;
let shift = rhs.to_uint32()? & 0x1f;
ctx.frame.accumulator = JsValue::Smi(lhs >> shift);
}
Ok(DispatchAction::Continue)
}
fn handle_shift_right_logical(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(v) = *instr.operand(0) else {
return Err(err_bad_operand("ShiftRightLogical", 0));
};
let rhs = ctx.frame.read_reg(v)?;
if let JsValue::Smi(a) = ctx.frame.accumulator
&& let JsValue::Smi(b) = rhs
{
let lhs = a as u32;
let shift = (*b as u32) & 0x1f;
let result = lhs >> shift;
ctx.frame.accumulator = number_to_jsvalue(result as f64);
return Ok(DispatchAction::Continue);
}
let lhs = ctx.frame.accumulator.to_int32()? as u32;
let shift = rhs.to_uint32()? & 0x1f;
let result = lhs >> shift;
ctx.frame.accumulator = number_to_jsvalue(result as f64);
Ok(DispatchAction::Continue)
}
#[inline]
fn handle_add_smi(ctx: &mut DispatchContext, instr: &Instruction) -> StatorResult<DispatchAction> {
let Operand::Immediate(imm) = *instr.operand(0) else {
return Err(err_bad_operand("AddSmi", 0));
};
if let JsValue::Smi(n) = &ctx.frame.accumulator
&& let Some(result) = n.checked_add(imm)
{
ctx.frame.accumulator = JsValue::Smi(result);
return Ok(DispatchAction::Continue);
}
if let JsValue::BigInt(n) = &ctx.frame.accumulator {
ctx.frame.accumulator = JsValue::BigInt(Box::new(n.wrapping_add(i128::from(imm))));
} else {
let lhs_n = ctx.frame.accumulator.to_number()?;
ctx.frame.accumulator = number_to_jsvalue(lhs_n + imm as f64);
}
Ok(DispatchAction::Continue)
}
#[inline]
fn handle_add_smi_star(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Immediate(imm) = *instr.operand(0) else {
return Err(err_bad_operand("AddSmiStar", 0));
};
let Operand::FeedbackSlot(_slot) = *instr.operand(1) else {
return Err(err_bad_operand("AddSmiStar", 1));
};
let Operand::Register(dst) = *instr.operand(2) else {
return Err(err_bad_operand("AddSmiStar", 2));
};
if let JsValue::Smi(n) = &ctx.frame.accumulator
&& let Some(result) = n.checked_add(imm)
{
let value = JsValue::Smi(result);
ctx.frame.accumulator = value.cheap_clone();
ctx.frame.write_reg(dst, value)?;
return Ok(DispatchAction::Continue);
}
if let JsValue::BigInt(n) = &ctx.frame.accumulator {
ctx.frame.accumulator = JsValue::BigInt(Box::new(n.wrapping_add(i128::from(imm))));
} else {
let lhs_n = ctx.frame.accumulator.to_number()?;
ctx.frame.accumulator = number_to_jsvalue(lhs_n + imm as f64);
}
ctx.frame
.write_reg(dst, ctx.frame.accumulator.cheap_clone())?;
Ok(DispatchAction::Continue)
}
#[inline]
fn handle_sub_smi(ctx: &mut DispatchContext, instr: &Instruction) -> StatorResult<DispatchAction> {
let Operand::Immediate(imm) = *instr.operand(0) else {
return Err(err_bad_operand("SubSmi", 0));
};
if let JsValue::Smi(n) = &ctx.frame.accumulator
&& let Some(result) = n.checked_sub(imm)
{
ctx.frame.accumulator = JsValue::Smi(result);
return Ok(DispatchAction::Continue);
}
if let JsValue::BigInt(n) = &ctx.frame.accumulator {
ctx.frame.accumulator = JsValue::BigInt(Box::new(n.wrapping_sub(i128::from(imm))));
} else {
let lhs_n = ctx.frame.accumulator.to_number()?;
ctx.frame.accumulator = number_to_jsvalue(lhs_n - imm as f64);
}
Ok(DispatchAction::Continue)
}
fn handle_mul_smi(ctx: &mut DispatchContext, instr: &Instruction) -> StatorResult<DispatchAction> {
let Operand::Immediate(imm) = *instr.operand(0) else {
return Err(err_bad_operand("MulSmi", 0));
};
if let JsValue::Smi(n) = &ctx.frame.accumulator
&& let Some(result) = n.checked_mul(imm)
{
ctx.frame.accumulator = JsValue::Smi(result);
return Ok(DispatchAction::Continue);
}
if let JsValue::BigInt(n) = &ctx.frame.accumulator {
ctx.frame.accumulator = JsValue::BigInt(Box::new(n.wrapping_mul(i128::from(imm))));
} else {
let lhs_n = ctx.frame.accumulator.to_number()?;
ctx.frame.accumulator = number_to_jsvalue(lhs_n * imm as f64);
}
Ok(DispatchAction::Continue)
}
#[inline]
fn handle_mul_smi_star(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Immediate(imm) = *instr.operand(0) else {
return Err(err_bad_operand("MulSmiStar", 0));
};
let Operand::FeedbackSlot(_slot) = *instr.operand(1) else {
return Err(err_bad_operand("MulSmiStar", 1));
};
let Operand::Register(dst) = *instr.operand(2) else {
return Err(err_bad_operand("MulSmiStar", 2));
};
if let JsValue::Smi(n) = &ctx.frame.accumulator
&& let Some(result) = n.checked_mul(imm)
{
let value = JsValue::Smi(result);
ctx.frame.accumulator = value.cheap_clone();
ctx.frame.write_reg(dst, value)?;
return Ok(DispatchAction::Continue);
}
if let JsValue::BigInt(n) = &ctx.frame.accumulator {
ctx.frame.accumulator = JsValue::BigInt(Box::new(n.wrapping_mul(i128::from(imm))));
} else {
let lhs_n = ctx.frame.accumulator.to_number()?;
ctx.frame.accumulator = number_to_jsvalue(lhs_n * imm as f64);
}
ctx.frame
.write_reg(dst, ctx.frame.accumulator.cheap_clone())?;
Ok(DispatchAction::Continue)
}
fn handle_div_smi(ctx: &mut DispatchContext, instr: &Instruction) -> StatorResult<DispatchAction> {
let Operand::Immediate(imm) = *instr.operand(0) else {
return Err(err_bad_operand("DivSmi", 0));
};
if let JsValue::BigInt(n) = &ctx.frame.accumulator {
if imm == 0 {
return Err(StatorError::RangeError("Division by zero".to_string()));
}
ctx.frame.accumulator = JsValue::BigInt(Box::new(**n / i128::from(imm)));
} else {
let lhs_n = ctx.frame.accumulator.to_number()?;
ctx.frame.accumulator = number_to_jsvalue(lhs_n / imm as f64);
}
Ok(DispatchAction::Continue)
}
fn handle_mod_smi(ctx: &mut DispatchContext, instr: &Instruction) -> StatorResult<DispatchAction> {
let Operand::Immediate(imm) = *instr.operand(0) else {
return Err(err_bad_operand("ModSmi", 0));
};
if let JsValue::BigInt(n) = &ctx.frame.accumulator {
if imm == 0 {
return Err(StatorError::RangeError("Division by zero".to_string()));
}
ctx.frame.accumulator = JsValue::BigInt(Box::new(**n % i128::from(imm)));
} else {
let lhs_n = ctx.frame.accumulator.to_number()?;
ctx.frame.accumulator = number_to_jsvalue(lhs_n % imm as f64);
}
Ok(DispatchAction::Continue)
}
fn handle_exp_smi(ctx: &mut DispatchContext, instr: &Instruction) -> StatorResult<DispatchAction> {
let Operand::Immediate(imm) = *instr.operand(0) else {
return Err(err_bad_operand("ExpSmi", 0));
};
if let JsValue::BigInt(n) = &ctx.frame.accumulator {
if imm < 0 {
return Err(StatorError::RangeError(
"Exponent must be positive".to_string(),
));
}
ctx.frame.accumulator = JsValue::BigInt(Box::new(bigint_pow(**n, imm as u32)));
} else {
let lhs_n = ctx.frame.accumulator.to_number()?;
ctx.frame.accumulator = number_to_jsvalue(lhs_n.powf(imm as f64));
}
Ok(DispatchAction::Continue)
}
fn handle_bitwise_or_smi(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Immediate(imm) = *instr.operand(0) else {
return Err(err_bad_operand("BitwiseOrSmi", 0));
};
if let JsValue::Smi(lhs) = &ctx.frame.accumulator {
ctx.frame.accumulator = JsValue::Smi(*lhs | imm);
return Ok(DispatchAction::Continue);
}
if let JsValue::BigInt(n) = &ctx.frame.accumulator {
ctx.frame.accumulator = JsValue::BigInt(Box::new(**n | i128::from(imm)));
} else {
let lhs = ctx.frame.accumulator.to_int32()?;
ctx.frame.accumulator = JsValue::Smi(lhs | imm);
}
Ok(DispatchAction::Continue)
}
fn handle_bitwise_xor_smi(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Immediate(imm) = *instr.operand(0) else {
return Err(err_bad_operand("BitwiseXorSmi", 0));
};
if let JsValue::Smi(lhs) = &ctx.frame.accumulator {
ctx.frame.accumulator = JsValue::Smi(*lhs ^ imm);
return Ok(DispatchAction::Continue);
}
if let JsValue::BigInt(n) = &ctx.frame.accumulator {
ctx.frame.accumulator = JsValue::BigInt(Box::new(**n ^ i128::from(imm)));
} else {
let lhs = ctx.frame.accumulator.to_int32()?;
ctx.frame.accumulator = JsValue::Smi(lhs ^ imm);
}
Ok(DispatchAction::Continue)
}
fn handle_bitwise_and_smi(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Immediate(imm) = *instr.operand(0) else {
return Err(err_bad_operand("BitwiseAndSmi", 0));
};
if let JsValue::Smi(lhs) = &ctx.frame.accumulator {
ctx.frame.accumulator = JsValue::Smi(*lhs & imm);
return Ok(DispatchAction::Continue);
}
if let JsValue::BigInt(n) = &ctx.frame.accumulator {
ctx.frame.accumulator = JsValue::BigInt(Box::new(**n & i128::from(imm)));
} else {
let lhs = ctx.frame.accumulator.to_int32()?;
ctx.frame.accumulator = JsValue::Smi(lhs & imm);
}
Ok(DispatchAction::Continue)
}
fn handle_shift_left_smi(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Immediate(imm) = *instr.operand(0) else {
return Err(err_bad_operand("ShiftLeftSmi", 0));
};
if let JsValue::Smi(lhs) = &ctx.frame.accumulator {
let shift = (imm as u32) & 0x1f;
ctx.frame.accumulator = JsValue::Smi(*lhs << shift);
return Ok(DispatchAction::Continue);
}
if let JsValue::BigInt(n) = &ctx.frame.accumulator {
ctx.frame.accumulator = JsValue::BigInt(Box::new(n.wrapping_shl(imm as u32)));
} else {
let lhs = ctx.frame.accumulator.to_int32()?;
let shift = (imm as u32) & 0x1f;
ctx.frame.accumulator = JsValue::Smi(lhs << shift);
}
Ok(DispatchAction::Continue)
}
fn handle_shift_right_smi(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Immediate(imm) = *instr.operand(0) else {
return Err(err_bad_operand("ShiftRightSmi", 0));
};
if let JsValue::Smi(lhs) = &ctx.frame.accumulator {
let shift = (imm as u32) & 0x1f;
ctx.frame.accumulator = JsValue::Smi(*lhs >> shift);
return Ok(DispatchAction::Continue);
}
if let JsValue::BigInt(n) = &ctx.frame.accumulator {
ctx.frame.accumulator = JsValue::BigInt(Box::new(n.wrapping_shr(imm as u32)));
} else {
let lhs = ctx.frame.accumulator.to_int32()?;
let shift = (imm as u32) & 0x1f;
ctx.frame.accumulator = JsValue::Smi(lhs >> shift);
}
Ok(DispatchAction::Continue)
}
fn handle_shift_right_logical_smi(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Immediate(imm) = *instr.operand(0) else {
return Err(err_bad_operand("ShiftRightLogicalSmi", 0));
};
if let JsValue::Smi(lhs) = &ctx.frame.accumulator {
let shift = (imm as u32) & 0x1f;
let result = (*lhs as u32) >> shift;
ctx.frame.accumulator = number_to_jsvalue(result as f64);
return Ok(DispatchAction::Continue);
}
let lhs = ctx.frame.accumulator.to_int32()? as u32;
let shift = (imm as u32) & 0x1f;
let result = lhs >> shift;
ctx.frame.accumulator = number_to_jsvalue(result as f64);
Ok(DispatchAction::Continue)
}
#[inline(always)]
fn handle_inc(ctx: &mut DispatchContext, _instr: &Instruction) -> StatorResult<DispatchAction> {
if let JsValue::Smi(n) = ctx.frame.accumulator {
if let Some(result) = n.checked_add(1) {
ctx.frame.accumulator = JsValue::Smi(result);
return Ok(DispatchAction::Continue);
}
ctx.frame.accumulator = JsValue::HeapNumber((n as f64) + 1.0);
return Ok(DispatchAction::Continue);
}
if let JsValue::BigInt(n) = &ctx.frame.accumulator {
ctx.frame.accumulator = JsValue::BigInt(Box::new(n.wrapping_add(1)));
} else {
let n = ctx.frame.accumulator.to_number()?;
ctx.frame.accumulator = number_to_jsvalue(n + 1.0);
}
Ok(DispatchAction::Continue)
}
#[inline(always)]
fn handle_inc_star(ctx: &mut DispatchContext, instr: &Instruction) -> StatorResult<DispatchAction> {
let Operand::FeedbackSlot(_slot) = *instr.operand(0) else {
return Err(err_bad_operand("IncStar", 0));
};
let Operand::Register(dst) = *instr.operand(1) else {
return Err(err_bad_operand("IncStar", 1));
};
if let JsValue::Smi(n) = ctx.frame.accumulator {
if let Some(result) = n.checked_add(1) {
let value = JsValue::Smi(result);
ctx.frame.accumulator = value.cheap_clone();
ctx.frame.write_reg(dst, value)?;
return Ok(DispatchAction::Continue);
}
ctx.frame.accumulator = JsValue::HeapNumber((n as f64) + 1.0);
} else if let JsValue::BigInt(n) = &ctx.frame.accumulator {
ctx.frame.accumulator = JsValue::BigInt(Box::new(n.wrapping_add(1)));
} else {
let n = ctx.frame.accumulator.to_number()?;
ctx.frame.accumulator = number_to_jsvalue(n + 1.0);
}
ctx.frame
.write_reg(dst, ctx.frame.accumulator.cheap_clone())?;
Ok(DispatchAction::Continue)
}
#[inline(always)]
fn handle_dec(ctx: &mut DispatchContext, _instr: &Instruction) -> StatorResult<DispatchAction> {
if let JsValue::Smi(n) = ctx.frame.accumulator {
if let Some(result) = n.checked_sub(1) {
ctx.frame.accumulator = JsValue::Smi(result);
return Ok(DispatchAction::Continue);
}
ctx.frame.accumulator = JsValue::HeapNumber((n as f64) - 1.0);
return Ok(DispatchAction::Continue);
}
if let JsValue::BigInt(n) = &ctx.frame.accumulator {
ctx.frame.accumulator = JsValue::BigInt(Box::new(n.wrapping_sub(1)));
} else {
let n = ctx.frame.accumulator.to_number()?;
ctx.frame.accumulator = number_to_jsvalue(n - 1.0);
}
Ok(DispatchAction::Continue)
}
#[inline(always)]
fn handle_test_equal(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(v) = *instr.operand(0) else {
return Err(err_bad_operand("TestEqual", 0));
};
let rhs = ctx.frame.read_reg(v)?;
let result = abstract_eq(&ctx.frame.accumulator, rhs);
ctx.frame.accumulator = JsValue::Boolean(result);
Ok(DispatchAction::Continue)
}
fn handle_test_not_equal(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(v) = *instr.operand(0) else {
return Err(err_bad_operand("TestNotEqual", 0));
};
let rhs = ctx.frame.read_reg(v)?;
let result = !abstract_eq(&ctx.frame.accumulator, rhs);
ctx.frame.accumulator = JsValue::Boolean(result);
Ok(DispatchAction::Continue)
}
fn handle_test_equal_strict(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(v) = *instr.operand(0) else {
return Err(err_bad_operand("TestEqualStrict", 0));
};
let rhs = ctx.frame.read_reg(v)?;
if let JsValue::Smi(a) = ctx.frame.accumulator
&& let JsValue::Smi(b) = rhs
{
ctx.frame.accumulator = JsValue::Boolean(a == *b);
return Ok(DispatchAction::Continue);
}
let rhs = rhs.clone();
let result = strict_eq(&ctx.frame.accumulator, &rhs);
ctx.frame.accumulator = JsValue::Boolean(result);
Ok(DispatchAction::Continue)
}
#[inline(always)]
fn handle_test_less_than(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(v) = *instr.operand(0) else {
return Err(err_bad_operand("TestLessThan", 0));
};
let rhs = ctx.frame.read_reg(v)?;
if let JsValue::Smi(a) = ctx.frame.accumulator
&& let JsValue::Smi(b) = rhs
{
ctx.frame.accumulator = JsValue::Boolean(a < *b);
return Ok(DispatchAction::Continue);
}
let rhs = rhs.clone();
let result = js_less_than(&ctx.frame.accumulator, &rhs)?;
ctx.frame.accumulator = JsValue::Boolean(result);
Ok(DispatchAction::Continue)
}
#[inline(always)]
fn handle_test_less_than_jump(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(v) = *instr.operand(0) else {
return Err(err_bad_operand("TestLessThanJump", 0));
};
let Operand::FeedbackSlot(_slot) = *instr.operand(1) else {
return Err(err_bad_operand("TestLessThanJump", 1));
};
let Operand::JumpOffset(_delta) = *instr.operand(2) else {
return Err(err_bad_operand("TestLessThanJump", 2));
};
let Operand::Flag(is_true_flag) = *instr.operand(3) else {
return Err(err_bad_operand("TestLessThanJump", 3));
};
let rhs = ctx.frame.read_reg(v)?;
let comparison = if let JsValue::Smi(a) = ctx.frame.accumulator
&& let JsValue::Smi(b) = rhs
{
a < *b
} else {
let rhs = rhs.clone();
js_less_than(&ctx.frame.accumulator, &rhs)?
};
ctx.frame.accumulator = JsValue::Boolean(comparison);
let should_jump = if is_true_flag == 0 {
!comparison
} else {
comparison
};
if should_jump {
ctx.frame.pc = resolve_cached_jump(ctx, "TestLessThanJump")?;
}
Ok(DispatchAction::Continue)
}
#[inline(always)]
fn handle_test_greater_than_jump(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(v) = *instr.operand(0) else {
return Err(err_bad_operand("TestGreaterThanJump", 0));
};
let Operand::FeedbackSlot(_slot) = *instr.operand(1) else {
return Err(err_bad_operand("TestGreaterThanJump", 1));
};
let Operand::JumpOffset(_delta) = *instr.operand(2) else {
return Err(err_bad_operand("TestGreaterThanJump", 2));
};
let Operand::Flag(is_true_flag) = *instr.operand(3) else {
return Err(err_bad_operand("TestGreaterThanJump", 3));
};
let rhs = ctx.frame.read_reg(v)?;
let comparison = if let JsValue::Smi(a) = ctx.frame.accumulator
&& let JsValue::Smi(b) = rhs
{
a > *b
} else {
let rhs = rhs.clone();
JsValue::abstract_relational_comparison(&rhs, &ctx.frame.accumulator, false)?
.unwrap_or(false)
};
ctx.frame.accumulator = JsValue::Boolean(comparison);
let should_jump = if is_true_flag == 0 {
!comparison
} else {
comparison
};
if should_jump {
ctx.frame.pc = resolve_cached_jump(ctx, "TestGreaterThanJump")?;
}
Ok(DispatchAction::Continue)
}
#[inline(always)]
fn handle_test_equal_jump(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(v) = *instr.operand(0) else {
return Err(err_bad_operand("TestEqualJump", 0));
};
let Operand::FeedbackSlot(_slot) = *instr.operand(1) else {
return Err(err_bad_operand("TestEqualJump", 1));
};
let Operand::JumpOffset(_delta) = *instr.operand(2) else {
return Err(err_bad_operand("TestEqualJump", 2));
};
let Operand::Flag(is_true_flag) = *instr.operand(3) else {
return Err(err_bad_operand("TestEqualJump", 3));
};
let rhs = ctx.frame.read_reg(v)?;
let comparison = abstract_eq(&ctx.frame.accumulator, rhs);
ctx.frame.accumulator = JsValue::Boolean(comparison);
let should_jump = if is_true_flag == 0 {
!comparison
} else {
comparison
};
if should_jump {
ctx.frame.pc = resolve_cached_jump(ctx, "TestEqualJump")?;
}
Ok(DispatchAction::Continue)
}
#[inline(always)]
fn handle_test_not_equal_jump(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(v) = *instr.operand(0) else {
return Err(err_bad_operand("TestNotEqualJump", 0));
};
let Operand::FeedbackSlot(_slot) = *instr.operand(1) else {
return Err(err_bad_operand("TestNotEqualJump", 1));
};
let Operand::JumpOffset(_delta) = *instr.operand(2) else {
return Err(err_bad_operand("TestNotEqualJump", 2));
};
let Operand::Flag(is_true_flag) = *instr.operand(3) else {
return Err(err_bad_operand("TestNotEqualJump", 3));
};
let rhs = ctx.frame.read_reg(v)?;
let comparison = !abstract_eq(&ctx.frame.accumulator, rhs);
ctx.frame.accumulator = JsValue::Boolean(comparison);
let should_jump = if is_true_flag == 0 {
!comparison
} else {
comparison
};
if should_jump {
ctx.frame.pc = resolve_cached_jump(ctx, "TestNotEqualJump")?;
}
Ok(DispatchAction::Continue)
}
#[inline(always)]
fn handle_test_equal_strict_jump(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(v) = *instr.operand(0) else {
return Err(err_bad_operand("TestEqualStrictJump", 0));
};
let Operand::FeedbackSlot(_slot) = *instr.operand(1) else {
return Err(err_bad_operand("TestEqualStrictJump", 1));
};
let Operand::JumpOffset(_delta) = *instr.operand(2) else {
return Err(err_bad_operand("TestEqualStrictJump", 2));
};
let Operand::Flag(is_true_flag) = *instr.operand(3) else {
return Err(err_bad_operand("TestEqualStrictJump", 3));
};
let rhs = ctx.frame.read_reg(v)?;
let comparison = if let JsValue::Smi(a) = ctx.frame.accumulator
&& let JsValue::Smi(b) = rhs
{
a == *b
} else {
let rhs = rhs.clone();
strict_eq(&ctx.frame.accumulator, &rhs)
};
ctx.frame.accumulator = JsValue::Boolean(comparison);
let should_jump = if is_true_flag == 0 {
!comparison
} else {
comparison
};
if should_jump {
ctx.frame.pc = resolve_cached_jump(ctx, "TestEqualStrictJump")?;
}
Ok(DispatchAction::Continue)
}
#[inline(always)]
fn handle_test_less_than_or_equal_jump(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(v) = *instr.operand(0) else {
return Err(err_bad_operand("TestLessThanOrEqualJump", 0));
};
let Operand::FeedbackSlot(_slot) = *instr.operand(1) else {
return Err(err_bad_operand("TestLessThanOrEqualJump", 1));
};
let Operand::JumpOffset(_delta) = *instr.operand(2) else {
return Err(err_bad_operand("TestLessThanOrEqualJump", 2));
};
let Operand::Flag(is_true_flag) = *instr.operand(3) else {
return Err(err_bad_operand("TestLessThanOrEqualJump", 3));
};
let rhs = ctx.frame.read_reg(v)?;
let comparison = if let JsValue::Smi(a) = ctx.frame.accumulator
&& let JsValue::Smi(b) = rhs
{
a <= *b
} else {
let rhs = rhs.clone();
JsValue::abstract_relational_comparison(&rhs, &ctx.frame.accumulator, false)?
.map(|result| !result)
.unwrap_or(false)
};
ctx.frame.accumulator = JsValue::Boolean(comparison);
let should_jump = if is_true_flag == 0 {
!comparison
} else {
comparison
};
if should_jump {
ctx.frame.pc = resolve_cached_jump(ctx, "TestLessThanOrEqualJump")?;
}
Ok(DispatchAction::Continue)
}
#[inline(always)]
fn handle_test_greater_than_or_equal_jump(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(v) = *instr.operand(0) else {
return Err(err_bad_operand("TestGreaterThanOrEqualJump", 0));
};
let Operand::FeedbackSlot(_slot) = *instr.operand(1) else {
return Err(err_bad_operand("TestGreaterThanOrEqualJump", 1));
};
let Operand::JumpOffset(_delta) = *instr.operand(2) else {
return Err(err_bad_operand("TestGreaterThanOrEqualJump", 2));
};
let Operand::Flag(is_true_flag) = *instr.operand(3) else {
return Err(err_bad_operand("TestGreaterThanOrEqualJump", 3));
};
let rhs = ctx.frame.read_reg(v)?;
let comparison = if let JsValue::Smi(a) = ctx.frame.accumulator
&& let JsValue::Smi(b) = rhs
{
a >= *b
} else {
let rhs = rhs.clone();
JsValue::abstract_relational_comparison(&ctx.frame.accumulator, &rhs, true)?
.map(|result| !result)
.unwrap_or(false)
};
ctx.frame.accumulator = JsValue::Boolean(comparison);
let should_jump = if is_true_flag == 0 {
!comparison
} else {
comparison
};
if should_jump {
ctx.frame.pc = resolve_cached_jump(ctx, "TestGreaterThanOrEqualJump")?;
}
Ok(DispatchAction::Continue)
}
#[inline(always)]
fn handle_sub_smi_star(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Immediate(imm) = *instr.operand(0) else {
return Err(err_bad_operand("SubSmiStar", 0));
};
let Operand::FeedbackSlot(_slot) = *instr.operand(1) else {
return Err(err_bad_operand("SubSmiStar", 1));
};
let Operand::Register(dst) = *instr.operand(2) else {
return Err(err_bad_operand("SubSmiStar", 2));
};
if let JsValue::Smi(n) = &ctx.frame.accumulator
&& let Some(result) = n.checked_sub(imm)
{
let value = JsValue::Smi(result);
ctx.frame.accumulator = value.cheap_clone();
ctx.frame.write_reg(dst, value)?;
return Ok(DispatchAction::Continue);
}
if let JsValue::BigInt(n) = &ctx.frame.accumulator {
ctx.frame.accumulator = JsValue::BigInt(Box::new(n.wrapping_sub(i128::from(imm))));
} else {
let lhs_n = ctx.frame.accumulator.to_number()?;
ctx.frame.accumulator = number_to_jsvalue(lhs_n - imm as f64);
}
ctx.frame
.write_reg(dst, ctx.frame.accumulator.cheap_clone())?;
Ok(DispatchAction::Continue)
}
#[inline(always)]
fn handle_test_greater_than(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(v) = *instr.operand(0) else {
return Err(err_bad_operand("TestGreaterThan", 0));
};
let rhs = ctx.frame.read_reg(v)?;
if let JsValue::Smi(a) = ctx.frame.accumulator
&& let JsValue::Smi(b) = rhs
{
ctx.frame.accumulator = JsValue::Boolean(a > *b);
return Ok(DispatchAction::Continue);
}
let rhs = rhs.clone();
let result = JsValue::abstract_relational_comparison(&rhs, &ctx.frame.accumulator, false)?
.unwrap_or(false);
ctx.frame.accumulator = JsValue::Boolean(result);
Ok(DispatchAction::Continue)
}
fn handle_test_less_than_or_equal(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(v) = *instr.operand(0) else {
return Err(err_bad_operand("TestLessThanOrEqual", 0));
};
let rhs = ctx.frame.read_reg(v)?;
if let JsValue::Smi(a) = ctx.frame.accumulator
&& let JsValue::Smi(b) = rhs
{
ctx.frame.accumulator = JsValue::Boolean(a <= *b);
return Ok(DispatchAction::Continue);
}
let rhs = rhs.clone();
let result = JsValue::abstract_relational_comparison(&rhs, &ctx.frame.accumulator, false)?
.map(|r| !r)
.unwrap_or(false);
ctx.frame.accumulator = JsValue::Boolean(result);
Ok(DispatchAction::Continue)
}
fn handle_test_greater_than_or_equal(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(v) = *instr.operand(0) else {
return Err(err_bad_operand("TestGreaterThanOrEqual", 0));
};
let rhs = ctx.frame.read_reg(v)?;
if let JsValue::Smi(a) = ctx.frame.accumulator
&& let JsValue::Smi(b) = rhs
{
ctx.frame.accumulator = JsValue::Boolean(a >= *b);
return Ok(DispatchAction::Continue);
}
let rhs = rhs.clone();
let result = JsValue::abstract_relational_comparison(&ctx.frame.accumulator, &rhs, true)?
.map(|r| !r)
.unwrap_or(false);
ctx.frame.accumulator = JsValue::Boolean(result);
Ok(DispatchAction::Continue)
}
fn handle_test_null(
ctx: &mut DispatchContext,
_instr: &Instruction,
) -> StatorResult<DispatchAction> {
ctx.frame.accumulator = JsValue::Boolean(ctx.frame.accumulator.is_null());
Ok(DispatchAction::Continue)
}
fn handle_test_undefined(
ctx: &mut DispatchContext,
_instr: &Instruction,
) -> StatorResult<DispatchAction> {
ctx.frame.accumulator = JsValue::Boolean(ctx.frame.accumulator.is_undefined());
Ok(DispatchAction::Continue)
}
fn handle_logical_not(
ctx: &mut DispatchContext,
_instr: &Instruction,
) -> StatorResult<DispatchAction> {
ctx.frame.accumulator = JsValue::Boolean(!ctx.frame.accumulator.to_boolean());
Ok(DispatchAction::Continue)
}
fn handle_to_boolean_logical_not(
ctx: &mut DispatchContext,
_instr: &Instruction,
) -> StatorResult<DispatchAction> {
ctx.frame.accumulator = JsValue::Boolean(!ctx.frame.accumulator.to_boolean());
Ok(DispatchAction::Continue)
}
#[inline(always)]
fn handle_jump(ctx: &mut DispatchContext, instr: &Instruction) -> StatorResult<DispatchAction> {
let Operand::JumpOffset(_delta) = *instr.operand(0) else {
return Err(err_bad_operand("Jump", 0));
};
ctx.frame.pc = resolve_cached_jump(ctx, "Jump")?;
Ok(DispatchAction::Continue)
}
fn handle_jump_loop(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::JumpOffset(_delta) = *instr.operand(0) else {
return Err(err_bad_operand("JumpLoop", 0));
};
ctx.frame.pc = resolve_cached_jump(ctx, "JumpLoop")?;
ctx.frame.osr_loop_count = ctx.frame.osr_loop_count.saturating_add(1);
if ctx.frame.osr_loop_count >= OSR_LOOP_THRESHOLD {
if !ctx.frame.bytecode_array.has_any_jit_code() {
maybe_compile_baseline(&ctx.frame.bytecode_array);
}
maybe_compile_maglev(&ctx.frame.bytecode_array);
if ctx.frame.osr_loop_count >= TURBOFAN_OSR_LOOP_THRESHOLD {
maybe_compile_turbofan(&ctx.frame.bytecode_array);
}
if let Some(jit_result) =
try_execute_best_jit(&ctx.frame.bytecode_array, &ctx.frame.call_args)
{
return Ok(DispatchAction::Return(jit_result?));
}
}
Ok(DispatchAction::Continue)
}
#[inline(always)]
fn handle_jump_if_true(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::JumpOffset(_delta) = *instr.operand(0) else {
return Err(err_bad_operand("JumpIfTrue", 0));
};
if matches!(ctx.frame.accumulator, JsValue::Boolean(true)) {
ctx.frame.pc = resolve_cached_jump(ctx, "JumpIfTrue")?;
}
Ok(DispatchAction::Continue)
}
#[inline(always)]
fn handle_jump_if_false(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::JumpOffset(_delta) = *instr.operand(0) else {
return Err(err_bad_operand("JumpIfFalse", 0));
};
if matches!(ctx.frame.accumulator, JsValue::Boolean(false)) {
ctx.frame.pc = resolve_cached_jump(ctx, "JumpIfFalse")?;
}
Ok(DispatchAction::Continue)
}
#[inline]
fn handle_jump_if_to_boolean_true(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::JumpOffset(_delta) = *instr.operand(0) else {
return Err(err_bad_operand("JumpIfToBooleanTrue", 0));
};
if ctx.frame.accumulator.to_boolean() {
ctx.frame.pc = resolve_cached_jump(ctx, "JumpIfToBooleanTrue")?;
}
Ok(DispatchAction::Continue)
}
#[inline]
fn handle_jump_if_to_boolean_false(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::JumpOffset(_delta) = *instr.operand(0) else {
return Err(err_bad_operand("JumpIfToBooleanFalse", 0));
};
if !ctx.frame.accumulator.to_boolean() {
ctx.frame.pc = resolve_cached_jump(ctx, "JumpIfToBooleanFalse")?;
}
Ok(DispatchAction::Continue)
}
fn handle_jump_if_null(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::JumpOffset(_delta) = *instr.operand(0) else {
return Err(err_bad_operand("JumpIfNull", 0));
};
if ctx.frame.accumulator.is_null() {
ctx.frame.pc = resolve_cached_jump(ctx, "JumpIfNull")?;
}
Ok(DispatchAction::Continue)
}
fn handle_jump_if_not_null(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::JumpOffset(_delta) = *instr.operand(0) else {
return Err(err_bad_operand("JumpIfNotNull", 0));
};
if !ctx.frame.accumulator.is_null() {
ctx.frame.pc = resolve_cached_jump(ctx, "JumpIfNotNull")?;
}
Ok(DispatchAction::Continue)
}
fn handle_jump_if_undefined(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::JumpOffset(_delta) = *instr.operand(0) else {
return Err(err_bad_operand("JumpIfUndefined", 0));
};
if ctx.frame.accumulator.is_undefined() {
ctx.frame.pc = resolve_cached_jump(ctx, "JumpIfUndefined")?;
}
Ok(DispatchAction::Continue)
}
fn handle_jump_if_not_undefined(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::JumpOffset(_delta) = *instr.operand(0) else {
return Err(err_bad_operand("JumpIfNotUndefined", 0));
};
if !ctx.frame.accumulator.is_undefined() {
ctx.frame.pc = resolve_cached_jump(ctx, "JumpIfNotUndefined")?;
}
Ok(DispatchAction::Continue)
}
fn handle_jump_if_undefined_or_null(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::JumpOffset(_delta) = *instr.operand(0) else {
return Err(err_bad_operand("JumpIfUndefinedOrNull", 0));
};
if ctx.frame.accumulator.is_nullish() {
ctx.frame.pc = resolve_cached_jump(ctx, "JumpIfUndefinedOrNull")?;
}
Ok(DispatchAction::Continue)
}
#[inline]
fn handle_return(ctx: &mut DispatchContext, _instr: &Instruction) -> StatorResult<DispatchAction> {
Ok(DispatchAction::Return(ctx.frame.accumulator.cheap_clone()))
}
#[cold]
fn handle_create_closure(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::ConstantPoolIdx(idx) = *instr.operand(0) else {
return Err(err_bad_operand("CreateClosure", 0));
};
let entry = ctx.frame.bytecode_array.get_constant(idx).ok_or_else(|| {
StatorError::Internal(format!(
"CreateClosure: constant pool index {idx} out of bounds"
))
})?;
let ConstantPoolEntry::Function(ba) = entry else {
return Err(StatorError::Internal(
"CreateClosure: constant pool entry is not a Function".into(),
));
};
let closure_ctx = match &ctx.frame.context {
Some(JsValue::Context(c)) => Some(Rc::clone(c)),
_ => None,
};
let func_rc = Rc::new(ba.clone_for_closure(closure_ctx));
let is_arrow = matches!(instr.operand_at(2), Some(Operand::Flag(1)));
if is_arrow {
let lexical_this = ctx
.frame
.global_env
.borrow()
.get_this()
.cloned()
.unwrap_or(JsValue::Undefined);
fn_props_set(&func_rc, ".this".to_string(), lexical_this);
if !matches!(ctx.frame.new_target, JsValue::Undefined) {
fn_props_set(
&func_rc,
".new_target".to_string(),
ctx.frame.new_target.clone(),
);
}
}
if !is_arrow && func_rc.is_generator() {
let func_val = JsValue::Function(Rc::clone(&func_rc));
let mut proto = PropertyMap::new();
proto.insert("constructor".to_string(), func_val);
if let Some(generator_proto) = super::default_generator_object_prototype() {
proto.insert("__proto__".to_string(), generator_proto);
}
fn_props_set(
&func_rc,
"prototype".to_string(),
JsValue::PlainObject(Rc::new(RefCell::new(proto))),
);
}
ctx.frame.accumulator = JsValue::Function(func_rc);
Ok(DispatchAction::Continue)
}
fn handle_call_any_receiver(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(callee_v) = *instr.operand(0) else {
return Err(err_bad_operand("CallAnyReceiver", 0));
};
let Operand::Register(args_start_v) = *instr.operand(1) else {
return Err(err_bad_operand("CallAnyReceiver", 1));
};
let Operand::RegisterCount(arg_count) = *instr.operand(2) else {
return Err(err_bad_operand("CallAnyReceiver", 2));
};
let callee = ctx.frame.read_reg(callee_v)?.cheap_clone();
match callee {
JsValue::Function(ba) => {
if ba.is_generator() {
let state = GeneratorState::new(Rc::clone(&ba));
super::init_generator_state_prototype(&state, &ba);
ctx.frame.accumulator = JsValue::Generator(state);
} else if ba.is_async() {
let args = collect_args(ctx.frame, args_start_v, arg_count)?;
ctx.frame.accumulator = Interpreter::run_async_function(Rc::clone(&ba), args)?;
} else {
if arg_count == 0
&& ba.bytecode_count() <= INLINE_BYTECODE_THRESHOLD
&& !ba.has_exception_handler()
&& let Some(result) =
try_inline_small_function(ba.as_ref(), &[], &ctx.frame.global_env)
{
ctx.frame.accumulator = result;
return Ok(DispatchAction::Continue);
}
let args = collect_args(ctx.frame, args_start_v, arg_count)?;
let count = ba.increment_invocation_count();
if count >= TIERING_THRESHOLD {
if !ba.has_any_jit_code() {
maybe_compile_baseline(&ba);
}
if count >= MAGLEV_TIERING_THRESHOLD {
maybe_compile_maglev(&ba);
}
if count >= TURBOFAN_TIERING_THRESHOLD {
maybe_compile_turbofan(&ba);
}
if let Some(jit_result) = try_execute_best_jit(&ba, &args) {
ctx.frame.accumulator = jit_result?;
return Ok(DispatchAction::Continue);
}
}
let callee_val = JsValue::Function(Rc::clone(&ba));
let mut callee_frame =
acquire_frame(Rc::clone(&ba), args, Rc::clone(&ctx.frame.global_env));
restore_closure_context(&mut callee_frame, &ba);
if ba.is_arrow() && ba.has_fn_props() {
callee_frame.new_target = fn_props_get(&ba, ".new_target");
}
populate_self_name(&mut callee_frame, &ba, &callee_val);
push_call_frame("<anonymous>")?;
let gen_before = ctx.frame.cache_generation;
let result = run_callee_pooled(callee_frame);
pop_call_frame();
if gen_before != ctx.frame.global_env.borrow().generation() {
ctx.frame.global_cache_invalidate();
}
ctx.frame.accumulator = result?;
}
}
JsValue::NativeFunction(f) => {
let args = collect_args(ctx.frame, args_start_v, arg_count)?;
ctx.frame.accumulator = f(args.into_vec())?;
ctx.frame.global_cache_invalidate();
}
JsValue::PlainObject(ref map) => {
let call_val = map.borrow().get("__call__").cloned();
match call_val {
Some(JsValue::NativeFunction(f)) => {
let args = collect_args(ctx.frame, args_start_v, arg_count)?;
ctx.frame.accumulator = f(args.into_vec())?;
ctx.frame.global_cache_invalidate();
}
Some(JsValue::Function(ba)) => {
let args = collect_args(ctx.frame, args_start_v, arg_count)?;
call_plain_object_function(ctx, &ba, map, args)?;
}
_ => {
return Err(StatorError::TypeError(
"CallAnyReceiver: callee is not a function (got PlainObject)".to_string(),
));
}
}
}
other => {
return Err(StatorError::TypeError(format!(
"CallAnyReceiver: callee is not a function (got {other:?})"
)));
}
}
Ok(DispatchAction::Continue)
}
fn handle_tail_call(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
if !ctx.frame.bytecode_array.is_strict() {
handle_call_any_receiver(ctx, instr)?;
return Ok(DispatchAction::Return(ctx.frame.accumulator.cheap_clone()));
}
let Operand::Register(callee_v) = *instr.operand(0) else {
return Err(err_bad_operand("TailCall", 0));
};
let Operand::Register(args_start_v) = *instr.operand(1) else {
return Err(err_bad_operand("TailCall", 1));
};
let Operand::RegisterCount(arg_count) = *instr.operand(2) else {
return Err(err_bad_operand("TailCall", 2));
};
let callee = ctx.frame.read_reg(callee_v)?.cheap_clone();
match callee {
JsValue::Function(ba) => {
if ba.is_generator() || ba.is_async() {
let args = collect_args(ctx.frame, args_start_v, arg_count)?;
if ba.is_generator() {
let state = GeneratorState::new(Rc::clone(&ba));
super::init_generator_state_prototype(&state, &ba);
ctx.frame.accumulator = JsValue::Generator(state);
} else {
ctx.frame.accumulator = Interpreter::run_async_function(Rc::clone(&ba), args)?;
}
} else {
let args = collect_args(ctx.frame, args_start_v, arg_count)?;
let count = ba.increment_invocation_count();
if count >= TIERING_THRESHOLD {
if !ba.has_any_jit_code() {
maybe_compile_baseline(&ba);
}
if count >= MAGLEV_TIERING_THRESHOLD {
maybe_compile_maglev(&ba);
}
if count >= TURBOFAN_TIERING_THRESHOLD {
maybe_compile_turbofan(&ba);
}
}
let param_count = ba.parameter_count() as usize;
let frame_size = ba.frame_size() as usize;
let total_regs = param_count + frame_size;
let inherited_new_target = ctx.frame.new_target.clone();
ctx.frame.bytecode_array = Rc::clone(&ba);
ctx.frame.call_args = args;
ctx.frame.registers.clear();
ctx.frame.registers.resize(total_regs, JsValue::Undefined);
for (i, arg) in ctx
.frame
.call_args
.iter()
.cloned()
.enumerate()
.take(param_count)
{
ctx.frame.registers[i] = arg;
}
ctx.frame.accumulator = JsValue::Undefined;
ctx.frame.pc = 0;
ctx.frame.context = None;
ctx.frame.suspend_result = None;
ctx.frame.generator_state = None;
ctx.frame.osr_loop_count = 0;
ctx.frame.pending_message = JsValue::Undefined;
ctx.frame.new_target = if ba.is_arrow() {
inherited_new_target
} else {
JsValue::Undefined
};
if !ba.is_arrow() && ba.is_strict() {
ctx.frame
.global_env
.borrow_mut()
.set_this(JsValue::Undefined);
}
restore_closure_context(ctx.frame, &ba);
if ba.self_name_register().is_some() {
populate_self_name(ctx.frame, &ba, &JsValue::Function(Rc::clone(&ba)));
}
ctx.frame.string_cache = None;
ctx.frame.mono_load_cache = None;
ctx.frame.poly_load_cache = None;
ctx.frame.mega_load_ic = None;
ctx.frame.proto_load_ic = None;
ctx.frame.mega_store_ic = None;
ctx.frame.global_ic_reset();
ctx.frame.global_cache_invalidate();
return Ok(DispatchAction::TailCall);
}
}
JsValue::NativeFunction(f) => {
let args = collect_args(ctx.frame, args_start_v, arg_count)?;
ctx.frame.accumulator = f(args.into_vec())?;
ctx.frame.global_cache_invalidate();
}
JsValue::PlainObject(ref map) => {
if let Some(JsValue::NativeFunction(f)) = map.borrow().get("__call__").cloned() {
let args = collect_args(ctx.frame, args_start_v, arg_count)?;
ctx.frame.accumulator = f(args.into_vec())?;
ctx.frame.global_cache_invalidate();
} else {
return Err(StatorError::TypeError(
"TailCall: callee is not a function (got PlainObject)".to_string(),
));
}
}
other => {
return Err(StatorError::TypeError(format!(
"TailCall: callee is not a function (got {other:?})"
)));
}
}
Ok(DispatchAction::Continue)
}
fn handle_call_undefined_receiver0(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(callee_v) = *instr.operand(0) else {
return Err(err_bad_operand("CallUndefinedReceiver0", 0));
};
let callee = ctx.frame.read_reg(callee_v)?.cheap_clone();
match callee {
JsValue::Function(ba) => {
if ba.has_any_jit_code() && !ba.is_generator() && !ba.is_async() {
ba.increment_invocation_count();
let no_args: &[JsValue] = &[];
if let Some(jit_result) = try_execute_best_jit(&ba, no_args) {
ctx.frame.accumulator = jit_result?;
return Ok(DispatchAction::Continue);
}
}
if ba.bytecode_count() <= INLINE_BYTECODE_THRESHOLD
&& !ba.has_exception_handler()
&& !ba.is_generator()
&& !ba.is_async()
{
let no_args: &[JsValue] = &[];
if let Some(result) =
try_inline_small_function(ba.as_ref(), no_args, &ctx.frame.global_env)
{
ctx.frame.accumulator = result;
return Ok(DispatchAction::Continue);
}
}
if ba.is_generator() {
#[cold]
fn make_generator(ba: &Rc<BytecodeArray>) -> JsValue {
let state = GeneratorState::new(Rc::clone(ba));
super::init_generator_state_prototype(&state, ba);
JsValue::Generator(state)
}
ctx.frame.accumulator = make_generator(&ba);
} else if ba.is_async() {
#[cold]
fn run_async(ba: &Rc<BytecodeArray>) -> StatorResult<JsValue> {
Interpreter::run_async_function(Rc::clone(ba), CallArgs::new())
}
ctx.frame.accumulator = run_async(&ba)?;
} else {
let no_args: &[JsValue] = &[];
let count = ba.increment_invocation_count();
if count >= TIERING_THRESHOLD {
if !ba.has_any_jit_code() {
maybe_compile_baseline(&ba);
}
if count >= MAGLEV_TIERING_THRESHOLD {
maybe_compile_maglev(&ba);
}
if count >= TURBOFAN_TIERING_THRESHOLD {
maybe_compile_turbofan(&ba);
}
if let Some(jit_result) = try_execute_best_jit(&ba, no_args) {
ctx.frame.accumulator = jit_result?;
return Ok(DispatchAction::Continue);
}
}
let saved_this = if !ba.is_arrow() && ba.is_strict() {
let old = ctx.frame.global_env.borrow().get_this().cloned();
ctx.frame
.global_env
.borrow_mut()
.set_this(JsValue::Undefined);
old
} else {
None
};
let mut callee_frame = if ba.parameter_count() == 0 {
acquire_frame(
Rc::clone(&ba),
std::iter::empty(),
clone_shared_global_env(&ctx.frame.global_env),
)
} else {
acquire_frame(
Rc::clone(&ba),
CallArgs::new(),
clone_shared_global_env(&ctx.frame.global_env),
)
};
restore_closure_context(&mut callee_frame, &ba);
if ba.is_arrow() && ba.has_fn_props() {
callee_frame.new_target = fn_props_get(&ba, ".new_target");
}
if ba.self_name_register().is_some() {
populate_self_name(&mut callee_frame, &ba, &JsValue::Function(Rc::clone(&ba)));
}
push_call_frame("<anonymous>")?;
let gen_before = ctx.frame.cache_generation;
let result = run_callee_pooled(callee_frame);
pop_call_frame();
if gen_before != ctx.frame.global_env.borrow().generation() {
ctx.frame.global_cache_invalidate();
}
if !ba.is_arrow() && ba.is_strict() {
match saved_this {
Some(v) => {
ctx.frame.global_env.borrow_mut().set_this(v);
}
None => {
ctx.frame.global_env.borrow_mut().remove_this();
}
}
}
ctx.frame.accumulator = result?;
}
}
JsValue::NativeFunction(f) => {
ctx.frame.accumulator = f(CallArgs::new().into_vec())?;
ctx.frame.global_cache_invalidate();
}
JsValue::PlainObject(ref map) => {
if let Some(JsValue::NativeFunction(f)) = map.borrow().get("__call__").cloned() {
ctx.frame.accumulator = f(CallArgs::new().into_vec())?;
ctx.frame.global_cache_invalidate();
} else {
return Err(StatorError::TypeError(
"CallUndefinedReceiver0: callee is not a function (got PlainObject)"
.to_string(),
));
}
}
other => {
return Err(StatorError::TypeError(format!(
"CallUndefinedReceiver0: callee is not a function (got {other:?})"
)));
}
}
Ok(DispatchAction::Continue)
}
fn handle_call_undefined_receiver1(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(callee_v) = *instr.operand(0) else {
return Err(err_bad_operand("CallUndefinedReceiver1", 0));
};
let Operand::Register(arg1_v) = *instr.operand(1) else {
return Err(err_bad_operand("CallUndefinedReceiver1", 1));
};
let callee = ctx.frame.read_reg(callee_v)?.cheap_clone();
if fast_array_method_name(&callee).as_deref() == Some("push")
&& let Some(JsValue::Array(arr)) = fast_array_method_target(&callee)
{
let arg1 = ctx.frame.read_reg(arg1_v)?.cheap_clone();
let mut items = arr.borrow_mut();
items.push(arg1);
ctx.frame.accumulator = JsValue::Smi(items.len() as i32);
return Ok(DispatchAction::Continue);
}
match callee {
JsValue::Function(ba) => {
if ba.bytecode_count() <= INLINE_BYTECODE_THRESHOLD
&& !ba.has_exception_handler()
&& !ba.is_generator()
&& !ba.is_async()
{
let arg1 = ctx.frame.read_reg(arg1_v)?.cheap_clone();
let inline_args = [arg1.clone()];
if let Some(result) =
try_inline_small_function(ba.as_ref(), &inline_args, &ctx.frame.global_env)
{
ctx.frame.accumulator = result;
return Ok(DispatchAction::Continue);
}
}
if ba.is_generator() {
#[cold]
fn make_generator(ba: &Rc<BytecodeArray>) -> JsValue {
let state = GeneratorState::new(Rc::clone(ba));
super::init_generator_state_prototype(&state, ba);
JsValue::Generator(state)
}
ctx.frame.accumulator = make_generator(&ba);
} else if ba.is_async() {
#[cold]
fn run_async(ba: &Rc<BytecodeArray>, args: CallArgs) -> StatorResult<JsValue> {
Interpreter::run_async_function(Rc::clone(ba), args)
}
let arg1 = ctx.frame.read_reg(arg1_v)?.cheap_clone();
let args: CallArgs = smallvec![arg1];
ctx.frame.accumulator = run_async(&ba, args)?;
} else {
let arg1 = ctx.frame.read_reg(arg1_v)?.cheap_clone();
let inline_args = [arg1.clone()];
let count = ba.increment_invocation_count();
if count >= TIERING_THRESHOLD {
if !ba.has_any_jit_code() {
maybe_compile_baseline(&ba);
}
if count >= MAGLEV_TIERING_THRESHOLD {
maybe_compile_maglev(&ba);
}
if count >= TURBOFAN_TIERING_THRESHOLD {
maybe_compile_turbofan(&ba);
}
if let Some(jit_result) = try_execute_best_jit(&ba, &inline_args) {
ctx.frame.accumulator = jit_result?;
return Ok(DispatchAction::Continue);
}
}
let saved_this = if !ba.is_arrow() && ba.is_strict() {
let old = ctx.frame.global_env.borrow().get_this().cloned();
ctx.frame
.global_env
.borrow_mut()
.set_this(JsValue::Undefined);
old
} else {
None
};
let args: CallArgs = smallvec![arg1];
let mut callee_frame = acquire_frame(
Rc::clone(&ba),
args,
clone_shared_global_env(&ctx.frame.global_env),
);
restore_closure_context(&mut callee_frame, &ba);
if ba.is_arrow() && ba.has_fn_props() {
callee_frame.new_target = fn_props_get(&ba, ".new_target");
}
if ba.self_name_register().is_some() {
populate_self_name(&mut callee_frame, &ba, &JsValue::Function(Rc::clone(&ba)));
}
push_call_frame("<anonymous>")?;
let gen_before = ctx.frame.cache_generation;
let result = run_callee_pooled(callee_frame);
pop_call_frame();
if gen_before != ctx.frame.global_env.borrow().generation() {
ctx.frame.global_cache_invalidate();
}
if !ba.is_arrow() && ba.is_strict() {
match saved_this {
Some(v) => {
ctx.frame.global_env.borrow_mut().set_this(v);
}
None => {
ctx.frame.global_env.borrow_mut().remove_this();
}
}
}
ctx.frame.accumulator = result?;
}
}
JsValue::NativeFunction(f) => {
let arg1 = ctx.frame.read_reg(arg1_v)?.cheap_clone();
let args: CallArgs = smallvec![arg1];
ctx.frame.accumulator = f(args.into_vec())?;
ctx.frame.global_cache_invalidate();
}
JsValue::PlainObject(ref map) => {
if let Some(JsValue::NativeFunction(f)) = map.borrow().get("__call__").cloned() {
let arg1 = ctx.frame.read_reg(arg1_v)?.cheap_clone();
let args: CallArgs = smallvec![arg1];
ctx.frame.accumulator = f(args.into_vec())?;
ctx.frame.global_cache_invalidate();
} else {
return Err(StatorError::TypeError(
"CallUndefinedReceiver1: callee is not a function (got PlainObject)"
.to_string(),
));
}
}
other => {
return Err(StatorError::TypeError(format!(
"CallUndefinedReceiver1: callee is not a function (got {other:?})"
)));
}
}
Ok(DispatchAction::Continue)
}
fn handle_call_undefined_receiver2(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(callee_v) = *instr.operand(0) else {
return Err(err_bad_operand("CallUndefinedReceiver2", 0));
};
let Operand::Register(arg1_v) = *instr.operand(1) else {
return Err(err_bad_operand("CallUndefinedReceiver2", 1));
};
let Operand::Register(arg2_v) = *instr.operand(2) else {
return Err(err_bad_operand("CallUndefinedReceiver2", 2));
};
let callee = ctx.frame.read_reg(callee_v)?.cheap_clone();
match callee {
JsValue::Function(ba) => {
if ba.bytecode_count() <= INLINE_BYTECODE_THRESHOLD
&& !ba.has_exception_handler()
&& !ba.is_generator()
&& !ba.is_async()
{
let arg1 = ctx.frame.read_reg(arg1_v)?.cheap_clone();
let arg2 = ctx.frame.read_reg(arg2_v)?.cheap_clone();
let inline_args = [arg1, arg2];
if let Some(result) =
try_inline_small_function(ba.as_ref(), &inline_args, &ctx.frame.global_env)
{
ctx.frame.accumulator = result;
return Ok(DispatchAction::Continue);
}
}
if ba.is_generator() {
#[cold]
fn make_generator(ba: &Rc<BytecodeArray>) -> JsValue {
let state = GeneratorState::new(Rc::clone(ba));
super::init_generator_state_prototype(&state, ba);
JsValue::Generator(state)
}
ctx.frame.accumulator = make_generator(&ba);
} else if ba.is_async() {
#[cold]
fn run_async(ba: &Rc<BytecodeArray>, args: CallArgs) -> StatorResult<JsValue> {
Interpreter::run_async_function(Rc::clone(ba), args)
}
let arg1 = ctx.frame.read_reg(arg1_v)?.cheap_clone();
let arg2 = ctx.frame.read_reg(arg2_v)?.cheap_clone();
let args: CallArgs = smallvec![arg1, arg2];
ctx.frame.accumulator = run_async(&ba, args)?;
} else {
let arg1 = ctx.frame.read_reg(arg1_v)?.cheap_clone();
let arg2 = ctx.frame.read_reg(arg2_v)?.cheap_clone();
let args: CallArgs = smallvec![arg1, arg2];
let count = ba.increment_invocation_count();
if count >= TIERING_THRESHOLD && !ba.has_any_jit_code() {
maybe_compile_baseline(&ba);
}
if count >= MAGLEV_TIERING_THRESHOLD {
maybe_compile_maglev(&ba);
}
if count >= TURBOFAN_TIERING_THRESHOLD {
maybe_compile_turbofan(&ba);
}
let mut tried_jit = false;
if let Some(jit_result) = try_execute_best_jit(&ba, &args) {
ctx.frame.accumulator = jit_result?;
tried_jit = true;
}
if !tried_jit {
let saved_this = if !ba.is_arrow() && ba.is_strict() {
let old = ctx.frame.global_env.borrow().get_this().cloned();
ctx.frame
.global_env
.borrow_mut()
.set_this(JsValue::Undefined);
old
} else {
None
};
let mut callee_frame =
acquire_frame(Rc::clone(&ba), args, Rc::clone(&ctx.frame.global_env));
restore_closure_context(&mut callee_frame, &ba);
if ba.is_arrow() && ba.has_fn_props() {
callee_frame.new_target = fn_props_get(&ba, ".new_target");
}
if ba.self_name_register().is_some() {
populate_self_name(
&mut callee_frame,
&ba,
&JsValue::Function(Rc::clone(&ba)),
);
}
push_call_frame("<anonymous>")?;
let gen_before = ctx.frame.cache_generation;
let result = run_callee_pooled(callee_frame);
pop_call_frame();
if gen_before != ctx.frame.global_env.borrow().generation() {
ctx.frame.global_cache_invalidate();
}
if !ba.is_arrow() && ba.is_strict() {
match saved_this {
Some(v) => {
ctx.frame.global_env.borrow_mut().set_this(v);
}
None => {
ctx.frame.global_env.borrow_mut().remove_this();
}
}
}
ctx.frame.accumulator = result?;
}
}
}
JsValue::NativeFunction(f) => {
let arg1 = ctx.frame.read_reg(arg1_v)?.cheap_clone();
let arg2 = ctx.frame.read_reg(arg2_v)?.cheap_clone();
let args: CallArgs = smallvec![arg1, arg2];
ctx.frame.accumulator = f(args.into_vec())?;
ctx.frame.global_cache_invalidate();
}
JsValue::PlainObject(ref map) => {
if let Some(JsValue::NativeFunction(f)) = map.borrow().get("__call__").cloned() {
let arg1 = ctx.frame.read_reg(arg1_v)?.cheap_clone();
let arg2 = ctx.frame.read_reg(arg2_v)?.cheap_clone();
let args: CallArgs = smallvec![arg1, arg2];
ctx.frame.accumulator = f(args.into_vec())?;
ctx.frame.global_cache_invalidate();
} else {
return Err(StatorError::TypeError(
"CallUndefinedReceiver2: callee is not a function (got PlainObject)"
.to_string(),
));
}
}
other => {
return Err(StatorError::TypeError(format!(
"CallUndefinedReceiver2: callee is not a function (got {other:?})"
)));
}
}
Ok(DispatchAction::Continue)
}
fn handle_call_property(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(callee_v) = *instr.operand(0) else {
return Err(err_bad_operand("CallProperty", 0));
};
let Operand::Register(recv_v) = *instr.operand(1) else {
return Err(err_bad_operand("CallProperty", 1));
};
let Operand::RegisterCount(arg_count) = *instr.operand(2) else {
return Err(err_bad_operand("CallProperty", 2));
};
if arg_count == 1 {
let callee_ref = ctx.frame.read_reg(callee_v)?;
if let JsValue::PlainObject(callee_map) = callee_ref {
let is_push = callee_map
.borrow()
.get(FAST_ARRAY_METHOD_KEY)
.is_some_and(|v| matches!(&v, JsValue::String(s) if s.as_ref() == "push"));
if is_push {
let this_val = ctx.frame.read_reg(recv_v)?;
if let JsValue::Array(arr) = this_val {
let arr = Rc::clone(arr);
let callee_flat = ctx.frame.reg_index(callee_v)?;
let arg = ctx.frame.registers[callee_flat + 1].clone();
let mut items = arr.borrow_mut();
if items.capacity() == 0 {
items.reserve(1024);
}
items.push(arg);
ctx.frame.accumulator = JsValue::Smi(items.len() as i32);
return Ok(DispatchAction::Continue);
}
}
}
}
let callee = ctx.frame.read_reg(callee_v)?.cheap_clone();
let this_val = ctx.frame.read_reg(recv_v)?.cheap_clone();
let callee_flat = ctx.frame.reg_index(callee_v)?;
let args = (0..arg_count as usize)
.map(|i| ctx.frame.registers[callee_flat + 1 + i].clone())
.collect::<CallArgs>();
dispatch_call_property(ctx.frame, &callee, this_val, args)?;
Ok(DispatchAction::Continue)
}
fn expand_spread_args(raw_args: impl IntoIterator<Item = JsValue>) -> CallArgs {
let mut out = SmallVec::new();
for arg in raw_args {
match &arg {
JsValue::Array(items) => {
out.extend(items.borrow().iter().cloned());
}
JsValue::PlainObject(map) => {
let map_ref = map.borrow();
if map_ref.get("__is_array__").is_some() {
let len = match map_ref.get("length") {
Some(JsValue::Smi(n)) => *n as usize,
Some(JsValue::HeapNumber(n)) => *n as usize,
_ => 0,
};
for i in 0..len {
out.push(
map_ref
.get(&i.to_string())
.cloned()
.unwrap_or(JsValue::Undefined),
);
}
} else {
let iter_fn = map_ref.get("@@iterator").cloned().or_else(|| {
let sym_key =
format!("Symbol({})", crate::builtins::symbol::SYMBOL_ITERATOR);
map_ref.get(&sym_key).cloned()
});
drop(map_ref);
if let Some(ref f) = iter_fn {
if let Ok(iter_obj) =
dispatch_call_with_this(f, arg.clone(), CallArgs::new())
{
match iter_obj {
JsValue::Iterator(ni) => {
while let Some(v) = ni.borrow_mut().next_item() {
out.push(v);
}
}
JsValue::Generator(gs) => loop {
match Interpreter::run_generator_step(&gs, JsValue::Undefined) {
Ok(GeneratorStep::Yield(v)) => out.push(v),
Ok(GeneratorStep::Return(v)) => {
if !matches!(v, JsValue::Undefined) {
out.push(v);
}
break;
}
Err(_) => break,
}
},
JsValue::PlainObject(ref iter_map)
if iter_map.borrow().contains_key("next") =>
{
if let Ok(items) =
collect_from_plain_object_iterator(&iter_obj, iter_map)
{
out.extend(items);
} else {
out.push(arg.clone());
}
}
_ => out.push(arg.clone()),
}
} else {
out.push(arg.clone());
}
} else if map.borrow().contains_key("next") {
if let Ok(items) = collect_from_plain_object_iterator(&arg, map) {
out.extend(items);
} else {
out.push(arg.clone());
}
} else {
out.push(arg.clone());
}
}
}
JsValue::String(s) => {
for ch in s.chars() {
out.push(JsValue::String(ch.to_string().into()));
}
}
JsValue::Iterator(ni) => {
let mut it = ni.borrow_mut();
while let Some(v) = it.next_item() {
out.push(v);
}
}
JsValue::Generator(gs) => loop {
match Interpreter::run_generator_step(gs, JsValue::Undefined) {
Ok(GeneratorStep::Yield(v)) => out.push(v),
Ok(GeneratorStep::Return(v)) => {
if !matches!(v, JsValue::Undefined) {
out.push(v);
}
break;
}
Err(_) => break,
}
},
_ => {
out.push(arg.clone());
}
}
}
out
}
fn handle_call_with_spread(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(callee_v) = *instr.operand(0) else {
return Err(err_bad_operand("CallWithSpread", 0));
};
let Operand::Register(args_start_v) = *instr.operand(1) else {
return Err(err_bad_operand("CallWithSpread", 1));
};
let Operand::RegisterCount(arg_count) = *instr.operand(2) else {
return Err(err_bad_operand("CallWithSpread", 2));
};
let callee = ctx.frame.read_reg(callee_v)?.cheap_clone();
let raw_args = collect_args(ctx.frame, args_start_v, arg_count)?;
let args = expand_spread_args(raw_args);
match callee {
JsValue::Function(ba) => {
let count = ba.increment_invocation_count();
if count >= TIERING_THRESHOLD && !ba.has_any_jit_code() {
maybe_compile_baseline(&ba);
}
if count >= MAGLEV_TIERING_THRESHOLD {
maybe_compile_maglev(&ba);
}
if count >= TURBOFAN_TIERING_THRESHOLD {
maybe_compile_turbofan(&ba);
}
let mut tried_jit = false;
if let Some(jit_result) = try_execute_best_jit(&ba, &args) {
ctx.frame.accumulator = jit_result?;
tried_jit = true;
}
if !tried_jit {
let mut callee_frame =
acquire_frame(Rc::clone(&ba), args, Rc::clone(&ctx.frame.global_env));
restore_closure_context(&mut callee_frame, &ba);
if ba.self_name_register().is_some() {
populate_self_name(&mut callee_frame, &ba, &JsValue::Function(Rc::clone(&ba)));
}
push_call_frame("<anonymous>")?;
let result = run_callee_pooled(callee_frame);
pop_call_frame();
ctx.frame.global_cache_invalidate();
ctx.frame.accumulator = result?;
}
}
JsValue::NativeFunction(f) => {
ctx.frame.accumulator = f(args.into_vec())?;
ctx.frame.global_cache_invalidate();
}
JsValue::PlainObject(ref map) => {
if let Some(JsValue::NativeFunction(f)) = map.borrow().get("__call__").cloned() {
ctx.frame.accumulator = f(args.into_vec())?;
ctx.frame.global_cache_invalidate();
} else {
return Err(StatorError::TypeError(
"CallWithSpread: callee is not a function (got PlainObject)".to_string(),
));
}
}
other => {
return Err(StatorError::TypeError(format!(
"CallWithSpread: callee is not a function (got {other:?})"
)));
}
}
Ok(DispatchAction::Continue)
}
fn call_plain_object_function(
ctx: &mut DispatchContext,
ba: &Rc<crate::bytecode::bytecode_array::BytecodeArray>,
map: &Rc<RefCell<PropertyMap>>,
args: CallArgs,
) -> StatorResult<()> {
let is_class_constructor = matches!(
map.borrow().get(".class_constructor"),
Some(JsValue::Boolean(true))
);
if is_class_constructor {
let pending_this = ctx
.frame
.global_env
.borrow()
.get(".class_pending_this")
.cloned();
match pending_this {
Some(pending_this) => {
let current_this = ctx
.frame
.global_env
.borrow()
.get_this()
.cloned()
.unwrap_or(JsValue::Undefined);
if current_this != JsValue::TheHole {
return Err(StatorError::ReferenceError(
"Super constructor may only be called once".into(),
));
}
ctx.frame.global_env.borrow_mut().set_this(pending_this);
}
None => {
return Err(StatorError::TypeError(
"Class constructor cannot be invoked without 'new'".into(),
));
}
}
}
let mut callee_frame = acquire_frame(Rc::clone(ba), args, Rc::clone(&ctx.frame.global_env));
restore_closure_context(&mut callee_frame, ba);
callee_frame.new_target = ctx.frame.new_target.clone();
push_call_frame("<anonymous>")?;
let result = run_callee_pooled(callee_frame);
pop_call_frame();
ctx.frame.global_cache_invalidate();
ctx.frame.accumulator = result?;
Ok(())
}
fn construct_class_from_plain_object(
ctx: &mut DispatchContext,
ba: &Rc<crate::bytecode::bytecode_array::BytecodeArray>,
class_map: &Rc<RefCell<PropertyMap>>,
ctor_proto: &JsValue,
args: CallArgs,
) -> StatorResult<()> {
let this_obj: Rc<RefCell<PropertyMap>> = Rc::new(RefCell::new(PropertyMap::new()));
if !matches!(ctor_proto, JsValue::Undefined) {
this_obj
.borrow_mut()
.insert("__proto__".to_string(), ctor_proto.clone());
}
let this_val = JsValue::PlainObject(Rc::clone(&this_obj));
let mut callee_frame = acquire_frame(Rc::clone(ba), args, Rc::clone(&ctx.frame.global_env));
restore_closure_context(&mut callee_frame, ba);
callee_frame.new_target = JsValue::PlainObject(Rc::clone(class_map));
let is_derived = if let Some(parent) = class_map.borrow().get("__proto__").cloned()
&& !matches!(parent, JsValue::Undefined | JsValue::Null)
{
callee_frame
.global_env
.borrow_mut()
.insert(".class_pending_this".to_string(), this_val.clone());
callee_frame
.global_env
.borrow_mut()
.set_this(JsValue::TheHole);
callee_frame
.global_env
.borrow_mut()
.insert("super".to_string(), parent);
true
} else {
callee_frame
.global_env
.borrow_mut()
.set_this(this_val.clone());
false
};
let new_target = callee_frame.new_target.clone();
let run_field_init = |env: &Rc<RefCell<GlobalEnv>>, this: &JsValue| -> StatorResult<()> {
if let Some(JsValue::Function(init_ba)) =
class_map.borrow().get(".class_field_initializer").cloned()
{
let mut init_frame =
acquire_frame(Rc::clone(&init_ba), vec![this.clone()], Rc::clone(env));
restore_closure_context(&mut init_frame, &init_ba);
init_frame.new_target = new_target.clone();
{
let mut globals = init_frame.global_env.borrow_mut();
globals.set_this(this.clone());
globals.insert(
".class_initializer_class".to_string(),
JsValue::PlainObject(Rc::clone(class_map)),
);
}
push_call_frame("<field_init>")?;
let result = run_callee_pooled(init_frame);
pop_call_frame();
env.borrow_mut().remove(".class_initializer_class");
result?;
}
Ok(())
};
if !is_derived {
run_field_init(&ctx.frame.global_env, &this_val)?;
ctx.frame.global_cache_invalidate();
}
push_call_frame("<anonymous>")?;
let result = run_callee_pooled(callee_frame);
pop_call_frame();
ctx.frame.global_cache_invalidate();
let val = result?;
if is_derived {
let derived_this = ctx
.frame
.global_env
.borrow()
.get_this()
.cloned()
.unwrap_or(this_val.clone());
run_field_init(&ctx.frame.global_env, &derived_this)?;
ctx.frame.global_cache_invalidate();
}
ctx.frame
.global_env
.borrow_mut()
.remove(".class_pending_this");
ctx.frame.accumulator = match val {
JsValue::PlainObject(_) | JsValue::Object(_) => val,
_ => this_val,
};
Ok(())
}
fn handle_construct(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(ctor_v) = *instr.operand(0) else {
return Err(err_bad_operand("Construct", 0));
};
let Operand::Register(args_start_v) = *instr.operand(1) else {
return Err(err_bad_operand("Construct", 1));
};
let Operand::RegisterCount(arg_count) = *instr.operand(2) else {
return Err(err_bad_operand("Construct", 2));
};
let ctor = ctx.frame.read_reg(ctor_v)?.cheap_clone();
match ctor {
JsValue::Function(ba) => {
if ba.is_arrow() {
return Err(StatorError::TypeError(
"Function is not a constructor".to_string(),
));
}
let args = collect_args(ctx.frame, args_start_v, arg_count)?;
let ctor_proto = resolve_construct_proto(&JsValue::Function(Rc::clone(&ba)), &ba);
let this_val = make_construct_this(&ba, &ctor_proto);
if args.is_empty() && ba.has_trivial_body() {
maybe_cache_construct_boilerplate(&ba, &this_val);
ctx.frame.accumulator = this_val;
} else {
let mut callee_frame =
acquire_frame(Rc::clone(&ba), args, Rc::clone(&ctx.frame.global_env));
restore_closure_context(&mut callee_frame, &ba);
callee_frame.new_target = JsValue::Function(Rc::clone(&ba));
callee_frame
.global_env
.borrow_mut()
.set_this(this_val.clone());
push_call_frame("<anonymous>")?;
let result = run_callee_pooled(callee_frame);
pop_call_frame();
ctx.frame.global_cache_invalidate();
let val = result?;
ctx.frame.accumulator = match val {
JsValue::PlainObject(_) | JsValue::Object(_) => val,
_ => {
maybe_cache_construct_boilerplate(&ba, &this_val);
this_val
}
};
}
}
JsValue::NativeFunction(f) => {
let args = collect_args(ctx.frame, args_start_v, arg_count)?;
let ctor_proto = proto_lookup(&JsValue::NativeFunction(Rc::clone(&f)), "prototype");
let val = f(args.into_vec())?;
ctx.frame.global_cache_invalidate();
ctx.frame.accumulator = construct_builtin_result(val, &ctor_proto)?;
}
JsValue::PlainObject(ref map) => {
if map.borrow().get("__no_construct__").is_some() {
return Err(StatorError::TypeError(
"Symbol is not a constructor".to_string(),
));
}
let ctor_proto = proto_lookup(&ctor, "prototype");
let call_val = map.borrow().get("__call__").cloned();
match call_val {
Some(JsValue::NativeFunction(f)) => {
let args = collect_args(ctx.frame, args_start_v, arg_count)?;
let val = f(args.into_vec())?;
ctx.frame.global_cache_invalidate();
ctx.frame.accumulator = construct_builtin_result(val, &ctor_proto)?;
}
Some(JsValue::Function(ba)) => {
let args = collect_args(ctx.frame, args_start_v, arg_count)?;
construct_class_from_plain_object(ctx, &ba, map, &ctor_proto, args)?;
}
_ => {
return Err(StatorError::TypeError(format!(
"Construct: constructor is not a function (got {other:?})",
other = JsValue::PlainObject(Rc::clone(map))
)));
}
}
}
JsValue::Proxy(ref p) => {
let args = collect_args(ctx.frame, args_start_v, arg_count)?;
let ctor_val = ctx.frame.accumulator.cheap_clone();
let obj = proxy_construct(&mut p.borrow_mut(), args.into_vec(), ctor_val)?;
ctx.frame.accumulator = obj;
}
other => {
return Err(StatorError::TypeError(format!(
"Construct: constructor is not a function (got {other:?})"
)));
}
}
Ok(DispatchAction::Continue)
}
fn handle_construct_with_spread(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(ctor_v) = *instr.operand(0) else {
return Err(err_bad_operand("ConstructWithSpread", 0));
};
let Operand::Register(args_start_v) = *instr.operand(1) else {
return Err(err_bad_operand("ConstructWithSpread", 1));
};
let Operand::RegisterCount(arg_count) = *instr.operand(2) else {
return Err(err_bad_operand("ConstructWithSpread", 2));
};
let ctor = ctx.frame.read_reg(ctor_v)?.cheap_clone();
let raw_args = collect_args(ctx.frame, args_start_v, arg_count)?;
let args = expand_spread_args(raw_args);
match ctor {
JsValue::Function(ba) => {
let ctor_proto = resolve_construct_proto(&JsValue::Function(Rc::clone(&ba)), &ba);
let this_val = make_construct_this(&ba, &ctor_proto);
let mut callee_frame =
acquire_frame(Rc::clone(&ba), args, Rc::clone(&ctx.frame.global_env));
restore_closure_context(&mut callee_frame, &ba);
callee_frame.new_target = JsValue::Function(Rc::clone(&ba));
callee_frame
.global_env
.borrow_mut()
.set_this(this_val.clone());
push_call_frame("<anonymous>")?;
let result = run_callee_pooled(callee_frame);
pop_call_frame();
ctx.frame.global_cache_invalidate();
let val = result?;
ctx.frame.accumulator = match val {
JsValue::PlainObject(_) | JsValue::Object(_) => val,
_ => {
maybe_cache_construct_boilerplate(&ba, &this_val);
this_val
}
};
}
JsValue::NativeFunction(f) => {
let ctor_proto = proto_lookup(&JsValue::NativeFunction(Rc::clone(&f)), "prototype");
let val = f(args.into_vec())?;
ctx.frame.global_cache_invalidate();
ctx.frame.accumulator = construct_builtin_result(val, &ctor_proto)?;
}
JsValue::PlainObject(ref map) => {
if map.borrow().get("__no_construct__").is_some() {
return Err(StatorError::TypeError(
"Symbol is not a constructor".to_string(),
));
}
let ctor_proto = proto_lookup(&ctor, "prototype");
let call_val = map.borrow().get("__call__").cloned();
match call_val {
Some(JsValue::NativeFunction(f)) => {
let val = f(args.into_vec())?;
ctx.frame.global_cache_invalidate();
ctx.frame.accumulator = construct_builtin_result(val, &ctor_proto)?;
}
Some(JsValue::Function(ba)) => {
construct_class_from_plain_object(ctx, &ba, map, &ctor_proto, args)?;
}
_ => {
return Err(StatorError::TypeError(
"ConstructWithSpread: constructor is not a function".to_string(),
));
}
}
}
JsValue::Proxy(ref p) => {
let ctor_val = ctx.frame.accumulator.cheap_clone();
let obj = proxy_construct(&mut p.borrow_mut(), args.into_vec(), ctor_val)?;
ctx.frame.accumulator = obj;
}
other => {
return Err(StatorError::TypeError(format!(
"ConstructWithSpread: constructor is not a function (got {other:?})"
)));
}
}
Ok(DispatchAction::Continue)
}
fn handle_push_context(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(v) = *instr.operand(0) else {
return Err(err_bad_operand("PushContext", 0));
};
let old_ctx = ctx.frame.context.take().unwrap_or(JsValue::Undefined);
ctx.frame.write_reg(v, old_ctx)?;
ctx.frame.context = Some(ctx.frame.accumulator.cheap_clone());
Ok(DispatchAction::Continue)
}
fn handle_pop_context(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(v) = *instr.operand(0) else {
return Err(err_bad_operand("PopContext", 0));
};
let saved = ctx.frame.read_reg(v)?.cheap_clone();
ctx.frame.context = if saved.is_undefined() {
None
} else {
Some(saved)
};
Ok(DispatchAction::Continue)
}
fn handle_lda_context_slot(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(ctx_v) = *instr.operand(0) else {
return Err(err_bad_operand("LdaContextSlot", 0));
};
let Operand::ConstantPoolIdx(slot_idx) = *instr.operand(1) else {
return Err(err_bad_operand("LdaContextSlot", 1));
};
let Operand::Immediate(depth) = *instr.operand(2) else {
return Err(err_bad_operand("LdaContextSlot", 2));
};
let ctx_val = ctx.frame.read_reg(ctx_v)?.cheap_clone();
let js_ctx = extract_context(&ctx_val, "LdaContextSlot")?;
let target = walk_context_chain(&js_ctx, depth as u32, "LdaContextSlot")?;
let borrowed = target.borrow();
let slot = slot_idx as usize;
ctx.frame.accumulator = borrowed
.slots
.get(slot)
.cloned()
.unwrap_or(JsValue::Undefined);
Ok(DispatchAction::Continue)
}
fn handle_lda_current_context_slot(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::ConstantPoolIdx(slot_idx) = *instr.operand(0) else {
return Err(err_bad_operand("LdaCurrentContextSlot", 0));
};
let ctx_val = ctx
.frame
.context
.as_ref()
.ok_or_else(|| StatorError::Internal("LdaCurrentContextSlot: no active context".into()))?
.clone();
let js_ctx = extract_context(&ctx_val, "LdaCurrentContextSlot")?;
let borrowed = js_ctx.borrow();
let slot = slot_idx as usize;
ctx.frame.accumulator = borrowed
.slots
.get(slot)
.cloned()
.unwrap_or(JsValue::Undefined);
Ok(DispatchAction::Continue)
}
fn handle_sta_context_slot(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(ctx_v) = *instr.operand(0) else {
return Err(err_bad_operand("StaContextSlot", 0));
};
let Operand::ConstantPoolIdx(slot_idx) = *instr.operand(1) else {
return Err(err_bad_operand("StaContextSlot", 1));
};
let Operand::Immediate(depth) = *instr.operand(2) else {
return Err(err_bad_operand("StaContextSlot", 2));
};
let ctx_val = ctx.frame.read_reg(ctx_v)?.cheap_clone();
let js_ctx = extract_context(&ctx_val, "StaContextSlot")?;
let target = walk_context_chain(&js_ctx, depth as u32, "StaContextSlot")?;
let mut borrowed = target.borrow_mut();
let slot = slot_idx as usize;
if slot >= borrowed.slots.len() {
borrowed.slots.resize(slot + 1, JsValue::Undefined);
}
borrowed.slots[slot] = ctx.frame.accumulator.cheap_clone();
Ok(DispatchAction::Continue)
}
fn handle_sta_current_context_slot(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::ConstantPoolIdx(slot_idx) = *instr.operand(0) else {
return Err(err_bad_operand("StaCurrentContextSlot", 0));
};
let ctx_val = ctx
.frame
.context
.as_ref()
.ok_or_else(|| StatorError::Internal("StaCurrentContextSlot: no active context".into()))?
.clone();
let js_ctx = extract_context(&ctx_val, "StaCurrentContextSlot")?;
let mut borrowed = js_ctx.borrow_mut();
let slot = slot_idx as usize;
if slot >= borrowed.slots.len() {
borrowed.slots.resize(slot + 1, JsValue::Undefined);
}
let old = std::mem::replace(
&mut borrowed.slots[slot],
ctx.frame.accumulator.cheap_clone(),
);
if let JsValue::PlainObject(rc) = old {
recycle_object_rc(rc);
}
Ok(DispatchAction::Continue)
}
#[cold]
fn handle_create_function_context(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Immediate(slot_count) = *instr.operand(1) else {
return Err(err_bad_operand("CreateFunctionContext", 1));
};
let parent = match &ctx.frame.context {
Some(JsValue::Context(c)) => Some(Rc::clone(c)),
_ => None,
};
let js_ctx = JsContext::new(slot_count as usize, parent);
ctx.frame.accumulator = JsValue::Context(js_ctx);
Ok(DispatchAction::Continue)
}
#[cold]
fn handle_create_block_context(
ctx: &mut DispatchContext,
_instr: &Instruction,
) -> StatorResult<DispatchAction> {
let parent = match &ctx.frame.context {
Some(JsValue::Context(c)) => Some(Rc::clone(c)),
_ => None,
};
let js_ctx = JsContext::new(0, parent);
ctx.frame.accumulator = JsValue::Context(js_ctx);
Ok(DispatchAction::Continue)
}
#[cold]
fn handle_create_eval_context(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Immediate(slot_count) = *instr.operand(1) else {
return Err(err_bad_operand("CreateEvalContext", 1));
};
let parent = match &ctx.frame.context {
Some(JsValue::Context(c)) => Some(Rc::clone(c)),
_ => None,
};
let js_ctx = JsContext::new(slot_count as usize, parent);
ctx.frame.accumulator = JsValue::Context(js_ctx);
Ok(DispatchAction::Continue)
}
#[cold]
fn handle_create_catch_context(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(exc_v) = *instr.operand(0) else {
return Err(err_bad_operand("CreateCatchContext", 0));
};
let exception = ctx.frame.read_reg(exc_v)?.cheap_clone();
let parent = match &ctx.frame.context {
Some(JsValue::Context(c)) => Some(Rc::clone(c)),
_ => None,
};
let js_ctx = JsContext::new(1, parent);
js_ctx.borrow_mut().slots[0] = exception;
ctx.frame.accumulator = JsValue::Context(js_ctx);
Ok(DispatchAction::Continue)
}
#[cold]
fn handle_create_with_context(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(obj_v) = *instr.operand(0) else {
return Err(err_bad_operand("CreateWithContext", 0));
};
let obj = ctx.frame.read_reg(obj_v)?.cheap_clone();
let parent = match &ctx.frame.context {
Some(JsValue::Context(c)) => Some(Rc::clone(c)),
_ => None,
};
let js_ctx = JsContext::new(1, parent);
js_ctx.borrow_mut().slots[0] = obj;
ctx.frame.accumulator = JsValue::Context(js_ctx);
Ok(DispatchAction::Continue)
}
#[cold]
fn handle_throw(ctx: &mut DispatchContext, _instr: &Instruction) -> StatorResult<DispatchAction> {
let thrown = ctx.frame.accumulator.cheap_clone();
let throw_idx = (ctx.frame.pc - 1) as u32;
if let Some(handler_pc) = find_handler(throw_idx, ctx.handler_table) {
ctx.frame.accumulator = thrown;
ctx.frame.pc = handler_pc;
return Ok(DispatchAction::Continue);
}
let throw_offset = ctx.byte_offsets[throw_idx as usize] as u32;
if let Some(pause_err) = ACTIVE_DEBUGGER.with(|d| {
let opt = d.borrow();
opt.as_ref().and_then(|rc| {
let mut dbg = rc.borrow_mut();
if dbg.pause_on_exceptions && !dbg.consume_exception_resume() {
ctx.frame.pc = throw_idx as usize; Some(dbg.on_exception(throw_offset))
} else {
None
}
})
}) {
return Err(pause_err);
}
set_pending_exception(thrown.clone());
let msg = error_message_from_value(&thrown);
Err(StatorError::JsException(msg))
}
fn handle_stack_check(
_ctx: &mut DispatchContext,
_instr: &Instruction,
) -> StatorResult<DispatchAction> {
Ok(DispatchAction::Continue)
}
fn handle_lda_global(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::ConstantPoolIdx(name_idx) = *instr.operand(0) else {
return Err(err_bad_operand("LdaGlobal", 0));
};
if let Some((slot_idx, cached_gen)) = ctx.frame.global_ic_get(name_idx) {
let env = ctx.frame.global_env.borrow();
if env.generation() == cached_gen {
let value = env.get_by_index(slot_idx).cheap_clone();
drop(env);
if value != JsValue::TheHole {
ctx.frame.accumulator = value;
return Ok(DispatchAction::Continue);
}
}
}
let name = ctx.frame.get_string_constant(name_idx)?;
let value = ctx.frame.load_global(name.as_ref())?;
{
let env = ctx.frame.global_env.borrow();
if let Some(slot_idx) = env.slot_index_for(name.as_ref()) {
let generation_val = env.generation();
drop(env);
ctx.frame.global_ic_put(name_idx, slot_idx, generation_val);
}
}
ctx.frame.accumulator = value;
Ok(DispatchAction::Continue)
}
fn handle_lda_global_star(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::ConstantPoolIdx(name_idx) = *instr.operand(0) else {
return Err(err_bad_operand("LdaGlobalStar", 0));
};
let Operand::FeedbackSlot(_slot) = *instr.operand(1) else {
return Err(err_bad_operand("LdaGlobalStar", 1));
};
let Operand::Register(dst) = *instr.operand(2) else {
return Err(err_bad_operand("LdaGlobalStar", 2));
};
let ic_hit = ctx
.frame
.global_ic
.as_ref()
.and_then(|ic| ic.get(name_idx as usize).copied().flatten());
if let Some((slot_idx, cached_gen)) = ic_hit {
let env = ctx.frame.global_env.borrow();
if env.generation() == cached_gen {
let value = env.get_by_index(slot_idx).clone();
drop(env);
if value != JsValue::TheHole {
ctx.frame.accumulator = value.cheap_clone();
ctx.frame.write_reg(dst, value)?;
return Ok(DispatchAction::Continue);
}
}
}
let name = ctx.frame.get_string_constant(name_idx)?;
let value = ctx.frame.load_global(name.as_ref())?;
{
let env = ctx.frame.global_env.borrow();
let slot_gen = env
.slot_index_for(name.as_ref())
.map(|idx| (idx, env.generation()));
drop(env);
if let Some((slot_idx, cached_gen)) = slot_gen {
ctx.frame.global_ic_put(name_idx, slot_idx, cached_gen);
}
}
ctx.frame.accumulator = value.cheap_clone();
ctx.frame.write_reg(dst, value)?;
Ok(DispatchAction::Continue)
}
fn handle_lda_global_inside_typeof(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::ConstantPoolIdx(name_idx) = *instr.operand(0) else {
return Err(err_bad_operand("LdaGlobalInsideTypeof", 0));
};
if let Some((slot_idx, cached_gen)) = ctx.frame.global_ic_get(name_idx) {
let env = ctx.frame.global_env.borrow();
if env.generation() == cached_gen {
let value = env.get_by_index(slot_idx).cheap_clone();
drop(env);
ctx.frame.accumulator = value;
return Ok(DispatchAction::Continue);
}
}
let name = ctx.frame.get_string_constant(name_idx)?;
if let Some(val) = ctx.frame.global_cache_get(name.as_ref()) {
ctx.frame.accumulator = val.clone();
return Ok(DispatchAction::Continue);
}
let val = ctx
.frame
.global_env
.borrow()
.get(name.as_ref())
.cloned()
.unwrap_or(JsValue::Undefined);
ctx.frame.global_cache_put(name.as_ref(), val.clone());
{
let env = ctx.frame.global_env.borrow();
if let Some(slot_idx) = env.slot_index_for(name.as_ref()) {
let generation_val = env.generation();
drop(env);
ctx.frame.global_ic_put(name_idx, slot_idx, generation_val);
}
}
ctx.frame.accumulator = val;
Ok(DispatchAction::Continue)
}
fn handle_sta_global(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::ConstantPoolIdx(name_idx) = *instr.operand(0) else {
return Err(err_bad_operand("StaGlobal", 0));
};
let name = ctx.frame.get_string_constant(name_idx)?;
let val = ctx.frame.accumulator.cheap_clone();
if ctx.frame.bytecode_array.is_strict() && !ctx.frame.global_env.borrow().contains_key(&name) {
return Err(StatorError::ReferenceError(format!(
"{name} is not defined"
)));
}
ctx.frame.store_global(name_idx, &name, val);
Ok(DispatchAction::Continue)
}
fn set_named_property_function_metadata(value: &JsValue, obj: &JsValue, name: &str) {
if let JsValue::Function(ba) = value {
fn_props_set(ba, ".home_object".to_string(), obj.clone());
set_function_name_if_missing(value, name);
}
}
fn handle_lda_named_property(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(obj_v) = *instr.operand(0) else {
return Err(err_bad_operand("LdaNamedProperty", 0));
};
let Operand::ConstantPoolIdx(name_idx) = *instr.operand(1) else {
return Err(err_bad_operand("LdaNamedProperty", 1));
};
let slot = if let Operand::FeedbackSlot(s) = *instr.operand(2) {
s
} else {
u32::MAX
};
let prop_name = ctx.frame.get_string_constant(name_idx)?;
if prop_name.as_ref() == "length"
&& let JsValue::Array(arr) = ctx.frame.read_reg(obj_v)?
{
let len = arr.borrow().len() as i32;
ctx.frame.accumulator = JsValue::Smi(len);
return Ok(DispatchAction::Continue);
}
let obj = ctx.frame.read_reg(obj_v)?.cheap_clone();
if is_private_storage_key(&prop_name) {
ctx.frame.accumulator = load_private_named_property(&obj, &prop_name)?;
return Ok(DispatchAction::Continue);
}
if slot != u32::MAX
&& let JsValue::PlainObject(ref map) = obj
{
let pm = map.borrow();
let layout = pm.layout_id();
if let Some((cached_layout, cached_offset)) = mono_load_probe(ctx.frame, slot)
&& layout as usize == cached_layout
&& let Some(val) = pm.get_by_offset(cached_offset)
{
ctx.frame.accumulator = val.cheap_clone();
return Ok(DispatchAction::Continue);
}
if let Some(ic) = ctx
.frame
.mega_load_ic
.as_ref()
.and_then(|cache| cache.probe(slot, layout))
{
if !ic.is_proto {
if pm.matches_key_at_offset(ic.cached_offset as usize, prop_name.as_ref())
&& let Some(val) = pm.get_by_offset(ic.cached_offset as usize)
{
let v = val.cheap_clone();
drop(pm);
ctx.frame.accumulator = v;
return Ok(DispatchAction::Continue);
}
} else {
let proto_layout = ic.proto_layout;
let offset = ic.cached_offset;
if let Some(JsValue::PlainObject(proto_map)) = pm.get(INTERNAL_PROTO_PROPERTY_KEY) {
let proto_pm = proto_map.borrow();
if proto_pm.layout_id() == proto_layout
&& proto_pm.matches_key_at_offset(offset as usize, prop_name.as_ref())
&& let Some(val) = proto_pm.get_by_offset(offset as usize)
{
let v = val.cheap_clone();
drop(proto_pm);
drop(pm);
ctx.frame.accumulator = v;
return Ok(DispatchAction::Continue);
}
}
}
}
let global_epoch = PropertyMap::global_proto_mutation_epoch();
if let Some(ic) = ctx
.frame
.proto_load_ic
.as_ref()
.and_then(|cache| cache.probe_epoch(slot, layout, global_epoch))
{
ctx.frame.accumulator = ic.value.cheap_clone();
return Ok(DispatchAction::Continue);
}
if let Some(ic) = ctx
.frame
.proto_load_ic
.as_ref()
.and_then(|cache| cache.probe(slot, layout))
&& ic.proto_generation == pm.proto_generation_with_epoch(global_epoch)
{
ctx.frame.accumulator = ic.value.cheap_clone();
return Ok(DispatchAction::Continue);
}
}
if slot != u32::MAX {
let obj_ptr = match &obj {
JsValue::Array(arr) => Some(Rc::as_ptr(arr) as usize),
JsValue::Function(ba) => Some(Rc::as_ptr(ba) as usize),
_ => None,
};
if let Some(ptr) = obj_ptr
&& let Some(entries) = ctx
.frame
.poly_load_cache
.as_ref()
.and_then(|cache| cache.get(&slot))
{
for &(cached_ptr, ref cached_val) in entries {
if cached_ptr == ptr {
ctx.frame.accumulator = cached_val.cheap_clone();
return Ok(DispatchAction::Continue);
}
}
}
}
if matches!(obj, JsValue::Null | JsValue::Undefined) {
return Err(StatorError::TypeError(format!(
"Cannot read properties of {} (reading '{prop_name}')",
if matches!(obj, JsValue::Null) {
"null"
} else {
"undefined"
}
)));
}
let mut inherited_proto_depth = None;
let result = if let Some(result) = try_fast_named_property_lookup(&obj, &prop_name) {
result
} else {
let result = try_proto_lookup_rc(&obj, &prop_name)?;
if let JsValue::PlainObject(ref map) = obj
&& !matches!(result, JsValue::Undefined)
{
inherited_proto_depth = proto_lookup_cached_resolution(map, &prop_name)
.map(|(_, depth)| depth)
.filter(|depth| *depth != 0);
if inherited_proto_depth.is_none() {
let explicit_proto = {
let pm = map.borrow();
pm.get(INTERNAL_PROTO_PROPERTY_KEY)
.or_else(|| pm.get("__proto__"))
.cloned()
};
if let Some(proto) = explicit_proto {
inherited_proto_depth = proto_lookup_chain_depth(&proto, &prop_name);
}
}
}
result
};
if slot != u32::MAX
&& let JsValue::PlainObject(ref map) = obj
{
let pm = map.borrow();
let has_accessor = pm.has_getter_for(&prop_name);
if !has_accessor {
match pm.offset_of(&prop_name) {
Some(offset) if offset <= u16::MAX as usize => {
ctx.frame.mega_load_ic_mut().update(MegamorphicIcEntry {
slot,
receiver_layout: pm.layout_id(),
cached_offset: offset as u16,
is_proto: false,
proto_layout: 0,
});
ctx.frame.mono_load_cache_mut().insert(
slot,
pm.layout_id() as usize,
offset as i32,
);
}
None => {
if let Some(JsValue::PlainObject(proto_map)) =
pm.get(INTERNAL_PROTO_PROPERTY_KEY)
{
let proto_pm = proto_map.borrow();
let proto_has_accessor = proto_pm.has_getter_for(&prop_name);
if !proto_has_accessor
&& let Some(proto_offset) = proto_pm.offset_of(&prop_name)
&& proto_offset <= u16::MAX as usize
{
let receiver_layout = pm.layout_id();
let proto_layout = proto_pm.layout_id();
drop(proto_pm);
drop(pm);
ctx.frame.mega_load_ic_mut().update(MegamorphicIcEntry {
slot,
receiver_layout,
cached_offset: proto_offset as u16,
is_proto: true,
proto_layout,
});
let obj_ptr = match &obj {
JsValue::Array(arr) => Some(Rc::as_ptr(arr) as usize),
JsValue::Function(ba) => Some(Rc::as_ptr(ba) as usize),
_ => None,
};
if let Some(ptr) = obj_ptr {
let entries =
ctx.frame.poly_load_cache_mut().entry(slot).or_default();
let mut found = false;
for entry in entries.iter_mut() {
if entry.0 == ptr {
entry.1 = result.cheap_clone();
found = true;
break;
}
}
if !found && entries.len() < POLYMORPHIC_LOAD_CACHE_CAP {
entries.push((ptr, result.cheap_clone()));
}
}
ctx.frame.accumulator = result;
return Ok(DispatchAction::Continue);
}
}
}
_ => {}
}
}
if let Some(_depth) = inherited_proto_depth {
ctx.frame.proto_load_ic_mut().update(ProtoLoadIc {
slot,
receiver_shape: pm.layout_id(),
proto_generation: pm.proto_generation(),
proto_global_epoch: PropertyMap::global_proto_mutation_epoch(),
value: result.cheap_clone(),
});
} else if let Some(cache) = &mut ctx.frame.proto_load_ic {
cache.remove(slot);
}
}
if slot != u32::MAX && inherited_proto_depth.is_none() {
let obj_ptr = match &obj {
JsValue::Array(arr) => Some(Rc::as_ptr(arr) as usize),
JsValue::Function(ba) => Some(Rc::as_ptr(ba) as usize),
_ => None,
};
if let Some(ptr) = obj_ptr {
let entries = ctx.frame.poly_load_cache_mut().entry(slot).or_default();
let mut found = false;
for entry in entries.iter_mut() {
if entry.0 == ptr {
entry.1 = result.cheap_clone();
found = true;
break;
}
}
if !found && entries.len() < POLYMORPHIC_LOAD_CACHE_CAP {
entries.push((ptr, result.cheap_clone()));
}
}
}
ctx.frame.accumulator = result;
Ok(DispatchAction::Continue)
}
fn handle_sta_named_property(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(obj_v) = *instr.operand(0) else {
return Err(err_bad_operand("StaNamedProperty", 0));
};
let Operand::ConstantPoolIdx(name_idx) = *instr.operand(1) else {
return Err(err_bad_operand("StaNamedProperty", 1));
};
let slot = if let Operand::FeedbackSlot(s) = *instr.operand(2) {
s
} else {
u32::MAX
};
let prop_name = ctx.frame.get_string_constant(name_idx)?;
let val = ctx.frame.accumulator.cheap_clone();
let obj = ctx.frame.read_reg(obj_v)?.cheap_clone();
if is_private_storage_key(&prop_name) {
store_private_named_property(ctx, &obj, &prop_name, val)?;
return Ok(DispatchAction::Continue);
}
if matches!(obj, JsValue::Null | JsValue::Undefined) {
return Err(StatorError::TypeError(format!(
"Cannot set properties of {} (setting '{prop_name}')",
if matches!(obj, JsValue::Null) {
"null"
} else {
"undefined"
}
)));
}
match obj {
JsValue::Proxy(ref p) => {
let stored = proxy_set_with_receiver(&mut p.borrow_mut(), &prop_name, val, &obj)?;
if !stored && ctx.frame.bytecode_array.is_strict() {
return Err(StatorError::TypeError(format!(
"Cannot assign to read only property '{prop_name}'"
)));
}
}
JsValue::PlainObject(ref map) => {
if slot != u32::MAX
&& let Some((cached_layout, cached_offset)) = mono_load_probe(ctx.frame, slot)
{
let pm = map.borrow();
if pm.layout_id() as usize == cached_layout
&& pm.matches_key_at_offset(cached_offset, prop_name.as_ref())
&& pm.is_writable_by_offset(cached_offset)
{
drop(pm);
map.borrow_mut().set_by_offset(cached_offset, val);
invalidate_plain_object_caches(ctx, map);
return Ok(DispatchAction::Continue);
}
}
if slot != u32::MAX
&& let Some(ic) = ctx
.frame
.mega_store_ic
.as_ref()
.and_then(|cache| cache.probe(slot, map.borrow().layout_id()))
{
let offset = ic.cached_offset as usize;
let pm = map.borrow();
if pm.matches_key_at_offset(offset, prop_name.as_ref()) {
if pm.is_writable_by_offset(offset) {
drop(pm);
map.borrow_mut().set_by_offset(offset, val);
let map_ptr = Rc::as_ptr(map) as usize;
if let Some(cache) = &mut ctx.frame.mono_load_cache {
cache.invalidate_layout(map_ptr);
}
if let Some(cache) = &mut ctx.frame.poly_load_cache {
cache.retain(|_, entries| {
entries.retain(|(ptr, _)| *ptr != map_ptr);
!entries.is_empty()
});
}
return Ok(DispatchAction::Continue);
}
if ctx.frame.bytecode_array.is_strict() {
return Err(StatorError::TypeError(format!(
"Cannot assign to read only property '{prop_name}'"
)));
}
return Ok(DispatchAction::Continue);
}
}
if map.borrow().has_accessors {
let setter = map.borrow().get_setter_for(&prop_name).cloned();
if let Some(setter_fn) = setter {
dispatch_setter(&setter_fn, &obj, val)?;
ctx.frame.global_cache_invalidate();
return Ok(DispatchAction::Continue);
}
if map.borrow().has_getter_for(&prop_name) {
if ctx.frame.bytecode_array.is_strict() {
return Err(StatorError::TypeError(format!(
"Cannot set property {prop_name} which has only a getter"
)));
}
return Ok(DispatchAction::Continue);
}
}
{
let mut proto = map.borrow().get("__proto__").cloned();
for _ in 0..256 {
match proto.take() {
Some(JsValue::PlainObject(p)) => {
let pb = p.borrow();
if pb.has_accessors {
if let Some(s) = pb.get_setter_for(&prop_name).cloned() {
drop(pb);
dispatch_setter(&s, &obj, val)?;
ctx.frame.global_cache_invalidate();
return Ok(DispatchAction::Continue);
}
if pb.has_getter_for(&prop_name) {
if ctx.frame.bytecode_array.is_strict() {
return Err(StatorError::TypeError(format!(
"Cannot set property {prop_name} which has only a getter"
)));
}
return Ok(DispatchAction::Continue);
}
}
proto = pb.get("__proto__").cloned();
}
_ => break,
}
}
}
let pm = map.borrow();
if pm.contains_key(prop_name.as_ref()) && !pm.is_writable(prop_name.as_ref()) {
if ctx.frame.bytecode_array.is_strict() {
return Err(StatorError::TypeError(format!(
"Cannot assign to read only property '{prop_name}'"
)));
}
return Ok(DispatchAction::Continue);
}
drop(pm);
{
let pm = map.borrow();
if !pm.extensible && !pm.contains_key(prop_name.as_ref()) {
if ctx.frame.bytecode_array.is_strict() {
return Err(StatorError::TypeError(format!(
"Cannot add property {prop_name}, object is not extensible"
)));
}
return Ok(DispatchAction::Continue);
}
}
set_function_name_if_missing(&val, &prop_name);
let fill_result = map.borrow_mut().try_template_fill(&prop_name, val);
match fill_result {
Ok(offset) => {
if slot != u32::MAX && offset <= u16::MAX as usize {
let receiver_layout = map.borrow().layout_id();
ctx.frame.mega_store_ic_mut().update(MegamorphicIcEntry {
slot,
receiver_layout,
cached_offset: offset as u16,
is_proto: false,
proto_layout: 0,
});
ctx.frame.mono_load_cache_mut().insert(
slot,
receiver_layout as usize,
offset as i32,
);
}
let map_ptr = Rc::as_ptr(map) as usize;
if let Some(cache) = &mut ctx.frame.mono_load_cache {
cache.invalidate_layout(map_ptr);
}
if let Some(cache) = &mut ctx.frame.poly_load_cache {
cache.retain(|_, entries| {
entries.retain(|(ptr, _)| *ptr != map_ptr);
!entries.is_empty()
});
}
return Ok(DispatchAction::Continue);
}
Err(val) => map.borrow_mut().insert_rc(Rc::clone(&prop_name), val),
}
if slot != u32::MAX {
let pm = map.borrow();
if let Some(offset) = pm.offset_of(&prop_name)
&& offset <= u16::MAX as usize
{
ctx.frame.mega_store_ic_mut().update(MegamorphicIcEntry {
slot,
receiver_layout: pm.layout_id(),
cached_offset: offset as u16,
is_proto: false,
proto_layout: 0,
});
ctx.frame.mono_load_cache_mut().insert(
slot,
pm.layout_id() as usize,
offset as i32,
);
}
}
let map_ptr = Rc::as_ptr(map) as usize;
if let Some(cache) = &mut ctx.frame.mono_load_cache {
cache.invalidate_layout(map_ptr);
}
if let Some(cache) = &mut ctx.frame.poly_load_cache {
cache.retain(|_, entries| {
entries.retain(|(ptr, _)| *ptr != map_ptr);
!entries.is_empty()
});
}
}
JsValue::Function(ref ba) => {
if matches!(fn_props_get(ba, &prop_name), JsValue::Undefined) {
set_function_name_if_missing(&val, &prop_name);
}
fn_props_set(ba, prop_name.to_string(), val);
}
JsValue::Array(ref arr) => {
if prop_name.as_ref() == "length" {
let new_len = val.to_number()?;
let new_len_u32 = new_len as u32;
if (new_len_u32 as f64) != new_len || new_len < 0.0 || !new_len.is_finite() {
return Err(StatorError::RangeError("Invalid array length".to_string()));
}
let mut v = arr.borrow_mut();
let current_len = v.len();
if (new_len_u32 as usize) < current_len {
v.truncate(new_len_u32 as usize);
} else {
v.resize(new_len_u32 as usize, JsValue::TheHole);
}
}
}
JsValue::Error(ref e) => {
e.props.borrow_mut().insert_rc(Rc::clone(&prop_name), val);
}
_ => {}
}
Ok(DispatchAction::Continue)
}
fn handle_lda_keyed_property(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(obj_v) = *instr.operand(0) else {
return Err(err_bad_operand("LdaKeyedProperty", 0));
};
if let JsValue::Smi(idx) = &ctx.frame.accumulator
&& *idx >= 0
{
let idx_val = *idx;
let obj = ctx.frame.read_reg(obj_v)?;
if let JsValue::Array(items) = obj {
let data = unsafe { &*items.as_ptr() };
let i = idx_val as usize;
ctx.frame.accumulator = if i < data.len() {
let v = unsafe { data.get_unchecked(i) };
if v.is_the_hole() {
JsValue::Undefined
} else {
v.cheap_clone()
}
} else {
JsValue::Undefined
};
return Ok(DispatchAction::Continue);
} else if let JsValue::PlainObject(map) = obj {
let borrow = map.borrow();
let key_str = itoa_stack(idx_val as u32);
if let Some(val) = borrow.get(key_str.as_str()) {
let result = val.cheap_clone();
drop(borrow);
ctx.frame.accumulator = result;
return Ok(DispatchAction::Continue);
}
}
}
let obj = ctx.frame.read_reg(obj_v)?.cheap_clone();
let key = ctx.frame.accumulator.cheap_clone();
if matches!(obj, JsValue::Null | JsValue::Undefined) {
let key_str = key.to_js_string().unwrap_or_default();
return Err(StatorError::TypeError(format!(
"Cannot read properties of {} (reading '{key_str}')",
if matches!(obj, JsValue::Null) {
"null"
} else {
"undefined"
}
)));
}
ctx.frame.accumulator = keyed_load(&obj, &key)?;
Ok(DispatchAction::Continue)
}
fn handle_sta_keyed_property(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(obj_v) = *instr.operand(0) else {
return Err(err_bad_operand("StaKeyedProperty", 0));
};
let Operand::Register(key_v) = *instr.operand(1) else {
return Err(err_bad_operand("StaKeyedProperty", 1));
};
if let JsValue::Smi(idx) = ctx.frame.read_reg(key_v)?
&& *idx >= 0
{
let idx_val = *idx;
let val = ctx.frame.accumulator.cheap_clone();
let obj_ref = ctx.frame.read_reg(obj_v)?;
if let JsValue::Array(items) = obj_ref {
let i = idx_val as usize;
let v = unsafe { &mut *items.as_ptr() };
if i < v.len() {
unsafe { *v.get_unchecked_mut(i) = val };
} else if i == v.len() {
v.push(val);
} else {
v.resize(i, JsValue::TheHole);
v.push(val);
}
return Ok(DispatchAction::Continue);
} else if let JsValue::PlainObject(map) = obj_ref {
let m = unsafe { &mut *map.as_ptr() };
let key_str = itoa_stack(idx_val as u32);
m.insert(key_str.as_str().to_owned(), val);
let i = idx_val as usize;
let cur_len = match m.get("length") {
Some(JsValue::Smi(n)) => *n as usize,
_ => 0,
};
if i >= cur_len {
m.insert("length".to_owned(), JsValue::Smi((i + 1) as i32));
}
return Ok(DispatchAction::Continue);
}
}
let obj = ctx.frame.read_reg(obj_v)?.cheap_clone();
let key = ctx.frame.read_reg(key_v)?.cheap_clone();
let val = ctx.frame.accumulator.cheap_clone();
if let JsValue::Array(arr) = &obj {
let is_index = match &key {
JsValue::Smi(i) => *i >= 0,
JsValue::String(s) => s.parse::<usize>().is_ok(),
_ => false,
};
let is_length = matches!(&key, JsValue::String(s) if &**s == "length");
if !is_index && !is_length {
let arr_ptr = Rc::as_ptr(arr);
let items = arr.borrow().clone();
let mut map = PropertyMap::new();
map.insert_with_attrs(
"__is_array__".to_string(),
JsValue::Boolean(true),
PropertyAttributes::empty(),
);
map.insert_with_attrs(
"length".to_string(),
JsValue::Smi(items.len() as i32),
PropertyAttributes::WRITABLE,
);
for (i, item) in items.iter().enumerate() {
if !item.is_the_hole() {
map.insert(i.to_string(), item.clone());
}
}
let prop_name = to_property_key(&key)?;
map.insert(prop_name, val);
let new_obj = JsValue::PlainObject(Rc::new(RefCell::new(map)));
ctx.frame.write_reg(obj_v, new_obj.clone())?;
let global_name = {
let env = ctx.frame.global_env.borrow();
env.vars.iter().find_map(|(name, v)| {
if let JsValue::Array(other) = v
&& Rc::as_ptr(other) == arr_ptr
{
return Some(name.clone());
}
None
})
};
if let Some(name) = global_name {
ctx.frame.global_env.borrow_mut().insert(name, new_obj);
ctx.frame.global_cache_invalidate();
}
return Ok(DispatchAction::Continue);
}
}
if matches!(obj, JsValue::Null | JsValue::Undefined) {
let key_str = key.to_js_string().unwrap_or_default();
return Err(StatorError::TypeError(format!(
"Cannot set properties of {} (setting '{key_str}')",
if matches!(obj, JsValue::Null) {
"null"
} else {
"undefined"
}
)));
}
if let JsValue::PlainObject(ref map) = obj {
let key_str = to_property_key(&key)?;
set_function_name_if_missing(&val, &key_str);
if map.borrow().has_accessors {
let setter = map.borrow().get_setter_for(&key_str).cloned();
if let Some(setter_fn) = setter {
dispatch_setter(&setter_fn, &obj, val)?;
ctx.frame.global_cache_invalidate();
return Ok(DispatchAction::Continue);
}
if map.borrow().has_getter_for(&key_str) {
if ctx.frame.bytecode_array.is_strict() {
return Err(StatorError::TypeError(format!(
"Cannot set property {key_str} which has only a getter"
)));
}
return Ok(DispatchAction::Continue);
}
}
{
let mut proto = map.borrow().get("__proto__").cloned();
for _ in 0..256 {
match proto.take() {
Some(JsValue::PlainObject(p)) => {
let pb = p.borrow();
if pb.has_accessors {
if let Some(s) = pb.get_setter_for(&key_str).cloned() {
drop(pb);
dispatch_setter(&s, &obj, val)?;
ctx.frame.global_cache_invalidate();
return Ok(DispatchAction::Continue);
}
if pb.has_getter_for(&key_str) {
if ctx.frame.bytecode_array.is_strict() {
return Err(StatorError::TypeError(format!(
"Cannot set property {key_str} which has only a getter"
)));
}
return Ok(DispatchAction::Continue);
}
}
proto = pb.get("__proto__").cloned();
}
_ => break,
}
}
}
let pm = map.borrow();
if pm.contains_key(&key_str) && !pm.is_writable(&key_str) {
if ctx.frame.bytecode_array.is_strict() {
return Err(StatorError::TypeError(format!(
"Cannot assign to read only property '{key_str}'"
)));
}
return Ok(DispatchAction::Continue);
}
if !pm.extensible && !pm.contains_key(&key_str) {
if ctx.frame.bytecode_array.is_strict() {
return Err(StatorError::TypeError(format!(
"Cannot add property {key_str}, object is not extensible"
)));
}
return Ok(DispatchAction::Continue);
}
drop(pm);
}
keyed_store(&obj, &key, val)?;
Ok(DispatchAction::Continue)
}
fn handle_get_iterator(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(iter_v) = *instr.operand(0) else {
return Err(err_bad_operand("GetIterator", 0));
};
let iterable = ctx.frame.read_reg(iter_v)?.cheap_clone();
ctx.frame.accumulator = match iterable {
JsValue::Array(items) => {
let items_vec: Vec<JsValue> = items
.borrow()
.iter()
.cloned()
.map(|value| {
if value.is_the_hole() {
JsValue::Undefined
} else {
value
}
})
.collect();
JsValue::Iterator(NativeIterator::from_items(items_vec))
}
JsValue::String(ref s) => JsValue::Iterator(NativeIterator::from_string(s)),
JsValue::Generator(_) | JsValue::Iterator(_) => iterable,
JsValue::PlainObject(ref map) => {
let borrow = map.borrow();
let iter_fn = borrow.get("@@iterator").cloned().or_else(|| {
let sym_key = format!("Symbol({})", crate::builtins::symbol::SYMBOL_ITERATOR);
borrow.get(&sym_key).cloned()
});
drop(borrow);
match iter_fn {
Some(ref f @ (JsValue::NativeFunction(_) | JsValue::Function(_))) => {
let result = dispatch_call_with_this(f, iterable.clone(), vec![])?;
match &result {
JsValue::PlainObject(_)
| JsValue::Iterator(_)
| JsValue::Generator(_)
| JsValue::Object(_)
| JsValue::Array(_) => result,
_ => {
return Err(StatorError::TypeError(
"Result of the Symbol.iterator method is not an object".into(),
));
}
}
}
Some(JsValue::PlainObject(ref call_obj))
if call_obj.borrow().contains_key("__call__") =>
{
let result = dispatch_call_with_this(
&JsValue::PlainObject(call_obj.clone()),
iterable.clone(),
vec![],
)?;
match &result {
JsValue::PlainObject(_)
| JsValue::Iterator(_)
| JsValue::Generator(_)
| JsValue::Object(_)
| JsValue::Array(_) => result,
_ => {
return Err(StatorError::TypeError(
"Result of the Symbol.iterator method is not an object".into(),
));
}
}
}
Some(_) => {
return Err(StatorError::TypeError(
"GetIterator: @@iterator is not a function".into(),
));
}
None => {
if map.borrow().contains_key("length") {
let items = plain_object_to_array_items(map);
JsValue::Iterator(NativeIterator::from_items(items))
} else {
return Err(StatorError::TypeError("object is not iterable".into()));
}
}
}
}
other => {
let desc = match &other {
JsValue::Null => "null".to_string(),
JsValue::Undefined => "undefined".to_string(),
JsValue::Smi(n) => n.to_string(),
JsValue::HeapNumber(n) => n.to_string(),
JsValue::Boolean(b) => b.to_string(),
_ => format!("{other:?}"),
};
return Err(StatorError::TypeError(format!("{desc} is not iterable")));
}
};
Ok(DispatchAction::Continue)
}
fn handle_get_async_iterator(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(iter_v) = *instr.operand(0) else {
return Err(err_bad_operand("GetAsyncIterator", 0));
};
let iterable = ctx.frame.read_reg(iter_v)?.cheap_clone();
ctx.frame.accumulator = match iterable {
JsValue::Array(items) => {
let items_vec: Vec<JsValue> = items.borrow().clone();
normalize_async_iterator(JsValue::Iterator(NativeIterator::from_items(items_vec)))
}
JsValue::String(ref s) => {
normalize_async_iterator(JsValue::Iterator(NativeIterator::from_string(s)))
}
JsValue::Generator(ref state) if state.borrow().bytecode_array.is_async() => iterable,
JsValue::Generator(_) | JsValue::Iterator(_) => normalize_async_iterator(iterable),
JsValue::PlainObject(ref map)
if map.borrow().contains_key("@@asyncIterator")
|| map.borrow().contains_key("Symbol(2)") =>
{
let iter_fn = map
.borrow()
.get("@@asyncIterator")
.cloned()
.or_else(|| map.borrow().get("Symbol(2)").cloned());
match iter_fn {
Some(ref f @ (JsValue::NativeFunction(_) | JsValue::Function(_))) => {
normalize_async_iterator(dispatch_call_with_this(f, iterable.clone(), vec![])?)
}
_ => {
return Err(StatorError::TypeError(
"GetAsyncIterator: @@asyncIterator is not a function".into(),
));
}
}
}
JsValue::PlainObject(ref map)
if map.borrow().contains_key("@@iterator")
|| map.borrow().contains_key("Symbol(1)") =>
{
let iter_fn = map
.borrow()
.get("@@iterator")
.cloned()
.or_else(|| map.borrow().get("Symbol(1)").cloned());
match iter_fn {
Some(ref f @ (JsValue::NativeFunction(_) | JsValue::Function(_))) => {
normalize_async_iterator(dispatch_call_with_this(f, iterable.clone(), vec![])?)
}
_ => {
return Err(StatorError::TypeError(
"GetAsyncIterator: @@iterator is not a function".into(),
));
}
}
}
JsValue::PlainObject(ref map) if map.borrow().contains_key("length") => {
let items = plain_object_to_array_items(map);
normalize_async_iterator(JsValue::Iterator(NativeIterator::from_items(items)))
}
JsValue::PlainObject(_) => {
return Err(StatorError::TypeError(
"GetAsyncIterator: value is not async iterable".into(),
));
}
other => {
return Err(StatorError::TypeError(format!(
"GetAsyncIterator: value is not iterable (got {other:?})"
)));
}
};
Ok(DispatchAction::Continue)
}
fn handle_iterator_next(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(iter_v) = *instr.operand(0) else {
return Err(err_bad_operand("IteratorNext", 0));
};
let Operand::Register(value_out_v) = *instr.operand(1) else {
return Err(err_bad_operand("IteratorNext", 1));
};
let iter = ctx.frame.read_reg(iter_v)?.cheap_clone();
let (value, done) = match iter {
JsValue::Iterator(ni) => match ni.borrow_mut().next_item() {
Some(v) => (v, false),
None => (JsValue::Undefined, true),
},
JsValue::Generator(gs) => {
if gs.borrow().bytecode_array.is_async() {
let queue = crate::builtins::promise::MicrotaskQueue::new();
let result = Interpreter::run_generator_step(&gs, JsValue::Undefined).map(
|step| match step {
GeneratorStep::Yield(v) => super::make_iterator_result(v, false),
GeneratorStep::Return(v) => super::make_iterator_result(v, true),
},
)?;
settle_async_iterator_result(result, &queue)
.map_err(super::async_iterator_reason_to_error)?
} else {
match Interpreter::run_generator_step(&gs, JsValue::Undefined)? {
GeneratorStep::Yield(v) => (v, false),
GeneratorStep::Return(v) => (v, true),
}
}
}
JsValue::PlainObject(ref map) if map.borrow().contains_key("next") => {
let next_fn = map.borrow().get("next").cloned();
match next_fn {
Some(ref f @ (JsValue::NativeFunction(_) | JsValue::Function(_))) => {
let result = dispatch_call_with_this(f, iter.clone(), vec![])?;
if map
.borrow()
.get("__async_iterator__")
.is_some_and(|flag| flag.to_boolean())
{
let queue = crate::builtins::promise::MicrotaskQueue::new();
settle_async_iterator_result(result, &queue)
.map_err(super::async_iterator_reason_to_error)?
} else {
match result {
JsValue::PlainObject(ref res_map) => {
let done =
res_map.borrow().get("done").is_some_and(|d| d.to_boolean());
let value = res_map
.borrow()
.get("value")
.cloned()
.unwrap_or(JsValue::Undefined);
(value, done)
}
_ => {
return Err(StatorError::TypeError(
"Iterator result is not an object".into(),
));
}
}
}
}
Some(ref f @ JsValue::PlainObject(ref call_obj))
if call_obj.borrow().contains_key("__call__") =>
{
let result = dispatch_call_with_this(f, iter.clone(), vec![])?;
if map
.borrow()
.get("__async_iterator__")
.is_some_and(|flag| flag.to_boolean())
{
let queue = crate::builtins::promise::MicrotaskQueue::new();
settle_async_iterator_result(result, &queue)
.map_err(super::async_iterator_reason_to_error)?
} else {
match result {
JsValue::PlainObject(ref res_map) => {
let done =
res_map.borrow().get("done").is_some_and(|d| d.to_boolean());
let value = res_map
.borrow()
.get("value")
.cloned()
.unwrap_or(JsValue::Undefined);
(value, done)
}
_ => {
return Err(StatorError::TypeError(
"Iterator result is not an object".into(),
));
}
}
}
}
_ => {
return Err(StatorError::TypeError(
"IteratorNext: next is not a function".into(),
));
}
}
}
other => {
return Err(StatorError::TypeError(format!(
"IteratorNext: value is not an iterator (got {other:?})"
)));
}
};
ctx.frame.write_reg(value_out_v, value)?;
ctx.frame.accumulator = JsValue::Boolean(done);
Ok(DispatchAction::Continue)
}
fn handle_iterator_close(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(iter_v) = *instr.operand(0) else {
return Err(err_bad_operand("IteratorClose", 0));
};
let iter = ctx.frame.read_reg(iter_v)?.cheap_clone();
match &iter {
JsValue::PlainObject(map) => {
let return_fn = map.borrow().get("return").cloned();
match return_fn {
Some(ref f @ (JsValue::NativeFunction(_) | JsValue::Function(_))) => {
let result =
dispatch_call_with_this(f, iter.clone(), vec![JsValue::Undefined])?;
if map
.borrow()
.get("__async_iterator__")
.is_some_and(|flag| flag.to_boolean())
{
let queue = crate::builtins::promise::MicrotaskQueue::new();
settle_async_iterator_result(result, &queue)
.map_err(super::async_iterator_reason_to_error)?;
} else if !result.is_object_like() {
return Err(StatorError::TypeError(
"Iterator .return() result is not an object".into(),
));
}
}
Some(JsValue::PlainObject(ref call_obj))
if call_obj.borrow().contains_key("__call__") =>
{
let result = dispatch_call_with_this(
&JsValue::PlainObject(call_obj.clone()),
iter.clone(),
vec![JsValue::Undefined],
)?;
if map
.borrow()
.get("__async_iterator__")
.is_some_and(|flag| flag.to_boolean())
{
let queue = crate::builtins::promise::MicrotaskQueue::new();
settle_async_iterator_result(result, &queue)
.map_err(super::async_iterator_reason_to_error)?;
} else if !result.is_object_like() {
return Err(StatorError::TypeError(
"Iterator .return() result is not an object".into(),
));
}
}
_ => {}
}
}
JsValue::Generator(gs) => {
let result = Interpreter::generator_return(gs, JsValue::Undefined)?;
if gs.borrow().bytecode_array.is_async() {
let queue = crate::builtins::promise::MicrotaskQueue::new();
settle_async_iterator_result(result, &queue)
.map_err(super::async_iterator_reason_to_error)?;
} else if !result.is_object_like() {
return Err(StatorError::TypeError(
"Iterator .return() result is not an object".into(),
));
}
}
_ => {}
}
Ok(DispatchAction::Continue)
}
fn handle_copy_data_properties(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(target_v) = *instr.operand(0) else {
return Err(err_bad_operand("CopyDataProperties", 0));
};
let Operand::Register(source_v) = *instr.operand(1) else {
return Err(err_bad_operand("CopyDataProperties", 1));
};
let target = ctx.frame.read_reg(target_v)?.cheap_clone();
let source = ctx.frame.read_reg(source_v)?.cheap_clone();
if let JsValue::PlainObject(t) = &target {
match &source {
JsValue::PlainObject(s) => {
let entries: Vec<(String, JsValue)> = s
.borrow()
.enumerable_iter()
.filter(|(k, _)| {
!((k.starts_with("__") && k.ends_with("__"))
|| k.starts_with("@@")
|| k.starts_with("Symbol(")
|| k.starts_with('.'))
})
.map(|(k, v)| (k.to_string(), v.clone()))
.collect();
for (k, v) in entries {
t.borrow_mut().insert(k, v);
}
}
JsValue::Array(items) => {
for (i, v) in items.borrow().iter().enumerate() {
t.borrow_mut().insert(i.to_string(), v.clone());
}
}
JsValue::String(s) => {
for i in 0..utf16_len(s) {
t.borrow_mut().insert(
i.to_string(),
JsValue::String(string_char_at(s, i as i64).into()),
);
}
}
_ => {}
}
}
Ok(DispatchAction::Continue)
}
#[cold]
fn handle_suspend_generator(
ctx: &mut DispatchContext,
_instr: &Instruction,
) -> StatorResult<DispatchAction> {
let yield_val = ctx.frame.accumulator.cheap_clone();
if let Some(gs_rc) = ctx.frame.generator_state.as_ref() {
let mut gs = gs_rc.borrow_mut();
gs.registers.clone_from(&ctx.frame.registers);
gs.resume_pc = ctx.frame.pc;
gs.status = GeneratorStatus::SuspendedAtYield;
}
ctx.frame.suspend_result = Some(yield_val.clone());
Ok(DispatchAction::Return(yield_val))
}
#[cold]
fn handle_resume_generator(
ctx: &mut DispatchContext,
_instr: &Instruction,
) -> StatorResult<DispatchAction> {
let mut throw_val: Option<JsValue> = None;
let mut return_val: Option<JsValue> = None;
if let Some(gs_rc) = ctx.frame.generator_state.as_ref() {
let mut gs = gs_rc.borrow_mut();
let count = gs.registers.len().min(ctx.frame.registers.len());
ctx.frame.registers[..count].clone_from_slice(&gs.registers[..count]);
gs.status = GeneratorStatus::Executing;
match std::mem::replace(&mut gs.resume_mode, GeneratorResumeMode::Normal) {
GeneratorResumeMode::Throw(val) => throw_val = Some(val),
GeneratorResumeMode::Return(val) => return_val = Some(val),
GeneratorResumeMode::Normal => {}
}
}
if let Some(val) = throw_val {
ctx.frame.accumulator = val.clone();
set_pending_exception(val.clone());
let msg = error_message_from_value(&val);
return Err(StatorError::JsException(msg));
}
if return_val.is_some() {
let sentinel = JsValue::String(super::GENERATOR_RETURN_SENTINEL.into());
ctx.frame.accumulator = sentinel.clone();
set_pending_exception(sentinel);
return Err(StatorError::JsException(
super::GENERATOR_RETURN_SENTINEL.into(),
));
}
Ok(DispatchAction::Continue)
}
#[cold]
fn handle_get_generator_state(
ctx: &mut DispatchContext,
_instr: &Instruction,
) -> StatorResult<DispatchAction> {
ctx.frame.accumulator = if let Some(gs_rc) = ctx.frame.generator_state.as_ref() {
JsValue::Smi(gs_rc.borrow().status.to_smi())
} else {
JsValue::Smi(GeneratorStatus::Completed.to_smi())
};
Ok(DispatchAction::Continue)
}
#[cold]
fn handle_set_generator_state(
ctx: &mut DispatchContext,
_instr: &Instruction,
) -> StatorResult<DispatchAction> {
if let Some(gs_rc) = ctx.frame.generator_state.as_ref()
&& let JsValue::Smi(n) = ctx.frame.accumulator
{
gs_rc.borrow_mut().status = GeneratorStatus::from_smi(n);
}
Ok(DispatchAction::Continue)
}
#[cold]
fn handle_switch_on_generator_state(
ctx: &mut DispatchContext,
_instr: &Instruction,
) -> StatorResult<DispatchAction> {
if let Some(gs_rc) = ctx.frame.generator_state.as_ref() {
let resume_pc = gs_rc.borrow().resume_pc;
if resume_pc > 0 {
ctx.frame.pc = resume_pc;
}
}
Ok(DispatchAction::Continue)
}
#[cold]
fn handle_debugger(
ctx: &mut DispatchContext,
_instr: &Instruction,
) -> StatorResult<DispatchAction> {
let stmt_offset = ctx.byte_offsets[ctx.frame.pc - 1] as u32;
if let Some(pause_err) = ACTIVE_DEBUGGER.with(|d| {
let opt = d.borrow();
opt.as_ref()
.map(|rc| rc.borrow_mut().on_debugger_statement(stmt_offset))
}) {
return Err(pause_err);
}
Ok(DispatchAction::Continue)
}
fn handle_type_of(ctx: &mut DispatchContext, _instr: &Instruction) -> StatorResult<DispatchAction> {
let type_str = match &ctx.frame.accumulator {
JsValue::Undefined | JsValue::TheHole => "undefined",
JsValue::Null => "object",
JsValue::Boolean(_) => "boolean",
JsValue::Smi(_) | JsValue::HeapNumber(_) => "number",
JsValue::String(_) => "string",
JsValue::Symbol(_) => "symbol",
JsValue::BigInt(_) => "bigint",
JsValue::Function(_) | JsValue::NativeFunction(_) => "function",
JsValue::Object(_) | JsValue::Array(_) | JsValue::Error(_) => "object",
JsValue::PlainObject(map) => {
if map.borrow().get("__call__").is_some() {
"function"
} else {
"object"
}
}
JsValue::Generator(_) => "object",
JsValue::Iterator(_) => "object",
JsValue::Promise(_) => "object",
JsValue::Context(_) => "object",
JsValue::Proxy(p) => {
let proxy = p.borrow();
if proxy.is_callable() {
"function"
} else {
"object"
}
}
JsValue::ArrayBuffer(_) | JsValue::TypedArray(_) | JsValue::DataView(_) => "object",
};
ctx.frame.accumulator = JsValue::String(type_str.to_owned().into());
Ok(DispatchAction::Continue)
}
fn handle_test_type_of(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Flag(flag) = *instr.operand(0) else {
return Err(err_bad_operand("TestTypeOf", 0));
};
let matches_type = match flag {
0 => matches!(
ctx.frame.accumulator,
JsValue::Smi(_) | JsValue::HeapNumber(_)
),
1 => matches!(ctx.frame.accumulator, JsValue::String(_)),
2 => matches!(ctx.frame.accumulator, JsValue::Symbol(_)),
3 => matches!(ctx.frame.accumulator, JsValue::Boolean(_)),
4 => matches!(ctx.frame.accumulator, JsValue::BigInt(_)),
5 => matches!(ctx.frame.accumulator, JsValue::Undefined | JsValue::TheHole),
6 => match &ctx.frame.accumulator {
JsValue::Function(_) | JsValue::NativeFunction(_) => true,
JsValue::PlainObject(map) => map.borrow().get("__call__").is_some(),
JsValue::Proxy(p) => p.borrow().is_callable(),
_ => false,
},
7 => match &ctx.frame.accumulator {
JsValue::Null
| JsValue::Object(_)
| JsValue::Array(_)
| JsValue::Error(_)
| JsValue::Generator(_)
| JsValue::Iterator(_)
| JsValue::Promise(_)
| JsValue::ArrayBuffer(_)
| JsValue::TypedArray(_)
| JsValue::DataView(_)
| JsValue::Context(_) => true,
JsValue::PlainObject(map) => map.borrow().get("__call__").is_none(),
JsValue::Proxy(p) => !p.borrow().is_callable(),
_ => false,
},
_ => false,
};
ctx.frame.accumulator = JsValue::Boolean(matches_type);
Ok(DispatchAction::Continue)
}
fn handle_to_number(
ctx: &mut DispatchContext,
_instr: &Instruction,
) -> StatorResult<DispatchAction> {
let n = ctx.frame.accumulator.to_number()?;
ctx.frame.accumulator = number_to_jsvalue(n);
Ok(DispatchAction::Continue)
}
fn handle_to_string(
ctx: &mut DispatchContext,
_instr: &Instruction,
) -> StatorResult<DispatchAction> {
let s = ctx.frame.accumulator.to_js_string()?;
ctx.frame.accumulator = JsValue::String(s.into());
Ok(DispatchAction::Continue)
}
fn handle_to_boolean(
ctx: &mut DispatchContext,
_instr: &Instruction,
) -> StatorResult<DispatchAction> {
let b = ctx.frame.accumulator.to_boolean();
ctx.frame.accumulator = JsValue::Boolean(b);
Ok(DispatchAction::Continue)
}
fn handle_to_object(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(dst) = *instr.operand(0) else {
return Err(err_bad_operand("ToObject", 0));
};
let wrapped = match &ctx.frame.accumulator {
JsValue::Null | JsValue::Undefined | JsValue::TheHole => {
return Err(StatorError::TypeError(
"Cannot convert undefined or null to object".to_string(),
));
}
JsValue::PlainObject(_)
| JsValue::Array(_)
| JsValue::Function(_)
| JsValue::NativeFunction(_)
| JsValue::Promise(_)
| JsValue::Generator(_)
| JsValue::Error(_)
| JsValue::Proxy(_)
| JsValue::Object(_)
| JsValue::Context(_)
| JsValue::Iterator(_)
| JsValue::ArrayBuffer(_)
| JsValue::TypedArray(_)
| JsValue::DataView(_) => ctx.frame.accumulator.cheap_clone(),
JsValue::Boolean(b) => {
let b_val = *b;
let mut map = PropertyMap::new();
map.insert("__wrapped__".into(), JsValue::Boolean(b_val));
map.insert(
"valueOf".into(),
JsValue::NativeFunction(Rc::new(move |_| Ok(JsValue::Boolean(b_val)))),
);
map.insert(
"toString".into(),
JsValue::NativeFunction(Rc::new(move |_| {
Ok(JsValue::String(if b_val { "true" } else { "false" }.into()))
})),
);
JsValue::PlainObject(Rc::new(RefCell::new(map)))
}
JsValue::Smi(n) => {
let n_val = *n;
let mut map = PropertyMap::new();
map.insert("__wrapped__".into(), JsValue::Smi(n_val));
map.insert(
"valueOf".into(),
JsValue::NativeFunction(Rc::new(move |_| Ok(JsValue::Smi(n_val)))),
);
map.insert(
"toString".into(),
JsValue::NativeFunction(Rc::new(move |_| {
Ok(JsValue::String(n_val.to_string().into()))
})),
);
JsValue::PlainObject(Rc::new(RefCell::new(map)))
}
JsValue::HeapNumber(n) => {
let n_val = *n;
let mut map = PropertyMap::new();
map.insert("__wrapped__".into(), JsValue::HeapNumber(n_val));
map.insert(
"valueOf".into(),
JsValue::NativeFunction(Rc::new(move |_| Ok(JsValue::HeapNumber(n_val)))),
);
map.insert(
"toString".into(),
JsValue::NativeFunction(Rc::new(move |_| {
Ok(JsValue::String(n_val.to_string().into()))
})),
);
JsValue::PlainObject(Rc::new(RefCell::new(map)))
}
JsValue::String(s) => {
let s_val = s.clone();
let mut map = PropertyMap::new();
map.insert("__wrapped__".into(), JsValue::String(s_val.clone()));
map.insert("length".into(), JsValue::Smi(utf16_len(&s_val) as i32));
for i in 0..utf16_len(&s_val) {
map.insert(
i.to_string(),
JsValue::String(string_char_at(&s_val, i as i64).into()),
);
}
let s_vo = s_val.clone();
map.insert(
"valueOf".into(),
JsValue::NativeFunction(Rc::new(move |_| Ok(JsValue::String(s_vo.clone())))),
);
let s_ts = s_val;
map.insert(
"toString".into(),
JsValue::NativeFunction(Rc::new(move |_| Ok(JsValue::String(s_ts.clone())))),
);
JsValue::PlainObject(Rc::new(RefCell::new(map)))
}
JsValue::Symbol(sym) => {
let sym_val = *sym;
let mut map = PropertyMap::new();
map.insert("__wrapped__".into(), JsValue::Symbol(sym_val));
map.insert(
"valueOf".into(),
JsValue::NativeFunction(Rc::new(move |_| Ok(JsValue::Symbol(sym_val)))),
);
map.insert(
"toString".into(),
JsValue::NativeFunction(Rc::new(move |_| {
Ok(JsValue::String(format!("Symbol({})", sym_val).into()))
})),
);
JsValue::PlainObject(Rc::new(RefCell::new(map)))
}
JsValue::BigInt(n) => {
let n_val = **n;
let mut map = PropertyMap::new();
map.insert("__wrapped__".into(), JsValue::BigInt(Box::new(n_val)));
map.insert(
"valueOf".into(),
JsValue::NativeFunction(Rc::new(move |_| Ok(JsValue::BigInt(Box::new(n_val))))),
);
map.insert(
"toString".into(),
JsValue::NativeFunction(Rc::new(move |_| {
Ok(JsValue::String(n_val.to_string().into()))
})),
);
JsValue::PlainObject(Rc::new(RefCell::new(map)))
}
};
ctx.frame.write_reg(dst, wrapped)?;
Ok(DispatchAction::Continue)
}
fn handle_to_name(ctx: &mut DispatchContext, instr: &Instruction) -> StatorResult<DispatchAction> {
let Operand::Register(dst) = *instr.operand(0) else {
return Err(err_bad_operand("ToName", 0));
};
let key = match &ctx.frame.accumulator {
JsValue::String(_) | JsValue::Symbol(_) => ctx.frame.accumulator.cheap_clone(),
other => {
let prim = other.to_primitive(crate::objects::value::ToPrimitiveHint::String)?;
match prim {
JsValue::String(s) => JsValue::String(s),
JsValue::Symbol(_) => prim,
_ => JsValue::String(prim.to_js_string()?.into()),
}
}
};
ctx.frame.write_reg(dst, key)?;
Ok(DispatchAction::Continue)
}
fn handle_negate(ctx: &mut DispatchContext, _instr: &Instruction) -> StatorResult<DispatchAction> {
if let JsValue::BigInt(n) = &ctx.frame.accumulator {
ctx.frame.accumulator = JsValue::BigInt(Box::new(n.wrapping_neg()));
} else {
let n = ctx.frame.accumulator.to_number()?;
ctx.frame.accumulator = number_to_jsvalue(-n);
}
Ok(DispatchAction::Continue)
}
fn handle_bitwise_not(
ctx: &mut DispatchContext,
_instr: &Instruction,
) -> StatorResult<DispatchAction> {
if let JsValue::BigInt(n) = &ctx.frame.accumulator {
ctx.frame.accumulator = JsValue::BigInt(Box::new(!(**n)));
} else {
let n = ctx.frame.accumulator.to_int32()?;
ctx.frame.accumulator = JsValue::Smi(!n);
}
Ok(DispatchAction::Continue)
}
fn handle_create_empty_object_literal(
ctx: &mut DispatchContext,
_instr: &Instruction,
) -> StatorResult<DispatchAction> {
ctx.frame.accumulator =
JsValue::PlainObject(Rc::new(RefCell::new(PropertyMap::with_capacity(4))));
Ok(DispatchAction::Continue)
}
fn handle_create_empty_array_literal(
ctx: &mut DispatchContext,
_instr: &Instruction,
) -> StatorResult<DispatchAction> {
ctx.frame.accumulator = JsValue::Array(Rc::new(RefCell::new(Vec::new())));
Ok(DispatchAction::Continue)
}
fn handle_create_array_literal(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let capacity = match instr.operand_at(2) {
Some(Operand::Flag(hint)) => usize::from(*hint),
_ => 0,
};
ctx.frame.accumulator = JsValue::Array(Rc::new(RefCell::new(Vec::with_capacity(capacity))));
Ok(DispatchAction::Continue)
}
fn collect_from_plain_object_iterator(
iter_obj: &JsValue,
map: &Rc<RefCell<PropertyMap>>,
) -> StatorResult<Vec<JsValue>> {
let mut out = Vec::new();
loop {
let next_fn = map.borrow().get("next").cloned();
match next_fn {
Some(ref f) => {
let result = dispatch_call_with_this(f, iter_obj.clone(), vec![])?;
match result {
JsValue::PlainObject(ref res_map) => {
let done = res_map.borrow().get("done").is_some_and(|d| d.to_boolean());
if done {
break;
}
let value = res_map
.borrow()
.get("value")
.cloned()
.unwrap_or(JsValue::Undefined);
out.push(value);
}
_ => {
return Err(StatorError::TypeError(
"Iterator result is not an object".into(),
));
}
}
}
None => {
return Err(StatorError::TypeError(
"Iterator .next() is not a function".into(),
));
}
}
}
Ok(out)
}
fn handle_create_array_from_iterable(
ctx: &mut DispatchContext,
_instr: &Instruction,
) -> StatorResult<DispatchAction> {
let iterable = ctx.frame.accumulator.cheap_clone();
let items: Vec<JsValue> = match &iterable {
JsValue::Array(arr) => arr
.borrow()
.iter()
.map(|v| {
if v.is_the_hole() {
JsValue::Undefined
} else {
v.clone()
}
})
.collect(),
JsValue::Iterator(iter) => {
let mut out = Vec::new();
loop {
let mut it = iter.borrow_mut();
match it.next_item() {
Some(v) => out.push(v),
None => break,
}
}
out
}
JsValue::String(s) => s
.chars()
.map(|c| JsValue::String(c.to_string().into()))
.collect(),
JsValue::Generator(gs) => {
let mut out = Vec::new();
loop {
match Interpreter::run_generator_step(gs, JsValue::Undefined)? {
GeneratorStep::Yield(v) => out.push(v),
GeneratorStep::Return(v) => {
if !matches!(v, JsValue::Undefined) {
out.push(v);
}
break;
}
}
}
out
}
JsValue::PlainObject(map) => {
let iter_fn = {
let borrow = map.borrow();
borrow.get("@@iterator").cloned().or_else(|| {
let sym_key = format!("Symbol({})", crate::builtins::symbol::SYMBOL_ITERATOR);
borrow.get(&sym_key).cloned()
})
};
if let Some(ref f) = iter_fn {
let iter_obj = dispatch_call_with_this(f, iterable.clone(), vec![])?;
match &iter_obj {
JsValue::Iterator(ni) => {
let mut out = Vec::new();
while let Some(v) = ni.borrow_mut().next_item() {
out.push(v);
}
out
}
JsValue::Generator(gs) => {
let mut out = Vec::new();
loop {
match Interpreter::run_generator_step(gs, JsValue::Undefined)? {
GeneratorStep::Yield(v) => out.push(v),
GeneratorStep::Return(v) => {
if !matches!(v, JsValue::Undefined) {
out.push(v);
}
break;
}
}
}
out
}
JsValue::PlainObject(iter_map) if iter_map.borrow().contains_key("next") => {
collect_from_plain_object_iterator(&iter_obj, iter_map)?
}
_ => {
return Err(StatorError::TypeError(
"Result of the Symbol.iterator method is not an object".into(),
));
}
}
} else if map.borrow().contains_key("next") {
collect_from_plain_object_iterator(&iterable, map)?
} else if map.borrow().contains_key("length") {
plain_object_to_array_items(map)
} else {
return Err(StatorError::TypeError("object is not iterable".into()));
}
}
_ => {
return Err(StatorError::TypeError("object is not iterable".into()));
}
};
ctx.frame.accumulator = JsValue::Array(Rc::new(RefCell::new(items)));
Ok(DispatchAction::Continue)
}
fn handle_create_object_literal(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let slot = match instr.operand_at(1) {
Some(Operand::FeedbackSlot(s)) => Some(*s),
_ => None,
};
let capacity = match instr.operand_at(2) {
Some(Operand::Flag(count)) if *count > 0 => *count as usize,
_ => 4,
};
if let Some(slot) = slot {
if let Some(rc) = unsafe {
ctx.frame
.bytecode_array
.clone_object_literal_template_pooled_unchecked(slot)
} {
ctx.frame.accumulator = JsValue::PlainObject(rc);
return Ok(DispatchAction::Continue);
}
if let Some(rc) = ctx
.frame
.bytecode_array
.promote_object_literal_template_pooled(slot)
{
ctx.frame.accumulator = JsValue::PlainObject(rc);
return Ok(DispatchAction::Continue);
}
let map = PropertyMap::with_capacity(capacity);
let rc = Rc::new(RefCell::new(map));
unsafe {
ctx.frame
.bytecode_array
.set_object_literal_pending_unchecked(slot, Rc::clone(&rc));
}
ctx.frame.accumulator = JsValue::PlainObject(rc);
return Ok(DispatchAction::Continue);
}
ctx.frame.accumulator =
JsValue::PlainObject(Rc::new(RefCell::new(PropertyMap::with_capacity(capacity))));
Ok(DispatchAction::Continue)
}
fn handle_create_reg_exp_literal(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::ConstantPoolIdx(pattern_idx) = *instr.operand(0) else {
return Err(err_bad_operand("CreateRegExpLiteral", 0));
};
let Operand::Flag(flags_val) = *instr.operand(2) else {
return Err(err_bad_operand("CreateRegExpLiteral", 2));
};
let pattern = match ctx.frame.bytecode_array.get_constant(pattern_idx) {
Some(ConstantPoolEntry::String(s)) => decode_string_constant(s),
_ => String::new(),
};
let mut flags_str = String::new();
if flags_val & 0x01 != 0 {
flags_str.push('g');
}
if flags_val & 0x02 != 0 {
flags_str.push('i');
}
if flags_val & 0x04 != 0 {
flags_str.push('m');
}
if flags_val & 0x08 != 0 {
flags_str.push('s');
}
if flags_val & 0x10 != 0 {
flags_str.push('u');
}
if flags_val & 0x20 != 0 {
flags_str.push('v');
}
if flags_val & 0x40 != 0 {
flags_str.push('y');
}
if flags_val & 0x80 != 0 {
flags_str.push('d');
}
let re = crate::objects::regexp::JsRegExp::new(&pattern, &flags_str)?;
ctx.frame.accumulator = crate::builtins::regexp::wrap_regexp(re);
Ok(DispatchAction::Continue)
}
fn handle_sta_in_array_literal(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(arr_v) = *instr.operand(0) else {
return Err(err_bad_operand("StaInArrayLiteral", 0));
};
let Operand::Register(idx_v) = *instr.operand(1) else {
return Err(err_bad_operand("StaInArrayLiteral", 1));
};
let arr = ctx.frame.read_reg(arr_v)?.cheap_clone();
let key = ctx.frame.read_reg(idx_v)?.cheap_clone();
let val = ctx.frame.accumulator.cheap_clone();
if let JsValue::Array(ref items) = arr {
if let Some(idx) = to_array_index(&key) {
let mut v = items.borrow_mut();
if idx >= v.len() {
v.resize(idx + 1, JsValue::TheHole);
}
v[idx] = val;
}
} else if let JsValue::PlainObject(ref map) = arr {
let idx_str = to_property_key(&key)?;
map.borrow_mut().insert(idx_str, val);
if let Some(idx) = to_array_index(&key) {
let new_len = (idx + 1) as i32;
let cur_len = match map.borrow().get("length") {
Some(JsValue::Smi(n)) => *n,
_ => 0,
};
if new_len > cur_len {
map.borrow_mut().insert_with_attrs(
"length".to_string(),
JsValue::Smi(new_len),
PropertyAttributes::WRITABLE,
);
}
}
}
Ok(DispatchAction::Continue)
}
fn handle_define_named_own_property(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(obj_v) = *instr.operand(0) else {
return Err(err_bad_operand("DefineNamedOwnProperty", 0));
};
let Operand::ConstantPoolIdx(name_idx) = *instr.operand(1) else {
return Err(err_bad_operand("DefineNamedOwnProperty", 1));
};
let prop_name_ref = match ctx.frame.bytecode_array.get_constant(name_idx) {
Some(ConstantPoolEntry::String(s)) => s,
_ => {
return Err(StatorError::Internal(
"DefineNamedOwnProperty: property name is not a string".into(),
));
}
};
let val = ctx.frame.accumulator.cheap_clone();
let obj = ctx.frame.read_reg(obj_v)?.cheap_clone();
if let JsValue::PlainObject(ref map) = obj {
set_named_property_function_metadata(&val, &obj, prop_name_ref);
if let Some(attrs) = private_named_property_attrs(prop_name_ref) {
map.borrow_mut()
.insert_with_attrs(prop_name_ref.to_string(), val, attrs);
} else {
let pm = unsafe { &mut *map.as_ptr() };
let fill_result = pm.try_template_fill(prop_name_ref, val);
if let Err(val) = fill_result {
pm.insert(prop_name_ref.to_string(), val);
}
}
}
Ok(DispatchAction::Continue)
}
fn handle_define_keyed_own_property(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(obj_v) = *instr.operand(0) else {
return Err(err_bad_operand("DefineKeyedOwnProperty", 0));
};
let Operand::Register(key_v) = *instr.operand(1) else {
return Err(err_bad_operand("DefineKeyedOwnProperty", 1));
};
let obj = ctx.frame.read_reg(obj_v)?.cheap_clone();
let key = ctx.frame.read_reg(key_v)?.cheap_clone();
let val = ctx.frame.accumulator.cheap_clone();
if let JsValue::PlainObject(ref map) = obj {
let prop_name = to_property_key(&key)?;
set_named_property_function_metadata(&val, &obj, &prop_name);
map.borrow_mut().insert(prop_name, val);
}
Ok(DispatchAction::Continue)
}
fn handle_define_keyed_own_property_in_literal(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(obj_v) = *instr.operand(0) else {
return Err(err_bad_operand("DefineKeyedOwnPropertyInLiteral", 0));
};
let Operand::Register(key_v) = *instr.operand(1) else {
return Err(err_bad_operand("DefineKeyedOwnPropertyInLiteral", 1));
};
let obj = ctx.frame.read_reg(obj_v)?.cheap_clone();
let key = ctx.frame.read_reg(key_v)?.cheap_clone();
let val = ctx.frame.accumulator.cheap_clone();
if let JsValue::PlainObject(ref map) = obj {
let prop_name = to_property_key(&key)?;
set_named_property_function_metadata(&val, &obj, &prop_name);
map.borrow_mut().insert(prop_name, val);
}
Ok(DispatchAction::Continue)
}
fn handle_test_instance_of(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(v) = *instr.operand(0) else {
return Err(err_bad_operand("TestInstanceOf", 0));
};
let constructor = ctx.frame.read_reg(v)?.cheap_clone();
let rhs_callable = match &constructor {
JsValue::Function(_) | JsValue::NativeFunction(_) => true,
JsValue::PlainObject(map) => map.borrow().contains_key("__call__"),
JsValue::Proxy(p) => p.borrow().is_callable(),
_ => false,
};
if !rhs_callable {
return Err(StatorError::TypeError(
"Right-hand side of 'instanceof' is not callable".to_string(),
));
}
let has_instance_fn = match &constructor {
JsValue::PlainObject(map) => map.borrow().get("@@hasInstance").cloned(),
JsValue::NativeFunction(_) | JsValue::Function(_) => {
let v = proto_lookup(&constructor, "@@hasInstance");
if matches!(v, JsValue::Undefined) {
None
} else {
Some(v)
}
}
_ => None,
};
if let Some(ref hi) = has_instance_fn {
match hi {
JsValue::NativeFunction(f) => {
let result = f(vec![ctx.frame.accumulator.cheap_clone()])?;
ctx.frame.global_cache_invalidate();
ctx.frame.accumulator = JsValue::Boolean(result.to_boolean());
return Ok(DispatchAction::Continue);
}
JsValue::Function(_) | JsValue::PlainObject(_) => {
let result = dispatch_call_value(hi, vec![ctx.frame.accumulator.cheap_clone()])?;
ctx.frame.accumulator = JsValue::Boolean(result.to_boolean());
return Ok(DispatchAction::Continue);
}
_ => {}
}
}
if let JsValue::NativeFunction(ref ctor_fn) = constructor {
let acc = &ctx.frame.accumulator;
let global = ctx.frame.global_env.borrow();
type BuiltinCheck = (&'static str, fn(&JsValue) -> bool);
let builtin_checks: &[BuiltinCheck] = &[
("Array", |v| {
matches!(v, JsValue::Array(_))
|| matches!(v, JsValue::PlainObject(m) if m.borrow().get("__is_array__").is_some())
}),
("Function", |v| {
matches!(v, JsValue::Function(_) | JsValue::NativeFunction(_))
}),
("Promise", |v| matches!(v, JsValue::Promise(_))),
("Error", |v| matches!(v, JsValue::Error(_))),
(
"RegExp",
|v| matches!(v, JsValue::PlainObject(m) if m.borrow().get("__is_regexp__").is_some()),
),
(
"Date",
|v| matches!(v, JsValue::PlainObject(m) if m.borrow().get("__is_date__").is_some()),
),
(
"Map",
|v| matches!(v, JsValue::PlainObject(m) if m.borrow().get("__is_map__").is_some()),
),
(
"Set",
|v| matches!(v, JsValue::PlainObject(m) if m.borrow().get("__is_set__").is_some()),
),
(
"WeakMap",
|v| matches!(v, JsValue::PlainObject(m) if m.borrow().get("__is_weakmap__").is_some()),
),
(
"WeakSet",
|v| matches!(v, JsValue::PlainObject(m) if m.borrow().get("__is_weakset__").is_some()),
),
(
"WeakRef",
|v| matches!(v, JsValue::PlainObject(m) if m.borrow().get("__is_weakref__").is_some()),
),
];
for &(name, predicate) in builtin_checks {
if let Some(JsValue::NativeFunction(global_fn)) = global.get(name)
&& Rc::ptr_eq(ctor_fn, global_fn)
{
let result = predicate(acc);
drop(global);
ctx.frame.accumulator = JsValue::Boolean(result);
return Ok(DispatchAction::Continue);
}
}
let error_kinds: &[(&str, ErrorKind)] = &[
("TypeError", ErrorKind::TypeError),
("RangeError", ErrorKind::RangeError),
("ReferenceError", ErrorKind::ReferenceError),
("SyntaxError", ErrorKind::SyntaxError),
("URIError", ErrorKind::URIError),
("EvalError", ErrorKind::EvalError),
("AggregateError", ErrorKind::AggregateError),
];
for &(name, kind) in error_kinds {
if let Some(JsValue::NativeFunction(global_fn)) = global.get(name)
&& Rc::ptr_eq(ctor_fn, global_fn)
{
let is_match = matches!(acc, JsValue::Error(e) if e.kind == kind);
drop(global);
ctx.frame.accumulator = JsValue::Boolean(is_match);
return Ok(DispatchAction::Continue);
}
}
drop(global);
}
let ctor_proto = match &constructor {
JsValue::PlainObject(map) => map.borrow().get("prototype").cloned(),
JsValue::Function(_) | JsValue::NativeFunction(_) => {
let v = proto_lookup(&constructor, "prototype");
if matches!(v, JsValue::Undefined) {
None
} else {
Some(v)
}
}
_ => None,
};
let result = if let Some(proto_val) = ctor_proto {
has_prototype_in_chain(&ctx.frame.accumulator, &proto_val)
} else {
false
};
ctx.frame.accumulator = JsValue::Boolean(result);
Ok(DispatchAction::Continue)
}
fn handle_test_in(ctx: &mut DispatchContext, instr: &Instruction) -> StatorResult<DispatchAction> {
let Operand::Register(v) = *instr.operand(0) else {
return Err(err_bad_operand("TestIn", 0));
};
let object = ctx.frame.read_reg(v)?.cheap_clone();
let key = &ctx.frame.accumulator;
let result = match &object {
JsValue::Proxy(p) => {
let prop = to_property_key(key)?;
proxy_has(&p.borrow(), &prop)?
}
JsValue::PlainObject(_) => {
let prop = to_property_key(key)?;
!matches!(proto_lookup(&object, &prop), JsValue::Undefined)
}
JsValue::Array(items) => {
if let JsValue::String(s) = key
&& &**s == "length"
{
true
} else if let Some(idx) = to_array_index(key) {
items
.borrow()
.get(idx)
.is_some_and(|value| !value.is_the_hole())
} else {
let prop = to_property_key(key)?;
!matches!(proto_lookup(&object, &prop), JsValue::Undefined)
}
}
JsValue::TypedArray(ta) => {
if let JsValue::String(s) = key
&& &**s == "length"
{
true
} else if let Some(idx) = to_array_index(key) {
idx < ta.borrow().length
} else {
let prop = to_property_key(key)?;
!matches!(proto_lookup(&object, &prop), JsValue::Undefined)
}
}
JsValue::Function(_)
| JsValue::Error(_)
| JsValue::Promise(_)
| JsValue::Generator(_)
| JsValue::Iterator(_)
| JsValue::NativeFunction(_)
| JsValue::ArrayBuffer(_)
| JsValue::DataView(_)
| JsValue::Object(_) => {
let prop = to_property_key(key)?;
!matches!(proto_lookup(&object, &prop), JsValue::Undefined)
}
other => {
return Err(StatorError::TypeError(format!(
"Cannot use 'in' operator to search for '{}' in {}",
key.to_display_string(),
other.to_display_string()
)));
}
};
ctx.frame.accumulator = JsValue::Boolean(result);
Ok(DispatchAction::Continue)
}
fn handle_for_in_enumerate(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(obj_v) = *instr.operand(0) else {
return Err(err_bad_operand("ForInEnumerate", 0));
};
let obj = ctx.frame.read_reg(obj_v)?.cheap_clone();
let keys: Vec<JsValue> = match &obj {
JsValue::PlainObject(map) => {
let mut all_keys = Vec::new();
let mut seen = std::collections::HashSet::new();
let mut current_map = Some(Rc::clone(map));
for _ in 0..256 {
let Some(m) = current_map.take() else { break };
let borrow = m.borrow();
for (k, _val, attrs) in borrow.iter_with_attrs() {
let is_enumerable = attrs.contains(PropertyAttributes::ENUMERABLE);
if let Some(prop) = k.strip_prefix("__get_").and_then(|s| s.strip_suffix("__"))
{
seen.insert(k.clone());
if seen.insert(prop.to_string().into()) && is_enumerable {
all_keys.push(JsValue::String(prop.to_string().into()));
}
continue;
}
if let Some(prop) = k.strip_prefix("__set_").and_then(|s| s.strip_suffix("__"))
{
seen.insert(k.clone());
if seen.insert(prop.to_string().into()) && is_enumerable {
all_keys.push(JsValue::String(prop.to_string().into()));
}
continue;
}
if (k.starts_with("__") && k.ends_with("__"))
|| crate::builtins::symbol::is_symbol_property_key(k)
|| k.starts_with('.')
|| k.starts_with('#')
{
seen.insert(k.clone());
continue;
}
if seen.insert(k.clone()) && is_enumerable {
all_keys.push(JsValue::String(k.clone()));
}
}
current_map = borrow.get("__proto__").and_then(|v| {
if let JsValue::PlainObject(proto) = v {
Some(Rc::clone(proto))
} else {
None
}
});
}
all_keys
}
JsValue::Array(items) => (0..items.borrow().len())
.map(|i| JsValue::String(i.to_string().into()))
.collect(),
JsValue::String(s) => (0..utf16_len(s))
.map(|i| JsValue::String(i.to_string().into()))
.collect(),
JsValue::Null | JsValue::Undefined => vec![],
_ => vec![],
};
ctx.frame.accumulator = JsValue::new_array(keys);
Ok(DispatchAction::Continue)
}
fn is_for_in_excluded_key(key: &str) -> bool {
(key.starts_with("__") && key.ends_with("__"))
|| crate::builtins::symbol::is_symbol_property_key(key)
|| key.starts_with('.')
|| key.starts_with('#')
}
fn for_in_key_still_enumerable(obj: &JsValue, key: &str) -> bool {
if is_for_in_excluded_key(key) {
return false;
}
match obj {
JsValue::PlainObject(map) => {
let mut current_map = Some(Rc::clone(map));
for _ in 0..256 {
let Some(m) = current_map.take() else { break };
let borrow = m.borrow();
if let Some((_value, attrs)) = borrow.get_with_attrs(key) {
return attrs.contains(PropertyAttributes::ENUMERABLE);
}
current_map = borrow.get("__proto__").and_then(|v| {
if let JsValue::PlainObject(proto) = v {
Some(Rc::clone(proto))
} else {
None
}
});
}
false
}
JsValue::Array(items) => key
.parse::<usize>()
.ok()
.is_some_and(|idx| idx < items.borrow().len()),
JsValue::String(s) => key
.parse::<usize>()
.ok()
.is_some_and(|idx| idx < utf16_len(s)),
_ => false,
}
}
fn handle_for_in_prepare(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(keys_v) = *instr.operand(0) else {
return Err(err_bad_operand("ForInPrepare", 0));
};
let keys = ctx.frame.read_reg(keys_v)?.cheap_clone();
let len = match &keys {
JsValue::Array(items) => items.borrow().len() as i32,
_ => 0,
};
ctx.frame.accumulator = JsValue::Smi(len);
Ok(DispatchAction::Continue)
}
fn handle_for_in_next(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(_receiver_v) = *instr.operand(0) else {
return Err(err_bad_operand("ForInNext", 0));
};
let Operand::Register(idx_v) = *instr.operand(1) else {
return Err(err_bad_operand("ForInNext", 1));
};
let Operand::Register(keys_v) = *instr.operand(2) else {
return Err(err_bad_operand("ForInNext", 2));
};
let idx = match ctx.frame.read_reg(idx_v)? {
JsValue::Smi(n) => (*n).max(0) as usize,
_ => 0,
};
let obj = ctx.frame.read_reg(_receiver_v)?.cheap_clone();
let keys = ctx.frame.read_reg(keys_v)?.cheap_clone();
let key = match &keys {
JsValue::Array(items) => items
.borrow()
.get(idx)
.cloned()
.unwrap_or(JsValue::Undefined),
_ => JsValue::Undefined,
};
ctx.frame.accumulator = match key {
JsValue::String(key_string) if for_in_key_still_enumerable(&obj, &key_string) => {
JsValue::String(key_string)
}
other if !matches!(other, JsValue::String(_)) => other,
_ => JsValue::Undefined,
};
Ok(DispatchAction::Continue)
}
fn handle_for_in_step(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(idx_v) = *instr.operand(0) else {
return Err(err_bad_operand("ForInStep", 0));
};
let idx = match ctx.frame.read_reg(idx_v)? {
JsValue::Smi(n) => *n,
_ => 0,
};
ctx.frame.accumulator = JsValue::Smi(idx + 1);
Ok(DispatchAction::Continue)
}
fn handle_jump_if_for_in_done(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::JumpOffset(_delta) = *instr.operand(0) else {
return Err(err_bad_operand("JumpIfForInDone", 0));
};
let Operand::Register(idx_v) = *instr.operand(1) else {
return Err(err_bad_operand("JumpIfForInDone", 1));
};
let Operand::Register(len_v) = *instr.operand(2) else {
return Err(err_bad_operand("JumpIfForInDone", 2));
};
let idx = match ctx.frame.read_reg(idx_v)? {
JsValue::Smi(n) => *n,
_ => 0,
};
let len = match ctx.frame.read_reg(len_v)? {
JsValue::Smi(n) => *n,
_ => 0,
};
if idx >= len {
ctx.frame.pc = resolve_cached_jump(ctx, "JumpIfForInDone")?;
}
Ok(DispatchAction::Continue)
}
fn handle_delete_property_sloppy(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(obj_v) = *instr.operand(0) else {
return Err(err_bad_operand("DeletePropertySloppy", 0));
};
let key = to_property_key(&ctx.frame.accumulator)?;
let obj = ctx.frame.read_reg(obj_v)?.cheap_clone();
let removed = if let JsValue::Proxy(ref p) = obj {
proxy_delete_property(&mut p.borrow_mut(), &key)?
} else if let JsValue::PlainObject(ref map) = obj {
let pm = map.borrow();
if pm.contains_key(&key) {
if !pm.is_configurable(&key) {
false
} else {
drop(pm);
map.borrow_mut().remove(&key);
true
}
} else {
true
}
} else if let JsValue::Array(ref items) = obj {
if key == "length" {
false
} else if let Ok(idx) = key.parse::<usize>() {
let mut arr = items.borrow_mut();
if idx < arr.len() {
arr[idx] = JsValue::TheHole;
}
true
} else {
true
}
} else {
true
};
ctx.frame.accumulator = JsValue::Boolean(removed);
Ok(DispatchAction::Continue)
}
fn handle_delete_property_strict(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(obj_v) = *instr.operand(0) else {
return Err(err_bad_operand("DeletePropertyStrict", 0));
};
let key = to_property_key(&ctx.frame.accumulator)?;
let obj = ctx.frame.read_reg(obj_v)?.cheap_clone();
if let JsValue::Proxy(ref p) = obj {
let deleted = proxy_delete_property(&mut p.borrow_mut(), &key)?;
if !deleted {
return Err(StatorError::TypeError(format!(
"Cannot delete property '{key}'"
)));
}
} else if let JsValue::PlainObject(ref map) = obj {
let pm = map.borrow();
if pm.contains_key(&key) && !pm.is_configurable(&key) {
return Err(StatorError::TypeError(format!(
"Cannot delete property '{key}' of object"
)));
}
drop(pm);
map.borrow_mut().remove(&key);
} else if let JsValue::Array(ref items) = obj {
if key == "length" {
return Err(StatorError::TypeError(
"Cannot delete property 'length' of array".to_string(),
));
}
if let Ok(idx) = key.parse::<usize>() {
let mut arr = items.borrow_mut();
if idx < arr.len() {
arr[idx] = JsValue::TheHole;
}
}
}
ctx.frame.accumulator = JsValue::Boolean(true);
Ok(DispatchAction::Continue)
}
fn handle_create_rest_parameter(
ctx: &mut DispatchContext,
_instr: &Instruction,
) -> StatorResult<DispatchAction> {
let param_count = ctx.frame.bytecode_array.parameter_count() as usize;
let rest: Vec<JsValue> = if ctx.frame.call_args.len() > param_count {
ctx.frame.call_args[param_count..].to_vec()
} else {
vec![]
};
ctx.frame.accumulator = JsValue::new_array(rest);
Ok(DispatchAction::Continue)
}
fn handle_create_mapped_arguments(
ctx: &mut DispatchContext,
_instr: &Instruction,
) -> StatorResult<DispatchAction> {
let param_count = ctx.frame.bytecode_array.parameter_count() as usize;
let args: Vec<JsValue> = ctx
.frame
.call_args
.get(..param_count)
.unwrap_or(&[])
.to_vec();
let mut map = PropertyMap::new();
for (i, v) in args.iter().enumerate() {
map.insert(i.to_string(), v.clone());
}
map.insert_with_attrs(
"length".to_string(),
JsValue::Smi(args.len() as i32),
PropertyAttributes::WRITABLE | PropertyAttributes::CONFIGURABLE,
);
map.insert(
"callee".to_string(),
JsValue::Function(Rc::clone(&ctx.frame.bytecode_array)),
);
let args_for_iter = args.clone();
map.insert(
"@@iterator".to_string(),
JsValue::NativeFunction(Rc::new(move |_args: Vec<JsValue>| {
Ok(JsValue::Iterator(NativeIterator::from_items(
args_for_iter.clone(),
)))
})),
);
ctx.frame.accumulator = JsValue::PlainObject(Rc::new(RefCell::new(map)));
Ok(DispatchAction::Continue)
}
fn handle_create_unmapped_arguments(
ctx: &mut DispatchContext,
_instr: &Instruction,
) -> StatorResult<DispatchAction> {
let param_count = ctx.frame.bytecode_array.parameter_count() as usize;
let args: Vec<JsValue> = ctx
.frame
.call_args
.get(..param_count)
.unwrap_or(&[])
.to_vec();
let mut map = PropertyMap::new();
for (i, v) in args.iter().enumerate() {
map.insert(i.to_string(), v.clone());
}
map.insert_with_attrs(
"length".to_string(),
JsValue::Smi(args.len() as i32),
PropertyAttributes::WRITABLE | PropertyAttributes::CONFIGURABLE,
);
map.insert(
"callee".to_string(),
JsValue::NativeFunction(Rc::new(|_args: Vec<JsValue>| {
Err(StatorError::TypeError(
"'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them".into(),
))
})),
);
let args_for_iter = args.clone();
map.insert(
"@@iterator".to_string(),
JsValue::NativeFunction(Rc::new(move |_args: Vec<JsValue>| {
Ok(JsValue::Iterator(NativeIterator::from_items(
args_for_iter.clone(),
)))
})),
);
ctx.frame.accumulator = JsValue::PlainObject(Rc::new(RefCell::new(map)));
Ok(DispatchAction::Continue)
}
#[cold]
fn handle_throw_reference_error_if_hole(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::ConstantPoolIdx(name_idx) = *instr.operand(0) else {
return Err(err_bad_operand("ThrowReferenceErrorIfHole", 0));
};
if ctx.frame.accumulator == JsValue::TheHole {
let name = match ctx.frame.bytecode_array.get_constant(name_idx) {
Some(ConstantPoolEntry::String(s)) => s.clone(),
_ => "<unknown>".to_string(),
};
return Err(StatorError::ReferenceError(format!(
"Cannot access '{name}' before initialization"
)));
}
Ok(DispatchAction::Continue)
}
#[cold]
fn handle_throw_super_not_called_if_hole(
ctx: &mut DispatchContext,
_instr: &Instruction,
) -> StatorResult<DispatchAction> {
if ctx.frame.accumulator == JsValue::TheHole {
return Err(StatorError::ReferenceError(
"Must call super constructor in derived class \
before accessing 'this' or returning from \
derived constructor"
.to_string(),
));
}
Ok(DispatchAction::Continue)
}
#[cold]
fn handle_throw_super_already_called_if_not_hole(
ctx: &mut DispatchContext,
_instr: &Instruction,
) -> StatorResult<DispatchAction> {
if ctx.frame.accumulator != JsValue::TheHole {
return Err(StatorError::ReferenceError(
"Super constructor may only be called once".to_string(),
));
}
Ok(DispatchAction::Continue)
}
#[cold]
fn handle_throw_if_null_or_undefined(
ctx: &mut DispatchContext,
_instr: &Instruction,
) -> StatorResult<DispatchAction> {
match &ctx.frame.accumulator {
JsValue::Null => Err(StatorError::TypeError(
"Cannot destructure 'null' as it is null".to_string(),
)),
JsValue::Undefined => Err(StatorError::TypeError(
"Cannot destructure 'undefined' as it is undefined".to_string(),
)),
_ => Ok(DispatchAction::Continue),
}
}
fn handle_call_property0(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(callee_v) = *instr.operand(0) else {
return Err(err_bad_operand("CallProperty0", 0));
};
let Operand::Register(recv_v) = *instr.operand(1) else {
return Err(err_bad_operand("CallProperty0", 1));
};
let callee = ctx.frame.read_reg(callee_v)?.cheap_clone();
let this_val = ctx.frame.read_reg(recv_v)?.cheap_clone();
dispatch_call_property(ctx.frame, &callee, this_val, CallArgs::new())?;
Ok(DispatchAction::Continue)
}
fn handle_call_property1(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(callee_v) = *instr.operand(0) else {
return Err(err_bad_operand("CallProperty1", 0));
};
let Operand::Register(recv_v) = *instr.operand(1) else {
return Err(err_bad_operand("CallProperty1", 1));
};
let Operand::Register(arg0_v) = *instr.operand(2) else {
return Err(err_bad_operand("CallProperty1", 2));
};
let callee = ctx.frame.read_reg(callee_v)?.cheap_clone();
if let JsValue::PlainObject(callee_map) = &callee {
let obj = callee_map.borrow();
if let Some(JsValue::String(name)) = obj.get(FAST_ARRAY_METHOD_KEY)
&& &**name == "push"
{
drop(obj);
let recv = ctx.frame.read_reg(recv_v)?.cheap_clone();
if let JsValue::Array(arr) = recv {
let arg0 = ctx.frame.read_reg(arg0_v)?.cheap_clone();
let mut items = arr.borrow_mut();
if items.capacity() == 0 {
items.reserve(256);
}
items.push(arg0);
let new_len = items.len() as i32;
drop(items);
ctx.frame.accumulator = JsValue::Smi(new_len);
return Ok(DispatchAction::Continue);
}
}
}
let this_val = ctx.frame.read_reg(recv_v)?.cheap_clone();
let arg0 = ctx.frame.read_reg(arg0_v)?.cheap_clone();
dispatch_call_property(ctx.frame, &callee, this_val, smallvec![arg0])?;
Ok(DispatchAction::Continue)
}
fn handle_call_property2(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(callee_v) = *instr.operand(0) else {
return Err(err_bad_operand("CallProperty2", 0));
};
let Operand::Register(recv_v) = *instr.operand(1) else {
return Err(err_bad_operand("CallProperty2", 1));
};
let Operand::Register(arg0_v) = *instr.operand(2) else {
return Err(err_bad_operand("CallProperty2", 2));
};
let Operand::Register(arg1_v) = *instr.operand(3) else {
return Err(err_bad_operand("CallProperty2", 3));
};
let callee = ctx.frame.read_reg(callee_v)?.cheap_clone();
let this_val = ctx.frame.read_reg(recv_v)?.cheap_clone();
let arg0 = ctx.frame.read_reg(arg0_v)?.cheap_clone();
let arg1 = ctx.frame.read_reg(arg1_v)?.cheap_clone();
dispatch_call_property(ctx.frame, &callee, this_val, smallvec![arg0, arg1])?;
Ok(DispatchAction::Continue)
}
fn handle_call_runtime(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::RuntimeId(runtime_id) = *instr.operand(0) else {
return Err(err_bad_operand("CallRuntime", 0));
};
let Operand::Register(_args_start_v) = *instr.operand(1) else {
return Err(err_bad_operand("CallRuntime", 1));
};
let Operand::RegisterCount(_arg_count) = *instr.operand(2) else {
return Err(err_bad_operand("CallRuntime", 2));
};
if runtime_id == crate::bytecode::bytecode_generator::RUNTIME_DYNAMIC_IMPORT {
use crate::builtins::promise::{MicrotaskQueue, promise_reject};
let queue = MicrotaskQueue::new();
let reason = JsValue::Error(Rc::new(JsError::new(
ErrorKind::TypeError,
"dynamic import is not supported by this host".to_string(),
)));
let p = promise_reject(reason, &queue);
ctx.frame.accumulator = JsValue::Promise(p);
}
Ok(DispatchAction::Continue)
}
fn handle_sta_named_own_property(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(obj_v) = *instr.operand(0) else {
return Err(err_bad_operand("StaNamedOwnProperty", 0));
};
let Operand::ConstantPoolIdx(name_idx) = *instr.operand(1) else {
return Err(err_bad_operand("StaNamedOwnProperty", 1));
};
let prop_name = match ctx.frame.bytecode_array.get_constant(name_idx) {
Some(ConstantPoolEntry::String(s)) => s.clone(),
_ => {
return Err(StatorError::Internal(
"StaNamedOwnProperty: property name is \
not a string"
.into(),
));
}
};
let val = ctx.frame.accumulator.cheap_clone();
let obj = ctx.frame.read_reg(obj_v)?.cheap_clone();
if let JsValue::PlainObject(ref map) = obj {
map.borrow_mut().insert(prop_name, val);
}
Ok(DispatchAction::Continue)
}
fn with_object_is_unscopable(obj: &JsValue, name: &str) -> bool {
match proto_lookup(obj, "@@unscopables") {
JsValue::PlainObject(map) => matches!(map.borrow().get(name), Some(JsValue::Boolean(true))),
_ => false,
}
}
fn plain_object_has_property(map: &Rc<RefCell<PropertyMap>>, name: &str) -> bool {
let proto = {
let borrow = map.borrow();
if borrow.contains_key(name) {
return true;
}
borrow.get("__proto__").cloned()
};
proto.is_some_and(|value| with_object_has_binding(&value, name))
}
fn with_object_has_binding(obj: &JsValue, name: &str) -> bool {
if with_object_is_unscopable(obj, name) {
return false;
}
match obj {
JsValue::PlainObject(map) => {
plain_object_has_property(map, name)
|| !matches!(proto_lookup(obj, name), JsValue::Undefined)
}
_ => !matches!(proto_lookup(obj, name), JsValue::Undefined),
}
}
fn lookup_with_binding(context: &Option<JsValue>, name: &str) -> Option<JsValue> {
let Some(JsValue::Context(root)) = context else {
return None;
};
let mut current = Some(Rc::clone(root));
while let Some(ctx_rc) = current {
let (object, parent) = {
let borrow = ctx_rc.borrow();
(borrow.slots.first().cloned(), borrow.parent.clone())
};
if let Some(object) = object
&& with_object_has_binding(&object, name)
{
return Some(proto_lookup(&object, name));
}
current = parent;
}
None
}
fn store_with_binding(context: &Option<JsValue>, name: &str, value: &JsValue) -> bool {
let Some(JsValue::Context(root)) = context else {
return false;
};
let mut current = Some(Rc::clone(root));
while let Some(ctx_rc) = current {
let (object, parent) = {
let borrow = ctx_rc.borrow();
(borrow.slots.first().cloned(), borrow.parent.clone())
};
if let Some(JsValue::PlainObject(map)) = object
&& with_object_has_binding(&JsValue::PlainObject(Rc::clone(&map)), name)
{
map.borrow_mut().insert(name.into(), value.clone());
return true;
}
current = parent;
}
false
}
fn delete_with_binding(context: &Option<JsValue>, name: &str) -> Option<bool> {
let Some(JsValue::Context(root)) = context else {
return None;
};
let mut current = Some(Rc::clone(root));
while let Some(ctx_rc) = current {
let (object, parent) = {
let borrow = ctx_rc.borrow();
(borrow.slots.first().cloned(), borrow.parent.clone())
};
if let Some(JsValue::PlainObject(map)) = object
&& with_object_has_binding(&JsValue::PlainObject(Rc::clone(&map)), name)
{
map.borrow_mut().remove(name);
return Some(true);
}
current = parent;
}
None
}
fn handle_delete_lookup_slot(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::ConstantPoolIdx(name_idx) = *instr.operand(0) else {
return Err(err_bad_operand("DeleteLookupSlot", 0));
};
let name = ctx.frame.get_string_constant(name_idx)?;
ctx.frame.accumulator = if let Some(deleted) = delete_with_binding(&ctx.frame.context, &name) {
JsValue::Boolean(deleted)
} else {
JsValue::Boolean(true)
};
Ok(DispatchAction::Continue)
}
fn handle_sta_lookup_slot(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::ConstantPoolIdx(name_idx) = *instr.operand(0) else {
return Err(err_bad_operand("StaLookupSlot", 0));
};
let name = ctx.frame.get_string_constant(name_idx)?;
let val = ctx.frame.accumulator.cheap_clone();
if store_with_binding(&ctx.frame.context, &name, &val) {
return Ok(DispatchAction::Continue);
}
let mut env = ctx.frame.global_env.borrow_mut();
if ctx.frame.bytecode_array.is_strict() && !env.contains_key(&name) {
return Err(StatorError::ReferenceError(format!(
"{name} is not defined"
)));
}
env.insert(name.to_string(), val.clone());
drop(env);
ctx.frame.global_cache_put(&name, val);
Ok(DispatchAction::Continue)
}
fn handle_lda_lookup_slot(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::ConstantPoolIdx(name_idx) = *instr.operand(0) else {
return Err(err_bad_operand("LdaLookupSlot", 0));
};
let name = ctx.frame.get_string_constant(name_idx)?;
ctx.frame.accumulator = if let Some(value) = lookup_with_binding(&ctx.frame.context, &name) {
value
} else {
match ctx.frame.global_env.borrow().get(&name) {
Some(v) => v.clone(),
None => {
return Err(StatorError::ReferenceError(format!(
"{name} is not defined"
)));
}
}
};
Ok(DispatchAction::Continue)
}
fn handle_lda_lookup_slot_inside_typeof(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::ConstantPoolIdx(name_idx) = *instr.operand(0) else {
return Err(err_bad_operand("LdaLookupSlotInsideTypeof", 0));
};
let name = ctx.frame.get_string_constant(name_idx)?;
ctx.frame.accumulator = lookup_with_binding(&ctx.frame.context, &name)
.or_else(|| ctx.frame.global_env.borrow().get(&name).cloned())
.unwrap_or(JsValue::Undefined);
Ok(DispatchAction::Continue)
}
fn handle_lda_lookup_context_slot(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::ConstantPoolIdx(name_idx) = *instr.operand(0) else {
return Err(err_bad_operand("LdaLookupContextSlot", 0));
};
let Operand::ConstantPoolIdx(slot_idx) = *instr.operand(1) else {
return Err(err_bad_operand("LdaLookupContextSlot", 1));
};
let Operand::Immediate(depth) = *instr.operand(2) else {
return Err(err_bad_operand("LdaLookupContextSlot", 2));
};
if let Some(ctx_val) = &ctx.frame.context
&& let Ok(js_ctx) = extract_context(ctx_val, "LdaLookupContextSlot")
&& let Ok(target) = walk_context_chain(&js_ctx, depth as u32, "LdaLookupContextSlot")
{
let borrowed = target.borrow();
let slot = slot_idx as usize;
if let Some(val) = borrowed.slots.get(slot) {
ctx.frame.accumulator = val.clone();
return Ok(DispatchAction::Continue);
}
}
let name = ctx.frame.get_string_constant(name_idx)?;
ctx.frame.accumulator = match ctx.frame.global_env.borrow().get(&name) {
Some(v) => v.clone(),
None => {
return Err(StatorError::ReferenceError(format!(
"{name} is not defined"
)));
}
};
Ok(DispatchAction::Continue)
}
fn handle_lda_lookup_context_slot_inside_typeof(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::ConstantPoolIdx(name_idx) = *instr.operand(0) else {
return Err(err_bad_operand("LdaLookupContextSlotInsideTypeof", 0));
};
let Operand::ConstantPoolIdx(slot_idx) = *instr.operand(1) else {
return Err(err_bad_operand("LdaLookupContextSlotInsideTypeof", 1));
};
let Operand::Immediate(depth) = *instr.operand(2) else {
return Err(err_bad_operand("LdaLookupContextSlotInsideTypeof", 2));
};
if let Some(ctx_val) = &ctx.frame.context
&& let Ok(js_ctx) = extract_context(ctx_val, "LdaLookupContextSlotInsideTypeof")
&& let Ok(target) =
walk_context_chain(&js_ctx, depth as u32, "LdaLookupContextSlotInsideTypeof")
{
let borrowed = target.borrow();
let slot = slot_idx as usize;
if let Some(val) = borrowed.slots.get(slot) {
ctx.frame.accumulator = val.clone();
return Ok(DispatchAction::Continue);
}
}
let name = ctx.frame.get_string_constant(name_idx)?;
ctx.frame.accumulator = ctx
.frame
.global_env
.borrow()
.get(&name)
.cloned()
.unwrap_or(JsValue::Undefined);
Ok(DispatchAction::Continue)
}
fn handle_lda_lookup_global_slot(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::ConstantPoolIdx(name_idx) = *instr.operand(0) else {
return Err(err_bad_operand("LdaLookupGlobalSlot", 0));
};
let name = ctx.frame.get_string_constant(name_idx)?;
if let Some(val) = ctx.frame.global_cache_get(&name) {
ctx.frame.accumulator = val.clone();
return Ok(DispatchAction::Continue);
}
let lookup_result = ctx.frame.global_env.borrow().get(&name).cloned();
ctx.frame.accumulator = match lookup_result {
Some(v) => {
ctx.frame.global_cache_put(&name, v.clone());
v
}
None => {
return Err(StatorError::ReferenceError(format!(
"{name} is not defined"
)));
}
};
Ok(DispatchAction::Continue)
}
fn handle_lda_lookup_global_slot_inside_typeof(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::ConstantPoolIdx(name_idx) = *instr.operand(0) else {
return Err(err_bad_operand("LdaLookupGlobalSlotInsideTypeof", 0));
};
let name = ctx.frame.get_string_constant(name_idx)?;
if let Some(val) = ctx.frame.global_cache_get(&name) {
ctx.frame.accumulator = val.clone();
return Ok(DispatchAction::Continue);
}
let val = ctx
.frame
.global_env
.borrow()
.get(&name)
.cloned()
.unwrap_or(JsValue::Undefined);
ctx.frame.global_cache_put(&name, val.clone());
ctx.frame.accumulator = val;
Ok(DispatchAction::Continue)
}
fn handle_lda_named_property_from_super(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(_receiver_v) = *instr.operand(0) else {
return Err(err_bad_operand("LdaNamedPropertyFromSuper", 0));
};
let Operand::ConstantPoolIdx(name_idx) = *instr.operand(1) else {
return Err(err_bad_operand("LdaNamedPropertyFromSuper", 1));
};
let prop_name = ctx.frame.get_string_constant(name_idx)?;
let lookup_start = ctx.frame.accumulator.cheap_clone();
ctx.frame.accumulator = if matches!(lookup_start, JsValue::Undefined | JsValue::Null) {
JsValue::Undefined
} else {
try_proto_lookup_rc(&lookup_start, &prop_name)?
};
Ok(DispatchAction::Continue)
}
fn handle_get_template_object(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::ConstantPoolIdx(tpl_idx) = *instr.operand(0) else {
return Err(err_bad_operand("GetTemplateObject", 0));
};
let cache_key = ctx.byte_offsets[ctx.frame.pc - 1] as u32;
if let Some(cached) = ctx.frame.bytecode_array.cached_template_object(cache_key) {
ctx.frame.accumulator = cached;
} else {
let entry = ctx
.frame
.bytecode_array
.get_constant(tpl_idx)
.ok_or_else(|| {
StatorError::Internal(format!(
"GetTemplateObject: constant pool index {tpl_idx} out of bounds"
))
})?;
let tpl_val = constant_to_value(entry);
if let JsValue::PlainObject(ref map) = tpl_val {
let raw_clone = map.borrow().get("raw").cloned();
if let Some(JsValue::PlainObject(ref raw_map)) = raw_clone {
raw_map.borrow_mut().freeze();
}
map.borrow_mut().freeze();
}
ctx.frame
.bytecode_array
.cache_template_object(cache_key, tpl_val.clone());
ctx.frame.accumulator = tpl_val;
}
Ok(DispatchAction::Continue)
}
#[cold]
fn handle_set_pending_message(
ctx: &mut DispatchContext,
_instr: &Instruction,
) -> StatorResult<DispatchAction> {
std::mem::swap(&mut ctx.frame.accumulator, &mut ctx.frame.pending_message);
Ok(DispatchAction::Continue)
}
fn handle_test_reference_equal(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(v) = *instr.operand(0) else {
return Err(err_bad_operand("TestReferenceEqual", 0));
};
let rhs = ctx.frame.read_reg(v)?.cheap_clone();
let result = strict_eq(&ctx.frame.accumulator, &rhs);
ctx.frame.accumulator = JsValue::Boolean(result);
Ok(DispatchAction::Continue)
}
fn handle_test_undetectable(
ctx: &mut DispatchContext,
_instr: &Instruction,
) -> StatorResult<DispatchAction> {
let result = matches!(ctx.frame.accumulator, JsValue::Null | JsValue::Undefined);
ctx.frame.accumulator = JsValue::Boolean(result);
Ok(DispatchAction::Continue)
}
fn handle_jump_if_js_receiver(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::JumpOffset(_delta) = *instr.operand(0) else {
return Err(err_bad_operand("JumpIfJSReceiver", 0));
};
if is_js_receiver(&ctx.frame.accumulator) {
ctx.frame.pc = resolve_cached_jump(ctx, "JumpIfJSReceiver")?;
}
Ok(DispatchAction::Continue)
}
fn handle_jump_if_js_receiver_constant(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::ConstantPoolIdx(idx) = *instr.operand(0) else {
return Err(err_bad_operand("JumpIfJSReceiverConstant", 0));
};
if is_js_receiver(&ctx.frame.accumulator) {
let delta = constant_pool_jump_delta(ctx.frame, idx, "JumpIfJSReceiverConstant")?;
ctx.frame.pc = resolve_jump(
ctx.frame.pc,
delta,
ctx.byte_offsets,
ctx.instructions.len(),
)?;
}
Ok(DispatchAction::Continue)
}
fn handle_to_numeric(
ctx: &mut DispatchContext,
_instr: &Instruction,
) -> StatorResult<DispatchAction> {
if !matches!(ctx.frame.accumulator, JsValue::BigInt(_)) {
let n = ctx.frame.accumulator.to_number()?;
ctx.frame.accumulator = number_to_jsvalue(n);
}
Ok(DispatchAction::Continue)
}
#[cold]
fn handle_wide(_ctx: &mut DispatchContext, _instr: &Instruction) -> StatorResult<DispatchAction> {
Err(StatorError::Internal(
"Wide/ExtraWide prefix should not appear as a decoded opcode".into(),
))
}
fn handle_jump_constant(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::ConstantPoolIdx(idx) = *instr.operand(0) else {
return Err(err_bad_operand("JumpConstant", 0));
};
let delta = constant_pool_jump_delta(ctx.frame, idx, "JumpConstant")?;
ctx.frame.pc = resolve_jump(
ctx.frame.pc,
delta,
ctx.byte_offsets,
ctx.instructions.len(),
)?;
Ok(DispatchAction::Continue)
}
fn handle_jump_if_true_constant(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::ConstantPoolIdx(idx) = *instr.operand(0) else {
return Err(err_bad_operand("JumpIfTrueConstant", 0));
};
if matches!(ctx.frame.accumulator, JsValue::Boolean(true)) {
let delta = constant_pool_jump_delta(ctx.frame, idx, "JumpIfTrueConstant")?;
ctx.frame.pc = resolve_jump(
ctx.frame.pc,
delta,
ctx.byte_offsets,
ctx.instructions.len(),
)?;
}
Ok(DispatchAction::Continue)
}
fn handle_jump_if_false_constant(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::ConstantPoolIdx(idx) = *instr.operand(0) else {
return Err(err_bad_operand("JumpIfFalseConstant", 0));
};
if matches!(ctx.frame.accumulator, JsValue::Boolean(false)) {
let delta = constant_pool_jump_delta(ctx.frame, idx, "JumpIfFalseConstant")?;
ctx.frame.pc = resolve_jump(
ctx.frame.pc,
delta,
ctx.byte_offsets,
ctx.instructions.len(),
)?;
}
Ok(DispatchAction::Continue)
}
fn handle_jump_if_to_boolean_true_constant(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::ConstantPoolIdx(idx) = *instr.operand(0) else {
return Err(err_bad_operand("JumpIfToBooleanTrueConstant", 0));
};
if ctx.frame.accumulator.to_boolean() {
let delta = constant_pool_jump_delta(ctx.frame, idx, "JumpIfToBooleanTrueConstant")?;
ctx.frame.pc = resolve_jump(
ctx.frame.pc,
delta,
ctx.byte_offsets,
ctx.instructions.len(),
)?;
}
Ok(DispatchAction::Continue)
}
fn handle_jump_if_to_boolean_false_constant(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::ConstantPoolIdx(idx) = *instr.operand(0) else {
return Err(err_bad_operand("JumpIfToBooleanFalseConstant", 0));
};
if !ctx.frame.accumulator.to_boolean() {
let delta = constant_pool_jump_delta(ctx.frame, idx, "JumpIfToBooleanFalseConstant")?;
ctx.frame.pc = resolve_jump(
ctx.frame.pc,
delta,
ctx.byte_offsets,
ctx.instructions.len(),
)?;
}
Ok(DispatchAction::Continue)
}
fn handle_jump_if_null_constant(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::ConstantPoolIdx(idx) = *instr.operand(0) else {
return Err(err_bad_operand("JumpIfNullConstant", 0));
};
if ctx.frame.accumulator.is_null() {
let delta = constant_pool_jump_delta(ctx.frame, idx, "JumpIfNullConstant")?;
ctx.frame.pc = resolve_jump(
ctx.frame.pc,
delta,
ctx.byte_offsets,
ctx.instructions.len(),
)?;
}
Ok(DispatchAction::Continue)
}
fn handle_jump_if_not_null_constant(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::ConstantPoolIdx(idx) = *instr.operand(0) else {
return Err(err_bad_operand("JumpIfNotNullConstant", 0));
};
if !ctx.frame.accumulator.is_null() {
let delta = constant_pool_jump_delta(ctx.frame, idx, "JumpIfNotNullConstant")?;
ctx.frame.pc = resolve_jump(
ctx.frame.pc,
delta,
ctx.byte_offsets,
ctx.instructions.len(),
)?;
}
Ok(DispatchAction::Continue)
}
fn handle_jump_if_undefined_constant(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::ConstantPoolIdx(idx) = *instr.operand(0) else {
return Err(err_bad_operand("JumpIfUndefinedConstant", 0));
};
if ctx.frame.accumulator.is_undefined() {
let delta = constant_pool_jump_delta(ctx.frame, idx, "JumpIfUndefinedConstant")?;
ctx.frame.pc = resolve_jump(
ctx.frame.pc,
delta,
ctx.byte_offsets,
ctx.instructions.len(),
)?;
}
Ok(DispatchAction::Continue)
}
fn handle_jump_if_not_undefined_constant(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::ConstantPoolIdx(idx) = *instr.operand(0) else {
return Err(err_bad_operand("JumpIfNotUndefinedConstant", 0));
};
if !ctx.frame.accumulator.is_undefined() {
let delta = constant_pool_jump_delta(ctx.frame, idx, "JumpIfNotUndefinedConstant")?;
ctx.frame.pc = resolve_jump(
ctx.frame.pc,
delta,
ctx.byte_offsets,
ctx.instructions.len(),
)?;
}
Ok(DispatchAction::Continue)
}
fn handle_jump_if_undefined_or_null_constant(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::ConstantPoolIdx(idx) = *instr.operand(0) else {
return Err(err_bad_operand("JumpIfUndefinedOrNullConstant", 0));
};
if ctx.frame.accumulator.is_nullish() {
let delta = constant_pool_jump_delta(ctx.frame, idx, "JumpIfUndefinedOrNullConstant")?;
ctx.frame.pc = resolve_jump(
ctx.frame.pc,
delta,
ctx.byte_offsets,
ctx.instructions.len(),
)?;
}
Ok(DispatchAction::Continue)
}
fn handle_call_js_runtime(
_ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::ConstantPoolIdx(_ctx_idx) = *instr.operand(0) else {
return Err(err_bad_operand("CallJSRuntime", 0));
};
Ok(DispatchAction::Continue)
}
fn handle_invoke_intrinsic(
_ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::RuntimeId(_runtime_id) = *instr.operand(0) else {
return Err(err_bad_operand("InvokeIntrinsic", 0));
};
Ok(DispatchAction::Continue)
}
fn handle_call_runtime_for_pair(
_ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::RuntimeId(_runtime_id) = *instr.operand(0) else {
return Err(err_bad_operand("CallRuntimeForPair", 0));
};
Ok(DispatchAction::Continue)
}
fn handle_construct_forward_all_args(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(ctor_v) = *instr.operand(0) else {
return Err(err_bad_operand("ConstructForwardAllArgs", 0));
};
let ctor = ctx.frame.read_reg(ctor_v)?.cheap_clone();
let param_count = ctx.frame.bytecode_array.parameter_count() as usize;
let args: CallArgs = ctx
.frame
.registers
.get(..param_count)
.unwrap_or(&[])
.iter()
.cloned()
.collect();
match ctor {
JsValue::Function(ba) => {
if ba.is_arrow() {
return Err(StatorError::TypeError(
"Function is not a constructor".to_string(),
));
}
let ctor_proto = resolve_construct_proto(&JsValue::Function(Rc::clone(&ba)), &ba);
let this_val = make_construct_this(&ba, &ctor_proto);
let mut callee_frame =
acquire_frame(Rc::clone(&ba), args, Rc::clone(&ctx.frame.global_env));
restore_closure_context(&mut callee_frame, &ba);
callee_frame.new_target = JsValue::Function(Rc::clone(&ba));
callee_frame
.global_env
.borrow_mut()
.set_this(this_val.clone());
push_call_frame("<anonymous>")?;
let result = run_callee_pooled(callee_frame);
pop_call_frame();
ctx.frame.global_cache_invalidate();
let val = result?;
ctx.frame.accumulator = match val {
JsValue::PlainObject(_) | JsValue::Object(_) => val,
_ => {
maybe_cache_construct_boilerplate(&ba, &this_val);
this_val
}
};
}
JsValue::NativeFunction(f) => {
let ctor_proto = proto_lookup(&JsValue::NativeFunction(Rc::clone(&f)), "prototype");
let val = f(args.into_vec())?;
ctx.frame.global_cache_invalidate();
ctx.frame.accumulator = construct_builtin_result(val, &ctor_proto)?;
}
JsValue::PlainObject(ref map) => {
if map.borrow().get("__no_construct__").is_some() {
return Err(StatorError::TypeError(
"Symbol is not a constructor".to_string(),
));
}
let ctor_proto = proto_lookup(&ctor, "prototype");
let call_val = map.borrow().get("__call__").cloned();
match call_val {
Some(JsValue::NativeFunction(f)) => {
let val = f(args.into_vec())?;
ctx.frame.global_cache_invalidate();
ctx.frame.accumulator = construct_builtin_result(val, &ctor_proto)?;
}
Some(JsValue::Function(ba)) => {
construct_class_from_plain_object(ctx, &ba, map, &ctor_proto, args)?;
}
_ => {
return Err(StatorError::TypeError(format!(
"ConstructForwardAllArgs: constructor is not a function (got {other:?})",
other = JsValue::PlainObject(Rc::clone(map))
)));
}
}
}
other => {
return Err(StatorError::TypeError(format!(
"ConstructForwardAllArgs: constructor is not a function (got {other:?})"
)));
}
}
Ok(DispatchAction::Continue)
}
#[cold]
fn handle_collect_type_profile(
_ctx: &mut DispatchContext,
_instr: &Instruction,
) -> StatorResult<DispatchAction> {
Ok(DispatchAction::Continue)
}
fn handle_create_object_from_iterable(
ctx: &mut DispatchContext,
_instr: &Instruction,
) -> StatorResult<DispatchAction> {
let iterable = ctx.frame.accumulator.cheap_clone();
let map: PropertyMap = match &iterable {
JsValue::PlainObject(obj) => obj.borrow().clone(),
JsValue::Array(arr) => {
let mut m = PropertyMap::new();
for (i, v) in arr.borrow().iter().enumerate() {
m.insert(i.to_string(), v.clone());
}
m.insert(
"length".to_string(),
JsValue::Smi(arr.borrow().len() as i32),
);
m
}
JsValue::Iterator(iter) => {
let mut m = PropertyMap::new();
let mut idx = 0usize;
loop {
let mut it = iter.borrow_mut();
match it.next_item() {
Some(v) => {
m.insert(idx.to_string(), v);
idx += 1;
}
None => break,
}
}
m.insert("length".to_string(), JsValue::Smi(idx as i32));
m
}
_ => PropertyMap::new(),
};
ctx.frame.accumulator = JsValue::PlainObject(Rc::new(RefCell::new(map)));
Ok(DispatchAction::Continue)
}
fn handle_call_direct_eval(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(callee_v) = *instr.operand(0) else {
return Err(err_bad_operand("CallDirectEval", 0));
};
let Operand::Register(args_start_v) = *instr.operand(1) else {
return Err(err_bad_operand("CallDirectEval", 1));
};
let Operand::RegisterCount(arg_count) = *instr.operand(2) else {
return Err(err_bad_operand("CallDirectEval", 2));
};
let callee = ctx.frame.read_reg(callee_v)?.cheap_clone();
let args = collect_args(ctx.frame, args_start_v, arg_count)?;
let is_builtin = if let JsValue::NativeFunction(ref callee_fn) = callee {
if let Some(JsValue::NativeFunction(ref global_fn)) =
ctx.frame.global_env.borrow().get("eval").cloned()
{
Rc::ptr_eq(callee_fn, global_fn)
} else {
false
}
} else {
false
};
if is_builtin {
let source = match args.first() {
Some(JsValue::String(s)) => s.clone(),
Some(other) => {
ctx.frame.accumulator = other.clone();
return Ok(DispatchAction::Continue);
}
None => {
ctx.frame.accumulator = JsValue::Undefined;
return Ok(DispatchAction::Continue);
}
};
let binding_registers = ctx.frame.bytecode_array.binding_registers().clone();
let original_global_names: HashSet<String> =
ctx.frame.global_env.borrow().vars.keys().cloned().collect();
let mut eval_bindings = ctx.frame.global_env.borrow().vars.clone();
if let Some(this_val) = ctx.frame.global_env.borrow().get_this().cloned() {
eval_bindings.insert("this".to_string(), this_val);
}
for (name, reg) in &binding_registers {
eval_bindings.insert(name.clone(), ctx.frame.read_reg(*reg as u32)?.cheap_clone());
}
let eval_env = Rc::new(RefCell::new(GlobalEnv::with_vars(eval_bindings)));
let (result, final_env, is_strict) =
crate::builtins::global::global_eval_direct_with_scope_capture(
&source,
Rc::clone(&eval_env),
ctx.frame.context.clone(),
)?;
let final_bindings = final_env.borrow();
for (name, reg) in &binding_registers {
if let Some(value) = final_bindings.get(name) {
ctx.frame.write_reg(*reg as u32, value.clone())?;
}
}
{
let mut globals = ctx.frame.global_env.borrow_mut();
for (name, value) in final_bindings.vars.iter() {
if binding_registers.contains_key(name) {
continue;
}
if !is_strict || original_global_names.contains(name) {
globals.insert(name.clone(), value.clone());
}
}
}
ctx.frame.accumulator = result;
} else {
match callee {
JsValue::Function(ba) => {
if ba.is_generator() {
let state = GeneratorState::new(Rc::clone(&ba));
super::init_generator_state_prototype(&state, &ba);
ctx.frame.accumulator = JsValue::Generator(state);
} else {
let mut callee_frame =
acquire_frame(Rc::clone(&ba), args, Rc::clone(&ctx.frame.global_env));
restore_closure_context(&mut callee_frame, &ba);
if ba.self_name_register().is_some() {
populate_self_name(
&mut callee_frame,
&ba,
&JsValue::Function(Rc::clone(&ba)),
);
}
push_call_frame("<eval-fallback>")?;
let result = run_callee_pooled(callee_frame);
pop_call_frame();
ctx.frame.global_cache_invalidate();
ctx.frame.accumulator = result?;
}
}
JsValue::NativeFunction(f) => {
ctx.frame.accumulator = f(args.into_vec())?;
ctx.frame.global_cache_invalidate();
}
JsValue::PlainObject(ref map) => {
if let Some(JsValue::NativeFunction(f)) = map.borrow().get("__call__").cloned() {
ctx.frame.accumulator = f(args.into_vec())?;
ctx.frame.global_cache_invalidate();
} else {
return Err(StatorError::TypeError(
"CallDirectEval: callee is not a function (got PlainObject)".to_string(),
));
}
}
other => {
return Err(StatorError::TypeError(format!(
"CallDirectEval: callee is not a function (got {other:?})"
)));
}
}
}
Ok(DispatchAction::Continue)
}
#[cold]
fn handle_lda_new_target(
ctx: &mut DispatchContext,
_instr: &Instruction,
) -> StatorResult<DispatchAction> {
ctx.frame.accumulator = ctx.frame.new_target.clone();
Ok(DispatchAction::Continue)
}
#[cold]
fn handle_create_class(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::ConstantPoolIdx(ctor_idx) = *instr.operand(0) else {
return Err(err_bad_operand("CreateClass", 0));
};
let Operand::Register(super_v) = *instr.operand(1) else {
return Err(err_bad_operand("CreateClass", 1));
};
let entry = ctx
.frame
.bytecode_array
.get_constant(ctor_idx)
.ok_or_else(|| {
StatorError::Internal(format!(
"CreateClass: constant pool index {ctor_idx} out of bounds"
))
})?;
let ConstantPoolEntry::Function(ctor_ba) = entry else {
return Err(StatorError::Internal(
"CreateClass: constant pool entry is not a Function".into(),
));
};
let super_val = ctx.frame.read_reg(super_v)?.cheap_clone();
let ctor_ba_rc = Rc::clone(ctor_ba);
let class_obj: Rc<RefCell<PropertyMap>> = Rc::new(RefCell::new(PropertyMap::new()));
class_obj.borrow_mut().insert(
"__call__".to_string(),
JsValue::Function(Rc::clone(&ctor_ba_rc)),
);
let ctor_name = ctor_ba_rc.function_name().to_string();
class_obj
.borrow_mut()
.insert("name".to_string(), JsValue::String(ctor_name.into()));
class_obj.borrow_mut().insert(
"length".to_string(),
JsValue::Smi(ctor_ba_rc.function_length() as i32),
);
class_obj
.borrow_mut()
.insert(".class_constructor".to_string(), JsValue::Boolean(true));
let proto: Rc<RefCell<PropertyMap>> = Rc::new(RefCell::new(PropertyMap::new()));
if !matches!(super_val, JsValue::Undefined | JsValue::Null) {
let super_proto = proto_lookup(&super_val, "prototype");
if !matches!(super_proto, JsValue::Undefined) {
proto
.borrow_mut()
.insert("__proto__".to_string(), super_proto);
}
class_obj
.borrow_mut()
.insert("__proto__".to_string(), super_val);
}
let proto_val = JsValue::PlainObject(Rc::clone(&proto));
proto.borrow_mut().insert(
"constructor".to_string(),
JsValue::PlainObject(Rc::clone(&class_obj)),
);
class_obj
.borrow_mut()
.insert("prototype".to_string(), proto_val);
ctx.frame.accumulator = JsValue::PlainObject(class_obj);
Ok(DispatchAction::Continue)
}
fn handle_define_getter_property(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(obj_v) = *instr.operand(0) else {
return Err(err_bad_operand("DefineGetterProperty", 0));
};
let Operand::ConstantPoolIdx(name_idx) = *instr.operand(1) else {
return Err(err_bad_operand("DefineGetterProperty", 1));
};
let prop_name = match ctx.frame.bytecode_array.get_constant(name_idx) {
Some(ConstantPoolEntry::String(s)) => s.clone(),
_ => {
return Err(StatorError::Internal(
"DefineGetterProperty: property name is not a string".into(),
));
}
};
let getter = ctx.frame.accumulator.cheap_clone();
let obj = ctx.frame.read_reg(obj_v)?.cheap_clone();
if let JsValue::PlainObject(ref map) = obj {
if let JsValue::Function(ba) = &getter {
fn_props_set(ba, ".home_object".to_string(), obj.clone());
}
let accessor_attrs = if is_private_storage_key(&prop_name) {
PropertyAttributes::CONFIGURABLE
} else {
PropertyAttributes::ENUMERABLE | PropertyAttributes::CONFIGURABLE
};
map.borrow_mut()
.insert_with_attrs(format!("__get_{prop_name}__"), getter, accessor_attrs);
}
Ok(DispatchAction::Continue)
}
fn handle_define_setter_property(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(obj_v) = *instr.operand(0) else {
return Err(err_bad_operand("DefineSetterProperty", 0));
};
let Operand::ConstantPoolIdx(name_idx) = *instr.operand(1) else {
return Err(err_bad_operand("DefineSetterProperty", 1));
};
let prop_name = match ctx.frame.bytecode_array.get_constant(name_idx) {
Some(ConstantPoolEntry::String(s)) => s.clone(),
_ => {
return Err(StatorError::Internal(
"DefineSetterProperty: property name is not a string".into(),
));
}
};
let setter = ctx.frame.accumulator.cheap_clone();
let obj = ctx.frame.read_reg(obj_v)?.cheap_clone();
if let JsValue::PlainObject(ref map) = obj {
if let JsValue::Function(ba) = &setter {
fn_props_set(ba, ".home_object".to_string(), obj.clone());
}
let accessor_attrs = if is_private_storage_key(&prop_name) {
PropertyAttributes::CONFIGURABLE
} else {
PropertyAttributes::ENUMERABLE | PropertyAttributes::CONFIGURABLE
};
map.borrow_mut()
.insert_with_attrs(format!("__set_{prop_name}__"), setter, accessor_attrs);
}
Ok(DispatchAction::Continue)
}
fn handle_define_keyed_getter_property(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(obj_v) = *instr.operand(0) else {
return Err(err_bad_operand("DefineKeyedGetterProperty", 0));
};
let Operand::Register(key_v) = *instr.operand(1) else {
return Err(err_bad_operand("DefineKeyedGetterProperty", 1));
};
let getter = ctx.frame.accumulator.cheap_clone();
let obj = ctx.frame.read_reg(obj_v)?.cheap_clone();
let key = ctx.frame.read_reg(key_v)?.cheap_clone();
let key_str = to_property_key(&key)?;
if let JsValue::PlainObject(ref map) = obj {
if let JsValue::Function(ba) = &getter {
fn_props_set(ba, ".home_object".to_string(), obj.clone());
}
let accessor_attrs = PropertyAttributes::ENUMERABLE | PropertyAttributes::CONFIGURABLE;
map.borrow_mut()
.insert_with_attrs(format!("__get_{key_str}__"), getter, accessor_attrs);
}
Ok(DispatchAction::Continue)
}
fn handle_define_keyed_setter_property(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(obj_v) = *instr.operand(0) else {
return Err(err_bad_operand("DefineKeyedSetterProperty", 0));
};
let Operand::Register(key_v) = *instr.operand(1) else {
return Err(err_bad_operand("DefineKeyedSetterProperty", 1));
};
let setter = ctx.frame.accumulator.cheap_clone();
let obj = ctx.frame.read_reg(obj_v)?.cheap_clone();
let key = ctx.frame.read_reg(key_v)?.cheap_clone();
let key_str = to_property_key(&key)?;
if let JsValue::PlainObject(ref map) = obj {
if let JsValue::Function(ba) = &setter {
fn_props_set(ba, ".home_object".to_string(), obj.clone());
}
let accessor_attrs = PropertyAttributes::ENUMERABLE | PropertyAttributes::CONFIGURABLE;
map.borrow_mut()
.insert_with_attrs(format!("__set_{key_str}__"), setter, accessor_attrs);
}
Ok(DispatchAction::Continue)
}
fn handle_define_class_named_own_property(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(obj_v) = *instr.operand(0) else {
return Err(err_bad_operand("DefineClassNamedOwnProperty", 0));
};
let Operand::ConstantPoolIdx(name_idx) = *instr.operand(1) else {
return Err(err_bad_operand("DefineClassNamedOwnProperty", 1));
};
let prop_name = match ctx.frame.bytecode_array.get_constant(name_idx) {
Some(ConstantPoolEntry::String(s)) => s.clone(),
_ => {
return Err(StatorError::Internal(
"DefineClassNamedOwnProperty: property name is not a string".into(),
));
}
};
let val = ctx.frame.accumulator.cheap_clone();
let obj = ctx.frame.read_reg(obj_v)?.cheap_clone();
if let JsValue::PlainObject(ref map) = obj {
set_named_property_function_metadata(&val, &obj, &prop_name);
if let Some(attrs) = private_named_property_attrs(&prop_name) {
map.borrow_mut().insert_with_attrs(prop_name, val, attrs);
} else {
map.borrow_mut().insert_builtin(prop_name, val);
}
}
Ok(DispatchAction::Continue)
}
fn handle_define_class_getter_property(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(obj_v) = *instr.operand(0) else {
return Err(err_bad_operand("DefineClassGetterProperty", 0));
};
let Operand::ConstantPoolIdx(name_idx) = *instr.operand(1) else {
return Err(err_bad_operand("DefineClassGetterProperty", 1));
};
let prop_name = match ctx.frame.bytecode_array.get_constant(name_idx) {
Some(ConstantPoolEntry::String(s)) => s.clone(),
_ => {
return Err(StatorError::Internal(
"DefineClassGetterProperty: property name is not a string".into(),
));
}
};
let getter = ctx.frame.accumulator.cheap_clone();
let obj = ctx.frame.read_reg(obj_v)?.cheap_clone();
if let JsValue::PlainObject(ref map) = obj {
if let JsValue::Function(ba) = &getter {
fn_props_set(ba, ".home_object".to_string(), obj.clone());
}
let accessor_attrs = if is_private_storage_key(&prop_name) {
PropertyAttributes::CONFIGURABLE
} else {
PropertyAttributes::WRITABLE | PropertyAttributes::CONFIGURABLE
};
map.borrow_mut()
.insert_with_attrs(format!("__get_{prop_name}__"), getter, accessor_attrs);
}
Ok(DispatchAction::Continue)
}
fn handle_define_class_setter_property(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(obj_v) = *instr.operand(0) else {
return Err(err_bad_operand("DefineClassSetterProperty", 0));
};
let Operand::ConstantPoolIdx(name_idx) = *instr.operand(1) else {
return Err(err_bad_operand("DefineClassSetterProperty", 1));
};
let prop_name = match ctx.frame.bytecode_array.get_constant(name_idx) {
Some(ConstantPoolEntry::String(s)) => s.clone(),
_ => {
return Err(StatorError::Internal(
"DefineClassSetterProperty: property name is not a string".into(),
));
}
};
let setter = ctx.frame.accumulator.cheap_clone();
let obj = ctx.frame.read_reg(obj_v)?.cheap_clone();
if let JsValue::PlainObject(ref map) = obj {
if let JsValue::Function(ba) = &setter {
fn_props_set(ba, ".home_object".to_string(), obj.clone());
}
let accessor_attrs = if is_private_storage_key(&prop_name) {
PropertyAttributes::CONFIGURABLE
} else {
PropertyAttributes::WRITABLE | PropertyAttributes::CONFIGURABLE
};
map.borrow_mut()
.insert_with_attrs(format!("__set_{prop_name}__"), setter, accessor_attrs);
}
Ok(DispatchAction::Continue)
}
fn handle_define_class_keyed_own_property(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(obj_v) = *instr.operand(0) else {
return Err(err_bad_operand("DefineClassKeyedOwnProperty", 0));
};
let Operand::Register(key_v) = *instr.operand(1) else {
return Err(err_bad_operand("DefineClassKeyedOwnProperty", 1));
};
let obj = ctx.frame.read_reg(obj_v)?.cheap_clone();
let key = ctx.frame.read_reg(key_v)?.cheap_clone();
let val = ctx.frame.accumulator.cheap_clone();
if let JsValue::PlainObject(ref map) = obj {
let prop_name = to_property_key(&key)?;
set_named_property_function_metadata(&val, &obj, &prop_name);
map.borrow_mut().insert_builtin(prop_name, val);
}
Ok(DispatchAction::Continue)
}
fn handle_define_class_keyed_getter_property(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(obj_v) = *instr.operand(0) else {
return Err(err_bad_operand("DefineClassKeyedGetterProperty", 0));
};
let Operand::Register(key_v) = *instr.operand(1) else {
return Err(err_bad_operand("DefineClassKeyedGetterProperty", 1));
};
let getter = ctx.frame.accumulator.cheap_clone();
let obj = ctx.frame.read_reg(obj_v)?.cheap_clone();
let key = ctx.frame.read_reg(key_v)?.cheap_clone();
let key_str = to_property_key(&key)?;
if let JsValue::PlainObject(ref map) = obj {
if let JsValue::Function(ba) = &getter {
fn_props_set(ba, ".home_object".to_string(), obj.clone());
}
let accessor_attrs = PropertyAttributes::WRITABLE | PropertyAttributes::CONFIGURABLE;
map.borrow_mut()
.insert_with_attrs(format!("__get_{key_str}__"), getter, accessor_attrs);
}
Ok(DispatchAction::Continue)
}
fn handle_define_class_keyed_setter_property(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(obj_v) = *instr.operand(0) else {
return Err(err_bad_operand("DefineClassKeyedSetterProperty", 0));
};
let Operand::Register(key_v) = *instr.operand(1) else {
return Err(err_bad_operand("DefineClassKeyedSetterProperty", 1));
};
let setter = ctx.frame.accumulator.cheap_clone();
let obj = ctx.frame.read_reg(obj_v)?.cheap_clone();
let key = ctx.frame.read_reg(key_v)?.cheap_clone();
let key_str = to_property_key(&key)?;
if let JsValue::PlainObject(ref map) = obj {
if let JsValue::Function(ba) = &setter {
fn_props_set(ba, ".home_object".to_string(), obj.clone());
}
let accessor_attrs = PropertyAttributes::WRITABLE | PropertyAttributes::CONFIGURABLE;
map.borrow_mut()
.insert_with_attrs(format!("__set_{key_str}__"), setter, accessor_attrs);
}
Ok(DispatchAction::Continue)
}
fn handle_lda_enumerated_keyed_property(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(obj_v) = *instr.operand(0) else {
return Err(err_bad_operand("LdaEnumeratedKeyedProperty", 0));
};
let Operand::Register(key_v) = *instr.operand(1) else {
return Err(err_bad_operand("LdaEnumeratedKeyedProperty", 1));
};
let obj = ctx.frame.read_reg(obj_v)?.cheap_clone();
let key = ctx.frame.read_reg(key_v)?.cheap_clone();
ctx.frame.accumulator = keyed_load(&obj, &key)?;
Ok(DispatchAction::Continue)
}
#[cold]
fn handle_test_private_brand(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(obj_v) = *instr.operand(0) else {
return Err(err_bad_operand("TestPrivateBrand", 0));
};
let Operand::Register(brand_v) = *instr.operand(1) else {
return Err(err_bad_operand("TestPrivateBrand", 1));
};
let obj = ctx.frame.read_reg(obj_v)?.cheap_clone();
let brand = ctx.frame.read_reg(brand_v)?.cheap_clone();
let brand_key = to_property_key(&brand)?;
if !is_js_receiver(&obj) {
return Err(StatorError::TypeError(format!(
"Cannot use 'in' operator to search for private field '{brand_key}' in non-object",
)));
}
let has_brand = match &obj {
JsValue::PlainObject(map) => {
let borrow = map.borrow();
if brand_key.starts_with(PRIVATE_BRAND_PREFIX) {
borrow.contains_key(&brand_key)
} else {
own_private_element_exists(&borrow, &brand_key)
}
}
_ => false,
};
ctx.frame.accumulator = JsValue::Boolean(has_brand);
Ok(DispatchAction::Continue)
}
#[cold]
fn handle_define_private_brand(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::Register(obj_v) = *instr.operand(0) else {
return Err(err_bad_operand("DefinePrivateBrand", 0));
};
let obj = ctx.frame.read_reg(obj_v)?.cheap_clone();
let brand = ctx.frame.accumulator.cheap_clone();
let brand_key = to_property_key(&brand)?;
match &obj {
JsValue::PlainObject(map) => {
map.borrow_mut().insert_with_attrs(
brand_key,
JsValue::Boolean(true),
PropertyAttributes::CONFIGURABLE,
);
}
_ => {
return Err(StatorError::TypeError(
"Cannot define private brand on non-object".into(),
));
}
}
Ok(DispatchAction::Continue)
}
#[cold]
fn handle_lda_module_variable(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::ConstantPoolIdx(req_idx) = *instr.operand(0) else {
return Err(err_bad_operand("LdaModuleVariable", 0));
};
let Operand::Immediate(cell) = *instr.operand(1) else {
return Err(err_bad_operand("LdaModuleVariable", 1));
};
let specifier = match ctx.frame.bytecode_array.get_constant(req_idx) {
Some(ConstantPoolEntry::String(s)) => s.clone(),
_ => {
return Err(StatorError::Internal(
"LdaModuleVariable: module specifier is not a string".into(),
));
}
};
let key = format!("__mod:{specifier}:{cell}");
ctx.frame.accumulator = ctx
.frame
.global_env
.borrow()
.get(&key)
.cloned()
.unwrap_or(JsValue::Undefined);
Ok(DispatchAction::Continue)
}
#[cold]
fn handle_sta_module_variable(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::ConstantPoolIdx(req_idx) = *instr.operand(0) else {
return Err(err_bad_operand("StaModuleVariable", 0));
};
let Operand::Immediate(cell) = *instr.operand(1) else {
return Err(err_bad_operand("StaModuleVariable", 1));
};
let specifier = match ctx.frame.bytecode_array.get_constant(req_idx) {
Some(ConstantPoolEntry::String(s)) => s.clone(),
_ => {
return Err(StatorError::Internal(
"StaModuleVariable: module specifier is not a string".into(),
));
}
};
let key = format!("__mod:{specifier}:{cell}");
let val = ctx.frame.accumulator.cheap_clone();
ctx.frame.global_env.borrow_mut().insert(key, val);
Ok(DispatchAction::Continue)
}
#[cold]
fn handle_lda_import_meta(
ctx: &mut DispatchContext,
_instr: &Instruction,
) -> StatorResult<DispatchAction> {
let mut meta = PropertyMap::new();
meta.insert("url".into(), JsValue::String(String::new().into()));
meta.insert(
"resolve".into(),
JsValue::NativeFunction(Rc::new(|args| {
let value = match args.as_slice() {
[] => JsValue::Undefined,
[value] => value.clone(),
[_, value, ..] => value.clone(),
};
Ok(value)
})),
);
meta.freeze();
ctx.frame.accumulator = JsValue::PlainObject(Rc::new(RefCell::new(meta)));
Ok(DispatchAction::Continue)
}
#[cold]
fn handle_get_module_namespace(
ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
let Operand::ConstantPoolIdx(_req_idx) = *instr.operand(0) else {
return Err(err_bad_operand("GetModuleNamespace", 0));
};
ctx.frame.accumulator = JsValue::PlainObject(Rc::new(RefCell::new(PropertyMap::new())));
Ok(DispatchAction::Continue)
}
#[cold]
fn handle_unimplemented(
_ctx: &mut DispatchContext,
instr: &Instruction,
) -> StatorResult<DispatchAction> {
Err(StatorError::Internal(format!(
"unimplemented opcode: {:?}",
instr.opcode
)))
}
pub(super) static DISPATCH_TABLE: [OpcodeHandler; OPCODE_COUNT] = {
let mut table: [OpcodeHandler; OPCODE_COUNT] = [handle_unimplemented; OPCODE_COUNT];
table[Opcode::LdaZero as usize] = handle_lda_zero;
table[Opcode::LdaSmi as usize] = handle_lda_smi;
table[Opcode::LdaUndefined as usize] = handle_lda_undefined;
table[Opcode::LdaTheHole as usize] = handle_lda_the_hole;
table[Opcode::LdaNull as usize] = handle_lda_null;
table[Opcode::LdaTrue as usize] = handle_lda_true;
table[Opcode::LdaFalse as usize] = handle_lda_false;
table[Opcode::LdaConstant as usize] = handle_lda_constant;
table[Opcode::LdaGlobal as usize] = handle_lda_global;
table[Opcode::LdaGlobalInsideTypeof as usize] = handle_lda_global_inside_typeof;
table[Opcode::StaGlobal as usize] = handle_sta_global;
table[Opcode::LdaContextSlot as usize] = handle_lda_context_slot;
table[Opcode::LdaImmutableContextSlot as usize] = handle_lda_context_slot;
table[Opcode::LdaCurrentContextSlot as usize] = handle_lda_current_context_slot;
table[Opcode::LdaImmutableCurrentContextSlot as usize] = handle_lda_current_context_slot;
table[Opcode::StaContextSlot as usize] = handle_sta_context_slot;
table[Opcode::StaCurrentContextSlot as usize] = handle_sta_current_context_slot;
table[Opcode::LdaLookupSlot as usize] = handle_lda_lookup_slot;
table[Opcode::LdaLookupContextSlot as usize] = handle_lda_lookup_context_slot;
table[Opcode::LdaLookupGlobalSlot as usize] = handle_lda_lookup_global_slot;
table[Opcode::LdaLookupSlotInsideTypeof as usize] = handle_lda_lookup_slot_inside_typeof;
table[Opcode::LdaLookupContextSlotInsideTypeof as usize] =
handle_lda_lookup_context_slot_inside_typeof;
table[Opcode::LdaLookupGlobalSlotInsideTypeof as usize] =
handle_lda_lookup_global_slot_inside_typeof;
table[Opcode::StaLookupSlot as usize] = handle_sta_lookup_slot;
table[Opcode::DeleteLookupSlot as usize] = handle_delete_lookup_slot;
table[Opcode::Ldar as usize] = handle_ldar;
table[Opcode::LdarAddStar as usize] = handle_ldar_add_star;
table[Opcode::LdarSubStar as usize] = handle_ldar_sub_star;
table[Opcode::LdarMulStar as usize] = handle_ldar_mul_star;
table[Opcode::Star as usize] = handle_star;
table[Opcode::Mov as usize] = handle_mov;
table[Opcode::LdaNamedProperty as usize] = handle_lda_named_property;
table[Opcode::LdaNamedPropertyFromSuper as usize] = handle_lda_named_property_from_super;
table[Opcode::LdaKeyedProperty as usize] = handle_lda_keyed_property;
table[Opcode::LdaEnumeratedKeyedProperty as usize] = handle_lda_enumerated_keyed_property;
table[Opcode::StaNamedProperty as usize] = handle_sta_named_property;
table[Opcode::StaNamedOwnProperty as usize] = handle_sta_named_own_property;
table[Opcode::StaKeyedProperty as usize] = handle_sta_keyed_property;
table[Opcode::DefineNamedOwnProperty as usize] = handle_define_named_own_property;
table[Opcode::DefineKeyedOwnProperty as usize] = handle_define_keyed_own_property;
table[Opcode::StaInArrayLiteral as usize] = handle_sta_in_array_literal;
table[Opcode::DefineKeyedOwnPropertyInLiteral as usize] =
handle_define_keyed_own_property_in_literal;
table[Opcode::DefineGetterProperty as usize] = handle_define_getter_property;
table[Opcode::DefineSetterProperty as usize] = handle_define_setter_property;
table[Opcode::DefineKeyedGetterProperty as usize] = handle_define_keyed_getter_property;
table[Opcode::DefineKeyedSetterProperty as usize] = handle_define_keyed_setter_property;
table[Opcode::DefineClassNamedOwnProperty as usize] = handle_define_class_named_own_property;
table[Opcode::DefineClassGetterProperty as usize] = handle_define_class_getter_property;
table[Opcode::DefineClassSetterProperty as usize] = handle_define_class_setter_property;
table[Opcode::DefineClassKeyedOwnProperty as usize] = handle_define_class_keyed_own_property;
table[Opcode::DefineClassKeyedGetterProperty as usize] =
handle_define_class_keyed_getter_property;
table[Opcode::DefineClassKeyedSetterProperty as usize] =
handle_define_class_keyed_setter_property;
table[Opcode::CollectTypeProfile as usize] = handle_collect_type_profile;
table[Opcode::Add as usize] = handle_add;
table[Opcode::Sub as usize] = handle_sub;
table[Opcode::Mul as usize] = handle_mul;
table[Opcode::Div as usize] = handle_div;
table[Opcode::Mod as usize] = handle_mod;
table[Opcode::Exp as usize] = handle_exp;
table[Opcode::BitwiseOr as usize] = handle_bitwise_or;
table[Opcode::BitwiseXor as usize] = handle_bitwise_xor;
table[Opcode::BitwiseAnd as usize] = handle_bitwise_and;
table[Opcode::ShiftLeft as usize] = handle_shift_left;
table[Opcode::ShiftRight as usize] = handle_shift_right;
table[Opcode::ShiftRightLogical as usize] = handle_shift_right_logical;
table[Opcode::AddSmi as usize] = handle_add_smi;
table[Opcode::AddSmiStar as usize] = handle_add_smi_star;
table[Opcode::Nop as usize] = handle_nop;
table[Opcode::SubSmi as usize] = handle_sub_smi;
table[Opcode::MulSmi as usize] = handle_mul_smi;
table[Opcode::MulSmiStar as usize] = handle_mul_smi_star;
table[Opcode::DivSmi as usize] = handle_div_smi;
table[Opcode::ModSmi as usize] = handle_mod_smi;
table[Opcode::ExpSmi as usize] = handle_exp_smi;
table[Opcode::BitwiseOrSmi as usize] = handle_bitwise_or_smi;
table[Opcode::BitwiseXorSmi as usize] = handle_bitwise_xor_smi;
table[Opcode::BitwiseAndSmi as usize] = handle_bitwise_and_smi;
table[Opcode::ShiftLeftSmi as usize] = handle_shift_left_smi;
table[Opcode::ShiftRightSmi as usize] = handle_shift_right_smi;
table[Opcode::ShiftRightLogicalSmi as usize] = handle_shift_right_logical_smi;
table[Opcode::Inc as usize] = handle_inc;
table[Opcode::IncStar as usize] = handle_inc_star;
table[Opcode::Dec as usize] = handle_dec;
table[Opcode::Negate as usize] = handle_negate;
table[Opcode::BitwiseNot as usize] = handle_bitwise_not;
table[Opcode::ToBooleanLogicalNot as usize] = handle_to_boolean_logical_not;
table[Opcode::LogicalNot as usize] = handle_logical_not;
table[Opcode::TypeOf as usize] = handle_type_of;
table[Opcode::DeletePropertyStrict as usize] = handle_delete_property_strict;
table[Opcode::DeletePropertySloppy as usize] = handle_delete_property_sloppy;
table[Opcode::TestEqual as usize] = handle_test_equal;
table[Opcode::TestNotEqual as usize] = handle_test_not_equal;
table[Opcode::TestEqualStrict as usize] = handle_test_equal_strict;
table[Opcode::TestLessThan as usize] = handle_test_less_than;
table[Opcode::TestLessThanJump as usize] = handle_test_less_than_jump;
table[Opcode::TestGreaterThanJump as usize] = handle_test_greater_than_jump;
table[Opcode::TestEqualJump as usize] = handle_test_equal_jump;
table[Opcode::TestNotEqualJump as usize] = handle_test_not_equal_jump;
table[Opcode::TestEqualStrictJump as usize] = handle_test_equal_strict_jump;
table[Opcode::TestLessThanOrEqualJump as usize] = handle_test_less_than_or_equal_jump;
table[Opcode::TestGreaterThanOrEqualJump as usize] = handle_test_greater_than_or_equal_jump;
table[Opcode::SubSmiStar as usize] = handle_sub_smi_star;
table[Opcode::TestGreaterThan as usize] = handle_test_greater_than;
table[Opcode::TestLessThanOrEqual as usize] = handle_test_less_than_or_equal;
table[Opcode::TestGreaterThanOrEqual as usize] = handle_test_greater_than_or_equal;
table[Opcode::TestReferenceEqual as usize] = handle_test_reference_equal;
table[Opcode::TestInstanceOf as usize] = handle_test_instance_of;
table[Opcode::TestIn as usize] = handle_test_in;
table[Opcode::TestUndetectable as usize] = handle_test_undetectable;
table[Opcode::TestNull as usize] = handle_test_null;
table[Opcode::TestUndefined as usize] = handle_test_undefined;
table[Opcode::TestTypeOf as usize] = handle_test_type_of;
table[Opcode::LdaSmiStar as usize] = handle_lda_smi_star;
table[Opcode::LdaGlobalStar as usize] = handle_lda_global_star;
table[Opcode::ToName as usize] = handle_to_name;
table[Opcode::ToNumber as usize] = handle_to_number;
table[Opcode::ToNumeric as usize] = handle_to_numeric;
table[Opcode::ToObject as usize] = handle_to_object;
table[Opcode::ToString as usize] = handle_to_string;
table[Opcode::ToBoolean as usize] = handle_to_boolean;
table[Opcode::CallAnyReceiver as usize] = handle_call_any_receiver;
table[Opcode::CallProperty as usize] = handle_call_property;
table[Opcode::CallProperty0 as usize] = handle_call_property0;
table[Opcode::CallProperty1 as usize] = handle_call_property1;
table[Opcode::CallProperty2 as usize] = handle_call_property2;
table[Opcode::CallUndefinedReceiver0 as usize] = handle_call_undefined_receiver0;
table[Opcode::CallUndefinedReceiver1 as usize] = handle_call_undefined_receiver1;
table[Opcode::CallUndefinedReceiver2 as usize] = handle_call_undefined_receiver2;
table[Opcode::CallWithSpread as usize] = handle_call_with_spread;
table[Opcode::CallRuntime as usize] = handle_call_runtime;
table[Opcode::CallRuntimeForPair as usize] = handle_call_runtime_for_pair;
table[Opcode::CallJSRuntime as usize] = handle_call_js_runtime;
table[Opcode::InvokeIntrinsic as usize] = handle_invoke_intrinsic;
table[Opcode::CallDirectEval as usize] = handle_call_direct_eval;
table[Opcode::TailCall as usize] = handle_tail_call;
table[Opcode::Construct as usize] = handle_construct;
table[Opcode::ConstructWithSpread as usize] = handle_construct_with_spread;
table[Opcode::ConstructForwardAllArgs as usize] = handle_construct_forward_all_args;
table[Opcode::GetIterator as usize] = handle_get_iterator;
table[Opcode::GetAsyncIterator as usize] = handle_get_async_iterator;
table[Opcode::IteratorNext as usize] = handle_iterator_next;
table[Opcode::IteratorClose as usize] = handle_iterator_close;
table[Opcode::CopyDataProperties as usize] = handle_copy_data_properties;
table[Opcode::JumpLoop as usize] = handle_jump_loop;
table[Opcode::Jump as usize] = handle_jump;
table[Opcode::JumpConstant as usize] = handle_jump_constant;
table[Opcode::JumpIfTrue as usize] = handle_jump_if_true;
table[Opcode::JumpIfTrueConstant as usize] = handle_jump_if_true_constant;
table[Opcode::JumpIfFalse as usize] = handle_jump_if_false;
table[Opcode::JumpIfFalseConstant as usize] = handle_jump_if_false_constant;
table[Opcode::JumpIfNull as usize] = handle_jump_if_null;
table[Opcode::JumpIfNotNull as usize] = handle_jump_if_not_null;
table[Opcode::JumpIfUndefined as usize] = handle_jump_if_undefined;
table[Opcode::JumpIfNotUndefined as usize] = handle_jump_if_not_undefined;
table[Opcode::JumpIfUndefinedOrNull as usize] = handle_jump_if_undefined_or_null;
table[Opcode::JumpIfJSReceiver as usize] = handle_jump_if_js_receiver;
table[Opcode::JumpIfForInDone as usize] = handle_jump_if_for_in_done;
table[Opcode::JumpIfToBooleanTrue as usize] = handle_jump_if_to_boolean_true;
table[Opcode::JumpIfToBooleanFalse as usize] = handle_jump_if_to_boolean_false;
table[Opcode::JumpIfToBooleanTrueConstant as usize] = handle_jump_if_to_boolean_true_constant;
table[Opcode::JumpIfToBooleanFalseConstant as usize] = handle_jump_if_to_boolean_false_constant;
table[Opcode::JumpIfNullConstant as usize] = handle_jump_if_null_constant;
table[Opcode::JumpIfNotNullConstant as usize] = handle_jump_if_not_null_constant;
table[Opcode::JumpIfUndefinedConstant as usize] = handle_jump_if_undefined_constant;
table[Opcode::JumpIfNotUndefinedConstant as usize] = handle_jump_if_not_undefined_constant;
table[Opcode::JumpIfUndefinedOrNullConstant as usize] =
handle_jump_if_undefined_or_null_constant;
table[Opcode::JumpIfJSReceiverConstant as usize] = handle_jump_if_js_receiver_constant;
table[Opcode::Return as usize] = handle_return;
table[Opcode::ThrowReferenceErrorIfHole as usize] = handle_throw_reference_error_if_hole;
table[Opcode::ThrowSuperNotCalledIfHole as usize] = handle_throw_super_not_called_if_hole;
table[Opcode::ThrowSuperAlreadyCalledIfNotHole as usize] =
handle_throw_super_already_called_if_not_hole;
table[Opcode::ThrowIfNullOrUndefined as usize] = handle_throw_if_null_or_undefined;
table[Opcode::Throw as usize] = handle_throw;
table[Opcode::ReThrow as usize] = handle_throw;
table[Opcode::SetPendingMessage as usize] = handle_set_pending_message;
table[Opcode::Debugger as usize] = handle_debugger;
table[Opcode::CreateClosure as usize] = handle_create_closure;
table[Opcode::CreateBlockContext as usize] = handle_create_block_context;
table[Opcode::CreateCatchContext as usize] = handle_create_catch_context;
table[Opcode::CreateFunctionContext as usize] = handle_create_function_context;
table[Opcode::CreateEvalContext as usize] = handle_create_eval_context;
table[Opcode::CreateWithContext as usize] = handle_create_with_context;
table[Opcode::CreateMappedArguments as usize] = handle_create_mapped_arguments;
table[Opcode::CreateUnmappedArguments as usize] = handle_create_unmapped_arguments;
table[Opcode::CreateRestParameter as usize] = handle_create_rest_parameter;
table[Opcode::CreateRegExpLiteral as usize] = handle_create_reg_exp_literal;
table[Opcode::CreateArrayLiteral as usize] = handle_create_array_literal;
table[Opcode::CreateArrayFromIterable as usize] = handle_create_array_from_iterable;
table[Opcode::CreateEmptyArrayLiteral as usize] = handle_create_empty_array_literal;
table[Opcode::CreateObjectLiteral as usize] = handle_create_object_literal;
table[Opcode::CreateEmptyObjectLiteral as usize] = handle_create_empty_object_literal;
table[Opcode::CreateObjectFromIterable as usize] = handle_create_object_from_iterable;
table[Opcode::PushContext as usize] = handle_push_context;
table[Opcode::PopContext as usize] = handle_pop_context;
table[Opcode::ForInEnumerate as usize] = handle_for_in_enumerate;
table[Opcode::ForInPrepare as usize] = handle_for_in_prepare;
table[Opcode::ForInNext as usize] = handle_for_in_next;
table[Opcode::ForInStep as usize] = handle_for_in_step;
table[Opcode::GetTemplateObject as usize] = handle_get_template_object;
table[Opcode::StackCheck as usize] = handle_stack_check;
table[Opcode::SetExpressionPosition as usize] = handle_stack_check;
table[Opcode::SetExpressionPositionFromEnd as usize] = handle_stack_check;
table[Opcode::ResumeGenerator as usize] = handle_resume_generator;
table[Opcode::GetGeneratorState as usize] = handle_get_generator_state;
table[Opcode::SuspendGenerator as usize] = handle_suspend_generator;
table[Opcode::SetGeneratorState as usize] = handle_set_generator_state;
table[Opcode::SwitchOnGeneratorState as usize] = handle_switch_on_generator_state;
table[Opcode::CreateClass as usize] = handle_create_class;
table[Opcode::TestPrivateBrand as usize] = handle_test_private_brand;
table[Opcode::DefinePrivateBrand as usize] = handle_define_private_brand;
table[Opcode::LdaModuleVariable as usize] = handle_lda_module_variable;
table[Opcode::StaModuleVariable as usize] = handle_sta_module_variable;
table[Opcode::LdaImportMeta as usize] = handle_lda_import_meta;
table[Opcode::LdaNewTarget as usize] = handle_lda_new_target;
table[Opcode::GetModuleNamespace as usize] = handle_get_module_namespace;
table[Opcode::Wide as usize] = handle_wide;
table[Opcode::ExtraWide as usize] = handle_wide;
table[Opcode::Illegal as usize] = handle_unimplemented;
table
};
#[cfg(test)]
mod tests {
use crate::objects::value::JsValue;
use std::rc::Rc;
fn assert_eval_true(source: &str) {
let result = crate::builtins::global::global_eval(source).unwrap();
assert_eq!(result, JsValue::Boolean(true), "source: {source}");
}
#[test]
fn test_typeof_generator() {
let result =
crate::builtins::global::global_eval("function* gen() { yield 1; } typeof gen()")
.unwrap();
assert_eq!(result, JsValue::String("object".into()));
}
#[test]
fn test_typeof_promise() {
let result =
crate::builtins::global::global_eval("typeof new Promise(function(r) { r(1); })")
.unwrap();
assert_eq!(result, JsValue::String("object".into()));
}
#[test]
fn test_for_in_array() {
let result = crate::builtins::global::global_eval(
"var a = [10, 20, 30]; var keys = []; for (var k in a) { keys.push(k); } keys.length",
)
.unwrap();
assert_eq!(result, JsValue::Smi(3));
}
#[test]
fn test_for_in_array_keys_are_strings() {
let result = crate::builtins::global::global_eval(
"var a = [10, 20]; var keys = []; for (var k in a) { keys.push(k); } keys[0]",
)
.unwrap();
assert_eq!(result, JsValue::String("0".into()));
}
#[test]
#[ignore] fn e2e_user_constructor_instanceof() {
let result = crate::builtins::global::global_eval(
"function Foo() {} var x = new Foo(); x instanceof Foo",
)
.unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn e2e_function_has_prototype() {
let result = crate::builtins::global::global_eval(
"function Foo() {} typeof Foo.prototype === 'object'",
)
.unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn e2e_prototype_constructor_points_back() {
let result = crate::builtins::global::global_eval(
"function Foo() {} Foo.prototype.constructor === Foo",
)
.unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn e2e_arrow_no_prototype() {
let result =
crate::builtins::global::global_eval("var f = () => {}; f.prototype === undefined")
.unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn e2e_spread_skips_non_enumerable() {
let result = crate::builtins::global::global_eval(
"var a = [10, 20]; var o = {...a}; o[0] + ',' + o[1] + ',' + (o.length === undefined)",
)
.unwrap();
assert_eq!(result, JsValue::String("10,20,true".into()));
}
#[test]
fn e2e_spread_string() {
let result =
crate::builtins::global::global_eval("var o = {...'hi'}; o[0] + o[1]").unwrap();
assert_eq!(result, JsValue::String("hi".into()));
}
#[test]
#[ignore] fn e2e_private_method() {
let result = crate::builtins::global::global_eval(
"class Foo { #bar() { return 42; } test() { return this.#bar(); } } \
var f = new Foo(); f.test()",
)
.unwrap();
assert_eq!(result, JsValue::Smi(42));
}
#[test]
#[ignore] fn e2e_private_field() {
let result = crate::builtins::global::global_eval(
"class C { #x = 10; get() { return this.#x; } } new C().get()",
)
.unwrap();
assert_eq!(result, JsValue::Smi(10));
}
#[test]
#[ignore] fn e2e_private_field_write() {
let result = crate::builtins::global::global_eval(
"class C { #x = 0; set(v) { this.#x = v; } get() { return this.#x; } } \
var c = new C(); c.set(99); c.get()",
)
.unwrap();
assert_eq!(result, JsValue::Smi(99));
}
#[test]
fn e2e_constructor_name() {
let result = crate::builtins::global::global_eval(
"Date.name + ',' + Map.name + ',' + Set.name + ',' + Array.name",
)
.unwrap();
assert_eq!(result, JsValue::String("Date,Map,Set,Array".into()));
}
#[test]
fn e2e_prototype_constructor() {
let result = crate::builtins::global::global_eval(
"Date.prototype.constructor === Date && \
Map.prototype.constructor === Map && \
Array.prototype.constructor === Array",
)
.unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn e2e_instance_constructor() {
let result =
crate::builtins::global::global_eval("var d = new Date(); d.constructor === Date")
.unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn e2e_arrow_not_constructable() {
let result = crate::builtins::global::global_eval(
"var f = () => {}; try { new f(); 'no error' } catch(e) { e.message }",
)
.unwrap();
assert_eq!(
result,
JsValue::String("Function is not a constructor".into())
);
}
#[test]
fn e2e_typeof_callable_object() {
let result = crate::builtins::global::global_eval("typeof Date").unwrap();
assert_eq!(result, JsValue::String("function".into()));
}
#[test]
fn e2e_for_of_string() {
let result = crate::builtins::global::global_eval(
"var r = ''; for (var c of 'abc') r += c + ','; r",
)
.unwrap();
assert_eq!(result, JsValue::String("a,b,c,".into()));
}
#[test]
fn e2e_destructure_default() {
let result =
crate::builtins::global::global_eval("var {x = 10, y = 20} = {x: 5}; x + y").unwrap();
assert_eq!(result, JsValue::Smi(25));
}
#[test]
fn e2e_spread_call() {
let result = crate::builtins::global::global_eval(
"function sum(a,b,c) { return a+b+c; } sum(1,2,3)",
)
.unwrap();
assert_eq!(result, JsValue::Smi(6));
}
#[test]
fn e2e_template_literal_expr() {
let result =
crate::builtins::global::global_eval("var x = 10; `value is ${x + 5}`").unwrap();
assert_eq!(result, JsValue::String("value is 15".into()));
}
#[test]
fn e2e_typeof_undefined() {
let result = crate::builtins::global::global_eval("typeof undefined").unwrap();
assert_eq!(result, JsValue::String("undefined".into()));
}
#[test]
fn e2e_typeof_null_is_object() {
let result = crate::builtins::global::global_eval("typeof null").unwrap();
assert_eq!(result, JsValue::String("object".into()));
}
#[test]
fn e2e_typeof_boolean() {
let result = crate::builtins::global::global_eval("typeof true").unwrap();
assert_eq!(result, JsValue::String("boolean".into()));
}
#[test]
fn e2e_typeof_number_smi() {
let result = crate::builtins::global::global_eval("typeof 42").unwrap();
assert_eq!(result, JsValue::String("number".into()));
}
#[test]
fn e2e_typeof_number_float() {
let result = crate::builtins::global::global_eval("typeof 3.14").unwrap();
assert_eq!(result, JsValue::String("number".into()));
}
#[test]
fn e2e_typeof_string() {
let result = crate::builtins::global::global_eval("typeof 'hello'").unwrap();
assert_eq!(result, JsValue::String("string".into()));
}
#[test]
fn e2e_typeof_function_expr() {
let result =
crate::builtins::global::global_eval("var f = function() {}; typeof f").unwrap();
assert_eq!(result, JsValue::String("function".into()));
}
#[test]
fn e2e_typeof_arrow_is_function() {
let result = crate::builtins::global::global_eval("var f = () => {}; typeof f").unwrap();
assert_eq!(result, JsValue::String("function".into()));
}
#[test]
fn e2e_typeof_object_literal() {
let result = crate::builtins::global::global_eval("var o = {}; typeof o").unwrap();
assert_eq!(result, JsValue::String("object".into()));
}
#[test]
fn e2e_typeof_array_is_object() {
let result = crate::builtins::global::global_eval("typeof []").unwrap();
assert_eq!(result, JsValue::String("object".into()));
}
#[test]
fn e2e_strict_eq_null_null() {
let result = crate::builtins::global::global_eval("null === null").unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn e2e_strict_eq_undefined_undefined() {
let result = crate::builtins::global::global_eval("undefined === undefined").unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn e2e_strict_eq_null_undefined_is_false() {
let result = crate::builtins::global::global_eval("null === undefined").unwrap();
assert_eq!(result, JsValue::Boolean(false));
}
#[test]
fn e2e_strict_not_equal_null_undefined() {
let result = crate::builtins::global::global_eval("null !== undefined").unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn e2e_strict_eq_nan_is_not_nan() {
let result = crate::builtins::global::global_eval("NaN === NaN").unwrap();
assert_eq!(result, JsValue::Boolean(false));
}
#[test]
fn e2e_strict_not_equal_nan_nan() {
let result = crate::builtins::global::global_eval("NaN !== NaN").unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn e2e_to_object_null_throws_type_error() {
let result = crate::builtins::global::global_eval(
"var r; try { with(null) { r = 1; } } catch(e) { r = e.message; } r",
)
.unwrap();
assert_eq!(
result,
JsValue::String("Cannot convert undefined or null to object".into())
);
}
#[test]
fn e2e_to_object_undefined_throws_type_error() {
let result = crate::builtins::global::global_eval(
"var r; try { with(undefined) { r = 1; } } catch(e) { r = e.message; } r",
)
.unwrap();
assert_eq!(
result,
JsValue::String("Cannot convert undefined or null to object".into())
);
}
#[test]
fn e2e_null_variable_strict_eq() {
let result = crate::builtins::global::global_eval("var x = null; x === null").unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn e2e_undefined_variable_strict_eq() {
let result =
crate::builtins::global::global_eval("var x = undefined; x === undefined").unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn e2e_computed_property_toprimitive() {
let result = crate::builtins::global::global_eval(
"var k = { [Symbol.toPrimitive]() { return 'x'; } }; var o = {}; o[k] = 1; o.x",
);
assert!(result.is_ok());
}
#[test]
fn e2e_frozen_object_sloppy() {
let result = crate::builtins::global::global_eval(
"var o = { x: 1 }; Object.freeze(o); o.x = 2; o.x",
)
.unwrap();
assert_eq!(result, JsValue::Smi(1), "frozen prop should remain 1");
}
#[test]
fn e2e_frozen_object_strict() {
let result = crate::builtins::global::global_eval(
"'use strict'; var o = { x: 1 }; Object.freeze(o); o.x = 2;",
);
assert!(result.is_err(), "should throw TypeError");
}
#[test]
fn e2e_sealed_object_write_existing() {
let result =
crate::builtins::global::global_eval("var o = { x: 1 }; Object.seal(o); o.x = 2; o.x")
.unwrap();
assert_eq!(
result,
JsValue::Smi(2),
"sealed should allow writes to existing writable props"
);
}
#[test]
fn e2e_sealed_object_new_prop_strict() {
let result = crate::builtins::global::global_eval(
"'use strict'; var o = { x: 1 }; Object.seal(o); o.y = 2;",
);
assert!(
result.is_err(),
"sealed should reject new property in strict"
);
}
#[test]
fn e2e_object_is_frozen() {
let result = crate::builtins::global::global_eval(
"var o = {}; Object.freeze(o); Object.isFrozen(o)",
)
.unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn e2e_object_is_sealed() {
let result =
crate::builtins::global::global_eval("var o = {}; Object.seal(o); Object.isSealed(o)")
.unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn e2e_for_of_break_calls_return() {
let result = crate::builtins::global::global_eval(
"var count = 0;\
for (var x of [1,2,3]) { count++; break; }\
count",
)
.unwrap();
assert_eq!(
result,
JsValue::Smi(1),
"break should exit for-of after first iteration"
);
}
#[test]
fn e2e_spread_call_basic() {
let result = crate::builtins::global::global_eval(
"function sum(a, b, c) { return a + b + c; } sum(...[1, 2, 3])",
)
.unwrap();
assert_eq!(result, JsValue::Smi(6));
}
#[test]
fn e2e_spread_call_mixed_args() {
let result = crate::builtins::global::global_eval(
"function sum(a, b, c, d) { return a + b + c + d; } sum(0, ...[1, 2], 3)",
)
.unwrap();
assert_eq!(result, JsValue::Smi(6));
}
#[test]
fn e2e_spread_call_variable() {
let result = crate::builtins::global::global_eval(
"function sum(a, b, c) { return a + b + c; } var arr = [10, 20, 30]; sum(...arr)",
)
.unwrap();
assert_eq!(result, JsValue::Smi(60));
}
#[test]
fn e2e_spread_construct() {
let result = crate::builtins::global::global_eval(
"function Pair(a, b) { this.sum = a + b; } \
var p = new Pair(...[3, 7]); p.sum",
)
.unwrap();
assert_eq!(result, JsValue::Smi(10));
}
#[test]
fn e2e_spread_call_empty() {
let result = crate::builtins::global::global_eval(
"function len() { return arguments.length; } len(...[])",
)
.unwrap();
assert_eq!(result, JsValue::Smi(0));
}
#[test]
fn e2e_typeof_null() {
let result = crate::builtins::global::global_eval("typeof null").unwrap();
assert_eq!(result, JsValue::String("object".into()));
}
#[test]
fn e2e_nan_strict_equal() {
let result = crate::builtins::global::global_eval("NaN === NaN").unwrap();
assert_eq!(result, JsValue::Boolean(false));
}
#[test]
fn e2e_positive_negative_zero_equal() {
let result = crate::builtins::global::global_eval("+0 === -0").unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn test_object_literal_has_tostring() {
let result = crate::builtins::global::global_eval("({}).toString()").unwrap();
assert_eq!(result, JsValue::String("[object Object]".into()));
}
#[test]
fn test_object_literal_has_valueof() {
let result = crate::builtins::global::global_eval("typeof ({}).valueOf()").unwrap();
assert_eq!(result, JsValue::String("object".into()));
}
#[test]
fn test_object_literal_has_hasownproperty() {
let result = crate::builtins::global::global_eval("({a: 1}).hasOwnProperty('a')").unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn test_object_literal_hasownproperty_missing() {
let result = crate::builtins::global::global_eval("({a: 1}).hasOwnProperty('b')").unwrap();
assert_eq!(result, JsValue::Boolean(false));
}
#[test]
fn test_object_literal_tostring_tag() {
let result = crate::builtins::global::global_eval("var o = {x: 1}; o.toString()").unwrap();
assert_eq!(result, JsValue::String("[object Object]".into()));
}
#[test]
fn test_object_literal_property_writable() {
let result = crate::builtins::global::global_eval("var o = {a: 1}; o.a = 2; o.a").unwrap();
assert_eq!(result, JsValue::Smi(2));
}
#[test]
fn test_object_literal_property_enumerable() {
let result = crate::builtins::global::global_eval(
"var o = {a: 1, b: 2}; var r = ''; for (var k in o) r += k + ','; r",
)
.unwrap();
assert_eq!(result, JsValue::String("a,b,".into()));
}
#[test]
fn test_object_literal_property_configurable() {
let result =
crate::builtins::global::global_eval("var o = {a: 1}; delete o.a; o.a === undefined")
.unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn test_object_literal_descriptor_all_true() {
let result = crate::builtins::global::global_eval(
"var d = Object.getOwnPropertyDescriptor({a: 1}, 'a'); \
d.writable === true && d.enumerable === true && d.configurable === true",
)
.unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn test_frozen_object_strict_throws() {
let result = crate::builtins::global::global_eval(
"var o = Object.freeze({a: 1}); \
(function() { 'use strict'; try { o.a = 2; return 'no error'; } catch(e) { return e.message; } })()",
)
.unwrap();
match result {
JsValue::String(s) => assert!(
s.contains("read only"),
"Expected 'read only' in error: {s}"
),
other => panic!("Expected string error message, got: {other:?}"),
}
}
#[test]
fn test_sealed_object_no_add_strict() {
let result = crate::builtins::global::global_eval(
"var o = Object.seal({a: 1}); \
(function() { 'use strict'; try { o.b = 2; return 'no error'; } catch(e) { return e.message; } })()",
)
.unwrap();
match result {
JsValue::String(s) => assert!(
s.contains("not extensible"),
"Expected 'not extensible' in error: {s}"
),
other => panic!("Expected string error message, got: {other:?}"),
}
}
#[test]
fn test_frozen_object_is_frozen() {
let result =
crate::builtins::global::global_eval("Object.isFrozen(Object.freeze({a: 1}))").unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn test_sealed_object_existing_prop_writable() {
let result =
crate::builtins::global::global_eval("var o = Object.seal({a: 1}); o.a = 99; o.a")
.unwrap();
assert_eq!(result, JsValue::Smi(99));
}
#[test]
fn e2e_define_property_getter_invoked() {
let result = crate::builtins::global::global_eval(
"var o = {}; \
Object.defineProperty(o, 'x', { get: function() { return 42; } }); \
o.x",
)
.unwrap();
assert_eq!(result, JsValue::Smi(42));
}
#[test]
fn e2e_define_property_getter_error_propagates() {
let error = crate::builtins::global::global_eval(
"var o = {}; \
Object.defineProperty(o, 'x', { get: function() { throw new TypeError('getter boom'); } }); \
o.x",
)
.expect_err("throwing getter should fail the property load");
let message = error.to_string();
assert!(
message.contains("getter boom"),
"unexpected getter error: {message}"
);
}
#[test]
fn e2e_define_property_keyed_getter_error_propagates() {
let error = crate::builtins::global::global_eval(
"var o = {}; \
Object.defineProperty(o, 'x', { get: function() { throw new TypeError('keyed boom'); } }); \
o['x']",
)
.expect_err("throwing keyed getter should fail the property load");
let message = error.to_string();
assert!(
message.contains("keyed boom"),
"unexpected keyed getter error: {message}"
);
}
#[test]
fn e2e_define_property_prototype_getter_error_propagates() {
let error = crate::builtins::global::global_eval(
"var proto = {}; \
Object.defineProperty(proto, 'x', { get: function() { throw new TypeError('proto boom'); } }); \
var o = Object.create(proto); \
o.x",
)
.expect_err("throwing prototype getter should fail the property load");
let message = error.to_string();
assert!(
message.contains("proto boom"),
"unexpected prototype getter error: {message}"
);
}
#[test]
fn e2e_non_writable_sloppy_silent() {
let result = crate::builtins::global::global_eval(
"var o = {}; \
Object.defineProperty(o, 'x', { value: 10, writable: false }); \
o.x = 99; o.x",
)
.unwrap();
assert_eq!(result, JsValue::Smi(10));
}
#[test]
fn e2e_non_writable_strict_throws() {
let result = crate::builtins::global::global_eval(
"'use strict'; var o = {}; \
Object.defineProperty(o, 'x', { value: 10, writable: false }); \
o.x = 99;",
);
assert!(
result.is_err(),
"strict mode write to non-writable should throw"
);
}
#[test]
fn e2e_prevent_extensions_sloppy_new_prop() {
let result = crate::builtins::global::global_eval(
"var o = { x: 1 }; Object.preventExtensions(o); o.y = 2; \
typeof o.y",
)
.unwrap();
assert_eq!(result, JsValue::String("undefined".into()));
}
#[test]
fn e2e_prevent_extensions_existing_writable() {
let result = crate::builtins::global::global_eval(
"var o = { x: 1 }; Object.preventExtensions(o); o.x = 99; o.x",
)
.unwrap();
assert_eq!(result, JsValue::Smi(99));
}
#[test]
fn e2e_object_is_extensible() {
let result = crate::builtins::global::global_eval(
"var o = {}; var before = Object.isExtensible(o); \
Object.preventExtensions(o); \
var after = Object.isExtensible(o); \
'' + before + ',' + after",
)
.unwrap();
assert_eq!(result, JsValue::String("true,false".into()));
}
#[test]
fn e2e_non_configurable_delete_strict() {
let result = crate::builtins::global::global_eval(
"'use strict'; var o = {}; \
Object.defineProperty(o, 'x', { value: 1, configurable: false }); \
delete o.x;",
);
assert!(
result.is_err(),
"strict delete of non-configurable should throw"
);
}
#[test]
fn e2e_non_configurable_delete_sloppy() {
let result = crate::builtins::global::global_eval(
"var o = {}; \
Object.defineProperty(o, 'x', { value: 1, configurable: false }); \
delete o.x",
)
.unwrap();
assert_eq!(result, JsValue::Boolean(false));
}
#[test]
fn e2e_freeze_descriptor_check() {
let result = crate::builtins::global::global_eval(
"var o = { x: 1 }; Object.freeze(o); \
var d = Object.getOwnPropertyDescriptor(o, 'x'); \
'' + d.writable + ',' + d.configurable",
)
.unwrap();
assert_eq!(result, JsValue::String("false,false".into()));
}
#[test]
fn e2e_seal_descriptor_check() {
let result = crate::builtins::global::global_eval(
"var o = { x: 1 }; Object.seal(o); \
var d = Object.getOwnPropertyDescriptor(o, 'x'); \
'' + d.writable + ',' + d.configurable",
)
.unwrap();
assert_eq!(result, JsValue::String("true,false".into()));
}
#[test]
fn test_arguments_length() {
let result = crate::builtins::global::global_eval(
"(function(a, b, c) { return arguments.length; })(10, 20, 30)",
)
.unwrap();
assert_eq!(result, JsValue::Smi(3));
}
#[test]
fn test_arguments_indexed_access() {
let result = crate::builtins::global::global_eval(
"(function(a, b) { return arguments[0] + arguments[1]; })(3, 7)",
)
.unwrap();
assert_eq!(result, JsValue::Smi(10));
}
#[test]
fn test_arguments_callee_sloppy() {
let result = crate::builtins::global::global_eval(
"(function f() { return typeof arguments.callee; })()",
)
.unwrap();
assert_eq!(result, JsValue::String("function".into()));
}
#[test]
fn test_arguments_exists_in_function() {
let result = crate::builtins::global::global_eval(
"(function() { return typeof arguments; })('a','b')",
)
.unwrap();
assert_eq!(result, JsValue::String("object".into()));
}
#[test]
fn test_for_in_insertion_order_strings() {
let result = crate::builtins::global::global_eval(
"var o = {b: 1, a: 2, c: 3}; var r = ''; for (var k in o) r += k + ','; r",
)
.unwrap();
assert_eq!(result, JsValue::String("b,a,c,".into()));
}
#[test]
fn test_for_in_integer_indices_first() {
let result = crate::builtins::global::global_eval(
"var o = {b: 1, 2: 2, a: 3, 0: 4}; var r = ''; for (var k in o) r += k + ','; r",
)
.unwrap();
assert_eq!(result, JsValue::String("0,2,b,a,".into()));
}
#[test]
fn test_for_in_skips_nonenumerable() {
let result = crate::builtins::global::global_eval(
"var o = Object.freeze({x: 1, y: 2}); var r = ''; for (var k in o) r += k + ','; r",
)
.unwrap();
assert_eq!(result, JsValue::String("x,y,".into()));
}
#[test]
fn test_template_object_is_frozen() {
let result = crate::builtins::global::global_eval(
"function tag(strs) { return Object.isFrozen(strs); } tag`hello`",
)
.unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn test_template_object_raw_is_frozen() {
let result = crate::builtins::global::global_eval(
"function tag(strs) { return Object.isFrozen(strs.raw); } tag`hello`",
)
.unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn test_template_object_has_raw() {
let result = crate::builtins::global::global_eval(
"function tag(strs) { return strs.raw !== undefined; } tag`hello`",
)
.unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn test_template_object_length() {
let result = crate::builtins::global::global_eval(
"function tag(strs) { return strs.length; } tag`a${1}b`",
)
.unwrap();
assert_eq!(result, JsValue::Smi(2));
}
#[test]
fn test_typeof_null_is_object() {
let result = crate::builtins::global::global_eval("typeof null").unwrap();
assert_eq!(result, JsValue::String("object".into()));
}
#[test]
fn test_template_literal_basic() {
let result = crate::builtins::global::global_eval("`hello world`").unwrap();
assert_eq!(result, JsValue::String("hello world".into()));
}
#[test]
fn test_template_literal_expression() {
let result = crate::builtins::global::global_eval("var x = 42; `value is ${x}`").unwrap();
assert_eq!(result, JsValue::String("value is 42".into()));
}
#[test]
#[ignore] fn test_spread_in_array_literal() {
let result =
crate::builtins::global::global_eval("var a = [1,2,3]; [...a].length").unwrap();
assert_eq!(result, JsValue::Smi(3));
}
#[test]
fn test_object_spread() {
let result =
crate::builtins::global::global_eval("var a = {x:1}; var b = {...a, y:2}; b.x + b.y")
.unwrap();
assert_eq!(result, JsValue::Smi(3));
}
#[test]
fn test_nullish_coalescing_null() {
let result = crate::builtins::global::global_eval("null ?? 42").unwrap();
assert_eq!(result, JsValue::Smi(42));
}
#[test]
fn test_nullish_coalescing_zero() {
let result = crate::builtins::global::global_eval("0 ?? 42").unwrap();
assert_eq!(result, JsValue::Smi(0));
}
#[test]
fn test_optional_chaining_null() {
let result = crate::builtins::global::global_eval("var obj = null; obj?.x").unwrap();
assert_eq!(result, JsValue::Undefined);
}
#[test]
fn test_optional_chaining_value() {
let result = crate::builtins::global::global_eval("var obj = {x: 10}; obj?.x").unwrap();
assert_eq!(result, JsValue::Smi(10));
}
#[test]
fn test_logical_or_assignment() {
let result = crate::builtins::global::global_eval("var x = 0; x ||= 5; x").unwrap();
assert_eq!(result, JsValue::Smi(5));
}
#[test]
fn test_logical_and_assignment() {
let result = crate::builtins::global::global_eval("var x = 1; x &&= 5; x").unwrap();
assert_eq!(result, JsValue::Smi(5));
}
#[test]
fn test_nullish_assignment() {
let result = crate::builtins::global::global_eval("var x = null; x ??= 5; x").unwrap();
assert_eq!(result, JsValue::Smi(5));
}
#[test]
fn test_exponentiation() {
let result = crate::builtins::global::global_eval("2 ** 10").unwrap();
assert_eq!(result, JsValue::Smi(1024));
}
#[test]
fn test_destructuring_default() {
let result = crate::builtins::global::global_eval("var {x = 10} = {}; x").unwrap();
assert_eq!(result, JsValue::Smi(10));
}
#[test]
fn test_array_destructuring_basic() {
let result = crate::builtins::global::global_eval("var [a, b] = [1, 2]; a + b").unwrap();
assert_eq!(result, JsValue::Smi(3));
}
#[test]
fn test_computed_property_name() {
let result =
crate::builtins::global::global_eval("var key = 'x'; var obj = {[key]: 42}; obj.x")
.unwrap();
assert_eq!(result, JsValue::Smi(42));
}
#[test]
fn test_for_of_array_sum() {
let result = crate::builtins::global::global_eval(
"var sum = 0; for (var x of [1,2,3]) { sum += x; } sum",
)
.unwrap();
assert_eq!(result, JsValue::Smi(6));
}
#[test]
fn test_generator_basic_next() {
let result = crate::builtins::global::global_eval(
"function* gen() { yield 1; yield 2; } var g = gen(); g.next().value",
)
.unwrap();
assert_eq!(result, JsValue::Smi(1));
}
#[test]
fn test_promise_resolve_type() {
let result = crate::builtins::global::global_eval("typeof Promise.resolve(42)").unwrap();
assert_eq!(result, JsValue::String("object".into()));
}
#[test]
#[ignore] fn test_class_basic_method() {
let result = crate::builtins::global::global_eval(
"class Foo { bar() { return 42; } } new Foo().bar()",
)
.unwrap();
assert_eq!(result, JsValue::Smi(42));
}
#[test]
fn test_typeof_undeclared_returns_undefined_string() {
let result = crate::builtins::global::global_eval("typeof totallyUndeclared").unwrap();
assert_eq!(result, JsValue::String("undefined".into()));
}
#[test]
fn test_import_meta_returns_object() {
use crate::bytecode::bytecode_array::BytecodeArray;
use crate::bytecode::bytecodes::{Instruction, Opcode, encode};
use crate::bytecode::feedback::FeedbackMetadata;
use crate::interpreter::{Interpreter, InterpreterFrame};
let instrs = vec![
Instruction::new_unchecked(Opcode::LdaImportMeta, vec![]),
Instruction::new_unchecked(Opcode::Return, vec![]),
];
let ba = BytecodeArray::new(
encode(&instrs),
vec![],
0,
0,
vec![],
FeedbackMetadata::empty(),
vec![],
)
.with_module_flag(true);
let mut frame = InterpreterFrame::new(Rc::new(ba), vec![]);
let result = Interpreter::run(&mut frame).unwrap();
assert!(
matches!(result, JsValue::PlainObject(_)),
"import.meta should produce a PlainObject"
);
}
#[test]
fn test_module_variable_store_load_round_trip() {
use crate::bytecode::bytecode_array::{BytecodeArray, ConstantPoolEntry};
use crate::bytecode::bytecodes::{Instruction, Opcode, Operand, encode};
use crate::bytecode::feedback::FeedbackMetadata;
use crate::interpreter::{Interpreter, InterpreterFrame};
let instrs = vec![
Instruction::new_unchecked(Opcode::LdaSmi, vec![Operand::Immediate(99)]),
Instruction::new_unchecked(
Opcode::StaModuleVariable,
vec![Operand::ConstantPoolIdx(0), Operand::Immediate(0)],
),
Instruction::new_unchecked(Opcode::LdaUndefined, vec![]),
Instruction::new_unchecked(
Opcode::LdaModuleVariable,
vec![Operand::ConstantPoolIdx(0), Operand::Immediate(0)],
),
Instruction::new_unchecked(Opcode::Return, vec![]),
];
let ba = BytecodeArray::new(
encode(&instrs),
vec![ConstantPoolEntry::String(String::new())],
0,
0,
vec![],
FeedbackMetadata::empty(),
vec![],
)
.with_module_flag(true);
let mut frame = InterpreterFrame::new(Rc::new(ba), vec![]);
let result = Interpreter::run(&mut frame).unwrap();
assert_eq!(result, JsValue::Smi(99));
}
#[test]
fn test_get_module_namespace_returns_object() {
use crate::bytecode::bytecode_array::{BytecodeArray, ConstantPoolEntry};
use crate::bytecode::bytecodes::{Instruction, Opcode, Operand, encode};
use crate::bytecode::feedback::FeedbackMetadata;
use crate::interpreter::{Interpreter, InterpreterFrame};
let instrs = vec![
Instruction::new_unchecked(
Opcode::GetModuleNamespace,
vec![Operand::ConstantPoolIdx(0)],
),
Instruction::new_unchecked(Opcode::Return, vec![]),
];
let ba = BytecodeArray::new(
encode(&instrs),
vec![ConstantPoolEntry::String("./foo.js".to_string())],
0,
0,
vec![],
FeedbackMetadata::empty(),
vec![],
)
.with_module_flag(true);
let mut frame = InterpreterFrame::new(Rc::new(ba), vec![]);
let result = Interpreter::run(&mut frame).unwrap();
assert!(
matches!(result, JsValue::PlainObject(_)),
"GetModuleNamespace should produce a PlainObject"
);
}
#[test]
#[ignore]
fn test_for_in_prototype_chain() {
let result = crate::builtins::global::global_eval(
"function Base() {} \
Base.prototype.inherited = 1; \
var obj = new Base(); \
obj.own = 2; \
var keys = []; \
for (var k in obj) { keys.push(k); } \
keys.length",
)
.unwrap();
assert!(
matches!(result, JsValue::Smi(2)),
"expected 2 keys (own + inherited), got {result:?}"
);
}
#[test]
fn test_for_in_hides_internal_keys() {
let result = crate::builtins::global::global_eval(
"var o = {x: 1, y: 2}; \
var r = ''; \
for (var k in o) r += k + ','; \
r",
)
.unwrap();
assert_eq!(result, JsValue::String("x,y,".into()));
}
#[test]
fn test_for_in_shadowing() {
let result = crate::builtins::global::global_eval(
"function Base() {} \
Base.prototype.x = 1; \
var obj = new Base(); \
obj.x = 2; \
var count = 0; \
for (var k in obj) { if (k === 'x') count++; } \
count",
)
.unwrap();
assert_eq!(result, JsValue::Smi(1));
}
#[test]
fn test_for_in_null_undefined() {
let result = crate::builtins::global::global_eval(
"var count = 0; for (var k in null) count++; count",
)
.unwrap();
assert_eq!(result, JsValue::Smi(0));
}
#[test]
fn test_array_destructuring_with_defaults() {
let result =
crate::builtins::global::global_eval("var [a = 1, b = 2] = [10]; a * 100 + b").unwrap();
assert_eq!(result, JsValue::Smi(1002));
}
#[test]
fn test_object_destructuring_with_defaults() {
let result =
crate::builtins::global::global_eval("var {x = 1, y = 2} = {x: 10}; x * 100 + y")
.unwrap();
assert_eq!(result, JsValue::Smi(1002));
}
#[test]
fn test_nested_destructuring() {
let result =
crate::builtins::global::global_eval("var {a: {b}} = {a: {b: 42}}; b").unwrap();
assert_eq!(result, JsValue::Smi(42));
}
#[test]
fn test_array_destructuring_rest() {
let result =
crate::builtins::global::global_eval("var [a, ...b] = [1, 2, 3]; a * 100 + b.length")
.unwrap();
assert_eq!(result, JsValue::Smi(102));
}
#[test]
fn test_object_destructuring_rest() {
let result = crate::builtins::global::global_eval(
"var {a, ...rest} = {a: 1, b: 2, c: 3}; a + rest.b + rest.c",
)
.unwrap();
assert_eq!(result, JsValue::Smi(6));
}
#[test]
#[ignore] fn test_class_static_method() {
let result = crate::builtins::global::global_eval(
"class Foo { static bar() { return 99; } } Foo.bar()",
)
.unwrap();
assert_eq!(result, JsValue::Smi(99));
}
#[test]
#[ignore] fn test_class_expression() {
let result = crate::builtins::global::global_eval(
"var Foo = class { greet() { return 7; } }; new Foo().greet()",
)
.unwrap();
assert_eq!(result, JsValue::Smi(7));
}
#[test]
#[ignore] fn test_class_computed_property_name() {
let result = crate::builtins::global::global_eval(
"var name = 'greet'; \
class Foo { [name]() { return 55; } } \
new Foo().greet()",
)
.unwrap();
assert_eq!(result, JsValue::Smi(55));
}
#[test]
fn test_class_decl_basics() {
let result = crate::builtins::global::global_eval(
"class Foo { constructor(x) { this.x = x; } } \
typeof Foo === 'function' && Foo.prototype.constructor === Foo && new Foo(7).x === 7",
)
.unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn test_class_decl_tdz() {
let result = crate::builtins::global::global_eval(
"try { Foo; class Foo {} } catch (e) { e instanceof ReferenceError }",
)
.unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn test_class_body_is_strict() {
let result = crate::builtins::global::global_eval("class Foo { method() { delete x; } }");
assert!(
result.is_err(),
"class methods should compile in strict mode"
);
}
#[test]
#[ignore] fn test_super_method_call() {
let result = crate::builtins::global::global_eval(
"class Base { value() { return 10; } } \
class Child extends Base { value() { return super.value() + 5; } } \
new Child().value()",
)
.unwrap();
assert_eq!(result, JsValue::Smi(15));
}
#[test]
#[ignore] fn test_class_inheritance_instanceof() {
let result = crate::builtins::global::global_eval(
"class Base {} class Child extends Base {} \
var c = new Child(); c instanceof Base",
)
.unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
#[ignore] fn test_class_inheritance_prototype_chain() {
let result = crate::builtins::global::global_eval(
"class Foo {} \
class Bar extends Foo {} \
(Bar.__proto__ === Foo) && \
(Bar.prototype.__proto__ === Foo.prototype) && \
!(Bar.prototype instanceof Foo) && \
(new Bar() instanceof Foo)",
)
.unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn test_class_static_and_instance_fields() {
let result = crate::builtins::global::global_eval(
"class Foo { static x = 42; y = 7; } \
var a = new Foo(); \
var b = new Foo(); \
Foo.x === 42 && a.y === 7 && b.y === 7 && Foo.prototype.y === undefined",
)
.unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn test_class_static_block_runs_during_definition() {
assert_eval_true(
"var log = []; class C { static { log.push('block'); } } log.join(',') === 'block'",
);
}
#[test]
fn test_class_static_block_binds_this_to_constructor() {
assert_eval_true("class C { static { this.answer = 42; } } C.answer === 42");
}
#[test]
#[ignore] fn test_class_static_block_this_identity_matches_class() {
assert_eval_true("class C { static ok = false; static { this.ok = this === C; } } C.ok");
}
#[test]
fn test_class_multiple_static_blocks_follow_source_order() {
assert_eval_true(
"var log = []; class C { static { log.push('a'); } static { log.push('b'); } static { log.push('c'); } } log.join(',') === 'a,b,c'",
);
}
#[test]
fn test_class_static_blocks_interleave_with_static_fields() {
assert_eval_true(
"var log = []; class C { static a = log.push('a'); static { log.push('b'); } static c = log.push('c'); static { log.push('d'); } } log.join(',') === 'a,b,c,d'",
);
}
#[test]
fn test_class_static_block_reads_prior_static_field() {
assert_eval_true("class C { static x = 1; static { this.y = this.x + 1; } } C.y === 2");
}
#[test]
fn test_class_static_block_updates_static_field_before_later_field() {
assert_eval_true(
"class C { static x = 1; static { this.x += 1; } static y = this.x + 1; } C.x === 2 && C.y === 3",
);
}
#[test]
fn test_class_static_field_initializer_uses_class_this() {
assert_eval_true("class C { static x = 7; static y = this.x + 1; } C.y === 8");
}
#[test]
fn test_class_static_field_initializer_sees_prior_block_updates() {
assert_eval_true(
"class C { static x = 1; static { this.x = 5; } static y = this.x; } C.y === 5",
);
}
#[test]
fn test_class_static_block_reads_private_static_field() {
assert_eval_true(
"class C { static #x = 10; static y = 0; static { this.y = this.#x; } } C.y === 10",
);
}
#[test]
fn test_class_static_block_calls_private_static_method() {
assert_eval_true(
"class C { static #value() { return 9; } static y = 0; static { this.y = this.#value(); } } C.y === 9",
);
}
#[test]
fn test_class_static_blocks_share_private_static_state() {
assert_eval_true(
"class C { static #x = 1; static first = 0; static second = 0; static { this.first = this.#x; this.#x = 2; } static { this.second = this.#x; } } C.first === 1 && C.second === 2",
);
}
#[test]
#[ignore] fn test_class_static_block_super_calls_parent_static_method() {
assert_eval_true(
"class A { static value() { return 40; } } class B extends A { static result = 0; static { this.result = super.value() + 2; } } B.result === 42",
);
}
#[test]
#[ignore] fn test_class_static_block_super_uses_current_class_as_receiver() {
assert_eval_true(
"class A { static who() { return this.name; } } class B extends A { static result = ''; static { this.result = super.who(); } } B.result === 'B'",
);
}
#[test]
#[ignore] fn test_class_static_field_super_calls_parent_static_method() {
assert_eval_true(
"class A { static value() { return 5; } } class B extends A { static x = super.value() + 1; } B.x === 6",
);
}
#[test]
#[ignore] fn test_class_static_field_super_uses_current_class_as_receiver() {
assert_eval_true(
"class A { static who() { return this.name; } } class B extends A { static value = super.who(); } B.value === 'B'",
);
}
#[test]
fn test_class_static_block_throw_aborts_class_declaration() {
assert_eval_true("try { class C { static { throw 1; } } false; } catch (e) { e === 1; }");
}
#[test]
fn test_class_static_block_throw_aborts_class_expression() {
assert_eval_true(
"try { var C = class { static { throw 2; } }; false; } catch (e) { e === 2; }",
);
}
#[test]
fn test_class_static_block_runs_in_class_expression() {
assert_eval_true(
"var seen = 0; var C = class { static { seen = 1; } }; seen === 1 && typeof C === 'function'",
);
}
#[test]
fn test_class_static_blocks_in_class_expression_follow_order() {
assert_eval_true(
"var log = []; var C = class { static { log.push('x'); } static { log.push('y'); } }; log.join(',') === 'x,y' && typeof C === 'function'",
);
}
#[test]
#[ignore] fn test_class_static_block_sees_class_expression_name() {
assert_eval_true(
"var value = class C { static { this.ok = C === this; } }; value.ok === true",
);
}
#[test]
fn test_class_instance_fields_run_per_new_invocation() {
assert_eval_true(
"var n = 0; class C { x = ++n; } var a = new C(); var b = new C(); a.x === 1 && b.x === 2",
);
}
#[test]
fn test_class_instance_fields_follow_declaration_order() {
assert_eval_true(
"class C { a = 1; b = this.a + 1; c = this.b + 1; } var o = new C(); o.a === 1 && o.b === 2 && o.c === 3",
);
}
#[test]
fn test_class_instance_fields_run_before_base_constructor_body() {
assert_eval_true(
"class C { x = 1; constructor() { this.y = this.x + 1; } } new C().y === 2",
);
}
#[test]
#[ignore] fn test_class_instance_fields_run_after_super_in_derived_constructor() {
assert_eval_true(
"class A { constructor() { this.log = ['base']; } } class B extends A { x = this.log.push('field'); constructor() { super(); this.log.push('ctor'); } } new B().log.join(',') === 'base,field,ctor'",
);
}
#[test]
#[ignore] fn test_class_instance_computed_field_name_evaluates_once() {
assert_eval_true(
"var count = 0; function key() { count++; return 'x'; } class C { [key()] = 1; } var a = new C(); var b = new C(); count === 1 && a.x === 1 && b.x === 1",
);
}
#[test]
#[ignore] fn test_class_instance_computed_field_names_follow_source_order() {
assert_eval_true(
"var log = []; function key(v) { log.push(v); return v; } class C { [key('a')] = 1; [key('b')] = 2; } new C(); new C(); log.join(',') === 'a,b'",
);
}
#[test]
#[ignore] fn test_class_instance_computed_field_names_interleave_with_static_blocks() {
assert_eval_true(
"var log = []; function key(v) { log.push(v); return v; } class C { [key('a')] = 1; static { log.push('b'); } [key('c')] = 2; } log.join(',') === 'a,b,c'",
);
}
#[test]
#[ignore] fn test_class_instance_computed_field_names_interleave_with_static_fields() {
assert_eval_true(
"var log = []; function key(v) { log.push(v); return v; } class C { [key('a')] = 1; static x = log.push('b'); [key('c')] = 2; } log.join(',') === 'a,b,c'",
);
}
#[test]
#[ignore] fn test_class_instance_computed_field_names_are_cached_for_multiple_fields() {
assert_eval_true(
"var idx = 0; function key() { idx++; return 'k' + idx; } class C { [key()] = 1; [key()] = 2; } var o = new C(); idx === 2 && o.k1 === 1 && o.k2 === 2",
);
}
#[test]
#[ignore] fn test_class_instance_computed_field_names_are_cached_across_instances() {
assert_eval_true(
"var count = 0; function key() { count++; return 'x'; } var value = 0; class C { [key()] = ++value; } var a = new C(); var b = new C(); count === 1 && a.x === 1 && b.x === 2",
);
}
#[test]
#[ignore] fn test_class_instance_computed_field_names_only_run_once_for_derived_class() {
assert_eval_true(
"var count = 0; function key() { count++; return 'x'; } class A {} class B extends A { [key()] = 1; } new B(); new B(); count === 1",
);
}
#[test]
#[ignore] fn test_class_instance_computed_field_name_with_this_based_initializer() {
assert_eval_true(
"var count = 0; function key() { count++; return 'x'; } class C { y = 2; [key()] = this.y + 1; } var o = new C(); count === 1 && o.x === 3",
);
}
#[test]
#[ignore] fn test_class_instance_computed_field_names_are_defined_on_each_instance() {
assert_eval_true(
"var count = 0; function key() { count++; return 'x'; } class C { [key()] = 1; } var a = new C(); var b = new C(); count === 1 && a.hasOwnProperty('x') && b.hasOwnProperty('x')",
);
}
#[test]
fn test_class_instance_field_throw_propagates_per_new() {
assert_eval_true(
"class C { x = (() => { throw 7; })(); } try { new C(); false; } catch (e) { e === 7; }",
);
}
#[test]
fn test_class_expression_name_not_visible_outside() {
let result =
crate::builtins::global::global_eval("var Foo = class Bar {}; typeof Bar").unwrap();
assert_eq!(result, JsValue::String("undefined".into()));
}
#[test]
fn test_class_constructor_requires_new() {
let result = crate::builtins::global::global_eval(
"class Foo {} \
try { Foo(); false; } catch (e) { e instanceof TypeError }",
)
.unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn test_derived_class_requires_super_before_this() {
let result = crate::builtins::global::global_eval(
"class Foo {} \
class Bar extends Foo { constructor() { this.x = 1; super(); } } \
try { new Bar(); false; } catch (e) { e instanceof ReferenceError }",
)
.unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn test_derived_class_cannot_call_super_twice() {
let result = crate::builtins::global::global_eval(
"class Foo {} \
class Bar extends Foo { constructor() { super(); super(); } } \
try { new Bar(); false; } catch (e) { e instanceof ReferenceError }",
)
.unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn e2e_new_target_in_constructor_is_ctor() {
let r = crate::builtins::global::global_eval(
"var nt; function Foo() { nt = new.target; } new Foo(); nt === Foo",
)
.unwrap();
assert_eq!(r, JsValue::Boolean(true));
}
#[test]
fn e2e_new_target_undefined_in_normal_call() {
let r = crate::builtins::global::global_eval(
"var nt; function Foo() { nt = new.target; } Foo(); nt === undefined",
)
.unwrap();
assert_eq!(r, JsValue::Boolean(true));
}
#[test]
fn e2e_new_target_defined_in_class_constructor() {
let r = crate::builtins::global::global_eval(
"var nt; class A { constructor() { nt = new.target; } } new A(); nt !== undefined",
)
.unwrap();
assert_eq!(r, JsValue::Boolean(true));
}
#[test]
fn e2e_new_target_is_class_in_class_constructor() {
let r = crate::builtins::global::global_eval(
"var nt; class A { constructor() { nt = new.target; } } new A(); nt === A",
)
.unwrap();
assert_eq!(r, JsValue::Boolean(true));
}
#[test]
fn e2e_new_target_is_derived_in_parent_constructor() {
let r = crate::builtins::global::global_eval(
"var nt; \
class A { constructor() { nt = new.target; } } \
class B extends A { constructor() { super(); } } \
new B(); nt === B",
)
.unwrap();
assert_eq!(r, JsValue::Boolean(true));
}
#[test]
#[ignore] fn e2e_new_target_three_level_inheritance() {
let r = crate::builtins::global::global_eval(
"var nt; \
class A { constructor() { nt = new.target; } } \
class B extends A { constructor() { super(); } } \
class C extends B { constructor() { super(); } } \
new C(); nt === C",
)
.unwrap();
assert_eq!(r, JsValue::Boolean(true));
}
#[test]
fn e2e_new_target_in_arrow_inside_constructor() {
let r = crate::builtins::global::global_eval(
"var nt; \
function Foo() { var f = () => new.target; nt = f(); } \
new Foo(); nt === Foo",
)
.unwrap();
assert_eq!(r, JsValue::Boolean(true));
}
#[test]
fn e2e_new_target_arrow_in_normal_call_undefined() {
let r = crate::builtins::global::global_eval(
"var nt; \
function Foo() { var f = () => new.target; nt = f(); } \
Foo(); nt === undefined",
)
.unwrap();
assert_eq!(r, JsValue::Boolean(true));
}
#[test]
fn e2e_new_target_nested_arrow() {
let r = crate::builtins::global::global_eval(
"var nt; \
function Foo() { var f = () => (() => new.target)(); nt = f(); } \
new Foo(); nt === Foo",
)
.unwrap();
assert_eq!(r, JsValue::Boolean(true));
}
#[test]
fn e2e_new_target_arrow_stored_and_called_later() {
let r = crate::builtins::global::global_eval(
"var getter; \
function Foo() { getter = () => new.target; } \
new Foo(); \
getter() === Foo",
)
.unwrap();
assert_eq!(r, JsValue::Boolean(true));
}
#[test]
fn e2e_new_target_arrow_inside_class_constructor() {
let r = crate::builtins::global::global_eval(
"var nt; \
class A { constructor() { var f = () => new.target; nt = f(); } } \
new A(); nt === A",
)
.unwrap();
assert_eq!(r, JsValue::Boolean(true));
}
#[test]
fn e2e_new_target_arrow_in_derived_constructor() {
let r = crate::builtins::global::global_eval(
"var nt; \
class A { constructor() {} } \
class B extends A { constructor() { super(); var f = () => new.target; nt = f(); } } \
new B(); nt === B",
)
.unwrap();
assert_eq!(r, JsValue::Boolean(true));
}
#[test]
fn e2e_new_target_outside_function_syntax_error() {
let r = crate::builtins::global::global_eval("new.target");
assert!(
r.is_err(),
"new.target at top level should be a SyntaxError"
);
}
#[test]
fn e2e_new_target_in_eval_top_level_error() {
let r = crate::builtins::global::global_eval(
"try { eval('new.target'); false; } catch(e) { true }",
)
.unwrap();
assert_eq!(r, JsValue::Boolean(true));
}
#[test]
fn e2e_reflect_construct_new_target_is_target() {
let r = crate::builtins::global::global_eval(
"var nt; function A() { nt = new.target; } \
Reflect.construct(A, []); nt === A",
)
.unwrap();
assert_eq!(r, JsValue::Boolean(true));
}
#[test]
fn e2e_reflect_construct_with_new_target() {
let r = crate::builtins::global::global_eval(
"var nt; function A() { nt = new.target; } \
function B() {} \
Reflect.construct(A, [], B); nt === B",
)
.unwrap();
assert_eq!(r, JsValue::Boolean(true));
}
#[test]
#[ignore] fn e2e_reflect_construct_prototype_from_new_target() {
let r = crate::builtins::global::global_eval(
"function A() {} \
function B() {} \
B.prototype.x = 42; \
var obj = Reflect.construct(A, [], B); \
obj.x === 42",
)
.unwrap();
assert_eq!(r, JsValue::Boolean(true));
}
#[test]
fn e2e_reflect_construct_default_new_target() {
let r = crate::builtins::global::global_eval(
"var nt; function A() { nt = new.target; } \
Reflect.construct(A, []); nt === A",
)
.unwrap();
assert_eq!(r, JsValue::Boolean(true));
}
#[test]
fn e2e_reflect_construct_returns_object() {
let r = crate::builtins::global::global_eval(
"function A() { this.x = 1; } \
var o = Reflect.construct(A, []); o.x",
)
.unwrap();
assert_eq!(r, JsValue::Smi(1));
}
#[test]
fn e2e_reflect_construct_non_constructor_throws() {
let r = crate::builtins::global::global_eval(
"try { Reflect.construct(123, []); false; } catch(e) { e instanceof TypeError }",
)
.unwrap();
assert_eq!(r, JsValue::Boolean(true));
}
#[test]
fn e2e_reflect_construct_non_constructor_new_target_throws() {
let r = crate::builtins::global::global_eval(
"try { Reflect.construct(function(){}, [], 123); false; } \
catch(e) { e instanceof TypeError }",
)
.unwrap();
assert_eq!(r, JsValue::Boolean(true));
}
#[test]
fn e2e_new_target_base_vs_derived_identity() {
let r = crate::builtins::global::global_eval(
"var ntA, ntB; \
class A { constructor() { ntA = new.target; } } \
class B extends A { constructor() { super(); ntB = new.target; } } \
new B(); ntA === B && ntB === B",
)
.unwrap();
assert_eq!(r, JsValue::Boolean(true));
}
#[test]
fn e2e_new_target_direct_base_new() {
let r = crate::builtins::global::global_eval(
"var nt; \
class A { constructor() { nt = new.target; } } \
class B extends A { constructor() { super(); } } \
new A(); nt === A",
)
.unwrap();
assert_eq!(r, JsValue::Boolean(true));
}
#[test]
#[ignore] fn e2e_new_target_in_method_undefined() {
let r = crate::builtins::global::global_eval(
"var nt; \
class A { m() { nt = new.target; } } \
new A().m(); nt === undefined",
)
.unwrap();
assert_eq!(r, JsValue::Boolean(true));
}
#[test]
fn e2e_new_target_preserved_super_with_args() {
let r = crate::builtins::global::global_eval(
"var nt; \
class A { constructor(x) { nt = new.target; this.x = x; } } \
class B extends A { constructor() { super(42); } } \
var b = new B(); nt === B && b.x === 42",
)
.unwrap();
assert_eq!(r, JsValue::Boolean(true));
}
#[test]
fn e2e_new_target_function_expression() {
let r = crate::builtins::global::global_eval(
"var nt; var F = function() { nt = new.target; }; new F(); nt === F",
)
.unwrap();
assert_eq!(r, JsValue::Boolean(true));
}
#[test]
fn e2e_new_target_typeof() {
let r = crate::builtins::global::global_eval(
"var t; function Foo() { t = typeof new.target; } new Foo(); t",
)
.unwrap();
assert_eq!(r, JsValue::String("function".into()));
}
#[test]
fn e2e_new_target_typeof_normal_call() {
let r = crate::builtins::global::global_eval(
"var t; function Foo() { t = typeof new.target; } Foo(); t",
)
.unwrap();
assert_eq!(r, JsValue::String("undefined".into()));
}
#[test]
#[ignore] fn e2e_new_target_arrow_in_method_undefined() {
let r = crate::builtins::global::global_eval(
"var nt; \
class A { m() { var f = () => new.target; nt = f(); } } \
new A().m(); nt === undefined",
)
.unwrap();
assert_eq!(r, JsValue::Boolean(true));
}
#[test]
#[ignore] fn e2e_new_target_abstract_class_pattern() {
let r = crate::builtins::global::global_eval(
"class Abstract { \
constructor() { \
if (new.target === Abstract) throw new TypeError('abstract'); \
} \
} \
class Concrete extends Abstract { constructor() { super(); } } \
var ok = false; \
try { new Abstract(); } catch(e) { ok = e instanceof TypeError; } \
ok && (new Concrete() instanceof Concrete)",
)
.unwrap();
assert_eq!(r, JsValue::Boolean(true));
}
#[test]
fn e2e_reflect_construct_class_new_target() {
let r = crate::builtins::global::global_eval(
"var nt; \
class A { constructor() { nt = new.target; } } \
class B {} \
Reflect.construct(A, [], B); nt === B",
)
.unwrap();
assert_eq!(r, JsValue::Boolean(true));
}
#[test]
fn e2e_new_target_iife_arrow_in_constructor() {
let r = crate::builtins::global::global_eval(
"var nt; \
function Foo() { nt = (() => new.target)(); } \
new Foo(); nt === Foo",
)
.unwrap();
assert_eq!(r, JsValue::Boolean(true));
}
#[test]
fn e2e_new_target_inside_try_catch() {
let r = crate::builtins::global::global_eval(
"var nt; \
function Foo() { try { nt = new.target; } catch(e) {} } \
new Foo(); nt === Foo",
)
.unwrap();
assert_eq!(r, JsValue::Boolean(true));
}
#[test]
fn e2e_reflect_construct_passes_args() {
let r = crate::builtins::global::global_eval(
"function A(x, y) { this.sum = x + y; } \
var o = Reflect.construct(A, [3, 4]); o.sum",
)
.unwrap();
assert_eq!(r, JsValue::Smi(7));
}
#[test]
fn e2e_new_target_with_default_params() {
let r = crate::builtins::global::global_eval(
"var nt; function Foo(x = 1) { nt = new.target; } new Foo(); nt === Foo",
)
.unwrap();
assert_eq!(r, JsValue::Boolean(true));
}
#[test]
fn e2e_instanceof_non_callable_rhs_throws() {
let result = crate::builtins::global::global_eval("1 instanceof 42");
assert!(
result.is_err(),
"instanceof with non-callable RHS should throw TypeError"
);
}
#[test]
fn e2e_instanceof_null_rhs_throws() {
let result = crate::builtins::global::global_eval("({}) instanceof null");
assert!(
result.is_err(),
"instanceof with null RHS should throw TypeError"
);
}
#[test]
fn e2e_instanceof_undefined_rhs_throws() {
let result = crate::builtins::global::global_eval("({}) instanceof undefined");
assert!(
result.is_err(),
"instanceof with undefined RHS should throw TypeError"
);
}
#[test]
fn test_tagged_template_strings_array() {
let result = crate::builtins::global::global_eval(
"function tag(strs) { return strs.length; } tag`a${1}b${2}c`",
)
.unwrap();
assert_eq!(result, JsValue::Smi(3));
}
#[test]
fn test_tagged_template_expression_args() {
let result = crate::builtins::global::global_eval(
"function tag(strs, a, b) { return a + b; } tag`x${10}y${20}z`",
)
.unwrap();
assert_eq!(result, JsValue::Smi(30));
}
#[test]
fn test_template_literal_with_object() {
let result = crate::builtins::global::global_eval(
"var obj = {toString: function() { return 'hello'; }}; `${obj}`",
)
.unwrap();
assert_eq!(result, JsValue::String("hello".into()));
}
#[test]
fn test_optional_chaining_undefined_obj() {
let result = crate::builtins::global::global_eval("var x = undefined; x?.foo").unwrap();
assert_eq!(result, JsValue::Undefined);
}
#[test]
fn test_optional_chaining_nested() {
let result = crate::builtins::global::global_eval("var a = {b: {c: 42}}; a?.b?.c").unwrap();
assert_eq!(result, JsValue::Smi(42));
}
#[test]
fn test_optional_chaining_nested_null() {
let result = crate::builtins::global::global_eval("var a = {b: null}; a?.b?.c").unwrap();
assert_eq!(result, JsValue::Undefined);
}
#[test]
fn test_optional_chaining_method_null() {
let result =
crate::builtins::global::global_eval("var obj = null; obj?.toString()").unwrap();
assert_eq!(result, JsValue::Undefined);
}
#[test]
fn test_optional_chaining_computed_null() {
let result =
crate::builtins::global::global_eval("var obj = null; var key = 'x'; obj?.[key]")
.unwrap();
assert_eq!(result, JsValue::Undefined);
}
#[test]
fn test_optional_chaining_valid_computed() {
let result =
crate::builtins::global::global_eval("var obj = {x: 77}; var key = 'x'; obj?.[key]")
.unwrap();
assert_eq!(result, JsValue::Smi(77));
}
#[test]
fn e2e_optional_chain_null_prop() {
let r = crate::builtins::global::global_eval("null?.x").unwrap();
assert_eq!(r, JsValue::Undefined);
}
#[test]
fn e2e_optional_chain_undefined_prop() {
let r = crate::builtins::global::global_eval("undefined?.x").unwrap();
assert_eq!(r, JsValue::Undefined);
}
#[test]
fn e2e_optional_chain_value_prop() {
let r = crate::builtins::global::global_eval("({a:1})?.a").unwrap();
assert_eq!(r, JsValue::Smi(1));
}
#[test]
fn e2e_optional_chain_null_computed() {
let r = crate::builtins::global::global_eval("null?.[0]").unwrap();
assert_eq!(r, JsValue::Undefined);
}
#[test]
fn e2e_optional_chain_array_computed() {
let r = crate::builtins::global::global_eval("([10,20])?.[1]").unwrap();
assert_eq!(r, JsValue::Smi(20));
}
#[test]
fn e2e_optional_chain_method_null() {
let r = crate::builtins::global::global_eval("null?.toString()").unwrap();
assert_eq!(r, JsValue::Undefined);
}
#[test]
fn e2e_optional_chain_method_call_value() {
let r =
crate::builtins::global::global_eval("var o = {f: function(){ return 99; }}; o?.f()")
.unwrap();
assert_eq!(r, JsValue::Smi(99));
}
#[test]
fn e2e_optional_call_null() {
let r = crate::builtins::global::global_eval("var f = null; f?.()").unwrap();
assert_eq!(r, JsValue::Undefined);
}
#[test]
fn e2e_optional_call_function() {
let r = crate::builtins::global::global_eval("var f = function(){ return 42; }; f?.()")
.unwrap();
assert_eq!(r, JsValue::Smi(42));
}
#[test]
fn e2e_optional_chain_deep_all_present() {
let r = crate::builtins::global::global_eval("var a = {b:{c:{d:7}}}; a?.b?.c?.d").unwrap();
assert_eq!(r, JsValue::Smi(7));
}
#[test]
fn e2e_optional_chain_deep_null_intermediate() {
let r = crate::builtins::global::global_eval("var a = {b:null}; a?.b?.c?.d").unwrap();
assert_eq!(r, JsValue::Undefined);
}
#[test]
fn e2e_optional_chain_short_circuit_entire_chain() {
let r = crate::builtins::global::global_eval("var a = null; a?.b.c").unwrap();
assert_eq!(r, JsValue::Undefined);
}
#[test]
fn e2e_delete_optional_null() {
let r = crate::builtins::global::global_eval("var obj = null; delete obj?.x").unwrap();
assert_eq!(r, JsValue::Boolean(true));
}
#[test]
fn e2e_delete_optional_real() {
let r = crate::builtins::global::global_eval(
"var obj = {x:1}; delete obj?.x; obj.x === undefined",
)
.unwrap();
assert_eq!(r, JsValue::Boolean(true));
}
#[test]
fn e2e_nullish_coalesce_null() {
let r = crate::builtins::global::global_eval("null ?? 'default'").unwrap();
assert_eq!(r, JsValue::String("default".into()));
}
#[test]
fn e2e_nullish_coalesce_undefined() {
let r = crate::builtins::global::global_eval("undefined ?? 'default'").unwrap();
assert_eq!(r, JsValue::String("default".into()));
}
#[test]
fn e2e_nullish_coalesce_zero() {
let r = crate::builtins::global::global_eval("0 ?? 'default'").unwrap();
assert_eq!(r, JsValue::Smi(0));
}
#[test]
fn e2e_nullish_coalesce_empty_string() {
let r = crate::builtins::global::global_eval("'' ?? 'default'").unwrap();
assert_eq!(r, JsValue::String("".into()));
}
#[test]
fn e2e_nullish_coalesce_false() {
let r = crate::builtins::global::global_eval("false ?? 'default'").unwrap();
assert_eq!(r, JsValue::Boolean(false));
}
#[test]
fn e2e_nullish_assign_null() {
let r = crate::builtins::global::global_eval("var x = null; x ??= 42; x").unwrap();
assert_eq!(r, JsValue::Smi(42));
}
#[test]
fn e2e_nullish_assign_zero_keeps() {
let r = crate::builtins::global::global_eval("var x = 0; x ??= 42; x").unwrap();
assert_eq!(r, JsValue::Smi(0));
}
#[test]
fn e2e_nullish_assign_empty_string_keeps() {
let r = crate::builtins::global::global_eval("var x = ''; x ??= 'hi'; x").unwrap();
assert_eq!(r, JsValue::String("".into()));
}
#[test]
fn e2e_logical_and_assign_truthy() {
let r = crate::builtins::global::global_eval("var x = 1; x &&= 5; x").unwrap();
assert_eq!(r, JsValue::Smi(5));
}
#[test]
fn e2e_logical_and_assign_falsy() {
let r = crate::builtins::global::global_eval("var x = 0; x &&= 5; x").unwrap();
assert_eq!(r, JsValue::Smi(0));
}
#[test]
fn e2e_logical_or_assign_falsy() {
let r = crate::builtins::global::global_eval("var x = 0; x ||= 5; x").unwrap();
assert_eq!(r, JsValue::Smi(5));
}
#[test]
fn e2e_logical_or_assign_truthy() {
let r = crate::builtins::global::global_eval("var x = 1; x ||= 5; x").unwrap();
assert_eq!(r, JsValue::Smi(1));
}
#[test]
fn e2e_optional_chain_then_nullish() {
let r =
crate::builtins::global::global_eval("var obj = null; obj?.x ?? 'default'").unwrap();
assert_eq!(r, JsValue::String("default".into()));
}
#[test]
fn e2e_nullish_then_optional_chain() {
let r = crate::builtins::global::global_eval("var a = null; var b = {x: 10}; (a ?? b)?.x")
.unwrap();
assert_eq!(r, JsValue::Smi(10));
}
#[test]
fn e2e_nullish_mixed_and_error() {
let r = crate::builtins::global::global_eval("1 ?? 2 && 3");
assert!(r.is_err());
}
#[test]
fn e2e_nullish_mixed_or_error() {
let r = crate::builtins::global::global_eval("1 ?? 2 || 3");
assert!(r.is_err());
}
#[test]
fn e2e_or_then_nullish_error() {
let r = crate::builtins::global::global_eval("1 || 2 ?? 3");
assert!(r.is_err());
}
#[test]
fn e2e_paren_or_then_nullish_ok() {
let r = crate::builtins::global::global_eval("(null || undefined) ?? 42").unwrap();
assert_eq!(r, JsValue::Smi(42));
}
#[test]
fn e2e_nan_less_than_or_equal() {
let result = crate::builtins::global::global_eval("NaN <= 1").unwrap();
assert_eq!(result, JsValue::Boolean(false));
}
#[test]
fn e2e_one_lte_nan() {
let result = crate::builtins::global::global_eval("1 <= NaN").unwrap();
assert_eq!(result, JsValue::Boolean(false));
}
#[test]
fn e2e_nan_greater_than_or_equal() {
let result = crate::builtins::global::global_eval("NaN >= 1").unwrap();
assert_eq!(result, JsValue::Boolean(false));
}
#[test]
fn e2e_one_gte_nan() {
let result = crate::builtins::global::global_eval("1 >= NaN").unwrap();
assert_eq!(result, JsValue::Boolean(false));
}
#[test]
fn e2e_nan_greater_than() {
let result = crate::builtins::global::global_eval("NaN > 1").unwrap();
assert_eq!(result, JsValue::Boolean(false));
}
#[test]
fn e2e_nan_less_than() {
let result = crate::builtins::global::global_eval("NaN < 1").unwrap();
assert_eq!(result, JsValue::Boolean(false));
}
#[test]
fn e2e_nan_loose_eq_nan() {
let result = crate::builtins::global::global_eval("NaN == NaN").unwrap();
assert_eq!(result, JsValue::Boolean(false));
}
#[test]
fn e2e_nan_strict_eq_nan() {
let result = crate::builtins::global::global_eval("NaN === NaN").unwrap();
assert_eq!(result, JsValue::Boolean(false));
}
#[test]
fn e2e_null_loose_eq_undefined() {
let result = crate::builtins::global::global_eval("null == undefined").unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn e2e_null_not_eq_zero() {
let result = crate::builtins::global::global_eval("null == 0").unwrap();
assert_eq!(result, JsValue::Boolean(false));
}
#[test]
fn e2e_null_not_eq_false() {
let result = crate::builtins::global::global_eval("null == false").unwrap();
assert_eq!(result, JsValue::Boolean(false));
}
#[test]
fn e2e_empty_string_eq_zero() {
let result = crate::builtins::global::global_eval("'' == 0").unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn e2e_true_eq_one() {
let result = crate::builtins::global::global_eval("true == 1").unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn e2e_false_eq_zero() {
let result = crate::builtins::global::global_eval("false == 0").unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn e2e_pos_zero_strict_eq_neg_zero() {
let result = crate::builtins::global::global_eval("var x = -0; x === 0").unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn e2e_typeof_null_is_object_coerce() {
let result = crate::builtins::global::global_eval("typeof null === 'object'").unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn e2e_neg_zero_to_string() {
let result = crate::builtins::global::global_eval("String(-0)").unwrap();
assert_eq!(result, JsValue::String("0".into()));
}
#[test]
fn e2e_number_empty_string() {
let result = crate::builtins::global::global_eval("Number('')").unwrap();
assert_eq!(result, JsValue::Smi(0));
}
#[test]
fn e2e_number_null() {
let result = crate::builtins::global::global_eval("Number(null)").unwrap();
assert_eq!(result, JsValue::Smi(0));
}
#[test]
fn e2e_number_undefined() {
let result = crate::builtins::global::global_eval("Number(undefined)").unwrap();
assert!(matches!(result, JsValue::HeapNumber(n) if n.is_nan()));
}
#[test]
fn e2e_number_boolean() {
let r1 = crate::builtins::global::global_eval("Number(true)").unwrap();
assert_eq!(r1, JsValue::Smi(1));
let r2 = crate::builtins::global::global_eval("Number(false)").unwrap();
assert_eq!(r2, JsValue::Smi(0));
}
#[test]
fn e2e_string_of_symbol() {
let result = crate::builtins::global::global_eval("typeof String(Symbol())").unwrap();
assert_eq!(result, JsValue::String("string".into()));
}
#[test]
fn e2e_for_of_array_collect() {
let result = crate::builtins::global::global_eval(
"var r = 0; for (var x of [10, 20, 30]) r += x; r",
)
.unwrap();
assert_eq!(result, JsValue::Smi(60));
}
#[test]
fn e2e_for_of_string_unicode() {
let result = crate::builtins::global::global_eval(
"var r = []; for (var c of 'a\\u{1F600}b') r.push(c); r.length",
)
.unwrap();
assert_eq!(result, JsValue::Smi(3));
}
#[test]
fn e2e_array_keys_iterator() {
let result = crate::builtins::global::global_eval(
"var r = []; for (var k of ['a','b','c'].keys()) r.push(k); r.join(',')",
)
.unwrap();
assert_eq!(result, JsValue::String("0,1,2".into()));
}
#[test]
fn e2e_array_values_iterator() {
let result = crate::builtins::global::global_eval(
"var r = []; for (var v of ['x','y'].values()) r.push(v); r.join(',')",
)
.unwrap();
assert_eq!(result, JsValue::String("x,y".into()));
}
#[test]
fn e2e_array_entries_iterator() {
let result = crate::builtins::global::global_eval(
"var r = []; for (var e of ['a','b'].entries()) r.push(e[0] + ':' + e[1]); r.join(',')",
)
.unwrap();
assert_eq!(result, JsValue::String("0:a,1:b".into()));
}
#[test]
fn e2e_spread_array_in_call() {
let result = crate::builtins::global::global_eval(
"function f(a,b,c) { return a * 100 + b * 10 + c; } f(...[1,2,3])",
)
.unwrap();
assert_eq!(result, JsValue::Smi(123));
}
#[test]
#[ignore] fn e2e_spread_string_into_array() {
let result =
crate::builtins::global::global_eval("var a = [...'abc']; a.join('-')").unwrap();
assert_eq!(result, JsValue::String("a-b-c".into()));
}
#[test]
fn e2e_for_of_break_early_exit() {
let result = crate::builtins::global::global_eval(
"var r = []; for (var x of [1,2,3,4]) { r.push(x); if (x === 2) break; } r.join(',')",
)
.unwrap();
assert_eq!(result, JsValue::String("1,2".into()));
}
#[test]
fn e2e_for_of_return_exits_function() {
let result = crate::builtins::global::global_eval(
"function f() { for (var x of [1,2,3]) { if (x === 2) return x; } return 0; } f()",
)
.unwrap();
assert_eq!(result, JsValue::Smi(2));
}
#[test]
fn e2e_for_of_generator() {
let result = crate::builtins::global::global_eval(
"function* gen() { yield 10; yield 20; yield 30; }\
var r = 0; for (var x of gen()) r += x; r",
)
.unwrap();
assert_eq!(result, JsValue::Smi(60));
}
#[test]
#[ignore] fn e2e_spread_generator_into_array() {
let result = crate::builtins::global::global_eval(
"function* gen() { yield 1; yield 2; yield 3; } var a = [...gen()]; a.join(',')",
)
.unwrap();
assert_eq!(result, JsValue::String("1,2,3".into()));
}
#[test]
fn e2e_iterator_next_protocol() {
let result = crate::builtins::global::global_eval(
"var arr = [42]; var iter = arr.values(); var r = iter.next(); r.value",
)
.unwrap();
assert_eq!(result, JsValue::Smi(42));
}
#[test]
fn e2e_iterator_next_done() {
let result = crate::builtins::global::global_eval(
"var iter = [1].values(); iter.next(); var r = iter.next(); r.done",
)
.unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn e2e_for_of_const_binding() {
let result =
crate::builtins::global::global_eval("var r = ''; for (const ch of 'hi') r += ch; r")
.unwrap();
assert_eq!(result, JsValue::String("hi".into()));
}
#[test]
fn e2e_for_of_let_binding() {
let result =
crate::builtins::global::global_eval("var r = 0; for (let n of [1,2,3]) r += n; r")
.unwrap();
assert_eq!(result, JsValue::Smi(6));
}
#[test]
fn e2e_for_of_destructuring() {
let result = crate::builtins::global::global_eval(
"var r = 0; var pairs = [[1,2],[3,4]]; for (var [a, b] of pairs) r += a + b; r",
)
.unwrap();
assert_eq!(result, JsValue::Smi(10));
}
#[test]
fn e2e_for_of_nested() {
let result = crate::builtins::global::global_eval(
"var r = ''; for (var a of [1,2]) for (var b of ['x','y']) r += a + b + ','; r",
)
.unwrap();
assert_eq!(result, JsValue::String("1x,1y,2x,2y,".into()));
}
#[test]
#[ignore] fn e2e_for_of_arguments() {
let result = crate::builtins::global::global_eval(
"function f() { var r = 0; for (var x of arguments) r += x; return r; } f(1,2,3)",
)
.unwrap();
assert_eq!(result, JsValue::Smi(6));
}
#[test]
#[ignore] fn e2e_spread_arguments() {
let result = crate::builtins::global::global_eval(
"function f() { return [...arguments].join('-'); } f('a','b','c')",
)
.unwrap();
assert_eq!(result, JsValue::String("a-b-c".into()));
}
#[test]
fn e2e_for_in_prototype_chain() {
let result = crate::builtins::global::global_eval(
"var gp = { z: 3 }; \
var p = Object.create(gp); p.y = 2; \
var o = Object.create(p); o.x = 1; \
var keys = []; for (var k in o) { keys.push(k); } \
keys.join(',')",
)
.unwrap();
assert_eq!(result, JsValue::String("x,y,z".into()));
}
#[test]
fn e2e_for_in_non_enum_shadows_inherited() {
let result = crate::builtins::global::global_eval(
"var p = { x: 1, y: 2 }; \
var o = Object.create(p); \
Object.defineProperty(o, 'x', { value: 10, enumerable: false }); \
var keys = []; for (var k in o) { keys.push(k); } \
keys.join(',')",
)
.unwrap();
assert_eq!(result, JsValue::String("y".into()));
}
#[test]
#[ignore] fn e2e_for_in_integer_order() {
let result = crate::builtins::global::global_eval(
"var o = {}; o.z = 1; o['10'] = 2; o['2'] = 3; o.a = 4; \
var keys = []; for (var k in o) { keys.push(k); } \
keys.join(',')",
)
.unwrap();
assert_eq!(result, JsValue::String("2,10,z,a".into()));
}
#[test]
fn e2e_for_in_null() {
let result =
crate::builtins::global::global_eval("var n = 0; for (var k in null) { n++; } n")
.unwrap();
assert_eq!(result, JsValue::Smi(0));
}
#[test]
fn e2e_for_in_undefined() {
let result =
crate::builtins::global::global_eval("var n = 0; for (var k in undefined) { n++; } n")
.unwrap();
assert_eq!(result, JsValue::Smi(0));
}
#[test]
fn e2e_for_in_sum_values() {
let result = crate::builtins::global::global_eval(
"var o = { a: 10, b: 20, c: 30 }; \
var sum = 0; for (var k in o) { sum = sum + o[k]; } sum",
)
.unwrap();
assert_eq!(result, JsValue::Smi(60));
}
#[test]
fn e2e_for_in_break() {
let result = crate::builtins::global::global_eval(
"var o = { a: 1, b: 2, c: 3 }; \
var n = 0; for (var k in o) { n++; if (n === 2) break; } n",
)
.unwrap();
assert_eq!(result, JsValue::Smi(2));
}
#[test]
fn e2e_typeof_null_is_object_forin() {
let result = crate::builtins::global::global_eval("typeof null").unwrap();
assert_eq!(result, JsValue::String("object".into()));
}
#[test]
fn e2e_typeof_undefined_forin() {
let result = crate::builtins::global::global_eval("typeof undefined").unwrap();
assert_eq!(result, JsValue::String("undefined".into()));
}
#[test]
fn e2e_typeof_boolean_forin() {
let result = crate::builtins::global::global_eval("typeof true").unwrap();
assert_eq!(result, JsValue::String("boolean".into()));
}
#[test]
fn e2e_typeof_number_forin() {
let result = crate::builtins::global::global_eval("typeof 42").unwrap();
assert_eq!(result, JsValue::String("number".into()));
}
#[test]
fn e2e_typeof_string_forin() {
let result = crate::builtins::global::global_eval("typeof 'hello'").unwrap();
assert_eq!(result, JsValue::String("string".into()));
}
#[test]
fn e2e_typeof_symbol_forin() {
let result = crate::builtins::global::global_eval("typeof Symbol()").unwrap();
assert_eq!(result, JsValue::String("symbol".into()));
}
#[test]
fn e2e_typeof_bigint() {
let result = crate::builtins::global::global_eval("typeof BigInt(1)").unwrap();
assert_eq!(result, JsValue::String("bigint".into()));
}
#[test]
fn e2e_typeof_function() {
let result = crate::builtins::global::global_eval("typeof function() {}").unwrap();
assert_eq!(result, JsValue::String("function".into()));
}
#[test]
fn e2e_typeof_arrow_function() {
let result = crate::builtins::global::global_eval("typeof (() => {})").unwrap();
assert_eq!(result, JsValue::String("function".into()));
}
#[test]
fn e2e_typeof_object() {
let result = crate::builtins::global::global_eval("typeof {}").unwrap();
assert_eq!(result, JsValue::String("object".into()));
}
#[test]
fn e2e_typeof_array() {
let result = crate::builtins::global::global_eval("typeof []").unwrap();
assert_eq!(result, JsValue::String("object".into()));
}
#[test]
fn e2e_typeof_undeclared_var() {
let result = crate::builtins::global::global_eval("typeof someUndeclaredVariable").unwrap();
assert_eq!(result, JsValue::String("undefined".into()));
}
#[test]
fn e2e_typeof_bound_function() {
let result =
crate::builtins::global::global_eval("function f() {} typeof f.bind(null)").unwrap();
assert_eq!(result, JsValue::String("function".into()));
}
#[test]
fn e2e_instanceof_error() {
let result =
crate::builtins::global::global_eval("new Error('x') instanceof Error").unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn e2e_instanceof_typeerror_is_error() {
let result =
crate::builtins::global::global_eval("new TypeError('x') instanceof Error").unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn e2e_instanceof_typeerror() {
let result =
crate::builtins::global::global_eval("new TypeError('x') instanceof TypeError")
.unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn e2e_instanceof_rangeerror() {
let result =
crate::builtins::global::global_eval("new RangeError('x') instanceof RangeError")
.unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn e2e_instanceof_rangeerror_is_error() {
let result =
crate::builtins::global::global_eval("new RangeError('x') instanceof Error").unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn e2e_instanceof_not_callable_throws() {
let result = crate::builtins::global::global_eval("1 instanceof 2");
assert!(result.is_err());
}
#[test]
#[ignore] fn e2e_instanceof_custom_has_instance() {
let result = crate::builtins::global::global_eval(
"var obj = { [Symbol.hasInstance](v) { return v === 42; } }; 42 instanceof obj",
)
.unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
#[ignore] fn e2e_instanceof_class_symbol_has_instance_true() {
let result = crate::builtins::global::global_eval(
"class Foo { static [Symbol.hasInstance](x) { return x === 1; } } 1 instanceof Foo",
)
.unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn e2e_instanceof_class_symbol_has_instance_false() {
let result = crate::builtins::global::global_eval(
"class Foo { static [Symbol.hasInstance](x) { return x === 1; } } 2 instanceof Foo",
)
.unwrap();
assert_eq!(result, JsValue::Boolean(false));
}
#[test]
fn e2e_instanceof_function_symbol_has_instance_true() {
let result = crate::builtins::global::global_eval(
"function Foo() {} Foo[Symbol.hasInstance] = function(x) { return x && x.tag === 1; }; ({ tag: 1 }) instanceof Foo",
)
.unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn e2e_new_error_message_empty() {
let result = crate::builtins::global::global_eval("new Error().message").unwrap();
assert_eq!(result, JsValue::String("".into()));
}
#[test]
fn e2e_new_typeerror_message_empty() {
let result = crate::builtins::global::global_eval("new TypeError().message").unwrap();
assert_eq!(result, JsValue::String("".into()));
}
#[test]
fn e2e_new_error_message_string() {
let result = crate::builtins::global::global_eval("new Error('hello').message").unwrap();
assert_eq!(result, JsValue::String("hello".into()));
}
#[test]
fn e2e_error_name() {
let result = crate::builtins::global::global_eval("new Error().name").unwrap();
assert_eq!(result, JsValue::String("Error".into()));
}
#[test]
fn e2e_typeerror_name() {
let result = crate::builtins::global::global_eval("new TypeError().name").unwrap();
assert_eq!(result, JsValue::String("TypeError".into()));
}
#[test]
fn e2e_error_stack_is_string() {
let result = crate::builtins::global::global_eval("typeof new Error().stack").unwrap();
assert_eq!(result, JsValue::String("string".into()));
}
#[test]
fn e2e_error_tostring_with_message() {
let result = crate::builtins::global::global_eval("new Error('msg').toString()").unwrap();
assert_eq!(result, JsValue::String("Error: msg".into()));
}
#[test]
fn e2e_error_tostring_without_message() {
let result = crate::builtins::global::global_eval("new Error().toString()").unwrap();
assert_eq!(result, JsValue::String("Error".into()));
}
#[test]
fn e2e_typeerror_tostring() {
let result =
crate::builtins::global::global_eval("new TypeError('bad').toString()").unwrap();
assert_eq!(result, JsValue::String("TypeError: bad".into()));
}
#[test]
fn e2e_error_constructor_identity() {
let result =
crate::builtins::global::global_eval("new Error('msg').constructor === Error").unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn e2e_typeerror_constructor_identity() {
let result =
crate::builtins::global::global_eval("new TypeError('msg').constructor === TypeError")
.unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
#[ignore] fn e2e_error_subclass_prototype_chain() {
let result = crate::builtins::global::global_eval(
"TypeError.prototype instanceof Error \
|| Object.getPrototypeOf(TypeError.prototype) === Error.prototype",
)
.unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn e2e_throw_number() {
let result =
crate::builtins::global::global_eval("var r; try { throw 42; } catch(e) { r = e; } r")
.unwrap();
assert_eq!(result, JsValue::Smi(42));
}
#[test]
fn e2e_throw_string() {
let result = crate::builtins::global::global_eval(
"var r; try { throw 'oops'; } catch(e) { r = e; } r",
)
.unwrap();
assert_eq!(result, JsValue::String("oops".into()));
}
#[test]
fn e2e_throw_boolean() {
let result = crate::builtins::global::global_eval(
"var r; try { throw false; } catch(e) { r = e; } r",
)
.unwrap();
assert_eq!(result, JsValue::Boolean(false));
}
#[test]
fn e2e_throw_null() {
let result = crate::builtins::global::global_eval(
"var r; try { throw null; } catch(e) { r = e; } r",
)
.unwrap();
assert_eq!(result, JsValue::Null);
}
#[test]
fn e2e_throw_undefined() {
let result = crate::builtins::global::global_eval(
"var r; try { throw undefined; } catch(e) { r = e; } r",
)
.unwrap();
assert_eq!(result, JsValue::Undefined);
}
#[test]
fn e2e_throw_error_object() {
let result = crate::builtins::global::global_eval(
"var r; try { throw new TypeError('msg'); } catch(e) { r = e.message; } r",
)
.unwrap();
assert_eq!(result, JsValue::String("msg".into()));
}
#[test]
fn e2e_engine_error_caught_as_error_object() {
let result = crate::builtins::global::global_eval(
"var r; try { null(); } catch(e) { r = e instanceof TypeError; } r",
)
.unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn e2e_throw_object_literal() {
let result = crate::builtins::global::global_eval(
"var r; try { throw { code: 42 }; } catch(e) { r = e.code; } r",
)
.unwrap();
assert_eq!(result, JsValue::Smi(42));
}
#[test]
fn e2e_arrow_concise_body() {
let result = crate::builtins::global::global_eval("var f = x => x * 2; f(5)").unwrap();
assert_eq!(result, JsValue::Smi(10));
}
#[test]
fn e2e_arrow_block_body() {
let result =
crate::builtins::global::global_eval("var f = x => { return x * 3; }; f(4)").unwrap();
assert_eq!(result, JsValue::Smi(12));
}
#[test]
#[ignore] fn e2e_arrow_no_own_arguments() {
let result = crate::builtins::global::global_eval(
"function outer() { var f = () => typeof arguments; return f(); } outer(1,2,3)",
)
.unwrap();
assert_eq!(result, JsValue::String("object".into()));
}
#[test]
#[ignore] fn e2e_arrow_inherits_outer_arguments() {
let result = crate::builtins::global::global_eval(
"function outer() { var f = () => arguments.length; return f(); } outer(10,20,30)",
)
.unwrap();
assert_eq!(result, JsValue::Smi(3));
}
#[test]
fn e2e_arrow_lexical_this_in_method() {
let result = crate::builtins::global::global_eval(
"var obj = { x: 42, getX: function() { var f = () => this.x; return f(); } }; \
obj.getX()",
)
.unwrap();
assert_eq!(result, JsValue::Smi(42));
}
#[test]
fn e2e_arrow_lexical_this_not_overridden_by_call() {
let result = crate::builtins::global::global_eval(
"var obj = { x: 99, getX: function() { var f = () => this.x; \
return f.call({x: 1}); } }; obj.getX()",
)
.unwrap();
assert_eq!(result, JsValue::Smi(99));
}
#[test]
fn e2e_arrow_not_constructable_detailed() {
let result = crate::builtins::global::global_eval(
"var f = () => {}; var ok = false; \
try { new f(); } catch(e) { ok = e instanceof TypeError; } ok",
)
.unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn e2e_this_implicit_binding() {
let result = crate::builtins::global::global_eval(
"var obj = { val: 7, get: function() { return this.val; } }; obj.get()",
)
.unwrap();
assert_eq!(result, JsValue::Smi(7));
}
#[test]
fn e2e_this_explicit_call() {
let result =
crate::builtins::global::global_eval("function f() { return this.x; } f.call({x: 55})")
.unwrap();
assert_eq!(result, JsValue::Smi(55));
}
#[test]
fn e2e_this_explicit_apply() {
let result = crate::builtins::global::global_eval(
"function f(a) { return this.x + a; } f.apply({x: 10}, [5])",
)
.unwrap();
assert_eq!(result, JsValue::Smi(15));
}
#[test]
fn e2e_this_new_binding() {
let result = crate::builtins::global::global_eval(
"function Foo(v) { this.val = v; } var f = new Foo(33); f.val",
)
.unwrap();
assert_eq!(result, JsValue::Smi(33));
}
#[test]
fn e2e_bind_this() {
let result = crate::builtins::global::global_eval(
"function f() { return this.x; } var g = f.bind({x: 77}); g()",
)
.unwrap();
assert_eq!(result, JsValue::Smi(77));
}
#[test]
fn e2e_bind_partial_application() {
let result = crate::builtins::global::global_eval(
"function add(a, b) { return a + b; } \
var add5 = add.bind(null, 5); add5(3)",
)
.unwrap();
assert_eq!(result, JsValue::Smi(8));
}
#[test]
fn e2e_bind_length() {
let result = crate::builtins::global::global_eval(
"function f(a, b, c) {} var g = f.bind(null, 1); g.length",
)
.unwrap();
assert_eq!(result, JsValue::Smi(2));
}
#[test]
fn e2e_bind_name() {
let result = crate::builtins::global::global_eval(
"function f(a, b, c) {} var g = f.bind(null); typeof g.name === 'string'",
)
.unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
#[ignore] fn e2e_bind_subsequent_call_doesnt_change_this() {
let result = crate::builtins::global::global_eval(
"function f() { return this.x; } \
var g = f.bind({x: 100}); g.call({x: 999})",
)
.unwrap();
assert_eq!(result, JsValue::Smi(100));
}
#[test]
fn e2e_call_with_args() {
let result = crate::builtins::global::global_eval(
"function f(a, b) { return this.x + a + b; } f.call({x: 1}, 2, 3)",
)
.unwrap();
assert_eq!(result, JsValue::Smi(6));
}
#[test]
fn e2e_apply_with_array() {
let result = crate::builtins::global::global_eval(
"function f(a, b) { return a + b; } f.apply(null, [10, 20])",
)
.unwrap();
assert_eq!(result, JsValue::Smi(30));
}
#[test]
fn e2e_closure_captures_by_reference() {
let result = crate::builtins::global::global_eval(
"function make() { var x = 1; return { get: function() { return x; }, \
set: function(v) { x = v; } }; } \
var o = make(); o.set(42); o.get()",
)
.unwrap();
assert_eq!(result, JsValue::Smi(42));
}
#[test]
#[ignore] fn e2e_closure_survives_after_return() {
let result = crate::builtins::global::global_eval(
"function counter() { var n = 0; return function() { n = n + 1; return n; }; } \
var c = counter(); c(); c(); c()",
)
.unwrap();
assert_eq!(result, JsValue::Smi(3));
}
#[test]
fn e2e_closure_var_shared_in_loop() {
let result = crate::builtins::global::global_eval(
"var fns = []; \
for (var i = 0; i < 3; i++) { fns[i] = function() { return i; }; } \
fns[0]() + ',' + fns[1]() + ',' + fns[2]()",
)
.unwrap();
assert_eq!(result, JsValue::String("3,3,3".into()));
}
#[test]
#[ignore] fn e2e_arguments_length() {
let result = crate::builtins::global::global_eval(
"function f() { return arguments.length; } f(1,2,3)",
)
.unwrap();
assert_eq!(result, JsValue::Smi(3));
}
#[test]
#[ignore] fn e2e_arguments_indexed_access() {
let result = crate::builtins::global::global_eval(
"function f() { return arguments[0] + arguments[1]; } f(10, 20)",
)
.unwrap();
assert_eq!(result, JsValue::Smi(30));
}
#[test]
fn e2e_arguments_not_array() {
let result = crate::builtins::global::global_eval(
"function f() { return Array.isArray(arguments); } f(1,2)",
)
.unwrap();
assert_eq!(result, JsValue::Boolean(false));
}
#[test]
#[ignore] fn e2e_arguments_iterable() {
let result = crate::builtins::global::global_eval(
"function f() { var r = ''; for (var x of arguments) r += x + ','; return r; } \
f('a','b','c')",
)
.unwrap();
assert_eq!(result, JsValue::String("a,b,c,".into()));
}
#[test]
fn e2e_default_param_basic() {
let result =
crate::builtins::global::global_eval("function f(a = 10) { return a; } f()").unwrap();
assert_eq!(result, JsValue::Smi(10));
}
#[test]
fn e2e_default_param_overridden() {
let result =
crate::builtins::global::global_eval("function f(a = 10) { return a; } f(5)").unwrap();
assert_eq!(result, JsValue::Smi(5));
}
#[test]
fn e2e_default_param_references_earlier() {
let result =
crate::builtins::global::global_eval("function f(a, b = a * 2) { return b; } f(3)")
.unwrap();
assert_eq!(result, JsValue::Smi(6));
}
#[test]
fn e2e_default_param_left_to_right() {
let result =
crate::builtins::global::global_eval("function f(a = 1, b = 2) { return a + b; } f()")
.unwrap();
assert_eq!(result, JsValue::Smi(3));
}
#[test]
fn e2e_default_param_with_arrow() {
let result = crate::builtins::global::global_eval("var f = (a = 7) => a + 1; f()").unwrap();
assert_eq!(result, JsValue::Smi(8));
}
#[test]
fn e2e_computed_prop_string_concat() {
let result =
crate::builtins::global::global_eval("var o = {['a' + 'b']: 1}; o.ab").unwrap();
assert_eq!(result, JsValue::Smi(1));
}
#[test]
fn e2e_computed_prop_variable_key() {
let result =
crate::builtins::global::global_eval("var k = 'hello'; var o = {[k]: 42}; o.hello")
.unwrap();
assert_eq!(result, JsValue::Smi(42));
}
#[test]
fn e2e_computed_prop_numeric_expr() {
let result =
crate::builtins::global::global_eval("var o = {[1 + 2]: 'three'}; o[3]").unwrap();
assert_eq!(result, JsValue::String("three".into()));
}
#[test]
fn e2e_computed_method() {
let result = crate::builtins::global::global_eval(
"var k = 'greet'; var o = {[k]() { return 'hi'; }}; o.greet()",
)
.unwrap();
assert_eq!(result, JsValue::String("hi".into()));
}
#[test]
fn e2e_shorthand_property() {
let result = crate::builtins::global::global_eval(
"var x = 10; var y = 20; var o = {x, y}; o.x + o.y",
)
.unwrap();
assert_eq!(result, JsValue::Smi(30));
}
#[test]
fn e2e_shorthand_mixed_with_regular() {
let result =
crate::builtins::global::global_eval("var a = 1; var o = {a, b: 2}; o.a + o.b")
.unwrap();
assert_eq!(result, JsValue::Smi(3));
}
#[test]
fn e2e_shorthand_method() {
let result = crate::builtins::global::global_eval(
"var o = {add(a, b) { return a + b; }}; o.add(3, 4)",
)
.unwrap();
assert_eq!(result, JsValue::Smi(7));
}
#[test]
fn e2e_shorthand_method_this() {
let result = crate::builtins::global::global_eval(
"var o = {v: 5, get_v() { return this.v; }}; o.get_v()",
)
.unwrap();
assert_eq!(result, JsValue::Smi(5));
}
#[test]
fn e2e_getter_obj_literal() {
let result =
crate::builtins::global::global_eval("var o = {get x() { return 42; }}; o.x").unwrap();
assert_eq!(result, JsValue::Smi(42));
}
#[test]
fn e2e_setter_obj_literal() {
let result = crate::builtins::global::global_eval(
"var o = {_v: 0, set v(x) { this._v = x; }}; o.v = 7; o._v",
)
.unwrap();
assert_eq!(result, JsValue::Smi(7));
}
#[test]
fn e2e_getter_setter_pair_literal() {
let result = crate::builtins::global::global_eval(
"var o = {\
_val: 0,\
get val() { return this._val; },\
set val(v) { this._val = v * 2; }\
};\
o.val = 5;\
o.val",
)
.unwrap();
assert_eq!(result, JsValue::Smi(10));
}
#[test]
fn e2e_getter_computed_key() {
let result = crate::builtins::global::global_eval(
"var k = 'x'; var o = {get [k]() { return 99; }}; o.x",
)
.unwrap();
assert_eq!(result, JsValue::Smi(99));
}
#[test]
fn e2e_setter_computed_key() {
let result = crate::builtins::global::global_eval(
"var k = 'v'; var o = {_r: 0, set [k](x) { this._r = x; }}; o.v = 3; o._r",
)
.unwrap();
assert_eq!(result, JsValue::Smi(3));
}
#[test]
#[ignore] fn e2e_proto_in_literal() {
let result = crate::builtins::global::global_eval(
"var base = {greet() { return 'hello'; }};\
var child = {__proto__: base};\
child.greet()",
)
.unwrap();
assert_eq!(result, JsValue::String("hello".into()));
}
#[test]
#[ignore] fn e2e_proto_inherited_property() {
let result = crate::builtins::global::global_eval(
"var parent = {x: 100};\
var child = {__proto__: parent, y: 200};\
child.x + child.y",
)
.unwrap();
assert_eq!(result, JsValue::Smi(300));
}
#[test]
fn e2e_define_property_getter_accessible() {
let result = crate::builtins::global::global_eval(
"var o = {};\
Object.defineProperty(o, 'x', {\
get: function() { return 55; },\
configurable: true\
});\
o.x",
)
.unwrap();
assert_eq!(result, JsValue::Smi(55));
}
#[test]
fn e2e_define_property_setter_accessible() {
let result = crate::builtins::global::global_eval(
"var o = {_v: 0};\
Object.defineProperty(o, 'x', {\
set: function(v) { this._v = v; },\
configurable: true\
});\
o.x = 11;\
o._v",
)
.unwrap();
assert_eq!(result, JsValue::Smi(11));
}
#[test]
fn e2e_define_property_accessor_pair() {
let result = crate::builtins::global::global_eval(
"var o = {_v: 0};\
Object.defineProperty(o, 'val', {\
get: function() { return this._v; },\
set: function(v) { this._v = v + 1; },\
configurable: true\
});\
o.val = 10;\
o.val",
)
.unwrap();
assert_eq!(result, JsValue::Smi(11));
}
#[test]
fn e2e_getter_name_property() {
let result = crate::builtins::global::global_eval(
"var o = {get x() { return 1; }};\
var desc = Object.getOwnPropertyDescriptor(o, 'x');\
desc.get.name",
)
.unwrap();
assert_eq!(result, JsValue::String("get x".into()));
}
#[test]
fn e2e_setter_name_property() {
let result = crate::builtins::global::global_eval(
"var o = {set x(v) {}};\
var desc = Object.getOwnPropertyDescriptor(o, 'x');\
desc.set.name",
)
.unwrap();
assert_eq!(result, JsValue::String("set x".into()));
}
#[test]
fn e2e_method_name_property() {
let result = crate::builtins::global::global_eval(
"var o = {myMethod() { return 1; }}; o.myMethod.name",
)
.unwrap();
assert_eq!(result, JsValue::String("myMethod".into()));
}
#[test]
fn e2e_mixed_shorthand_computed_method() {
let result = crate::builtins::global::global_eval(
"var a = 1;\
var k = 'b';\
var o = {a, [k]: 2, c() { return 3; }};\
o.a + o.b + o.c()",
)
.unwrap();
assert_eq!(result, JsValue::Smi(6));
}
#[test]
fn e2e_computed_getter_setter_pair() {
let result = crate::builtins::global::global_eval(
"var k = 'prop';\
var o = {\
_s: 0,\
get [k]() { return this._s; },\
set [k](v) { this._s = v * 3; }\
};\
o.prop = 4;\
o.prop",
)
.unwrap();
assert_eq!(result, JsValue::Smi(12));
}
#[test]
fn e2e_labeled_block_break() {
let result = crate::builtins::global::global_eval(
"var r = 0; outer: { r = 1; break outer; r = 2; } r",
)
.unwrap();
assert_eq!(result, JsValue::Smi(1));
}
#[test]
fn e2e_labeled_block_completion_value() {
let result = crate::builtins::global::global_eval(
"var x; outer: { x = 10; break outer; x = 20; } x",
)
.unwrap();
assert_eq!(result, JsValue::Smi(10));
}
#[test]
fn e2e_labeled_while_break() {
let result = crate::builtins::global::global_eval(
"var i = 0; outer: while (true) { i = 42; break outer; } i",
)
.unwrap();
assert_eq!(result, JsValue::Smi(42));
}
#[test]
fn e2e_labeled_for_break() {
let result = crate::builtins::global::global_eval(
"var r = 0; outer: for (var i = 0; i < 10; i++) { r = i; if (i === 3) break outer; } r",
)
.unwrap();
assert_eq!(result, JsValue::Smi(3));
}
#[test]
fn e2e_nested_labeled_loops_break_outer() {
let result = crate::builtins::global::global_eval(
"var r = 0; outer: for (var i = 0; i < 5; i++) { inner: for (var j = 0; j < 5; j++) { r++; if (j === 1) break outer; } } r",
)
.unwrap();
assert_eq!(result, JsValue::Smi(2));
}
#[test]
fn e2e_nested_labeled_loops_break_inner() {
let result = crate::builtins::global::global_eval(
"var r = 0; outer: for (var i = 0; i < 3; i++) { inner: for (var j = 0; j < 10; j++) { if (j === 2) break inner; r++; } } r",
)
.unwrap();
assert_eq!(result, JsValue::Smi(6)); }
#[test]
fn e2e_continue_with_label() {
let result = crate::builtins::global::global_eval(
"var r = 0; outer: for (var i = 0; i < 5; i++) { if (i === 3) continue outer; r++; } r",
)
.unwrap();
assert_eq!(result, JsValue::Smi(4)); }
#[test]
fn e2e_continue_label_nested_loops() {
let result = crate::builtins::global::global_eval(
"var r = 0; outer: for (var i = 0; i < 3; i++) { for (var j = 0; j < 3; j++) { if (j === 1) continue outer; r++; } } r",
)
.unwrap();
assert_eq!(result, JsValue::Smi(3)); }
#[test]
fn e2e_break_in_switch_inside_loop() {
let result = crate::builtins::global::global_eval(
"var r = 0; for (var i = 0; i < 3; i++) { switch(i) { case 1: break; default: r++; } } r",
)
.unwrap();
assert_eq!(result, JsValue::Smi(2)); }
#[test]
fn e2e_break_label_exits_loop_from_switch() {
let result = crate::builtins::global::global_eval(
"var r = 0; loop1: for (var i = 0; i < 10; i++) { switch(i) { case 3: break loop1; default: r++; } } r",
)
.unwrap();
assert_eq!(result, JsValue::Smi(3)); }
#[test]
fn e2e_labeled_do_while_break() {
let result = crate::builtins::global::global_eval(
"var r = 0; outer: do { r++; if (r === 5) break outer; } while (true); r",
)
.unwrap();
assert_eq!(result, JsValue::Smi(5));
}
#[test]
fn e2e_labeled_do_while_continue() {
let result = crate::builtins::global::global_eval(
"var r = 0; var s = 0; outer: do { r++; if (r < 3) continue outer; s++; } while (r < 5); s",
)
.unwrap();
assert_eq!(result, JsValue::Smi(3)); }
#[test]
fn e2e_for_in_with_label_break() {
let result = crate::builtins::global::global_eval(
"var r = 0; var obj = {a:1, b:2, c:3}; outer: for (var k in obj) { r++; if (k === 'b') break outer; } r",
)
.unwrap();
assert_eq!(result, JsValue::Smi(2));
}
#[test]
fn e2e_for_in_with_label_continue() {
let result = crate::builtins::global::global_eval(
"var r = 0; var obj = {a:1, b:2, c:3}; outer: for (var k in obj) { if (k === 'b') continue outer; r++; } r",
)
.unwrap();
assert_eq!(result, JsValue::Smi(2)); }
#[test]
fn e2e_for_of_with_label_break() {
let result = crate::builtins::global::global_eval(
"var r = 0; outer: for (var v of [10, 20, 30, 40]) { r += v; if (v === 20) break outer; } r",
)
.unwrap();
assert_eq!(result, JsValue::Smi(30)); }
#[test]
fn e2e_for_of_with_label_continue() {
let result = crate::builtins::global::global_eval(
"var r = 0; outer: for (var v of [1, 2, 3, 4]) { if (v === 2) continue outer; r += v; } r",
)
.unwrap();
assert_eq!(result, JsValue::Smi(8)); }
#[test]
fn e2e_nested_for_in_break_outer() {
let result = crate::builtins::global::global_eval(
"var r = 0; var a = {x:1, y:2}; var b = {p:1, q:2}; \
outer: for (var k1 in a) { for (var k2 in b) { r++; if (k2 === 'q') break outer; } } r",
)
.unwrap();
assert_eq!(result, JsValue::Smi(2));
}
#[test]
fn e2e_try_finally_with_break() {
let result = crate::builtins::global::global_eval(
"var r = 0; outer: { try { r = 1; break outer; } finally { r = r + 10; } } r",
)
.unwrap();
assert_eq!(result, JsValue::Smi(11)); }
#[test]
fn e2e_try_finally_with_continue() {
let result = crate::builtins::global::global_eval(
"var r = 0; outer: for (var i = 0; i < 3; i++) { try { if (i === 1) continue outer; } finally { r++; } } r",
)
.unwrap();
assert_eq!(result, JsValue::Smi(3)); }
#[test]
fn e2e_try_finally_break_preserves_value() {
let result = crate::builtins::global::global_eval(
"var r = 0; var f = 0; outer: { try { r = 42; break outer; } finally { f = 99; } } r",
)
.unwrap();
assert_eq!(result, JsValue::Smi(42));
}
#[test]
fn e2e_nested_finally_with_break() {
let result = crate::builtins::global::global_eval(
"var r = ''; outer: { try { try { r += 'a'; break outer; } finally { r += 'b'; } } finally { r += 'c'; } } r",
)
.unwrap();
assert_eq!(result, JsValue::String("abc".into()));
}
#[test]
fn e2e_labeled_statement_simple() {
let result = crate::builtins::global::global_eval("var r; foo: r = 42; r").unwrap();
assert_eq!(result, JsValue::Smi(42));
}
#[test]
fn e2e_labeled_if_statement() {
let result =
crate::builtins::global::global_eval("var r = 0; myLabel: if (true) { r = 7; } r")
.unwrap();
assert_eq!(result, JsValue::Smi(7));
}
#[test]
fn e2e_break_unlabeled_in_for() {
let result = crate::builtins::global::global_eval(
"var r = 0; for (var i = 0; i < 10; i++) { if (i === 5) break; r++; } r",
)
.unwrap();
assert_eq!(result, JsValue::Smi(5));
}
#[test]
fn e2e_continue_unlabeled_in_for() {
let result = crate::builtins::global::global_eval(
"var r = 0; for (var i = 0; i < 5; i++) { if (i === 2) continue; r++; } r",
)
.unwrap();
assert_eq!(result, JsValue::Smi(4));
}
#[test]
fn e2e_break_unlabeled_in_while() {
let result = crate::builtins::global::global_eval(
"var r = 0; while (true) { r++; if (r === 3) break; } r",
)
.unwrap();
assert_eq!(result, JsValue::Smi(3));
}
#[test]
fn e2e_continue_unlabeled_in_while() {
let result = crate::builtins::global::global_eval(
"var r = 0; var i = 0; while (i < 5) { i++; if (i === 3) continue; r++; } r",
)
.unwrap();
assert_eq!(result, JsValue::Smi(4));
}
#[test]
fn e2e_nested_labeled_blocks() {
let result = crate::builtins::global::global_eval(
"var r = 0; outer: { r = 1; inner: { r = 2; break outer; r = 3; } r = 4; } r",
)
.unwrap();
assert_eq!(result, JsValue::Smi(2));
}
#[test]
fn e2e_nested_labeled_blocks_break_inner() {
let result = crate::builtins::global::global_eval(
"var r = 0; outer: { r = 1; inner: { r = 2; break inner; r = 3; } r = r + 10; } r",
)
.unwrap();
assert_eq!(result, JsValue::Smi(12)); }
#[test]
fn e2e_labeled_for_continue_update() {
let result = crate::builtins::global::global_eval(
"var r = 0; outer: for (var i = 0; i < 5; i++) { if (i % 2 === 0) continue outer; r += i; } r",
)
.unwrap();
assert_eq!(result, JsValue::Smi(4)); }
#[test]
fn e2e_switch_fall_through_then_break() {
let result = crate::builtins::global::global_eval(
"var r = ''; switch(1) { case 1: r += 'a'; case 2: r += 'b'; break; case 3: r += 'c'; } r",
)
.unwrap();
assert_eq!(result, JsValue::String("ab".into()));
}
#[test]
fn e2e_continue_in_switch_in_loop() {
let result = crate::builtins::global::global_eval(
"var r = 0; for (var i = 0; i < 4; i++) { switch(i) { case 1: continue; } r++; } r",
)
.unwrap();
assert_eq!(result, JsValue::Smi(3)); }
#[test]
fn e2e_for_of_nested_break_outer() {
let result = crate::builtins::global::global_eval(
"var r = 0; outer: for (var a of [1, 2, 3]) { for (var b of [10, 20]) { r += a * b; if (a === 2 && b === 10) break outer; } } r",
)
.unwrap();
assert_eq!(result, JsValue::Smi(50));
}
#[test]
fn e2e_try_catch_finally_break() {
let result = crate::builtins::global::global_eval(
"var r = ''; outer: { try { r += 'try'; break outer; } catch(e) { r += 'catch'; } finally { r += 'finally'; } r += 'after'; } r",
)
.unwrap();
assert_eq!(result, JsValue::String("tryfinally".into()));
}
#[test]
fn e2e_triple_nested_finally_break() {
let result = crate::builtins::global::global_eval(
"var r = ''; outer: { try { try { try { r += '1'; break outer; } finally { r += '2'; } } finally { r += '3'; } } finally { r += '4'; } } r",
)
.unwrap();
assert_eq!(result, JsValue::String("1234".into()));
}
#[test]
fn e2e_labeled_while_continue_label() {
let result = crate::builtins::global::global_eval(
"var r = 0; var i = 0; outer: while (i < 10) { i++; if (i % 3 === 0) continue outer; r++; } r",
)
.unwrap();
assert_eq!(result, JsValue::Smi(7)); }
#[test]
fn e2e_multiple_labels_same_loop() {
let result = crate::builtins::global::global_eval(
"var r = 0; a: b: for (var i = 0; i < 5; i++) { if (i === 3) break a; r++; } r",
)
.unwrap();
assert_eq!(result, JsValue::Smi(3));
}
#[test]
fn e2e_labeled_block_no_break() {
let result =
crate::builtins::global::global_eval("var r = 0; myLabel: { r = 1; r = 2; } r")
.unwrap();
assert_eq!(result, JsValue::Smi(2));
}
#[test]
fn e2e_for_in_nested_continue_outer() {
let result = crate::builtins::global::global_eval(
"var r = 0; var obj = {a:1, b:2, c:3, d:4}; \
outer: for (var k in obj) { for (var i = 0; i < 2; i++) { if (i === 1) continue outer; r++; } } r",
)
.unwrap();
assert_eq!(result, JsValue::Smi(4));
}
}