refactor: use GAT in enum Ir
This commit is contained in:
+91
-58
@@ -8,7 +8,7 @@ use fix_codegen::{BytecodeContext, InstructionPtr, Op};
|
||||
use fix_common::{StringId, Symbol};
|
||||
use fix_error::{Error, Result, Source};
|
||||
use fix_ir::downgrade::{Downgrade as _, DowngradeContext};
|
||||
use fix_ir::{Ir, IrRef, MaybeThunk, RawIrRef, ThunkId};
|
||||
use fix_ir::{GhostMaybeThunkRef, GhostRef, Ir, IrRef, MaybeThunk, RawIrRef, RawRef, ThunkId};
|
||||
use fix_vm::{ForceMode, StaticValue, Vm, VmCode, VmContext, VmRuntimeCtx};
|
||||
use ghost_cell::{GhostCell, GhostToken};
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
@@ -30,7 +30,7 @@ pub struct CodeState {
|
||||
pub sources: Vec<Source>,
|
||||
pub spans: Vec<(usize, rnix::TextRange)>,
|
||||
pub thunk_count: usize,
|
||||
pub global_env: HashMap<StringId, Ir<'static, RawIrRef<'static>>>,
|
||||
pub global_env: HashMap<StringId, Ir<'static, RawRef<'static>>>,
|
||||
}
|
||||
|
||||
pub struct Evaluator {
|
||||
@@ -85,13 +85,13 @@ impl Evaluator {
|
||||
source: Source,
|
||||
scope: &HashSet<StringId>,
|
||||
) -> Result<fix_common::Value> {
|
||||
self.do_eval(source, Some(Scope::Repl(scope)), ForceMode::Shallow)
|
||||
self.do_eval(source, Some(ExtraScope::Repl(scope)), ForceMode::Shallow)
|
||||
}
|
||||
|
||||
fn do_eval<'ctx>(
|
||||
&'ctx mut self,
|
||||
source: Source,
|
||||
extra_scope: Option<Scope<'ctx>>,
|
||||
extra_scope: Option<ExtraScope<'ctx>>,
|
||||
force_mode: ForceMode,
|
||||
) -> Result<fix_common::Value> {
|
||||
let ip = {
|
||||
@@ -175,14 +175,14 @@ impl<'a, R: VmRuntimeCtx> CompilerCtx<'a, R> {
|
||||
fn compile_bytecode(
|
||||
&mut self,
|
||||
source: Source,
|
||||
extra_scope: Option<Scope>,
|
||||
extra_scope: Option<ExtraScope>,
|
||||
) -> Result<InstructionPtr> {
|
||||
let root = self.downgrade(source, extra_scope)?;
|
||||
let ip = fix_codegen::compile_bytecode(root.as_ref(), self);
|
||||
Ok(ip)
|
||||
}
|
||||
|
||||
fn downgrade(&mut self, source: Source, extra_scope: Option<Scope>) -> Result<OwnedIr> {
|
||||
fn downgrade(&mut self, source: Source, extra_scope: Option<ExtraScope>) -> Result<OwnedIr> {
|
||||
tracing::debug!("Parsing Nix expression");
|
||||
|
||||
self.code.sources.push(source.clone());
|
||||
@@ -202,7 +202,7 @@ impl<'a, R: VmRuntimeCtx> CompilerCtx<'a, R> {
|
||||
token,
|
||||
self.runtime,
|
||||
&self.code.global_env,
|
||||
extra_scope,
|
||||
extra_scope.map(Into::into),
|
||||
&mut self.code.thunk_count,
|
||||
source,
|
||||
);
|
||||
@@ -245,6 +245,7 @@ impl<'a, R: VmRuntimeCtx> BytecodeContext for CompilerCtx<'a, R> {
|
||||
Float(x) => StaticValue::new_float(x),
|
||||
Bool(x) => StaticValue::new_inline(x),
|
||||
String(x) => StaticValue::new_inline(x),
|
||||
Path(_) => todo!(),
|
||||
PrimOp {
|
||||
id,
|
||||
arity,
|
||||
@@ -310,34 +311,20 @@ struct DowngradeCtx<'ctx, 'id, 'ir, R: VmRuntimeCtx> {
|
||||
token: GhostToken<'id>,
|
||||
runtime: &'ctx mut R,
|
||||
source: Source,
|
||||
scopes: Vec<Scope<'ctx>>,
|
||||
scopes: Vec<Scope<'ctx, 'id, 'ir>>,
|
||||
with_scope_count: u32,
|
||||
arg_count: u32,
|
||||
thunk_count: &'ctx mut usize,
|
||||
thunk_scopes: Vec<ThunkScope<'id, 'ir>>,
|
||||
}
|
||||
|
||||
fn should_thunk<'id>(ir: IrRef<'id, '_>, token: &GhostToken<'id>) -> bool {
|
||||
!matches!(
|
||||
ir.borrow(token),
|
||||
Ir::Builtin(_)
|
||||
| Ir::Builtins
|
||||
| Ir::Int(_)
|
||||
| Ir::Float(_)
|
||||
| Ir::Bool(_)
|
||||
| Ir::Null
|
||||
| Ir::Str(_)
|
||||
| Ir::Thunk(_)
|
||||
)
|
||||
}
|
||||
|
||||
impl<'ctx, 'id, 'ir, R: VmRuntimeCtx> DowngradeCtx<'ctx, 'id, 'ir, R> {
|
||||
fn new(
|
||||
bump: &'ir Bump,
|
||||
token: GhostToken<'id>,
|
||||
runtime: &'ctx mut R,
|
||||
global: &'ctx HashMap<StringId, Ir<'static, RawIrRef<'static>>>,
|
||||
extra_scope: Option<Scope<'ctx>>,
|
||||
global: &'ctx HashMap<StringId, Ir<'static, RawRef<'static>>>,
|
||||
extra_scope: Option<Scope<'ctx, 'id, 'ir>>,
|
||||
thunk_count: &'ctx mut usize,
|
||||
source: Source,
|
||||
) -> Self {
|
||||
@@ -360,23 +347,29 @@ impl<'ctx, 'id, 'ir, R: VmRuntimeCtx> DowngradeCtx<'ctx, 'id, 'ir, R> {
|
||||
impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir>
|
||||
for DowngradeCtx<'ctx, 'id, 'ir, R>
|
||||
{
|
||||
fn new_expr(&self, expr: Ir<'ir, IrRef<'id, 'ir>>) -> IrRef<'id, 'ir> {
|
||||
IrRef::new(self.bump.alloc(GhostCell::new(expr)))
|
||||
fn new_expr(&self, expr: Ir<'ir, GhostRef<'id, 'ir>>) -> IrRef<'id, 'ir> {
|
||||
self.bump.alloc(GhostCell::new(expr))
|
||||
}
|
||||
|
||||
fn maybe_thunk(&mut self, ir: IrRef<'id, 'ir>) -> MaybeThunk {
|
||||
fn maybe_thunk(&mut self, ir: IrRef<'id, 'ir>) -> GhostMaybeThunkRef<'id, 'ir> {
|
||||
use MaybeThunk::*;
|
||||
match *ir.borrow(&self.token) {
|
||||
Ir::Builtin(x) => return Builtin(x),
|
||||
Ir::Int(x) => return Int(x),
|
||||
Ir::Float(x) => return Float(x),
|
||||
Ir::Bool(x) => return Bool(x),
|
||||
Ir::Str(x) => return Str(x),
|
||||
Ir::Thunk(x) => return Thunk(x),
|
||||
Ir::Arg { layer } => return Arg { layer },
|
||||
Ir::Builtins => return Builtins,
|
||||
Ir::Null => return Null,
|
||||
_ => (),
|
||||
let expr = (|| {
|
||||
let expr = match *ir.borrow(&self.token) {
|
||||
Ir::Builtin(x) => Builtin(x),
|
||||
Ir::Int(x) => Int(x),
|
||||
Ir::Float(x) => Float(x),
|
||||
Ir::Bool(x) => Bool(x),
|
||||
Ir::Str(x) => Str(x),
|
||||
Ir::Arg { layer } => Arg { layer },
|
||||
Ir::Builtins => Builtins,
|
||||
Ir::Null => Null,
|
||||
Ir::MaybeThunk(thunk) => return Some(thunk),
|
||||
_ => return None,
|
||||
};
|
||||
Some(self.bump.alloc(GhostCell::new(expr)))
|
||||
})();
|
||||
if let Some(thunk) = expr {
|
||||
return thunk;
|
||||
}
|
||||
let id = ThunkId(*self.thunk_count);
|
||||
*self.thunk_count = self.thunk_count.checked_add(1).expect("thunk id overflow");
|
||||
@@ -384,7 +377,7 @@ impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir>
|
||||
.last_mut()
|
||||
.expect("no active cache scope")
|
||||
.add_binding(id, ir, &self.token);
|
||||
Thunk(id)
|
||||
self.bump.alloc(GhostCell::new(Thunk(id)))
|
||||
}
|
||||
|
||||
fn intern_string(&mut self, sym: impl AsRef<str>) -> StringId {
|
||||
@@ -395,7 +388,7 @@ impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir>
|
||||
self.runtime.resolve_string(id).into()
|
||||
}
|
||||
|
||||
fn lookup(&self, sym: StringId, span: rnix::TextRange) -> Result<MaybeThunk> {
|
||||
fn lookup(&self, sym: StringId, span: rnix::TextRange) -> Result<GhostMaybeThunkRef<'id, 'ir>> {
|
||||
for scope in self.scopes.iter().rev() {
|
||||
match scope {
|
||||
&Scope::Global(global_scope) => {
|
||||
@@ -408,22 +401,26 @@ impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir>
|
||||
Ir::Null => Null,
|
||||
_ => unreachable!("globals should only contain leaf IR nodes"),
|
||||
};
|
||||
return Ok(val);
|
||||
return Ok(self.bump.alloc(GhostCell::new(val)));
|
||||
}
|
||||
}
|
||||
&Scope::Repl(repl_bindings) => {
|
||||
if repl_bindings.contains(&sym) {
|
||||
return Ok(MaybeThunk::ReplBinding(sym));
|
||||
return Ok(self
|
||||
.bump
|
||||
.alloc(GhostCell::new(MaybeThunk::ReplBinding(sym))));
|
||||
}
|
||||
}
|
||||
Scope::ScopedImport(scoped_bindings) => {
|
||||
if scoped_bindings.contains(&sym) {
|
||||
return Ok(MaybeThunk::ScopedImportBinding(sym));
|
||||
return Ok(self
|
||||
.bump
|
||||
.alloc(GhostCell::new(MaybeThunk::ScopedImportBinding(sym))));
|
||||
}
|
||||
}
|
||||
Scope::Let(let_scope) => {
|
||||
if let Some(&expr) = let_scope.get(&sym) {
|
||||
return Ok(MaybeThunk::Thunk(expr));
|
||||
return Ok(expr);
|
||||
}
|
||||
}
|
||||
&Scope::Param {
|
||||
@@ -434,14 +431,14 @@ impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir>
|
||||
let layers: u8 =
|
||||
self.thunk_scopes.len().try_into().expect("scope too deep!");
|
||||
let layer = layers - abs_layer;
|
||||
return Ok(MaybeThunk::Arg { layer });
|
||||
return Ok(self.bump.alloc(GhostCell::new(MaybeThunk::Arg { layer })));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.with_scope_count > 0 {
|
||||
Ok(MaybeThunk::WithLookup(sym))
|
||||
Ok(self.bump.alloc(GhostCell::new(MaybeThunk::WithLookup(sym))))
|
||||
} else {
|
||||
Err(Error::downgrade_error(
|
||||
format!("'{}' not found", self.resolve_sym(sym)),
|
||||
@@ -464,11 +461,14 @@ impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir>
|
||||
.thunk_count
|
||||
.checked_add(keys.len())
|
||||
.expect("thunk id overflow");
|
||||
let iter = keys
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(offset, &key)| (key, ThunkId(base + offset)));
|
||||
self.scopes.push(Scope::Let(iter.collect()));
|
||||
let scope = {
|
||||
let mut scope = HashMap::new();
|
||||
for (offset, &key) in keys.iter().enumerate() {
|
||||
scope.insert(key, &*self.bump.alloc(GhostCell::new(MaybeThunk::Thunk(ThunkId(base + offset)))));
|
||||
}
|
||||
scope
|
||||
};
|
||||
self.scopes.push(Scope::Let(scope));
|
||||
let (vals, ret) = {
|
||||
let mut guard = ScopeGuard { ctx: self };
|
||||
f(guard.as_ctx())?
|
||||
@@ -538,8 +538,10 @@ impl<'id, 'ir, 'ctx: 'ir, R: VmRuntimeCtx> DowngradeCtx<'ctx, 'id, 'ir, R> {
|
||||
.pop()
|
||||
.expect("no thunk scope left???")
|
||||
.bindings;
|
||||
let ir = IrRef::alloc(self.bump, Ir::TopLevel { body, thunks });
|
||||
Ok(ir.freeze(self.token))
|
||||
Ok(Ir::freeze(
|
||||
self.new_expr(Ir::TopLevel { body, thunks }),
|
||||
self.token,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -563,14 +565,29 @@ impl<'id, 'ir> ThunkScope<'id, 'ir> {
|
||||
}
|
||||
}
|
||||
|
||||
enum Scope<'ctx> {
|
||||
Global(&'ctx HashMap<StringId, Ir<'static, RawIrRef<'static>>>),
|
||||
enum Scope<'ctx, 'id, 'ir> {
|
||||
Global(&'ctx HashMap<StringId, Ir<'static, RawRef<'static>>>),
|
||||
Repl(&'ctx HashSet<StringId>),
|
||||
ScopedImport(HashSet<StringId>),
|
||||
Let(HashMap<StringId, ThunkId>),
|
||||
Let(HashMap<StringId, GhostMaybeThunkRef<'id, 'ir>>),
|
||||
Param { sym: StringId, abs_layer: u8 },
|
||||
}
|
||||
|
||||
enum ExtraScope<'ctx> {
|
||||
Repl(&'ctx HashSet<StringId>),
|
||||
ScopedImport(HashSet<StringId>),
|
||||
}
|
||||
|
||||
impl<'ctx> From<ExtraScope<'ctx>> for Scope<'ctx, '_, '_> {
|
||||
fn from(value: ExtraScope<'ctx>) -> Self {
|
||||
use ExtraScope::*;
|
||||
match value {
|
||||
ScopedImport(scope) => Scope::ScopedImport(scope),
|
||||
Repl(scope) => Scope::Repl(scope),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ScopeGuard<'a, 'ctx, 'id, 'ir, R: VmRuntimeCtx> {
|
||||
ctx: &'a mut DowngradeCtx<'ctx, 'id, 'ir, R>,
|
||||
}
|
||||
@@ -593,15 +610,31 @@ struct OwnedIr {
|
||||
}
|
||||
|
||||
impl OwnedIr {
|
||||
/// # Safety
|
||||
/// `ir` must be an allocation backed by `bump`. The reference's
|
||||
/// lifetime is extended to `'static` as a placeholder; the stored IR
|
||||
/// must only be re-borrowed via [`OwnedIr::as_ref`], which narrows
|
||||
/// the lifetime back to that of the `&self` borrow. Moving `bump`
|
||||
/// into the struct keeps the underlying allocation live for the
|
||||
/// lifetime of the `OwnedIr`.
|
||||
unsafe fn new(ir: RawIrRef<'_>, bump: Bump) -> Self {
|
||||
Self {
|
||||
_bump: bump,
|
||||
// SAFETY: see function docs - caller guarantees `ir` is in `bump`,
|
||||
// and the `'static` lifetime is a placeholder narrowed by `as_ref`.
|
||||
ir: unsafe { std::mem::transmute::<RawIrRef<'_>, RawIrRef<'static>>(ir) },
|
||||
}
|
||||
}
|
||||
|
||||
fn as_ref(&self) -> RawIrRef<'_> {
|
||||
self.ir
|
||||
fn as_ref<'ir>(&'ir self) -> RawIrRef<'ir> {
|
||||
// SAFETY: narrows the placeholder `'static` lifetime stored in
|
||||
// `self.ir` down to `'ir = &'ir self`. Lifetime shortening is
|
||||
// logically sound for covariant positions; the transmute is only
|
||||
// needed because `RawRef<'ir>` carries `'ir` through a GAT
|
||||
// (`Ref::Ref<T>`), which prevents the compiler from inferring
|
||||
// covariance automatically. The bump arena that backs the IR is
|
||||
// owned by `self._bump`, so the data is live for at least `'ir`.
|
||||
unsafe { std::mem::transmute::<RawIrRef<'static>, RawIrRef<'ir>>(self.ir) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user