mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 02:31:13 +08:00
refactor: 拆分 bytecode/cache crate 并模块化 counter
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "oxide_bytecode"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "OxideJS - Shared bytecode protocol and compiled module ABI"
|
||||
|
||||
[dependencies]
|
||||
@@ -0,0 +1,7 @@
|
||||
#![doc = "OxideJS - Shared bytecode protocol and compiled module ABI"]
|
||||
|
||||
pub mod module;
|
||||
pub mod opcode;
|
||||
|
||||
pub use module::{CompiledModule, Constant};
|
||||
pub use opcode::{Instr, OpCode};
|
||||
@@ -11,6 +11,8 @@ name = "oxide"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
oxide_bytecode = { path = "../oxide_bytecode" }
|
||||
oxide_code_cache = { path = "../oxide_code_cache" }
|
||||
oxide_parser = { path = "../oxide_parser" }
|
||||
oxide_types = { path = "../oxide_types" }
|
||||
oxide_compiler = { path = "../oxide_compiler" }
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::sync::Arc;
|
||||
|
||||
use ansi_term::Colour::Red;
|
||||
use clap::{Parser, Subcommand};
|
||||
use oxide_compiler::compiler::Compiler;
|
||||
use oxide_compiler::compiler::{compiled_module_hash, Compiler};
|
||||
use oxide_kernel::kernel::{KernelConfig, KernelCore};
|
||||
use oxide_kernel::shape_forge::{ShapeForge, EMPTY_SHAPE_ID};
|
||||
use oxide_kernel::string_forge::StringForge;
|
||||
@@ -91,7 +91,8 @@ fn eval(code: &str, kernel: &Arc<KernelCore>, pool: &Arc<VmPool>) -> ExitCode {
|
||||
};
|
||||
|
||||
let compiler = Compiler::new();
|
||||
let module = match kernel.code_forge().get_or_compile(&program, &compiler) {
|
||||
let hash = compiled_module_hash(&program);
|
||||
let module = match kernel.code_forge().get_or_insert_with(hash, || compiler.compile(&program)) {
|
||||
Ok(m) => m,
|
||||
Err(err) => {
|
||||
eprintln!("{}", Red.paint(err));
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "oxide_code_cache"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "OxideJS - Compiler-independent compiled module cache"
|
||||
|
||||
[dependencies]
|
||||
oxide_bytecode = { path = "../oxide_bytecode" }
|
||||
dashmap.workspace = true
|
||||
@@ -0,0 +1,71 @@
|
||||
#![doc = "OxideJS - Compiler-independent compiled module cache"]
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use dashmap::DashMap;
|
||||
use oxide_bytecode::CompiledModule;
|
||||
|
||||
/// Shared compiled-module cache keyed by a caller-provided safe module hash.
|
||||
///
|
||||
/// The cache intentionally does not know how to parse or compile JavaScript.
|
||||
/// Compiler-aware callers compute the key and provide the compile callback.
|
||||
pub struct CodeForge {
|
||||
map: DashMap<u64, Arc<CompiledModule>>,
|
||||
}
|
||||
|
||||
impl CodeForge {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
map: DashMap::with_shard_amount(16),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, hash: u64) -> Option<Arc<CompiledModule>> {
|
||||
self.map.get(&hash).map(|entry| Arc::clone(&entry))
|
||||
}
|
||||
|
||||
pub fn insert(&self, hash: u64, module: CompiledModule) -> Arc<CompiledModule> {
|
||||
let module = Arc::new(module);
|
||||
Arc::clone(self.map.entry(hash).or_insert(module).value())
|
||||
}
|
||||
|
||||
pub fn get_or_insert_with<F>(&self, hash: u64, compile: F) -> Result<Arc<CompiledModule>, String>
|
||||
where
|
||||
F: FnOnce() -> Result<CompiledModule, String>,
|
||||
{
|
||||
if let Some(module) = self.get(hash) {
|
||||
return Ok(module);
|
||||
}
|
||||
|
||||
Ok(self.insert(hash, compile()?))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CodeForge {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn cache_returns_same_arc_for_same_hash() {
|
||||
let forge = CodeForge::new();
|
||||
let first = forge.insert(1, CompiledModule::new());
|
||||
let second = forge.get_or_insert_with(1, || Ok(CompiledModule::new())).expect("cache hit");
|
||||
|
||||
assert!(Arc::ptr_eq(&first, &second));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cache_misses_for_different_hashes() {
|
||||
let forge = CodeForge::new();
|
||||
let first = forge.insert(1, CompiledModule::new());
|
||||
let second = forge.insert(2, CompiledModule::new());
|
||||
|
||||
assert!(!Arc::ptr_eq(&first, &second));
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,11 @@ rust-version.workspace = true
|
||||
description = "OxideJS - AST to bytecode compiler"
|
||||
|
||||
[dependencies]
|
||||
oxide_bytecode = { path = "../oxide_bytecode" }
|
||||
oxide_parser = { path = "../oxide_parser" }
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
rustc-hash.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
oxide_kernel = { path = "../oxide_kernel" }
|
||||
oxide_code_cache = { path = "../oxide_code_cache" }
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::module::CompiledModule;
|
||||
use crate::opcode::{self, OpCode};
|
||||
use crate::symbol_table::{Binding, SymbolTable};
|
||||
use oxide_bytecode::module::CompiledModule;
|
||||
use oxide_bytecode::opcode::{self, OpCode};
|
||||
|
||||
pub use crate::hash::{compiled_module_hash, structural_hash};
|
||||
pub use crate::module::Constant;
|
||||
use crate::symbol_table::ScopeKind;
|
||||
pub use oxide_bytecode::module::Constant;
|
||||
pub use oxide_parser::VariableDeclarationKind;
|
||||
pub use oxide_parser::{AssignmentOperator, BinaryOperator, Expression, Statement, UnaryOperator};
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,434 @@
|
||||
use super::*;
|
||||
|
||||
impl Compiler {
|
||||
pub(crate) fn count_expression(&self, expr: &Expression, ctx: &mut CompileCtx) {
|
||||
match expr {
|
||||
Expression::BinaryExpression(bin) => {
|
||||
let checkpoint = ctx.reg_checkpoint();
|
||||
self.count_expression(&bin.left, ctx);
|
||||
self.count_expression(&bin.right, ctx);
|
||||
ctx.projected_pc += 1; // ADD/SUB/MUL/DIV/etc.
|
||||
if is_side_effect_free(&bin.left) && is_side_effect_free(&bin.right) {
|
||||
ctx.restore_reg_checkpoint(checkpoint.saturating_add(1));
|
||||
}
|
||||
}
|
||||
Expression::PrivateInExpression(pin) => {
|
||||
self.count_expression(&pin.right, ctx);
|
||||
ctx.count_private_access();
|
||||
}
|
||||
Expression::UnaryExpression(un) => {
|
||||
match un.operator {
|
||||
UnaryOperator::Delete => {
|
||||
match &un.argument {
|
||||
Expression::Identifier(_) => {
|
||||
// SyntaxError at compile time, no bytecode cost
|
||||
}
|
||||
Expression::StaticMemberExpression(member) => {
|
||||
self.count_expression(&member.object, ctx);
|
||||
ctx.count_delete_static();
|
||||
}
|
||||
Expression::ComputedMemberExpression(member) => {
|
||||
self.count_expression(&member.object, ctx);
|
||||
self.count_expression(&member.expression, ctx);
|
||||
ctx.projected_pc += 1; // DELETE_PROP_DYNAMIC
|
||||
}
|
||||
Expression::ChainExpression(chain) => {
|
||||
match &chain.expression {
|
||||
ChainElement::StaticMemberExpression(member) => {
|
||||
self.count_expression(&member.object, ctx);
|
||||
if member.optional {
|
||||
self.count_optional_guard(ctx);
|
||||
}
|
||||
ctx.count_delete_static();
|
||||
}
|
||||
ChainElement::ComputedMemberExpression(member) => {
|
||||
self.count_expression(&member.object, ctx);
|
||||
if member.optional {
|
||||
self.count_optional_guard(ctx);
|
||||
}
|
||||
self.count_expression(&member.expression, ctx);
|
||||
ctx.projected_pc += 1; // DELETE_PROP_DYNAMIC
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
ctx.projected_pc += 1; // JMP over nullish true writer
|
||||
ctx.projected_pc += 1; // LOAD_CONST true
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
self.count_expression(&un.argument, ctx);
|
||||
ctx.projected_pc += 1; // NEG/TYPEOF/VOID/NOT
|
||||
}
|
||||
}
|
||||
}
|
||||
Expression::CallExpression(call) => {
|
||||
if matches!(&call.callee, Expression::Super(_)) {
|
||||
for arg in &call.arguments {
|
||||
if let Some(expr) = arg.as_expression() {
|
||||
self.count_expression(expr, ctx);
|
||||
}
|
||||
}
|
||||
ctx.alloc_reg();
|
||||
ctx.count_call_instr_with_arg_ext(); // SUPER_CALL + arg_count ext word
|
||||
return;
|
||||
}
|
||||
match &call.callee {
|
||||
Expression::PrivateFieldExpression(member) => {
|
||||
self.count_expression(&member.object, ctx);
|
||||
ctx.count_private_access();
|
||||
}
|
||||
Expression::StaticMemberExpression(member) => {
|
||||
if matches!(&member.object, Expression::Super(_)) {
|
||||
ctx.count_load_var(); // this register
|
||||
ctx.count_load_const(); // key
|
||||
ctx.alloc_reg(); // callee
|
||||
ctx.count_instr(); // SUPER_GET_PROP
|
||||
} else {
|
||||
self.count_expression(&member.object, ctx);
|
||||
ctx.count_load_const(); // key
|
||||
ctx.count_load_var(); // callee object copy
|
||||
ctx.count_ic_instr_with_ext(); // IC_GET_PROP + 3 ext words
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
self.count_expression(&call.callee, ctx);
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1;
|
||||
}
|
||||
}
|
||||
for arg in &call.arguments {
|
||||
if let Some(expr) = arg.as_expression() {
|
||||
self.count_expression(expr, ctx);
|
||||
}
|
||||
}
|
||||
ctx.count_call_instr_with_arg_ext(); // CALL/CALL_NATIVE + arg_count ext word
|
||||
ctx.count_load_var(); // result <- regs[0]
|
||||
}
|
||||
Expression::AssignmentExpression(assign) => {
|
||||
if let oxide_parser::AssignmentTarget::StaticMemberExpression(member) = &assign.left {
|
||||
if let Some(logical_op) = assign.operator.to_logical_operator() {
|
||||
let id = ctx.next_label_id();
|
||||
self.count_expression(&member.object, ctx);
|
||||
ctx.count_load_const(); // key
|
||||
ctx.count_load_var(); // current value copy
|
||||
ctx.count_ic_instr_with_ext(); // IC_GET_PROP + 3 ext
|
||||
self.count_logical_assign_test(logical_op, id, ctx);
|
||||
self.count_expression(&assign.right, ctx);
|
||||
ctx.count_ic_set_with_ext(); // IC_SET_PROP + 3 ext
|
||||
ctx.projected_pc += 1; // LOAD_VAR result <- rhs
|
||||
ctx.label_map.insert(Label::TernaryEnd(id), ctx.projected_pc);
|
||||
return;
|
||||
}
|
||||
self.count_expression(&member.object, ctx);
|
||||
self.count_expression(&assign.right, ctx);
|
||||
if assign.operator != AssignmentOperator::Assign {
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1;
|
||||
}
|
||||
ctx.count_load_const(); // key
|
||||
ctx.count_ic_set_with_ext(); // IC_SET_PROP or COMPOUND_MEMBER_* + 3 ext words
|
||||
} else if let oxide_parser::AssignmentTarget::ComputedMemberExpression(member) = &assign.left {
|
||||
if let Some(logical_op) = assign.operator.to_logical_operator() {
|
||||
let id = ctx.next_label_id();
|
||||
self.count_expression(&member.object, ctx);
|
||||
self.count_expression(&member.expression, ctx);
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1; // GET_PROP_DYNAMIC
|
||||
self.count_logical_assign_test(logical_op, id, ctx);
|
||||
self.count_expression(&assign.right, ctx);
|
||||
ctx.projected_pc += 1; // SET_PROP_DYNAMIC
|
||||
ctx.projected_pc += 1; // LOAD_VAR result <- rhs
|
||||
ctx.label_map.insert(Label::TernaryEnd(id), ctx.projected_pc);
|
||||
return;
|
||||
}
|
||||
self.count_expression(&member.object, ctx);
|
||||
self.count_expression(&member.expression, ctx);
|
||||
self.count_expression(&assign.right, ctx);
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1; // SET_PROP_DYNAMIC
|
||||
} else if let oxide_parser::AssignmentTarget::PrivateFieldExpression(member) = &assign.left {
|
||||
self.count_expression(&member.object, ctx);
|
||||
self.count_expression(&assign.right, ctx);
|
||||
ctx.count_load_const(); // private id
|
||||
ctx.count_instr(); // SET_PRIVATE
|
||||
} else if let oxide_parser::AssignmentTarget::AssignmentTargetIdentifier(_) = &assign.left {
|
||||
if let Some(logical_op) = assign.operator.to_logical_operator() {
|
||||
let id = ctx.next_label_id();
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1; // LOAD_VAR result <- current var
|
||||
self.count_logical_assign_test(logical_op, id, ctx);
|
||||
self.count_expression(&assign.right, ctx);
|
||||
ctx.projected_pc += 1; // STORE_VAR
|
||||
ctx.projected_pc += 1; // LOAD_VAR result <- rhs
|
||||
ctx.label_map.insert(Label::TernaryEnd(id), ctx.projected_pc);
|
||||
} else {
|
||||
self.count_expression(&assign.right, ctx);
|
||||
if assign.operator != AssignmentOperator::Assign {
|
||||
ctx.projected_pc += 1; // COMPOUND_* on var
|
||||
} else {
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1; // STORE_VAR
|
||||
}
|
||||
}
|
||||
} else if let oxide_parser::AssignmentTarget::ArrayAssignmentTarget(ap) = &assign.left {
|
||||
self.count_expression(&assign.right, ctx);
|
||||
self.count_array_assignment(ap, ctx);
|
||||
} else if let oxide_parser::AssignmentTarget::ObjectAssignmentTarget(op) = &assign.left {
|
||||
self.count_expression(&assign.right, ctx);
|
||||
self.count_object_assignment(op, ctx);
|
||||
} else {
|
||||
self.count_expression(&assign.right, ctx);
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1;
|
||||
}
|
||||
}
|
||||
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.projected_pc += 1; // LOAD_VAR result <- alternate
|
||||
ctx.label_map.insert(end_label, ctx.projected_pc);
|
||||
}
|
||||
Expression::SequenceExpression(seq) => {
|
||||
for e in &seq.expressions {
|
||||
self.count_expression(e, ctx);
|
||||
}
|
||||
}
|
||||
Expression::LogicalExpression(log) => {
|
||||
self.count_expression(&log.left, ctx);
|
||||
|
||||
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, JMP_IF_TRUE, or JMP_IF_NULLISH
|
||||
if matches!(log.operator, LogicalOperator::Coalesce) {
|
||||
ctx.projected_pc += 1; // JMP over RHS on non-nullish
|
||||
ctx.label_map.insert(Label::TernaryElse(id), ctx.projected_pc);
|
||||
}
|
||||
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 => Label::TernaryEnd(id),
|
||||
};
|
||||
ctx.label_map.insert(skip_label, ctx.projected_pc);
|
||||
}
|
||||
}
|
||||
Expression::ChainExpression(chain) => {
|
||||
let id = ctx.next_label_id();
|
||||
self.count_chain_element(&chain.expression, true, ctx);
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1; // LOAD_VAR result <- chain value
|
||||
ctx.projected_pc += 1; // JMP over short-circuit writer
|
||||
ctx.label_map.insert(Label::TernaryElse(id), ctx.projected_pc);
|
||||
ctx.projected_pc += 1; // LOAD_CONST undefined
|
||||
ctx.label_map.insert(Label::TernaryEnd(id), ctx.projected_pc);
|
||||
}
|
||||
Expression::ObjectExpression(obj) => {
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1; // NEW_OBJECT
|
||||
let prop_checkpoint = ctx.reg_checkpoint();
|
||||
for prop in &obj.properties {
|
||||
if let oxide_parser::ObjectPropertyKind::ObjectProperty(p) = prop {
|
||||
if matches!(p.kind, oxide_parser::PropertyKind::Get | oxide_parser::PropertyKind::Set) {
|
||||
self.count_expression(&p.value, ctx);
|
||||
ctx.count_load_const(); // undefined getter/setter placeholder
|
||||
ctx.count_define_accessor(); // DEFINE_ACCESSOR + ext
|
||||
} else {
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1; // LOAD_CONST key
|
||||
self.count_expression(&p.value, ctx);
|
||||
ctx.projected_pc += 1; // SET_PROP
|
||||
}
|
||||
ctx.restore_reg_checkpoint(prop_checkpoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
Expression::TemplateLiteral(tl) => {
|
||||
// TemplateLiteral: N quasis (strings) + M expressions interleaved.
|
||||
// Allocate 1 result reg + count expressions.
|
||||
for expr in &tl.expressions {
|
||||
self.count_expression(expr, ctx);
|
||||
}
|
||||
let segment_count = tl.quasis.len() + tl.expressions.len();
|
||||
ctx.count_template_str(segment_count);
|
||||
}
|
||||
Expression::TaggedTemplateExpression(tt) => {
|
||||
// Tagged template: tag expr + quasis as LOAD_CONST + expression args + CALL
|
||||
// Consecutive arg registers at end (via LOAD_VAR copies)
|
||||
self.count_expression(&tt.tag, ctx);
|
||||
let quasi_count = tt.quasi.quasis.len();
|
||||
// Each quasi: LOAD_CONST string + LOAD_CONST index + SET_ELEM
|
||||
// for both cooked and raw arrays
|
||||
for _ in 0..quasi_count {
|
||||
ctx.count_load_const(); // string
|
||||
ctx.count_load_const(); // index
|
||||
ctx.count_instr(); // SET_ELEM (cooked)
|
||||
}
|
||||
// Cooked array: alloc + NEW_ARRAY
|
||||
ctx.alloc_reg(); // cooked_temp
|
||||
ctx.count_instr(); // NEW_ARRAY
|
||||
|
||||
for _ in 0..quasi_count {
|
||||
ctx.count_load_const(); // string
|
||||
ctx.count_load_const(); // index
|
||||
ctx.count_instr(); // SET_ELEM (raw)
|
||||
}
|
||||
// Raw array: alloc + NEW_ARRAY
|
||||
ctx.alloc_reg(); // raw_temp
|
||||
ctx.count_instr(); // NEW_ARRAY
|
||||
|
||||
// Count expression arguments
|
||||
for expr in &tt.quasi.expressions {
|
||||
self.count_expression(expr, ctx);
|
||||
}
|
||||
|
||||
// Consecutive arg slots: cooked_slot, raw_slot, N expr_slots
|
||||
ctx.alloc_reg(); // cooked_slot
|
||||
ctx.alloc_reg(); // raw_slot
|
||||
for _ in &tt.quasi.expressions {
|
||||
ctx.alloc_reg(); // expr_slot
|
||||
ctx.count_instr(); // LOAD_VAR
|
||||
}
|
||||
// LOAD_VAR for cooked and raw
|
||||
ctx.count_words(2);
|
||||
|
||||
// undefined this arg
|
||||
ctx.count_load_const();
|
||||
|
||||
// CALL + ext word
|
||||
ctx.count_call_instr_with_arg_ext();
|
||||
|
||||
// Result reg + LOAD_VAR
|
||||
ctx.count_load_var();
|
||||
}
|
||||
Expression::ArrowFunctionExpression(_arrow) => {
|
||||
// Arrow functions: alloc 1 register for the function value.
|
||||
// Body is compiled in the emit pass only (same pattern as FE).
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1; // LOAD_CONST
|
||||
}
|
||||
Expression::FunctionExpression(_fe) => {
|
||||
// No hoisting - function created at expression position.
|
||||
// Body is compiled in the emit pass only.
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1; // LOAD_CONST
|
||||
}
|
||||
Expression::ClassExpression(class) => {
|
||||
self.count_class(class, ctx);
|
||||
}
|
||||
Expression::NewExpression(ne) => {
|
||||
// Count callee expression
|
||||
self.count_expression(&ne.callee, ctx);
|
||||
// Count arguments
|
||||
for arg in &ne.arguments {
|
||||
if let Some(expr) = arg.as_expression() {
|
||||
self.count_expression(expr, ctx);
|
||||
}
|
||||
}
|
||||
ctx.alloc_reg(); // result register
|
||||
ctx.count_instr_with_ext(1); // NEW_EXPRESSION + arg_count ext word
|
||||
}
|
||||
Expression::ArrayExpression(arr) => {
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1; // NEW_ARRAY
|
||||
let elem_checkpoint = ctx.reg_checkpoint();
|
||||
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
|
||||
ctx.restore_reg_checkpoint(elem_checkpoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
Expression::StaticMemberExpression(member) => {
|
||||
if matches!(&member.object, Expression::Super(_)) {
|
||||
ctx.count_load_const(); // key
|
||||
ctx.count_load_var(); // this
|
||||
ctx.alloc_reg(); // result
|
||||
ctx.count_instr(); // SUPER_GET_PROP
|
||||
return;
|
||||
}
|
||||
self.count_expression(&member.object, ctx);
|
||||
ctx.count_load_const(); // key
|
||||
ctx.count_ic_instr_with_ext(); // IC_GET_PROP + 3 ext words
|
||||
}
|
||||
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::PrivateFieldExpression(member) => {
|
||||
self.count_expression(&member.object, ctx);
|
||||
ctx.count_private_access();
|
||||
}
|
||||
Expression::ParenthesizedExpression(p) => {
|
||||
self.count_expression(&p.expression, ctx);
|
||||
}
|
||||
Expression::ThisExpression(_) => {
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1; // LOAD_VAR from reg 254
|
||||
}
|
||||
Expression::Identifier(ident) => {
|
||||
if CompileCtx::is_known_builtin(ident.name.as_str()) {
|
||||
let _ = ctx.lookup_or_builtin(ident.name.as_str());
|
||||
}
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1; // LOAD_VAR
|
||||
}
|
||||
Expression::UpdateExpression(update) => match &update.argument {
|
||||
SimpleAssignmentTarget::AssignmentTargetIdentifier(_) => {
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1;
|
||||
}
|
||||
SimpleAssignmentTarget::StaticMemberExpression(member) => {
|
||||
self.count_expression(&member.object, ctx);
|
||||
ctx.count_load_const(); // key
|
||||
ctx.alloc_reg();
|
||||
ctx.count_ic_instr_with_ext(); // MEMBER_INC/MEMBER_DEC + 3 ext words
|
||||
}
|
||||
SimpleAssignmentTarget::ComputedMemberExpression(member) => {
|
||||
self.count_expression(&member.object, ctx);
|
||||
self.count_expression(&member.expression, ctx);
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1;
|
||||
}
|
||||
_ => {
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1;
|
||||
}
|
||||
},
|
||||
Expression::RegExpLiteral(_) => {
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1;
|
||||
}
|
||||
_ => {
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1; // LOAD_CONST or LOAD_VAR
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
use super::super::*;
|
||||
|
||||
impl Compiler {
|
||||
pub(in crate::counter) fn count_binding_pattern(
|
||||
&self, pattern: &oxide_parser::BindingPattern, ctx: &mut CompileCtx,
|
||||
) {
|
||||
match pattern {
|
||||
oxide_parser::BindingPattern::BindingIdentifier(_) => {
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1;
|
||||
}
|
||||
oxide_parser::BindingPattern::ArrayPattern(ap) => {
|
||||
ctx.count_instr(); // FOR_OF_INIT
|
||||
for elem in &ap.elements {
|
||||
ctx.alloc_reg();
|
||||
ctx.count_instr(); // FOR_OF_DONE
|
||||
ctx.alloc_reg();
|
||||
ctx.count_instr(); // FOR_OF_NEXT
|
||||
if let Some(inner) = elem {
|
||||
self.count_binding_pattern(inner, ctx);
|
||||
}
|
||||
}
|
||||
if let Some(rest) = &ap.rest {
|
||||
self.count_rest_array(ctx);
|
||||
self.count_binding_pattern(&rest.argument, ctx);
|
||||
}
|
||||
ctx.count_instr(); // FOR_OF_CLOSE
|
||||
}
|
||||
oxide_parser::BindingPattern::ObjectPattern(op) => {
|
||||
for prop in &op.properties {
|
||||
self.count_object_property_read_key(&prop.key, prop.computed, ctx);
|
||||
self.count_binding_pattern(&prop.value, ctx);
|
||||
}
|
||||
if let Some(rest) = &op.rest {
|
||||
ctx.alloc_reg();
|
||||
ctx.count_instr_with_ext(1); // REST_OBJECT + excluded-keys ext
|
||||
self.count_binding_pattern(&rest.argument, ctx);
|
||||
}
|
||||
}
|
||||
oxide_parser::BindingPattern::AssignmentPattern(ap) => {
|
||||
ctx.count_load_const(); // undefined
|
||||
ctx.count_instr(); // STRICT_EQ
|
||||
ctx.count_jump(); // JMP_IF_FALSE
|
||||
self.count_expression(&ap.right, ctx);
|
||||
ctx.count_instr(); // LOAD_VAR default
|
||||
self.count_binding_pattern(&ap.left, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::counter) fn count_rest_array(&self, ctx: &mut CompileCtx) {
|
||||
ctx.alloc_reg(); // rest
|
||||
ctx.count_instr(); // NEW_ARRAY
|
||||
ctx.count_load_const(); // idx = 0
|
||||
ctx.alloc_reg(); // has
|
||||
ctx.count_instr(); // FOR_OF_DONE
|
||||
ctx.count_jump(); // JMP_IF_FALSE
|
||||
ctx.alloc_reg(); // val
|
||||
ctx.count_instr(); // FOR_OF_NEXT
|
||||
ctx.count_instr(); // SET_ELEM
|
||||
ctx.alloc_reg(); // inc tmp
|
||||
ctx.count_instr(); // INC_PRE
|
||||
ctx.count_jump(); // JMP
|
||||
}
|
||||
|
||||
pub(in crate::counter) fn count_array_assignment(
|
||||
&self, ap: &oxide_parser::ArrayAssignmentTarget, ctx: &mut CompileCtx,
|
||||
) {
|
||||
ctx.count_instr(); // FOR_OF_INIT
|
||||
for elem in &ap.elements {
|
||||
ctx.alloc_reg();
|
||||
ctx.count_instr(); // FOR_OF_DONE
|
||||
ctx.alloc_reg();
|
||||
ctx.count_instr(); // FOR_OF_NEXT
|
||||
if elem.is_some() {
|
||||
ctx.alloc_reg();
|
||||
ctx.count_instr(); // STORE_VAR
|
||||
}
|
||||
}
|
||||
if ap.rest.is_some() {
|
||||
self.count_rest_array(ctx);
|
||||
ctx.alloc_reg();
|
||||
ctx.count_instr(); // STORE_VAR
|
||||
}
|
||||
ctx.count_instr(); // FOR_OF_CLOSE
|
||||
}
|
||||
|
||||
pub(in crate::counter) fn count_object_assignment(
|
||||
&self, op: &oxide_parser::ObjectAssignmentTarget, ctx: &mut CompileCtx,
|
||||
) {
|
||||
for prop in &op.properties {
|
||||
match prop {
|
||||
oxide_parser::AssignmentTargetProperty::AssignmentTargetPropertyIdentifier(id) => {
|
||||
self.count_object_property_read_static(ctx);
|
||||
if let Some(default_expr) = &id.init {
|
||||
ctx.count_load_const(); // undefined
|
||||
ctx.count_instr(); // STRICT_EQ
|
||||
ctx.count_jump(); // JMP_IF_FALSE
|
||||
self.count_expression(default_expr, ctx);
|
||||
ctx.count_instr(); // LOAD_VAR default
|
||||
}
|
||||
ctx.alloc_reg();
|
||||
ctx.count_instr(); // STORE_VAR
|
||||
}
|
||||
oxide_parser::AssignmentTargetProperty::AssignmentTargetPropertyProperty(prop) => {
|
||||
self.count_object_property_read_key(&prop.name, prop.computed, ctx);
|
||||
self.count_assignment_maybe_default(&prop.binding, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
if op.rest.is_some() {
|
||||
ctx.alloc_reg();
|
||||
ctx.count_instr_with_ext(1); // REST_OBJECT + excluded-keys ext
|
||||
ctx.alloc_reg();
|
||||
ctx.count_instr(); // STORE_VAR
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::counter) fn count_assignment_maybe_default(
|
||||
&self, target: &oxide_parser::AssignmentTargetMaybeDefault, ctx: &mut CompileCtx,
|
||||
) {
|
||||
match target {
|
||||
oxide_parser::AssignmentTargetMaybeDefault::AssignmentTargetWithDefault(default) => {
|
||||
ctx.count_load_const(); // undefined
|
||||
ctx.count_instr(); // STRICT_EQ
|
||||
ctx.count_jump(); // JMP_IF_FALSE
|
||||
self.count_expression(&default.init, ctx);
|
||||
ctx.count_instr(); // LOAD_VAR default
|
||||
self.count_assign_target(&default.binding, ctx);
|
||||
}
|
||||
oxide_parser::AssignmentTargetMaybeDefault::ArrayAssignmentTarget(ap) => {
|
||||
self.count_array_assignment(ap, ctx)
|
||||
}
|
||||
oxide_parser::AssignmentTargetMaybeDefault::ObjectAssignmentTarget(op) => {
|
||||
self.count_object_assignment(op, ctx)
|
||||
}
|
||||
oxide_parser::AssignmentTargetMaybeDefault::AssignmentTargetIdentifier(_) => {
|
||||
ctx.alloc_reg();
|
||||
ctx.count_instr(); // STORE_VAR
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::counter) fn count_assign_target(
|
||||
&self, target: &oxide_parser::AssignmentTarget, ctx: &mut CompileCtx,
|
||||
) {
|
||||
match target {
|
||||
oxide_parser::AssignmentTarget::AssignmentTargetIdentifier(_) => {
|
||||
ctx.alloc_reg();
|
||||
ctx.count_instr(); // STORE_VAR
|
||||
}
|
||||
oxide_parser::AssignmentTarget::ArrayAssignmentTarget(ap) => self.count_array_assignment(ap, ctx),
|
||||
oxide_parser::AssignmentTarget::ObjectAssignmentTarget(op) => self.count_object_assignment(op, ctx),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
use super::super::*;
|
||||
|
||||
impl Compiler {
|
||||
pub(in crate::counter) fn count_class(&self, class: &oxide_parser::Class, ctx: &mut CompileCtx) {
|
||||
ctx.alloc_reg(); // ctor_reg
|
||||
ctx.alloc_reg(); // proto_reg
|
||||
if let Some(super_class) = &class.super_class {
|
||||
self.count_expression(super_class, ctx);
|
||||
}
|
||||
ctx.count_instr(); // LOAD_CONST ctor
|
||||
ctx.count_instr(); // NEW_OBJECT proto
|
||||
|
||||
if class.super_class.is_some() {
|
||||
ctx.alloc_reg(); // parent prototype key
|
||||
ctx.count_instr(); // LOAD_CONST
|
||||
ctx.alloc_reg(); // parent prototype value
|
||||
ctx.count_instr(); // GET_PROP
|
||||
ctx.alloc_reg(); // __proto__ key
|
||||
ctx.count_instr(); // LOAD_CONST
|
||||
ctx.count_instr(); // SET_PROP proto.__proto__
|
||||
ctx.count_instr(); // SET_PROP ctor.__proto__
|
||||
}
|
||||
|
||||
ctx.count_load_const(); // constructor key
|
||||
ctx.count_instr(); // SET_PROP proto.constructor = ctor
|
||||
|
||||
ctx.count_load_const(); // prototype key
|
||||
ctx.count_instr(); // SET_PROP ctor.prototype = proto
|
||||
|
||||
for element in &class.body.body {
|
||||
match element {
|
||||
ClassElement::MethodDefinition(method) => {
|
||||
let method = method.as_ref();
|
||||
if matches!(method.kind, MethodDefinitionKind::Constructor) {
|
||||
continue;
|
||||
}
|
||||
if matches!(method.key, oxide_parser::PropertyKey::PrivateIdentifier(_)) {
|
||||
if method.r#static {
|
||||
ctx.count_load_const(); // private id
|
||||
ctx.count_load_const(); // method
|
||||
ctx.count_instr(); // INIT_PRIVATE
|
||||
}
|
||||
continue;
|
||||
}
|
||||
ctx.count_load_const(); // key_reg
|
||||
ctx.count_load_const(); // method_reg
|
||||
ctx.count_instr(); // SET_HOME_OBJECT
|
||||
match method.kind {
|
||||
MethodDefinitionKind::Method => {
|
||||
ctx.count_instr(); // SET_PROP
|
||||
}
|
||||
MethodDefinitionKind::Get | MethodDefinitionKind::Set => {
|
||||
ctx.count_load_const(); // undefined placeholder
|
||||
ctx.count_define_accessor(); // DEFINE_ACCESSOR + ext
|
||||
}
|
||||
MethodDefinitionKind::Constructor => {}
|
||||
}
|
||||
}
|
||||
ClassElement::PropertyDefinition(prop) => {
|
||||
let prop = prop.as_ref();
|
||||
if prop.r#static {
|
||||
ctx.count_load_const(); // key_reg
|
||||
if let Some(value) = &prop.value {
|
||||
self.count_expression(value, ctx);
|
||||
} else {
|
||||
ctx.count_load_const();
|
||||
}
|
||||
ctx.count_instr(); // SET_PROP
|
||||
}
|
||||
}
|
||||
ClassElement::StaticBlock(block) => {
|
||||
for stmt in &block.body {
|
||||
self.count_statement(stmt, ctx);
|
||||
}
|
||||
}
|
||||
ClassElement::AccessorProperty(_) | ClassElement::TSIndexSignature(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
use super::super::*;
|
||||
|
||||
impl Compiler {
|
||||
pub(in crate::counter) fn count_object_property_read_static(&self, ctx: &mut CompileCtx) {
|
||||
ctx.count_load_var(); // prop/object temp
|
||||
ctx.count_load_const(); // key reg
|
||||
ctx.count_ic_instr_with_ext(); // IC_GET_PROP + 3 ext words
|
||||
}
|
||||
|
||||
pub(in crate::counter) fn count_object_property_read_key(
|
||||
&self, key: &oxide_parser::PropertyKey, computed: bool, ctx: &mut CompileCtx,
|
||||
) {
|
||||
if !computed {
|
||||
self.count_object_property_read_static(ctx);
|
||||
let _ = key;
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.count_load_var(); // prop/object temp
|
||||
match key {
|
||||
oxide_parser::PropertyKey::Identifier(ident) => {
|
||||
let name = ident.name.as_str();
|
||||
let _ = ctx.lookup_or_builtin(name);
|
||||
ctx.count_load_var(); // key
|
||||
}
|
||||
oxide_parser::PropertyKey::StringLiteral(_) => {
|
||||
ctx.count_load_const(); // key
|
||||
}
|
||||
oxide_parser::PropertyKey::NumericLiteral(_) => {
|
||||
ctx.count_load_const(); // key
|
||||
}
|
||||
oxide_parser::PropertyKey::StaticIdentifier(_) => {
|
||||
ctx.count_load_const(); // key
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
ctx.alloc_reg(); // result reg
|
||||
ctx.count_instr(); // GET_PROP_DYNAMIC
|
||||
}
|
||||
|
||||
pub(in crate::counter) fn count_optional_guard(&self, ctx: &mut CompileCtx) {
|
||||
ctx.count_load_var(); // dup
|
||||
ctx.count_jump(); // JMP_IF_NULLISH
|
||||
}
|
||||
|
||||
pub(in crate::counter) fn count_static_member_get_preserve_base(
|
||||
&self, member: &oxide_parser::StaticMemberExpression, short_chain: bool, ctx: &mut CompileCtx,
|
||||
) {
|
||||
self.count_chainable_expression(&member.object, short_chain, ctx);
|
||||
if short_chain && member.optional {
|
||||
self.count_optional_guard(ctx);
|
||||
}
|
||||
ctx.count_load_const(); // key
|
||||
ctx.count_load_var(); // receiver copy
|
||||
ctx.count_ic_instr_with_ext(); // IC_GET_PROP + 3 ext words
|
||||
}
|
||||
|
||||
pub(in crate::counter) fn count_computed_member_get_preserve_base(
|
||||
&self, member: &oxide_parser::ComputedMemberExpression, short_chain: bool, ctx: &mut CompileCtx,
|
||||
) {
|
||||
self.count_chainable_expression(&member.object, short_chain, ctx);
|
||||
if short_chain && member.optional {
|
||||
self.count_optional_guard(ctx);
|
||||
}
|
||||
self.count_expression(&member.expression, ctx);
|
||||
ctx.alloc_reg();
|
||||
ctx.count_instr(); // GET_PROP_DYNAMIC
|
||||
}
|
||||
|
||||
pub(in crate::counter) fn count_chain_call(
|
||||
&self, call: &oxide_parser::CallExpression, short_chain: bool, ctx: &mut CompileCtx,
|
||||
) {
|
||||
match &call.callee {
|
||||
Expression::StaticMemberExpression(member) => {
|
||||
self.count_static_member_get_preserve_base(member, short_chain, ctx)
|
||||
}
|
||||
Expression::ComputedMemberExpression(member) => {
|
||||
self.count_computed_member_get_preserve_base(member, short_chain, ctx);
|
||||
}
|
||||
Expression::PrivateFieldExpression(member) => {
|
||||
self.count_chainable_expression(&member.object, short_chain, ctx);
|
||||
if short_chain && member.optional {
|
||||
self.count_optional_guard(ctx);
|
||||
}
|
||||
ctx.count_private_access();
|
||||
}
|
||||
_ => {
|
||||
self.count_chainable_expression(&call.callee, short_chain, ctx);
|
||||
if short_chain && call.optional {
|
||||
self.count_optional_guard(ctx);
|
||||
}
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1; // LOAD_CONST undefined this
|
||||
}
|
||||
}
|
||||
if short_chain
|
||||
&& call.optional
|
||||
&& matches!(
|
||||
&call.callee,
|
||||
Expression::StaticMemberExpression(_)
|
||||
| Expression::ComputedMemberExpression(_)
|
||||
| Expression::PrivateFieldExpression(_)
|
||||
)
|
||||
{
|
||||
self.count_optional_guard(ctx);
|
||||
}
|
||||
for arg in &call.arguments {
|
||||
if let Some(expr) = arg.as_expression() {
|
||||
self.count_expression(expr, ctx);
|
||||
}
|
||||
}
|
||||
ctx.count_call_instr_with_arg_ext(); // CALL + arg_count ext word
|
||||
ctx.count_load_var(); // result <- regs[0]
|
||||
}
|
||||
|
||||
pub(in crate::counter) fn count_chainable_expression(
|
||||
&self, expr: &Expression, short_chain: bool, ctx: &mut CompileCtx,
|
||||
) {
|
||||
match expr {
|
||||
Expression::StaticMemberExpression(member) => {
|
||||
self.count_static_member_get_preserve_base(member, short_chain, ctx)
|
||||
}
|
||||
Expression::ComputedMemberExpression(member) => {
|
||||
self.count_computed_member_get_preserve_base(member, short_chain, ctx)
|
||||
}
|
||||
Expression::PrivateFieldExpression(member) => {
|
||||
self.count_chainable_expression(&member.object, short_chain, ctx);
|
||||
if short_chain && member.optional {
|
||||
self.count_optional_guard(ctx);
|
||||
}
|
||||
ctx.count_private_access();
|
||||
}
|
||||
Expression::CallExpression(call) => self.count_chain_call(call, short_chain, ctx),
|
||||
Expression::ChainExpression(chain) => self.count_chain_element(&chain.expression, short_chain, ctx),
|
||||
_ => self.count_expression(expr, ctx),
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::counter) fn count_chain_element(
|
||||
&self, element: &ChainElement, short_chain: bool, ctx: &mut CompileCtx,
|
||||
) {
|
||||
match element {
|
||||
ChainElement::StaticMemberExpression(member) => {
|
||||
self.count_static_member_get_preserve_base(member, short_chain, ctx)
|
||||
}
|
||||
ChainElement::ComputedMemberExpression(member) => {
|
||||
self.count_computed_member_get_preserve_base(member, short_chain, ctx)
|
||||
}
|
||||
ChainElement::PrivateFieldExpression(member) => {
|
||||
self.count_chainable_expression(&member.object, short_chain, ctx);
|
||||
if short_chain && member.optional {
|
||||
self.count_optional_guard(ctx);
|
||||
}
|
||||
ctx.count_private_access();
|
||||
}
|
||||
ChainElement::CallExpression(call) => self.count_chain_call(call, short_chain, ctx),
|
||||
ChainElement::TSNonNullExpression(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::counter) fn count_logical_assign_test(&self, op: LogicalOperator, id: u32, ctx: &mut CompileCtx) {
|
||||
match op {
|
||||
LogicalOperator::And | LogicalOperator::Or => {
|
||||
ctx.projected_pc += 1;
|
||||
}
|
||||
LogicalOperator::Coalesce => {
|
||||
ctx.projected_pc += 1; // JMP_IF_NULLISH to store body
|
||||
ctx.projected_pc += 1; // JMP to end on non-nullish
|
||||
ctx.label_map.insert(Label::TernaryElse(id), ctx.projected_pc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
mod binding;
|
||||
mod class;
|
||||
mod member;
|
||||
@@ -0,0 +1,10 @@
|
||||
use oxide_parser::{
|
||||
AssignmentOperator, ChainElement, ClassElement, Expression, ForStatementInit, LogicalOperator,
|
||||
MethodDefinitionKind, SimpleAssignmentTarget, Statement, UnaryOperator, VariableDeclarationKind,
|
||||
};
|
||||
|
||||
use crate::compiler::{is_side_effect_free, CompileCtx, Compiler, Label};
|
||||
|
||||
mod expr;
|
||||
mod helper;
|
||||
mod stmt;
|
||||
@@ -0,0 +1,303 @@
|
||||
use super::*;
|
||||
|
||||
impl Compiler {
|
||||
pub(crate) fn count_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) {
|
||||
match stmt {
|
||||
Statement::ExpressionStatement(es) => {
|
||||
self.count_expression(&es.expression, ctx);
|
||||
}
|
||||
Statement::VariableDeclaration(decl) => {
|
||||
for d in &decl.declarations {
|
||||
if let Some(init) = &d.init {
|
||||
self.count_expression(init, ctx);
|
||||
self.count_binding_pattern(&d.id, ctx);
|
||||
} else {
|
||||
ctx.alloc_reg();
|
||||
ctx.count_words(2); // LOAD_CONST(undefined) + 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);
|
||||
ctx.alloc_reg(); // result register
|
||||
ctx.projected_pc += 1; // LOAD_VAR result <- consequent
|
||||
if ifs.alternate.is_some() {
|
||||
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::DoWhileStatement(dw) => {
|
||||
let id = ctx.next_label_id();
|
||||
let start_label = Label::DoWhileStart(id);
|
||||
let end_label = Label::DoWhileEnd(id);
|
||||
|
||||
ctx.label_map.insert(start_label, ctx.projected_pc);
|
||||
self.count_statement(&dw.body, ctx);
|
||||
self.count_expression(&dw.test, ctx);
|
||||
ctx.projected_pc += 1; // JMP_IF_TRUE (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);
|
||||
} else if let ForStatementInit::VariableDeclaration(decl) = init {
|
||||
for d in &decl.declarations {
|
||||
if let Some(init_expr) = &d.init {
|
||||
self.count_expression(init_expr, ctx);
|
||||
self.count_binding_pattern(&d.id, ctx);
|
||||
} else {
|
||||
ctx.alloc_reg();
|
||||
ctx.count_words(2); // LOAD_CONST(undefined) + STORE_VAR
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
ctx.projected_pc += 1; // JMP (backward)
|
||||
ctx.label_map.insert(end_label, ctx.projected_pc);
|
||||
}
|
||||
Statement::ForInStatement(fi) => {
|
||||
let id = ctx.next_label_id();
|
||||
let start_label = Label::ForInStart(id);
|
||||
let end_label = Label::ForInEnd(id);
|
||||
|
||||
self.count_expression(&fi.right, ctx);
|
||||
ctx.count_instr(); // FOR_IN_INIT
|
||||
|
||||
ctx.label_map.insert(start_label, ctx.projected_pc);
|
||||
ctx.count_instr(); // FOR_IN_DONE
|
||||
ctx.count_jump(); // JMP_IF_FALSE
|
||||
ctx.count_jump(); // JMP cleanup
|
||||
|
||||
ctx.count_instr(); // FOR_IN_NEXT
|
||||
match &fi.left {
|
||||
oxide_parser::ForStatementLeft::VariableDeclaration(decl) => {
|
||||
for _d in &decl.declarations {
|
||||
ctx.alloc_reg();
|
||||
ctx.count_instr(); // STORE_VAR
|
||||
}
|
||||
}
|
||||
oxide_parser::ForStatementLeft::AssignmentTargetIdentifier(_) => {
|
||||
ctx.alloc_reg(); // key register
|
||||
ctx.count_instr(); // STORE_VAR
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
self.count_statement(&fi.body, ctx);
|
||||
ctx.count_jump(); // JMP back to start
|
||||
|
||||
ctx.label_map.insert(end_label, ctx.projected_pc);
|
||||
ctx.count_instr(); // FOR_IN_CLEANUP
|
||||
}
|
||||
Statement::ForOfStatement(fo) => {
|
||||
let id = ctx.next_label_id();
|
||||
let start_label = Label::ForOfStart(id);
|
||||
let end_label = Label::ForOfEnd(id);
|
||||
|
||||
self.count_expression(&fo.right, ctx);
|
||||
ctx.count_instr(); // FOR_OF_INIT
|
||||
ctx.label_map.insert(start_label, ctx.projected_pc);
|
||||
ctx.alloc_reg(); // has_reg
|
||||
ctx.count_instr(); // FOR_OF_DONE
|
||||
ctx.count_jump(); // JMP_IF_FALSE
|
||||
ctx.alloc_reg(); // val_reg
|
||||
ctx.count_instr(); // FOR_OF_NEXT
|
||||
match &fo.left {
|
||||
oxide_parser::ForStatementLeft::VariableDeclaration(decl) => {
|
||||
for d in &decl.declarations {
|
||||
self.count_binding_pattern(&d.id, ctx);
|
||||
}
|
||||
}
|
||||
oxide_parser::ForStatementLeft::AssignmentTargetIdentifier(_) => {
|
||||
ctx.alloc_reg();
|
||||
ctx.count_instr(); // STORE_VAR
|
||||
}
|
||||
oxide_parser::ForStatementLeft::ArrayAssignmentTarget(ap) => {
|
||||
self.count_array_assignment(ap, ctx);
|
||||
}
|
||||
oxide_parser::ForStatementLeft::ObjectAssignmentTarget(op) => {
|
||||
self.count_object_assignment(op, ctx);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
self.count_statement(&fo.body, ctx);
|
||||
ctx.count_jump(); // JMP back
|
||||
ctx.label_map.insert(end_label, ctx.projected_pc);
|
||||
ctx.count_instr(); // FOR_OF_CLOSE
|
||||
}
|
||||
Statement::SwitchStatement(sw) => {
|
||||
let id = ctx.next_label_id();
|
||||
let end_label = Label::SwitchEnd(id);
|
||||
ctx.push_switch(end_label);
|
||||
|
||||
self.count_expression(&sw.discriminant, ctx);
|
||||
let compare_reg_checkpoint = ctx.reg_checkpoint();
|
||||
|
||||
let cases = &sw.cases;
|
||||
for case in cases.iter() {
|
||||
if let Some(test) = &case.test {
|
||||
self.count_expression(test, ctx);
|
||||
ctx.projected_pc += 1; // EQ
|
||||
ctx.alloc_reg(); // eq result
|
||||
ctx.projected_pc += 1; // JMP_IF_TRUE
|
||||
ctx.restore_reg_checkpoint(compare_reg_checkpoint);
|
||||
}
|
||||
}
|
||||
|
||||
let has_default = cases.iter().any(|c| c.test.is_none());
|
||||
if !has_default {
|
||||
ctx.projected_pc += 1; // JMP to SwitchEnd (no match)
|
||||
}
|
||||
|
||||
for (case_idx, case) in cases.iter().enumerate() {
|
||||
let case_label = Label::SwitchCase(id, case_idx as u32);
|
||||
ctx.label_map.insert(case_label, ctx.projected_pc);
|
||||
for s in &case.consequent {
|
||||
self.count_statement(s, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
ctx.label_map.insert(end_label, ctx.projected_pc);
|
||||
ctx.pop_switch();
|
||||
}
|
||||
Statement::BreakStatement(_) => {
|
||||
ctx.projected_pc += 1; // JMP
|
||||
}
|
||||
Statement::ContinueStatement(_) => {
|
||||
ctx.projected_pc += 1; // JMP
|
||||
}
|
||||
Statement::BlockStatement(block) => {
|
||||
for s in &block.body {
|
||||
self.count_statement(s, ctx);
|
||||
}
|
||||
}
|
||||
Statement::FunctionDeclaration(fd) => {
|
||||
let name = if let Some(id) = &fd.id {
|
||||
id.name.to_string()
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Hoisting: declare function name as initialized
|
||||
let func_reg = ctx.alloc_reg();
|
||||
let _ = ctx.declare_initialized(&name, func_reg, VariableDeclarationKind::Var, false);
|
||||
|
||||
// Body is compiled in the emit pass only.
|
||||
// FD emits LOAD_CONST(BytecodeFunc) + STORE_VAR
|
||||
ctx.count_words(2);
|
||||
}
|
||||
Statement::ClassDeclaration(class) => {
|
||||
ctx.alloc_reg(); // class binding reg
|
||||
self.count_class(class, ctx);
|
||||
ctx.projected_pc += 1; // STORE_VAR binding <- ctor
|
||||
}
|
||||
Statement::ThrowStatement(ts) => {
|
||||
self.count_expression(&ts.argument, ctx);
|
||||
ctx.projected_pc += 1; // THROW
|
||||
}
|
||||
Statement::TryStatement(ts) => {
|
||||
let id = ctx.next_label_id();
|
||||
let catch_label = Label::CatchBody(id);
|
||||
let try_end_label = Label::TryEnd(id);
|
||||
let has_catch = ts.handler.is_some();
|
||||
let has_finally = ts.finalizer.is_some();
|
||||
|
||||
ctx.alloc_reg(); // result_reg
|
||||
|
||||
if has_finally {
|
||||
ctx.projected_pc += 1; // TRY_FINALLY_BEGIN (before try body)
|
||||
}
|
||||
|
||||
if has_catch {
|
||||
ctx.projected_pc += 1; // TRY_BEGIN (before try body)
|
||||
}
|
||||
|
||||
for s in &ts.block.body {
|
||||
self.count_statement(s, ctx);
|
||||
}
|
||||
ctx.projected_pc += 1; // LOAD_VAR result_reg (if try body has result)
|
||||
|
||||
if has_catch {
|
||||
ctx.projected_pc += 1; // TRY_END
|
||||
}
|
||||
|
||||
let jmp_needed = has_catch || has_finally;
|
||||
if jmp_needed {
|
||||
ctx.projected_pc += 1; // JMP
|
||||
}
|
||||
|
||||
ctx.label_map.insert(catch_label, ctx.projected_pc);
|
||||
if let Some(catch) = &ts.handler {
|
||||
ctx.push_scope();
|
||||
if let Some(_param) = &catch.param {
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1; // STORE_VAR
|
||||
}
|
||||
for cs in &catch.body.body {
|
||||
self.count_statement(cs, ctx);
|
||||
}
|
||||
ctx.projected_pc += 1; // LOAD_VAR result_reg (if catch body has result)
|
||||
ctx.pop_scope();
|
||||
}
|
||||
|
||||
if let Some(finally) = &ts.finalizer {
|
||||
let finally_label = Label::FinallyBody(id);
|
||||
ctx.label_map.insert(finally_label, ctx.projected_pc);
|
||||
for fs in &finally.body {
|
||||
self.count_statement(fs, ctx);
|
||||
}
|
||||
ctx.projected_pc += 1; // LOAD_VAR result_reg (if finally has result)
|
||||
ctx.projected_pc += 1; // TRY_FINALLY_END
|
||||
}
|
||||
|
||||
ctx.label_map.insert(try_end_label, ctx.projected_pc);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::compiler::Label;
|
||||
use crate::opcode::{self, OpCode};
|
||||
use oxide_bytecode::module::Constant;
|
||||
use oxide_bytecode::opcode::{self, OpCode};
|
||||
use oxide_parser::{
|
||||
AssignmentOperator, AssignmentTarget, AssignmentTargetMaybeDefault, AssignmentTargetProperty, BindingPattern,
|
||||
ChainElement, Class, ClassElement, Expression, ForStatementInit, ForStatementLeft, LogicalOperator,
|
||||
@@ -10,8 +11,6 @@ use oxide_parser::{
|
||||
use crate::compiler::{
|
||||
is_int_literal, is_side_effect_free, BinaryOperator, CompileCtx, Compiler, FunctionBodyContext, ParamSpec,
|
||||
};
|
||||
use crate::module::Constant;
|
||||
|
||||
impl Compiler {
|
||||
fn emit_optional_guard(&self, reg: u8, short_label: Label, ctx: &mut CompileCtx) -> Result<(), String> {
|
||||
let short_pos = ctx.resolve_label(short_label)?;
|
||||
|
||||
@@ -4,6 +4,4 @@ pub mod compiler;
|
||||
pub mod counter;
|
||||
pub mod emitter;
|
||||
pub mod hash;
|
||||
pub mod module;
|
||||
pub mod opcode;
|
||||
pub mod symbol_table;
|
||||
|
||||
@@ -7,7 +7,7 @@ fn parse_to_hash(source: &str) -> u64 {
|
||||
oxide_compiler::compiler::structural_hash(&program)
|
||||
}
|
||||
|
||||
fn compile_source(source: &str) -> oxide_compiler::module::CompiledModule {
|
||||
fn compile_source(source: &str) -> oxide_bytecode::module::CompiledModule {
|
||||
let allocator = Allocator::default();
|
||||
let program = oxide_parser::parse(&allocator, source).expect("parse failed");
|
||||
let compiler = Compiler::new();
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use oxide_bytecode::module::{CompiledModule, Constant};
|
||||
use oxide_bytecode::opcode::{self, OpCode};
|
||||
use oxide_compiler::compiler::Compiler;
|
||||
use oxide_compiler::opcode::{self, OpCode};
|
||||
use oxide_parser::Allocator;
|
||||
|
||||
fn compile_source(source: &str) -> oxide_compiler::module::CompiledModule {
|
||||
fn compile_source(source: &str) -> CompiledModule {
|
||||
let allocator = Allocator::default();
|
||||
let program = oxide_parser::parse(&allocator, source).expect("parse failed");
|
||||
let compiler = Compiler::new();
|
||||
@@ -58,7 +59,7 @@ fn compile_constants() {
|
||||
let module = compile_source("42");
|
||||
|
||||
assert!(!module.constants.is_empty());
|
||||
assert_eq!(module.constants[0], oxide_compiler::compiler::Constant::Int(42));
|
||||
assert_eq!(module.constants[0], Constant::Int(42));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -67,7 +68,7 @@ fn compile_dedups_identical_string_constants() {
|
||||
let count = module
|
||||
.constants
|
||||
.iter()
|
||||
.filter(|c| matches!(c, oxide_compiler::compiler::Constant::String(s) if s == "hello"))
|
||||
.filter(|c| matches!(c, Constant::String(s) if s == "hello"))
|
||||
.count();
|
||||
assert_eq!(count, 1, "identical string constants should be interned once per module");
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use oxide_bytecode::module::{CompiledModule, Constant};
|
||||
use oxide_bytecode::opcode::{self, OpCode};
|
||||
use oxide_compiler::compiler::Compiler;
|
||||
use oxide_compiler::opcode::{self, OpCode};
|
||||
use oxide_parser::Allocator;
|
||||
|
||||
fn compile_source(source: &str) -> oxide_compiler::module::CompiledModule {
|
||||
fn compile_source(source: &str) -> CompiledModule {
|
||||
let allocator = Allocator::default();
|
||||
let program = oxide_parser::parse(&allocator, source).expect("parse failed");
|
||||
let compiler = Compiler::new();
|
||||
@@ -13,7 +14,7 @@ fn compile_source(source: &str) -> oxide_compiler::module::CompiledModule {
|
||||
fn compile_var_declaration() {
|
||||
let module = compile_source("var x = 42;");
|
||||
assert!(!module.bytecode.is_empty());
|
||||
assert_eq!(module.constants[0], oxide_compiler::compiler::Constant::Int(42));
|
||||
assert_eq!(module.constants[0], Constant::Int(42));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use oxide_code_cache::CodeForge;
|
||||
use oxide_compiler::compiler::Compiler;
|
||||
use oxide_kernel::code_forge::CodeForge;
|
||||
use oxide_parser::Allocator;
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn compile_source(source: &str) -> oxide_compiler::module::CompiledModule {
|
||||
fn compile_source(source: &str) -> oxide_bytecode::module::CompiledModule {
|
||||
let allocator = Allocator::default();
|
||||
let program = oxide_parser::parse(&allocator, source).expect("parse failed");
|
||||
let compiler = Compiler::new();
|
||||
@@ -29,8 +29,13 @@ fn structural_hash_hit() {
|
||||
let allocator = Allocator::default();
|
||||
let program = oxide_parser::parse(&allocator, "1 + 2").expect("parse failed");
|
||||
|
||||
let m1 = forge.get_or_compile(&program, &compiler).expect("first compile");
|
||||
let m2 = forge.get_or_compile(&program, &compiler).expect("second compile");
|
||||
let hash = oxide_compiler::compiler::compiled_module_hash(&program);
|
||||
let m1 = forge
|
||||
.get_or_insert_with(hash, || compiler.compile(&program))
|
||||
.expect("first compile");
|
||||
let m2 = forge
|
||||
.get_or_insert_with(hash, || compiler.compile(&program))
|
||||
.expect("second compile");
|
||||
|
||||
assert_eq!((m1.bytecode.len(), m1.n_registers), (m2.bytecode.len(), m2.n_registers));
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use oxide_bytecode::module::{CompiledModule, Constant};
|
||||
use oxide_bytecode::opcode::{self, OpCode};
|
||||
use oxide_compiler::compiler::Compiler;
|
||||
use oxide_compiler::opcode::{self, OpCode};
|
||||
use oxide_parser::Allocator;
|
||||
|
||||
fn compile_source(source: &str) -> oxide_compiler::module::CompiledModule {
|
||||
fn compile_source(source: &str) -> CompiledModule {
|
||||
let allocator = Allocator::default();
|
||||
let program = oxide_parser::parse(&allocator, source).expect("parse failed");
|
||||
let compiler = Compiler::new();
|
||||
@@ -13,7 +14,7 @@ fn compile_source(source: &str) -> oxide_compiler::module::CompiledModule {
|
||||
fn symbol_table_declare_and_lookup() {
|
||||
let module = compile_source("var x = 42;");
|
||||
assert!(!module.bytecode.is_empty());
|
||||
assert_eq!(module.constants[0], oxide_compiler::compiler::Constant::Int(42));
|
||||
assert_eq!(module.constants[0], Constant::Int(42));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -7,7 +7,8 @@ rust-version.workspace = true
|
||||
description = "OxideJS - Shared kernel (CodeForge, ShapeForge, PropForge, StringForge, BuiltinWorld)"
|
||||
|
||||
[dependencies]
|
||||
oxide_compiler = { path = "../oxide_compiler" }
|
||||
oxide_bytecode = { path = "../oxide_bytecode" }
|
||||
oxide_code_cache = { path = "../oxide_code_cache" }
|
||||
oxide_parser = { path = "../oxide_parser" }
|
||||
oxide_types = { path = "../oxide_types" }
|
||||
dashmap.workspace = true
|
||||
|
||||
@@ -1,141 +1 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use dashmap::DashMap;
|
||||
|
||||
use oxide_compiler::compiler::Compiler;
|
||||
use oxide_compiler::module::CompiledModule;
|
||||
|
||||
use crate::{kernel_debug, kernel_info};
|
||||
|
||||
pub use oxide_compiler::compiler::{compiled_module_hash, structural_hash};
|
||||
|
||||
/// Shared compiled-module cache.
|
||||
///
|
||||
/// Concurrency model:
|
||||
/// - Read-heavy after warmup: cache hits dominate steady-state execution.
|
||||
/// - Backed by a fixed-shard `DashMap` so lookup contention stays predictable across
|
||||
/// developer machines and CI hosts.
|
||||
pub struct CodeForge {
|
||||
map: DashMap<u64, Arc<CompiledModule>>,
|
||||
}
|
||||
|
||||
impl CodeForge {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
map: DashMap::with_shard_amount(16),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_or_compile(
|
||||
&self, program: &oxide_parser::Program, compiler: &Compiler,
|
||||
) -> Result<Arc<CompiledModule>, String> {
|
||||
let hash = compiled_module_hash(program);
|
||||
|
||||
if let Some(module) = self.map.get(&hash) {
|
||||
kernel_info!("CodeForge hit compiled_hash={:#x}", hash);
|
||||
return Ok(Arc::clone(&module));
|
||||
}
|
||||
|
||||
let module = compiler.compile(program)?;
|
||||
let module = Arc::new(module);
|
||||
kernel_debug!("CodeForge miss compiled_hash={:#x}", hash);
|
||||
|
||||
Ok(Arc::clone(self.map.entry(hash).or_insert(module).value()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CodeForge {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use oxide_parser::Allocator;
|
||||
|
||||
#[test]
|
||||
fn test_structural_hash_same_input() {
|
||||
let a1 = Allocator::default();
|
||||
let a2 = Allocator::default();
|
||||
let p1 = oxide_parser::parse(&a1, "1 + 2").expect("parse failed");
|
||||
let p2 = oxide_parser::parse(&a2, "1 + 2").expect("parse failed");
|
||||
assert_eq!(structural_hash(&p1), structural_hash(&p2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_structural_hash_same_structure() {
|
||||
let a1 = Allocator::default();
|
||||
let a2 = Allocator::default();
|
||||
let p1 = oxide_parser::parse(&a1, "var x = 1; var y = 2;").expect("parse failed");
|
||||
let p2 = oxide_parser::parse(&a2, "var a = 1; var b = 2;").expect("parse failed");
|
||||
assert_eq!(structural_hash(&p1), structural_hash(&p2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compiled_module_hash_distinguishes_identifier_loads() {
|
||||
let a1 = Allocator::default();
|
||||
let a2 = Allocator::default();
|
||||
let p1 = oxide_parser::parse(&a1, "var x = 1; var y = 2; x").expect("parse failed");
|
||||
let p2 = oxide_parser::parse(&a2, "var x = 1; var y = 2; y").expect("parse failed");
|
||||
|
||||
assert_eq!(structural_hash(&p1), structural_hash(&p2));
|
||||
assert_ne!(compiled_module_hash(&p1), compiled_module_hash(&p2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_structural_hash_different_ops() {
|
||||
let a1 = Allocator::default();
|
||||
let a2 = Allocator::default();
|
||||
let p1 = oxide_parser::parse(&a1, "1 + 2").expect("parse failed");
|
||||
let p2 = oxide_parser::parse(&a2, "1 - 2").expect("parse failed");
|
||||
assert_ne!(structural_hash(&p1), structural_hash(&p2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cache_hit() {
|
||||
let forge = CodeForge::new();
|
||||
let compiler = Compiler::new();
|
||||
let allocator = Allocator::default();
|
||||
let program = oxide_parser::parse(&allocator, "1 + 2").expect("parse failed");
|
||||
|
||||
let first = forge.get_or_compile(&program, &compiler).expect("first compile");
|
||||
let second = forge.get_or_compile(&program, &compiler).expect("second compile");
|
||||
|
||||
assert_eq!((first.bytecode.len(), first.n_registers), (second.bytecode.len(), second.n_registers));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cache_miss_new_program() {
|
||||
let forge = CodeForge::new();
|
||||
let compiler = Compiler::new();
|
||||
|
||||
let a1 = Allocator::default();
|
||||
let p1 = oxide_parser::parse(&a1, "1 + 2").expect("parse failed");
|
||||
let a2 = Allocator::default();
|
||||
let p2 = oxide_parser::parse(&a2, "3 * 4").expect("parse failed");
|
||||
|
||||
let m1 = forge.get_or_compile(&p1, &compiler).expect("compile p1");
|
||||
let m2 = forge.get_or_compile(&p2, &compiler).expect("compile p2");
|
||||
|
||||
assert_ne!(m1.bytecode, m2.bytecode);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cache_miss_same_shape_different_binding_program() {
|
||||
let forge = CodeForge::new();
|
||||
let compiler = Compiler::new();
|
||||
|
||||
let a1 = Allocator::default();
|
||||
let p1 = oxide_parser::parse(&a1, "var x = 1; var y = 2; x").expect("parse failed");
|
||||
let a2 = Allocator::default();
|
||||
let p2 = oxide_parser::parse(&a2, "var x = 1; var y = 2; y").expect("parse failed");
|
||||
|
||||
let m1 = forge.get_or_compile(&p1, &compiler).expect("compile p1");
|
||||
let m2 = forge.get_or_compile(&p2, &compiler).expect("compile p2");
|
||||
|
||||
assert!(!Arc::ptr_eq(&m1, &m2));
|
||||
assert_ne!(m1.bytecode, m2.bytecode);
|
||||
}
|
||||
}
|
||||
pub use oxide_code_cache::CodeForge;
|
||||
|
||||
@@ -11,6 +11,7 @@ name = "test262-runner"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
oxide_bytecode = { path = "../oxide_bytecode" }
|
||||
oxide_compiler = { path = "../oxide_compiler" }
|
||||
oxide_vm = { path = "../oxide_vm" }
|
||||
oxide_parser = { path = "../oxide_parser" }
|
||||
|
||||
@@ -7,7 +7,7 @@ rust-version.workspace = true
|
||||
description = "OxideJS - Register-based VM with epoch arena memory"
|
||||
|
||||
[dependencies]
|
||||
oxide_compiler = { path = "../oxide_compiler" }
|
||||
oxide_bytecode = { path = "../oxide_bytecode" }
|
||||
oxide_kernel = { path = "../oxide_kernel" }
|
||||
oxide_types = { path = "../oxide_types" }
|
||||
bumpalo.workspace = true
|
||||
@@ -23,5 +23,6 @@ unicode-normalization.workspace = true
|
||||
tracing.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
oxide_compiler = { path = "../oxide_compiler" }
|
||||
proptest.workspace = true
|
||||
oxide_parser = { path = "../oxide_parser" }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use oxide_compiler::opcode::OpCode;
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
use oxide_types::object::JsObject;
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use oxide_compiler::opcode::OpCode;
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
use oxide_kernel::prop_forge::PropTemplate;
|
||||
use oxide_kernel::{ic_debug, ic_trace};
|
||||
use oxide_types::object::JsObject;
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use oxide_compiler::module::CompiledModule;
|
||||
use oxide_compiler::opcode::{self, OpCode};
|
||||
use oxide_bytecode::module::CompiledModule;
|
||||
use oxide_bytecode::opcode::{self, OpCode};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
pub use crate::bindings::init_kernel_builtins;
|
||||
@@ -1286,7 +1286,7 @@ impl Default for Vm {
|
||||
mod tests {
|
||||
use super::{opcode, JsValue, TryHandler, Vm};
|
||||
use crate::native::NativeResult;
|
||||
use oxide_compiler::module::{CompiledModule, Constant};
|
||||
use oxide_bytecode::module::{CompiledModule, Constant};
|
||||
use oxide_types::object::NativeFnPtr;
|
||||
use oxide_types::object::{JsObject, PropAttributes};
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::vm::{FrameContinuation, TryHandler, Vm};
|
||||
use oxide_compiler::opcode;
|
||||
use oxide_bytecode::opcode;
|
||||
use oxide_types::object::PropAttributes;
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use oxide_compiler::module::CompiledModule;
|
||||
use oxide_compiler::opcode;
|
||||
use oxide_bytecode::module::CompiledModule;
|
||||
use oxide_bytecode::opcode;
|
||||
|
||||
use crate::vm::{CallFrame, InlineSyncState, Vm};
|
||||
use oxide_types::object::JsObject;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use oxide_compiler::compiler::Constant;
|
||||
use oxide_bytecode::module::Constant;
|
||||
|
||||
use crate::bindings;
|
||||
use crate::vm::{native_fn_ptr_to_fn, Vm};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use oxide_bytecode::module::CompiledModule;
|
||||
use oxide_compiler::compiler::Compiler;
|
||||
use oxide_compiler::module::CompiledModule;
|
||||
use oxide_parser::Allocator;
|
||||
use oxide_vm::vm::Vm;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user