refactor: Runtime

This commit is contained in:
2026-01-07 18:59:10 +08:00
parent 9d1d4a3763
commit 23950da6ea
2 changed files with 84 additions and 112 deletions

View File

@@ -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<Box<Ctx>>,
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<Value> {
@@ -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)
}
}

View File

@@ -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<Ctx: DowngradeContext>(&mut self) -> (&mut JsRuntime, &mut Ctx);
}
type ScopeRef<'p, 's> = v8::PinnedRef<'p, v8::HandleScope<'s>>;
fn nix_runtime(ctx: NonNull<Ctx>) -> Extension {
fn runtime_extension(ctx: NonNull<Ctx>) -> 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<Ctx>) -> 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,60 +144,14 @@ 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<v8::Local<'a, v8::Symbol>>,
primop_metadata_symbol: Option<v8::Local<'a, v8::Symbol>>,
pub struct Runtime {
js_runtime: JsRuntime,
is_thunk_symbol: v8::Global<v8::Symbol>,
primop_metadata_symbol: v8::Global<v8::Symbol>,
}
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);
Self {
scope,
is_thunk_symbol,
primop_metadata_symbol,
}
}
fn get_is_thunk_symbol(
scope: &v8::PinnedRef<'a, v8::HandleScope<'b>>,
) -> Option<v8::Local<'a, v8::Symbol>> {
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 is_thunk_sym_key = v8::String::new(scope, "IS_THUNK")?;
let is_thunk_sym = nix_obj.get(scope, is_thunk_sym_key.into())?;
if is_thunk_sym.is_symbol() {
is_thunk_sym.try_cast().ok()
} else {
None
}
}
fn get_primop_metadata_symbol(
scope: &v8::PinnedRef<'a, v8::HandleScope<'b>>,
) -> Option<v8::Local<'a, v8::Symbol>> {
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 primop_metadata_sym_key = v8::String::new(scope, "PRIMOP_METADATA")?;
let primop_metadata_sym = nix_obj.get(scope, primop_metadata_sym_key.into())?;
if primop_metadata_sym.is_symbol() {
primop_metadata_sym.try_cast().ok()
} else {
None
}
}
}
pub fn new_js_runtime(ctx: NonNull<Ctx>) -> JsRuntime {
impl Runtime {
pub fn new(ctx: NonNull<Ctx>) -> Self {
// Initialize V8 once
static INIT: Once = Once::new();
INIT.call_once(|| {
@@ -209,29 +161,70 @@ pub fn new_js_runtime(ctx: NonNull<Ctx>) -> JsRuntime {
);
});
JsRuntime::new(RuntimeOptions {
extensions: vec![nix_runtime(ctx)],
let mut js_runtime = JsRuntime::new(RuntimeOptions {
extensions: vec![runtime_extension(ctx)],
..Default::default()
})
}
});
// Main entry point
pub fn run(script: String, runtime: &mut JsRuntime) -> Result<Value> {
// Execute user script
let global_value = runtime
let (is_thunk_symbol, primop_metadata_symbol) = {
deno_core::scope!(scope, &mut js_runtime);
Self::get_symbols(scope)
};
Self {
js_runtime,
is_thunk_symbol,
primop_metadata_symbol,
}
}
pub fn eval(&mut self, script: String) -> Result<Value> {
let global_value = self
.js_runtime
.execute_script("<eval>", script)
.map_err(|e| Error::eval_error(format!("Execution error: {:?}", e)))?;
// Retrieve scope from JsRuntime
deno_core::scope!(scope, runtime);
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);
let runtime_ctx = RuntimeCtx::new(scope);
Ok(to_value(local_value, &runtime_ctx))
Ok(to_value(local_value, scope, is_thunk_symbol, primop_metadata_symbol))
}
/// get (IS_THUNK, PRIMOP_METADATA)
fn get_symbols(
scope: &v8::PinnedRef<'_, v8::HandleScope<'_>>,
) -> (v8::Global<v8::Symbol>, v8::Global<v8::Symbol>) {
let global = scope.get_current_context().global(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 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::<v8::Symbol>().unwrap();
let is_thunk = v8::Global::new(scope, is_thunk);
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::<v8::Symbol>().unwrap();
let primop_metadata = v8::Global::new(scope, primop_metadata);
(is_thunk, primop_metadata)
}
}
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<String> {
fn primop_name<'a>(val: v8::Local<'a, v8::Value>, scope: &ScopeRef<'a, '_>, symbol: v8::Local<'a, v8::Symbol>) -> Option<String> {
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<String> {
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<String> {
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([(