feat: 迁移 NativeResult

This commit is contained in:
爱特每羊队
2026-06-25 18:32:09 +08:00
parent 93326a6f31
commit 2c8f85d5a9
32 changed files with 164 additions and 66 deletions
+11
View File
@@ -0,0 +1,11 @@
[package]
name = "oxide_runtime_api"
version.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true
description = "OxideJS - VmHost trait: the abstract Vm interface builtins are written against"
[dependencies]
oxide_types = { path = "../oxide_types" }
oxide_kernel = { path = "../oxide_kernel" }
+108
View File
@@ -0,0 +1,108 @@
//! `oxide_runtime_api` — the abstract interface between builtins and the VM.
//!
//! Builtins are written generically against the [`VmHost`] trait
//! (`fn xxx<H: VmHost>(host: &mut H, args: &[u8]) -> NativeResult`); `Vm`
//! implements `VmHost`. This breaks what would otherwise be a circular
//! dependency between the builtins crate and `oxide_vm`:
//!
//! `oxide_types ← oxide_kernel ← oxide_runtime_api ← oxide_builtins ← oxide_vm`
//!
//! The trait is GENERIC-friendly, not object-safe: monomorphizing `H = Vm`
//! inlines every `host.*()` call, so there is zero runtime overhead versus
//! builtins living inside `oxide_vm`.
use std::sync::Arc;
use oxide_kernel::kernel::{KernelCore, KernelSession};
use oxide_types::mem::Epoch;
use oxide_types::object::{JsObject, PropAttributes};
use oxide_types::value::JsValue;
/// Return value of every builtin native function.
pub enum NativeResult {
Ok(JsValue),
Err(JsValue),
TailCall { callee: JsValue, this: JsValue, args: Vec<JsValue> },
}
impl NativeResult {
pub fn ok(val: JsValue) -> Self {
Self::Ok(val)
}
pub fn err(val: JsValue) -> Self {
Self::Err(val)
}
pub fn unwrap(self) -> JsValue {
match self {
Self::Ok(val) => val,
Self::Err(_) => panic!("called `NativeResult::unwrap()` on an `Err` value"),
Self::TailCall { .. } => panic!("called `NativeResult::unwrap()` on a `TailCall` value"),
}
}
pub fn map_err<E, F>(self, op: F) -> Result<JsValue, E>
where
F: FnOnce(JsValue) -> E,
{
match self {
Self::Ok(val) => Ok(val),
Self::Err(err) => Err(op(err)),
Self::TailCall { .. } => panic!("TailCall cannot be converted to Result"),
}
}
}
/// The set of `Vm` capabilities that builtins depend on.
///
/// Signatures are byte-for-byte copies of the corresponding inherent methods on
/// `Vm`; `impl VmHost for Vm` delegates to them. The trait is intentionally
/// flat and not object-safe — builtins always take `&mut impl VmHost`.
pub trait VmHost {
// Register access
fn reg(&self, idx: u8) -> JsValue;
fn set_reg(&mut self, idx: u8, val: JsValue);
// Object allocation / string creation
fn alloc_object(&mut self, obj: JsObject) -> *mut JsObject;
fn new_string(&mut self, s: &str) -> JsValue;
// Kernel accessors
fn kernel_core(&self) -> &Arc<KernelCore>;
fn session(&self) -> &KernelSession;
fn epoch(&self) -> &Epoch;
// Property resolution
fn property_key_si(&mut self, val: JsValue) -> u32;
fn resolve_property(&self, obj: &JsObject, prop_name_si: u32) -> Option<JsValue>;
fn get_own_property_slot(&self, obj: &JsObject, prop_name_si: u32) -> Option<u32>;
// Property access
fn ordinary_get(&mut self, obj: &JsObject, prop_name_si: u32, receiver: JsValue) -> Result<JsValue, String>;
fn ordinary_set(
&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, receiver: JsValue,
) -> Result<(), String>;
// Property definition
fn define_data_property(
&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, attributes: PropAttributes,
) -> Result<(), String>;
fn define_accessor_property(
&mut self, obj: &mut JsObject, prop_name_si: u32, get: JsValue, set: JsValue, attributes: PropAttributes,
) -> Result<(), String>;
fn set_or_create_prop_value(&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue);
// Lookup / coercion
fn lookup_str(&self, val: JsValue) -> Option<String>;
fn coerce_primitive_bounded(&mut self, value: JsValue, prefer_string: bool) -> Result<JsValue, String>;
fn coerce_number_bounded(&mut self, value: JsValue) -> Result<f64, String>;
// Call infrastructure
fn call_function_sync(&mut self, callee: JsValue, receiver: JsValue, args: &[JsValue]) -> Result<JsValue, String>;
// Error handling
fn checked_object_ptr(&mut self, val: JsValue, error_msg: &str) -> Result<Option<*mut JsObject>, String>;
fn raise_type_error(&mut self, msg: &str) -> Result<(), String>;
fn error_message_text(&self, kind: &str, msg: &str) -> String;
}
+1
View File
@@ -9,6 +9,7 @@ description = "OxideJS - Register-based VM with epoch arena memory"
[dependencies]
oxide_bytecode = { path = "../oxide_bytecode" }
oxide_kernel = { path = "../oxide_kernel" }
oxide_runtime_api = { path = "../oxide_runtime_api" }
oxide_types = { path = "../oxide_types" }
bumpalo.workspace = true
fast-float.workspace = true
+1 -1
View File
@@ -3,8 +3,8 @@ use oxide_types::object::{JsObject, MAX_DENSE_PROPS};
use oxide_types::value::JsValue;
use crate::coercion;
use crate::native::NativeResult;
use crate::vm::Vm;
use oxide_runtime_api::NativeResult;
macro_rules! array_ptr {
($vm:expr, $args:expr) => {{
+1 -1
View File
@@ -3,8 +3,8 @@ use oxide_types::object::{JsObject, NativeFnPtr, PropAttributes};
use oxide_types::value::JsValue;
use crate::coercion;
use crate::native::NativeResult;
use crate::vm::Vm;
use oxide_runtime_api::NativeResult;
pub(crate) const MAX_ARRAY_BUFFER_LENGTH: usize = 1 << 30;
+1 -1
View File
@@ -1,8 +1,8 @@
use oxide_types::object::JsObject;
use oxide_types::value::JsValue;
use crate::native::NativeResult;
use crate::vm::Vm;
use oxide_runtime_api::NativeResult;
pub fn boolean_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
+1 -1
View File
@@ -4,8 +4,8 @@ use oxide_types::value::JsValue;
use crate::builtins::array_buffer::array_buffer_data_ptr;
use crate::coercion;
use crate::native::NativeResult;
use crate::vm::Vm;
use oxide_runtime_api::NativeResult;
#[derive(Clone, Copy)]
pub(crate) struct DataViewData {
+1 -1
View File
@@ -5,8 +5,8 @@ use oxide_types::object::JsObject;
use oxide_types::value::JsValue;
use crate::coercion;
use crate::native::NativeResult;
use crate::vm::Vm;
use oxide_runtime_api::NativeResult;
fn get_timestamp(obj: &JsObject) -> f64 {
let v = obj.get_prop_at(0);
+1 -1
View File
@@ -6,8 +6,8 @@ use oxide_types::object::JsObject;
use oxide_types::value::JsValue;
use crate::coercion;
use crate::native::NativeResult;
use crate::vm::Vm;
use oxide_runtime_api::NativeResult;
fn create_error_object(vm: &mut Vm, error_proto_ptr: *mut JsObject, name: &str, message: &str) -> JsValue {
let obj = vm
+1 -1
View File
@@ -2,8 +2,8 @@ use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_types::object::{JsObject, NativeFnPtr};
use oxide_types::value::JsValue;
use crate::native::NativeResult;
use crate::vm::Vm;
use oxide_runtime_api::NativeResult;
fn invoke_target(vm: &mut Vm, target_val: JsValue, this_val: JsValue, arg_regs: &[u8]) -> NativeResult {
let args: Vec<JsValue> = arg_regs.iter().map(|&r| vm.reg(r)).collect();
@@ -1,5 +1,5 @@
use crate::native::NativeResult;
use crate::vm::Vm;
use oxide_runtime_api::NativeResult;
const ESCAPE_SAFE: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@*_+-./";
+1 -1
View File
@@ -1,5 +1,5 @@
use crate::native::NativeResult;
use crate::vm::Vm;
use oxide_runtime_api::NativeResult;
const URI_UNESCAPED: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.!~*'()";
const URI_RESERVED: &str = ";/?:@&=+$,#";
+1 -1
View File
@@ -3,8 +3,8 @@ use oxide_types::object::JsObject;
use oxide_types::value::JsValue;
use crate::coercion;
use crate::native::NativeResult;
use crate::vm::Vm;
use oxide_runtime_api::NativeResult;
const INNER_PROP: &str = "__inner__";
const INDEX_PROP: &str = "__index__";
+1 -1
View File
@@ -6,8 +6,8 @@ use oxide_types::object::JsObject;
use oxide_types::value::JsValue;
use crate::builtins::object::walk_own_keys;
use crate::native::NativeResult;
use crate::vm::Vm;
use oxide_runtime_api::NativeResult;
pub fn json_parse(vm: &mut Vm, args: &[u8]) -> NativeResult {
if args.len() < 2 {
+1 -1
View File
@@ -3,8 +3,8 @@ use oxide_types::object::JsObject;
use oxide_types::value::JsValue;
use crate::builtins::set::SetKey;
use crate::native::NativeResult;
use crate::vm::Vm;
use oxide_runtime_api::NativeResult;
macro_rules! native_try {
($expr:expr) => {
+1 -1
View File
@@ -1,8 +1,8 @@
use oxide_types::value::JsValue;
use crate::coercion;
use crate::native::NativeResult;
use crate::vm::Vm;
use oxide_runtime_api::NativeResult;
fn num(vm: &Vm, reg: u8) -> f64 {
coercion::to_number(vm.reg(reg))
+1 -1
View File
@@ -1,8 +1,8 @@
use oxide_types::value::JsValue;
use crate::coercion;
use crate::native::NativeResult;
use crate::vm::Vm;
use oxide_runtime_api::NativeResult;
pub fn number_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult {
let n = if args.len() > 1 { coercion::to_number(vm.reg(args[1])) } else { 0.0 };
+1 -1
View File
@@ -5,8 +5,8 @@ use oxide_types::private_key::is_private_name_key;
use oxide_types::value::JsValue;
use crate::coercion;
use crate::native::NativeResult;
use crate::vm::Vm;
use oxide_runtime_api::NativeResult;
pub(crate) fn walk_own_keys(vm: &Vm, obj: &JsObject) -> Vec<(u32, u32)> {
let mut keys: Vec<(u32, u32)> = Vec::new();
+1 -1
View File
@@ -4,8 +4,8 @@ use oxide_types::value::JsValue;
use crate::builtins::object::walk_own_keys;
use crate::coercion;
use crate::native::NativeResult;
use crate::vm::Vm;
use oxide_runtime_api::NativeResult;
pub fn reflect_apply(vm: &mut Vm, args: &[u8]) -> NativeResult {
let target = arg(vm, args, 1);
+1 -1
View File
@@ -3,8 +3,8 @@ use oxide_types::object::{JsObject, NativeFnPtr};
use oxide_types::value::JsValue;
use crate::coercion;
use crate::native::NativeResult;
use crate::vm::Vm;
use oxide_runtime_api::NativeResult;
fn get_regexp_ptr(vm: &mut Vm, args: &[u8]) -> Result<*mut JsObject, JsValue> {
let this_val = vm.reg(args[0]);
+1 -1
View File
@@ -4,8 +4,8 @@ use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_types::object::JsObject;
use oxide_types::value::JsValue;
use crate::native::NativeResult;
use crate::vm::Vm;
use oxide_runtime_api::NativeResult;
macro_rules! native_try {
($expr:expr) => {
+1 -1
View File
@@ -3,9 +3,9 @@ use oxide_types::object::JsObject;
use oxide_types::value::JsValue;
use crate::coercion;
use crate::native::NativeResult;
use crate::vm::Vm;
use memchr::memchr;
use oxide_runtime_api::NativeResult;
fn this_string(vm: &Vm, args: &[u8]) -> String {
coercion::to_string(vm.reg(args[0]))
+1 -1
View File
@@ -1,5 +1,5 @@
use crate::native::NativeResult;
use crate::vm::Vm;
use oxide_runtime_api::NativeResult;
fn stub_error(vm: &mut Vm, name: &str) -> NativeResult {
NativeResult::Err(crate::builtins::error::create_type_error(vm, &format!("{name} is not implemented")))
+1 -1
View File
@@ -2,8 +2,8 @@ use oxide_types::object::JsObject;
use oxide_types::value::JsValue;
use crate::coercion;
use crate::native::NativeResult;
use crate::vm::Vm;
use oxide_runtime_api::NativeResult;
pub fn symbol_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
+1 -1
View File
@@ -4,8 +4,8 @@ use oxide_types::value::JsValue;
use crate::builtins::array_buffer::{array_buffer_data_ptr, new_array_buffer, MAX_ARRAY_BUFFER_LENGTH};
use crate::coercion;
use crate::native::NativeResult;
use crate::vm::Vm;
use oxide_runtime_api::NativeResult;
#[derive(Clone, Copy)]
pub(crate) struct TypedArrayData {
+2 -1
View File
@@ -1,6 +1,7 @@
use crate::native::{NativeFn, NativeResult};
use crate::native::NativeFn;
use crate::vm::{native_fn_ptr_to_fn, FrameContinuation, Vm};
use oxide_kernel::{builtins_debug, builtins_trace};
use oxide_runtime_api::NativeResult;
use oxide_types::object::JsObject;
use oxide_types::value::JsValue;
+1 -36
View File
@@ -1,38 +1,3 @@
use oxide_types::value::JsValue;
pub enum NativeResult {
Ok(JsValue),
Err(JsValue),
TailCall { callee: JsValue, this: JsValue, args: Vec<JsValue> },
}
impl NativeResult {
pub fn ok(val: JsValue) -> Self {
Self::Ok(val)
}
pub fn err(val: JsValue) -> Self {
Self::Err(val)
}
pub fn unwrap(self) -> JsValue {
match self {
Self::Ok(val) => val,
Self::Err(_) => panic!("called `NativeResult::unwrap()` on an `Err` value"),
Self::TailCall { .. } => panic!("called `NativeResult::unwrap()` on a `TailCall` value"),
}
}
pub fn map_err<E, F>(self, op: F) -> Result<JsValue, E>
where
F: FnOnce(JsValue) -> E,
{
match self {
Self::Ok(val) => Ok(val),
Self::Err(err) => Err(op(err)),
Self::TailCall { .. } => panic!("TailCall cannot be converted to Result"),
}
}
}
use oxide_runtime_api::NativeResult;
pub type NativeFn = fn(&mut crate::vm::Vm, args: &[u8]) -> NativeResult;
+1 -1
View File
@@ -503,8 +503,8 @@ mod tests {
use super::*;
use crate::builtins::{array_buffer, data_view, map, set, typed_array};
use crate::native::NativeResult;
use crate::vm::{CallFrame, FrameContinuation};
use oxide_runtime_api::NativeResult;
fn plain_object(vm: &mut Vm) -> *mut JsObject {
let proto_ptr = vm.session.builtin_world().object_proto.as_ptr() as *mut JsObject;
+6 -5
View File
@@ -9,10 +9,11 @@ use smallvec::SmallVec;
pub use crate::bindings::init_kernel_builtins;
use crate::coercion;
use crate::native::{NativeFn, NativeResult};
use crate::native::NativeFn;
use crate::session_gc::SessionGc;
use oxide_kernel::kernel::{KernelCore, KernelSession};
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_runtime_api::NativeResult;
use oxide_types::error::{JsError, JsErrorKind};
use oxide_types::mem::{Epoch, P};
use oxide_types::object::{JsObject, JsString, NativeFnPtr};
@@ -785,11 +786,11 @@ impl Vm {
self.regs[2] = flags_val;
let func = unsafe { crate::vm::native_fn_ptr_to_fn(native_fn) };
let result = match func(self, &[0, 1, 2]) {
crate::native::NativeResult::Ok(v) => v,
crate::native::NativeResult::Err(e) => {
oxide_runtime_api::NativeResult::Ok(v) => v,
oxide_runtime_api::NativeResult::Err(e) => {
return Err(self.error_message_text("TypeError", &self.error_text(e)));
}
crate::native::NativeResult::TailCall { .. } => {
oxide_runtime_api::NativeResult::TailCall { .. } => {
return Err(self.error_message_text("TypeError", "unexpected tail call"));
}
};
@@ -1412,8 +1413,8 @@ impl Default for Vm {
#[cfg(test)]
mod tests {
use super::{opcode, JsValue, TryHandler, Vm};
use crate::native::NativeResult;
use oxide_bytecode::module::CompiledModule;
use oxide_runtime_api::NativeResult;
use oxide_types::object::NativeFnPtr;
use oxide_types::object::{JsObject, PropAttributes};
+2 -1
View File
@@ -1,7 +1,8 @@
use std::sync::Arc;
use crate::native::{NativeFn, NativeResult};
use crate::native::NativeFn;
use crate::vm::{native_fn_ptr_to_fn, CallFrame, ForInIter, FrameContinuation, Vm, MAX_PROTO_CHAIN_DEPTH};
use oxide_runtime_api::NativeResult;
use oxide_types::object::{JsObject, PropAttributes};
use oxide_types::private_key::is_private_name_key;
use oxide_types::value::JsValue;