mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 02:31:13 +08:00
feat: 支持 Object 成员方法
This commit is contained in:
@@ -69,7 +69,9 @@ fn main() -> ExitCode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn make_kernel() -> Arc<OxideKernel> {
|
fn make_kernel() -> Arc<OxideKernel> {
|
||||||
Arc::new(OxideKernel::new(KernelConfig::standard()))
|
let kernel = Arc::new(OxideKernel::new(KernelConfig::standard()));
|
||||||
|
oxide_vm::vm::init_kernel_builtins(&kernel);
|
||||||
|
kernel
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_pool(kernel: &Arc<OxideKernel>) -> Arc<VmPool> {
|
fn make_pool(kernel: &Arc<OxideKernel>) -> Arc<VmPool> {
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ impl CompileCtx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn reset_regs(&mut self) {
|
pub(crate) fn reset_regs(&mut self) {
|
||||||
self.next_reg = 0;
|
self.next_reg = self.builtin_reg_map.len() as u8;
|
||||||
self.projected_pc = 0;
|
self.projected_pc = 0;
|
||||||
self.label_counter = 0;
|
self.label_counter = 0;
|
||||||
}
|
}
|
||||||
@@ -169,9 +169,22 @@ impl CompileCtx {
|
|||||||
|
|
||||||
pub(crate) fn pre_register_builtins(&mut self) {
|
pub(crate) fn pre_register_builtins(&mut self) {
|
||||||
let builtins = [
|
let builtins = [
|
||||||
"NaN", "undefined", "Object", "Array", "String", "Number", "Boolean",
|
"NaN",
|
||||||
"Function", "Error", "Math", "JSON", "Promise", "parseInt", "parseFloat",
|
"undefined",
|
||||||
"isNaN", "isFinite",
|
"Object",
|
||||||
|
"Array",
|
||||||
|
"String",
|
||||||
|
"Number",
|
||||||
|
"Boolean",
|
||||||
|
"Function",
|
||||||
|
"Error",
|
||||||
|
"Math",
|
||||||
|
"JSON",
|
||||||
|
"Promise",
|
||||||
|
"parseInt",
|
||||||
|
"parseFloat",
|
||||||
|
"isNaN",
|
||||||
|
"isFinite",
|
||||||
];
|
];
|
||||||
for name in &builtins {
|
for name in &builtins {
|
||||||
let reg = self.alloc_reg();
|
let reg = self.alloc_reg();
|
||||||
|
|||||||
@@ -198,15 +198,26 @@ impl Compiler {
|
|||||||
ctx.projected_pc += 1; // NEG/TYPEOF/VOID/NOT
|
ctx.projected_pc += 1; // NEG/TYPEOF/VOID/NOT
|
||||||
}
|
}
|
||||||
Expression::CallExpression(call) => {
|
Expression::CallExpression(call) => {
|
||||||
self.count_expression(&call.callee, ctx);
|
match &call.callee {
|
||||||
ctx.alloc_reg();
|
Expression::StaticMemberExpression(member) => {
|
||||||
|
self.count_expression(&member.object, ctx);
|
||||||
|
ctx.alloc_reg();
|
||||||
|
ctx.projected_pc += 1;
|
||||||
|
ctx.alloc_reg();
|
||||||
|
ctx.projected_pc += 3;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.count_expression(&call.callee, ctx);
|
||||||
|
ctx.alloc_reg();
|
||||||
|
ctx.projected_pc += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
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.projected_pc += 2;
|
||||||
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
|
||||||
|
|||||||
@@ -739,15 +739,37 @@ impl Compiler {
|
|||||||
}
|
}
|
||||||
Expression::ParenthesizedExpression(p) => self.emit_expression(&p.expression, ctx),
|
Expression::ParenthesizedExpression(p) => self.emit_expression(&p.expression, ctx),
|
||||||
Expression::CallExpression(call) => {
|
Expression::CallExpression(call) => {
|
||||||
let callee_reg = self.emit_expression(&call.callee, ctx)?;
|
let (callee_reg, this_reg) = match &call.callee {
|
||||||
let this_idx = ctx.add_constant(Constant::Undefined);
|
Expression::StaticMemberExpression(member) => {
|
||||||
let this_reg = ctx.alloc_reg();
|
let obj_reg = self.emit_expression(&member.object, ctx)?;
|
||||||
ctx.emit(opcode::encode(
|
let prop_name = member.property.name.as_str();
|
||||||
OpCode::LOAD_CONST,
|
let idx = ctx.add_constant(Constant::String(prop_name.to_string()));
|
||||||
this_reg,
|
let key_reg = ctx.alloc_reg();
|
||||||
(this_idx & 0xFF) as u8,
|
ctx.emit(opcode::encode(
|
||||||
((this_idx >> 8) & 0xFF) as u8,
|
OpCode::LOAD_CONST,
|
||||||
));
|
key_reg,
|
||||||
|
(idx & 0xFF) as u8,
|
||||||
|
((idx >> 8) & 0xFF) as u8,
|
||||||
|
));
|
||||||
|
let callee_reg = ctx.alloc_reg();
|
||||||
|
ctx.emit(opcode::encode(OpCode::LOAD_VAR, callee_reg, obj_reg, 0));
|
||||||
|
ctx.emit(opcode::encode(OpCode::IC_GET_PROP, 0, callee_reg, key_reg));
|
||||||
|
ctx.emit(0);
|
||||||
|
(callee_reg, obj_reg)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
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,
|
||||||
|
));
|
||||||
|
(callee_reg, this_reg)
|
||||||
|
}
|
||||||
|
};
|
||||||
let mut arg_regs = Vec::new();
|
let mut arg_regs = Vec::new();
|
||||||
for arg in &call.arguments {
|
for arg in &call.arguments {
|
||||||
if let Some(expr) = arg.as_expression() {
|
if let Some(expr) = arg.as_expression() {
|
||||||
@@ -759,7 +781,6 @@ impl Compiler {
|
|||||||
} else {
|
} else {
|
||||||
arg_regs[0]
|
arg_regs[0]
|
||||||
};
|
};
|
||||||
let result_reg = ctx.alloc_reg();
|
|
||||||
ctx.emit(opcode::encode(
|
ctx.emit(opcode::encode(
|
||||||
OpCode::CALL,
|
OpCode::CALL,
|
||||||
callee_reg,
|
callee_reg,
|
||||||
@@ -767,7 +788,7 @@ impl Compiler {
|
|||||||
first_arg_reg,
|
first_arg_reg,
|
||||||
));
|
));
|
||||||
ctx.emit(arg_regs.len() as u32);
|
ctx.emit(arg_regs.len() as u32);
|
||||||
Ok(result_reg)
|
Ok(0u8)
|
||||||
}
|
}
|
||||||
_ => Err(format!("unsupported expression type: {:?}", expr)),
|
_ => Err(format!("unsupported expression type: {:?}", expr)),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,14 @@ use oxide_types::value::JsValue;
|
|||||||
use crate::shape_forge::{ShapeForge, EMPTY_SHAPE_ID};
|
use crate::shape_forge::{ShapeForge, EMPTY_SHAPE_ID};
|
||||||
use crate::string_forge::StringForge;
|
use crate::string_forge::StringForge;
|
||||||
|
|
||||||
|
pub struct ObjectMethods {
|
||||||
|
pub keys: *const (),
|
||||||
|
pub create: *const (),
|
||||||
|
pub assign: *const (),
|
||||||
|
pub define_property: *const (),
|
||||||
|
pub get_own_property_descriptor: *const (),
|
||||||
|
}
|
||||||
|
|
||||||
pub struct BuiltinWorld {
|
pub struct BuiltinWorld {
|
||||||
pub object_proto: P<JsObject>,
|
pub object_proto: P<JsObject>,
|
||||||
pub array_proto: P<JsObject>,
|
pub array_proto: P<JsObject>,
|
||||||
@@ -152,6 +160,36 @@ impl BuiltinWorld {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn bind_object_methods(
|
||||||
|
&self,
|
||||||
|
methods: &ObjectMethods,
|
||||||
|
string_forge: &StringForge,
|
||||||
|
shape_forge: &ShapeForge,
|
||||||
|
) {
|
||||||
|
let ctor_ptr = P::as_ptr(&self.object_constructor) as *mut JsObject;
|
||||||
|
let ctor = unsafe { &mut *ctor_ptr };
|
||||||
|
|
||||||
|
let _ = Self::bind_method(ctor, shape_forge, string_forge, "keys", methods.keys, 1);
|
||||||
|
let _ = Self::bind_method(ctor, shape_forge, string_forge, "create", methods.create, 2);
|
||||||
|
let _ = Self::bind_method(ctor, shape_forge, string_forge, "assign", methods.assign, 2);
|
||||||
|
let _ = Self::bind_method(
|
||||||
|
ctor,
|
||||||
|
shape_forge,
|
||||||
|
string_forge,
|
||||||
|
"defineProperty",
|
||||||
|
methods.define_property,
|
||||||
|
3,
|
||||||
|
);
|
||||||
|
let _ = Self::bind_method(
|
||||||
|
ctor,
|
||||||
|
shape_forge,
|
||||||
|
string_forge,
|
||||||
|
"getOwnPropertyDescriptor",
|
||||||
|
methods.get_own_property_descriptor,
|
||||||
|
2,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn bind_method(
|
pub fn bind_method(
|
||||||
proto: &mut JsObject,
|
proto: &mut JsObject,
|
||||||
shape_forge: &ShapeForge,
|
shape_forge: &ShapeForge,
|
||||||
@@ -170,8 +208,7 @@ impl BuiltinWorld {
|
|||||||
let new_shape = shape_forge.make_shape(proto.shape_id(), si);
|
let new_shape = shape_forge.make_shape(proto.shape_id(), si);
|
||||||
proto.set_shape_id(new_shape);
|
proto.set_shape_id(new_shape);
|
||||||
proto.set_prop_count(new_offset + 1);
|
proto.set_prop_count(new_offset + 1);
|
||||||
let bump = bumpalo::Bump::new();
|
proto.set_prop_expand_heap(new_offset, wrapper_val);
|
||||||
proto.set_prop_expand(new_offset, wrapper_val, &bump);
|
|
||||||
proto.bump_generation();
|
proto.bump_generation();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -395,7 +395,10 @@ fn main() {
|
|||||||
|
|
||||||
let path_str = path.to_string_lossy();
|
let path_str = path.to_string_lossy();
|
||||||
if path_str.contains("staging") {
|
if path_str.contains("staging") {
|
||||||
results.push(TestResult::skip(path.clone(), "staging tests excluded".into()));
|
results.push(TestResult::skip(
|
||||||
|
path.clone(),
|
||||||
|
"staging tests excluded".into(),
|
||||||
|
));
|
||||||
stats.skip += 1;
|
stats.skip += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -426,9 +429,21 @@ fn main() {
|
|||||||
println!("═══════════════════════════════════════");
|
println!("═══════════════════════════════════════");
|
||||||
println!(" total : {}", results.len());
|
println!(" total : {}", results.len());
|
||||||
let total = results.len() as f64;
|
let total = results.len() as f64;
|
||||||
println!(" pass : {} ({:.1}%)", stats.pass, stats.pass as f64 / total * 100.0);
|
println!(
|
||||||
println!(" fail : {} ({:.1}%)", stats.fail, stats.fail as f64 / total * 100.0);
|
" pass : {} ({:.1}%)",
|
||||||
println!(" skip : {} ({:.1}%)", stats.skip, stats.skip as f64 / total * 100.0);
|
stats.pass,
|
||||||
|
stats.pass as f64 / total * 100.0
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
" fail : {} ({:.1}%)",
|
||||||
|
stats.fail,
|
||||||
|
stats.fail as f64 / total * 100.0
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
" skip : {} ({:.1}%)",
|
||||||
|
stats.skip,
|
||||||
|
stats.skip as f64 / total * 100.0
|
||||||
|
);
|
||||||
println!(" time : {:?}", Duration::from_millis(stats.total_ms));
|
println!(" time : {:?}", Duration::from_millis(stats.total_ms));
|
||||||
println!(
|
println!(
|
||||||
" pass% : {:.1}% (of ran: {:.1}%)",
|
" pass% : {:.1}% (of ran: {:.1}%)",
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ impl<T> P<T> {
|
|||||||
{
|
{
|
||||||
Self(Arc::new(value.clone()))
|
Self(Arc::new(value.clone()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn as_ptr(&self) -> *const T {
|
||||||
|
Arc::as_ptr(&self.0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Clone for P<T> {
|
impl<T> Clone for P<T> {
|
||||||
|
|||||||
@@ -229,6 +229,26 @@ impl JsObject {
|
|||||||
self.overflow = ptr;
|
self.overflow = ptr;
|
||||||
ptr
|
ptr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn alloc_overflow_heap(&mut self, new_count: usize) -> *mut JsValue {
|
||||||
|
debug_assert!(new_count > 4);
|
||||||
|
debug_assert!(new_count <= 32, "overflow limited to 32 slots max");
|
||||||
|
let n = new_count - 4;
|
||||||
|
let v: Vec<JsValue> = vec![JsValue::undefined(); n];
|
||||||
|
let ptr = Box::into_raw(v.into_boxed_slice()) as *mut JsValue;
|
||||||
|
self.overflow = ptr;
|
||||||
|
ptr
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_prop_expand_heap(&mut self, offset: u8, val: JsValue) {
|
||||||
|
if offset >= 4 {
|
||||||
|
let needed = (offset as usize) + 1;
|
||||||
|
if self.overflow.is_null() || offset >= self.prop_count() + 4 {
|
||||||
|
self.alloc_overflow_heap(needed.max(8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.set_prop(offset, val);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use oxide_kernel::kernel::OxideKernel;
|
use oxide_kernel::kernel::OxideKernel;
|
||||||
|
|
||||||
pub fn register_global_properties(_kernel: &Arc<OxideKernel>, _bump: &bumpalo::Bump) -> Result<(), String> {
|
pub fn register_global_properties(
|
||||||
|
_kernel: &Arc<OxideKernel>,
|
||||||
|
_bump: &bumpalo::Bump,
|
||||||
|
) -> Result<(), String> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
pub mod global;
|
pub mod global;
|
||||||
|
pub mod object;
|
||||||
|
|||||||
@@ -0,0 +1,239 @@
|
|||||||
|
use oxide_kernel::shape_forge::{ShapeForge, EMPTY_SHAPE_ID};
|
||||||
|
use oxide_kernel::string_forge::StringForge;
|
||||||
|
use oxide_types::object::JsObject;
|
||||||
|
use oxide_types::value::JsValue;
|
||||||
|
|
||||||
|
use crate::coercion;
|
||||||
|
use crate::native::NativeResult;
|
||||||
|
use crate::vm::Vm;
|
||||||
|
|
||||||
|
fn walk_own_keys(vm: &Vm, obj: &JsObject) -> Vec<(u32, u8)> {
|
||||||
|
let mut keys: Vec<(u32, u8)> = Vec::new();
|
||||||
|
let mut shape_id = obj.shape_id();
|
||||||
|
while shape_id != EMPTY_SHAPE_ID {
|
||||||
|
if let Some(shape) = vm.kernel().shape_forge().get_shape(shape_id) {
|
||||||
|
if shape.property_name != 0 {
|
||||||
|
keys.push((shape.property_name, shape.property_offset));
|
||||||
|
}
|
||||||
|
shape_id = shape.parent.unwrap_or(EMPTY_SHAPE_ID);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
keys.reverse();
|
||||||
|
keys
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn object_constructor(vm: &mut Vm, _args: &[u8]) -> NativeResult {
|
||||||
|
let obj = vm
|
||||||
|
.epoch()
|
||||||
|
.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
|
||||||
|
Ok(JsValue::from_js_object(obj))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn object_keys(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||||
|
if args.len() < 2 {
|
||||||
|
return Err(JsValue::undefined());
|
||||||
|
}
|
||||||
|
let val = vm.reg(args[1]);
|
||||||
|
if !val.is_object() {
|
||||||
|
return Err(JsValue::undefined());
|
||||||
|
}
|
||||||
|
let obj_ptr = val.as_js_object_ptr();
|
||||||
|
if obj_ptr.is_null() {
|
||||||
|
return Err(JsValue::undefined());
|
||||||
|
}
|
||||||
|
|
||||||
|
let key_names: Vec<String>;
|
||||||
|
{
|
||||||
|
let obj = unsafe { &*obj_ptr };
|
||||||
|
let keys = walk_own_keys(vm, obj);
|
||||||
|
key_names = keys
|
||||||
|
.iter()
|
||||||
|
.map(|(si, _offset)| vm.kernel().string_forge().lookup(*si).unwrap_or_default())
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
let n = key_names.len().min(255);
|
||||||
|
let array_proto = vm.kernel().builtin_world().array_proto.as_ptr() as *mut JsObject;
|
||||||
|
let arr = vm.epoch().alloc(JsObject::new_array(
|
||||||
|
EMPTY_SHAPE_ID,
|
||||||
|
JsValue::from_js_object(array_proto),
|
||||||
|
n,
|
||||||
|
vm.epoch().bump(),
|
||||||
|
));
|
||||||
|
for (i, k) in key_names.iter().take(n).enumerate() {
|
||||||
|
let str_val = vm.intern(k);
|
||||||
|
unsafe {
|
||||||
|
(*arr).set_prop(i as u8, str_val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
(*arr).set_prop_count(n as u8);
|
||||||
|
}
|
||||||
|
Ok(JsValue::from_js_object(arr))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn object_create(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||||
|
if args.len() < 2 {
|
||||||
|
return Ok(JsValue::undefined());
|
||||||
|
}
|
||||||
|
let proto_val = vm.reg(args[1]);
|
||||||
|
if !proto_val.is_null() && !proto_val.is_object() {
|
||||||
|
return Ok(JsValue::undefined());
|
||||||
|
}
|
||||||
|
let obj = vm
|
||||||
|
.epoch()
|
||||||
|
.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, proto_val));
|
||||||
|
Ok(JsValue::from_js_object(obj))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn object_assign(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||||
|
if args.len() < 2 {
|
||||||
|
return Err(JsValue::undefined());
|
||||||
|
}
|
||||||
|
let target_val = vm.reg(args[1]);
|
||||||
|
if !target_val.is_object() {
|
||||||
|
return Ok(target_val);
|
||||||
|
}
|
||||||
|
let target_ptr = target_val.as_js_object_ptr();
|
||||||
|
if target_ptr.is_null() {
|
||||||
|
return Ok(target_val);
|
||||||
|
}
|
||||||
|
let mut target_prop_count;
|
||||||
|
{
|
||||||
|
let target = unsafe { &mut *target_ptr };
|
||||||
|
target_prop_count = target.prop_count() as usize;
|
||||||
|
for &arg_reg in args.iter().skip(2) {
|
||||||
|
let source_val = vm.reg(arg_reg);
|
||||||
|
if !source_val.is_object() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let source_ptr = source_val.as_js_object_ptr();
|
||||||
|
if source_ptr.is_null() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let source = unsafe { &*source_ptr };
|
||||||
|
let source_keys = walk_own_keys(vm, source);
|
||||||
|
for (_si, offset) in source_keys {
|
||||||
|
let prop_val = source.get_prop(offset);
|
||||||
|
target.set_prop_expand(target_prop_count as u8, prop_val, vm.epoch().bump());
|
||||||
|
target_prop_count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(target_val)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn object_define_property(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||||
|
if args.len() < 4 {
|
||||||
|
return Err(JsValue::undefined());
|
||||||
|
}
|
||||||
|
let obj_val = vm.reg(args[1]);
|
||||||
|
if !obj_val.is_object() {
|
||||||
|
return Err(JsValue::undefined());
|
||||||
|
}
|
||||||
|
let obj_ptr = obj_val.as_js_object_ptr();
|
||||||
|
if obj_ptr.is_null() {
|
||||||
|
return Err(JsValue::undefined());
|
||||||
|
}
|
||||||
|
let desc_val = vm.reg(args[3]);
|
||||||
|
if !desc_val.is_object() {
|
||||||
|
return Err(JsValue::undefined());
|
||||||
|
}
|
||||||
|
let desc_ptr = desc_val.as_js_object_ptr();
|
||||||
|
if desc_ptr.is_null() {
|
||||||
|
return Err(JsValue::undefined());
|
||||||
|
}
|
||||||
|
let prop_name_str = coercion::to_string(vm.kernel().string_forge().as_ref(), vm.reg(args[2]));
|
||||||
|
let si = vm.kernel().string_forge().intern(&prop_name_str).0;
|
||||||
|
let shape_forge = vm.kernel().shape_forge().as_ref();
|
||||||
|
let new_shape = shape_forge.make_shape(unsafe { (&*obj_ptr).shape_id() }, si);
|
||||||
|
let new_offset;
|
||||||
|
{
|
||||||
|
let obj = unsafe { &mut *obj_ptr };
|
||||||
|
new_offset = obj.prop_count();
|
||||||
|
obj.set_shape_id(new_shape);
|
||||||
|
obj.set_prop_count(new_offset + 1);
|
||||||
|
}
|
||||||
|
let desc = unsafe { &*desc_ptr };
|
||||||
|
let value = desc.get_prop(0);
|
||||||
|
unsafe {
|
||||||
|
(&mut *obj_ptr).set_prop_expand(new_offset, value, vm.epoch().bump());
|
||||||
|
}
|
||||||
|
Ok(obj_val)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn object_get_own_property_descriptor(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||||
|
if args.len() < 3 {
|
||||||
|
return Err(JsValue::undefined());
|
||||||
|
}
|
||||||
|
let obj_val = vm.reg(args[1]);
|
||||||
|
if !obj_val.is_object() {
|
||||||
|
return Ok(JsValue::undefined());
|
||||||
|
}
|
||||||
|
let obj_ptr = obj_val.as_js_object_ptr();
|
||||||
|
if obj_ptr.is_null() {
|
||||||
|
return Ok(JsValue::undefined());
|
||||||
|
}
|
||||||
|
let prop_name_str = coercion::to_string(vm.kernel().string_forge().as_ref(), vm.reg(args[2]));
|
||||||
|
let si = vm.kernel().string_forge().intern(&prop_name_str).0;
|
||||||
|
|
||||||
|
let (found_value, found) = {
|
||||||
|
let obj = unsafe { &*obj_ptr };
|
||||||
|
let keys = walk_own_keys(vm, obj);
|
||||||
|
let mut found_value = JsValue::undefined();
|
||||||
|
let mut found = false;
|
||||||
|
for (prop_si, offset) in keys {
|
||||||
|
if prop_si == si {
|
||||||
|
found_value = obj.get_prop(offset);
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(found_value, found)
|
||||||
|
};
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return Ok(JsValue::undefined());
|
||||||
|
}
|
||||||
|
|
||||||
|
let sf_ptr = vm.kernel().string_forge().as_ref() as *const StringForge;
|
||||||
|
let sh_ptr = vm.kernel().shape_forge().as_ref() as *const ShapeForge;
|
||||||
|
let desc_proto = vm.kernel().builtin_world().object_proto.as_ptr() as *mut JsObject;
|
||||||
|
let desc = vm.epoch().alloc(JsObject::new_empty(
|
||||||
|
EMPTY_SHAPE_ID,
|
||||||
|
JsValue::from_js_object(desc_proto),
|
||||||
|
));
|
||||||
|
let true_val = JsValue::bool(true);
|
||||||
|
let b = vm.epoch().bump();
|
||||||
|
let sf = unsafe { &*sf_ptr };
|
||||||
|
let sh = unsafe { &*sh_ptr };
|
||||||
|
|
||||||
|
let d: &mut JsObject = unsafe { &mut *desc };
|
||||||
|
let si_val = sf.intern("value").0;
|
||||||
|
let sh_val = sh.make_shape(EMPTY_SHAPE_ID, si_val);
|
||||||
|
d.set_shape_id(sh_val);
|
||||||
|
d.set_prop_count(1);
|
||||||
|
d.set_prop_expand(0, found_value, b);
|
||||||
|
|
||||||
|
let si_wr = sf.intern("writable").0;
|
||||||
|
let sh_wr = sh.make_shape(sh_val, si_wr);
|
||||||
|
d.set_shape_id(sh_wr);
|
||||||
|
d.set_prop_count(2);
|
||||||
|
d.set_prop_expand(1, true_val, b);
|
||||||
|
|
||||||
|
let si_en = sf.intern("enumerable").0;
|
||||||
|
let sh_en = sh.make_shape(sh_wr, si_en);
|
||||||
|
d.set_shape_id(sh_en);
|
||||||
|
d.set_prop_count(3);
|
||||||
|
d.set_prop_expand(2, true_val, b);
|
||||||
|
|
||||||
|
let si_cf = sf.intern("configurable").0;
|
||||||
|
let sh_cf = sh.make_shape(sh_en, si_cf);
|
||||||
|
d.set_shape_id(sh_cf);
|
||||||
|
d.set_prop_count(4);
|
||||||
|
d.set_prop_expand(3, true_val, b);
|
||||||
|
|
||||||
|
Ok(JsValue::from_js_object(desc))
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
#![doc = "OxideJS - Register-based VM with epoch arena memory"]
|
#![doc = "OxideJS - Register-based VM with epoch arena memory"]
|
||||||
|
|
||||||
|
pub mod builtins;
|
||||||
pub mod coercion;
|
pub mod coercion;
|
||||||
pub mod native;
|
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;
|
||||||
|
|||||||
+72
-18
@@ -8,6 +8,7 @@ use oxide_compiler::opcode::{self, OpCode};
|
|||||||
|
|
||||||
use crate::coercion;
|
use crate::coercion;
|
||||||
use crate::native::NativeFn;
|
use crate::native::NativeFn;
|
||||||
|
use oxide_kernel::builtin::ObjectMethods;
|
||||||
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;
|
||||||
@@ -39,8 +40,39 @@ pub struct Vm {
|
|||||||
pub object_prototype: P<JsObject>,
|
pub object_prototype: P<JsObject>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn init_kernel_builtins(kernel: &Arc<OxideKernel>) {
|
||||||
|
let methods = ObjectMethods {
|
||||||
|
keys: crate::builtins::object::object_keys as *const (),
|
||||||
|
create: crate::builtins::object::object_create as *const (),
|
||||||
|
assign: crate::builtins::object::object_assign as *const (),
|
||||||
|
define_property: crate::builtins::object::object_define_property as *const (),
|
||||||
|
get_own_property_descriptor: crate::builtins::object::object_get_own_property_descriptor
|
||||||
|
as *const (),
|
||||||
|
};
|
||||||
|
kernel.builtin_world().bind_object_methods(
|
||||||
|
&methods,
|
||||||
|
kernel.string_forge().as_ref(),
|
||||||
|
kernel.shape_forge().as_ref(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let global_ptr = kernel.global_object().as_ptr() as *mut JsObject;
|
||||||
|
let global = unsafe { &mut *global_ptr };
|
||||||
|
let si_obj = kernel.string_forge().intern("Object").0;
|
||||||
|
let obj_shape = kernel.shape_forge().make_shape(global.shape_id(), si_obj);
|
||||||
|
let obj_val = JsValue::from_js_object(
|
||||||
|
kernel.builtin_world().object_constructor.as_ptr() as *mut JsObject
|
||||||
|
);
|
||||||
|
let cur_count = global.prop_count();
|
||||||
|
global.set_shape_id(obj_shape);
|
||||||
|
global.set_prop_count(cur_count + 1);
|
||||||
|
let bump = bumpalo::Bump::new();
|
||||||
|
global.set_prop_expand(cur_count, obj_val, &bump);
|
||||||
|
}
|
||||||
|
|
||||||
impl Vm {
|
impl Vm {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
|
let kernel = Arc::new(OxideKernel::new(KernelConfig::minimal()));
|
||||||
|
init_kernel_builtins(&kernel);
|
||||||
Self {
|
Self {
|
||||||
regs: [JsValue::undefined(); 256],
|
regs: [JsValue::undefined(); 256],
|
||||||
pc: 0,
|
pc: 0,
|
||||||
@@ -48,7 +80,7 @@ impl Vm {
|
|||||||
constants: Vec::new(),
|
constants: Vec::new(),
|
||||||
frames: Vec::with_capacity(128),
|
frames: Vec::with_capacity(128),
|
||||||
for_in_iters: Vec::new(),
|
for_in_iters: Vec::new(),
|
||||||
kernel: Arc::new(OxideKernel::new(KernelConfig::minimal())),
|
kernel,
|
||||||
interned_strings: Vec::new(),
|
interned_strings: Vec::new(),
|
||||||
epoch: Epoch::new(),
|
epoch: Epoch::new(),
|
||||||
object_prototype: P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
|
object_prototype: P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
|
||||||
@@ -56,10 +88,42 @@ impl Vm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_kernel(kernel: Arc<OxideKernel>) -> Self {
|
pub fn with_kernel(kernel: Arc<OxideKernel>) -> Self {
|
||||||
let mut vm = Self::new();
|
Self {
|
||||||
vm.kernel = Arc::clone(&kernel);
|
regs: [JsValue::undefined(); 256],
|
||||||
vm.object_prototype = P::clone(&kernel.builtin_world().object_proto);
|
pc: 0,
|
||||||
vm
|
bytecode: Vec::new(),
|
||||||
|
constants: Vec::new(),
|
||||||
|
frames: Vec::with_capacity(128),
|
||||||
|
for_in_iters: Vec::new(),
|
||||||
|
kernel: Arc::clone(&kernel),
|
||||||
|
interned_strings: Vec::new(),
|
||||||
|
epoch: Epoch::new(),
|
||||||
|
object_prototype: P::clone(&kernel.builtin_world().object_proto),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn kernel(&self) -> &Arc<OxideKernel> {
|
||||||
|
&self.kernel
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reg(&self, idx: u8) -> JsValue {
|
||||||
|
self.regs[idx as usize]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_reg(&mut self, idx: u8, val: JsValue) {
|
||||||
|
self.regs[idx as usize] = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn regs_mut(&mut self) -> &mut [JsValue; 256] {
|
||||||
|
&mut self.regs
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn epoch_mut(&mut self) -> &mut Epoch {
|
||||||
|
&mut self.epoch
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn epoch(&self) -> &Epoch {
|
||||||
|
&self.epoch
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self) {
|
||||||
@@ -393,8 +457,7 @@ impl Vm {
|
|||||||
let mut args_buf = [0u8; 257];
|
let mut args_buf = [0u8; 257];
|
||||||
args_buf[0] = this_reg;
|
args_buf[0] = this_reg;
|
||||||
for i in 0..arg_count.min(256) {
|
for i in 0..arg_count.min(256) {
|
||||||
args_buf[i + 1] =
|
args_buf[i + 1] = first_arg_reg.wrapping_add(i as u8);
|
||||||
first_arg_reg.wrapping_add(i as u8);
|
|
||||||
}
|
}
|
||||||
let args_slice = &args_buf[..arg_count + 1];
|
let args_slice = &args_buf[..arg_count + 1];
|
||||||
|
|
||||||
@@ -403,10 +466,7 @@ impl Vm {
|
|||||||
match func(self, args_slice) {
|
match func(self, args_slice) {
|
||||||
Ok(val) => self.regs[0] = val,
|
Ok(val) => self.regs[0] = val,
|
||||||
Err(err_val) => {
|
Err(err_val) => {
|
||||||
return Err(format!(
|
return Err(format!("Native error: {:?}", err_val));
|
||||||
"Native error: {:?}",
|
|
||||||
err_val
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
@@ -414,13 +474,7 @@ impl Vm {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let offset = opcode::offset16(instr) as usize;
|
return Err("CALL target is not a native function".into());
|
||||||
self.frames.push(CallFrame {
|
|
||||||
return_addr: self.pc,
|
|
||||||
n_locals: b as u8,
|
|
||||||
n_args: a as u8,
|
|
||||||
});
|
|
||||||
self.pc = offset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
OpCode::RETURN => {
|
OpCode::RETURN => {
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
use oxide_compiler::compiler::Compiler;
|
||||||
|
use oxide_vm::vm::Vm;
|
||||||
|
|
||||||
|
fn eval(source: &str) -> Result<oxide_types::value::JsValue, String> {
|
||||||
|
let allocator = oxide_parser::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 object_keys_empty() {
|
||||||
|
let result = eval("Object.keys({})").unwrap();
|
||||||
|
assert!(result.is_object());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn object_keys_has_own_property() {
|
||||||
|
eval("Object.keys({a:1})").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn object_create_null_proto() {
|
||||||
|
eval("Object.create(null)").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn object_assign_copies() {
|
||||||
|
let result = eval("Object.assign({a:1},{b:2})").unwrap();
|
||||||
|
assert!(result.is_object());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn object_get_own_property_descriptor_value() {
|
||||||
|
let result = eval("Object.getOwnPropertyDescriptor({a:1},'a')").unwrap();
|
||||||
|
assert!(result.is_object());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn object_get_own_property_descriptor_missing() {
|
||||||
|
let result = eval("Object.getOwnPropertyDescriptor({},'x')").unwrap();
|
||||||
|
assert!(result.is_undefined());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn object_define_property_sets_value() {
|
||||||
|
let result = eval("Object.defineProperty({},'x',{value:42})").unwrap();
|
||||||
|
assert!(result.is_object());
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
use oxide_compiler::compiler::Compiler;
|
||||||
|
use oxide_vm::vm::Vm;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_object_is_object() {
|
||||||
|
let allocator = oxide_parser::Allocator::default();
|
||||||
|
let program = oxide_parser::parse(&allocator, "Object").unwrap();
|
||||||
|
let module = Compiler::new().compile(&program).unwrap();
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = vm.run(&module).unwrap();
|
||||||
|
assert!(
|
||||||
|
result.is_object(),
|
||||||
|
"Object should be an object, got {:?}",
|
||||||
|
result
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn verify_global_object_props() {
|
||||||
|
let vm = Vm::new();
|
||||||
|
let global = vm.kernel().global_object();
|
||||||
|
|
||||||
|
let obj_si = vm.kernel().string_forge().intern("Object").0;
|
||||||
|
let mut shape_id = global.shape_id();
|
||||||
|
let mut found = false;
|
||||||
|
while shape_id != oxide_kernel::shape_forge::EMPTY_SHAPE_ID {
|
||||||
|
if let Some(shape) = vm.kernel().shape_forge().get_shape(shape_id) {
|
||||||
|
if shape.property_name == obj_si {
|
||||||
|
found = true;
|
||||||
|
let val = global.get_prop(shape.property_offset);
|
||||||
|
assert!(
|
||||||
|
val.is_object(),
|
||||||
|
"Object prop should be object, got {:?}",
|
||||||
|
val
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
shape_id = shape
|
||||||
|
.parent
|
||||||
|
.unwrap_or(oxide_kernel::shape_forge::EMPTY_SHAPE_ID);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert!(found, "Object property not found on global object");
|
||||||
|
}
|
||||||
@@ -121,7 +121,9 @@ fn as_js_object_ptr_non_object_returns_null() {
|
|||||||
assert!(JsValue::null().as_js_object_ptr().is_null());
|
assert!(JsValue::null().as_js_object_ptr().is_null());
|
||||||
assert!(JsValue::undefined().as_js_object_ptr().is_null());
|
assert!(JsValue::undefined().as_js_object_ptr().is_null());
|
||||||
let x = 42u8;
|
let x = 42u8;
|
||||||
assert!(!JsValue::object(&x as *const u8).as_js_object_ptr().is_null());
|
assert!(!JsValue::object(&x as *const u8)
|
||||||
|
.as_js_object_ptr()
|
||||||
|
.is_null());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user