feat: 搭建原生函数基础设施
This commit is contained in:
Generated
+1
@@ -783,6 +783,7 @@ dependencies = [
|
|||||||
name = "oxide_kernel"
|
name = "oxide_kernel"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
"hashbrown 0.14.5",
|
"hashbrown 0.14.5",
|
||||||
"oxide_compiler",
|
"oxide_compiler",
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ pub(crate) struct CompileCtx {
|
|||||||
pub(crate) switch_stack: Vec<Label>,
|
pub(crate) switch_stack: Vec<Label>,
|
||||||
pub(crate) label_counter: u32,
|
pub(crate) label_counter: u32,
|
||||||
pub(crate) projected_pc: usize,
|
pub(crate) projected_pc: usize,
|
||||||
|
pub(crate) builtin_reg_map: Vec<(String, u8)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CompileCtx {
|
impl CompileCtx {
|
||||||
@@ -72,6 +73,7 @@ impl CompileCtx {
|
|||||||
switch_stack: Vec::new(),
|
switch_stack: Vec::new(),
|
||||||
label_counter: 0,
|
label_counter: 0,
|
||||||
projected_pc: 0,
|
projected_pc: 0,
|
||||||
|
builtin_reg_map: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,6 +166,19 @@ impl CompileCtx {
|
|||||||
pub(crate) fn current_switch(&self) -> Option<&Label> {
|
pub(crate) fn current_switch(&self) -> Option<&Label> {
|
||||||
self.switch_stack.last()
|
self.switch_stack.last()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn pre_register_builtins(&mut self) {
|
||||||
|
let builtins = [
|
||||||
|
"NaN", "undefined", "Object", "Array", "String", "Number", "Boolean",
|
||||||
|
"Function", "Error", "Math", "JSON", "Promise", "parseInt", "parseFloat",
|
||||||
|
"isNaN", "isFinite",
|
||||||
|
];
|
||||||
|
for name in &builtins {
|
||||||
|
let reg = self.alloc_reg();
|
||||||
|
self.symbols.pre_register_global(name, reg);
|
||||||
|
self.builtin_reg_map.push((name.to_string(), reg));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Compiler {
|
impl Compiler {
|
||||||
@@ -173,6 +188,7 @@ impl Compiler {
|
|||||||
|
|
||||||
pub fn compile(&self, program: &oxide_parser::Program) -> Result<CompiledModule, String> {
|
pub fn compile(&self, program: &oxide_parser::Program) -> Result<CompiledModule, String> {
|
||||||
let mut ctx = CompileCtx::new();
|
let mut ctx = CompileCtx::new();
|
||||||
|
ctx.pre_register_builtins();
|
||||||
|
|
||||||
for stmt in &program.body {
|
for stmt in &program.body {
|
||||||
self.count_statement(stmt, &mut ctx);
|
self.count_statement(stmt, &mut ctx);
|
||||||
@@ -205,6 +221,7 @@ impl Compiler {
|
|||||||
bytecode: ctx.bytecode,
|
bytecode: ctx.bytecode,
|
||||||
constants: ctx.constants,
|
constants: ctx.constants,
|
||||||
n_registers: ctx.max_regs,
|
n_registers: ctx.max_regs,
|
||||||
|
builtin_reg_map: ctx.builtin_reg_map,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -199,13 +199,14 @@ impl Compiler {
|
|||||||
}
|
}
|
||||||
Expression::CallExpression(call) => {
|
Expression::CallExpression(call) => {
|
||||||
self.count_expression(&call.callee, ctx);
|
self.count_expression(&call.callee, ctx);
|
||||||
|
ctx.alloc_reg();
|
||||||
for arg in &call.arguments {
|
for arg in &call.arguments {
|
||||||
if let Some(expr) = arg.as_expression() {
|
if let Some(expr) = arg.as_expression() {
|
||||||
self.count_expression(expr, ctx);
|
self.count_expression(expr, ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx.alloc_reg();
|
ctx.alloc_reg();
|
||||||
ctx.projected_pc += 1; // CALL
|
ctx.projected_pc += 2; // CALL + extension word (arg_count)
|
||||||
}
|
}
|
||||||
Expression::AssignmentExpression(assign) => {
|
Expression::AssignmentExpression(assign) => {
|
||||||
if let oxide_parser::AssignmentTarget::StaticMemberExpression(member) = &assign.left
|
if let oxide_parser::AssignmentTarget::StaticMemberExpression(member) = &assign.left
|
||||||
|
|||||||
@@ -668,10 +668,10 @@ impl Compiler {
|
|||||||
ctx.emit(opcode::encode(op, var_reg, rhs, 0));
|
ctx.emit(opcode::encode(op, var_reg, rhs, 0));
|
||||||
Ok(var_reg)
|
Ok(var_reg)
|
||||||
} else {
|
} else {
|
||||||
return Err(format!(
|
Err(format!(
|
||||||
"compound assignment operator {:?} not supported",
|
"compound assignment operator {:?} not supported",
|
||||||
assign.operator
|
assign.operator
|
||||||
));
|
))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let val_reg = self.emit_expression(&assign.right, ctx)?;
|
let val_reg = self.emit_expression(&assign.right, ctx)?;
|
||||||
@@ -738,6 +738,37 @@ impl Compiler {
|
|||||||
Ok(r)
|
Ok(r)
|
||||||
}
|
}
|
||||||
Expression::ParenthesizedExpression(p) => self.emit_expression(&p.expression, ctx),
|
Expression::ParenthesizedExpression(p) => self.emit_expression(&p.expression, ctx),
|
||||||
|
Expression::CallExpression(call) => {
|
||||||
|
let callee_reg = self.emit_expression(&call.callee, ctx)?;
|
||||||
|
let this_idx = ctx.add_constant(Constant::Undefined);
|
||||||
|
let this_reg = ctx.alloc_reg();
|
||||||
|
ctx.emit(opcode::encode(
|
||||||
|
OpCode::LOAD_CONST,
|
||||||
|
this_reg,
|
||||||
|
(this_idx & 0xFF) as u8,
|
||||||
|
((this_idx >> 8) & 0xFF) as u8,
|
||||||
|
));
|
||||||
|
let mut arg_regs = Vec::new();
|
||||||
|
for arg in &call.arguments {
|
||||||
|
if let Some(expr) = arg.as_expression() {
|
||||||
|
arg_regs.push(self.emit_expression(expr, ctx)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let first_arg_reg = if arg_regs.is_empty() {
|
||||||
|
0u8
|
||||||
|
} else {
|
||||||
|
arg_regs[0]
|
||||||
|
};
|
||||||
|
let result_reg = ctx.alloc_reg();
|
||||||
|
ctx.emit(opcode::encode(
|
||||||
|
OpCode::CALL,
|
||||||
|
callee_reg,
|
||||||
|
this_reg,
|
||||||
|
first_arg_reg,
|
||||||
|
));
|
||||||
|
ctx.emit(arg_regs.len() as u32);
|
||||||
|
Ok(result_reg)
|
||||||
|
}
|
||||||
_ => Err(format!("unsupported expression type: {:?}", expr)),
|
_ => Err(format!("unsupported expression type: {:?}", expr)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -125,8 +125,15 @@ fn hash_expression(expr: &Expression, h: &mut rustc_hash::FxHasher) {
|
|||||||
std::mem::discriminant(&un.operator).hash(h);
|
std::mem::discriminant(&un.operator).hash(h);
|
||||||
hash_expression(&un.argument, h);
|
hash_expression(&un.argument, h);
|
||||||
}
|
}
|
||||||
Expression::CallExpression(_) => {
|
Expression::CallExpression(call) => {
|
||||||
2u8.hash(h);
|
2u8.hash(h);
|
||||||
|
(call.arguments.len() as u32).hash(h);
|
||||||
|
hash_expression(&call.callee, h);
|
||||||
|
for arg in &call.arguments {
|
||||||
|
if let Some(expr) = arg.as_expression() {
|
||||||
|
hash_expression(expr, h);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Expression::LogicalExpression(log) => {
|
Expression::LogicalExpression(log) => {
|
||||||
3u8.hash(h);
|
3u8.hash(h);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ pub struct CompiledModule {
|
|||||||
pub bytecode: Vec<opcode::Instr>,
|
pub bytecode: Vec<opcode::Instr>,
|
||||||
pub constants: Vec<Constant>,
|
pub constants: Vec<Constant>,
|
||||||
pub n_registers: u8,
|
pub n_registers: u8,
|
||||||
|
pub builtin_reg_map: Vec<(String, u8)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for CompiledModule {
|
impl fmt::Display for CompiledModule {
|
||||||
|
|||||||
@@ -73,6 +73,13 @@ impl SymbolTable {
|
|||||||
reg_for_new
|
reg_for_new
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn pre_register_global(&mut self, name: &str, reg: u8) {
|
||||||
|
self.scopes[0].entry(name.to_string()).or_insert(Binding {
|
||||||
|
reg,
|
||||||
|
initialized: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
pub fn init_var(&mut self, name: &str) {
|
pub fn init_var(&mut self, name: &str) {
|
||||||
let current = self.scopes.last_mut().unwrap();
|
let current = self.scopes.last_mut().unwrap();
|
||||||
if let Some(b) = current.get_mut(name) {
|
if let Some(b) = current.get_mut(name) {
|
||||||
|
|||||||
@@ -13,3 +13,4 @@ oxide_types = { path = "../oxide_types" }
|
|||||||
dashmap.workspace = true
|
dashmap.workspace = true
|
||||||
hashbrown.workspace = true
|
hashbrown.workspace = true
|
||||||
rustc-hash.workspace = true
|
rustc-hash.workspace = true
|
||||||
|
bumpalo.workspace = true
|
||||||
|
|||||||
@@ -151,6 +151,30 @@ impl BuiltinWorld {
|
|||||||
symbol_constructor,
|
symbol_constructor,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn bind_method(
|
||||||
|
proto: &mut JsObject,
|
||||||
|
shape_forge: &ShapeForge,
|
||||||
|
string_forge: &StringForge,
|
||||||
|
method_name: &str,
|
||||||
|
native_fn_ptr: *const (),
|
||||||
|
arg_count: u8,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let si = string_forge.intern(method_name).0;
|
||||||
|
let mut wrapper = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
|
||||||
|
wrapper.set_function(true);
|
||||||
|
wrapper.set_native_fn(Some(native_fn_ptr));
|
||||||
|
wrapper.set_native_arg_count(arg_count);
|
||||||
|
let wrapper_val = JsValue::from_js_object(Box::into_raw(wrapper));
|
||||||
|
let new_offset = proto.prop_count();
|
||||||
|
let new_shape = shape_forge.make_shape(proto.shape_id(), si);
|
||||||
|
proto.set_shape_id(new_shape);
|
||||||
|
proto.set_prop_count(new_offset + 1);
|
||||||
|
let bump = bumpalo::Bump::new();
|
||||||
|
proto.set_prop_expand(new_offset, wrapper_val, &bump);
|
||||||
|
proto.bump_generation();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -2,10 +2,14 @@
|
|||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use oxide_types::mem::P;
|
||||||
|
use oxide_types::object::JsObject;
|
||||||
|
use oxide_types::value::JsValue;
|
||||||
|
|
||||||
use crate::builtin::BuiltinWorld;
|
use crate::builtin::BuiltinWorld;
|
||||||
use crate::code_forge::CodeForge;
|
use crate::code_forge::CodeForge;
|
||||||
use crate::prop_forge::PropForge;
|
use crate::prop_forge::PropForge;
|
||||||
use crate::shape_forge::ShapeForge;
|
use crate::shape_forge::{ShapeForge, EMPTY_SHAPE_ID};
|
||||||
use crate::string_forge::StringForge;
|
use crate::string_forge::StringForge;
|
||||||
|
|
||||||
pub struct KernelConfig {
|
pub struct KernelConfig {
|
||||||
@@ -65,6 +69,7 @@ pub struct OxideKernel {
|
|||||||
pub code_forge: Arc<CodeForge>,
|
pub code_forge: Arc<CodeForge>,
|
||||||
pub prop_forge: Arc<PropForge>,
|
pub prop_forge: Arc<PropForge>,
|
||||||
pub builtin_world: Arc<BuiltinWorld>,
|
pub builtin_world: Arc<BuiltinWorld>,
|
||||||
|
pub global_object: P<JsObject>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OxideKernel {
|
impl OxideKernel {
|
||||||
@@ -75,6 +80,23 @@ impl OxideKernel {
|
|||||||
let code_forge = Arc::new(CodeForge::new());
|
let code_forge = Arc::new(CodeForge::new());
|
||||||
let prop_forge = Arc::new(PropForge::new());
|
let prop_forge = Arc::new(PropForge::new());
|
||||||
|
|
||||||
|
let mut global_obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null());
|
||||||
|
|
||||||
|
let si_nan = string_forge.intern("NaN").0;
|
||||||
|
let si_undef = string_forge.intern("undefined").0;
|
||||||
|
|
||||||
|
let nan_shape = shape_forge.make_shape(EMPTY_SHAPE_ID, si_nan);
|
||||||
|
global_obj.set_shape_id(nan_shape);
|
||||||
|
global_obj.set_prop_count(1);
|
||||||
|
global_obj.set_inline_prop(0, JsValue::float(f64::NAN));
|
||||||
|
|
||||||
|
let undef_shape = shape_forge.make_shape(nan_shape, si_undef);
|
||||||
|
global_obj.set_shape_id(undef_shape);
|
||||||
|
global_obj.set_prop_count(2);
|
||||||
|
global_obj.set_inline_prop(1, JsValue::undefined());
|
||||||
|
|
||||||
|
let global_object = P::new(global_obj);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
config,
|
config,
|
||||||
string_forge,
|
string_forge,
|
||||||
@@ -82,6 +104,7 @@ impl OxideKernel {
|
|||||||
code_forge,
|
code_forge,
|
||||||
prop_forge,
|
prop_forge,
|
||||||
builtin_world,
|
builtin_world,
|
||||||
|
global_object,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,6 +131,10 @@ impl OxideKernel {
|
|||||||
pub fn config(&self) -> &KernelConfig {
|
pub fn config(&self) -> &KernelConfig {
|
||||||
&self.config
|
&self.config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn global_object(&self) -> &P<JsObject> {
|
||||||
|
&self.global_object
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -2,37 +2,40 @@ use crate::value::JsValue;
|
|||||||
|
|
||||||
pub type ShapeId = u32;
|
pub type ShapeId = u32;
|
||||||
|
|
||||||
/// Layout:
|
/// Layout (72B):
|
||||||
/// header: u32 bits
|
/// header: u32 bits
|
||||||
/// [0:23] shape_id
|
/// [0:23] shape_id
|
||||||
/// [24:28] prop_count (5 bits, 0-31)
|
/// [24:28] prop_count (5 bits, 0-31)
|
||||||
/// [29] is_array
|
/// [29] is_array
|
||||||
/// [30] is_extensible
|
/// [30] is_extensible
|
||||||
/// [31] is_function
|
/// [31] is_function
|
||||||
|
/// native_arg_count: u8 (1 byte + 3 pad)
|
||||||
/// inline[0..=3]: 4 x JsValue (32 bytes)
|
/// inline[0..=3]: 4 x JsValue (32 bytes)
|
||||||
/// overflow: *mut JsValue (8 bytes, null if ≤4 properties)
|
/// overflow: *mut JsValue (8 bytes, null if <=4 properties)
|
||||||
/// proto: JsValue (8 bytes)
|
/// proto: JsValue (8 bytes)
|
||||||
/// generation: u32 (4 bytes)
|
/// generation: u32 (4 bytes + 4 pad)
|
||||||
/// _pad: u32 (4 bytes, 64B total = one cache line)
|
/// native_fn: u64 (8 bytes, 0 = None sentinel)
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct JsObject {
|
pub struct JsObject {
|
||||||
header: u32,
|
header: u32,
|
||||||
|
native_arg_count: u8,
|
||||||
inline: [JsValue; 4],
|
inline: [JsValue; 4],
|
||||||
overflow: *mut JsValue,
|
overflow: *mut JsValue,
|
||||||
proto: JsValue,
|
proto: JsValue,
|
||||||
generation: u32,
|
generation: u32,
|
||||||
_pad: u32,
|
native_fn: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JsObject {
|
impl JsObject {
|
||||||
pub fn new_empty(shape_id: ShapeId, proto: JsValue) -> Self {
|
pub fn new_empty(shape_id: ShapeId, proto: JsValue) -> Self {
|
||||||
Self {
|
Self {
|
||||||
header: (shape_id & 0x00FF_FFFF) | (1 << 30),
|
header: (shape_id & 0x00FF_FFFF) | (1 << 30),
|
||||||
|
native_arg_count: 0,
|
||||||
inline: [JsValue::undefined(); 4],
|
inline: [JsValue::undefined(); 4],
|
||||||
overflow: std::ptr::null_mut(),
|
overflow: std::ptr::null_mut(),
|
||||||
proto,
|
proto,
|
||||||
generation: 1,
|
generation: 1,
|
||||||
_pad: 0,
|
native_fn: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,11 +47,12 @@ impl JsObject {
|
|||||||
) -> Self {
|
) -> Self {
|
||||||
let mut obj = Self {
|
let mut obj = Self {
|
||||||
header: (shape_id & 0x00FF_FFFF) | (1 << 30) | (1 << 29),
|
header: (shape_id & 0x00FF_FFFF) | (1 << 30) | (1 << 29),
|
||||||
|
native_arg_count: 0,
|
||||||
inline: [JsValue::undefined(); 4],
|
inline: [JsValue::undefined(); 4],
|
||||||
overflow: std::ptr::null_mut(),
|
overflow: std::ptr::null_mut(),
|
||||||
proto,
|
proto,
|
||||||
generation: 1,
|
generation: 1,
|
||||||
_pad: 0,
|
native_fn: 0,
|
||||||
};
|
};
|
||||||
let count = (n_elements as u8).min(31);
|
let count = (n_elements as u8).min(31);
|
||||||
obj.set_prop_count(count);
|
obj.set_prop_count(count);
|
||||||
@@ -189,6 +193,26 @@ impl JsObject {
|
|||||||
self.generation = self.generation.wrapping_add(1);
|
self.generation = self.generation.wrapping_add(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn native_fn(&self) -> Option<*const ()> {
|
||||||
|
if self.native_fn == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(self.native_fn as *const ())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_native_fn(&mut self, ptr: Option<*const ()>) {
|
||||||
|
self.native_fn = ptr.map_or(0, |p| p as u64);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn native_arg_count(&self) -> u8 {
|
||||||
|
self.native_arg_count
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_native_arg_count(&mut self, n: u8) {
|
||||||
|
self.native_arg_count = n;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn alloc_overflow(&mut self, _bump: &bumpalo::Bump, new_count: usize) -> *mut JsValue {
|
pub fn alloc_overflow(&mut self, _bump: &bumpalo::Bump, new_count: usize) -> *mut JsValue {
|
||||||
use std::alloc::Layout;
|
use std::alloc::Layout;
|
||||||
debug_assert!(new_count > 4);
|
debug_assert!(new_count > 4);
|
||||||
@@ -213,11 +237,11 @@ mod tests {
|
|||||||
use crate::shape::EMPTY_SHAPE_ID;
|
use crate::shape::EMPTY_SHAPE_ID;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn object_size_64_bytes() {
|
fn object_size_72_bytes() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
std::mem::size_of::<JsObject>(),
|
std::mem::size_of::<JsObject>(),
|
||||||
64,
|
72,
|
||||||
"JsObject layout mismatch — expected 64B (one cache line)"
|
"JsObject layout mismatch - expected 72B"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use oxide_kernel::kernel::OxideKernel;
|
||||||
|
|
||||||
|
pub fn register_global_properties(_kernel: &Arc<OxideKernel>, _bump: &bumpalo::Bump) -> Result<(), String> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pub mod global;
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
#![doc = "OxideJS - Register-based VM with epoch arena memory"]
|
#![doc = "OxideJS - Register-based VM with epoch arena memory"]
|
||||||
|
|
||||||
pub mod coercion;
|
pub mod coercion;
|
||||||
|
pub mod native;
|
||||||
|
pub mod builtins;
|
||||||
pub mod vm;
|
pub mod vm;
|
||||||
pub mod vm_pool;
|
pub mod vm_pool;
|
||||||
pub use oxide_types::value::JsValue;
|
pub use oxide_types::value::JsValue;
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
use oxide_types::value::JsValue;
|
||||||
|
|
||||||
|
pub type NativeResult = Result<JsValue, JsValue>;
|
||||||
|
|
||||||
|
pub type NativeFn = fn(&mut crate::vm::Vm, args: &[u8]) -> NativeResult;
|
||||||
@@ -7,6 +7,7 @@ use oxide_compiler::module::CompiledModule;
|
|||||||
use oxide_compiler::opcode::{self, OpCode};
|
use oxide_compiler::opcode::{self, OpCode};
|
||||||
|
|
||||||
use crate::coercion;
|
use crate::coercion;
|
||||||
|
use crate::native::NativeFn;
|
||||||
use oxide_kernel::kernel::{KernelConfig, OxideKernel};
|
use oxide_kernel::kernel::{KernelConfig, OxideKernel};
|
||||||
use oxide_kernel::prop_forge::PropTemplate;
|
use oxide_kernel::prop_forge::PropTemplate;
|
||||||
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
||||||
@@ -172,6 +173,27 @@ impl Vm {
|
|||||||
self.regs = [JsValue::undefined(); 256];
|
self.regs = [JsValue::undefined(); 256];
|
||||||
self.frames.clear();
|
self.frames.clear();
|
||||||
|
|
||||||
|
for (name, reg) in &module.builtin_reg_map {
|
||||||
|
let si = self.kernel.string_forge().intern(name.as_str()).0;
|
||||||
|
let global = self.kernel.global_object();
|
||||||
|
let mut shape_id = global.shape_id();
|
||||||
|
let mut found: Option<u8> = None;
|
||||||
|
while shape_id != EMPTY_SHAPE_ID {
|
||||||
|
if let Some(shape) = self.kernel.shape_forge().get_shape(shape_id) {
|
||||||
|
if shape.property_name == si {
|
||||||
|
found = Some(shape.property_offset);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
shape_id = shape.parent.unwrap_or(EMPTY_SHAPE_ID);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(offset) = found {
|
||||||
|
self.regs[*reg as usize] = global.get_prop(offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.dispatch()
|
self.dispatch()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,6 +375,45 @@ impl Vm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
OpCode::CALL => {
|
OpCode::CALL => {
|
||||||
|
let callee_reg = rd;
|
||||||
|
let this_reg = a as u8;
|
||||||
|
let first_arg_reg = b as u8;
|
||||||
|
|
||||||
|
let callee = self.regs[callee_reg];
|
||||||
|
|
||||||
|
if callee.is_object() {
|
||||||
|
let obj_ptr = callee.as_js_object_ptr();
|
||||||
|
if !obj_ptr.is_null() {
|
||||||
|
let obj = unsafe { &*obj_ptr };
|
||||||
|
if obj.is_function() && obj.native_fn().is_some() {
|
||||||
|
let ext = self.bytecode[self.pc];
|
||||||
|
self.pc += 1;
|
||||||
|
let arg_count = (ext & 0xFF) as usize;
|
||||||
|
|
||||||
|
let mut args_buf = [0u8; 257];
|
||||||
|
args_buf[0] = this_reg;
|
||||||
|
for i in 0..arg_count.min(256) {
|
||||||
|
args_buf[i + 1] =
|
||||||
|
first_arg_reg.wrapping_add(i as u8);
|
||||||
|
}
|
||||||
|
let args_slice = &args_buf[..arg_count + 1];
|
||||||
|
|
||||||
|
let func: NativeFn =
|
||||||
|
unsafe { std::mem::transmute(obj.native_fn().unwrap()) };
|
||||||
|
match func(self, args_slice) {
|
||||||
|
Ok(val) => self.regs[0] = val,
|
||||||
|
Err(err_val) => {
|
||||||
|
return Err(format!(
|
||||||
|
"Native error: {:?}",
|
||||||
|
err_val
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let offset = opcode::offset16(instr) as usize;
|
let offset = opcode::offset16(instr) as usize;
|
||||||
self.frames.push(CallFrame {
|
self.frames.push(CallFrame {
|
||||||
return_addr: self.pc,
|
return_addr: self.pc,
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
use oxide_compiler::compiler::Compiler;
|
||||||
|
use oxide_parser::Allocator;
|
||||||
|
use oxide_vm::vm::Vm;
|
||||||
|
|
||||||
|
fn eval(source: &str) -> Result<oxide_types::value::JsValue, String> {
|
||||||
|
let allocator = Allocator::default();
|
||||||
|
let program =
|
||||||
|
oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?;
|
||||||
|
let module = Compiler::new()
|
||||||
|
.compile(&program)
|
||||||
|
.map_err(|e| format!("Compile error: {}", e))?;
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
vm.run(&module)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_nan_returns_nan() {
|
||||||
|
let result = eval("NaN").unwrap();
|
||||||
|
assert!(result.is_double());
|
||||||
|
assert!(result.as_double().is_nan());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_undefined_returns_undefined() {
|
||||||
|
let result = eval("undefined").unwrap();
|
||||||
|
assert!(result.is_undefined());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn typeof_nan_is_number() {
|
||||||
|
let result = eval("typeof NaN").unwrap();
|
||||||
|
assert!(result.is_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn typeof_undefined_is_undefined() {
|
||||||
|
let result = eval("typeof undefined").unwrap();
|
||||||
|
assert!(result.is_string());
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user