refactor: Runtime
This commit is contained in:
@@ -2,7 +2,6 @@ use std::path::PathBuf;
|
|||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::ptr::NonNull;
|
use std::ptr::NonNull;
|
||||||
|
|
||||||
use deno_core::JsRuntime;
|
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use itertools::Itertools as _;
|
use itertools::Itertools as _;
|
||||||
use string_interner::DefaultStringInterner;
|
use string_interner::DefaultStringInterner;
|
||||||
@@ -10,6 +9,7 @@ use string_interner::DefaultStringInterner;
|
|||||||
use crate::codegen::{CodegenContext, Compile};
|
use crate::codegen::{CodegenContext, Compile};
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::ir::{DowngradeContext, ExprId, Ir, SymId};
|
use crate::ir::{DowngradeContext, ExprId, Ir, SymId};
|
||||||
|
use crate::runtime::Runtime;
|
||||||
use crate::value::Value;
|
use crate::value::Value;
|
||||||
|
|
||||||
use downgrade::DowngradeCtx;
|
use downgrade::DowngradeCtx;
|
||||||
@@ -18,7 +18,7 @@ mod downgrade;
|
|||||||
|
|
||||||
pub struct Context {
|
pub struct Context {
|
||||||
ctx: Pin<Box<Ctx>>,
|
ctx: Pin<Box<Ctx>>,
|
||||||
pub(crate) js_runtime: JsRuntime,
|
pub(crate) runtime: Runtime,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Context {
|
impl Default for Context {
|
||||||
@@ -31,9 +31,9 @@ impl Context {
|
|||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let mut ctx = Box::pin(Ctx::new());
|
let mut ctx = Box::pin(Ctx::new());
|
||||||
let ptr = unsafe { NonNull::new_unchecked(Pin::get_unchecked_mut(ctx.as_mut())) };
|
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> {
|
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 = ctx.get_ir(root).compile(Pin::get_ref(ctx.as_ref()));
|
||||||
let code = format!("Nix.force({})", code);
|
let code = format!("Nix.force({})", code);
|
||||||
println!("[DEBUG] generated code: {}", &code);
|
println!("[DEBUG] generated code: {}", &code);
|
||||||
crate::runtime::run(code, &mut self.js_runtime)
|
self.runtime.eval(code)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,11 +13,9 @@ use crate::error::{Error, Result};
|
|||||||
use crate::ir::DowngradeContext;
|
use crate::ir::DowngradeContext;
|
||||||
use crate::value::{AttrSet, Const, List, Symbol, Value};
|
use crate::value::{AttrSet, Const, List, Symbol, Value};
|
||||||
|
|
||||||
pub trait RuntimeContext {
|
type ScopeRef<'p, 's> = v8::PinnedRef<'p, v8::HandleScope<'s>>;
|
||||||
fn split<Ctx: DowngradeContext>(&mut self) -> (&mut JsRuntime, &mut Ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn nix_runtime(ctx: NonNull<Ctx>) -> Extension {
|
fn runtime_extension(ctx: NonNull<Ctx>) -> Extension {
|
||||||
const ESM: &[ExtensionFileSource] =
|
const ESM: &[ExtensionFileSource] =
|
||||||
&deno_core::include_js_files!(nix_runtime dir "runtime-ts/dist", "runtime.js");
|
&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)]
|
#[derive(Debug)]
|
||||||
pub struct SimpleErrorWrapper(pub String);
|
pub struct SimpleErrorWrapper(String);
|
||||||
|
|
||||||
impl std::fmt::Display for SimpleErrorWrapper {
|
impl std::fmt::Display for SimpleErrorWrapper {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
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() })
|
.map_err(|e| -> NixError { format!("Failed to resolve path {}: {}", path, e).into() })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Runtime context for V8 value conversion
|
pub struct Runtime {
|
||||||
struct RuntimeCtx<'a, 'b> {
|
js_runtime: JsRuntime,
|
||||||
scope: &'a v8::PinnedRef<'a, v8::HandleScope<'b>>,
|
is_thunk_symbol: v8::Global<v8::Symbol>,
|
||||||
is_thunk_symbol: Option<v8::Local<'a, v8::Symbol>>,
|
primop_metadata_symbol: v8::Global<v8::Symbol>,
|
||||||
primop_metadata_symbol: Option<v8::Local<'a, v8::Symbol>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> RuntimeCtx<'a, 'b> {
|
impl Runtime {
|
||||||
fn new(scope: &'a v8::PinnedRef<'a, v8::HandleScope<'b>>) -> Self {
|
pub fn new(ctx: NonNull<Ctx>) -> 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 {
|
|
||||||
// Initialize V8 once
|
// Initialize V8 once
|
||||||
static INIT: Once = Once::new();
|
static INIT: Once = Once::new();
|
||||||
INIT.call_once(|| {
|
INIT.call_once(|| {
|
||||||
@@ -209,29 +161,70 @@ pub fn new_js_runtime(ctx: NonNull<Ctx>) -> JsRuntime {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
JsRuntime::new(RuntimeOptions {
|
let mut js_runtime = JsRuntime::new(RuntimeOptions {
|
||||||
extensions: vec![nix_runtime(ctx)],
|
extensions: vec![runtime_extension(ctx)],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// Main entry point
|
let (is_thunk_symbol, primop_metadata_symbol) = {
|
||||||
pub fn run(script: String, runtime: &mut JsRuntime) -> Result<Value> {
|
deno_core::scope!(scope, &mut js_runtime);
|
||||||
// Execute user script
|
Self::get_symbols(scope)
|
||||||
let global_value = runtime
|
};
|
||||||
|
|
||||||
|
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)
|
.execute_script("<eval>", script)
|
||||||
.map_err(|e| Error::eval_error(format!("Execution error: {:?}", e)))?;
|
.map_err(|e| Error::eval_error(format!("Execution error: {:?}", e)))?;
|
||||||
|
|
||||||
// Retrieve scope from JsRuntime
|
// 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 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, scope, is_thunk_symbol, primop_metadata_symbol))
|
||||||
Ok(to_value(local_value, &runtime_ctx))
|
}
|
||||||
|
|
||||||
|
/// 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 {
|
fn to_value<'a>(
|
||||||
let scope = ctx.scope;
|
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 () {
|
match () {
|
||||||
_ if val.is_big_int() => {
|
_ if val.is_big_int() => {
|
||||||
let (val, lossless) = val.to_big_int(scope).unwrap().i64_value();
|
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)
|
let list = (0..len)
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
let val = val.get_index(scope, i).unwrap();
|
let val = val.get_index(scope, i).unwrap();
|
||||||
to_value(val, ctx)
|
to_value(val, scope, is_thunk_symbol, primop_metadata_symbol)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
Value::List(List::new(list))
|
Value::List(List::new(list))
|
||||||
}
|
}
|
||||||
_ if val.is_function() => {
|
_ 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)
|
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)
|
Value::PrimOp(name)
|
||||||
} else {
|
} else {
|
||||||
Value::Func
|
Value::Func
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ if val.is_object() => {
|
_ if val.is_object() => {
|
||||||
if is_thunk(val, ctx) {
|
if is_thunk(val, scope, is_thunk_symbol) {
|
||||||
return Value::Thunk;
|
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 key = keys.get_index(scope, i).unwrap();
|
||||||
let val = val.get(scope, key).unwrap();
|
let val = val.get(scope, key).unwrap();
|
||||||
let key = key.to_rust_string_lossy(scope);
|
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();
|
.collect();
|
||||||
Value::AttrSet(AttrSet::new(attrs))
|
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() {
|
if !val.is_object() {
|
||||||
return false;
|
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();
|
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>(val: v8::Local<'a, v8::Value>, scope: &ScopeRef<'a, '_>, symbol: v8::Local<'a, v8::Symbol>) -> Option<String> {
|
||||||
fn primop_name<'a, 'b>(val: v8::Local<'a, v8::Value>, ctx: &RuntimeCtx<'a, 'b>) -> Option<String> {
|
|
||||||
if !val.is_function() {
|
if !val.is_function() {
|
||||||
return None;
|
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();
|
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(metadata_obj) = metadata.to_object(scope)
|
||||||
&& let Some(name_key) = v8::String::new(scope, "name")
|
&& let Some(name_key) = v8::String::new(scope, "name")
|
||||||
&& let Some(name_val) = metadata_obj.get(scope, name_key.into())
|
&& 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>(val: v8::Local<'a, v8::Value>, scope: &ScopeRef<'a, '_>, symbol: v8::Local<'a, v8::Symbol>) -> Option<String> {
|
||||||
fn primop_app_name<'a, 'b>(
|
let name = primop_name(val, scope, symbol)?;
|
||||||
val: v8::Local<'a, v8::Value>,
|
|
||||||
ctx: &RuntimeCtx<'a, 'b>,
|
|
||||||
) -> Option<String> {
|
|
||||||
let name = primop_name(val, ctx)?;
|
|
||||||
|
|
||||||
// Get cached PRIMOP_METADATA symbol
|
|
||||||
let primop_metadata_sym = ctx.primop_metadata_symbol?;
|
|
||||||
|
|
||||||
let scope = ctx.scope;
|
|
||||||
let obj = val.to_object(scope).unwrap();
|
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(metadata_obj) = metadata.to_object(scope)
|
||||||
&& let Some(applied_key) = v8::String::new(scope, "applied")
|
&& let Some(applied_key) = v8::String::new(scope, "applied")
|
||||||
&& let Some(applied_val) = metadata_obj.get(scope, applied_key.into())
|
&& let Some(applied_val) = metadata_obj.get(scope, applied_key.into())
|
||||||
@@ -370,12 +343,11 @@ mod test {
|
|||||||
fn to_value_working() {
|
fn to_value_working() {
|
||||||
let mut ctx = Context::new();
|
let mut ctx = Context::new();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
run(
|
ctx.runtime.eval(
|
||||||
"({
|
"({
|
||||||
test: [1., 9223372036854775807n, true, false, 'hello world!']
|
test: [1., 9223372036854775807n, true, false, 'hello world!']
|
||||||
})"
|
})"
|
||||||
.into(),
|
.into(),
|
||||||
&mut ctx.js_runtime
|
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
Value::AttrSet(AttrSet::new(std::collections::BTreeMap::from([(
|
Value::AttrSet(AttrSet::new(std::collections::BTreeMap::from([(
|
||||||
|
|||||||
Reference in New Issue
Block a user