refactor: 扁平化对象属性存储并优化常量池与 IC 热路径
This commit is contained in:
Generated
+1
@@ -982,6 +982,7 @@ dependencies = [
|
||||
"regex",
|
||||
"ryu",
|
||||
"serde_json",
|
||||
"smallvec",
|
||||
"tracing",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
@@ -40,6 +40,7 @@ ryu = "1"
|
||||
memchr = "2"
|
||||
regex = "1"
|
||||
indexmap = "2"
|
||||
smallvec = { version = "1", features = ["union"] }
|
||||
chrono = { version = "0.4", default-features = false, features = ["clock"] }
|
||||
unicode-normalization = "0.1"
|
||||
tracing = "0.1.44"
|
||||
|
||||
@@ -54,6 +54,7 @@ pub(crate) enum Label {
|
||||
pub(crate) struct CompileCtx {
|
||||
pub(crate) bytecode: Vec<opcode::Instr>,
|
||||
pub(crate) constants: Vec<Constant>,
|
||||
constant_map: HashMap<ConstantKey, u16>,
|
||||
next_reg: u8,
|
||||
pub(crate) max_regs: u8,
|
||||
reserved_reg_start: u8,
|
||||
@@ -85,11 +86,23 @@ pub(crate) enum FunctionBodyContext {
|
||||
ClassElement,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
enum ConstantKey {
|
||||
Number(u64),
|
||||
Int(i32),
|
||||
String(String),
|
||||
Boolean(bool),
|
||||
Null,
|
||||
Undefined,
|
||||
BytecodeFunc(u32),
|
||||
}
|
||||
|
||||
impl CompileCtx {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
bytecode: Vec::new(),
|
||||
constants: Vec::new(),
|
||||
constant_map: HashMap::new(),
|
||||
next_reg: 0,
|
||||
max_regs: 0,
|
||||
reserved_reg_start: 0,
|
||||
@@ -159,9 +172,17 @@ impl CompileCtx {
|
||||
}
|
||||
|
||||
pub(crate) fn add_constant(&mut self, c: Constant) -> u16 {
|
||||
if let Some(idx) = self.constants.iter().position(|x| x == &c) {
|
||||
return idx as u16;
|
||||
if let Some(key) = ConstantKey::from_constant(&c) {
|
||||
if let Some(&idx) = self.constant_map.get(&key) {
|
||||
return idx;
|
||||
}
|
||||
|
||||
let idx = self.constants.len() as u16;
|
||||
self.constants.push(c);
|
||||
self.constant_map.insert(key, idx);
|
||||
return idx;
|
||||
}
|
||||
|
||||
let idx = self.constants.len();
|
||||
self.constants.push(c);
|
||||
idx as u16
|
||||
@@ -290,6 +311,21 @@ impl CompileCtx {
|
||||
}
|
||||
}
|
||||
|
||||
impl ConstantKey {
|
||||
fn from_constant(value: &Constant) -> Option<Self> {
|
||||
match value {
|
||||
Constant::Number(v) => Some(Self::Number(v.to_bits())),
|
||||
Constant::Int(v) => Some(Self::Int(*v)),
|
||||
Constant::String(v) => Some(Self::String(v.clone())),
|
||||
Constant::Boolean(v) => Some(Self::Boolean(*v)),
|
||||
Constant::Null => Some(Self::Null),
|
||||
Constant::Undefined => Some(Self::Undefined),
|
||||
Constant::BytecodeFunc(v) => Some(Self::BytecodeFunc(*v)),
|
||||
Constant::RegExp(_, _) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Compiler {
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
|
||||
@@ -52,6 +52,17 @@ fn compile_constants() {
|
||||
assert_eq!(module.constants[0], oxide_compiler::compiler::Constant::Int(42));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compile_dedups_identical_string_constants() {
|
||||
let module = compile_source("var a = 'hello'; var b = 'hello';");
|
||||
let count = module
|
||||
.constants
|
||||
.iter()
|
||||
.filter(|c| matches!(c, oxide_compiler::compiler::Constant::String(s) if s == "hello"))
|
||||
.count();
|
||||
assert_eq!(count, 1, "identical string constants should be interned once per module");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compile_negation() {
|
||||
let module = compile_source("-5");
|
||||
|
||||
@@ -227,9 +227,9 @@ fn make_pair(
|
||||
|
||||
// Pre-allocate Vec slots so wire_ctor_proto can overwrite vec[0] later.
|
||||
// Proto: 1 property ("constructor"), Ctor: 2 properties ("prototype", "name").
|
||||
proto.ensure_hash_props().push(Box::new(JsValue::undefined())); // slot 0: "constructor" placeholder
|
||||
ctor.ensure_hash_props().push(Box::new(JsValue::undefined())); // slot 0: "prototype" placeholder
|
||||
ctor.ensure_hash_props().push(Box::new(JsValue::undefined())); // slot 1: "name" placeholder
|
||||
proto.ensure_hash_props().push(JsValue::undefined()); // slot 0: "constructor" placeholder
|
||||
ctor.ensure_hash_props().push(JsValue::undefined()); // slot 0: "prototype" placeholder
|
||||
ctor.ensure_hash_props().push(JsValue::undefined()); // slot 1: "name" placeholder
|
||||
|
||||
(P::new(proto), P::new(ctor))
|
||||
}
|
||||
@@ -299,13 +299,13 @@ impl BuiltinWorld {
|
||||
let ctor = unsafe { &mut *ctor_ptr };
|
||||
let vec = ctor.ensure_hash_props();
|
||||
if !vec.is_empty() {
|
||||
*vec[0] = JsValue::from_js_object(proto.as_ptr() as *mut JsObject);
|
||||
vec[0] = JsValue::from_js_object(proto.as_ptr() as *mut JsObject);
|
||||
}
|
||||
let proto_ptr = proto.as_ptr() as *mut JsObject;
|
||||
let proto = unsafe { &mut *proto_ptr };
|
||||
let pvec = proto.ensure_hash_props();
|
||||
if !pvec.is_empty() {
|
||||
*pvec[0] = JsValue::from_js_object(ctor_ptr);
|
||||
pvec[0] = JsValue::from_js_object(ctor_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -626,11 +626,11 @@ impl BuiltinWorld {
|
||||
wrapper.set_shape_id(name_shape);
|
||||
wrapper
|
||||
.ensure_hash_props()
|
||||
.push(Box::new(JsValue::string(string_forge.intern(method_name).0, 0)));
|
||||
.push(JsValue::string(string_forge.intern(method_name).0, 0));
|
||||
let wrapper_val = JsValue::from_js_object(Box::into_raw(wrapper));
|
||||
let new_shape = shape_forge.make_shape(proto.shape_id(), si);
|
||||
proto.set_shape_id(new_shape);
|
||||
proto.ensure_hash_props().push(Box::new(wrapper_val));
|
||||
proto.ensure_hash_props().push(wrapper_val);
|
||||
proto.bump_generation();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -9,13 +9,21 @@ use crate::{kernel_debug, kernel_info};
|
||||
|
||||
pub use oxide_compiler::compiler::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::new() }
|
||||
Self {
|
||||
map: DashMap::with_shard_amount(16),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_or_compile(
|
||||
|
||||
@@ -103,15 +103,15 @@ impl OxideKernel {
|
||||
|
||||
let nan_shape = shape_forge.make_shape(EMPTY_SHAPE_ID, si_nan);
|
||||
global_obj.set_shape_id(nan_shape);
|
||||
global_obj.ensure_hash_props().push(Box::new(JsValue::float(f64::NAN)));
|
||||
global_obj.ensure_hash_props().push(JsValue::float(f64::NAN));
|
||||
|
||||
let undef_shape = shape_forge.make_shape(nan_shape, si_undef);
|
||||
global_obj.set_shape_id(undef_shape);
|
||||
global_obj.ensure_hash_props().push(Box::new(JsValue::undefined()));
|
||||
global_obj.ensure_hash_props().push(JsValue::undefined());
|
||||
|
||||
let inf_shape = shape_forge.make_shape(undef_shape, si_infinity);
|
||||
global_obj.set_shape_id(inf_shape);
|
||||
global_obj.ensure_hash_props().push(Box::new(JsValue::float(f64::INFINITY)));
|
||||
global_obj.ensure_hash_props().push(JsValue::float(f64::INFINITY));
|
||||
|
||||
let global_object = P::new(global_obj);
|
||||
|
||||
|
||||
@@ -18,6 +18,13 @@ pub struct Shape {
|
||||
pub parent: Option<ShapeId>,
|
||||
}
|
||||
|
||||
/// Shared hidden-class store.
|
||||
///
|
||||
/// Concurrency model:
|
||||
/// - `shapes` is append-mostly behind an `RwLock`.
|
||||
/// - `transitions` is a sharded `DashMap` keyed by `(parent_shape, property_name)`.
|
||||
/// - Shard count is fixed for reproducible contention behavior across machines instead
|
||||
/// of relying on DashMap's CPU-dependent default.
|
||||
pub struct ShapeForge {
|
||||
shapes: RwLock<Vec<Option<Arc<Shape>>>>,
|
||||
transitions: DashMap<u64, ShapeId>,
|
||||
@@ -28,7 +35,7 @@ impl ShapeForge {
|
||||
pub fn new() -> Self {
|
||||
let forge = Self {
|
||||
shapes: RwLock::new(Vec::with_capacity(256)),
|
||||
transitions: DashMap::with_capacity(256),
|
||||
transitions: DashMap::with_capacity_and_shard_amount(256, 16),
|
||||
next_id: AtomicU32::new(2),
|
||||
};
|
||||
{
|
||||
|
||||
@@ -24,6 +24,14 @@ struct StringForgeInner {
|
||||
entries: Vec<StringEntry>,
|
||||
}
|
||||
|
||||
/// Global string interning pool shared by all VMs.
|
||||
///
|
||||
/// Concurrency model:
|
||||
/// `StringForge` keeps a single `RwLock` around the interner state.
|
||||
/// - `lookup()` takes a shared read lock and scales across readers.
|
||||
/// - `intern()` uses a read-check then write-check pattern to keep hot-path hits cheap.
|
||||
/// - New string insertion is serialized, which matches the workload here: most strings
|
||||
/// are interned early and later accesses are dominated by reads.
|
||||
pub struct StringForge {
|
||||
inner: RwLock<StringForgeInner>,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum JsErrorKind {
|
||||
TypeError,
|
||||
RangeError,
|
||||
ReferenceError,
|
||||
SyntaxError,
|
||||
Error,
|
||||
URIError,
|
||||
EvalError,
|
||||
}
|
||||
|
||||
impl fmt::Display for JsErrorKind {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let name = match self {
|
||||
Self::TypeError => "TypeError",
|
||||
Self::RangeError => "RangeError",
|
||||
Self::ReferenceError => "ReferenceError",
|
||||
Self::SyntaxError => "SyntaxError",
|
||||
Self::Error => "Error",
|
||||
Self::URIError => "URIError",
|
||||
Self::EvalError => "EvalError",
|
||||
};
|
||||
f.write_str(name)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct JsError {
|
||||
pub kind: JsErrorKind,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
impl JsError {
|
||||
pub fn type_error(msg: impl Into<String>) -> Self {
|
||||
Self::new(JsErrorKind::TypeError, msg)
|
||||
}
|
||||
|
||||
pub fn range_error(msg: impl Into<String>) -> Self {
|
||||
Self::new(JsErrorKind::RangeError, msg)
|
||||
}
|
||||
|
||||
pub fn reference_error(msg: impl Into<String>) -> Self {
|
||||
Self::new(JsErrorKind::ReferenceError, msg)
|
||||
}
|
||||
|
||||
pub fn syntax_error(msg: impl Into<String>) -> Self {
|
||||
Self::new(JsErrorKind::SyntaxError, msg)
|
||||
}
|
||||
|
||||
pub fn generic(msg: impl Into<String>) -> Self {
|
||||
Self::new(JsErrorKind::Error, msg)
|
||||
}
|
||||
|
||||
pub fn new(kind: JsErrorKind, msg: impl Into<String>) -> Self {
|
||||
Self { kind, message: msg.into() }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for JsError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}: {}", self.kind, self.message)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JsError> for String {
|
||||
fn from(value: JsError) -> Self {
|
||||
value.to_string()
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
#![doc = "OxideJS - Shared core types (JsValue, JsObject, Shape, P, Epoch)"]
|
||||
|
||||
pub mod error;
|
||||
pub mod mem;
|
||||
pub mod object;
|
||||
pub mod shape;
|
||||
pub mod value;
|
||||
|
||||
pub use error::{JsError, JsErrorKind};
|
||||
|
||||
@@ -152,7 +152,7 @@ impl PropIndex for i32 {
|
||||
/// [30] is_extensible
|
||||
/// [31] is_function
|
||||
/// native_arg_count: u8 (1 byte + 3 pad)
|
||||
/// hash_props: *mut u8 (8 bytes, points to Box<Vec<Box<JsValue>>>)
|
||||
/// hash_props: *mut u8 (8 bytes, points to Box<Vec<JsValue>>)
|
||||
/// prop_meta: *mut u8 (8 bytes, points to Box<Vec<Option<PropMetaEntry>>>)
|
||||
/// proto: JsValue (8 bytes)
|
||||
/// generation: u32 (4 bytes + 4 pad)
|
||||
@@ -213,7 +213,7 @@ impl JsObject {
|
||||
captured_this: JsValue::undefined(),
|
||||
home_object: JsValue::undefined(),
|
||||
};
|
||||
let vec = Box::new(vec![Box::new(JsValue::undefined()); n_elements]);
|
||||
let vec = Box::new(vec![JsValue::undefined(); n_elements]);
|
||||
obj.hash_props = Box::into_raw(vec) as *mut u8;
|
||||
obj
|
||||
}
|
||||
@@ -232,7 +232,9 @@ impl JsObject {
|
||||
if self.hash_props.is_null() {
|
||||
0
|
||||
} else {
|
||||
let vec = unsafe { &*(self.hash_props as *const Vec<Box<JsValue>>) };
|
||||
// SAFETY: hash_props is either null or was created from Box<Vec<JsValue>>
|
||||
// in ensure_hash_props/new_array and remains owned by this object.
|
||||
let vec = unsafe { &*(self.hash_props as *const Vec<JsValue>) };
|
||||
vec.len() as u32
|
||||
}
|
||||
}
|
||||
@@ -246,7 +248,7 @@ impl JsObject {
|
||||
vec.truncate(target);
|
||||
} else {
|
||||
while vec.len() < target {
|
||||
vec.push(Box::new(JsValue::undefined()));
|
||||
vec.push(JsValue::undefined());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -271,6 +273,8 @@ impl JsObject {
|
||||
let vec = Box::new(vec![None::<PropMetaEntry>; len]);
|
||||
self.prop_meta = Box::into_raw(vec) as *mut u8;
|
||||
}
|
||||
// SAFETY: prop_meta was set from Box<Vec<Option<PropMetaEntry>>> in
|
||||
// ensure_prop_meta and remains valid while this object owns it.
|
||||
unsafe { &mut *(self.prop_meta as *mut Vec<Option<PropMetaEntry>>) }
|
||||
}
|
||||
|
||||
@@ -278,6 +282,8 @@ impl JsObject {
|
||||
if self.prop_meta.is_null() {
|
||||
None
|
||||
} else {
|
||||
// SAFETY: prop_meta was set from Box<Vec<Option<PropMetaEntry>>> in
|
||||
// ensure_prop_meta and remains valid while this object owns it.
|
||||
unsafe { Some(&*(self.prop_meta as *const Vec<Option<PropMetaEntry>>)) }
|
||||
}
|
||||
}
|
||||
@@ -286,6 +292,8 @@ impl JsObject {
|
||||
if self.prop_meta.is_null() {
|
||||
None
|
||||
} else {
|
||||
// SAFETY: prop_meta was set from Box<Vec<Option<PropMetaEntry>>> in
|
||||
// ensure_prop_meta and remains valid while this object owns it.
|
||||
unsafe { Some(&mut *(self.prop_meta as *mut Vec<Option<PropMetaEntry>>)) }
|
||||
}
|
||||
}
|
||||
@@ -375,20 +383,22 @@ impl JsObject {
|
||||
}
|
||||
|
||||
/// Initialize hash_props if null, return mutable reference to Vec.
|
||||
pub fn ensure_hash_props(&mut self) -> &mut Vec<Box<JsValue>> {
|
||||
pub fn ensure_hash_props(&mut self) -> &mut Vec<JsValue> {
|
||||
if self.hash_props.is_null() {
|
||||
let vec = Box::new(Vec::<Box<JsValue>>::new());
|
||||
let vec = Box::new(Vec::<JsValue>::new());
|
||||
self.hash_props = Box::into_raw(vec) as *mut u8;
|
||||
}
|
||||
unsafe { &mut *(self.hash_props as *mut Vec<Box<JsValue>>) }
|
||||
// SAFETY: hash_props was set from Box<Vec<JsValue>> in this method or new_array.
|
||||
unsafe { &mut *(self.hash_props as *mut Vec<JsValue>) }
|
||||
}
|
||||
|
||||
/// Safe read access to hash_props vec. Returns None if not allocated.
|
||||
pub fn hash_props_vec(&self) -> Option<&Vec<Box<JsValue>>> {
|
||||
pub fn hash_props_vec(&self) -> Option<&Vec<JsValue>> {
|
||||
if self.hash_props.is_null() {
|
||||
None
|
||||
} else {
|
||||
unsafe { Some(&*(self.hash_props as *const Vec<Box<JsValue>>)) }
|
||||
// SAFETY: hash_props was set from Box<Vec<JsValue>> in ensure_hash_props/new_array.
|
||||
unsafe { Some(&*(self.hash_props as *const Vec<JsValue>)) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -398,8 +408,9 @@ impl JsObject {
|
||||
if self.hash_props.is_null() {
|
||||
return JsValue::undefined();
|
||||
}
|
||||
let vec = unsafe { &*(self.hash_props as *const Vec<Box<JsValue>>) };
|
||||
vec.get(position.to_u32() as usize).map(|b| **b).unwrap_or(JsValue::undefined())
|
||||
// SAFETY: hash_props was set from Box<Vec<JsValue>> in ensure_hash_props/new_array.
|
||||
let vec = unsafe { &*(self.hash_props as *const Vec<JsValue>) };
|
||||
vec.get(position.to_u32() as usize).copied().unwrap_or(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// Set property value at position index. Vec auto-grows if needed.
|
||||
@@ -408,12 +419,12 @@ impl JsObject {
|
||||
{
|
||||
let vec = self.ensure_hash_props();
|
||||
if pos < vec.len() {
|
||||
*vec[pos] = val;
|
||||
vec[pos] = val;
|
||||
} else {
|
||||
while vec.len() < pos {
|
||||
vec.push(Box::new(JsValue::undefined()));
|
||||
vec.push(JsValue::undefined());
|
||||
}
|
||||
vec.push(Box::new(val));
|
||||
vec.push(val);
|
||||
}
|
||||
}
|
||||
if let Some(meta) = self.prop_meta_vec_mut() {
|
||||
@@ -427,21 +438,22 @@ impl JsObject {
|
||||
pub fn push_prop(&mut self, val: JsValue) -> u32 {
|
||||
let vec = self.ensure_hash_props();
|
||||
let pos = vec.len();
|
||||
vec.push(Box::new(val));
|
||||
vec.push(val);
|
||||
if let Some(meta) = self.prop_meta_vec_mut() {
|
||||
meta.push(None);
|
||||
}
|
||||
pos as u32
|
||||
}
|
||||
|
||||
/// Get a stable pointer to the Box<JsValue> at position.
|
||||
/// Get a pointer to the JsValue at position.
|
||||
/// Returns None if hash_props not allocated or position out of bounds.
|
||||
pub fn prop_ptr_at(&self, position: impl PropIndex) -> Option<*const JsValue> {
|
||||
if self.hash_props.is_null() {
|
||||
return None;
|
||||
}
|
||||
let vec = unsafe { &*(self.hash_props as *const Vec<Box<JsValue>>) };
|
||||
vec.get(position.to_u32() as usize).map(|b| &**b as *const JsValue)
|
||||
// SAFETY: hash_props was set from Box<Vec<JsValue>> in ensure_hash_props/new_array.
|
||||
let vec = unsafe { &*(self.hash_props as *const Vec<JsValue>) };
|
||||
vec.get(position.to_u32() as usize).map(|v| v as *const JsValue)
|
||||
}
|
||||
|
||||
/// Get the length of hash_props vec (returns 0 if not allocated).
|
||||
@@ -449,7 +461,8 @@ impl JsObject {
|
||||
if self.hash_props.is_null() {
|
||||
0
|
||||
} else {
|
||||
unsafe { &*(self.hash_props as *const Vec<Box<JsValue>>) }.len()
|
||||
// SAFETY: hash_props was set from Box<Vec<JsValue>> in ensure_hash_props/new_array.
|
||||
unsafe { &*(self.hash_props as *const Vec<JsValue>) }.len()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -473,6 +486,8 @@ impl JsObject {
|
||||
if std::ptr::eq(cursor_ptr, self_ptr) {
|
||||
return Err("cyclic __proto__ value");
|
||||
}
|
||||
debug_assert!(!cursor_ptr.is_null(), "prototype cursor pointer must not be null");
|
||||
// SAFETY: cursor is known to be an object JsValue, so it encodes a valid JsObject pointer.
|
||||
let obj = unsafe { &*cursor_ptr };
|
||||
cursor = obj.proto;
|
||||
}
|
||||
@@ -607,7 +622,7 @@ mod tests {
|
||||
fn prop_count_roundtrip() {
|
||||
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null());
|
||||
assert_eq!(obj.prop_count(), 0);
|
||||
obj.ensure_hash_props().push(Box::new(JsValue::int(17)));
|
||||
obj.ensure_hash_props().push(JsValue::int(17));
|
||||
assert_eq!(obj.prop_count(), 1);
|
||||
}
|
||||
|
||||
@@ -654,14 +669,10 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_props_stable_pointer() {
|
||||
fn hash_props_flat_storage_roundtrip() {
|
||||
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null());
|
||||
obj.set_prop_at(0, JsValue::int(100));
|
||||
let ptr = obj.prop_ptr_at(0);
|
||||
obj.set_prop_at(1, JsValue::int(200));
|
||||
// Pointer to first element should be stable (per Box allocation)
|
||||
let ptr2 = obj.prop_ptr_at(0);
|
||||
assert_eq!(ptr, ptr2);
|
||||
assert_eq!(obj.get_prop_at(0), JsValue::int(100));
|
||||
assert_eq!(obj.get_prop_at(1), JsValue::int(200));
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ regex.workspace = true
|
||||
serde_json.workspace = true
|
||||
chrono.workspace = true
|
||||
indexmap.workspace = true
|
||||
smallvec.workspace = true
|
||||
unicode-normalization.workspace = true
|
||||
tracing.workspace = true
|
||||
|
||||
|
||||
@@ -26,19 +26,19 @@ fn bind_error_subtype_constructor(
|
||||
let ctor_shape1 = sh.make_shape(EMPTY_SHAPE_ID, si_prototype);
|
||||
let ctor_shape2 = sh.make_shape(ctor_shape1, si_name);
|
||||
ctor.set_shape_id(ctor_shape2);
|
||||
ctor.ensure_hash_props().push(Box::new(JsValue::from_js_object(proto_ptr)));
|
||||
ctor.ensure_hash_props().push(Box::new(JsValue::string(name_si, 0)));
|
||||
ctor.ensure_hash_props().push(JsValue::from_js_object(proto_ptr));
|
||||
ctor.ensure_hash_props().push(JsValue::string(name_si, 0));
|
||||
|
||||
let ctor_ptr = Box::into_raw(ctor);
|
||||
|
||||
let proto = unsafe { &mut *proto_ptr };
|
||||
let proto_ctor_shape = sh.make_shape(proto.shape_id(), sf.intern("constructor").0);
|
||||
proto.set_shape_id(proto_ctor_shape);
|
||||
proto.ensure_hash_props().push(Box::new(JsValue::from_js_object(ctor_ptr)));
|
||||
proto.ensure_hash_props().push(JsValue::from_js_object(ctor_ptr));
|
||||
|
||||
let global_shape = sh.make_shape(global.shape_id(), name_si);
|
||||
global.set_shape_id(global_shape);
|
||||
global.ensure_hash_props().push(Box::new(JsValue::from_js_object(ctor_ptr)));
|
||||
global.ensure_hash_props().push(JsValue::from_js_object(ctor_ptr));
|
||||
global.bump_generation();
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ pub fn bind_error(kernel: &Arc<OxideKernel>, global: &mut JsObject) {
|
||||
let err_shape = kernel.shape_forge().make_shape(global.shape_id(), si_err);
|
||||
let err_val = JsValue::from_js_object(kernel.builtin_world().error_constructor.as_ptr() as *mut JsObject);
|
||||
global.set_shape_id(err_shape);
|
||||
global.ensure_hash_props().push(Box::new(err_val));
|
||||
global.ensure_hash_props().push(err_val);
|
||||
global.bump_generation();
|
||||
|
||||
bind_error_subtype_constructor(
|
||||
|
||||
@@ -68,13 +68,13 @@ pub fn bind_math(kernel: &Arc<OxideKernel>, global: &mut JsObject) {
|
||||
let si = kernel.string_forge().as_ref().intern(name).0;
|
||||
let sh_c = kernel.shape_forge().as_ref().make_shape(math.shape_id(), si);
|
||||
math.set_shape_id(sh_c);
|
||||
math.ensure_hash_props().push(Box::new(JsValue::float(val)));
|
||||
math.ensure_hash_props().push(JsValue::float(val));
|
||||
}
|
||||
|
||||
let si_m = kernel.string_forge().intern("Math").0;
|
||||
let m_shape = kernel.shape_forge().make_shape(global.shape_id(), si_m);
|
||||
let m_val = JsValue::from_js_object(kernel.builtin_world().math_object.as_ptr() as *mut JsObject);
|
||||
global.set_shape_id(m_shape);
|
||||
global.ensure_hash_props().push(Box::new(m_val));
|
||||
global.ensure_hash_props().push(m_val);
|
||||
global.bump_generation();
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ pub fn bind_number(kernel: &Arc<OxideKernel>, global: &mut JsObject) {
|
||||
let num_shape = kernel.shape_forge().make_shape(global.shape_id(), si_num);
|
||||
let num_val = JsValue::from_js_object(kernel.builtin_world().number_constructor.as_ptr() as *mut JsObject);
|
||||
global.set_shape_id(num_shape);
|
||||
global.ensure_hash_props().push(Box::new(num_val));
|
||||
global.ensure_hash_props().push(num_val);
|
||||
global.bump_generation();
|
||||
|
||||
{
|
||||
@@ -52,7 +52,7 @@ pub fn bind_number(kernel: &Arc<OxideKernel>, global: &mut JsObject) {
|
||||
("NEGATIVE_INFINITY", JsValue::float(f64::NEG_INFINITY)),
|
||||
("POSITIVE_INFINITY", JsValue::float(f64::INFINITY)),
|
||||
] {
|
||||
num_ctor.ensure_hash_props().push(Box::new(value));
|
||||
num_ctor.ensure_hash_props().push(value);
|
||||
let prop_si = kernel.string_forge().intern(name).0;
|
||||
let next_shape = kernel.shape_forge().make_shape(num_ctor.shape_id(), prop_si);
|
||||
num_ctor.set_shape_id(next_shape);
|
||||
|
||||
@@ -44,6 +44,6 @@ pub fn bind_string(kernel: &Arc<OxideKernel>, global: &mut JsObject) {
|
||||
let str_shape = kernel.shape_forge().make_shape(global.shape_id(), si_str);
|
||||
let str_val = JsValue::from_js_object(kernel.builtin_world().string_constructor.as_ptr() as *mut JsObject);
|
||||
global.set_shape_id(str_shape);
|
||||
global.ensure_hash_props().push(Box::new(str_val));
|
||||
global.ensure_hash_props().push(str_val);
|
||||
global.bump_generation();
|
||||
}
|
||||
|
||||
@@ -54,6 +54,6 @@ fn bind_well_known_symbol(kernel: &Arc<OxideKernel>, ctor: &mut JsObject, name:
|
||||
let si = kernel.string_forge().intern(name).0;
|
||||
let shape_id = kernel.shape_forge().make_shape(ctor.shape_id(), si);
|
||||
ctor.set_shape_id(shape_id);
|
||||
ctor.ensure_hash_props().push(Box::new(val));
|
||||
ctor.ensure_hash_props().push(val);
|
||||
ctor.bump_generation();
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ macro_rules! bind_constructor_hash {
|
||||
let shape = $kernel.shape_forge().make_shape($global.shape_id(), si);
|
||||
let val = $crate::JsValue::from_js_object($ctor_ptr);
|
||||
$global.set_shape_id(shape);
|
||||
$global.ensure_hash_props().push(Box::new(val));
|
||||
$global.ensure_hash_props().push(val);
|
||||
$global.bump_generation();
|
||||
let ctor = unsafe { &mut *$ctor_ptr };
|
||||
// SAFETY: $ctor_fn is a fn-item expression; casting to *const () preserves validity.
|
||||
@@ -73,7 +73,7 @@ pub(crate) fn bind_global_value(kernel: &Arc<OxideKernel>, global: &mut JsObject
|
||||
let si = kernel.string_forge().intern(name).0;
|
||||
let shape = kernel.shape_forge().make_shape(global.shape_id(), si);
|
||||
global.set_shape_id(shape);
|
||||
global.ensure_hash_props().push(Box::new(value));
|
||||
global.ensure_hash_props().push(value);
|
||||
global.bump_generation();
|
||||
}
|
||||
|
||||
|
||||
@@ -118,12 +118,12 @@ pub fn error_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let name_val = obj
|
||||
.hash_props_vec()
|
||||
.and_then(|v| v.first())
|
||||
.map(|b| **b)
|
||||
.copied()
|
||||
.unwrap_or(JsValue::undefined());
|
||||
let msg_val = obj
|
||||
.hash_props_vec()
|
||||
.and_then(|v| v.get(1))
|
||||
.map(|b| **b)
|
||||
.copied()
|
||||
.unwrap_or(JsValue::undefined());
|
||||
(
|
||||
vm.lookup_str(name_val).unwrap_or_else(|| "Error".to_string()),
|
||||
|
||||
@@ -19,11 +19,11 @@ fn bind_dispatcher(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let wrapper = unsafe { &*wrapper_val.as_js_object_ptr() };
|
||||
let bound_target = wrapper
|
||||
.hash_props_vec()
|
||||
.and_then(|v| v.first().map(|b| **b))
|
||||
.and_then(|v| v.first().copied())
|
||||
.unwrap_or(JsValue::undefined());
|
||||
let bound_this = wrapper
|
||||
.hash_props_vec()
|
||||
.and_then(|v| v.get(1).map(|b| **b))
|
||||
.and_then(|v| v.get(1).copied())
|
||||
.unwrap_or(JsValue::undefined());
|
||||
|
||||
// Forward bound call arguments (skip args[0], the bound-wrapper receiver).
|
||||
@@ -63,7 +63,7 @@ pub fn function_apply(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
for i in 0..n {
|
||||
let vec = arr.hash_props_vec();
|
||||
if let Some(v) = vec.and_then(|v| v.get(i)) {
|
||||
vm.set_reg(base + i as u8, **v);
|
||||
vm.set_reg(base + i as u8, *v);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -97,8 +97,8 @@ pub fn function_bind(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
// SAFETY: bind_dispatcher is a NativeFn fn-item; valid to store as NativeFnPtr.
|
||||
(*wrapper).set_native_fn(Some(NativeFnPtr::from_raw(bind_dispatcher as *const ())));
|
||||
(*wrapper).set_native_arg_count(0);
|
||||
(*wrapper).ensure_hash_props().push(Box::new(target_val));
|
||||
(*wrapper).ensure_hash_props().push(Box::new(bound_this));
|
||||
(*wrapper).ensure_hash_props().push(target_val);
|
||||
(*wrapper).ensure_hash_props().push(bound_this);
|
||||
}
|
||||
|
||||
NativeResult::Ok(JsValue::from_js_object(wrapper))
|
||||
|
||||
@@ -70,7 +70,7 @@ fn value_to_jsvalue(vm: &mut Vm, val: &serde_json::Value) -> JsValue {
|
||||
let jsv = value_to_jsvalue(vm, val);
|
||||
let new_shape = vm.kernel().shape_forge().make_shape(obj.shape_id(), si);
|
||||
obj.set_shape_id(new_shape);
|
||||
obj.ensure_hash_props().push(Box::new(jsv));
|
||||
obj.ensure_hash_props().push(jsv);
|
||||
}
|
||||
let obj_ptr = vm.epoch().alloc(obj);
|
||||
JsValue::from_js_object(obj_ptr)
|
||||
|
||||
@@ -43,13 +43,13 @@ fn set_prop(obj: &mut JsObject, name: &str, val: JsValue, vm: &Vm) {
|
||||
let si = vm.kernel().string_forge().intern(name).0;
|
||||
let shape_id = vm.kernel().shape_forge().make_shape(obj.shape_id(), si);
|
||||
obj.set_shape_id(shape_id);
|
||||
obj.ensure_hash_props().push(Box::new(val));
|
||||
obj.ensure_hash_props().push(val);
|
||||
}
|
||||
|
||||
fn get_prop(obj: &JsObject, idx: usize) -> JsValue {
|
||||
obj.hash_props_vec()
|
||||
.and_then(|v| v.get(idx))
|
||||
.map(|b| **b)
|
||||
.copied()
|
||||
.unwrap_or(JsValue::undefined())
|
||||
}
|
||||
|
||||
@@ -57,9 +57,9 @@ fn set_prop_at(obj: *mut JsObject, idx: usize, val: JsValue) {
|
||||
unsafe {
|
||||
let re = &mut *obj;
|
||||
if let Some(vec) = re.hash_props_vec() {
|
||||
let ptr = vec.as_ptr() as *mut Box<JsValue>;
|
||||
let ptr = vec.as_ptr() as *mut JsValue;
|
||||
let v = &mut *ptr.add(idx);
|
||||
**v = val;
|
||||
*v = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -201,14 +201,14 @@ pub fn regexp_exec(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
|
||||
let full_match = &haystack[m.start()..m.end()];
|
||||
let full_si = vm.kernel().string_forge().intern(full_match).0;
|
||||
match_obj.ensure_hash_props().push(Box::new(JsValue::string(full_si, 0)));
|
||||
match_obj.ensure_hash_props().push(JsValue::string(full_si, 0));
|
||||
|
||||
let captures = regex.captures(&haystack[m.start()..m.end()]);
|
||||
if let Some(caps) = &captures {
|
||||
for i in 1..caps.len() {
|
||||
let cap_str = caps.get(i).map(|cm| cm.as_str()).unwrap_or("");
|
||||
let si = vm.kernel().string_forge().intern(cap_str).0;
|
||||
match_obj.ensure_hash_props().push(Box::new(JsValue::string(si, 0)));
|
||||
match_obj.ensure_hash_props().push(JsValue::string(si, 0));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -306,7 +306,7 @@ pub fn to_object(val: JsValue, vm: &mut Vm) -> Result<JsValue, &'static str> {
|
||||
let obj = vm.epoch.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, proto_val));
|
||||
let obj_val = JsValue::from_js_object(obj);
|
||||
let obj_ref = unsafe { &mut *obj };
|
||||
obj_ref.ensure_hash_props().push(Box::new(val));
|
||||
obj_ref.ensure_hash_props().push(val);
|
||||
obj_ref.set_prop_count(1);
|
||||
Ok(obj_val)
|
||||
}
|
||||
|
||||
@@ -2,9 +2,32 @@ use oxide_compiler::opcode::OpCode;
|
||||
use oxide_kernel::prop_forge::PropTemplate;
|
||||
use oxide_kernel::{ic_debug, ic_trace};
|
||||
use oxide_types::object::JsObject;
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
use crate::vm::Vm;
|
||||
|
||||
#[inline(always)]
|
||||
fn ic_get_hit(obj: &JsObject, shape_id: u32, slot_index: u32) -> Option<JsValue> {
|
||||
if shape_id != 0 && obj.shape_id() == shape_id && slot_index < obj.prop_vec_len() as u32 {
|
||||
Some(obj.get_prop_at(slot_index))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn ic_set_hit(obj: &mut JsObject, shape_id: u32, slot_index: u32, value: JsValue) -> bool {
|
||||
if shape_id != 0 && obj.shape_id() == shape_id && slot_index < obj.prop_vec_len() as u32 {
|
||||
obj.set_prop_at(slot_index, value);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[cold]
|
||||
fn prop_cache_miss() {}
|
||||
|
||||
impl Vm {
|
||||
pub(crate) fn dispatch_property_op(&mut self, op: OpCode, rd: usize, a: usize, b: usize) -> Result<(), String> {
|
||||
match op {
|
||||
@@ -70,10 +93,11 @@ impl Vm {
|
||||
let cached_shape_id = ext0 & 0x00FF_FFFF;
|
||||
let cached_slot = ext1;
|
||||
|
||||
if cached_shape_id != 0 && cached_shape_id == obj.shape_id() && cached_slot < obj.prop_vec_len() as u32 {
|
||||
self.regs[a] = obj.get_prop_at(cached_slot);
|
||||
if let Some(value) = ic_get_hit(obj, cached_shape_id, cached_slot) {
|
||||
self.regs[a] = value;
|
||||
ic_trace!("IC_GET hit shape={} slot={}", cached_shape_id, cached_slot);
|
||||
} else if let Some(template) = self.kernel.prop_forge().get_template(obj.shape_id()) {
|
||||
prop_cache_miss();
|
||||
if template.prop_name == prop_name_si {
|
||||
if template.position < obj.prop_vec_len() as u32 {
|
||||
self.write_ic_back(obj.shape_id(), template.position);
|
||||
@@ -93,6 +117,7 @@ impl Vm {
|
||||
self.regs[a] = self.ordinary_get(obj, prop_name_si, val)?;
|
||||
}
|
||||
} else {
|
||||
prop_cache_miss();
|
||||
ic_debug!("IC_GET miss shape={} prop={}", obj.shape_id(), prop_name_si);
|
||||
let resolved = self.ordinary_get(obj, prop_name_si, val)?;
|
||||
if let Some(pos) = self.kernel.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
|
||||
@@ -136,14 +161,15 @@ impl Vm {
|
||||
let cached_shape_id = ext0 & 0x00FF_FFFF;
|
||||
let cached_slot = ext1;
|
||||
|
||||
if cached_shape_id != 0 && cached_shape_id == obj.shape_id() && cached_slot < obj.prop_vec_len() as u32 {
|
||||
obj.set_prop_at(cached_slot, value);
|
||||
if ic_set_hit(obj, cached_shape_id, cached_slot, value) {
|
||||
ic_trace!("IC_SET hit shape={} slot={}", cached_shape_id, cached_slot);
|
||||
} else if let Some(pos) = self.kernel.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
|
||||
prop_cache_miss();
|
||||
obj.set_prop_at(pos, value);
|
||||
self.write_ic_back(obj.shape_id(), pos);
|
||||
ic_debug!("IC_SET write-back shape={} slot={}", obj.shape_id(), pos);
|
||||
} else {
|
||||
prop_cache_miss();
|
||||
let old_shape = obj.shape_id();
|
||||
self.ordinary_set_dispatch(obj, prop_name_si, value, receiver)?;
|
||||
if old_shape != obj.shape_id() {
|
||||
|
||||
+52
-16
@@ -5,6 +5,7 @@ use std::sync::Arc;
|
||||
use oxide_compiler::compiler::Constant;
|
||||
use oxide_compiler::module::CompiledModule;
|
||||
use oxide_compiler::opcode::{self, OpCode};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::bindings;
|
||||
pub use crate::bindings::init_kernel_builtins;
|
||||
@@ -13,6 +14,7 @@ use crate::native::{NativeFn, NativeResult};
|
||||
use oxide_kernel::kernel::{KernelConfig, OxideKernel};
|
||||
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
||||
use oxide_kernel::{vm_debug, vm_trace};
|
||||
use oxide_types::error::{JsError, JsErrorKind};
|
||||
use oxide_types::mem::{Epoch, P};
|
||||
use oxide_types::object::{JsObject, NativeFnPtr, PropAttributes};
|
||||
use oxide_types::value::JsValue;
|
||||
@@ -27,6 +29,30 @@ pub(crate) unsafe fn native_fn_ptr_to_fn(ptr: NativeFnPtr) -> NativeFn {
|
||||
std::mem::transmute::<*const (), NativeFn>(ptr.as_ptr())
|
||||
}
|
||||
|
||||
fn js_error_kind(kind: &'static str) -> JsErrorKind {
|
||||
match kind {
|
||||
"TypeError" => JsErrorKind::TypeError,
|
||||
"RangeError" => JsErrorKind::RangeError,
|
||||
"ReferenceError" => JsErrorKind::ReferenceError,
|
||||
"SyntaxError" => JsErrorKind::SyntaxError,
|
||||
"URIError" => JsErrorKind::URIError,
|
||||
"EvalError" => JsErrorKind::EvalError,
|
||||
_ => JsErrorKind::Error,
|
||||
}
|
||||
}
|
||||
|
||||
fn js_error_kind_name(kind: JsErrorKind) -> &'static str {
|
||||
match kind {
|
||||
JsErrorKind::TypeError => "TypeError",
|
||||
JsErrorKind::RangeError => "RangeError",
|
||||
JsErrorKind::ReferenceError => "ReferenceError",
|
||||
JsErrorKind::SyntaxError => "SyntaxError",
|
||||
JsErrorKind::Error => "Error",
|
||||
JsErrorKind::URIError => "URIError",
|
||||
JsErrorKind::EvalError => "EvalError",
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! throw_err {
|
||||
($self:ident, $kind:ident, $msg:expr) => {{
|
||||
@@ -96,7 +122,7 @@ struct InlineSyncState {
|
||||
active_reg_limit: u8,
|
||||
root_reg_limit: u8,
|
||||
try_stack: Vec<TryHandler>,
|
||||
frames: Vec<CallFrame>,
|
||||
frames: SmallVec<[CallFrame; 16]>,
|
||||
exception_value: Option<JsValue>,
|
||||
pending_exception: Option<JsValue>,
|
||||
pending_error_kind: Option<&'static str>,
|
||||
@@ -110,7 +136,7 @@ pub struct Vm {
|
||||
pub(crate) pc: usize,
|
||||
pub(crate) bytecode: Vec<opcode::Instr>,
|
||||
pub(crate) constants: Vec<JsValue>,
|
||||
pub(crate) frames: Vec<CallFrame>,
|
||||
pub(crate) frames: SmallVec<[CallFrame; 16]>,
|
||||
pub for_in_iters: Vec<*mut ForInIter<'static>>,
|
||||
pub(crate) kernel: Arc<OxideKernel>,
|
||||
pub(crate) interned_strings: Vec<u32>,
|
||||
@@ -156,11 +182,16 @@ impl Vm {
|
||||
}
|
||||
|
||||
pub(crate) fn raise_error_kind(&mut self, kind: &'static str, msg: &str) -> Result<(), String> {
|
||||
self.raise_js_error(JsError::new(js_error_kind(kind), msg))
|
||||
}
|
||||
|
||||
pub(crate) fn raise_js_error(&mut self, err: JsError) -> Result<(), String> {
|
||||
let kind = js_error_kind_name(err.kind);
|
||||
let error = match kind {
|
||||
"TypeError" => crate::builtins::error::create_type_error(self, msg),
|
||||
"ReferenceError" => crate::builtins::error::create_reference_error(self, msg),
|
||||
"SyntaxError" => crate::builtins::error::create_syntax_error(self, msg),
|
||||
_ => crate::builtins::error::create_error(self, msg),
|
||||
"TypeError" => crate::builtins::error::create_type_error(self, &err.message),
|
||||
"ReferenceError" => crate::builtins::error::create_reference_error(self, &err.message),
|
||||
"SyntaxError" => crate::builtins::error::create_syntax_error(self, &err.message),
|
||||
_ => crate::builtins::error::create_error(self, &err.message),
|
||||
};
|
||||
self.exception_value = Some(error);
|
||||
self.pending_error_kind = Some(kind);
|
||||
@@ -180,7 +211,7 @@ impl Vm {
|
||||
pc: 0,
|
||||
bytecode: Vec::new(),
|
||||
constants: Vec::new(),
|
||||
frames: Vec::with_capacity(128),
|
||||
frames: SmallVec::new(),
|
||||
for_in_iters: Vec::new(),
|
||||
kernel,
|
||||
interned_strings: Vec::new(),
|
||||
@@ -212,7 +243,7 @@ impl Vm {
|
||||
pc: 0,
|
||||
bytecode: Vec::new(),
|
||||
constants: Vec::new(),
|
||||
frames: Vec::with_capacity(128),
|
||||
frames: SmallVec::new(),
|
||||
for_in_iters: Vec::new(),
|
||||
kernel,
|
||||
interned_strings: Vec::new(),
|
||||
@@ -284,6 +315,11 @@ impl Vm {
|
||||
}
|
||||
|
||||
fn clear_execution_state(&mut self) {
|
||||
// Reset contract:
|
||||
// - Clears register file, pc, frame/iterator stacks, saved execution stacks,
|
||||
// try handlers, pending exceptions, and native call depth.
|
||||
// - Leaves kernel-owned shared state intact.
|
||||
// - `reset()` additionally clears bytecode/constants and resets epoch ownership.
|
||||
self.regs = [JsValue::undefined(); 256];
|
||||
self.pc = 0;
|
||||
self.frames.clear();
|
||||
@@ -356,7 +392,7 @@ impl Vm {
|
||||
let func = unsafe { &mut *obj_ptr };
|
||||
let prototype_shape = self.kernel.shape_forge().make_shape(func.shape_id(), prototype_si);
|
||||
func.set_shape_id(prototype_shape);
|
||||
func.ensure_hash_props().push(Box::new(prototype_val));
|
||||
func.ensure_hash_props().push(prototype_val);
|
||||
func.bump_generation();
|
||||
}
|
||||
|
||||
@@ -384,7 +420,7 @@ impl Vm {
|
||||
format!("{val}")
|
||||
}
|
||||
|
||||
fn convert_constant(&mut self, constant: &Constant) -> Result<JsValue, String> {
|
||||
fn convert_constant(&mut self, constant: &Constant) -> Result<JsValue, JsError> {
|
||||
match constant {
|
||||
Constant::Number(v) => Ok(JsValue::float(*v)),
|
||||
Constant::Int(v) => Ok(JsValue::int(*v)),
|
||||
@@ -423,7 +459,7 @@ impl Vm {
|
||||
let ctor_ptr = self.kernel.builtin_world().regexp_constructor.as_ptr() as *mut JsObject;
|
||||
let ctor = unsafe { &*ctor_ptr };
|
||||
let Some(native_fn) = ctor.native_fn() else {
|
||||
return Err("SyntaxError: RegExp constructor unavailable".into());
|
||||
return Err(JsError::syntax_error("RegExp constructor unavailable"));
|
||||
};
|
||||
|
||||
let saved_0 = self.regs[0];
|
||||
@@ -439,13 +475,13 @@ impl Vm {
|
||||
self.regs[0] = saved_0;
|
||||
self.regs[1] = saved_1;
|
||||
self.regs[2] = saved_2;
|
||||
result.map_err(|err| self.error_text(err))
|
||||
result.map_err(|err| JsError::syntax_error(self.error_text(err)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a module constant pool into runtime values.
|
||||
fn convert_constants(&mut self, constants: &[Constant]) -> Result<Vec<JsValue>, String> {
|
||||
fn convert_constants(&mut self, constants: &[Constant]) -> Result<Vec<JsValue>, JsError> {
|
||||
let mut values = Vec::with_capacity(constants.len());
|
||||
for constant in constants {
|
||||
values.push(self.convert_constant(constant)?);
|
||||
@@ -609,7 +645,7 @@ impl Vm {
|
||||
}
|
||||
|
||||
let sub = self.sub_modules[sub_idx].clone();
|
||||
let converted_constants = self.convert_constants(&sub.constants)?;
|
||||
let converted_constants = self.convert_constants(&sub.constants).map_err(String::from)?;
|
||||
|
||||
// Heap-allocate the saved state so this function's Rust stack frame stays small.
|
||||
let saved = Box::new(InlineSyncState {
|
||||
@@ -712,7 +748,7 @@ impl Vm {
|
||||
self.regs[254] = if sub_is_arrow { obj.captured_this() } else { this_value };
|
||||
self.regs[255] = new_target;
|
||||
|
||||
let converted_sub_constants = self.convert_constants(&sub_constants)?;
|
||||
let converted_sub_constants = self.convert_constants(&sub_constants).map_err(String::from)?;
|
||||
self.saved_bytecode_stack.push(std::mem::take(&mut self.bytecode));
|
||||
self.saved_constants_stack.push(std::mem::take(&mut self.constants));
|
||||
|
||||
@@ -1114,7 +1150,7 @@ impl Vm {
|
||||
pub fn run(&mut self, module: &CompiledModule) -> Result<JsValue, String> {
|
||||
self.clear_execution_state();
|
||||
self.sub_modules = module.sub_modules.clone();
|
||||
self.constants = self.convert_constants(&module.constants)?;
|
||||
self.constants = self.convert_constants(&module.constants).map_err(String::from)?;
|
||||
self.sub_module_constants = vec![Vec::new(); self.sub_modules.len()];
|
||||
self.bytecode = module.bytecode.clone();
|
||||
self.root_reg_limit = module.n_registers.max(1);
|
||||
|
||||
Reference in New Issue
Block a user