use crate::compiler::{is_side_effect_free, BinaryOperator, CompileCtx, Compiler, Label}; use oxide_bytecode::module::Constant; use oxide_bytecode::opcode::{self, OpCode}; use oxide_parser::{ChainElement, Expression, LogicalOperator, SimpleAssignmentTarget, UnaryOperator, UpdateOperator}; impl Compiler { fn emit_private_in_expression( &self, pin: &oxide_parser::PrivateInExpression, ctx: &mut CompileCtx, ) -> Result { let obj_reg = self.emit_expression(&pin.right, ctx)?; let key_reg = self.emit_private_id_reg(pin.left.name.as_str(), ctx)?; let result_reg = ctx.alloc_reg(); ctx.emit(opcode::encode(OpCode::PRIVATE_BRAND_IN, result_reg, obj_reg, key_reg)); Ok(result_reg) } fn emit_binary_expression(&self, bin: &oxide_parser::BinaryExpression, ctx: &mut CompileCtx) -> Result { let checkpoint = ctx.reg_checkpoint(); let left = self.emit_expression(&bin.left, ctx)?; let right = self.emit_expression(&bin.right, ctx)?; let op = match bin.operator { BinaryOperator::Addition => OpCode::ADD, BinaryOperator::Subtraction => OpCode::SUB, BinaryOperator::Multiplication => OpCode::MUL, BinaryOperator::Division => OpCode::DIV, BinaryOperator::Remainder => OpCode::MOD, BinaryOperator::BitwiseAnd => OpCode::BIT_AND, BinaryOperator::BitwiseOR => OpCode::BIT_OR, BinaryOperator::BitwiseXOR => OpCode::BIT_XOR, BinaryOperator::ShiftLeft => OpCode::SHL, BinaryOperator::ShiftRight => OpCode::SHR, BinaryOperator::ShiftRightZeroFill => OpCode::USHR, BinaryOperator::Equality => OpCode::EQ, BinaryOperator::Inequality => OpCode::NEQ, BinaryOperator::LessThan => OpCode::LT, BinaryOperator::GreaterThan => OpCode::GT, BinaryOperator::LessEqualThan => OpCode::LTE, BinaryOperator::GreaterEqualThan => OpCode::GTE, BinaryOperator::In => OpCode::IN, BinaryOperator::Instanceof => OpCode::INSTANCEOF, BinaryOperator::StrictEquality => OpCode::STRICT_EQ, BinaryOperator::StrictInequality => OpCode::STRICT_NEQ, _ => return Err(format!("unsupported binary operator: {:?}", bin.operator)), }; ctx.emit(opcode::encode(op, left, left, right)); if is_side_effect_free(&bin.left) && is_side_effect_free(&bin.right) { ctx.restore_reg_checkpoint(checkpoint.saturating_add(1).max(left.saturating_add(1))); } Ok(left) } fn emit_unary_expression(&self, un: &oxide_parser::UnaryExpression, ctx: &mut CompileCtx) -> Result { if matches!(un.operator, UnaryOperator::Delete) { return match &un.argument { Expression::Identifier(_) => { Err("SyntaxError: delete of an unqualified identifier in strict mode".into()) } Expression::StaticMemberExpression(member) => { let obj_reg = self.emit_expression(&member.object, ctx)?; let prop_name = member.property.name.as_str(); let const_idx = ctx.add_constant(Constant::String(prop_name.to_string())); ctx.emit(opcode::encode(OpCode::DELETE_PROP_STATIC, obj_reg, obj_reg, 0)); ctx.emit(const_idx as u32); Ok(obj_reg) } Expression::ComputedMemberExpression(member) => { let obj_reg = self.emit_expression(&member.object, ctx)?; let key_reg = self.emit_expression(&member.expression, ctx)?; ctx.emit(opcode::encode(OpCode::DELETE_PROP_DYNAMIC, obj_reg, obj_reg, key_reg)); Ok(obj_reg) } Expression::ChainExpression(chain) => { let mut nullish_jumps = Vec::new(); let result_reg = match &chain.expression { ChainElement::StaticMemberExpression(member) => { let obj_reg = self.emit_expression(&member.object, ctx)?; if member.optional { let dup_reg = ctx.alloc_reg(); ctx.emit(opcode::encode(OpCode::LOAD_VAR, dup_reg, obj_reg, 0)); let jump_pos = ctx.bytecode.len(); ctx.emit(opcode::encode_jmp_if_nullish(dup_reg, 0)); nullish_jumps.push(jump_pos); } let prop_name = member.property.name.as_str(); let const_idx = ctx.add_constant(Constant::String(prop_name.to_string())); ctx.emit(opcode::encode(OpCode::DELETE_PROP_STATIC, obj_reg, obj_reg, 0)); ctx.emit(const_idx as u32); obj_reg } ChainElement::ComputedMemberExpression(member) => { let obj_reg = self.emit_expression(&member.object, ctx)?; if member.optional { let dup_reg = ctx.alloc_reg(); ctx.emit(opcode::encode(OpCode::LOAD_VAR, dup_reg, obj_reg, 0)); let jump_pos = ctx.bytecode.len(); ctx.emit(opcode::encode_jmp_if_nullish(dup_reg, 0)); nullish_jumps.push(jump_pos); } let key_reg = self.emit_expression(&member.expression, ctx)?; ctx.emit(opcode::encode(OpCode::DELETE_PROP_DYNAMIC, obj_reg, obj_reg, key_reg)); obj_reg } _ => return Err("invalid delete target".into()), }; let end_jump_pos = ctx.bytecode.len(); ctx.emit(opcode::encode_jmp(0)); let short_pos = ctx.bytecode.len(); for jump_pos in nullish_jumps { let offset = (short_pos as isize) - (jump_pos as isize); let instr = ctx.bytecode[jump_pos]; let rd = opcode::rd(instr); let offset = ctx.checked_jump_offset(offset); ctx.bytecode[jump_pos] = opcode::encode_jmp_if_nullish(rd, offset); } let true_idx = ctx.add_constant(Constant::Boolean(true)); ctx.emit_load_const(result_reg, true_idx); let end_pos = ctx.bytecode.len(); let offset = (end_pos as isize) - (end_jump_pos as isize); let offset = ctx.checked_jump_offset(offset); ctx.bytecode[end_jump_pos] = opcode::encode_jmp(offset); Ok(result_reg) } _ => Err("invalid delete target".into()), }; } let arg = self.emit_expression(&un.argument, ctx)?; match un.operator { UnaryOperator::UnaryNegation => { ctx.emit(opcode::encode(OpCode::NEG, arg, arg, 0)); Ok(arg) } UnaryOperator::Typeof => { ctx.emit(opcode::encode(OpCode::TYPEOF, arg, arg, 0)); Ok(arg) } UnaryOperator::Void => { ctx.emit(opcode::encode(OpCode::VOID, arg, arg, 0)); Ok(arg) } UnaryOperator::LogicalNot => { ctx.emit(opcode::encode(OpCode::NOT, arg, arg, 0)); Ok(arg) } UnaryOperator::BitwiseNot => { ctx.emit(opcode::encode(OpCode::BIT_NOT, arg, arg, 0)); Ok(arg) } UnaryOperator::UnaryPlus => { ctx.emit(opcode::encode(OpCode::UNARY_PLUS, arg, arg, 0)); Ok(arg) } UnaryOperator::Delete => Err("invalid delete target".into()), } } fn emit_conditional_expression( &self, cond: &oxide_parser::ConditionalExpression, ctx: &mut CompileCtx, ) -> Result { let id = ctx.next_label_id(); let else_label = Label::TernaryElse(id); let end_label = Label::TernaryEnd(id); let test_reg = self.emit_expression(&cond.test, ctx)?; ctx.emit_jmp_if_false_labeled(test_reg, else_label); let cons_reg = self.emit_expression(&cond.consequent, ctx)?; let result_reg = ctx.alloc_reg(); ctx.emit(opcode::encode(OpCode::LOAD_VAR, result_reg, cons_reg, 0)); ctx.emit_jmp_labeled(end_label); ctx.labels.label_map.insert(else_label, ctx.bytecode.len()); let alt_reg = self.emit_expression(&cond.alternate, ctx)?; ctx.emit(opcode::encode(OpCode::LOAD_VAR, result_reg, alt_reg, 0)); ctx.labels.label_map.insert(end_label, ctx.bytecode.len()); Ok(result_reg) } fn emit_logical_expression( &self, log: &oxide_parser::LogicalExpression, ctx: &mut CompileCtx, ) -> Result { let left_reg = self.emit_expression(&log.left, ctx)?; if is_side_effect_free(&log.left) && is_side_effect_free(&log.right) { let right_reg = self.emit_expression(&log.right, ctx)?; let r = ctx.alloc_reg(); let op = match log.operator { LogicalOperator::And => OpCode::AND, LogicalOperator::Or => OpCode::OR, LogicalOperator::Coalesce => OpCode::NULLISH, }; ctx.emit(opcode::encode(op, r, left_reg, right_reg)); return Ok(r); } if matches!(log.operator, LogicalOperator::Coalesce) { let id = ctx.next_label_id(); let rhs_label = Label::TernaryElse(id); let end_label = Label::TernaryEnd(id); let dup_reg = ctx.alloc_reg(); ctx.emit(opcode::encode(OpCode::LOAD_VAR, dup_reg, left_reg, 0)); ctx.emit_jmp_if_nullish_labeled(dup_reg, rhs_label); ctx.emit_jmp_labeled(end_label); ctx.labels.label_map.insert(rhs_label, ctx.bytecode.len()); let right_reg = self.emit_expression(&log.right, ctx)?; ctx.emit(opcode::encode(OpCode::LOAD_VAR, dup_reg, right_reg, 0)); ctx.labels.label_map.insert(end_label, ctx.bytecode.len()); return Ok(dup_reg); } let id = ctx.next_label_id(); let skip_label = match log.operator { LogicalOperator::And => Label::TernaryEnd(id), LogicalOperator::Or => Label::TernaryElse(id), LogicalOperator::Coalesce => return Err("invalid logical operator dispatch".into()), }; let dup_reg = ctx.alloc_reg(); ctx.emit(opcode::encode(OpCode::LOAD_VAR, dup_reg, left_reg, 0)); match log.operator { LogicalOperator::And => ctx.emit_jmp_if_false_labeled(dup_reg, skip_label), LogicalOperator::Or => ctx.emit_jmp_if_true_labeled(dup_reg, skip_label), LogicalOperator::Coalesce => return Err("invalid logical operator dispatch".into()), } let right_reg = self.emit_expression(&log.right, ctx)?; ctx.emit(opcode::encode(OpCode::LOAD_VAR, dup_reg, right_reg, 0)); ctx.labels.label_map.insert(skip_label, ctx.bytecode.len()); Ok(dup_reg) } fn emit_update_expression( &self, update: &oxide_parser::UpdateExpression, ctx: &mut CompileCtx, ) -> Result { match &update.argument { SimpleAssignmentTarget::AssignmentTargetIdentifier(id) => { let name = id.name.as_str(); // Check if this is an upvalue or captured cell reference let uv_idx = ctx.current_upvalue_captures.iter().position(|u| u.name == name); let is_captured = ctx.scopes.symbols.lookup_is_captured(name); if let Some(uv) = uv_idx { // Upvalue: LOAD_UPVALUE + CONST(1) + ADD/SUB + STORE_UPVALUE let val_reg = ctx.alloc_reg(); ctx.emit(opcode::encode(OpCode::LOAD_UPVALUE, val_reg, uv as u8, 0)); let one_idx = ctx.add_constant(Constant::Int(1)); let one_reg = ctx.alloc_reg(); ctx.emit_load_const(one_reg, one_idx); let op = if update.operator == UpdateOperator::Increment { OpCode::ADD } else { OpCode::SUB }; ctx.emit(opcode::encode(op, val_reg, val_reg, one_reg)); ctx.emit(opcode::encode(OpCode::STORE_UPVALUE, 0, val_reg, uv as u8)); Ok(val_reg) } else if is_captured { // Captured cell: CELL_GET + CONST(1) + ADD/SUB + CELL_SET let cell_idx = ctx .scopes .cell_registry .iter() .find(|(n, _)| n == name) .map(|(_, idx)| *idx) .unwrap_or(0); let val_reg = ctx.alloc_reg(); if let Some((binding, _)) = ctx.scopes.symbols.lookup_any_binding(name) { ctx.emit(opcode::encode(OpCode::CELL_GET, val_reg, binding.reg, cell_idx)); } else { ctx.emit(opcode::encode(OpCode::CELL_GET, val_reg, 0, cell_idx)); } let one_idx = ctx.add_constant(Constant::Int(1)); let one_reg = ctx.alloc_reg(); ctx.emit_load_const(one_reg, one_idx); let op = if update.operator == UpdateOperator::Increment { OpCode::ADD } else { OpCode::SUB }; ctx.emit(opcode::encode(op, val_reg, val_reg, one_reg)); ctx.emit(opcode::encode(OpCode::CELL_SET, 0, val_reg, cell_idx)); Ok(val_reg) } else { let var_reg = ctx.lookup_or_global(name); let result_reg = ctx.alloc_reg(); let op = match (update.operator, update.prefix) { (UpdateOperator::Increment, true) => OpCode::INC_PRE, (UpdateOperator::Increment, false) => OpCode::INC_POST, (UpdateOperator::Decrement, true) => OpCode::DEC_PRE, (UpdateOperator::Decrement, false) => OpCode::DEC_POST, }; ctx.emit(opcode::encode(op, var_reg, result_reg, result_reg)); Ok(result_reg) } } SimpleAssignmentTarget::StaticMemberExpression(member) => { let obj_reg = self.emit_expression(&member.object, ctx)?; let prop_name = member.property.name.as_str(); let key_idx = ctx.add_constant(Constant::String(prop_name.to_string())); let key_reg = ctx.alloc_reg(); ctx.emit(opcode::encode( OpCode::LOAD_CONST, key_reg, (key_idx & 0xFF) as u8, ((key_idx >> 8) & 0xFF) as u8, )); let val_reg = ctx.alloc_reg(); let op = match update.operator { UpdateOperator::Increment => OpCode::MEMBER_INC, UpdateOperator::Decrement => OpCode::MEMBER_DEC, }; ctx.emit(opcode::encode(op, obj_reg, val_reg, key_reg)); ctx.emit(0); ctx.emit(0); ctx.emit(0); Ok(val_reg) } SimpleAssignmentTarget::ComputedMemberExpression(member) => { let obj_reg = self.emit_expression(&member.object, ctx)?; let key_reg = self.emit_expression(&member.expression, ctx)?; let val_reg = ctx.alloc_reg(); let op = match update.operator { UpdateOperator::Increment => OpCode::DYN_MEMBER_INC, UpdateOperator::Decrement => OpCode::DYN_MEMBER_DEC, }; ctx.emit(opcode::encode(op, obj_reg, key_reg, val_reg)); Ok(val_reg) } _ => Err("member update not yet supported".into()), } } pub(crate) fn emit_operator(&self, expr: &Expression, ctx: &mut CompileCtx) -> Result { match expr { Expression::BinaryExpression(bin) => self.emit_binary_expression(bin, ctx), Expression::PrivateInExpression(pin) => self.emit_private_in_expression(pin, ctx), Expression::UnaryExpression(un) => self.emit_unary_expression(un, ctx), Expression::ConditionalExpression(cond) => self.emit_conditional_expression(cond, ctx), Expression::LogicalExpression(log) => self.emit_logical_expression(log, ctx), Expression::UpdateExpression(update) => self.emit_update_expression(update, ctx), _ => self.emit_unsupported_expression(expr, ctx), } } }