use oxide_bytecode::opcode::OpCode; use oxide_types::object::JsObject; use oxide_types::value::JsValue; use crate::vm::Vm; use crate::vm_trace; use oxide_runtime_api as coercion; impl Vm { pub(crate) fn dispatch_member_op(&mut self, op: OpCode, rd: usize, a: usize, b: usize) -> Result<(), String> { vm_trace!("MEMBER_OP {:?} rd={} a={} b={}", op, rd, a, b); match op { OpCode::MEMBER_INC => self.dispatch_member_inc(rd, a, b), OpCode::MEMBER_DEC => self.dispatch_member_dec(rd, a, b), OpCode::DYN_MEMBER_INC => self.dispatch_dyn_member_inc(rd, a, b), OpCode::DYN_MEMBER_DEC => self.dispatch_dyn_member_dec(rd, a, b), OpCode::COMPOUND_MEMBER_ADD => self.dispatch_compound_member_add(rd, a, b), OpCode::COMPOUND_MEMBER_SUB => self.dispatch_compound_member_numeric( "COMPOUND_MEMBER_SUB", rd, a, b, "COMPOUND_MEMBER_SUB on non-object", |l, r| l - r, ), OpCode::COMPOUND_MEMBER_MUL => self.dispatch_compound_member_numeric( "COMPOUND_MEMBER_MUL", rd, a, b, "COMPOUND_MEMBER_MUL on non-object", |l, r| l * r, ), OpCode::COMPOUND_MEMBER_DIV => self.dispatch_compound_member_numeric( "COMPOUND_MEMBER_DIV", rd, a, b, "COMPOUND_MEMBER_DIV on non-object", |l, r| l / r, ), OpCode::COMPOUND_MEMBER_MOD => self.dispatch_compound_member_numeric( "COMPOUND_MEMBER_MOD", rd, a, b, "COMPOUND_MEMBER_MOD on non-object", |l, r| l % r, ), OpCode::COMPOUND_MEMBER_EXP => self.dispatch_compound_member_numeric( "COMPOUND_MEMBER_EXP", rd, a, b, "COMPOUND_MEMBER_EXP on non-object", |l, r| l.powf(r), ), OpCode::COMPOUND_MEMBER_BIT_AND => self.dispatch_compound_member_bitwise( "COMPOUND_MEMBER_BIT_AND", rd, a, b, "COMPOUND_MEMBER_BIT_AND on non-object", |l, r| l & r, ), OpCode::COMPOUND_MEMBER_BIT_OR => self.dispatch_compound_member_bitwise( "COMPOUND_MEMBER_BIT_OR", rd, a, b, "COMPOUND_MEMBER_BIT_OR on non-object", |l, r| l | r, ), OpCode::COMPOUND_MEMBER_BIT_XOR => self.dispatch_compound_member_bitwise( "COMPOUND_MEMBER_BIT_XOR", rd, a, b, "COMPOUND_MEMBER_BIT_XOR on non-object", |l, r| l ^ r, ), OpCode::COMPOUND_MEMBER_SHL => self.dispatch_compound_member_shift( "COMPOUND_MEMBER_SHL", rd, a, b, "COMPOUND_MEMBER_SHL on non-object", false, false, ), OpCode::COMPOUND_MEMBER_SHR => self.dispatch_compound_member_shift( "COMPOUND_MEMBER_SHR", rd, a, b, "COMPOUND_MEMBER_SHR on non-object", true, false, ), OpCode::COMPOUND_MEMBER_USHR => self.dispatch_compound_member_shift( "COMPOUND_MEMBER_USHR", rd, a, b, "COMPOUND_MEMBER_USHR on non-object", true, true, ), _ => unreachable!("non-member opcode passed to dispatch_member_op"), } } fn member_target(&mut self, rd: usize, b: usize, error_msg: &str) -> Result<(*mut JsObject, u32), String> { vm_trace!("MEMBER_TARGET rd={} b={}", rd, b); let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], error_msg)? else { return Err(String::new()); }; let prop_name_si = self.property_key_si(self.regs[b]); Ok((obj_ptr, prop_name_si)) } fn dispatch_member_inc(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> { vm_trace!("MEMBER_INC rd={} a={} b={}", rd, a, b); let Ok((obj_ptr, prop_name_si)) = self.member_target(rd, b, "MEMBER_INC on non-object") else { return Ok(()); }; let receiver = self.regs[rd]; let obj = unsafe { &mut *obj_ptr }; let prop_val = self.read_member_prop(obj, prop_name_si, receiver)?; let n = self.coerce_number_bounded(prop_val)?; let new_val = JsValue::float(n + 1.0); self.set_member_prop(obj, prop_name_si, new_val, receiver)?; self.regs[a] = new_val; Ok(()) } fn dispatch_member_dec(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> { vm_trace!("MEMBER_DEC rd={} a={} b={}", rd, a, b); let Ok((obj_ptr, prop_name_si)) = self.member_target(rd, b, "MEMBER_DEC on non-object") else { return Ok(()); }; let receiver = self.regs[rd]; let obj = unsafe { &mut *obj_ptr }; let prop_val = self.read_member_prop(obj, prop_name_si, receiver)?; let n = self.coerce_number_bounded(prop_val)?; let new_val = JsValue::float(n - 1.0); self.set_member_prop(obj, prop_name_si, new_val, receiver)?; self.regs[a] = new_val; Ok(()) } fn dispatch_dyn_member_inc(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> { vm_trace!("DYN_MEMBER_INC rd={} a={} b={}", rd, a, b); let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "DYN_MEMBER_INC on non-object")? else { return Ok(()); }; let receiver = self.regs[rd]; let obj = unsafe { &mut *obj_ptr }; let prop_name_si = self.property_key_si(self.regs[a]); let prop_val = self.ordinary_get(obj, prop_name_si, receiver)?; let n = self.coerce_number_bounded(prop_val)?; let new_val = JsValue::float(n + 1.0); self.ordinary_set(obj, prop_name_si, new_val, receiver)?; self.regs[b] = new_val; Ok(()) } fn dispatch_dyn_member_dec(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> { vm_trace!("DYN_MEMBER_DEC rd={} a={} b={}", rd, a, b); let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "DYN_MEMBER_DEC on non-object")? else { return Ok(()); }; let receiver = self.regs[rd]; let obj = unsafe { &mut *obj_ptr }; let prop_name_si = self.property_key_si(self.regs[a]); let prop_val = self.ordinary_get(obj, prop_name_si, receiver)?; let n = self.coerce_number_bounded(prop_val)?; let new_val = JsValue::float(n - 1.0); self.ordinary_set(obj, prop_name_si, new_val, receiver)?; self.regs[b] = new_val; Ok(()) } fn dispatch_compound_member_add(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> { vm_trace!("COMPOUND_MEMBER_ADD rd={} a={} b={}", rd, a, b); let Ok((obj_ptr, prop_name_si)) = self.member_target(rd, b, "COMPOUND_MEMBER_ADD on non-object") else { return Ok(()); }; let receiver = self.regs[rd]; let prop_raw = { let obj = unsafe { &mut *obj_ptr }; self.read_member_prop(obj, prop_name_si, receiver)? }; let prop_val = self.coerce_primitive_bounded(prop_raw, false)?; let rhs = self.coerce_primitive_bounded(self.regs[a], false)?; let new_val = if prop_val.is_string() || rhs.is_string() { let ls = coercion::to_string(prop_val); let rs = coercion::to_string(rhs); self.new_string(&format!("{ls}{rs}")) } else { let ln = coercion::to_number(prop_val); let rn = coercion::to_number(rhs); JsValue::float(ln + rn) }; let obj = unsafe { &mut *obj_ptr }; self.set_member_prop(obj, prop_name_si, new_val, receiver)?; self.regs[a] = new_val; Ok(()) } fn dispatch_compound_member_numeric( &mut self, op_name: &str, rd: usize, a: usize, b: usize, error_msg: &str, op: F, ) -> Result<(), String> where F: FnOnce(f64, f64) -> f64, { vm_trace!("{} rd={} a={} b={}", op_name, rd, a, b); let Ok((obj_ptr, prop_name_si)) = self.member_target(rd, b, error_msg) else { return Ok(()); }; let receiver = self.regs[rd]; let ln = { let obj = unsafe { &mut *obj_ptr }; let prop_val = self.read_member_prop(obj, prop_name_si, receiver)?; self.coerce_number_bounded(prop_val)? }; let rn = self.coerce_number_bounded(self.regs[a])?; let new_val = JsValue::float(op(ln, rn)); self.regs[a] = new_val; let obj = unsafe { &mut *obj_ptr }; self.set_member_prop(obj, prop_name_si, new_val, receiver)?; Ok(()) } fn dispatch_compound_member_bitwise( &mut self, op_name: &str, rd: usize, a: usize, b: usize, error_msg: &str, op: F, ) -> Result<(), String> where F: FnOnce(i32, i32) -> i32, { vm_trace!("{} rd={} a={} b={}", op_name, rd, a, b); let Ok((obj_ptr, prop_name_si)) = self.member_target(rd, b, error_msg) else { return Ok(()); }; let receiver = self.regs[rd]; let ln = { let obj = unsafe { &mut *obj_ptr }; let prop_val = self.read_member_prop(obj, prop_name_si, receiver)?; self.coerce_int32_bounded(prop_val)? }; let rn = self.coerce_int32_bounded(self.regs[a])?; let new_val = JsValue::int(op(ln, rn)); self.regs[a] = new_val; let obj = unsafe { &mut *obj_ptr }; self.set_member_prop(obj, prop_name_si, new_val, receiver)?; Ok(()) } fn dispatch_compound_member_shift( &mut self, op_name: &str, rd: usize, a: usize, b: usize, error_msg: &str, is_right: bool, is_unsigned: bool, ) -> Result<(), String> { vm_trace!("{} rd={} a={} b={}", op_name, rd, a, b); let Ok((obj_ptr, prop_name_si)) = self.member_target(rd, b, error_msg) else { return Ok(()); }; let receiver = self.regs[rd]; let ln = { let obj = unsafe { &mut *obj_ptr }; let prop_val = self.read_member_prop(obj, prop_name_si, receiver)?; self.coerce_int32_bounded(prop_val)? }; let shift = self.coerce_uint32_bounded(self.regs[a])? & 0x1F; let new_val = if !is_right { JsValue::int(ln.wrapping_shl(shift)) } else if is_unsigned { let u = (ln as u32) >> shift; if u <= i32::MAX as u32 { JsValue::int(u as i32) } else { JsValue::float(u as f64) } } else { JsValue::int(ln >> shift) }; self.regs[a] = new_val; let obj = unsafe { &mut *obj_ptr }; self.set_member_prop(obj, prop_name_si, new_val, receiver)?; Ok(()) } }