refactor: avoid Pin hack
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -1053,7 +1053,6 @@ dependencies = [
|
||||
"itertools 0.14.0",
|
||||
"mimalloc",
|
||||
"nix-js-macros",
|
||||
"pin-project",
|
||||
"regex",
|
||||
"rnix",
|
||||
"rustyline",
|
||||
|
||||
@@ -12,12 +12,11 @@ anyhow = "1.0"
|
||||
rustyline = "14.0"
|
||||
|
||||
derive_more = { version = "2", features = ["full"] }
|
||||
pin-project = "1"
|
||||
thiserror = "2"
|
||||
|
||||
hashbrown = "0.16"
|
||||
string-interner = "0.19"
|
||||
|
||||
thiserror = "2"
|
||||
itertools = "0.14"
|
||||
|
||||
regex = "1.11"
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use std::path::PathBuf;
|
||||
use std::pin::Pin;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
@@ -9,109 +8,87 @@ use string_interner::DefaultStringInterner;
|
||||
use crate::codegen::{CodegenContext, Compile};
|
||||
use crate::error::{Error, Result};
|
||||
use crate::ir::{Builtin, DowngradeContext, ExprId, Ir, SymId};
|
||||
use crate::runtime::Runtime;
|
||||
use crate::runtime::{Runtime, RuntimeCtx};
|
||||
use crate::value::Value;
|
||||
|
||||
use downgrade::DowngradeCtx;
|
||||
use drop_guard::{PathDropGuard, PathStackProvider};
|
||||
|
||||
mod downgrade;
|
||||
mod drop_guard;
|
||||
|
||||
pub struct Context {
|
||||
ctx: Pin<Box<Ctx>>,
|
||||
runtime: Runtime,
|
||||
}
|
||||
mod private {
|
||||
use super::*;
|
||||
use std::ops::DerefMut;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
pub(crate) struct CtxPtr(NonNull<Ctx>);
|
||||
impl CtxPtr {
|
||||
pub(crate) unsafe fn as_ref(&self) -> &Ctx {
|
||||
pub struct CtxPtr(NonNull<Ctx>);
|
||||
impl CtxPtr {
|
||||
pub fn new(ctx: &mut Ctx) -> Self {
|
||||
unsafe { CtxPtr(NonNull::new_unchecked(ctx)) }
|
||||
}
|
||||
fn as_ref(&self) -> &Ctx {
|
||||
// SAFETY: This is safe since inner `NonNull<Ctx>` is obtained from `&mut Ctx`
|
||||
unsafe { self.0.as_ref() }
|
||||
}
|
||||
pub(crate) unsafe fn as_mut(&mut self) -> Pin<&mut Ctx> {
|
||||
unsafe { Pin::new_unchecked(self.0.as_mut()) }
|
||||
fn as_mut(&mut self) -> &mut Ctx {
|
||||
// SAFETY: This is safe since inner `NonNull<Ctx>` is obtained from `&mut Ctx`
|
||||
unsafe { self.0.as_mut() }
|
||||
}
|
||||
}
|
||||
impl PathStackProvider for CtxPtr {
|
||||
fn path_stack(&mut self) -> &mut Vec<PathBuf> {
|
||||
&mut self.as_mut().path_stack
|
||||
}
|
||||
}
|
||||
|
||||
impl RuntimeCtx for CtxPtr {
|
||||
fn get_current_dir(&self) -> PathBuf {
|
||||
self.as_ref().get_current_dir()
|
||||
}
|
||||
fn push_path_stack(&mut self, path: PathBuf) -> impl DerefMut<Target = Self> {
|
||||
PathDropGuard::new(path, self)
|
||||
}
|
||||
fn compile_code(&mut self, expr: &str) -> Result<String> {
|
||||
self.as_mut().compile_code(expr)
|
||||
}
|
||||
}
|
||||
}
|
||||
use private::CtxPtr;
|
||||
|
||||
pub struct Context {
|
||||
ctx: Ctx,
|
||||
runtime: Runtime<CtxPtr>,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub fn new() -> Result<Self> {
|
||||
let mut ctx = Box::pin(Ctx::new());
|
||||
let ptr = unsafe { CtxPtr(NonNull::new_unchecked(Pin::get_unchecked_mut(ctx.as_mut()))) };
|
||||
let runtime = Runtime::new(ptr)?;
|
||||
let ctx = Ctx::new();
|
||||
let runtime = Runtime::new()?;
|
||||
|
||||
Ok(Self { ctx, runtime })
|
||||
}
|
||||
|
||||
pub fn eval_code(&mut self, expr: &str) -> Result<Value> {
|
||||
// Initialize `path_stack` with current directory for relative path resolution
|
||||
let mut guard = PathDropGuard::new_cwd(self.ctx.as_mut())?;
|
||||
let mut guard = PathDropGuard::new_cwd(&mut self.ctx)?;
|
||||
let ctx = guard.as_ctx();
|
||||
|
||||
let root = rnix::Root::parse(expr);
|
||||
if !root.errors().is_empty() {
|
||||
return Err(Error::parse_error(root.errors().iter().join("; ")));
|
||||
}
|
||||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
// Always `Some` since there is no parse error
|
||||
let root = ctx
|
||||
.as_mut()
|
||||
.downgrade_ctx()
|
||||
.downgrade(root.tree().expr().unwrap())?;
|
||||
let code = ctx.get_ir(root).compile(Pin::get_ref(ctx.as_ref()));
|
||||
let code = format!("Nix.force({})", code);
|
||||
println!("[DEBUG] generated code: {}", &code);
|
||||
self.runtime.eval(code)
|
||||
let code = ctx.compile_code(expr)?;
|
||||
self.runtime.eval(code, CtxPtr::new(&mut self.ctx))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn eval_js(&mut self, code: String) -> Result<Value> {
|
||||
self.runtime.eval(code)
|
||||
self.runtime.eval(code, CtxPtr::new(&mut self.ctx))
|
||||
}
|
||||
}
|
||||
|
||||
#[pin_project::pin_project(PinnedDrop)]
|
||||
pub(crate) struct Ctx {
|
||||
irs: Vec<Ir>,
|
||||
symbols: DefaultStringInterner,
|
||||
global: NonNull<HashMap<SymId, ExprId>>,
|
||||
path_stack: Vec<PathBuf>,
|
||||
#[pin]
|
||||
_marker: std::marker::PhantomPinned,
|
||||
}
|
||||
|
||||
#[pin_project::pinned_drop]
|
||||
impl PinnedDrop for Ctx {
|
||||
fn drop(self: Pin<&mut Self>) {
|
||||
unsafe {
|
||||
drop(Box::from_raw(self.global.as_ptr()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct PathDropGuard<'ctx> {
|
||||
ctx: Pin<&'ctx mut Ctx>,
|
||||
}
|
||||
|
||||
impl<'ctx> PathDropGuard<'ctx> {
|
||||
pub(crate) fn new(path: PathBuf, mut ctx: Pin<&'ctx mut Ctx>) -> Self {
|
||||
ctx.as_mut().project().path_stack.push(path);
|
||||
Self { ctx }
|
||||
}
|
||||
pub(crate) fn new_cwd(mut ctx: Pin<&'ctx mut Ctx>) -> Result<Self> {
|
||||
let cwd = std::env::current_dir()
|
||||
.map_err(|err| Error::downgrade_error(format!("cannot get cwd: {err}")))?;
|
||||
let virtual_file = cwd.join("__eval__.nix");
|
||||
ctx.as_mut().project().path_stack.push(virtual_file);
|
||||
Ok(Self { ctx })
|
||||
}
|
||||
pub(crate) fn as_ctx<'a>(&'a mut self) -> &'a mut Pin<&'ctx mut Ctx> {
|
||||
&mut self.ctx
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for PathDropGuard<'_> {
|
||||
fn drop(&mut self) {
|
||||
self.ctx.as_mut().project().path_stack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Ctx {
|
||||
@@ -165,7 +142,6 @@ impl Default for Ctx {
|
||||
irs,
|
||||
global: unsafe { NonNull::new_unchecked(Box::leak(Box::new(global))) },
|
||||
path_stack: Vec::new(),
|
||||
_marker: std::marker::PhantomPinned,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -175,7 +151,7 @@ impl Ctx {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub(crate) fn downgrade_ctx<'a>(self: Pin<&'a mut Self>) -> DowngradeCtx<'a> {
|
||||
pub(crate) fn downgrade_ctx<'a>(&'a mut self) -> DowngradeCtx<'a> {
|
||||
let global_ref = unsafe { self.global.as_ref() };
|
||||
DowngradeCtx::new(self, global_ref)
|
||||
}
|
||||
@@ -190,6 +166,23 @@ impl Ctx {
|
||||
.expect("path in path_stack should always have a parent dir. this is a bug")
|
||||
.to_path_buf()
|
||||
}
|
||||
|
||||
fn compile_code(&mut self, expr: &str) -> Result<String> {
|
||||
let root = rnix::Root::parse(expr);
|
||||
if !root.errors().is_empty() {
|
||||
return Err(Error::parse_error(root.errors().iter().join("; ")));
|
||||
}
|
||||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
// Always `Some` since there is no parse error
|
||||
let root = self
|
||||
.downgrade_ctx()
|
||||
.downgrade(root.tree().expr().unwrap())?;
|
||||
let code = self.get_ir(root).compile(self);
|
||||
let code = format!("Nix.force({})", code);
|
||||
println!("[DEBUG] generated code: {}", &code);
|
||||
Ok(code)
|
||||
}
|
||||
}
|
||||
|
||||
impl CodegenContext for Ctx {
|
||||
@@ -202,6 +195,12 @@ impl CodegenContext for Ctx {
|
||||
}
|
||||
}
|
||||
|
||||
impl PathStackProvider for Ctx {
|
||||
fn path_stack(&mut self) -> &mut Vec<PathBuf> {
|
||||
&mut self.path_stack
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
mod test {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use std::pin::Pin;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
|
||||
use crate::codegen::CodegenContext;
|
||||
@@ -32,14 +30,14 @@ impl<'a, 'ctx> ScopeGuard<'a, 'ctx> {
|
||||
}
|
||||
|
||||
pub struct DowngradeCtx<'ctx> {
|
||||
ctx: Pin<&'ctx mut Ctx>,
|
||||
ctx: &'ctx mut Ctx,
|
||||
irs: Vec<Option<Ir>>,
|
||||
scopes: Vec<Scope<'ctx>>,
|
||||
arg_id: usize,
|
||||
}
|
||||
|
||||
impl<'ctx> DowngradeCtx<'ctx> {
|
||||
pub fn new(ctx: Pin<&'ctx mut Ctx>, global: &'ctx HashMap<SymId, ExprId>) -> Self {
|
||||
pub fn new(ctx: &'ctx mut Ctx, global: &'ctx HashMap<SymId, ExprId>) -> Self {
|
||||
Self {
|
||||
scopes: vec![Scope::Global(global)],
|
||||
irs: vec![],
|
||||
@@ -62,7 +60,7 @@ impl DowngradeContext for DowngradeCtx<'_> {
|
||||
}
|
||||
|
||||
fn new_sym(&mut self, sym: String) -> SymId {
|
||||
self.ctx.as_mut().project().symbols.get_or_intern(sym)
|
||||
self.ctx.symbols.get_or_intern(sym)
|
||||
}
|
||||
|
||||
fn get_sym(&self, id: SymId) -> &str {
|
||||
@@ -144,8 +142,6 @@ impl DowngradeContext for DowngradeCtx<'_> {
|
||||
fn downgrade(mut self, root: rnix::ast::Expr) -> Result<ExprId> {
|
||||
let root = root.downgrade(&mut self)?;
|
||||
self.ctx
|
||||
.as_mut()
|
||||
.project()
|
||||
.irs
|
||||
.extend(self.irs.into_iter().map(Option::unwrap));
|
||||
Ok(root)
|
||||
|
||||
41
nix-js/src/context/drop_guard.rs
Normal file
41
nix-js/src/context/drop_guard.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
|
||||
pub trait PathStackProvider {
|
||||
fn path_stack(&mut self) -> &mut Vec<PathBuf>;
|
||||
}
|
||||
|
||||
pub struct PathDropGuard<'ctx, Ctx: PathStackProvider> {
|
||||
ctx: &'ctx mut Ctx,
|
||||
}
|
||||
|
||||
impl<'ctx, Ctx: PathStackProvider> PathDropGuard<'ctx, Ctx> {
|
||||
pub fn new(path: PathBuf, ctx: &'ctx mut Ctx) -> Self {
|
||||
ctx.path_stack().push(path);
|
||||
Self { ctx }
|
||||
}
|
||||
pub fn new_cwd(ctx: &'ctx mut Ctx) -> Result<Self> {
|
||||
let cwd = std::env::current_dir()
|
||||
.map_err(|err| Error::downgrade_error(format!("cannot get cwd: {err}")))?;
|
||||
let virtual_file = cwd.join("__eval__.nix");
|
||||
ctx.path_stack().push(virtual_file);
|
||||
Ok(Self { ctx })
|
||||
}
|
||||
pub fn as_ctx(&mut self) -> &mut Ctx {
|
||||
self.ctx
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: PathStackProvider> Deref for PathDropGuard<'_, Ctx> {
|
||||
type Target = Ctx;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.ctx
|
||||
}
|
||||
}
|
||||
impl<Ctx: PathStackProvider> DerefMut for PathDropGuard<'_, Ctx> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.ctx
|
||||
}
|
||||
}
|
||||
@@ -77,6 +77,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Path {
|
||||
path_str
|
||||
} else {
|
||||
let current_dir = ctx.get_current_dir();
|
||||
dbg!(¤t_dir);
|
||||
|
||||
current_dir
|
||||
.join(&path_str)
|
||||
|
||||
@@ -234,7 +234,7 @@ where
|
||||
Ctx: DowngradeContext,
|
||||
F: FnOnce(&mut Ctx, &[SymId]) -> Result<R>,
|
||||
{
|
||||
// 1. Collect all top-level binding keys
|
||||
// Collect all top-level binding keys
|
||||
let mut binding_syms = HashSet::new();
|
||||
|
||||
for entry in &entries {
|
||||
@@ -271,14 +271,14 @@ where
|
||||
|
||||
let binding_keys: Vec<_> = binding_syms.into_iter().collect();
|
||||
|
||||
// 2. Reserve slots for bindings
|
||||
// Reserve slots for bindings
|
||||
let slots_iter = ctx.reserve_slots(binding_keys.len());
|
||||
let slots_clone = slots_iter.clone();
|
||||
|
||||
// 3. Create let scope bindings
|
||||
// Create let scope bindings
|
||||
let let_bindings: HashMap<_, _> = binding_keys.iter().copied().zip(slots_iter).collect();
|
||||
|
||||
// 4. Process entries in let scope
|
||||
// Process entries in let scope
|
||||
let body = ctx.with_let_scope(let_bindings, |ctx| {
|
||||
// Collect all bindings in a temporary AttrSet
|
||||
let mut temp_attrs = AttrSet {
|
||||
@@ -313,6 +313,5 @@ where
|
||||
body_fn(ctx, &binding_keys)
|
||||
})?;
|
||||
|
||||
// 5. Return the slots and body
|
||||
Ok((slots_clone.collect(), body))
|
||||
}
|
||||
|
||||
@@ -1,38 +1,40 @@
|
||||
use std::borrow::Cow;
|
||||
use std::pin::Pin;
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::DerefMut;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Once;
|
||||
|
||||
use deno_core::{Extension, ExtensionFileSource, JsRuntime, OpDecl, OpState, RuntimeOptions, v8};
|
||||
use deno_core::{Extension, ExtensionFileSource, JsRuntime, OpState, RuntimeOptions, v8};
|
||||
use deno_error::JsErrorClass;
|
||||
|
||||
use crate::codegen::{CodegenContext, Compile};
|
||||
use crate::context::{CtxPtr, PathDropGuard};
|
||||
use crate::error::{Error, Result};
|
||||
use crate::ir::DowngradeContext;
|
||||
use crate::value::{AttrSet, List, Symbol, Value};
|
||||
|
||||
type ScopeRef<'p, 's> = v8::PinnedRef<'p, v8::HandleScope<'s>>;
|
||||
type LocalValue<'a> = v8::Local<'a, v8::Value>;
|
||||
type LocalSymbol<'a> = v8::Local<'a, v8::Symbol>;
|
||||
|
||||
fn runtime_extension(ctx: CtxPtr) -> Extension {
|
||||
pub(crate) trait RuntimeCtx: 'static {
|
||||
fn get_current_dir(&self) -> PathBuf;
|
||||
fn push_path_stack(&mut self, path: PathBuf) -> impl DerefMut<Target = Self>;
|
||||
fn compile_code(&mut self, code: &str) -> Result<String>;
|
||||
}
|
||||
|
||||
fn runtime_extension<Ctx: RuntimeCtx>() -> Extension {
|
||||
const ESM: &[ExtensionFileSource] =
|
||||
&deno_core::include_js_files!(nix_runtime dir "runtime-ts/dist", "runtime.js");
|
||||
const OPS: &[OpDecl] = &[
|
||||
op_import(),
|
||||
let ops = vec![
|
||||
op_import::<Ctx>(),
|
||||
op_read_file(),
|
||||
op_path_exists(),
|
||||
op_resolve_path(),
|
||||
op_resolve_path::<Ctx>(),
|
||||
];
|
||||
|
||||
Extension {
|
||||
name: "nix_runtime",
|
||||
esm_files: Cow::Borrowed(ESM),
|
||||
esm_entry_point: Some("ext:nix_runtime/runtime.js"),
|
||||
ops: Cow::Borrowed(OPS),
|
||||
op_state_fn: Some(Box::new(move |state| {
|
||||
state.put(ctx);
|
||||
})),
|
||||
ops: Cow::Owned(ops),
|
||||
enabled: true,
|
||||
..Default::default()
|
||||
}
|
||||
@@ -67,9 +69,11 @@ use private::NixError;
|
||||
|
||||
#[deno_core::op2]
|
||||
#[string]
|
||||
fn op_import(state: &mut OpState, #[string] path: String) -> std::result::Result<String, NixError> {
|
||||
let ptr = state.borrow_mut::<CtxPtr>();
|
||||
let ctx = unsafe { ptr.as_mut() };
|
||||
fn op_import<Ctx: RuntimeCtx>(
|
||||
state: &mut OpState,
|
||||
#[string] path: String,
|
||||
) -> std::result::Result<String, NixError> {
|
||||
let ctx = state.borrow_mut::<Ctx>();
|
||||
|
||||
let current_dir = ctx.get_current_dir();
|
||||
let mut absolute_path = current_dir
|
||||
@@ -80,30 +84,13 @@ fn op_import(state: &mut OpState, #[string] path: String) -> std::result::Result
|
||||
absolute_path.push("default.nix")
|
||||
}
|
||||
|
||||
let mut guard = PathDropGuard::new(absolute_path.clone(), ctx);
|
||||
let ctx = guard.as_ctx();
|
||||
|
||||
let content = std::fs::read_to_string(&absolute_path)
|
||||
.map_err(|e| format!("Failed to read {}: {}", absolute_path.display(), e))?;
|
||||
|
||||
let root = rnix::Root::parse(&content);
|
||||
if !root.errors().is_empty() {
|
||||
return Err(format!(
|
||||
"Parse error in {}: {:?}",
|
||||
absolute_path.display(),
|
||||
root.errors()
|
||||
)
|
||||
.into());
|
||||
}
|
||||
let mut guard = ctx.push_path_stack(absolute_path);
|
||||
let ctx = guard.deref_mut();
|
||||
|
||||
let expr = root.tree().expr().ok_or("No expression in file")?;
|
||||
let expr_id = ctx
|
||||
.as_mut()
|
||||
.downgrade_ctx()
|
||||
.downgrade(expr)
|
||||
.map_err(|e| format!("Downgrade error: {}", e))?;
|
||||
|
||||
Ok(ctx.get_ir(expr_id).compile(Pin::get_ref(ctx.as_ref())))
|
||||
Ok(ctx.compile_code(&content).map_err(|err| err.to_string())?)
|
||||
}
|
||||
|
||||
#[deno_core::op2]
|
||||
@@ -119,12 +106,11 @@ fn op_path_exists(#[string] path: String) -> bool {
|
||||
|
||||
#[deno_core::op2]
|
||||
#[string]
|
||||
fn op_resolve_path(
|
||||
fn op_resolve_path<Ctx: RuntimeCtx>(
|
||||
state: &mut OpState,
|
||||
#[string] path: String,
|
||||
) -> std::result::Result<String, NixError> {
|
||||
let ptr = state.borrow::<CtxPtr>();
|
||||
let ctx = unsafe { ptr.as_ref() };
|
||||
let ctx = state.borrow::<Ctx>();
|
||||
|
||||
// If already absolute, return as-is
|
||||
if path.starts_with('/') {
|
||||
@@ -141,14 +127,15 @@ fn op_resolve_path(
|
||||
.map_err(|e| format!("Failed to resolve path {}: {}", path, e))?)
|
||||
}
|
||||
|
||||
pub(crate) struct Runtime {
|
||||
pub(crate) struct Runtime<Ctx: RuntimeCtx> {
|
||||
js_runtime: JsRuntime,
|
||||
is_thunk_symbol: v8::Global<v8::Symbol>,
|
||||
primop_metadata_symbol: v8::Global<v8::Symbol>,
|
||||
_marker: PhantomData<Ctx>,
|
||||
}
|
||||
|
||||
impl Runtime {
|
||||
pub(crate) fn new(ctx: CtxPtr) -> Result<Self> {
|
||||
impl<Ctx: RuntimeCtx> Runtime<Ctx> {
|
||||
pub(crate) fn new() -> Result<Self> {
|
||||
// Initialize V8 once
|
||||
static INIT: Once = Once::new();
|
||||
INIT.call_once(|| {
|
||||
@@ -159,7 +146,7 @@ impl Runtime {
|
||||
});
|
||||
|
||||
let mut js_runtime = JsRuntime::new(RuntimeOptions {
|
||||
extensions: vec![runtime_extension(ctx)],
|
||||
extensions: vec![runtime_extension::<Ctx>()],
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
@@ -172,10 +159,13 @@ impl Runtime {
|
||||
js_runtime,
|
||||
is_thunk_symbol,
|
||||
primop_metadata_symbol,
|
||||
_marker: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn eval(&mut self, script: String) -> Result<Value> {
|
||||
pub(crate) fn eval(&mut self, script: String, ctx: Ctx) -> Result<Value> {
|
||||
self.js_runtime.op_state().borrow_mut().put(ctx);
|
||||
|
||||
let global_value = self
|
||||
.js_runtime
|
||||
.execute_script("<eval>", script)
|
||||
|
||||
Reference in New Issue
Block a user