From 7d286072e38d7cf03a2790642e13cedb57179a5f Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sat, 6 Jun 2026 12:35:28 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=8E=A7=E5=88=B6?= =?UTF-8?q?=E6=B5=81=E7=BC=96=E8=AF=91=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_compiler/src/compiler.rs | 17 ++- crates/oxide_compiler/src/counter.rs | 104 +++++++++++-- crates/oxide_compiler/src/emitter.rs | 205 +++++++++++++++++++++++++- 3 files changed, 311 insertions(+), 15 deletions(-) diff --git a/crates/oxide_compiler/src/compiler.rs b/crates/oxide_compiler/src/compiler.rs index 923b1cf0..05e304da 100644 --- a/crates/oxide_compiler/src/compiler.rs +++ b/crates/oxide_compiler/src/compiler.rs @@ -14,8 +14,18 @@ pub(crate) fn is_int_literal(value: f64) -> bool { value.fract() == 0.0 && value >= i32::MIN as f64 && value <= i32::MAX as f64 } +pub(crate) fn is_side_effect_free(expr: &Expression) -> bool { + matches!( + expr, + Expression::NumericLiteral(_) + | Expression::StringLiteral(_) + | Expression::BooleanLiteral(_) + | Expression::NullLiteral(_) + | Expression::Identifier(_) + ) +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[allow(dead_code)] pub(crate) enum Label { IfElse(u32), IfEnd(u32), @@ -26,11 +36,8 @@ pub(crate) enum Label { ForEnd(u32), TernaryEnd(u32), TernaryElse(u32), - BreakTarget(u32), - ContinueTarget(u32), } -#[allow(dead_code)] pub(crate) struct CompileCtx { pub(crate) bytecode: Vec, pub(crate) constants: Vec, @@ -43,7 +50,6 @@ pub(crate) struct CompileCtx { pub(crate) projected_pc: usize, } -#[allow(dead_code)] impl CompileCtx { pub(crate) fn new() -> Self { Self { @@ -75,6 +81,7 @@ impl CompileCtx { pub(crate) fn reset_regs(&mut self) { self.next_reg = 0; self.projected_pc = 0; + self.label_counter = 0; } pub(crate) fn add_constant(&mut self, c: Constant) -> u16 { diff --git a/crates/oxide_compiler/src/counter.rs b/crates/oxide_compiler/src/counter.rs index b9d99470..bb7848c9 100644 --- a/crates/oxide_compiler/src/counter.rs +++ b/crates/oxide_compiler/src/counter.rs @@ -1,6 +1,6 @@ use oxide_parser::{Expression, Statement}; -use crate::compiler::{CompileCtx, Compiler}; +use crate::compiler::{is_side_effect_free, CompileCtx, Compiler, Label}; impl Compiler { pub(crate) fn count_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) { @@ -14,37 +14,74 @@ impl Compiler { if let Some(init) = &d.init { self.count_expression(init, ctx); } + ctx.projected_pc += 1; // STORE_VAR } } Statement::ReturnStatement(ret) => { if let Some(arg) = &ret.argument { self.count_expression(arg, ctx); } + ctx.projected_pc += 1; // RETURN } Statement::IfStatement(ifs) => { + let id = ctx.next_label_id(); + let else_label = Label::IfElse(id); + let end_label = Label::IfEnd(id); + self.count_expression(&ifs.test, ctx); + ctx.projected_pc += 1; // JMP_IF_FALSE self.count_statement(&ifs.consequent, ctx); - if let Some(alt) = &ifs.alternate { - self.count_statement(alt, ctx); + ctx.alloc_reg(); // result register + ctx.projected_pc += 1; // LOAD_VAR result <- consequent + ctx.projected_pc += 1; // JMP (skip else) + ctx.label_map.insert(else_label, ctx.projected_pc); + if let Some(alt_stmt) = &ifs.alternate { + self.count_statement(alt_stmt, ctx); + ctx.projected_pc += 1; // LOAD_VAR result <- alternate } + ctx.label_map.insert(end_label, ctx.projected_pc); } Statement::WhileStatement(wh) => { + let id = ctx.next_label_id(); + let start_label = Label::WhileStart(id); + let end_label = Label::WhileEnd(id); + + ctx.label_map.insert(start_label, ctx.projected_pc); self.count_expression(&wh.test, ctx); + ctx.projected_pc += 1; // JMP_IF_FALSE self.count_statement(&wh.body, ctx); + ctx.projected_pc += 1; // JMP (backward) + ctx.label_map.insert(end_label, ctx.projected_pc); } Statement::ForStatement(fr) => { + let id = ctx.next_label_id(); + let start_label = Label::ForStart(id); + let update_label = Label::ForUpdate(id); + let end_label = Label::ForEnd(id); + if let Some(init) = &fr.init { if let Some(expr) = init.as_expression() { self.count_expression(expr, ctx); } } + ctx.label_map.insert(start_label, ctx.projected_pc); if let Some(test) = &fr.test { self.count_expression(test, ctx); + ctx.projected_pc += 1; // JMP_IF_FALSE } + self.count_statement(&fr.body, ctx); + ctx.label_map.insert(update_label, ctx.projected_pc); if let Some(update) = &fr.update { self.count_expression(update, ctx); } - self.count_statement(&fr.body, ctx); + ctx.projected_pc += 1; // JMP (backward) + ctx.label_map.insert(end_label, ctx.projected_pc); + } + Statement::BreakStatement(_) => { + ctx.projected_pc += 1; // JMP + } + Statement::ContinueStatement(_) => { + ctx.projected_pc += 1; // JMP } Statement::BlockStatement(block) => { for s in &block.body { @@ -61,14 +98,17 @@ impl Compiler { self.count_expression(&bin.left, ctx); self.count_expression(&bin.right, ctx); ctx.alloc_reg(); + ctx.projected_pc += 1; // ADD/SUB/MUL/DIV/etc. } Expression::UnaryExpression(un) => { self.count_expression(&un.argument, ctx); ctx.alloc_reg(); + ctx.projected_pc += 1; // NEG/TYPEOF/VOID/NOT } Expression::CallExpression(call) => { self.count_expression(&call.callee, ctx); ctx.alloc_reg(); + ctx.projected_pc += 1; // CALL } Expression::AssignmentExpression(assign) => { if let oxide_parser::AssignmentTarget::StaticMemberExpression(member) = &assign.left @@ -76,6 +116,7 @@ impl Compiler { self.count_expression(&member.object, ctx); self.count_expression(&assign.right, ctx); ctx.alloc_reg(); + ctx.projected_pc += 1; // IC_SET_PROP } else if let oxide_parser::AssignmentTarget::ComputedMemberExpression(member) = &assign.left { @@ -83,16 +124,28 @@ impl Compiler { self.count_expression(&member.expression, ctx); self.count_expression(&assign.right, ctx); ctx.alloc_reg(); + ctx.projected_pc += 1; // SET_PROP_DYNAMIC } else { self.count_expression(&assign.right, ctx); ctx.alloc_reg(); + ctx.projected_pc += 1; // STORE_VAR } } Expression::ConditionalExpression(cond) => { + let id = ctx.next_label_id(); + let else_label = Label::TernaryElse(id); + let end_label = Label::TernaryEnd(id); + self.count_expression(&cond.test, ctx); + ctx.projected_pc += 1; // JMP_IF_FALSE self.count_expression(&cond.consequent, ctx); + ctx.alloc_reg(); // result register + ctx.projected_pc += 1; // LOAD_VAR result <- consequent + ctx.projected_pc += 1; // JMP to end + ctx.label_map.insert(else_label, ctx.projected_pc); self.count_expression(&cond.alternate, ctx); - ctx.alloc_reg(); + ctx.projected_pc += 1; // LOAD_VAR result <- alternate + ctx.label_map.insert(end_label, ctx.projected_pc); } Expression::SequenceExpression(seq) => { for e in &seq.expressions { @@ -101,42 +154,75 @@ impl Compiler { } Expression::LogicalExpression(log) => { self.count_expression(&log.left, ctx); - self.count_expression(&log.right, ctx); - ctx.alloc_reg(); + + let is_simple = is_side_effect_free(&log.left) && is_side_effect_free(&log.right); + + if is_simple { + self.count_expression(&log.right, ctx); + ctx.alloc_reg(); + ctx.projected_pc += 1; // AND/OR + } else { + use oxide_parser::LogicalOperator; + let id = ctx.next_label_id(); + ctx.alloc_reg(); // dup register + ctx.projected_pc += 1; // LOAD_VAR (DUP) + ctx.projected_pc += 1; // JMP_IF_FALSE or JMP_IF_TRUE + self.count_expression(&log.right, ctx); + ctx.projected_pc += 1; // LOAD_VAR (overwrite) + let skip_label = match log.operator { + LogicalOperator::And => Label::TernaryEnd(id), + LogicalOperator::Or => Label::TernaryElse(id), + LogicalOperator::Coalesce => { + return; + } + }; + ctx.label_map.insert(skip_label, ctx.projected_pc); + } } Expression::ObjectExpression(obj) => { ctx.alloc_reg(); + ctx.projected_pc += 1; // NEW_OBJECT for prop in &obj.properties { if let oxide_parser::ObjectPropertyKind::ObjectProperty(p) = prop { ctx.alloc_reg(); + ctx.projected_pc += 1; // LOAD_CONST key self.count_expression(&p.value, ctx); + ctx.projected_pc += 1; // SET_PROP } } } Expression::ArrayExpression(arr) => { ctx.alloc_reg(); + ctx.projected_pc += 1; // NEW_ARRAY for elem in &arr.elements { if let Some(e) = elem.as_expression() { self.count_expression(e, ctx); ctx.alloc_reg(); + ctx.projected_pc += 1; // LOAD_CONST index + ctx.projected_pc += 1; // SET_ELEM } } } Expression::StaticMemberExpression(member) => { self.count_expression(&member.object, ctx); - ctx.alloc_reg(); - ctx.alloc_reg(); + ctx.alloc_reg(); // key register + ctx.projected_pc += 1; // LOAD_CONST key + ctx.alloc_reg(); // result register + ctx.projected_pc += 1; // IC_GET_PROP + ctx.projected_pc += 1; // IC ext word } Expression::ComputedMemberExpression(member) => { self.count_expression(&member.object, ctx); self.count_expression(&member.expression, ctx); ctx.alloc_reg(); + ctx.projected_pc += 1; // GET_PROP_DYNAMIC } Expression::ParenthesizedExpression(p) => { self.count_expression(&p.expression, ctx); } _ => { ctx.alloc_reg(); + ctx.projected_pc += 1; // LOAD_CONST or LOAD_VAR } } } diff --git a/crates/oxide_compiler/src/emitter.rs b/crates/oxide_compiler/src/emitter.rs index fd112ba9..980f0b5a 100644 --- a/crates/oxide_compiler/src/emitter.rs +++ b/crates/oxide_compiler/src/emitter.rs @@ -1,7 +1,8 @@ +use crate::compiler::Label; use crate::opcode::{self, OpCode}; use oxide_parser::{AssignmentOperator, Expression, Statement, UnaryOperator}; -use crate::compiler::{is_int_literal, BinaryOperator, CompileCtx, Compiler}; +use crate::compiler::{is_int_literal, is_side_effect_free, BinaryOperator, CompileCtx, Compiler}; use crate::module::Constant; impl Compiler { @@ -67,6 +68,130 @@ impl Compiler { ctx.pop_scope(); Ok(r) } + Statement::IfStatement(ifs) => { + let id = ctx.next_label_id(); + let else_label = Label::IfElse(id); + let end_label = Label::IfEnd(id); + + let test_reg = self.emit_expression(&ifs.test, ctx)?; + + let else_pos = ctx.label_map[&else_label]; + let offset = (else_pos as isize) - (ctx.bytecode.len() as isize); + ctx.emit(opcode::encode_jmp_if_false(test_reg, offset as i16)); + + let cons_reg = self.emit_statement(&ifs.consequent, ctx)?; + let result_reg = ctx.alloc_reg(); + if let Some(r) = cons_reg { + ctx.emit(opcode::encode(OpCode::LOAD_VAR, result_reg, r, 0)); + } else { + let undef_idx = ctx.add_constant(Constant::Undefined); + ctx.emit(opcode::encode( + OpCode::LOAD_CONST, + result_reg, + (undef_idx & 0xFF) as u8, + ((undef_idx >> 8) & 0xFF) as u8, + )); + } + + let end_pos = ctx.label_map[&end_label]; + let offset = (end_pos as isize) - (ctx.bytecode.len() as isize); + ctx.emit(opcode::encode_jmp(offset as i16)); + + if let Some(alt) = &ifs.alternate { + let alt_reg = self.emit_statement(alt, ctx)?; + if let Some(r) = alt_reg { + ctx.emit(opcode::encode(OpCode::LOAD_VAR, result_reg, r, 0)); + } else { + let undef_idx = ctx.add_constant(Constant::Undefined); + ctx.emit(opcode::encode( + OpCode::LOAD_CONST, + result_reg, + (undef_idx & 0xFF) as u8, + ((undef_idx >> 8) & 0xFF) as u8, + )); + } + } + + Ok(Some(result_reg)) + } + Statement::WhileStatement(wh) => { + let id = ctx.next_label_id(); + let start_label = Label::WhileStart(id); + let end_label = Label::WhileEnd(id); + + ctx.push_loop(end_label, start_label); + + let test_reg = self.emit_expression(&wh.test, ctx)?; + + let end_pos = ctx.label_map[&end_label]; + let offset = (end_pos as isize) - (ctx.bytecode.len() as isize); + ctx.emit(opcode::encode_jmp_if_false(test_reg, offset as i16)); + + self.emit_statement(&wh.body, ctx)?; + + let start_pos = ctx.label_map[&start_label]; + let offset = (start_pos as isize) - (ctx.bytecode.len() as isize); + ctx.emit(opcode::encode_jmp(offset as i16)); + + ctx.pop_loop(); + Ok(None) + } + Statement::ForStatement(fr) => { + let id = ctx.next_label_id(); + let start_label = Label::ForStart(id); + let update_label = Label::ForUpdate(id); + let end_label = Label::ForEnd(id); + + ctx.push_loop(end_label, update_label); + + if let Some(init) = &fr.init { + if let Some(expr) = init.as_expression() { + self.emit_expression(expr, ctx)?; + } else if init.is_var_declaration() { + return Err( + "for-loop `let` init not yet supported; use expression init".into() + ); + } + } + + if let Some(test) = &fr.test { + let test_reg = self.emit_expression(test, ctx)?; + let end_pos = ctx.label_map[&end_label]; + let offset = (end_pos as isize) - (ctx.bytecode.len() as isize); + ctx.emit(opcode::encode_jmp_if_false(test_reg, offset as i16)); + } + + self.emit_statement(&fr.body, ctx)?; + + if let Some(update) = &fr.update { + self.emit_expression(update, ctx)?; + } + + let start_pos = ctx.label_map[&start_label]; + let offset = (start_pos as isize) - (ctx.bytecode.len() as isize); + ctx.emit(opcode::encode_jmp(offset as i16)); + + ctx.pop_loop(); + + Ok(None) + } + Statement::BreakStatement(_) => { + let (break_label, _) = + ctx.current_loop().ok_or("break outside loop".to_string())?; + let break_pos = ctx.label_map[break_label]; + let offset = (break_pos as isize) - (ctx.bytecode.len() as isize); + ctx.emit(opcode::encode_jmp(offset as i16)); + Ok(None) + } + Statement::ContinueStatement(_) => { + let (_, continue_label) = ctx + .current_loop() + .ok_or("continue outside loop".to_string())?; + let continue_pos = ctx.label_map[continue_label]; + let offset = (continue_pos as isize) - (ctx.bytecode.len() as isize); + ctx.emit(opcode::encode_jmp(offset as i16)); + Ok(None) + } _ => Ok(None), } } @@ -165,9 +290,87 @@ impl Compiler { ctx.emit(opcode::encode(OpCode::VOID, r, arg, 0)); Ok(r) } + UnaryOperator::LogicalNot => { + let r = ctx.alloc_reg(); + ctx.emit(opcode::encode(OpCode::NOT, r, arg, 0)); + Ok(r) + } _ => Err(format!("unsupported unary operator: {:?}", un.operator)), } } + Expression::ConditionalExpression(cond) => { + 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)?; + let else_pos = ctx.label_map[&else_label]; + let end_pos = ctx.label_map[&end_label]; + + let offset = (else_pos as isize) - (ctx.bytecode.len() as isize); + ctx.emit(opcode::encode_jmp_if_false(test_reg, offset as i16)); + + 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)); + + let offset = (end_pos as isize) - (ctx.bytecode.len() as isize); + ctx.emit(opcode::encode_jmp(offset as i16)); + + let alt_reg = self.emit_expression(&cond.alternate, ctx)?; + ctx.emit(opcode::encode(OpCode::LOAD_VAR, result_reg, alt_reg, 0)); + + Ok(result_reg) + } + Expression::LogicalExpression(log) => { + use oxide_parser::LogicalOperator; + 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 => { + return Err("nullish coalescing not supported".into()); + } + }; + ctx.emit(opcode::encode(op, r, left_reg, right_reg)); + Ok(r) + } else { + 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("nullish coalescing not supported".into()); + } + }; + let skip_pos = ctx.label_map[&skip_label]; + + let dup_reg = ctx.alloc_reg(); + ctx.emit(opcode::encode(OpCode::LOAD_VAR, dup_reg, left_reg, 0)); + + let offset = (skip_pos as isize) - (ctx.bytecode.len() as isize); + match log.operator { + LogicalOperator::And => { + ctx.emit(opcode::encode_jmp_if_false(dup_reg, offset as i16)); + } + LogicalOperator::Or => { + ctx.emit(opcode::encode_jmp_if_true(dup_reg, offset as i16)); + } + LogicalOperator::Coalesce => { + return Err("nullish coalescing not supported".into()); + } + } + + let right_reg = self.emit_expression(&log.right, ctx)?; + ctx.emit(opcode::encode(OpCode::LOAD_VAR, dup_reg, right_reg, 0)); + + Ok(dup_reg) + } + } Expression::StaticMemberExpression(member) => { let obj_reg = self.emit_expression(&member.object, ctx)?; let prop_name = member.property.name.as_str();