From 23950da6eab6540a576fdc783dc80930d512009e Mon Sep 17 00:00:00 2001 From: imxyy_soope_ Date: Wed, 7 Jan 2026 18:59:10 +0800 Subject: [PATCH] refactor: Runtime --- nix-js/src/context.rs | 10 +-- nix-js/src/runtime.rs | 186 ++++++++++++++++++------------------------ 2 files changed, 84 insertions(+), 112 deletions(-) diff --git a/nix-js/src/context.rs b/nix-js/src/context.rs index b49183d..fded8d1 100644 --- a/nix-js/src/context.rs +++ b/nix-js/src/context.rs @@ -2,7 +2,6 @@ use std::path::PathBuf; use std::pin::Pin; use std::ptr::NonNull; -use deno_core::JsRuntime; use hashbrown::HashMap; use itertools::Itertools as _; use string_interner::DefaultStringInterner; @@ -10,6 +9,7 @@ use string_interner::DefaultStringInterner; use crate::codegen::{CodegenContext, Compile}; use crate::error::{Error, Result}; use crate::ir::{DowngradeContext, ExprId, Ir, SymId}; +use crate::runtime::Runtime; use crate::value::Value; use downgrade::DowngradeCtx; @@ -18,7 +18,7 @@ mod downgrade; pub struct Context { ctx: Pin>, - pub(crate) js_runtime: JsRuntime, + pub(crate) runtime: Runtime, } impl Default for Context { @@ -31,9 +31,9 @@ impl Context { pub fn new() -> Self { let mut ctx = Box::pin(Ctx::new()); let ptr = unsafe { NonNull::new_unchecked(Pin::get_unchecked_mut(ctx.as_mut())) }; - let js_runtime = crate::runtime::new_js_runtime(ptr); + let runtime = Runtime::new(ptr); - Self { ctx, js_runtime } + Self { ctx, runtime } } pub fn eval_code(&mut self, expr: &str) -> Result { @@ -49,7 +49,7 @@ impl Context { let code = ctx.get_ir(root).compile(Pin::get_ref(ctx.as_ref())); let code = format!("Nix.force({})", code); println!("[DEBUG] generated code: {}", &code); - crate::runtime::run(code, &mut self.js_runtime) + self.runtime.eval(code) } } diff --git a/nix-js/src/runtime.rs b/nix-js/src/runtime.rs index f8823dc..ead7f6b 100644 --- a/nix-js/src/runtime.rs +++ b/nix-js/src/runtime.rs @@ -13,11 +13,9 @@ use crate::error::{Error, Result}; use crate::ir::DowngradeContext; use crate::value::{AttrSet, Const, List, Symbol, Value}; -pub trait RuntimeContext { - fn split(&mut self) -> (&mut JsRuntime, &mut Ctx); -} +type ScopeRef<'p, 's> = v8::PinnedRef<'p, v8::HandleScope<'s>>; -fn nix_runtime(ctx: NonNull) -> Extension { +fn runtime_extension(ctx: NonNull) -> Extension { const ESM: &[ExtensionFileSource] = &deno_core::include_js_files!(nix_runtime dir "runtime-ts/dist", "runtime.js"); @@ -40,7 +38,7 @@ fn nix_runtime(ctx: NonNull) -> Extension { } #[derive(Debug)] -pub struct SimpleErrorWrapper(pub String); +pub struct SimpleErrorWrapper(String); impl std::fmt::Display for SimpleErrorWrapper { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -146,92 +144,87 @@ fn op_resolve_path( .map_err(|e| -> NixError { format!("Failed to resolve path {}: {}", path, e).into() }) } -// Runtime context for V8 value conversion -struct RuntimeCtx<'a, 'b> { - scope: &'a v8::PinnedRef<'a, v8::HandleScope<'b>>, - is_thunk_symbol: Option>, - primop_metadata_symbol: Option>, +pub struct Runtime { + js_runtime: JsRuntime, + is_thunk_symbol: v8::Global, + primop_metadata_symbol: v8::Global, } -impl<'a, 'b> RuntimeCtx<'a, 'b> { - fn new(scope: &'a v8::PinnedRef<'a, v8::HandleScope<'b>>) -> Self { - let is_thunk_symbol = Self::get_is_thunk_symbol(scope); - let primop_metadata_symbol = Self::get_primop_metadata_symbol(scope); +impl Runtime { + pub fn new(ctx: NonNull) -> Self { + // Initialize V8 once + static INIT: Once = Once::new(); + INIT.call_once(|| { + JsRuntime::init_platform( + Some(v8::new_default_platform(0, false).make_shared()), + false, + ); + }); + + let mut js_runtime = JsRuntime::new(RuntimeOptions { + extensions: vec![runtime_extension(ctx)], + ..Default::default() + }); + + let (is_thunk_symbol, primop_metadata_symbol) = { + deno_core::scope!(scope, &mut js_runtime); + Self::get_symbols(scope) + }; + Self { - scope, + js_runtime, is_thunk_symbol, primop_metadata_symbol, } } - fn get_is_thunk_symbol( - scope: &v8::PinnedRef<'a, v8::HandleScope<'b>>, - ) -> Option> { - let global = scope.get_current_context().global(scope); - let nix_key = v8::String::new(scope, "Nix")?; - let nix_obj = global.get(scope, nix_key.into())?.to_object(scope)?; + pub fn eval(&mut self, script: String) -> Result { + let global_value = self + .js_runtime + .execute_script("", script) + .map_err(|e| Error::eval_error(format!("Execution error: {:?}", e)))?; - let is_thunk_sym_key = v8::String::new(scope, "IS_THUNK")?; - let is_thunk_sym = nix_obj.get(scope, is_thunk_sym_key.into())?; + // Retrieve scope from JsRuntime + deno_core::scope!(scope, self.js_runtime); + let local_value = v8::Local::new(scope, &global_value); + let is_thunk_symbol = v8::Local::new(scope, &self.is_thunk_symbol); + let primop_metadata_symbol = v8::Local::new(scope, &self.primop_metadata_symbol); - if is_thunk_sym.is_symbol() { - is_thunk_sym.try_cast().ok() - } else { - None - } + Ok(to_value(local_value, scope, is_thunk_symbol, primop_metadata_symbol)) } - fn get_primop_metadata_symbol( - scope: &v8::PinnedRef<'a, v8::HandleScope<'b>>, - ) -> Option> { + /// get (IS_THUNK, PRIMOP_METADATA) + fn get_symbols( + scope: &v8::PinnedRef<'_, v8::HandleScope<'_>>, + ) -> (v8::Global, v8::Global) { let global = scope.get_current_context().global(scope); - let nix_key = v8::String::new(scope, "Nix")?; - let nix_obj = global.get(scope, nix_key.into())?.to_object(scope)?; + let nix_key = v8::String::new(scope, "Nix").unwrap(); + let nix_obj = global + .get(scope, nix_key.into()) + .unwrap() + .to_object(scope) + .unwrap(); - let primop_metadata_sym_key = v8::String::new(scope, "PRIMOP_METADATA")?; - let primop_metadata_sym = nix_obj.get(scope, primop_metadata_sym_key.into())?; + let is_thunk_sym_key = v8::String::new(scope, "IS_THUNK").unwrap(); + let is_thunk_sym = nix_obj.get(scope, is_thunk_sym_key.into()).unwrap(); + let is_thunk = is_thunk_sym.try_cast::().unwrap(); + let is_thunk = v8::Global::new(scope, is_thunk); - if primop_metadata_sym.is_symbol() { - primop_metadata_sym.try_cast().ok() - } else { - None - } + let primop_metadata_sym_key = v8::String::new(scope, "PRIMOP_METADATA").unwrap(); + let primop_metadata_sym = nix_obj.get(scope, primop_metadata_sym_key.into()).unwrap(); + let primop_metadata = primop_metadata_sym.try_cast::().unwrap(); + let primop_metadata = v8::Global::new(scope, primop_metadata); + + (is_thunk, primop_metadata) } } -pub fn new_js_runtime(ctx: NonNull) -> JsRuntime { - // Initialize V8 once - static INIT: Once = Once::new(); - INIT.call_once(|| { - JsRuntime::init_platform( - Some(v8::new_default_platform(0, false).make_shared()), - false, - ); - }); - - JsRuntime::new(RuntimeOptions { - extensions: vec![nix_runtime(ctx)], - ..Default::default() - }) -} - -// Main entry point -pub fn run(script: String, runtime: &mut JsRuntime) -> Result { - // Execute user script - let global_value = runtime - .execute_script("", script) - .map_err(|e| Error::eval_error(format!("Execution error: {:?}", e)))?; - - // Retrieve scope from JsRuntime - deno_core::scope!(scope, runtime); - let local_value = v8::Local::new(scope, &global_value); - - let runtime_ctx = RuntimeCtx::new(scope); - Ok(to_value(local_value, &runtime_ctx)) -} - -fn to_value<'a, 'b>(val: v8::Local<'a, v8::Value>, ctx: &RuntimeCtx<'a, 'b>) -> Value { - let scope = ctx.scope; +fn to_value<'a>( + val: v8::Local<'a, v8::Value>, + scope: &ScopeRef<'a, '_>, + is_thunk_symbol: v8::Local<'a, v8::Symbol>, + primop_metadata_symbol: v8::Local<'a, v8::Symbol>, +) -> Value { match () { _ if val.is_big_int() => { let (val, lossless) = val.to_big_int(scope).unwrap().i64_value(); @@ -258,22 +251,22 @@ fn to_value<'a, 'b>(val: v8::Local<'a, v8::Value>, ctx: &RuntimeCtx<'a, 'b>) -> let list = (0..len) .map(|i| { let val = val.get_index(scope, i).unwrap(); - to_value(val, ctx) + to_value(val, scope, is_thunk_symbol, primop_metadata_symbol) }) .collect(); Value::List(List::new(list)) } _ if val.is_function() => { - if let Some(name) = primop_app_name(val, ctx) { + if let Some(name) = primop_app_name(val, scope, primop_metadata_symbol) { Value::PrimOpApp(name) - } else if let Some(name) = primop_name(val, ctx) { + } else if let Some(name) = primop_name(val, scope, primop_metadata_symbol) { Value::PrimOp(name) } else { Value::Func } } _ if val.is_object() => { - if is_thunk(val, ctx) { + if is_thunk(val, scope, is_thunk_symbol) { return Value::Thunk; } @@ -287,7 +280,7 @@ fn to_value<'a, 'b>(val: v8::Local<'a, v8::Value>, ctx: &RuntimeCtx<'a, 'b>) -> let key = keys.get_index(scope, i).unwrap(); let val = val.get(scope, key).unwrap(); let key = key.to_rust_string_lossy(scope); - (Symbol::new(key), to_value(val, ctx)) + (Symbol::new(key), to_value(val, scope, is_thunk_symbol, primop_metadata_symbol)) }) .collect(); Value::AttrSet(AttrSet::new(attrs)) @@ -296,35 +289,23 @@ fn to_value<'a, 'b>(val: v8::Local<'a, v8::Value>, ctx: &RuntimeCtx<'a, 'b>) -> } } -fn is_thunk<'a, 'b>(val: v8::Local<'a, v8::Value>, ctx: &RuntimeCtx<'a, 'b>) -> bool { +fn is_thunk<'a>(val: v8::Local<'a, v8::Value>, scope: &ScopeRef<'a, '_>, symbol: v8::Local<'a, v8::Symbol>) -> bool { if !val.is_object() { return false; } - // Use cached IS_THUNK symbol from context - let is_thunk_sym = match ctx.is_thunk_symbol { - Some(sym) => sym, - None => return false, - }; - - let scope = ctx.scope; let obj = val.to_object(scope).unwrap(); - matches!(obj.get(scope, is_thunk_sym.into()), Some(v) if v.is_true()) + matches!(obj.get(scope, symbol.into()), Some(v) if v.is_true()) } -/// Check if a function is a primop -fn primop_name<'a, 'b>(val: v8::Local<'a, v8::Value>, ctx: &RuntimeCtx<'a, 'b>) -> Option { +fn primop_name<'a>(val: v8::Local<'a, v8::Value>, scope: &ScopeRef<'a, '_>, symbol: v8::Local<'a, v8::Symbol>) -> Option { if !val.is_function() { return None; } - // Use cached PRIMOP_METADATA symbol from context - let primop_metadata_sym = ctx.primop_metadata_symbol?; - - let scope = ctx.scope; let obj = val.to_object(scope).unwrap(); - if let Some(metadata) = obj.get(scope, primop_metadata_sym.into()) + if let Some(metadata) = obj.get(scope, symbol.into()) && let Some(metadata_obj) = metadata.to_object(scope) && let Some(name_key) = v8::String::new(scope, "name") && let Some(name_val) = metadata_obj.get(scope, name_key.into()) @@ -335,20 +316,12 @@ fn primop_name<'a, 'b>(val: v8::Local<'a, v8::Value>, ctx: &RuntimeCtx<'a, 'b>) } } -/// Check if a primop is partially applied (has applied > 0) -fn primop_app_name<'a, 'b>( - val: v8::Local<'a, v8::Value>, - ctx: &RuntimeCtx<'a, 'b>, -) -> Option { - let name = primop_name(val, ctx)?; +fn primop_app_name<'a>(val: v8::Local<'a, v8::Value>, scope: &ScopeRef<'a, '_>, symbol: v8::Local<'a, v8::Symbol>) -> Option { + let name = primop_name(val, scope, symbol)?; - // Get cached PRIMOP_METADATA symbol - let primop_metadata_sym = ctx.primop_metadata_symbol?; - - let scope = ctx.scope; let obj = val.to_object(scope).unwrap(); - if let Some(metadata) = obj.get(scope, primop_metadata_sym.into()) + if let Some(metadata) = obj.get(scope, symbol.into()) && let Some(metadata_obj) = metadata.to_object(scope) && let Some(applied_key) = v8::String::new(scope, "applied") && let Some(applied_val) = metadata_obj.get(scope, applied_key.into()) @@ -370,12 +343,11 @@ mod test { fn to_value_working() { let mut ctx = Context::new(); assert_eq!( - run( + ctx.runtime.eval( "({ test: [1., 9223372036854775807n, true, false, 'hello world!'] })" .into(), - &mut ctx.js_runtime ) .unwrap(), Value::AttrSet(AttrSet::new(std::collections::BTreeMap::from([(