|
|
|
@@ -1,20 +1,13 @@
|
|
|
|
|
#![warn(clippy::unwrap_used)]
|
|
|
|
|
#![allow(dead_code)]
|
|
|
|
|
|
|
|
|
|
use bumpalo::Bump;
|
|
|
|
|
use fix_abstract_vm::{ForceMode, StaticValue, VmCode, VmContext, VmRuntimeCtx};
|
|
|
|
|
use fix_builtins::PrimOpPhase;
|
|
|
|
|
use fix_codegen::disassembler::{Disassembler, DisassemblerContext};
|
|
|
|
|
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::{
|
|
|
|
|
GhostMaybeThunkRef, GhostRoIrRef, GhostRoMaybeThunkRef, GhostRoRef, Ir, MaybeThunk, RawIrRef,
|
|
|
|
|
ThunkId,
|
|
|
|
|
};
|
|
|
|
|
use fix_bytecode::InstructionPtr;
|
|
|
|
|
use fix_bytecode::disassembler::{Disassembler, DisassemblerContext};
|
|
|
|
|
use fix_compiler::{CodeState, ExtraScope};
|
|
|
|
|
use fix_error::{Result, Source};
|
|
|
|
|
use fix_lang::StringId;
|
|
|
|
|
use fix_runtime::{ForceMode, StaticValue, VmCode, VmContext, VmRuntimeCtx};
|
|
|
|
|
use fix_vm::Vm;
|
|
|
|
|
use ghost_cell::{GhostCell, GhostToken};
|
|
|
|
|
use hashbrown::{HashMap, HashSet};
|
|
|
|
|
use string_interner::{DefaultStringInterner, Symbol as _};
|
|
|
|
|
|
|
|
|
@@ -29,15 +22,6 @@ pub struct RuntimeState {
|
|
|
|
|
pub constants: Constants,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub struct CodeState {
|
|
|
|
|
pub bytecode: Vec<u8>,
|
|
|
|
|
pub sources: Vec<Source>,
|
|
|
|
|
pub spans: Vec<(usize, rnix::TextRange)>,
|
|
|
|
|
pub thunk_count: usize,
|
|
|
|
|
pub global_env: HashMap<StringId, MaybeThunk>,
|
|
|
|
|
pub current_scope_slot: Option<u32>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub struct Evaluator {
|
|
|
|
|
pub runtime: RuntimeState,
|
|
|
|
|
pub code: CodeState,
|
|
|
|
@@ -52,37 +36,25 @@ impl Default for Evaluator {
|
|
|
|
|
impl Evaluator {
|
|
|
|
|
pub fn new() -> Self {
|
|
|
|
|
let mut strings = DefaultStringInterner::new();
|
|
|
|
|
let global_env = fix_ir::new_global_env(&mut strings);
|
|
|
|
|
let mut bytecode = Vec::with_capacity(PrimOpPhase::Illegal as usize * 2);
|
|
|
|
|
for phase in 0..=PrimOpPhase::Illegal as u8 {
|
|
|
|
|
bytecode.push(Op::DispatchPrimOp as u8);
|
|
|
|
|
bytecode.push(phase);
|
|
|
|
|
}
|
|
|
|
|
let code = CodeState::new(&mut strings);
|
|
|
|
|
Self {
|
|
|
|
|
runtime: RuntimeState {
|
|
|
|
|
strings,
|
|
|
|
|
constants: Constants::default(),
|
|
|
|
|
},
|
|
|
|
|
code: CodeState {
|
|
|
|
|
sources: Vec::new(),
|
|
|
|
|
spans: Vec::new(),
|
|
|
|
|
thunk_count: 0,
|
|
|
|
|
bytecode,
|
|
|
|
|
global_env,
|
|
|
|
|
current_scope_slot: None,
|
|
|
|
|
},
|
|
|
|
|
code,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn eval(&mut self, source: Source) -> Result<fix_common::Value> {
|
|
|
|
|
pub fn eval(&mut self, source: Source) -> Result<fix_lang::Value> {
|
|
|
|
|
self.do_eval(source, None, ForceMode::AsIs)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn eval_shallow(&mut self, source: Source) -> Result<fix_common::Value> {
|
|
|
|
|
pub fn eval_shallow(&mut self, source: Source) -> Result<fix_lang::Value> {
|
|
|
|
|
self.do_eval(source, None, ForceMode::Shallow)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn eval_deep(&mut self, source: Source) -> Result<fix_common::Value> {
|
|
|
|
|
pub fn eval_deep(&mut self, source: Source) -> Result<fix_lang::Value> {
|
|
|
|
|
self.do_eval(source, None, ForceMode::Deep)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -90,7 +62,7 @@ impl Evaluator {
|
|
|
|
|
&mut self,
|
|
|
|
|
source: Source,
|
|
|
|
|
scope: &HashSet<StringId>,
|
|
|
|
|
) -> Result<fix_common::Value> {
|
|
|
|
|
) -> Result<fix_lang::Value> {
|
|
|
|
|
self.do_eval(source, Some(ExtraScope::Repl(scope)), ForceMode::Shallow)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -99,14 +71,10 @@ impl Evaluator {
|
|
|
|
|
source: Source,
|
|
|
|
|
extra_scope: Option<ExtraScope<'ctx>>,
|
|
|
|
|
force_mode: ForceMode,
|
|
|
|
|
) -> Result<fix_common::Value> {
|
|
|
|
|
let ip = {
|
|
|
|
|
let mut compiler = CompilerCtx {
|
|
|
|
|
code: &mut self.code,
|
|
|
|
|
runtime: &mut self.runtime,
|
|
|
|
|
};
|
|
|
|
|
compiler.compile_bytecode(source, extra_scope)?
|
|
|
|
|
};
|
|
|
|
|
) -> Result<fix_lang::Value> {
|
|
|
|
|
let ip = self
|
|
|
|
|
.code
|
|
|
|
|
.compile_bytecode(source, extra_scope, &mut self.runtime)?;
|
|
|
|
|
Vm::run(self, ip, force_mode)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -115,16 +83,12 @@ impl Evaluator {
|
|
|
|
|
_ident: &str,
|
|
|
|
|
_expr: &str,
|
|
|
|
|
_scope: &mut HashSet<StringId>,
|
|
|
|
|
) -> Result<fix_common::Value> {
|
|
|
|
|
) -> Result<fix_lang::Value> {
|
|
|
|
|
todo!("add_binding")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn compile_bytecode(&mut self, source: Source) -> Result<InstructionPtr> {
|
|
|
|
|
let mut compiler = CompilerCtx {
|
|
|
|
|
code: &mut self.code,
|
|
|
|
|
runtime: &mut self.runtime,
|
|
|
|
|
};
|
|
|
|
|
compiler.compile_bytecode(source, None)
|
|
|
|
|
self.code.compile_bytecode(source, None, &mut self.runtime)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn disassemble_colored(&self, ip: InstructionPtr) -> String {
|
|
|
|
@@ -149,151 +113,12 @@ impl VmRuntimeCtx for RuntimeState {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl VmCode for CodeState {
|
|
|
|
|
fn bytecode(&self) -> &[u8] {
|
|
|
|
|
&self.bytecode
|
|
|
|
|
}
|
|
|
|
|
fn compile_with_scope(
|
|
|
|
|
&mut self,
|
|
|
|
|
source: Source,
|
|
|
|
|
extra_scope: Option<fix_abstract_vm::ExtraScope>,
|
|
|
|
|
runtime: &mut impl VmRuntimeCtx,
|
|
|
|
|
) -> Result<InstructionPtr> {
|
|
|
|
|
let mut compiler = CompilerCtx {
|
|
|
|
|
code: self,
|
|
|
|
|
runtime,
|
|
|
|
|
};
|
|
|
|
|
let extra = extra_scope.map(|s| match s {
|
|
|
|
|
fix_abstract_vm::ExtraScope::ScopedImport { keys, slot_id } => {
|
|
|
|
|
ExtraScope::ScopedImport { keys, slot_id }
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
compiler.compile_bytecode(source, extra)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl VmContext for Evaluator {
|
|
|
|
|
fn split(&mut self) -> (&mut impl VmCode, &mut impl VmRuntimeCtx) {
|
|
|
|
|
(&mut self.code, &mut self.runtime)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct CompilerCtx<'a, R: VmRuntimeCtx> {
|
|
|
|
|
code: &'a mut CodeState,
|
|
|
|
|
runtime: &'a mut R,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a, R: VmRuntimeCtx> CompilerCtx<'a, R> {
|
|
|
|
|
fn compile_bytecode(
|
|
|
|
|
&mut self,
|
|
|
|
|
source: Source,
|
|
|
|
|
extra_scope: Option<ExtraScope>,
|
|
|
|
|
) -> Result<InstructionPtr> {
|
|
|
|
|
let prev_scope_slot = self.code.current_scope_slot;
|
|
|
|
|
self.code.current_scope_slot = match &extra_scope {
|
|
|
|
|
Some(ExtraScope::ScopedImport { slot_id, .. }) => Some(*slot_id),
|
|
|
|
|
_ => None,
|
|
|
|
|
};
|
|
|
|
|
let result = (|| -> Result<InstructionPtr> {
|
|
|
|
|
let root = self.downgrade(source, extra_scope)?;
|
|
|
|
|
let ip = fix_codegen::compile_bytecode(root.as_ref(), self);
|
|
|
|
|
Ok(ip)
|
|
|
|
|
})();
|
|
|
|
|
self.code.current_scope_slot = prev_scope_slot;
|
|
|
|
|
result
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn downgrade(&mut self, source: Source, extra_scope: Option<ExtraScope>) -> Result<OwnedIr> {
|
|
|
|
|
tracing::debug!("Parsing Nix expression");
|
|
|
|
|
|
|
|
|
|
self.code.sources.push(source.clone());
|
|
|
|
|
|
|
|
|
|
let root = rnix::Root::parse(&source.src);
|
|
|
|
|
handle_parse_error(root.errors(), source.clone()).map_or(Ok(()), Err)?;
|
|
|
|
|
|
|
|
|
|
tracing::debug!("Downgrading Nix expression");
|
|
|
|
|
let expr = root
|
|
|
|
|
.tree()
|
|
|
|
|
.expr()
|
|
|
|
|
.ok_or_else(|| Error::parse_error("unexpected EOF".into()))?;
|
|
|
|
|
let bump = Bump::new();
|
|
|
|
|
GhostToken::new(|token| {
|
|
|
|
|
let downgrade_ctx = DowngradeCtx::new(
|
|
|
|
|
&bump,
|
|
|
|
|
token,
|
|
|
|
|
self.runtime,
|
|
|
|
|
&self.code.global_env,
|
|
|
|
|
extra_scope.map(Into::into),
|
|
|
|
|
&mut self.code.thunk_count,
|
|
|
|
|
source,
|
|
|
|
|
);
|
|
|
|
|
let ir = downgrade_ctx.downgrade_toplevel(expr)?;
|
|
|
|
|
let ir = unsafe { std::mem::transmute::<RawIrRef<'_>, RawIrRef<'static>>(ir) };
|
|
|
|
|
Ok(OwnedIr { _bump: bump, ir })
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a, R: VmRuntimeCtx> BytecodeContext for CompilerCtx<'a, R> {
|
|
|
|
|
fn intern_string(&mut self, s: &str) -> StringId {
|
|
|
|
|
self.runtime.intern_string(s)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn register_span(&mut self, range: rnix::TextRange) -> u32 {
|
|
|
|
|
let id = self.code.spans.len();
|
|
|
|
|
let source_id = self
|
|
|
|
|
.code
|
|
|
|
|
.sources
|
|
|
|
|
.len()
|
|
|
|
|
.checked_sub(1)
|
|
|
|
|
.expect("current_source not set");
|
|
|
|
|
self.code.spans.push((source_id, range));
|
|
|
|
|
id as u32
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn get_code(&self) -> &[u8] {
|
|
|
|
|
&self.code.bytecode
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn get_code_mut(&mut self) -> &mut Vec<u8> {
|
|
|
|
|
&mut self.code.bytecode
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn add_constant(&mut self, val: fix_codegen::Const) -> u32 {
|
|
|
|
|
use fix_codegen::Const::*;
|
|
|
|
|
let val = match val {
|
|
|
|
|
Smi(x) => StaticValue::new_inline(x),
|
|
|
|
|
Float(x) => StaticValue::new_float(x),
|
|
|
|
|
Bool(x) => StaticValue::new_inline(x),
|
|
|
|
|
String(x) => StaticValue::new_inline(x),
|
|
|
|
|
Path(_) => todo!("path value type"),
|
|
|
|
|
PrimOp {
|
|
|
|
|
id,
|
|
|
|
|
arity,
|
|
|
|
|
dispatch_ip,
|
|
|
|
|
} => StaticValue::new_primop(id, arity, dispatch_ip),
|
|
|
|
|
Null => StaticValue::default(),
|
|
|
|
|
};
|
|
|
|
|
self.runtime.add_const(val)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn current_source_dir(&mut self) -> StringId {
|
|
|
|
|
let dir = self
|
|
|
|
|
.code
|
|
|
|
|
.sources
|
|
|
|
|
.last()
|
|
|
|
|
.expect("current_source not set")
|
|
|
|
|
.get_dir()
|
|
|
|
|
.to_string_lossy()
|
|
|
|
|
.into_owned();
|
|
|
|
|
self.runtime.intern_string(dir)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn current_scope_slot(&self) -> Option<u32> {
|
|
|
|
|
self.code.current_scope_slot
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Default)]
|
|
|
|
|
pub struct Constants {
|
|
|
|
|
data: Vec<StaticValue>,
|
|
|
|
@@ -315,402 +140,6 @@ impl Constants {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn parse_error_span(error: &rnix::ParseError) -> Option<rnix::TextRange> {
|
|
|
|
|
use rnix::ParseError::*;
|
|
|
|
|
match error {
|
|
|
|
|
Unexpected(range)
|
|
|
|
|
| UnexpectedExtra(range)
|
|
|
|
|
| UnexpectedWanted(_, range, _)
|
|
|
|
|
| UnexpectedDoubleBind(range)
|
|
|
|
|
| DuplicatedArgs(range, _) => Some(*range),
|
|
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn handle_parse_error<'a>(
|
|
|
|
|
errors: impl IntoIterator<Item = &'a rnix::ParseError>,
|
|
|
|
|
source: Source,
|
|
|
|
|
) -> Option<Box<Error>> {
|
|
|
|
|
for err in errors {
|
|
|
|
|
if let Some(span) = parse_error_span(err) {
|
|
|
|
|
return Some(
|
|
|
|
|
Error::parse_error(err.to_string())
|
|
|
|
|
.with_source(source)
|
|
|
|
|
.with_span(span),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct DowngradeCtx<'ctx, 'id, 'ir, R: VmRuntimeCtx> {
|
|
|
|
|
bump: &'ir Bump,
|
|
|
|
|
token: GhostToken<'id>,
|
|
|
|
|
runtime: &'ctx mut R,
|
|
|
|
|
source: Source,
|
|
|
|
|
scopes: Vec<Scope<'ctx, 'id, 'ir>>,
|
|
|
|
|
with_stack: Vec<GhostRoMaybeThunkRef<'id, 'ir>>,
|
|
|
|
|
arg_count: u32,
|
|
|
|
|
thunk_count: &'ctx mut usize,
|
|
|
|
|
thunk_scopes: Vec<ThunkScope<'id, 'ir>>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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, MaybeThunk>,
|
|
|
|
|
extra_scope: Option<Scope<'ctx, 'id, 'ir>>,
|
|
|
|
|
thunk_count: &'ctx mut usize,
|
|
|
|
|
source: Source,
|
|
|
|
|
) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
bump,
|
|
|
|
|
token,
|
|
|
|
|
runtime,
|
|
|
|
|
source,
|
|
|
|
|
scopes: std::iter::once(Scope::Global(global))
|
|
|
|
|
.chain(extra_scope)
|
|
|
|
|
.collect(),
|
|
|
|
|
thunk_count,
|
|
|
|
|
arg_count: 0,
|
|
|
|
|
with_stack: Vec::new(),
|
|
|
|
|
thunk_scopes: vec![ThunkScope::new_in(bump)],
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir>
|
|
|
|
|
for DowngradeCtx<'ctx, 'id, 'ir, R>
|
|
|
|
|
{
|
|
|
|
|
fn new_expr(&self, expr: Ir<'ir, GhostRoRef<'id, 'ir>>) -> GhostRoIrRef<'id, 'ir> {
|
|
|
|
|
self.bump.alloc(GhostCell::new(expr).into())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn maybe_thunk(&mut self, ir: GhostRoIrRef<'id, 'ir>) -> GhostRoMaybeThunkRef<'id, 'ir> {
|
|
|
|
|
use MaybeThunk::*;
|
|
|
|
|
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).into()))
|
|
|
|
|
})();
|
|
|
|
|
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");
|
|
|
|
|
self.thunk_scopes
|
|
|
|
|
.last_mut()
|
|
|
|
|
.expect("no active cache scope")
|
|
|
|
|
.add_binding(id, ir);
|
|
|
|
|
self.bump.alloc(GhostCell::new(Thunk(id)).into())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn intern_string(&mut self, sym: impl AsRef<str>) -> StringId {
|
|
|
|
|
self.runtime.intern_string(sym)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn resolve_sym(&self, id: StringId) -> Symbol<'_> {
|
|
|
|
|
self.runtime.resolve_string(id).into()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn lookup(
|
|
|
|
|
&mut self,
|
|
|
|
|
sym: StringId,
|
|
|
|
|
span: rnix::TextRange,
|
|
|
|
|
) -> Result<GhostRoMaybeThunkRef<'id, 'ir>> {
|
|
|
|
|
for scope in self.scopes.iter().rev() {
|
|
|
|
|
match scope {
|
|
|
|
|
&Scope::Global(global_scope) => {
|
|
|
|
|
if let Some(expr) = global_scope.get(&sym) {
|
|
|
|
|
return Ok(expr.into());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
&Scope::Repl(repl_bindings) => {
|
|
|
|
|
if repl_bindings.contains(&sym) {
|
|
|
|
|
return Ok(self
|
|
|
|
|
.bump
|
|
|
|
|
.alloc(GhostCell::new(MaybeThunk::ReplBinding(sym)).into()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Scope::ScopedImport { keys, .. } => {
|
|
|
|
|
if keys.contains(&sym) {
|
|
|
|
|
return Ok(self
|
|
|
|
|
.bump
|
|
|
|
|
.alloc(GhostCell::new(MaybeThunk::ScopedImportBinding(sym)).into()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Scope::Let(let_scope) => {
|
|
|
|
|
if let Some(&expr) = let_scope.get(&sym) {
|
|
|
|
|
return Ok(expr.into());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
&Scope::Param {
|
|
|
|
|
sym: param_sym,
|
|
|
|
|
abs_layer,
|
|
|
|
|
} => {
|
|
|
|
|
if param_sym == sym {
|
|
|
|
|
let layers: u8 =
|
|
|
|
|
self.thunk_scopes.len().try_into().expect("scope too deep!");
|
|
|
|
|
let layer = layers - abs_layer;
|
|
|
|
|
return Ok(self
|
|
|
|
|
.bump
|
|
|
|
|
.alloc(GhostCell::new(MaybeThunk::Arg { layer }).into()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !self.with_stack.is_empty() {
|
|
|
|
|
let id = ThunkId(*self.thunk_count);
|
|
|
|
|
*self.thunk_count = self.thunk_count.checked_add(1).expect("thunk id overflow");
|
|
|
|
|
let mut namespaces =
|
|
|
|
|
bumpalo::collections::Vec::with_capacity_in(self.with_stack.len(), self.bump);
|
|
|
|
|
namespaces.extend(self.with_stack.iter().rev().copied());
|
|
|
|
|
let body = self
|
|
|
|
|
.bump
|
|
|
|
|
.alloc(GhostCell::new(Ir::WithLookup { sym, namespaces }).into());
|
|
|
|
|
self.thunk_scopes
|
|
|
|
|
.last_mut()
|
|
|
|
|
.expect("no active thunk scope")
|
|
|
|
|
.add_binding(id, body);
|
|
|
|
|
Ok(self
|
|
|
|
|
.bump
|
|
|
|
|
.alloc(GhostCell::new(MaybeThunk::Thunk(id)).into()))
|
|
|
|
|
} else {
|
|
|
|
|
Err(Error::downgrade_error(
|
|
|
|
|
format!("'{}' not found", self.resolve_sym(sym)),
|
|
|
|
|
self.get_current_source(),
|
|
|
|
|
span,
|
|
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn get_current_source(&self) -> Source {
|
|
|
|
|
self.source.clone()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn with_let_scope<F, Ret>(&mut self, keys: &[StringId], f: F) -> Result<Ret>
|
|
|
|
|
where
|
|
|
|
|
F: FnOnce(
|
|
|
|
|
&mut Self,
|
|
|
|
|
) -> Result<(
|
|
|
|
|
bumpalo::collections::Vec<'ir, GhostRoMaybeThunkRef<'id, 'ir>>,
|
|
|
|
|
Ret,
|
|
|
|
|
)>,
|
|
|
|
|
{
|
|
|
|
|
let base = *self.thunk_count;
|
|
|
|
|
*self.thunk_count = self
|
|
|
|
|
.thunk_count
|
|
|
|
|
.checked_add(keys.len())
|
|
|
|
|
.expect("thunk id overflow");
|
|
|
|
|
let handles = (base..base + keys.len())
|
|
|
|
|
.map(|id| {
|
|
|
|
|
&*self
|
|
|
|
|
.bump
|
|
|
|
|
.alloc(GhostCell::new(MaybeThunk::Thunk(ThunkId(id))))
|
|
|
|
|
})
|
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
let scope = keys.iter().copied().zip(handles.iter().copied()).collect();
|
|
|
|
|
self.scopes.push(Scope::Let(scope));
|
|
|
|
|
let (vals, ret) = { f(self)? };
|
|
|
|
|
self.scopes.pop();
|
|
|
|
|
assert_eq!(keys.len(), vals.len());
|
|
|
|
|
let scope = self.thunk_scopes.last_mut().expect("no active thunk scope");
|
|
|
|
|
for (i, (val, handle)) in vals.into_iter().zip(handles).enumerate() {
|
|
|
|
|
let thunk = *val.borrow(&self.token);
|
|
|
|
|
*handle.borrow_mut(&mut self.token) = thunk;
|
|
|
|
|
let id = ThunkId(base + i);
|
|
|
|
|
let ir_ref = self
|
|
|
|
|
.bump
|
|
|
|
|
.alloc(GhostCell::new(Ir::MaybeThunk(handle.into())).into());
|
|
|
|
|
scope.add_binding(id, ir_ref);
|
|
|
|
|
}
|
|
|
|
|
Ok(ret)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn with_param_scope<F, Ret>(&mut self, sym: StringId, f: F) -> Ret
|
|
|
|
|
where
|
|
|
|
|
F: FnOnce(&mut Self) -> Ret,
|
|
|
|
|
{
|
|
|
|
|
self.scopes.push(Scope::Param {
|
|
|
|
|
sym,
|
|
|
|
|
abs_layer: self.thunk_scopes.len().try_into().expect("scope too deep!"),
|
|
|
|
|
});
|
|
|
|
|
let mut guard = ScopeGuard { ctx: self };
|
|
|
|
|
f(guard.as_ctx())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn with_with_scope<F, Ret>(&mut self, namespace: GhostRoMaybeThunkRef<'id, 'ir>, f: F) -> Ret
|
|
|
|
|
where
|
|
|
|
|
F: FnOnce(&mut Self) -> Ret,
|
|
|
|
|
{
|
|
|
|
|
self.with_stack.push(namespace);
|
|
|
|
|
let ret = f(self);
|
|
|
|
|
self.with_stack.pop();
|
|
|
|
|
ret
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn with_thunk_scope<F, Ret>(
|
|
|
|
|
&mut self,
|
|
|
|
|
f: F,
|
|
|
|
|
) -> (
|
|
|
|
|
Ret,
|
|
|
|
|
bumpalo::collections::Vec<'ir, (ThunkId, GhostRoIrRef<'id, 'ir>)>,
|
|
|
|
|
)
|
|
|
|
|
where
|
|
|
|
|
F: FnOnce(&mut Self) -> Ret,
|
|
|
|
|
{
|
|
|
|
|
if self.thunk_scopes.len() == u8::MAX as usize {
|
|
|
|
|
panic!("scope too deep!");
|
|
|
|
|
}
|
|
|
|
|
self.thunk_scopes.push(ThunkScope::new_in(self.bump));
|
|
|
|
|
let ret = f(self);
|
|
|
|
|
(
|
|
|
|
|
ret,
|
|
|
|
|
self.thunk_scopes
|
|
|
|
|
.pop()
|
|
|
|
|
.expect("no thunk scope left???")
|
|
|
|
|
.bindings,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn bump(&self) -> &'ir bumpalo::Bump {
|
|
|
|
|
self.bump
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'id, 'ir, 'ctx: 'ir, R: VmRuntimeCtx> DowngradeCtx<'ctx, 'id, 'ir, R> {
|
|
|
|
|
fn downgrade_toplevel(mut self, root: rnix::ast::Expr) -> Result<RawIrRef<'ir>> {
|
|
|
|
|
let body = root.downgrade(&mut self)?;
|
|
|
|
|
let thunks = self
|
|
|
|
|
.thunk_scopes
|
|
|
|
|
.pop()
|
|
|
|
|
.expect("no thunk scope left???")
|
|
|
|
|
.bindings;
|
|
|
|
|
Ok(Ir::freeze(
|
|
|
|
|
self.new_expr(Ir::TopLevel { body, thunks }),
|
|
|
|
|
self.token,
|
|
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct ThunkScope<'id, 'ir> {
|
|
|
|
|
bindings: bumpalo::collections::Vec<'ir, (ThunkId, GhostRoIrRef<'id, 'ir>)>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'id, 'ir> ThunkScope<'id, 'ir> {
|
|
|
|
|
fn new_in(bump: &'ir Bump) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
bindings: bumpalo::collections::Vec::new_in(bump),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn add_binding(&mut self, id: ThunkId, ir: GhostRoIrRef<'id, 'ir>) {
|
|
|
|
|
self.bindings.push((id, ir));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn extend_bindings(
|
|
|
|
|
&mut self,
|
|
|
|
|
iter: impl IntoIterator<Item = (ThunkId, GhostRoIrRef<'id, 'ir>)>,
|
|
|
|
|
) {
|
|
|
|
|
self.bindings.extend(iter);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum Scope<'ctx, 'id, 'ir> {
|
|
|
|
|
Global(&'ctx HashMap<StringId, MaybeThunk>),
|
|
|
|
|
Repl(&'ctx HashSet<StringId>),
|
|
|
|
|
ScopedImport {
|
|
|
|
|
keys: HashSet<StringId>,
|
|
|
|
|
slot_id: u32,
|
|
|
|
|
},
|
|
|
|
|
Let(HashMap<StringId, GhostMaybeThunkRef<'id, 'ir>>),
|
|
|
|
|
Param {
|
|
|
|
|
sym: StringId,
|
|
|
|
|
abs_layer: u8,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub enum ExtraScope<'ctx> {
|
|
|
|
|
Repl(&'ctx HashSet<StringId>),
|
|
|
|
|
ScopedImport {
|
|
|
|
|
keys: HashSet<StringId>,
|
|
|
|
|
slot_id: u32,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'ctx> From<ExtraScope<'ctx>> for Scope<'ctx, '_, '_> {
|
|
|
|
|
fn from(value: ExtraScope<'ctx>) -> Self {
|
|
|
|
|
use ExtraScope::*;
|
|
|
|
|
match value {
|
|
|
|
|
ScopedImport { keys, slot_id } => Scope::ScopedImport { keys, slot_id },
|
|
|
|
|
Repl(scope) => Scope::Repl(scope),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct ScopeGuard<'a, 'ctx, 'id, 'ir, R: VmRuntimeCtx> {
|
|
|
|
|
ctx: &'a mut DowngradeCtx<'ctx, 'id, 'ir, R>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a, 'ctx, 'id, 'ir, R: VmRuntimeCtx> Drop for ScopeGuard<'a, 'ctx, 'id, 'ir, R> {
|
|
|
|
|
fn drop(&mut self) {
|
|
|
|
|
self.ctx.scopes.pop();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a, 'ctx, 'id, 'ir, R: VmRuntimeCtx> ScopeGuard<'a, 'ctx, 'id, 'ir, R> {
|
|
|
|
|
fn as_ctx(&mut self) -> &mut DowngradeCtx<'ctx, 'id, 'ir, R> {
|
|
|
|
|
self.ctx
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct OwnedIr {
|
|
|
|
|
_bump: Bump,
|
|
|
|
|
ir: RawIrRef<'static>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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<'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) }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl DisassemblerContext for Evaluator {
|
|
|
|
|
fn get_code(&self) -> &[u8] {
|
|
|
|
|
&self.code.bytecode
|
|
|
|
|