refactor(emit): 寄存器号 u8→u32 全链放宽,vreg 化去 checkpoint/reg_overflow(B005)

This commit is contained in:
爱特每羊队
2026-08-06 16:31:16 +08:00
parent c52bf6313e
commit 38d6c7a60e
54 changed files with 404 additions and 431 deletions
+32 -52
View File
@@ -140,9 +140,9 @@ pub struct CompileCtx {
pub(crate) insts: Vec<Inst>,
pub(crate) constants: Vec<Constant>,
constant_map: HashMap<ConstantKey, u16>,
next_reg: u8,
pub(crate) max_regs: u8,
reserved_reg_start: u8,
next_reg: u32,
pub(crate) max_regs: u32,
reserved_reg_start: u32,
pub(crate) labels: LabelCtx,
pub(crate) scopes: ScopeCtx,
pub(crate) nested: Vec<IRFunction>,
@@ -162,9 +162,6 @@ pub struct CompileCtx {
/// 捕获判断(MAKE_CELL / CELL_GET / CELL_SET)与子函数 upvalue cell_idx 统一查此映射,
/// 消除符号表时序依赖与 cell 索引错位。
pub(crate) captured_bindings: BTreeMap<String, u8>,
/// Set when alloc_reg() overflows into the reserved this/new.target range (≥254).
/// Checked after each emit phase to produce a compile error rather than silent corruption.
pub(crate) reg_overflow: bool,
pub(crate) const_overflow: bool,
}
@@ -240,7 +237,6 @@ impl CompileCtx {
current_upvalue_captures: Vec::new(),
own_bindings: HashSet::new(),
captured_bindings: BTreeMap::new(),
reg_overflow: false,
const_overflow: false,
}
}
@@ -249,17 +245,12 @@ impl CompileCtx {
self.insts.push(inst);
}
pub(crate) fn alloc_reg(&mut self) -> u8 {
pub(crate) fn alloc_reg(&mut self) -> u32 {
let r = self.next_reg;
// Registers 254 (this) and 255 (new.target) are reserved by the VM.
// Allocating into them silently corrupts the call convention, turning
// method calls' `this` into garbage. Clamp to 253 and set a flag so
// the compiler can surface a proper error after the emit pass.
if r >= 254 {
self.reg_overflow = true;
return 253; // clamp to last safe register; emit continues but reg_overflow triggers error
}
self.next_reg = self.next_reg.wrapping_add(1);
// vreg 化(D-02):寄存器号无上限,RegAlloc 阶段负责压缩到物理域(≤253)。
// 254/255 是 VM 保留的 this/new.target,vreg 世界允许虚拟号越过它们,
// 只有 RegAlloc 完成映射后 lower 的物理域检查才相关。
self.next_reg += 1;
if self.next_reg > self.max_regs {
self.max_regs = self.next_reg;
}
@@ -271,15 +262,7 @@ impl CompileCtx {
self.labels.label_counter = 0;
}
pub(crate) fn reg_checkpoint(&self) -> u8 {
self.next_reg
}
pub(crate) fn restore_reg_checkpoint(&mut self, checkpoint: u8) {
self.next_reg = checkpoint;
}
pub(crate) fn reserve_reg(&mut self, reg: u8) {
pub(crate) fn reserve_reg(&mut self, reg: u32) {
let next = reg.wrapping_add(1);
if self.next_reg <= reg {
self.next_reg = next;
@@ -323,13 +306,13 @@ impl CompileCtx {
}
pub(crate) fn declare(
&mut self, name: &str, reg: u8, kind: VariableDeclarationKind, is_const: bool,
&mut self, name: &str, reg: u32, kind: VariableDeclarationKind, is_const: bool,
) -> Result<(), String> {
self.scopes.symbols.declare(name, reg, kind, is_const)
}
pub(crate) fn declare_initialized(
&mut self, name: &str, reg: u8, kind: VariableDeclarationKind, is_const: bool,
&mut self, name: &str, reg: u32, kind: VariableDeclarationKind, is_const: bool,
) -> Result<(), String> {
self.scopes.symbols.declare_initialized(name, reg, kind, is_const)
}
@@ -338,11 +321,11 @@ impl CompileCtx {
self.scopes.symbols.push_scope_with_kind(kind);
}
pub(crate) fn lookup(&self, name: &str) -> Result<u8, String> {
pub(crate) fn lookup(&self, name: &str) -> Result<u32, String> {
self.scopes.symbols.lookup(name)
}
pub(crate) fn lookup_or_builtin(&mut self, name: &str) -> Result<u8, String> {
pub(crate) fn lookup_or_builtin(&mut self, name: &str) -> Result<u32, String> {
match self.scopes.symbols.lookup(name) {
Ok(reg) => Ok(reg),
Err(err) if Self::is_known_builtin(name) && err.contains("is not defined") => {
@@ -355,7 +338,7 @@ impl CompileCtx {
}
}
pub(crate) fn lookup_or_global(&mut self, name: &str) -> u8 {
pub(crate) fn lookup_or_global(&mut self, name: &str) -> u32 {
if let Some(reg) = self.scopes.symbols.lookup_any(name) {
return reg;
}
@@ -465,7 +448,7 @@ impl CompileCtx {
BUILTIN_GLOBALS.contains(&name)
}
fn builtin_reg_floor(&self) -> u8 {
fn builtin_reg_floor(&self) -> u32 {
self.scopes
.builtin_reg_map
.iter()
@@ -513,7 +496,6 @@ impl CompileCtx {
needs_home_object: false,
captured_this_const_idx: 0,
function_name: None,
reg_overflow: self.reg_overflow,
const_overflow: self.const_overflow,
nested: std::mem::take(&mut self.nested),
}
@@ -591,7 +573,7 @@ impl Emitter {
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,
is_expression_body: bool, extra_bindings: &[(&str, u32)], body_context: FunctionBodyContext,
) -> Result<IRFunction, String> {
self.compile_function_body_with_field_hooks(
param_specs,
@@ -610,14 +592,12 @@ impl Emitter {
/// (expressions, member objects, call args, class fields, etc.) so their
/// register slots are reserved *before* any temporary register is emitted.
///
/// Without this, single-pass emission lazily allocates builtin slots via
/// `lookup_or_builtin` at first use, which can collide with a temporary
/// register already reused by `restore_reg_checkpoint`. Pre-scanning moves
/// builtin slot allocation ahead of the temporary register pool.
/// vreg 化(D-01)后临时值不复用(独立 vreg),但 builtin 槽预注册仍
/// 保证分配序稳定:builtin 槽先于临时值池,nested 继承边界(B012)不受扰。
#[allow(clippy::too_many_arguments)]
pub(crate) fn compile_function_body_with_field_hooks<'a, E>(
&self, param_specs: &[ParamSpec<'a>], body_stmts: &[Statement<'a>], parent_ctx: &CompileCtx,
is_expression_body: bool, extra_bindings: &[(&str, u8)], body_context: FunctionBodyContext,
is_expression_body: bool, extra_bindings: &[(&str, u32)], body_context: FunctionBodyContext,
mut emit_fields: Option<E>, fields_after_super: bool,
) -> Result<IRFunction, String>
where
@@ -647,7 +627,7 @@ impl Emitter {
// Inherit parent's global scope entries so previously-declared function names
// are visible from within the body.
let mut inherited_reg_start = 1u8.max(ctx.builtin_reg_floor());
let mut inherited_reg_start = 1u32.max(ctx.builtin_reg_floor());
for (name, binding) in &parent_ctx.scopes.symbols.scopes[0].bindings {
ctx.scopes.symbols.scopes[0].bindings.insert(
name.clone(),
@@ -718,23 +698,23 @@ impl Emitter {
// statement body returns undefined.
if is_expression_body {
if let Some(reg) = last_result_reg {
ctx.inst(Inst::new(OpCode::RETURN, Operand::Reg(reg as u32), Operand::None, Operand::None));
ctx.inst(Inst::new(OpCode::RETURN, Operand::Reg(reg), Operand::None, Operand::None));
} else {
let undef_idx = ctx.add_constant(Constant::Undefined);
let undef_reg = ctx.alloc_reg();
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));
ctx.inst(Inst::load_const(Operand::Reg(undef_reg), undef_idx));
ctx.inst(Inst::new(OpCode::RETURN, Operand::Reg(undef_reg), Operand::None, Operand::None));
}
} else {
let undef_idx = ctx.add_constant(Constant::Undefined);
let undef_reg = ctx.alloc_reg();
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));
ctx.inst(Inst::load_const(Operand::Reg(undef_reg), undef_idx));
ctx.inst(Inst::new(OpCode::RETURN, Operand::Reg(undef_reg), Operand::None, Operand::None));
}
let ir = ctx.assemble_ir(
oxide_ir::ParamLayout {
base: param_base as u32,
base: param_base,
count: param_specs.len() as u32,
},
Some(parent_ctx),
@@ -746,7 +726,7 @@ impl Emitter {
fn emit_params_prologue<'a>(
&self, param_specs: &[ParamSpec<'a>], body_stmts: &[Statement<'a>], parent_ctx: &CompileCtx,
ctx: &mut CompileCtx, body_context: FunctionBodyContext,
) -> Result<u8, String> {
) -> Result<u32, String> {
ctx.push_scope_with_kind(ScopeKind::FunctionScope);
let param_base = ctx.next_reg;
@@ -779,7 +759,7 @@ impl Emitter {
}
/// 双 sub-pass emit body:先函数声明(hoisting),再其余语句。返回最后结果寄存器。
fn emit_body_stmts(&self, body_stmts: &[Statement], ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
fn emit_body_stmts(&self, body_stmts: &[Statement], ctx: &mut CompileCtx) -> Result<Option<u32>, String> {
let mut last_result_reg = None;
for stmt in body_stmts {
if matches!(stmt, Statement::FunctionDeclaration(_)) {
@@ -799,7 +779,7 @@ impl Emitter {
Ok(last_result_reg)
}
pub(crate) fn emit_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
pub(crate) fn emit_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u32>, String> {
match stmt {
Statement::ExpressionStatement(_) | Statement::ReturnStatement(_) | Statement::EmptyStatement(_) => {
self.emit_basic_domain(stmt, ctx)
@@ -823,7 +803,7 @@ impl Emitter {
}
}
pub(crate) fn emit_expression(&self, expr: &Expression, ctx: &mut CompileCtx) -> Result<u8, String> {
pub(crate) fn emit_expression(&self, expr: &Expression, ctx: &mut CompileCtx) -> Result<u32, String> {
match expr {
Expression::NumericLiteral(_)
| Expression::StringLiteral(_)
@@ -887,7 +867,7 @@ impl Emitter {
}
// Second sub-pass: emit all other statements
let mut last_result: Option<u8> = None;
let mut last_result: Option<u32> = None;
for stmt in &program.body {
if matches!(stmt, Statement::FunctionDeclaration(_)) {
continue; // Already emitted above
@@ -898,7 +878,7 @@ impl Emitter {
}
}
if let Some(r) = last_result {
ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::None, Operand::Reg(r as u32), Operand::None));
ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::None, Operand::Reg(r), Operand::None));
} else {
let undef_idx = ctx.add_constant(Constant::Undefined);
ctx.inst(Inst::load_const(Operand::None, undef_idx));