refactor: 原子切换 emit 产出符号化 IR

This commit is contained in:
2026-08-05 17:52:42 +08:00
parent ef3cd65eb6
commit f34f791b9a
38 changed files with 720 additions and 866 deletions
+86 -275
View File
@@ -2,10 +2,14 @@ use std::cell::Cell;
use std::collections::HashMap;
use crate::emit_ctx::{LabelCtx, ScopeCtx};
use crate::ir::inst::Inst;
use crate::ir::lower::lower;
use crate::ir::operand::{LabelId, Operand};
use crate::ir::IRFunction;
use crate::symbol_table::{Binding, SymbolTable};
use oxide_bytecode::module::CompiledModule;
use oxide_bytecode::module::UpvalueCapture;
use oxide_bytecode::opcode::{self, OpCode};
use oxide_bytecode::opcode::OpCode;
pub use crate::hash::{compiled_module_hash, structural_hash};
use crate::symbol_table::ScopeKind;
@@ -105,43 +109,9 @@ const BUILTIN_GLOBALS: &[&str] = &[
"decodeURIComponent",
];
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[allow(dead_code)]
pub(crate) enum Label {
IfElse(u32),
IfEnd(u32),
WhileStart(u32),
WhileEnd(u32),
ForStart(u32),
ForUpdate(u32),
ForEnd(u32),
TernaryEnd(u32),
TernaryElse(u32),
DoWhileStart(u32),
DoWhileEnd(u32),
ForInStart(u32),
ForInEnd(u32),
ForOfStart(u32),
ForOfEnd(u32),
SwitchEnd(u32),
SwitchCase(u32, u32),
CatchBody(u32),
FinallyBody(u32),
TryEnd(u32),
LabeledEnd(u32),
}
pub(crate) struct JumpFixup {
pub(crate) pc: usize,
pub(crate) label: Label,
pub(crate) opcode: OpCode,
pub(crate) rd: u8,
}
pub(crate) struct FieldBuffer {
pub(crate) bytecode: Vec<opcode::Instr>,
pub(crate) labels: Vec<(Label, usize)>,
pub(crate) fixups: Vec<JumpFixup>,
pub(crate) insts: Vec<Inst>,
pub(crate) labels: Vec<(LabelId, usize)>,
}
/// A labeled-statement scope active during emission. `break label` targets
@@ -150,13 +120,12 @@ pub(crate) struct FieldBuffer {
#[derive(Debug, Clone)]
pub(crate) struct LabelScope {
pub(crate) name: String,
pub(crate) break_label: Label,
pub(crate) continue_label: Option<Label>,
pub(crate) break_label: LabelId,
pub(crate) continue_label: Option<LabelId>,
}
pub(crate) struct CompileCtx {
pub(crate) bytecode: Vec<opcode::Instr>,
pub(crate) fixups: Vec<JumpFixup>,
pub(crate) insts: Vec<Inst>,
pub(crate) constants: Vec<Constant>,
constant_map: HashMap<ConstantKey, u16>,
next_reg: u8,
@@ -164,7 +133,7 @@ pub(crate) struct CompileCtx {
reserved_reg_start: u8,
pub(crate) labels: LabelCtx,
pub(crate) scopes: ScopeCtx,
pub(crate) sub_modules: Vec<CompiledModule>,
pub(crate) nested: Vec<IRFunction>,
/// Register holding `this` in the enclosing function context.
/// Used by arrow functions to capture lexical `this`.
/// Initialized to 254 (conventional this register) at the top level.
@@ -179,7 +148,6 @@ pub(crate) struct CompileCtx {
/// Checked after each emit phase to produce a compile error rather than silent corruption.
pub(crate) reg_overflow: bool,
pub(crate) const_overflow: bool,
pub(crate) jump_overflow: bool,
}
#[derive(Clone, Copy)]
@@ -219,15 +187,14 @@ enum ConstantKey {
impl CompileCtx {
pub(crate) fn new() -> Self {
Self {
bytecode: Vec::new(),
fixups: Vec::new(),
insts: Vec::new(),
constants: Vec::new(),
constant_map: HashMap::new(),
next_reg: 1,
max_regs: 1,
reserved_reg_start: 1,
labels: LabelCtx {
label_map: HashMap::new(),
label_pos: Vec::new(),
loop_stack: Vec::new(),
switch_stack: Vec::new(),
label_scopes: Vec::new(),
@@ -241,7 +208,7 @@ impl CompileCtx {
next_private_name_id: 1,
cell_registry: Vec::new(),
},
sub_modules: Vec::new(),
nested: Vec::new(),
enclosing_this_reg: 254, // conventional this register at top level
in_derived_constructor: false,
in_instance_method: false,
@@ -251,92 +218,11 @@ impl CompileCtx {
current_upvalue_captures: Vec::new(),
reg_overflow: false,
const_overflow: false,
jump_overflow: false,
}
}
pub(crate) fn emit(&mut self, instr: opcode::Instr) {
self.bytecode.push(instr);
}
pub(crate) fn emit_load_const(&mut self, reg: u8, idx: u16) {
self.emit(opcode::encode(OpCode::LOAD_CONST, reg, (idx & 0xFF) as u8, ((idx >> 8) & 0xFF) as u8));
}
pub(crate) fn emit_create_closure(&mut self, reg: u8, sub_idx: u32) {
let idx = sub_idx as u16;
self.emit(opcode::encode(OpCode::CREATE_CLOSURE, reg, (idx & 0xFF) as u8, ((idx >> 8) & 0xFF) as u8));
}
#[allow(dead_code)]
pub(crate) fn emit_jmp_labeled(&mut self, label: Label) {
let pc = self.bytecode.len();
self.emit(opcode::encode_jmp(0));
self.fixups.push(JumpFixup {
pc,
label,
opcode: OpCode::JMP,
rd: 0,
});
}
#[allow(dead_code)]
pub(crate) fn emit_jmp_if_false_labeled(&mut self, rd: u8, label: Label) {
let pc = self.bytecode.len();
self.emit(opcode::encode_jmp_if_false(rd, 0));
self.fixups.push(JumpFixup {
pc,
label,
opcode: OpCode::JMP_IF_FALSE,
rd,
});
}
#[allow(dead_code)]
pub(crate) fn emit_jmp_if_true_labeled(&mut self, rd: u8, label: Label) {
let pc = self.bytecode.len();
self.emit(opcode::encode_jmp_if_true(rd, 0));
self.fixups.push(JumpFixup {
pc,
label,
opcode: OpCode::JMP_IF_TRUE,
rd,
});
}
#[allow(dead_code)]
pub(crate) fn emit_jmp_if_nullish_labeled(&mut self, rd: u8, label: Label) {
let pc = self.bytecode.len();
self.emit(opcode::encode_jmp_if_nullish(rd, 0));
self.fixups.push(JumpFixup {
pc,
label,
opcode: OpCode::JMP_IF_NULLISH,
rd,
});
}
#[allow(dead_code)]
pub(crate) fn emit_try_begin_labeled(&mut self, label: Label) {
let pc = self.bytecode.len();
self.emit(opcode::encode_try_begin(0));
self.fixups.push(JumpFixup {
pc,
label,
opcode: OpCode::TRY_BEGIN,
rd: 0,
});
}
pub(crate) fn emit_try_finally_begin_labeled(&mut self, label: Label) {
let pc = self.bytecode.len();
self.emit(opcode::encode_try_finally_begin(0));
self.fixups.push(JumpFixup {
pc,
label,
opcode: OpCode::TRY_FINALLY_BEGIN,
rd: 0,
});
pub(crate) fn inst(&mut self, inst: Inst) {
self.insts.push(inst);
}
pub(crate) fn alloc_reg(&mut self) -> u8 {
@@ -404,41 +290,6 @@ impl CompileCtx {
idx as u16
}
pub(crate) fn checked_jump_offset(&mut self, offset: isize) -> i16 {
if offset < i16::MIN as isize || offset > i16::MAX as isize {
self.jump_overflow = true;
0
} else {
offset as i16
}
}
pub(crate) fn resolve_label(&self, label: Label) -> Result<usize, String> {
self.labels
.label_map
.get(&label)
.copied()
.ok_or_else(|| format!("Label {:?} not found in bytecode map", label))
}
pub(crate) fn resolve_fixups(&mut self) -> Result<(), String> {
let fixups = std::mem::take(&mut self.fixups);
for fixup in fixups {
let target_pc = self.resolve_label(fixup.label)?;
let offset = self.checked_jump_offset(target_pc as isize - fixup.pc as isize);
self.bytecode[fixup.pc] = match fixup.opcode {
OpCode::JMP => opcode::encode_jmp(offset),
OpCode::JMP_IF_FALSE => opcode::encode_jmp_if_false(fixup.rd, offset),
OpCode::JMP_IF_TRUE => opcode::encode_jmp_if_true(fixup.rd, offset),
OpCode::JMP_IF_NULLISH => opcode::encode_jmp_if_nullish(fixup.rd, offset),
OpCode::TRY_BEGIN => opcode::encode_try_begin(offset),
OpCode::TRY_FINALLY_BEGIN => opcode::encode_try_finally_begin(offset),
_ => return Err(format!("Unsupported fixup opcode {:?}", fixup.opcode)),
};
}
Ok(())
}
pub(crate) fn push_scope(&mut self) {
self.scopes.symbols.push_scope();
}
@@ -503,7 +354,7 @@ impl CompileCtx {
id
}
pub(crate) fn push_loop(&mut self, break_label: Label, continue_label: Label) {
pub(crate) fn push_loop(&mut self, break_label: LabelId, continue_label: LabelId) {
self.labels.loop_stack.push((break_label, continue_label));
}
@@ -511,11 +362,11 @@ impl CompileCtx {
self.labels.loop_stack.pop();
}
pub(crate) fn current_loop(&self) -> Option<&(Label, Label)> {
pub(crate) fn current_loop(&self) -> Option<&(LabelId, LabelId)> {
self.labels.loop_stack.last()
}
pub(crate) fn push_switch(&mut self, break_label: Label) {
pub(crate) fn push_switch(&mut self, break_label: LabelId) {
self.labels.switch_stack.push(break_label);
}
@@ -523,12 +374,12 @@ impl CompileCtx {
self.labels.switch_stack.pop();
}
pub(crate) fn current_switch(&self) -> Option<&Label> {
pub(crate) fn current_switch(&self) -> Option<&LabelId> {
self.labels.switch_stack.last()
}
pub(crate) fn push_label_scope(
&mut self, name: &str, break_label: Label, continue_label: Option<Label>,
&mut self, name: &str, break_label: LabelId, continue_label: Option<LabelId>,
) -> Result<(), String> {
if self.labels.label_scopes.iter().any(|s| s.name == name) {
return Err(format!("SyntaxError: Label '{name}' has already been declared"));
@@ -564,7 +415,7 @@ impl CompileCtx {
/// Drain queued loop labels into active scopes bound to this loop's break and
/// continue targets. Returns how many scopes were pushed (to pop after).
pub(crate) fn take_pending_loop_labels(&mut self, break_label: Label, continue_label: Label) -> usize {
pub(crate) fn take_pending_loop_labels(&mut self, break_label: LabelId, continue_label: LabelId) -> usize {
let names = std::mem::take(&mut self.labels.pending_loop_labels);
let count = names.len();
for name in names {
@@ -880,7 +731,7 @@ impl Compiler {
pub(crate) fn compile_function_body<'a>(
&self, param_specs: &[ParamSpec<'a>], body_stmts: &[Statement<'a>], parent_ctx: &CompileCtx,
is_expression_body: bool, is_arrow: bool,
) -> Result<CompiledModule, String> {
) -> Result<IRFunction, String> {
let body_context = if is_arrow {
FunctionBodyContext::Arrow
} else {
@@ -899,7 +750,7 @@ impl Compiler {
pub(crate) fn compile_function_body_with_bindings<'a>(
&self, param_specs: &[ParamSpec<'a>], body_stmts: &[Statement<'a>], parent_ctx: &CompileCtx,
is_expression_body: bool, extra_bindings: &[(&str, u8)], body_context: FunctionBodyContext,
) -> Result<CompiledModule, String> {
) -> Result<IRFunction, String> {
self.compile_function_body_with_field_hooks(
param_specs,
body_stmts,
@@ -1355,7 +1206,7 @@ impl Compiler {
&self, param_specs: &[ParamSpec<'a>], body_stmts: &[Statement<'a>], parent_ctx: &CompileCtx,
is_expression_body: bool, extra_bindings: &[(&str, u8)], body_context: FunctionBodyContext,
mut emit_fields: Option<E>, fields_after_super: bool,
) -> Result<CompiledModule, String>
) -> Result<IRFunction, String>
where
E: FnMut(&Compiler, &mut CompileCtx) -> Result<(), String>,
{
@@ -1464,21 +1315,21 @@ impl Compiler {
if let Some(emit) = emit_fields.as_mut() {
if fields_after_super {
let mut parent_bytecode = Vec::new();
let mut parent_labels = HashMap::new();
let mut parent_fixups = Vec::new();
std::mem::swap(&mut ctx.bytecode, &mut parent_bytecode);
std::mem::swap(&mut ctx.labels.label_map, &mut parent_labels);
std::mem::swap(&mut ctx.fixups, &mut parent_fixups);
let mut parent_insts = Vec::new();
let mut parent_label_pos = Vec::new();
std::mem::swap(&mut ctx.insts, &mut parent_insts);
std::mem::swap(&mut ctx.labels.label_pos, &mut parent_label_pos);
emit(self, &mut ctx)?;
let field_buffer = FieldBuffer {
bytecode: std::mem::take(&mut ctx.bytecode),
labels: std::mem::take(&mut ctx.labels.label_map).into_iter().collect(),
fixups: std::mem::take(&mut ctx.fixups),
insts: std::mem::take(&mut ctx.insts),
labels: std::mem::take(&mut ctx.labels.label_pos)
.into_iter()
.enumerate()
.filter_map(|(id, pos)| pos.map(|p| (id as LabelId, p)))
.collect(),
};
ctx.bytecode = parent_bytecode;
ctx.labels.label_map = parent_labels;
ctx.fixups = parent_fixups;
ctx.insts = parent_insts;
ctx.labels.label_pos = parent_label_pos;
ctx.field_buffer = Some(field_buffer);
} else {
emit(self, &mut ctx)?;
@@ -1505,60 +1356,33 @@ impl Compiler {
}
}
ctx.resolve_fixups()?;
// Emit implicit RETURN: expression body returns the last expression,
// statement body returns undefined.
if is_expression_body {
if let Some(reg) = last_result_reg {
ctx.emit(opcode::encode(OpCode::RETURN, reg, 0, 0));
ctx.inst(Inst::new(OpCode::RETURN, Operand::Reg(reg as u32), Operand::None, Operand::None));
} else {
let undef_idx = ctx.add_constant(Constant::Undefined);
let undef_reg = ctx.alloc_reg();
ctx.emit(opcode::encode(
OpCode::LOAD_CONST,
undef_reg,
(undef_idx & 0xFF) as u8,
((undef_idx >> 8) & 0xFF) as u8,
));
ctx.emit(opcode::encode(OpCode::RETURN, undef_reg, 0, 0));
ctx.inst(Inst::load_const(Operand::Reg(undef_reg as u32), undef_idx));
ctx.inst(Inst::new(OpCode::RETURN, Operand::Reg(undef_reg as u32), Operand::None, Operand::None));
}
} else {
let undef_idx = ctx.add_constant(Constant::Undefined);
let undef_reg = ctx.alloc_reg();
ctx.emit(opcode::encode(
OpCode::LOAD_CONST,
undef_reg,
(undef_idx & 0xFF) as u8,
((undef_idx >> 8) & 0xFF) as u8,
));
ctx.emit(opcode::encode(OpCode::RETURN, undef_reg, 0, 0));
ctx.inst(Inst::load_const(Operand::Reg(undef_reg as u32), undef_idx));
ctx.inst(Inst::new(OpCode::RETURN, Operand::Reg(undef_reg as u32), Operand::None, Operand::None));
}
if ctx.reg_overflow {
return Err("RangeError: function body uses too many registers (max 253)".into());
}
if ctx.const_overflow {
return Err("RangeError: too many constants".into());
}
if ctx.jump_overflow {
return Err("RangeError: jump offset out of range".into());
}
Ok(CompiledModule {
bytecode: ctx.bytecode,
let ir = IRFunction {
insts: ctx.insts,
label_pos: ctx.labels.label_pos,
label_count: ctx.labels.label_counter,
constants: ctx.constants,
n_registers: ctx.max_regs,
n_args: param_specs.len() as u8,
param_base,
builtin_reg_map: ctx.scopes.builtin_reg_map,
sub_modules: ctx.sub_modules,
is_arrow: false,
captured_this_const_idx: 0,
function_name: None,
is_class_constructor: false,
is_derived_constructor: false,
needs_home_object: false,
param_layout: crate::ir::ParamLayout {
base: param_base as u32,
count: param_specs.len() as u32,
}, builtin_reg_map: ctx.scopes.builtin_reg_map,
upvalue_captures: ctx.current_upvalue_captures.clone(),
cells_needed: ctx
.scopes
@@ -1568,7 +1392,18 @@ impl Compiler {
.flat_map(|s| s.bindings.values())
.filter(|b| b.is_captured.get())
.count() as u8,
})
n_registers: ctx.max_regs,
is_arrow: false,
is_class_constructor: false,
is_derived_constructor: false,
needs_home_object: false,
captured_this_const_idx: 0,
function_name: None,
reg_overflow: ctx.reg_overflow,
const_overflow: ctx.const_overflow,
nested: ctx.nested,
};
Ok(ir)
}
pub(crate) fn emit_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
@@ -1663,48 +1498,26 @@ impl Compiler {
None => last_result = None,
}
}
ctx.resolve_fixups()?;
crate::compiler_debug!("emitter: {} bytes emitted", ctx.bytecode.len());
if let Some(r) = last_result {
ctx.emit(opcode::encode(OpCode::LOAD_VAR, 0, r, 0));
ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::None, Operand::Reg(r as u32), Operand::None));
} else {
let undef_idx = ctx.add_constant(Constant::Undefined);
ctx.emit(opcode::encode(
OpCode::LOAD_CONST,
0,
(undef_idx & 0xFF) as u8,
((undef_idx >> 8) & 0xFF) as u8,
));
ctx.inst(Inst::load_const(Operand::None, undef_idx));
}
ctx.emit(opcode::encode(OpCode::HALT, 0, 0, 0));
ctx.inst(Inst::new(OpCode::HALT, Operand::None, Operand::None, Operand::None));
if ctx.reg_overflow {
return Err("RangeError: function body uses too many registers (max 253)".into());
}
if ctx.const_overflow {
return Err("RangeError: too many constants".into());
}
if ctx.jump_overflow {
return Err("RangeError: jump offset out of range".into());
}
crate::compiler_debug!("compile: done, {} instructions, {} constants", ctx.insts.len(), ctx.constants.len());
crate::compiler_debug!("compile: done, {} instructions, {} constants", ctx.bytecode.len(), ctx.constants.len());
Ok(CompiledModule {
bytecode: ctx.bytecode,
let ir = IRFunction {
insts: ctx.insts,
label_pos: ctx.labels.label_pos,
label_count: ctx.labels.label_counter,
constants: ctx.constants,
n_registers: ctx.max_regs,
n_args: 0,
param_base: ctx.scopes.builtin_reg_map.len() as u8,
param_layout: crate::ir::ParamLayout {
base: ctx.scopes.builtin_reg_map.len() as u32,
count: 0,
},
builtin_reg_map: ctx.scopes.builtin_reg_map,
sub_modules: ctx.sub_modules,
is_arrow: false,
captured_this_const_idx: 0,
function_name: None,
is_class_constructor: false,
is_derived_constructor: false,
needs_home_object: false,
upvalue_captures: ctx.current_upvalue_captures.clone(),
cells_needed: ctx
.scopes
@@ -1714,7 +1527,18 @@ impl Compiler {
.flat_map(|s| s.bindings.values())
.filter(|b| b.is_captured.get())
.count() as u8,
})
n_registers: ctx.max_regs,
is_arrow: false,
is_class_constructor: false,
is_derived_constructor: false,
needs_home_object: false,
captured_this_const_idx: 0,
function_name: None,
reg_overflow: ctx.reg_overflow,
const_overflow: ctx.const_overflow,
nested: ctx.nested,
};
lower(&ir)
}
}
@@ -1723,16 +1547,3 @@ impl Default for Compiler {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::CompileCtx;
#[test]
fn test_jump_offset_overflow_range_error() {
let mut ctx = CompileCtx::new();
let offset = ctx.checked_jump_offset(i16::MAX as isize + 1);
assert_eq!(offset, 0);
assert!(ctx.jump_overflow);
}
}