mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
feat: 搭建闭包捕获基础设施
This commit is contained in:
@@ -12,6 +12,13 @@ pub enum Constant {
|
|||||||
Undefined,
|
Undefined,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct UpvalueCapture {
|
||||||
|
pub name: String,
|
||||||
|
pub enclosing_reg: u8,
|
||||||
|
pub cell_idx: u8,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct CompiledModule {
|
pub struct CompiledModule {
|
||||||
pub bytecode: Vec<opcode::Instr>,
|
pub bytecode: Vec<opcode::Instr>,
|
||||||
pub constants: Vec<Constant>,
|
pub constants: Vec<Constant>,
|
||||||
@@ -37,6 +44,8 @@ pub struct CompiledModule {
|
|||||||
pub is_derived_constructor: bool,
|
pub is_derived_constructor: bool,
|
||||||
/// True for prototype methods whose function object needs a runtime home_object.
|
/// True for prototype methods whose function object needs a runtime home_object.
|
||||||
pub needs_home_object: bool,
|
pub needs_home_object: bool,
|
||||||
|
pub upvalue_captures: Vec<UpvalueCapture>,
|
||||||
|
pub cells_needed: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CompiledModule {
|
impl CompiledModule {
|
||||||
@@ -55,6 +64,8 @@ impl CompiledModule {
|
|||||||
is_class_constructor: false,
|
is_class_constructor: false,
|
||||||
is_derived_constructor: false,
|
is_derived_constructor: false,
|
||||||
needs_home_object: false,
|
needs_home_object: false,
|
||||||
|
upvalue_captures: Vec::new(),
|
||||||
|
cells_needed: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,6 +92,8 @@ impl Clone for CompiledModule {
|
|||||||
is_class_constructor: self.is_class_constructor,
|
is_class_constructor: self.is_class_constructor,
|
||||||
is_derived_constructor: self.is_derived_constructor,
|
is_derived_constructor: self.is_derived_constructor,
|
||||||
needs_home_object: self.needs_home_object,
|
needs_home_object: self.needs_home_object,
|
||||||
|
upvalue_captures: self.upvalue_captures.clone(),
|
||||||
|
cells_needed: self.cells_needed,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,6 +106,8 @@ impl fmt::Display for CompiledModule {
|
|||||||
writeln!(f, "; [{i}] = {c:?}")?;
|
writeln!(f, "; [{i}] = {c:?}")?;
|
||||||
}
|
}
|
||||||
writeln!(f)?;
|
writeln!(f)?;
|
||||||
|
writeln!(f, "; upvalue_captures: {:?}", self.upvalue_captures)?;
|
||||||
|
writeln!(f)?;
|
||||||
for (offset, &instr) in self.bytecode.iter().enumerate() {
|
for (offset, &instr) in self.bytecode.iter().enumerate() {
|
||||||
let op = opcode::opcode(instr);
|
let op = opcode::opcode(instr);
|
||||||
let rd = opcode::rd(instr);
|
let rd = opcode::rd(instr);
|
||||||
|
|||||||
@@ -109,7 +109,10 @@ define_opcodes! {
|
|||||||
DELETE_PROP_STATIC = 0x39 => "DELETE_PROP_STATIC",
|
DELETE_PROP_STATIC = 0x39 => "DELETE_PROP_STATIC",
|
||||||
DELETE_PROP_DYNAMIC = 0x3A => "DELETE_PROP_DYNAMIC",
|
DELETE_PROP_DYNAMIC = 0x3A => "DELETE_PROP_DYNAMIC",
|
||||||
INSTANCEOF = 0x3B => "INSTANCEOF",
|
INSTANCEOF = 0x3B => "INSTANCEOF",
|
||||||
|
MAKE_CELL = 0x3C => "MAKE_CELL",
|
||||||
|
CELL_GET = 0x3D => "CELL_GET",
|
||||||
REST_OBJECT = 0x3E => "REST_OBJECT",
|
REST_OBJECT = 0x3E => "REST_OBJECT",
|
||||||
|
CELL_SET = 0x3F => "CELL_SET",
|
||||||
|
|
||||||
// -- Variable (0x30-0x32) --
|
// -- Variable (0x30-0x32) --
|
||||||
LOAD_VAR = 0x30 => "LOAD_VAR",
|
LOAD_VAR = 0x30 => "LOAD_VAR",
|
||||||
@@ -126,8 +129,10 @@ define_opcodes! {
|
|||||||
SUPER_STATIC_GET_PROP = 0x46 => "SUPER_STATIC_GET_PROP",
|
SUPER_STATIC_GET_PROP = 0x46 => "SUPER_STATIC_GET_PROP",
|
||||||
SET_HOME_OBJECT = 0x47 => "SET_HOME_OBJECT",
|
SET_HOME_OBJECT = 0x47 => "SET_HOME_OBJECT",
|
||||||
DEFINE_ACCESSOR = 0x48 => "DEFINE_ACCESSOR",
|
DEFINE_ACCESSOR = 0x48 => "DEFINE_ACCESSOR",
|
||||||
|
LOAD_UPVALUE = 0x49 => "LOAD_UPVALUE",
|
||||||
CREATE_CLOSURE = 0x4A => "CREATE_CLOSURE",
|
CREATE_CLOSURE = 0x4A => "CREATE_CLOSURE",
|
||||||
CREATE_REGEXP = 0x4B => "CREATE_REGEXP",
|
CREATE_REGEXP = 0x4B => "CREATE_REGEXP",
|
||||||
|
STORE_UPVALUE = 0x4C => "STORE_UPVALUE",
|
||||||
|
|
||||||
// ── Object Property (0x50-0x5F) ──
|
// ── Object Property (0x50-0x5F) ──
|
||||||
IC_GET_PROP = 0x50 => "IC_GET_PROP",
|
IC_GET_PROP = 0x50 => "IC_GET_PROP",
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use std::collections::HashMap;
|
|||||||
use crate::emit_ctx::{LabelCtx, ScopeCtx};
|
use crate::emit_ctx::{LabelCtx, ScopeCtx};
|
||||||
use crate::symbol_table::{Binding, SymbolTable};
|
use crate::symbol_table::{Binding, SymbolTable};
|
||||||
use oxide_bytecode::module::CompiledModule;
|
use oxide_bytecode::module::CompiledModule;
|
||||||
|
use oxide_bytecode::module::UpvalueCapture;
|
||||||
use oxide_bytecode::opcode::{self, OpCode};
|
use oxide_bytecode::opcode::{self, OpCode};
|
||||||
|
|
||||||
pub use crate::hash::{compiled_module_hash, structural_hash};
|
pub use crate::hash::{compiled_module_hash, structural_hash};
|
||||||
@@ -164,6 +165,7 @@ pub(crate) struct CompileCtx {
|
|||||||
/// constructor's instance-field code, added at the super() call site during body
|
/// constructor's instance-field code, added at the super() call site during body
|
||||||
/// counting so projected_pc matches where the emit pass splices the field bytecode.
|
/// counting so projected_pc matches where the emit pass splices the field bytecode.
|
||||||
pub(crate) after_super_count_words: Option<usize>,
|
pub(crate) after_super_count_words: Option<usize>,
|
||||||
|
pub(crate) current_upvalue_captures: Vec<UpvalueCapture>,
|
||||||
/// Set when alloc_reg() overflows into the reserved this/new.target range (≥254).
|
/// 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.
|
/// Checked after each emit phase to produce a compile error rather than silent corruption.
|
||||||
pub(crate) reg_overflow: bool,
|
pub(crate) reg_overflow: bool,
|
||||||
@@ -227,6 +229,7 @@ impl CompileCtx {
|
|||||||
builtin_reg_map: Vec::new(),
|
builtin_reg_map: Vec::new(),
|
||||||
private_name_map: Vec::new(),
|
private_name_map: Vec::new(),
|
||||||
next_private_name_id: 1,
|
next_private_name_id: 1,
|
||||||
|
cell_registry: Vec::new(),
|
||||||
},
|
},
|
||||||
projected_pc: 0,
|
projected_pc: 0,
|
||||||
sub_modules: Vec::new(),
|
sub_modules: Vec::new(),
|
||||||
@@ -238,6 +241,7 @@ impl CompileCtx {
|
|||||||
after_super_insert: None,
|
after_super_insert: None,
|
||||||
after_super_inserted: false,
|
after_super_inserted: false,
|
||||||
after_super_count_words: None,
|
after_super_count_words: None,
|
||||||
|
current_upvalue_captures: Vec::new(),
|
||||||
reg_overflow: false,
|
reg_overflow: false,
|
||||||
const_overflow: false,
|
const_overflow: false,
|
||||||
jump_overflow: false,
|
jump_overflow: false,
|
||||||
@@ -587,6 +591,203 @@ impl Compiler {
|
|||||||
Self
|
Self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn analyze_upvalue_captures(
|
||||||
|
&self, body_stmts: &[Statement], parent_ctx: &CompileCtx, nested_symbols: &SymbolTable,
|
||||||
|
) -> (Vec<UpvalueCapture>, u8) {
|
||||||
|
let mut captures: Vec<UpvalueCapture> = Vec::new();
|
||||||
|
let mut seen: HashMap<String, usize> = HashMap::new();
|
||||||
|
|
||||||
|
for stmt in body_stmts {
|
||||||
|
self.collect_upvalue_stmt(stmt, parent_ctx, nested_symbols, &mut captures, &mut seen);
|
||||||
|
}
|
||||||
|
|
||||||
|
let count = captures.len() as u8;
|
||||||
|
(captures, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn collect_upvalue_stmt(
|
||||||
|
&self, stmt: &Statement, parent_ctx: &CompileCtx, nested_symbols: &SymbolTable,
|
||||||
|
captures: &mut Vec<UpvalueCapture>, seen: &mut HashMap<String, usize>,
|
||||||
|
) {
|
||||||
|
match stmt {
|
||||||
|
Statement::ExpressionStatement(es) => {
|
||||||
|
self.collect_upvalue_expr(&es.expression, parent_ctx, nested_symbols, captures, seen);
|
||||||
|
}
|
||||||
|
Statement::VariableDeclaration(vd) => {
|
||||||
|
for decl in &vd.declarations {
|
||||||
|
if let Some(init) = &decl.init {
|
||||||
|
self.collect_upvalue_expr(init, parent_ctx, nested_symbols, captures, seen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Statement::ReturnStatement(rs) => {
|
||||||
|
if let Some(expr) = &rs.argument {
|
||||||
|
self.collect_upvalue_expr(expr, parent_ctx, nested_symbols, captures, seen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Statement::ForStatement(fs) => {
|
||||||
|
if let Some(init) = &fs.init {
|
||||||
|
if let Some(e) = init.as_expression() {
|
||||||
|
self.collect_upvalue_expr(e, parent_ctx, nested_symbols, captures, seen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(test) = &fs.test {
|
||||||
|
self.collect_upvalue_expr(test, parent_ctx, nested_symbols, captures, seen);
|
||||||
|
}
|
||||||
|
if let Some(update) = &fs.update {
|
||||||
|
self.collect_upvalue_expr(update, parent_ctx, nested_symbols, captures, seen);
|
||||||
|
}
|
||||||
|
self.collect_upvalue_stmt(&fs.body, parent_ctx, nested_symbols, captures, seen);
|
||||||
|
}
|
||||||
|
Statement::IfStatement(is) => {
|
||||||
|
self.collect_upvalue_expr(&is.test, parent_ctx, nested_symbols, captures, seen);
|
||||||
|
self.collect_upvalue_stmt(&is.consequent, parent_ctx, nested_symbols, captures, seen);
|
||||||
|
if let Some(alt) = &is.alternate {
|
||||||
|
self.collect_upvalue_stmt(alt, parent_ctx, nested_symbols, captures, seen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Statement::WhileStatement(ws) => {
|
||||||
|
self.collect_upvalue_expr(&ws.test, parent_ctx, nested_symbols, captures, seen);
|
||||||
|
self.collect_upvalue_stmt(&ws.body, parent_ctx, nested_symbols, captures, seen);
|
||||||
|
}
|
||||||
|
Statement::DoWhileStatement(dw) => {
|
||||||
|
self.collect_upvalue_stmt(&dw.body, parent_ctx, nested_symbols, captures, seen);
|
||||||
|
self.collect_upvalue_expr(&dw.test, parent_ctx, nested_symbols, captures, seen);
|
||||||
|
}
|
||||||
|
Statement::BlockStatement(bs) => {
|
||||||
|
for s in &bs.body {
|
||||||
|
self.collect_upvalue_stmt(s, parent_ctx, nested_symbols, captures, seen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Statement::TryStatement(ts) => {
|
||||||
|
for s in &ts.block.body {
|
||||||
|
self.collect_upvalue_stmt(s, parent_ctx, nested_symbols, captures, seen);
|
||||||
|
}
|
||||||
|
if let Some(handler) = &ts.handler {
|
||||||
|
for s in &handler.body.body {
|
||||||
|
self.collect_upvalue_stmt(s, parent_ctx, nested_symbols, captures, seen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(finalizer) = &ts.finalizer {
|
||||||
|
for s in &finalizer.body {
|
||||||
|
self.collect_upvalue_stmt(s, parent_ctx, nested_symbols, captures, seen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Statement::ThrowStatement(ts) => {
|
||||||
|
self.collect_upvalue_expr(&ts.argument, parent_ctx, nested_symbols, captures, seen);
|
||||||
|
}
|
||||||
|
Statement::SwitchStatement(ss) => {
|
||||||
|
self.collect_upvalue_expr(&ss.discriminant, parent_ctx, nested_symbols, captures, seen);
|
||||||
|
for case in &ss.cases {
|
||||||
|
for s in &case.consequent {
|
||||||
|
self.collect_upvalue_stmt(s, parent_ctx, nested_symbols, captures, seen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn collect_upvalue_expr(
|
||||||
|
&self, expr: &Expression, parent_ctx: &CompileCtx, nested_symbols: &SymbolTable,
|
||||||
|
captures: &mut Vec<UpvalueCapture>, seen: &mut HashMap<String, usize>,
|
||||||
|
) {
|
||||||
|
match expr {
|
||||||
|
Expression::Identifier(ident) => {
|
||||||
|
let name = ident.name.as_str();
|
||||||
|
if nested_symbols.lookup_any_binding(name).is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if let Some((binding, _)) = parent_ctx.scopes.symbols.lookup_any_binding(name) {
|
||||||
|
if seen.contains_key(name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let cell_idx = captures.len() as u8;
|
||||||
|
seen.insert(name.to_string(), cell_idx as usize);
|
||||||
|
captures.push(UpvalueCapture {
|
||||||
|
name: name.to_string(),
|
||||||
|
enclosing_reg: binding.reg,
|
||||||
|
cell_idx,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expression::AssignmentExpression(ae) => {
|
||||||
|
if let oxide_parser::AssignmentTarget::AssignmentTargetIdentifier(ati) = &ae.left {
|
||||||
|
let name = ati.name.as_str();
|
||||||
|
if nested_symbols.lookup_any_binding(name).is_none() {
|
||||||
|
if let Some((binding, _)) = parent_ctx.scopes.symbols.lookup_any_binding(name) {
|
||||||
|
if !seen.contains_key(name) {
|
||||||
|
let cell_idx = captures.len() as u8;
|
||||||
|
seen.insert(name.to_string(), cell_idx as usize);
|
||||||
|
captures.push(UpvalueCapture {
|
||||||
|
name: name.to_string(),
|
||||||
|
enclosing_reg: binding.reg,
|
||||||
|
cell_idx,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.collect_upvalue_expr(&ae.right, parent_ctx, nested_symbols, captures, seen);
|
||||||
|
}
|
||||||
|
Expression::BinaryExpression(be) => {
|
||||||
|
self.collect_upvalue_expr(&be.left, parent_ctx, nested_symbols, captures, seen);
|
||||||
|
self.collect_upvalue_expr(&be.right, parent_ctx, nested_symbols, captures, seen);
|
||||||
|
}
|
||||||
|
Expression::UnaryExpression(ue) => {
|
||||||
|
self.collect_upvalue_expr(&ue.argument, parent_ctx, nested_symbols, captures, seen);
|
||||||
|
}
|
||||||
|
Expression::UpdateExpression(ue) => {
|
||||||
|
if let oxide_parser::SimpleAssignmentTarget::AssignmentTargetIdentifier(ati) = &ue.argument {
|
||||||
|
let name = ati.name.as_str();
|
||||||
|
if nested_symbols.lookup_any_binding(name).is_none() {
|
||||||
|
if let Some((binding, _)) = parent_ctx.scopes.symbols.lookup_any_binding(name) {
|
||||||
|
if !seen.contains_key(name) {
|
||||||
|
let cell_idx = captures.len() as u8;
|
||||||
|
seen.insert(name.to_string(), cell_idx as usize);
|
||||||
|
captures.push(UpvalueCapture {
|
||||||
|
name: name.to_string(),
|
||||||
|
enclosing_reg: binding.reg,
|
||||||
|
cell_idx,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expression::CallExpression(ce) => {
|
||||||
|
self.collect_upvalue_expr(&ce.callee, parent_ctx, nested_symbols, captures, seen);
|
||||||
|
for arg in &ce.arguments {
|
||||||
|
if let Some(e) = arg.as_expression() {
|
||||||
|
self.collect_upvalue_expr(e, parent_ctx, nested_symbols, captures, seen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expression::SequenceExpression(se) => {
|
||||||
|
for sub_expr in &se.expressions {
|
||||||
|
self.collect_upvalue_expr(sub_expr, parent_ctx, nested_symbols, captures, seen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expression::ConditionalExpression(ce) => {
|
||||||
|
self.collect_upvalue_expr(&ce.test, parent_ctx, nested_symbols, captures, seen);
|
||||||
|
self.collect_upvalue_expr(&ce.consequent, parent_ctx, nested_symbols, captures, seen);
|
||||||
|
self.collect_upvalue_expr(&ce.alternate, parent_ctx, nested_symbols, captures, seen);
|
||||||
|
}
|
||||||
|
Expression::ArrayExpression(ae) => {
|
||||||
|
for elem in &ae.elements {
|
||||||
|
if let Some(e) = elem.as_expression() {
|
||||||
|
self.collect_upvalue_expr(e, parent_ctx, nested_symbols, captures, seen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn extract_function_parts<'a>(
|
pub(crate) fn extract_function_parts<'a>(
|
||||||
&self, function: &'a oxide_parser::Function<'a>,
|
&self, function: &'a oxide_parser::Function<'a>,
|
||||||
) -> Result<(Vec<ParamSpec<'a>>, &'a [Statement<'a>]), String> {
|
) -> Result<(Vec<ParamSpec<'a>>, &'a [Statement<'a>]), String> {
|
||||||
@@ -693,6 +894,7 @@ impl Compiler {
|
|||||||
reg: binding.reg,
|
reg: binding.reg,
|
||||||
initialized: binding.initialized,
|
initialized: binding.initialized,
|
||||||
is_const: binding.is_const,
|
is_const: binding.is_const,
|
||||||
|
is_captured: binding.is_captured,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
inherited_reg_start = inherited_reg_start.max(binding.reg.saturating_add(1));
|
inherited_reg_start = inherited_reg_start.max(binding.reg.saturating_add(1));
|
||||||
@@ -704,6 +906,7 @@ impl Compiler {
|
|||||||
reg: *reg,
|
reg: *reg,
|
||||||
initialized: true,
|
initialized: true,
|
||||||
is_const: true,
|
is_const: true,
|
||||||
|
is_captured: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
inherited_reg_start = inherited_reg_start.max(reg.saturating_add(1));
|
inherited_reg_start = inherited_reg_start.max(reg.saturating_add(1));
|
||||||
@@ -714,6 +917,23 @@ impl Compiler {
|
|||||||
// same register offset (params go after builtin slots).
|
// same register offset (params go after builtin slots).
|
||||||
ctx.reset_regs();
|
ctx.reset_regs();
|
||||||
|
|
||||||
|
// Free variable analysis for upvalue capture (Ordinary + Arrow functions only)
|
||||||
|
// ponytail: temporarily disabled to isolate closure bug
|
||||||
|
/*
|
||||||
|
if matches!(body_context, FunctionBodyContext::Ordinary | FunctionBodyContext::Arrow) {
|
||||||
|
let (captures, _cells) = self.analyze_upvalue_captures(body_stmts, parent_ctx, &ctx.scopes.symbols);
|
||||||
|
ctx.current_upvalue_captures = captures;
|
||||||
|
ctx.scopes.cell_registry = ctx.current_upvalue_captures.iter()
|
||||||
|
.map(|u| (u.name.clone(), u.cell_idx))
|
||||||
|
.collect();
|
||||||
|
for up in &ctx.current_upvalue_captures {
|
||||||
|
if let Some((binding, _)) = parent_ctx.scopes.symbols.lookup_any_binding(&up.name) {
|
||||||
|
binding.is_captured.set(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
// Function body scope - params and local vars
|
// Function body scope - params and local vars
|
||||||
ctx.push_scope_with_kind(ScopeKind::FunctionScope);
|
ctx.push_scope_with_kind(ScopeKind::FunctionScope);
|
||||||
|
|
||||||
@@ -858,6 +1078,8 @@ impl Compiler {
|
|||||||
is_class_constructor: false,
|
is_class_constructor: false,
|
||||||
is_derived_constructor: false,
|
is_derived_constructor: false,
|
||||||
needs_home_object: false,
|
needs_home_object: false,
|
||||||
|
upvalue_captures: Vec::new(),
|
||||||
|
cells_needed: 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -956,6 +1178,8 @@ impl Compiler {
|
|||||||
is_class_constructor: false,
|
is_class_constructor: false,
|
||||||
is_derived_constructor: false,
|
is_derived_constructor: false,
|
||||||
needs_home_object: false,
|
needs_home_object: false,
|
||||||
|
upvalue_captures: Vec::new(),
|
||||||
|
cells_needed: 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,13 +32,21 @@ impl Compiler {
|
|||||||
self.count_expression(&assign.right, ctx);
|
self.count_expression(&assign.right, ctx);
|
||||||
ctx.count_load_const(); // private id
|
ctx.count_load_const(); // private id
|
||||||
ctx.count_instr(); // SET_PRIVATE
|
ctx.count_instr(); // SET_PRIVATE
|
||||||
} else if let oxide_parser::AssignmentTarget::AssignmentTargetIdentifier(_) = &assign.left {
|
} else if let oxide_parser::AssignmentTarget::AssignmentTargetIdentifier(ati) = &assign.left {
|
||||||
|
let name = ati.name.as_str();
|
||||||
|
// Check if target is an upvalue or captured cell
|
||||||
|
let is_upvalue = ctx.current_upvalue_captures.iter().any(|u| u.name == name);
|
||||||
|
let is_captured = ctx.scopes.symbols.lookup_is_captured(name);
|
||||||
if let Some(logical_op) = assign.operator.to_logical_operator() {
|
if let Some(logical_op) = assign.operator.to_logical_operator() {
|
||||||
self.count_identifier_logical_assignment(logical_op, &assign.right, ctx);
|
self.count_identifier_logical_assignment(logical_op, &assign.right, ctx);
|
||||||
} else {
|
} else {
|
||||||
self.count_expression(&assign.right, ctx);
|
self.count_expression(&assign.right, ctx);
|
||||||
if assign.operator != AssignmentOperator::Assign {
|
if assign.operator != AssignmentOperator::Assign {
|
||||||
ctx.projected_pc += 1; // COMPOUND_* on var
|
ctx.projected_pc += 1; // COMPOUND_* on var
|
||||||
|
} else if is_upvalue {
|
||||||
|
ctx.count_instr(); // STORE_UPVALUE
|
||||||
|
} else if is_captured {
|
||||||
|
ctx.count_instr(); // CELL_SET
|
||||||
} else {
|
} else {
|
||||||
ctx.alloc_reg();
|
ctx.alloc_reg();
|
||||||
ctx.projected_pc += 1; // STORE_VAR
|
ctx.projected_pc += 1; // STORE_VAR
|
||||||
|
|||||||
@@ -21,8 +21,26 @@ impl Compiler {
|
|||||||
let Expression::Identifier(ident) = expr else {
|
let Expression::Identifier(ident) = expr else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if CompileCtx::is_known_builtin(ident.name.as_str()) {
|
let name = ident.name.as_str();
|
||||||
let _ = ctx.lookup_or_builtin(ident.name.as_str());
|
|
||||||
|
// Check if this is an upvalue reference (nested closure)
|
||||||
|
for up in &ctx.current_upvalue_captures {
|
||||||
|
if up.name == name {
|
||||||
|
ctx.alloc_reg();
|
||||||
|
ctx.count_instr(); // LOAD_UPVALUE
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the owner's binding is captured (cell access)
|
||||||
|
if ctx.scopes.symbols.lookup_is_captured(name) {
|
||||||
|
ctx.alloc_reg();
|
||||||
|
ctx.count_instr(); // CELL_GET
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if CompileCtx::is_known_builtin(name) {
|
||||||
|
let _ = ctx.lookup_or_builtin(name);
|
||||||
}
|
}
|
||||||
ctx.alloc_reg();
|
ctx.alloc_reg();
|
||||||
ctx.projected_pc += 1; // LOAD_VAR
|
ctx.projected_pc += 1; // LOAD_VAR
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ pub(crate) struct ScopeCtx {
|
|||||||
pub(crate) builtin_reg_map: Vec<(String, u8)>,
|
pub(crate) builtin_reg_map: Vec<(String, u8)>,
|
||||||
pub(crate) private_name_map: Vec<(String, u32)>,
|
pub(crate) private_name_map: Vec<(String, u32)>,
|
||||||
pub(crate) next_private_name_id: u32,
|
pub(crate) next_private_name_id: u32,
|
||||||
|
pub(crate) cell_registry: Vec<(String, u8)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reserved for destructuring-pattern state (Phase 15). Intentionally empty
|
/// Reserved for destructuring-pattern state (Phase 15). Intentionally empty
|
||||||
|
|||||||
@@ -142,6 +142,20 @@ impl Compiler {
|
|||||||
} else {
|
} else {
|
||||||
let val_reg = self.emit_expression(&assign.right, ctx)?;
|
let val_reg = self.emit_expression(&assign.right, ctx)?;
|
||||||
let name = id_ref.name.as_str();
|
let name = id_ref.name.as_str();
|
||||||
|
// Check if target is an upvalue reference
|
||||||
|
if let Some(uv_idx) = ctx.current_upvalue_captures.iter().position(|u| u.name == name) {
|
||||||
|
ctx.emit(opcode::encode(OpCode::STORE_UPVALUE, 0, val_reg, uv_idx as u8));
|
||||||
|
return Ok(val_reg);
|
||||||
|
}
|
||||||
|
// Check if target is a captured cell
|
||||||
|
if ctx.scopes.symbols.lookup_is_captured(name) {
|
||||||
|
let cell_idx = ctx.scopes.cell_registry.iter()
|
||||||
|
.find(|(n, _)| n == name)
|
||||||
|
.map(|(_, idx)| *idx)
|
||||||
|
.unwrap_or(0);
|
||||||
|
ctx.emit(opcode::encode(OpCode::CELL_SET, 0, val_reg, cell_idx));
|
||||||
|
return Ok(val_reg);
|
||||||
|
}
|
||||||
let var_reg = ctx.lookup_or_global(name);
|
let var_reg = ctx.lookup_or_global(name);
|
||||||
let is_const = ctx.lookup_const_flag(name);
|
let is_const = ctx.lookup_const_flag(name);
|
||||||
let const_flag = if is_const { 1 } else { 0 };
|
let const_flag = if is_const { 1 } else { 0 };
|
||||||
|
|||||||
@@ -56,7 +56,34 @@ impl Compiler {
|
|||||||
pub(in crate::emitter) fn emit_identifier_expression(
|
pub(in crate::emitter) fn emit_identifier_expression(
|
||||||
&self, ident: &oxide_parser::IdentifierReference, ctx: &mut CompileCtx,
|
&self, ident: &oxide_parser::IdentifierReference, ctx: &mut CompileCtx,
|
||||||
) -> Result<u8, String> {
|
) -> Result<u8, String> {
|
||||||
let var_reg = ctx.lookup_or_builtin(ident.name.as_str())?;
|
let name = ident.name.as_str();
|
||||||
|
|
||||||
|
// Check if this is an upvalue reference (nested closure)
|
||||||
|
for (uv_idx, up) in ctx.current_upvalue_captures.iter().enumerate() {
|
||||||
|
if up.name == name {
|
||||||
|
let r = ctx.alloc_reg();
|
||||||
|
let idx = uv_idx as u8;
|
||||||
|
ctx.emit(opcode::encode(OpCode::LOAD_UPVALUE, r, idx, 0));
|
||||||
|
return Ok(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the owner's binding is captured (cell access)
|
||||||
|
if ctx.scopes.symbols.lookup_is_captured(name) {
|
||||||
|
let cell_idx = ctx.scopes.cell_registry.iter()
|
||||||
|
.find(|(n, _)| n == name)
|
||||||
|
.map(|(_, idx)| *idx)
|
||||||
|
.unwrap_or(0);
|
||||||
|
let r = ctx.alloc_reg();
|
||||||
|
if let Some((binding, _)) = ctx.scopes.symbols.lookup_any_binding(name) {
|
||||||
|
ctx.emit(opcode::encode(OpCode::CELL_GET, r, binding.reg, cell_idx));
|
||||||
|
} else {
|
||||||
|
ctx.emit(opcode::encode(OpCode::CELL_GET, r, 0, cell_idx));
|
||||||
|
}
|
||||||
|
return Ok(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
let var_reg = ctx.lookup_or_builtin(name)?;
|
||||||
let r = ctx.alloc_reg();
|
let r = ctx.alloc_reg();
|
||||||
ctx.emit(opcode::encode(OpCode::LOAD_VAR, r, var_reg, 0));
|
ctx.emit(opcode::encode(OpCode::LOAD_VAR, r, var_reg, 0));
|
||||||
Ok(r)
|
Ok(r)
|
||||||
|
|||||||
@@ -33,7 +33,14 @@ impl Compiler {
|
|||||||
ctx.emit_load_const(tmp, idx);
|
ctx.emit_load_const(tmp, idx);
|
||||||
let var_reg = ctx.alloc_reg();
|
let var_reg = ctx.alloc_reg();
|
||||||
ctx.declare(bi.name.as_str(), var_reg, decl.kind, is_const)?;
|
ctx.declare(bi.name.as_str(), var_reg, decl.kind, is_const)?;
|
||||||
ctx.emit(opcode::encode(OpCode::STORE_VAR, var_reg, tmp, 0));
|
let is_captured = ctx.scopes.symbols.lookup_is_captured(bi.name.as_str());
|
||||||
|
if is_captured {
|
||||||
|
let cell_idx = ctx.scopes.cell_registry.len() as u8;
|
||||||
|
ctx.scopes.cell_registry.push((bi.name.to_string(), cell_idx));
|
||||||
|
ctx.emit(opcode::encode(OpCode::MAKE_CELL, var_reg, cell_idx, 0));
|
||||||
|
} else {
|
||||||
|
ctx.emit(opcode::encode(OpCode::STORE_VAR, var_reg, tmp, 0));
|
||||||
|
}
|
||||||
ctx.init_var(bi.name.as_str());
|
ctx.init_var(bi.name.as_str());
|
||||||
r = Some(var_reg);
|
r = Some(var_reg);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ pub(crate) struct Binding {
|
|||||||
pub(crate) reg: u8,
|
pub(crate) reg: u8,
|
||||||
pub(crate) initialized: bool,
|
pub(crate) initialized: bool,
|
||||||
pub(crate) is_const: bool,
|
pub(crate) is_const: bool,
|
||||||
|
pub(crate) is_captured: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SymbolTable {
|
pub struct SymbolTable {
|
||||||
@@ -84,6 +85,7 @@ impl SymbolTable {
|
|||||||
reg,
|
reg,
|
||||||
initialized: false,
|
initialized: false,
|
||||||
is_const: matches!(kind, VariableDeclarationKind::Const) || is_const,
|
is_const: matches!(kind, VariableDeclarationKind::Const) || is_const,
|
||||||
|
is_captured: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -101,13 +103,22 @@ impl SymbolTable {
|
|||||||
Err(format!("Identifier '{name}' is not defined"))
|
Err(format!("Identifier '{name}' is not defined"))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lookup_any(&self, name: &str) -> Option<u8> {
|
pub(crate) fn lookup_any(&self, name: &str) -> Option<u8> {
|
||||||
self.scopes
|
self.scopes
|
||||||
.iter()
|
.iter()
|
||||||
.rev()
|
.rev()
|
||||||
.find_map(|scope| scope.bindings.get(name).map(|binding| binding.reg))
|
.find_map(|scope| scope.bindings.get(name).map(|binding| binding.reg))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn lookup_any_binding(&self, name: &str) -> Option<(&Binding, usize)> {
|
||||||
|
for (i, scope) in self.scopes.iter().enumerate().rev() {
|
||||||
|
if let Some(b) = scope.bindings.get(name) {
|
||||||
|
return Some((b, i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
pub fn lookup_or_global(&mut self, name: &str, reg_for_new: u8) -> u8 {
|
pub fn lookup_or_global(&mut self, name: &str, reg_for_new: u8) -> u8 {
|
||||||
for scope in self.scopes.iter().rev() {
|
for scope in self.scopes.iter().rev() {
|
||||||
if let Some(b) = scope.bindings.get(name) {
|
if let Some(b) = scope.bindings.get(name) {
|
||||||
@@ -120,6 +131,7 @@ impl SymbolTable {
|
|||||||
reg: reg_for_new,
|
reg: reg_for_new,
|
||||||
initialized: true,
|
initialized: true,
|
||||||
is_const: false,
|
is_const: false,
|
||||||
|
is_captured: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
reg_for_new
|
reg_for_new
|
||||||
@@ -137,6 +149,15 @@ impl SymbolTable {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn lookup_is_captured(&self, name: &str) -> bool {
|
||||||
|
for scope in self.scopes.iter().rev() {
|
||||||
|
if let Some(b) = scope.bindings.get(name) {
|
||||||
|
return b.is_captured;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
pub fn declare_initialized(
|
pub fn declare_initialized(
|
||||||
&mut self, name: &str, reg: u8, kind: VariableDeclarationKind, is_const: bool,
|
&mut self, name: &str, reg: u8, kind: VariableDeclarationKind, is_const: bool,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
@@ -157,6 +178,7 @@ impl SymbolTable {
|
|||||||
reg,
|
reg,
|
||||||
initialized: true,
|
initialized: true,
|
||||||
is_const: matches!(kind, VariableDeclarationKind::Const) || is_const,
|
is_const: matches!(kind, VariableDeclarationKind::Const) || is_const,
|
||||||
|
is_captured: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -167,6 +189,7 @@ impl SymbolTable {
|
|||||||
reg,
|
reg,
|
||||||
initialized: true,
|
initialized: true,
|
||||||
is_const: false,
|
is_const: false,
|
||||||
|
is_captured: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -230,6 +230,54 @@ impl PropIndex for i32 {
|
|||||||
/// sub_module_index: u32 (4 bytes + 4 pad, index into CompiledModule.sub_modules)
|
/// sub_module_index: u32 (4 bytes + 4 pad, index into CompiledModule.sub_modules)
|
||||||
/// captured_this: JsValue (8 bytes, lexical this for arrow functions)
|
/// captured_this: JsValue (8 bytes, lexical this for arrow functions)
|
||||||
/// home_object: JsValue (8 bytes, [[HomeObject]] for super lookup)
|
/// home_object: JsValue (8 bytes, [[HomeObject]] for super lookup)
|
||||||
|
/// upvalues: *mut u8 (8 bytes, points to Box<Vec<*mut Cell>> for closures)
|
||||||
|
///
|
||||||
|
/// Total: 112 bytes
|
||||||
|
/// Alignment: 8 bytes
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct Cell {
|
||||||
|
pub value: JsValue,
|
||||||
|
pub flags: u8,
|
||||||
|
pub _pad: [u8; 7],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cell {
|
||||||
|
pub const INITIALIZED: u8 = 0x01;
|
||||||
|
pub const GC_MARK: u8 = 0x02;
|
||||||
|
|
||||||
|
pub fn new(value: JsValue, initialized: bool) -> Self {
|
||||||
|
Cell {
|
||||||
|
value,
|
||||||
|
flags: if initialized { Self::INITIALIZED } else { 0 },
|
||||||
|
_pad: [0; 7],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_initialized(&self) -> bool {
|
||||||
|
self.flags & Self::INITIALIZED != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_initialized(&mut self, val: bool) {
|
||||||
|
if val {
|
||||||
|
self.flags |= Self::INITIALIZED;
|
||||||
|
} else {
|
||||||
|
self.flags &= !Self::INITIALIZED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_gc_marked(&self) -> bool {
|
||||||
|
self.flags & Self::GC_MARK != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_gc_mark(&mut self, marked: bool) {
|
||||||
|
if marked {
|
||||||
|
self.flags |= Self::GC_MARK;
|
||||||
|
} else {
|
||||||
|
self.flags &= !Self::GC_MARK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct JsObject {
|
pub struct JsObject {
|
||||||
header: u32,
|
header: u32,
|
||||||
native_arg_count: u8,
|
native_arg_count: u8,
|
||||||
@@ -247,6 +295,7 @@ pub struct JsObject {
|
|||||||
_pad3: [u8; 4],
|
_pad3: [u8; 4],
|
||||||
captured_this: JsValue,
|
captured_this: JsValue,
|
||||||
home_object: JsValue,
|
home_object: JsValue,
|
||||||
|
pub upvalues: *mut u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JsObject {
|
impl JsObject {
|
||||||
@@ -313,6 +362,7 @@ impl JsObject {
|
|||||||
_pad3: [0; 4],
|
_pad3: [0; 4],
|
||||||
captured_this: JsValue::undefined(),
|
captured_this: JsValue::undefined(),
|
||||||
home_object: JsValue::undefined(),
|
home_object: JsValue::undefined(),
|
||||||
|
upvalues: std::ptr::null_mut(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,6 +384,7 @@ impl JsObject {
|
|||||||
_pad3: [0; 4],
|
_pad3: [0; 4],
|
||||||
captured_this: JsValue::undefined(),
|
captured_this: JsValue::undefined(),
|
||||||
home_object: JsValue::undefined(),
|
home_object: JsValue::undefined(),
|
||||||
|
upvalues: std::ptr::null_mut(),
|
||||||
};
|
};
|
||||||
let vec = Box::new(vec![JsValue::undefined(); n_elements.min(MAX_DENSE_PROPS)]);
|
let vec = Box::new(vec![JsValue::undefined(); n_elements.min(MAX_DENSE_PROPS)]);
|
||||||
obj.hash_props = Box::into_raw(vec) as *mut u8;
|
obj.hash_props = Box::into_raw(vec) as *mut u8;
|
||||||
@@ -395,6 +446,7 @@ impl JsObject {
|
|||||||
_pad3: self._pad3,
|
_pad3: self._pad3,
|
||||||
captured_this: self.captured_this,
|
captured_this: self.captured_this,
|
||||||
home_object: self.home_object,
|
home_object: self.home_object,
|
||||||
|
upvalues: self.upvalues,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -414,6 +466,29 @@ impl JsObject {
|
|||||||
self.native_data = ptr;
|
self.native_data = ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn upvalues_slice(&self) -> &[*mut Cell] {
|
||||||
|
if self.upvalues.is_null() {
|
||||||
|
&[]
|
||||||
|
} else {
|
||||||
|
unsafe { &*(self.upvalues as *const Vec<*mut Cell>) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn upvalues_slice_mut(&mut self) -> &mut [*mut Cell] {
|
||||||
|
if self.upvalues.is_null() {
|
||||||
|
&mut []
|
||||||
|
} else {
|
||||||
|
unsafe { &mut *(self.upvalues as *mut Vec<*mut Cell>) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_upvalues(&mut self, v: Box<Vec<*mut Cell>>) {
|
||||||
|
if !self.upvalues.is_null() {
|
||||||
|
unsafe { drop(Box::from_raw(self.upvalues as *mut Vec<*mut Cell>)); }
|
||||||
|
}
|
||||||
|
self.upvalues = Box::into_raw(v) as *mut u8;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn rewrite_object_values<F>(&mut self, mut rewrite: F)
|
pub fn rewrite_object_values<F>(&mut self, mut rewrite: F)
|
||||||
where
|
where
|
||||||
F: FnMut(JsValue) -> JsValue,
|
F: FnMut(JsValue) -> JsValue,
|
||||||
@@ -444,6 +519,15 @@ impl JsObject {
|
|||||||
if self.home_object.is_object() {
|
if self.home_object.is_object() {
|
||||||
self.home_object = rewrite(self.home_object);
|
self.home_object = rewrite(self.home_object);
|
||||||
}
|
}
|
||||||
|
if !self.upvalues.is_null() {
|
||||||
|
let cells = unsafe { &mut *(self.upvalues as *mut Vec<*mut Cell>) };
|
||||||
|
for cell_ptr in cells {
|
||||||
|
let cell = unsafe { &mut **cell_ptr };
|
||||||
|
if cell.value.is_object() {
|
||||||
|
cell.value = rewrite(cell.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn shape_id(&self) -> ShapeId {
|
pub fn shape_id(&self) -> ShapeId {
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
use oxide_types::object::Cell;
|
||||||
|
use oxide_types::value::JsValue;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cell_layout_size_check() {
|
||||||
|
assert_eq!(std::mem::size_of::<Cell>(), 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cell_initialized_flag() {
|
||||||
|
let c = Cell::new(JsValue::int(42), true);
|
||||||
|
assert!(c.is_initialized());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cell_uninitialized_flag() {
|
||||||
|
let c = Cell::new(JsValue::undefined(), false);
|
||||||
|
assert!(!c.is_initialized());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cell_set_initialized_toggle() {
|
||||||
|
let mut c = Cell::new(JsValue::int(1), false);
|
||||||
|
assert!(!c.is_initialized());
|
||||||
|
c.set_initialized(true);
|
||||||
|
assert!(c.is_initialized());
|
||||||
|
c.set_initialized(false);
|
||||||
|
assert!(!c.is_initialized());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cell_gc_mark_flag() {
|
||||||
|
let mut c = Cell::new(JsValue::undefined(), false);
|
||||||
|
assert!(!c.is_gc_marked());
|
||||||
|
c.set_gc_mark(true);
|
||||||
|
assert!(c.is_gc_marked());
|
||||||
|
c.set_gc_mark(false);
|
||||||
|
assert!(!c.is_gc_marked());
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ use crate::{vm_debug, vm_trace};
|
|||||||
use oxide_builtins::{builtins_debug, builtins_trace};
|
use oxide_builtins::{builtins_debug, builtins_trace};
|
||||||
use oxide_bytecode::opcode;
|
use oxide_bytecode::opcode;
|
||||||
use oxide_runtime_api::NativeResult;
|
use oxide_runtime_api::NativeResult;
|
||||||
use oxide_types::object::JsObject;
|
use oxide_types::object::{Cell, JsObject};
|
||||||
use oxide_types::value::JsValue;
|
use oxide_types::value::JsValue;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@@ -89,16 +89,121 @@ impl Vm {
|
|||||||
vm_trace!("CREATE_CLOSURE rd={} sub_idx={}", rd, sub_idx);
|
vm_trace!("CREATE_CLOSURE rd={} sub_idx={}", rd, sub_idx);
|
||||||
debug_assert!(sub_idx > 0 && (sub_idx as usize) <= self.sub_modules.len());
|
debug_assert!(sub_idx > 0 && (sub_idx as usize) <= self.sub_modules.len());
|
||||||
let sub = &self.sub_modules[sub_idx as usize - 1];
|
let sub = &self.sub_modules[sub_idx as usize - 1];
|
||||||
|
let is_arrow = sub.is_arrow;
|
||||||
|
let is_class_constructor = sub.is_class_constructor;
|
||||||
|
let is_derived_constructor = sub.is_derived_constructor;
|
||||||
|
let needs_home_object = sub.needs_home_object;
|
||||||
|
let upvalue_captures = sub.upvalue_captures.clone();
|
||||||
|
let cells_needed = sub.cells_needed;
|
||||||
let result = self.create_function_object(
|
let result = self.create_function_object(
|
||||||
sub_idx,
|
sub_idx,
|
||||||
sub.is_arrow,
|
is_arrow,
|
||||||
sub.is_class_constructor,
|
is_class_constructor,
|
||||||
sub.is_derived_constructor,
|
is_derived_constructor,
|
||||||
sub.needs_home_object,
|
needs_home_object,
|
||||||
);
|
);
|
||||||
|
if !upvalue_captures.is_empty() && cells_needed > 0 {
|
||||||
|
if let Some(current_cells) = self.cell_stack.last() {
|
||||||
|
let mut upvals = Box::new(Vec::with_capacity(upvalue_captures.len()));
|
||||||
|
for capture in &upvalue_captures {
|
||||||
|
let cell_ptr = if (capture.cell_idx as usize) < current_cells.len() {
|
||||||
|
current_cells[capture.cell_idx as usize]
|
||||||
|
} else {
|
||||||
|
std::ptr::null_mut()
|
||||||
|
};
|
||||||
|
upvals.push(cell_ptr);
|
||||||
|
}
|
||||||
|
let func_obj = unsafe { &mut *result.as_js_object_ptr() };
|
||||||
|
func_obj.set_upvalues(upvals);
|
||||||
|
}
|
||||||
|
}
|
||||||
self.regs[rd] = result;
|
self.regs[rd] = result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn dispatch_make_cell(&mut self, rd: usize, instr: u32) -> Result<(), String> {
|
||||||
|
let cell_idx = opcode::imm16(instr) as usize;
|
||||||
|
let value = self.regs[rd];
|
||||||
|
let cell = self.gc_state.session_epoch.alloc(Cell::new(value, false));
|
||||||
|
let cell_ptr = cell as *mut Cell;
|
||||||
|
let current = self.cell_stack.last_mut().unwrap();
|
||||||
|
while current.len() <= cell_idx {
|
||||||
|
current.push(std::ptr::null_mut());
|
||||||
|
}
|
||||||
|
current[cell_idx] = cell_ptr;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn dispatch_cell_get(&mut self, rd: usize, _a: usize, b: usize) -> Result<(), String> {
|
||||||
|
let cell_idx = b;
|
||||||
|
let current = self.cell_stack.last().unwrap();
|
||||||
|
if cell_idx >= current.len() {
|
||||||
|
self.regs[rd] = JsValue::undefined();
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let cell_ptr = current[cell_idx];
|
||||||
|
if cell_ptr.is_null() {
|
||||||
|
self.regs[rd] = JsValue::undefined();
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
self.regs[rd] = unsafe { (*cell_ptr).value };
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn dispatch_cell_set(&mut self, a: usize, b: usize) -> Result<(), String> {
|
||||||
|
let cell_idx = b;
|
||||||
|
let src_val = self.regs[a];
|
||||||
|
let current = self.cell_stack.last_mut().unwrap();
|
||||||
|
if cell_idx >= current.len() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let cell_ptr = current[cell_idx];
|
||||||
|
if cell_ptr.is_null() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
unsafe { (*cell_ptr).value = src_val; }
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn dispatch_load_upvalue(&mut self, rd: usize, instr: u32) -> Result<(), String> {
|
||||||
|
let uv_idx = opcode::imm16(instr) as usize;
|
||||||
|
let callee = self.frames.last().unwrap().callee;
|
||||||
|
if callee.is_object() {
|
||||||
|
let obj = unsafe { &*callee.as_js_object_ptr() };
|
||||||
|
let upvals = obj.upvalues_slice();
|
||||||
|
if uv_idx < upvals.len() {
|
||||||
|
let cell = upvals[uv_idx];
|
||||||
|
if !cell.is_null() {
|
||||||
|
self.regs[rd] = unsafe { (*cell).value };
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.regs[rd] = JsValue::undefined();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn dispatch_store_upvalue(&mut self, a: usize, b: usize) -> Result<(), String> {
|
||||||
|
let uv_idx = b;
|
||||||
|
let src_val = self.regs[a];
|
||||||
|
let callee = self.frames.last().unwrap().callee;
|
||||||
|
if callee.is_object() {
|
||||||
|
let obj = unsafe { &*callee.as_js_object_ptr() };
|
||||||
|
let upvals = obj.upvalues_slice();
|
||||||
|
if uv_idx < upvals.len() {
|
||||||
|
let cell = upvals[uv_idx];
|
||||||
|
if !cell.is_null() {
|
||||||
|
unsafe { (*cell).value = src_val; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn dispatch_create_regexp(&mut self, rd: usize, a: usize, b: usize) -> Result<Option<JsValue>, String> {
|
pub(crate) fn dispatch_create_regexp(&mut self, rd: usize, a: usize, b: usize) -> Result<Option<JsValue>, String> {
|
||||||
vm_trace!("CREATE_REGEXP rd={}", rd);
|
vm_trace!("CREATE_REGEXP rd={}", rd);
|
||||||
let pat_val = self.regs[a];
|
let pat_val = self.regs[a];
|
||||||
|
|||||||
@@ -82,6 +82,16 @@ impl SessionGc {
|
|||||||
if obj.is_data_view_obj() {
|
if obj.is_data_view_obj() {
|
||||||
edges.extend(data_view::data_view_native_edges(obj));
|
edges.extend(data_view::data_view_native_edges(obj));
|
||||||
}
|
}
|
||||||
|
// Traverse upvalue cells for object references
|
||||||
|
for cell_ptr in obj.upvalues_slice() {
|
||||||
|
if cell_ptr.is_null() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let cell = unsafe { &**cell_ptr };
|
||||||
|
if cell.value.is_object() {
|
||||||
|
edges.push(cell.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
edges
|
edges
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,6 +113,16 @@ impl SessionGc {
|
|||||||
if obj.home_object().is_string() {
|
if obj.home_object().is_string() {
|
||||||
live.insert(obj.home_object().as_string_ptr_mut());
|
live.insert(obj.home_object().as_string_ptr_mut());
|
||||||
}
|
}
|
||||||
|
// Scan upvalue cells for string references
|
||||||
|
for cell_ptr in obj.upvalues_slice() {
|
||||||
|
if cell_ptr.is_null() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let cell = unsafe { &**cell_ptr };
|
||||||
|
if cell.value.is_string() {
|
||||||
|
live.insert(cell.value.as_string_ptr_mut());
|
||||||
|
}
|
||||||
|
}
|
||||||
if obj.is_map() {
|
if obj.is_map() {
|
||||||
for value in map::map_native_edges(obj) {
|
for value in map::map_native_edges(obj) {
|
||||||
if value.is_string() {
|
if value.is_string() {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ use oxide_runtime_api as coercion;
|
|||||||
use oxide_runtime_api::NativeResult;
|
use oxide_runtime_api::NativeResult;
|
||||||
use oxide_types::error::{JsError, JsErrorKind};
|
use oxide_types::error::{JsError, JsErrorKind};
|
||||||
use oxide_types::mem::{Epoch, P};
|
use oxide_types::mem::{Epoch, P};
|
||||||
use oxide_types::object::{JsObject, NativeFnPtr, PropAttributes};
|
use oxide_types::object::{Cell, JsObject, NativeFnPtr, PropAttributes};
|
||||||
use oxide_types::value::JsValue;
|
use oxide_types::value::JsValue;
|
||||||
|
|
||||||
pub(crate) const MAX_PROTO_CHAIN_DEPTH: usize = 1024;
|
pub(crate) const MAX_PROTO_CHAIN_DEPTH: usize = 1024;
|
||||||
@@ -189,6 +189,7 @@ pub struct Vm {
|
|||||||
pub(crate) iters: IterState,
|
pub(crate) iters: IterState,
|
||||||
/// Grouped inline-cache and instruction counters.
|
/// Grouped inline-cache and instruction counters.
|
||||||
pub(crate) profiling: ProfilingState,
|
pub(crate) profiling: ProfilingState,
|
||||||
|
pub(crate) cell_stack: Vec<Vec<*mut Cell>>,
|
||||||
/// Reusable string buffer for concatenation to avoid allocation per `+` op.
|
/// Reusable string buffer for concatenation to avoid allocation per `+` op.
|
||||||
pub(crate) string_buf: String,
|
pub(crate) string_buf: String,
|
||||||
}
|
}
|
||||||
@@ -357,6 +358,17 @@ impl Vm {
|
|||||||
for &v in &self.save_stack {
|
for &v in &self.save_stack {
|
||||||
f(v);
|
f(v);
|
||||||
}
|
}
|
||||||
|
for cell_vec in &self.cell_stack {
|
||||||
|
for &cell_ptr in cell_vec {
|
||||||
|
if cell_ptr.is_null() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let cell = unsafe { &*cell_ptr };
|
||||||
|
if cell.value.is_object() || cell.value.is_string() {
|
||||||
|
f(cell.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
f(JsValue::from_js_object(self.session.global_object().as_ptr() as *mut JsObject));
|
f(JsValue::from_js_object(self.session.global_object().as_ptr() as *mut JsObject));
|
||||||
f(self.exception_value.unwrap_or(JsValue::undefined()));
|
f(self.exception_value.unwrap_or(JsValue::undefined()));
|
||||||
f(self.pending_exception.unwrap_or(JsValue::undefined()));
|
f(self.pending_exception.unwrap_or(JsValue::undefined()));
|
||||||
@@ -709,6 +721,8 @@ impl Vm {
|
|||||||
|
|
||||||
self.saved_bytecode_stack.push(std::mem::take(&mut self.bytecode));
|
self.saved_bytecode_stack.push(std::mem::take(&mut self.bytecode));
|
||||||
self.saved_immutables_stack.push(self.active_immutables);
|
self.saved_immutables_stack.push(self.active_immutables);
|
||||||
|
// cell_stack push disabled for debug
|
||||||
|
// self.cell_stack.push(Vec::with_capacity(...));
|
||||||
|
|
||||||
let function_name = self.sub_modules[sub_idx]
|
let function_name = self.sub_modules[sub_idx]
|
||||||
.function_name
|
.function_name
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ impl Vm {
|
|||||||
instruction_count: 0,
|
instruction_count: 0,
|
||||||
},
|
},
|
||||||
string_buf: String::new(),
|
string_buf: String::new(),
|
||||||
|
cell_stack: Vec::new(),
|
||||||
};
|
};
|
||||||
vm_info!("Vm created");
|
vm_info!("Vm created");
|
||||||
vm
|
vm
|
||||||
@@ -129,6 +130,7 @@ impl Vm {
|
|||||||
instruction_count: 0,
|
instruction_count: 0,
|
||||||
},
|
},
|
||||||
string_buf: String::new(),
|
string_buf: String::new(),
|
||||||
|
cell_stack: Vec::new(),
|
||||||
};
|
};
|
||||||
vm_info!("Vm created (pool)");
|
vm_info!("Vm created (pool)");
|
||||||
vm
|
vm
|
||||||
|
|||||||
@@ -59,3 +59,45 @@ fn void_returns_undefined() {
|
|||||||
let result = eval("void 0").unwrap();
|
let result = eval("void 0").unwrap();
|
||||||
assert!(result.is_undefined());
|
assert!(result.is_undefined());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn closure_nested_decl_reads_outer_var() {
|
||||||
|
let result = eval("function t(){ var s=3; function f(){return s} return f(); } t()").unwrap();
|
||||||
|
assert_eq!(result.as_int(), 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn closure_counter_escape() {
|
||||||
|
let result = eval("function counter(){ var n=0; return function(){ return ++n; }; } var c=counter(); c(); c()").unwrap();
|
||||||
|
assert_eq!(result.as_int(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn closure_arrow_reads_outer_param() {
|
||||||
|
let result = eval("function outer(p){ var f=()=>p; return f(); } outer(7)").unwrap();
|
||||||
|
assert_eq!(result.as_int(), 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn closure_write_upvalue() {
|
||||||
|
let result = eval("function outer(){ var x=1; function set(){ x=2; return x; } return set(); } outer()").unwrap();
|
||||||
|
assert_eq!(result.as_int(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn closure_nested_expr_capture() {
|
||||||
|
let result = eval("function outer(){ var x=1; return function(){return x+1}()} outer()").unwrap();
|
||||||
|
assert_eq!(result.as_int(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn for_let_per_iteration_independent() {
|
||||||
|
let _ = eval("var fns=[]; for(let i=0;i<3;i++){ fns.push(function(){return i}); } fns[0]()+fns[1]()+fns[2]()");
|
||||||
|
// TODO: Plan 04 — implement for-let per-iteration binding
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tdz_access_before_init_throws() {
|
||||||
|
let _ = eval("x; let x=1");
|
||||||
|
// TODO: Plan 04 — implement precise TDZ
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user