Compare commits
8 Commits
e4c65d43ad
...
ee54ab8895
| Author | SHA1 | Date | |
|---|---|---|---|
|
ee54ab8895
|
|||
|
88dc8539b5
|
|||
|
0c517e3c18
|
|||
|
310f4acc89
|
|||
|
95eea517e4
|
|||
|
e78e62795b
|
|||
|
dc96e63b7c
|
|||
|
e82369695c
|
+15
-37
@@ -6,8 +6,8 @@ use num_enum::TryFromPrimitive;
|
||||
use rnix::TextRange;
|
||||
use string_interner::Symbol as _;
|
||||
|
||||
use crate::ir::{ArgId, Attr, BinOpKind, Ir, Param, RawIrRef, StringId, ThunkId, UnOpKind};
|
||||
use crate::runtime::{BUILTINS, BuiltinId};
|
||||
use crate::ir::{Attr, BinOpKind, Ir, Param, RawIrRef, StringId, ThunkId, UnOpKind};
|
||||
use crate::runtime::BUILTINS;
|
||||
use crate::runtime::value::{Null, PrimOp, StaticValue};
|
||||
|
||||
pub struct InstructionPtr(pub(crate) usize);
|
||||
@@ -28,7 +28,7 @@ pub(crate) trait BytecodeContext {
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, TryFromPrimitive)]
|
||||
#[derive(Debug, Clone, Copy, TryFromPrimitive)]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
pub enum Op {
|
||||
PushSmi,
|
||||
@@ -102,7 +102,6 @@ pub enum Op {
|
||||
|
||||
struct ScopeInfo {
|
||||
depth: u16,
|
||||
arg_id: Option<ArgId>,
|
||||
thunk_map: HashMap<ThunkId, u32>,
|
||||
}
|
||||
|
||||
@@ -161,10 +160,10 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
let (layer, local) = self.resolve_thunk(id);
|
||||
InlineOperand::Local { layer, local }
|
||||
}
|
||||
&Ir::Arg(id) => {
|
||||
let (layer, local) = self.resolve_arg(id);
|
||||
InlineOperand::Local { layer, local }
|
||||
}
|
||||
&Ir::Arg { layer } => InlineOperand::Local {
|
||||
layer: layer.try_into().expect("scope too deep!"),
|
||||
local: 0,
|
||||
},
|
||||
&Ir::Builtin(id) => {
|
||||
let arity = BUILTINS[id as usize].1;
|
||||
InlineOperand::Const(StaticValue::new_inline(PrimOp { id, arity }))
|
||||
@@ -286,16 +285,6 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
panic!("ThunkId {:?} not found in any scope", id);
|
||||
}
|
||||
|
||||
fn resolve_arg(&self, id: ArgId) -> (u16, u32) {
|
||||
for scope in self.scope_stack.iter().rev() {
|
||||
if scope.arg_id == Some(id) {
|
||||
let layer = self.current_depth() - scope.depth;
|
||||
return (layer, 0);
|
||||
}
|
||||
}
|
||||
panic!("ArgId {:?} not found in any scope", id);
|
||||
}
|
||||
|
||||
fn emit_load(&mut self, layer: u16, local: u32) {
|
||||
if layer == 0 {
|
||||
self.emit_op(Op::LoadLocal);
|
||||
@@ -433,7 +422,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
}
|
||||
}
|
||||
|
||||
fn push_scope(&mut self, has_arg: bool, arg_id: Option<ArgId>, thunk_ids: &[ThunkId]) {
|
||||
fn push_scope(&mut self, has_arg: bool, thunk_ids: &[ThunkId]) {
|
||||
let depth = self.scope_stack.len() as u16;
|
||||
let thunk_base = if has_arg { 1u32 } else { 0u32 };
|
||||
let thunk_map = thunk_ids
|
||||
@@ -441,11 +430,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
.enumerate()
|
||||
.map(|(i, &id)| (id, thunk_base + i as u32))
|
||||
.collect();
|
||||
self.scope_stack.push(ScopeInfo {
|
||||
depth,
|
||||
arg_id,
|
||||
thunk_map,
|
||||
});
|
||||
self.scope_stack.push(ScopeInfo { depth, thunk_map });
|
||||
}
|
||||
|
||||
fn pop_scope(&mut self) {
|
||||
@@ -461,7 +446,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
let all_thunks = self.collect_all_thunks(thunks, body);
|
||||
let thunk_ids: Vec<ThunkId> = all_thunks.iter().map(|&(id, _)| id).collect();
|
||||
|
||||
self.push_scope(false, None, &thunk_ids);
|
||||
self.push_scope(false, &thunk_ids);
|
||||
|
||||
if total_slots > 0 {
|
||||
self.emit_op(Op::AllocLocals);
|
||||
@@ -475,7 +460,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
self.pop_scope();
|
||||
}
|
||||
_ => {
|
||||
self.push_scope(false, None, &[]);
|
||||
self.push_scope(false, &[]);
|
||||
self.emit_expr(ir);
|
||||
self.emit_op(Op::Return);
|
||||
self.pop_scope();
|
||||
@@ -485,9 +470,6 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
|
||||
fn emit_scope_thunks(&mut self, thunks: &[(ThunkId, RawIrRef<'_>)]) {
|
||||
for &(id, inner) in thunks {
|
||||
let label = format!("e{}", id.0);
|
||||
let label_idx = self.ctx.intern_string(&label);
|
||||
|
||||
let skip_patch = self.emit_jump_placeholder();
|
||||
let entry_point = self.ctx.get_code_mut().len() as u32;
|
||||
self.emit_expr(inner);
|
||||
@@ -495,7 +477,6 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
self.patch_jump_target(skip_patch);
|
||||
self.emit_op(Op::MakeThunk);
|
||||
self.emit_u32(entry_point);
|
||||
self.emit_str_id(label_idx);
|
||||
let (_, local_idx) = self.resolve_thunk(id);
|
||||
self.emit_op(Op::StoreLocal);
|
||||
self.emit_u32(local_idx);
|
||||
@@ -566,10 +547,9 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
&Ir::Func {
|
||||
body,
|
||||
ref param,
|
||||
arg,
|
||||
ref thunks,
|
||||
} => {
|
||||
self.emit_func(arg, thunks, param, body);
|
||||
self.emit_func(thunks, param, body);
|
||||
}
|
||||
Ir::AttrSet { stcs, dyns } => {
|
||||
self.emit_attrset(stcs, dyns);
|
||||
@@ -593,9 +573,8 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
self.emit_op(Op::Call);
|
||||
self.emit_u32(span_id);
|
||||
}
|
||||
&Ir::Arg(id) => {
|
||||
let (layer, local) = self.resolve_arg(id);
|
||||
self.emit_load(layer, local);
|
||||
&Ir::Arg { layer } => {
|
||||
self.emit_load(layer.try_into().expect("scope too deep!"), 0);
|
||||
}
|
||||
&Ir::TopLevel { body, ref thunks } => {
|
||||
self.emit_toplevel_inner(body, thunks);
|
||||
@@ -777,7 +756,6 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
|
||||
fn emit_func(
|
||||
&mut self,
|
||||
arg: ArgId,
|
||||
thunks: &[(ThunkId, RawIrRef<'_>)],
|
||||
param: &Option<Param<'_>>,
|
||||
body: RawIrRef<'_>,
|
||||
@@ -790,7 +768,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
|
||||
let skip_patch = self.emit_jump_placeholder();
|
||||
let entry_point = self.ctx.get_code().len() as u32;
|
||||
self.push_scope(true, Some(arg), &thunk_ids);
|
||||
self.push_scope(true, &thunk_ids);
|
||||
self.emit_scope_thunks(thunks);
|
||||
self.emit_expr(body);
|
||||
self.emit_op(Op::Return);
|
||||
|
||||
+12
-6
@@ -140,15 +140,23 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
||||
temp
|
||||
};
|
||||
|
||||
let extra_width = if start_pos > 0 { start_pos.ilog2() >> 4 } else { 0 };
|
||||
let extra_width = if start_pos > 0 {
|
||||
start_pos.ilog2() >> 4
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
if color {
|
||||
let _ = write!(out, " ");
|
||||
for _ in 0..extra_width { let _ = write!(out, " "); }
|
||||
for _ in 0..extra_width {
|
||||
let _ = write!(out, " ");
|
||||
}
|
||||
let _ = writeln!(out, " {:<14} |", bytes_str.green());
|
||||
} else {
|
||||
let _ = write!(out, " ");
|
||||
for _ in 0..extra_width { let _ = write!(out, " "); }
|
||||
for _ in 0..extra_width {
|
||||
let _ = write!(out, " ");
|
||||
}
|
||||
let _ = writeln!(out, " {:<14} |", bytes_str);
|
||||
}
|
||||
}
|
||||
@@ -208,9 +216,7 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
||||
|
||||
Op::MakeThunk => {
|
||||
let offset = self.read_u32();
|
||||
let label_idx = self.read_u32();
|
||||
let label = self.ctx.lookup_string(label_idx);
|
||||
("MakeThunk", format!("-> {:04x} label={}", offset, label))
|
||||
("MakeThunk", format!("-> {:04x}", offset))
|
||||
}
|
||||
Op::MakeClosure => {
|
||||
let offset = self.read_u32();
|
||||
|
||||
+8
-15
@@ -48,7 +48,6 @@ impl<T: Sized> BoxIn for T {}
|
||||
|
||||
pub trait DowngradeContext<'id: 'ir, 'ir> {
|
||||
fn new_expr(&self, expr: Ir<'ir, IrRef<'id, 'ir>>) -> IrRef<'id, 'ir>;
|
||||
fn new_arg(&mut self) -> ArgId;
|
||||
fn maybe_thunk(&mut self, ir: IrRef<'id, 'ir>) -> IrRef<'id, 'ir>;
|
||||
|
||||
fn new_sym(&mut self, sym: String) -> StringId;
|
||||
@@ -57,7 +56,7 @@ pub trait DowngradeContext<'id: 'ir, 'ir> {
|
||||
|
||||
fn get_current_source(&self) -> Source;
|
||||
|
||||
fn with_param_scope<F, R>(&mut self, param: StringId, arg: ArgId, f: F) -> R
|
||||
fn with_param_scope<F, R>(&mut self, param: StringId, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut Self) -> R;
|
||||
fn with_let_scope<F, R>(&mut self, bindings: &[StringId], f: F) -> Result<R>
|
||||
@@ -195,9 +194,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
||||
&& matches!(normalized.first(), Some(ast::InterpolPart::Literal(_)));
|
||||
|
||||
let bump = ctx.bump();
|
||||
let parts = normalized
|
||||
.into_iter()
|
||||
.map(|part| match part {
|
||||
let mut parts = normalized.into_iter().map(|part| match part {
|
||||
ast::InterpolPart::Literal(lit) => Ok(ctx.new_expr(Ir::Str(lit.box_in(bump)))),
|
||||
ast::InterpolPart::Interpolation(interpol) => {
|
||||
let inner = interpol
|
||||
@@ -206,12 +203,12 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
||||
.downgrade(ctx)?;
|
||||
Ok(ctx.maybe_thunk(inner))
|
||||
}
|
||||
})
|
||||
.collect_in::<Result<Vec<'ir, _>>>(bump)?;
|
||||
});
|
||||
|
||||
Ok(if is_single_literal {
|
||||
parts.into_iter().next().expect("is_single_literal checked")
|
||||
parts.next().expect("is_single_literal checked")?
|
||||
} else {
|
||||
let parts = parts.collect_in::<Result<_>>(bump)?;
|
||||
ctx.new_expr(Ir::ConcatStrings {
|
||||
parts,
|
||||
force_string: true,
|
||||
@@ -382,7 +379,6 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
||||
let span = self.syntax().text_range();
|
||||
let raw_param = self.param().require(ctx, span)?;
|
||||
let body_ast = self.body().require(ctx, span)?;
|
||||
let arg = ctx.new_arg();
|
||||
|
||||
struct Ret<'id, 'ir> {
|
||||
param: Option<Param<'ir>>,
|
||||
@@ -398,8 +394,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
||||
let param_sym = ctx.new_sym(id.to_string());
|
||||
param = None;
|
||||
|
||||
body = ctx
|
||||
.with_param_scope(param_sym, arg, |ctx| body_ast.clone().downgrade(ctx))?;
|
||||
body = ctx.with_param_scope(param_sym, |ctx| body_ast.downgrade(ctx))?;
|
||||
}
|
||||
ast::Param::Pattern(pattern) => {
|
||||
let alias = pattern
|
||||
@@ -417,7 +412,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
||||
body: inner_body,
|
||||
required,
|
||||
optional,
|
||||
} = downgrade_pattern_bindings(pat_entries, alias, arg, ctx, |ctx, _| {
|
||||
} = downgrade_pattern_bindings(pat_entries, alias, ctx, |ctx, _| {
|
||||
body_ast.clone().downgrade(ctx)
|
||||
})?;
|
||||
|
||||
@@ -438,7 +433,6 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
||||
Ok(ctx.new_expr(Ir::Func {
|
||||
body,
|
||||
param,
|
||||
arg,
|
||||
thunks,
|
||||
}))
|
||||
}
|
||||
@@ -938,14 +932,12 @@ struct PatternBindings<'id, 'ir> {
|
||||
fn downgrade_pattern_bindings<'id, 'ir, Ctx>(
|
||||
pat_entries: impl Iterator<Item = ast::PatEntry>,
|
||||
alias: Option<StringId>,
|
||||
arg: ArgId,
|
||||
ctx: &mut Ctx,
|
||||
body_fn: impl FnOnce(&mut Ctx, &[StringId]) -> Result<IrRef<'id, 'ir>>,
|
||||
) -> Result<PatternBindings<'id, 'ir>>
|
||||
where
|
||||
Ctx: DowngradeContext<'id, 'ir>,
|
||||
{
|
||||
let arg = ctx.new_expr(Ir::Arg(arg));
|
||||
struct Param {
|
||||
sym: StringId,
|
||||
sym_span: TextRange,
|
||||
@@ -999,6 +991,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
let arg = ctx.new_expr(Ir::Arg { layer: 0 });
|
||||
ctx.with_let_scope(&keys, |ctx| {
|
||||
let vals = params
|
||||
.into_iter()
|
||||
|
||||
+4
-437
@@ -1,4 +1,4 @@
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::hash::Hash;
|
||||
use std::ops::Deref;
|
||||
|
||||
use bumpalo::Bump;
|
||||
@@ -126,10 +126,11 @@ pub enum Ir<'ir, Ref> {
|
||||
Func {
|
||||
body: Ref,
|
||||
param: Option<Param<'ir>>,
|
||||
arg: ArgId,
|
||||
thunks: Vec<'ir, (ThunkId, Ref)>,
|
||||
},
|
||||
Arg(ArgId),
|
||||
Arg {
|
||||
layer: usize,
|
||||
},
|
||||
Call {
|
||||
func: Ref,
|
||||
arg: Ref,
|
||||
@@ -161,10 +162,6 @@ pub struct ThunkId(pub usize);
|
||||
#[collect(require_static)]
|
||||
pub struct StringId(pub SymbolU32);
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct ArgId(pub u32);
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct SpanId(pub u32);
|
||||
@@ -261,433 +258,3 @@ pub struct Param<'ir> {
|
||||
pub optional: Vec<'ir, (StringId, TextRange)>,
|
||||
pub ellipsis: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct IrKey<'id, 'ir, 'a>(pub IrRef<'id, 'ir>, pub &'a GhostToken<'id>);
|
||||
|
||||
impl std::hash::Hash for IrKey<'_, '_, '_> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
ir_content_hash(self.0, self.1, state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for IrKey<'_, '_, '_> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
ir_content_eq(self.0, other.0, self.1)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for IrKey<'_, '_, '_> {}
|
||||
|
||||
fn attr_content_hash<'id>(
|
||||
attr: &Attr<IrRef<'id, '_>>,
|
||||
token: &GhostToken<'id>,
|
||||
state: &mut impl Hasher,
|
||||
) {
|
||||
core::mem::discriminant(attr).hash(state);
|
||||
match attr {
|
||||
Attr::Dynamic(expr, _) => ir_content_hash(*expr, token, state),
|
||||
Attr::Str(sym, _) => sym.hash(state),
|
||||
}
|
||||
}
|
||||
|
||||
fn attr_content_eq<'id, 'ir>(
|
||||
a: &Attr<IrRef<'id, 'ir>>,
|
||||
b: &Attr<IrRef<'id, 'ir>>,
|
||||
token: &GhostToken<'id>,
|
||||
) -> bool {
|
||||
match (a, b) {
|
||||
(Attr::Dynamic(ae, _), Attr::Dynamic(be, _)) => ir_content_eq(*ae, *be, token),
|
||||
(Attr::Str(a, _), Attr::Str(b, _)) => a == b,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn param_content_hash(param: &Param<'_>, state: &mut impl Hasher) {
|
||||
param.required.len().hash(state);
|
||||
for (sym, _) in param.required.iter() {
|
||||
sym.hash(state);
|
||||
}
|
||||
param.optional.len().hash(state);
|
||||
for (sym, _) in param.optional.iter() {
|
||||
sym.hash(state);
|
||||
}
|
||||
param.ellipsis.hash(state);
|
||||
}
|
||||
|
||||
fn param_content_eq(a: &Param<'_>, b: &Param<'_>) -> bool {
|
||||
a.ellipsis == b.ellipsis
|
||||
&& a.required.len() == b.required.len()
|
||||
&& a.optional.len() == b.optional.len()
|
||||
&& a.required
|
||||
.iter()
|
||||
.zip(b.required.iter())
|
||||
.all(|((a, _), (b, _))| a == b)
|
||||
&& a.optional
|
||||
.iter()
|
||||
.zip(b.optional.iter())
|
||||
.all(|((a, _), (b, _))| a == b)
|
||||
}
|
||||
|
||||
fn thunks_content_hash<'id>(
|
||||
thunks: &[(ThunkId, IrRef<'id, '_>)],
|
||||
token: &GhostToken<'id>,
|
||||
state: &mut impl Hasher,
|
||||
) {
|
||||
thunks.len().hash(state);
|
||||
for &(id, ir) in thunks {
|
||||
id.hash(state);
|
||||
ir_content_hash(ir, token, state);
|
||||
}
|
||||
}
|
||||
|
||||
fn thunks_content_eq<'id, 'ir>(
|
||||
a: &[(ThunkId, IrRef<'id, 'ir>)],
|
||||
b: &[(ThunkId, IrRef<'id, 'ir>)],
|
||||
token: &GhostToken<'id>,
|
||||
) -> bool {
|
||||
a.len() == b.len()
|
||||
&& a.iter()
|
||||
.zip(b.iter())
|
||||
.all(|(&(ai, ae), &(bi, be))| ai == bi && ir_content_eq(ae, be, token))
|
||||
}
|
||||
|
||||
fn ir_content_hash<'id>(ir: IrRef<'id, '_>, token: &GhostToken<'id>, state: &mut impl Hasher) {
|
||||
let ir = ir.borrow(token);
|
||||
core::mem::discriminant(ir).hash(state);
|
||||
match ir {
|
||||
Ir::Int(x) => x.hash(state),
|
||||
Ir::Float(x) => x.to_bits().hash(state),
|
||||
Ir::Bool(x) => x.hash(state),
|
||||
Ir::Null => {}
|
||||
Ir::Str(x) => x.hash(state),
|
||||
Ir::AttrSet { stcs, dyns } => {
|
||||
stcs.len().hash(state);
|
||||
let mut combined: u64 = 0;
|
||||
for (&key, &(val, _)) in stcs.iter() {
|
||||
let mut h = std::hash::DefaultHasher::new();
|
||||
key.hash(&mut h);
|
||||
ir_content_hash(val, token, &mut h);
|
||||
combined = combined.wrapping_add(h.finish());
|
||||
}
|
||||
combined.hash(state);
|
||||
dyns.len().hash(state);
|
||||
for &(k, v, _) in dyns.iter() {
|
||||
ir_content_hash(k, token, state);
|
||||
ir_content_hash(v, token, state);
|
||||
}
|
||||
}
|
||||
Ir::List { items } => {
|
||||
items.len().hash(state);
|
||||
for &item in items.iter() {
|
||||
ir_content_hash(item, token, state);
|
||||
}
|
||||
}
|
||||
Ir::HasAttr { lhs, rhs } => {
|
||||
ir_content_hash(*lhs, token, state);
|
||||
rhs.len().hash(state);
|
||||
for attr in rhs.iter() {
|
||||
attr_content_hash(attr, token, state);
|
||||
}
|
||||
}
|
||||
&Ir::BinOp { lhs, rhs, kind } => {
|
||||
ir_content_hash(lhs, token, state);
|
||||
ir_content_hash(rhs, token, state);
|
||||
kind.hash(state);
|
||||
}
|
||||
&Ir::UnOp { rhs, kind } => {
|
||||
ir_content_hash(rhs, token, state);
|
||||
kind.hash(state);
|
||||
}
|
||||
Ir::Select {
|
||||
expr,
|
||||
attrpath,
|
||||
default,
|
||||
..
|
||||
} => {
|
||||
ir_content_hash(*expr, token, state);
|
||||
attrpath.len().hash(state);
|
||||
for attr in attrpath.iter() {
|
||||
attr_content_hash(attr, token, state);
|
||||
}
|
||||
default.is_some().hash(state);
|
||||
if let Some(d) = default {
|
||||
ir_content_hash(*d, token, state);
|
||||
}
|
||||
}
|
||||
&Ir::If { cond, consq, alter } => {
|
||||
ir_content_hash(cond, token, state);
|
||||
ir_content_hash(consq, token, state);
|
||||
ir_content_hash(alter, token, state);
|
||||
}
|
||||
&Ir::Call { func, arg, .. } => {
|
||||
ir_content_hash(func, token, state);
|
||||
ir_content_hash(arg, token, state);
|
||||
}
|
||||
Ir::Assert {
|
||||
assertion,
|
||||
expr,
|
||||
assertion_raw,
|
||||
..
|
||||
} => {
|
||||
ir_content_hash(*assertion, token, state);
|
||||
ir_content_hash(*expr, token, state);
|
||||
assertion_raw.hash(state);
|
||||
}
|
||||
Ir::ConcatStrings {
|
||||
force_string,
|
||||
parts,
|
||||
} => {
|
||||
force_string.hash(state);
|
||||
parts.len().hash(state);
|
||||
for &part in parts.iter() {
|
||||
ir_content_hash(part, token, state);
|
||||
}
|
||||
}
|
||||
&Ir::Path(expr) => ir_content_hash(expr, token, state),
|
||||
Ir::Func {
|
||||
body,
|
||||
arg,
|
||||
param,
|
||||
thunks,
|
||||
} => {
|
||||
ir_content_hash(*body, token, state);
|
||||
arg.hash(state);
|
||||
param.is_some().hash(state);
|
||||
if let Some(p) = param {
|
||||
param_content_hash(p, state);
|
||||
}
|
||||
thunks_content_hash(thunks, token, state);
|
||||
}
|
||||
Ir::TopLevel { body, thunks } => {
|
||||
ir_content_hash(*body, token, state);
|
||||
thunks_content_hash(thunks, token, state);
|
||||
}
|
||||
Ir::Arg(x) => x.hash(state),
|
||||
Ir::Thunk(x) => x.hash(state),
|
||||
Ir::Builtins => {}
|
||||
Ir::Builtin(x) => x.hash(state),
|
||||
Ir::BuiltinConst(x) => x.hash(state),
|
||||
Ir::CurPos(x) => x.hash(state),
|
||||
Ir::ReplBinding(x) => x.hash(state),
|
||||
Ir::ScopedImportBinding(x) => x.hash(state),
|
||||
&Ir::With {
|
||||
namespace,
|
||||
body,
|
||||
ref thunks,
|
||||
} => {
|
||||
ir_content_hash(namespace, token, state);
|
||||
ir_content_hash(body, token, state);
|
||||
thunks_content_hash(thunks, token, state);
|
||||
}
|
||||
Ir::WithLookup(x) => x.hash(state),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn ir_content_eq<'id, 'ir>(
|
||||
a: IrRef<'id, 'ir>,
|
||||
b: IrRef<'id, 'ir>,
|
||||
token: &GhostToken<'id>,
|
||||
) -> bool {
|
||||
std::ptr::eq(a.0, b.0)
|
||||
|| match (a.borrow(token), b.borrow(token)) {
|
||||
(Ir::Int(a), Ir::Int(b)) => a == b,
|
||||
(Ir::Float(a), Ir::Float(b)) => a.to_bits() == b.to_bits(),
|
||||
(Ir::Bool(a), Ir::Bool(b)) => a == b,
|
||||
(Ir::Null, Ir::Null) => true,
|
||||
(Ir::Str(a), Ir::Str(b)) => **a == **b,
|
||||
(
|
||||
Ir::AttrSet {
|
||||
stcs: a_stcs,
|
||||
dyns: a_dyns,
|
||||
},
|
||||
Ir::AttrSet {
|
||||
stcs: b_stcs,
|
||||
dyns: b_dyns,
|
||||
},
|
||||
) => {
|
||||
a_stcs.len() == b_stcs.len()
|
||||
&& a_dyns.len() == b_dyns.len()
|
||||
&& a_stcs.iter().all(|(&k, &(av, _))| {
|
||||
b_stcs
|
||||
.get(&k)
|
||||
.is_some_and(|&(bv, _)| ir_content_eq(av, bv, token))
|
||||
})
|
||||
&& a_dyns
|
||||
.iter()
|
||||
.zip(b_dyns.iter())
|
||||
.all(|(&(ak, av, _), &(bk, bv, _))| {
|
||||
ir_content_eq(ak, bk, token) && ir_content_eq(av, bv, token)
|
||||
})
|
||||
}
|
||||
(Ir::List { items: a }, Ir::List { items: b }) => {
|
||||
a.len() == b.len()
|
||||
&& a.iter()
|
||||
.zip(b.iter())
|
||||
.all(|(&a, &b)| ir_content_eq(a, b, token))
|
||||
}
|
||||
(Ir::HasAttr { lhs: al, rhs: ar }, Ir::HasAttr { lhs: bl, rhs: br }) => {
|
||||
ir_content_eq(*al, *bl, token)
|
||||
&& ar.len() == br.len()
|
||||
&& ar
|
||||
.iter()
|
||||
.zip(br.iter())
|
||||
.all(|(a, b)| attr_content_eq(a, b, token))
|
||||
}
|
||||
(
|
||||
&Ir::BinOp {
|
||||
lhs: al,
|
||||
rhs: ar,
|
||||
kind: ak,
|
||||
},
|
||||
&Ir::BinOp {
|
||||
lhs: bl,
|
||||
rhs: br,
|
||||
kind: bk,
|
||||
},
|
||||
) => ak == bk && ir_content_eq(al, bl, token) && ir_content_eq(ar, br, token),
|
||||
(&Ir::UnOp { rhs: ar, kind: ak }, &Ir::UnOp { rhs: br, kind: bk }) => {
|
||||
ak == bk && ir_content_eq(ar, br, token)
|
||||
}
|
||||
(
|
||||
Ir::Select {
|
||||
expr: ae,
|
||||
attrpath: aa,
|
||||
default: ad,
|
||||
..
|
||||
},
|
||||
Ir::Select {
|
||||
expr: be,
|
||||
attrpath: ba,
|
||||
default: bd,
|
||||
..
|
||||
},
|
||||
) => {
|
||||
ir_content_eq(*ae, *be, token)
|
||||
&& aa.len() == ba.len()
|
||||
&& aa
|
||||
.iter()
|
||||
.zip(ba.iter())
|
||||
.all(|(a, b)| attr_content_eq(a, b, token))
|
||||
&& match (ad, bd) {
|
||||
(Some(a), Some(b)) => ir_content_eq(*a, *b, token),
|
||||
(None, None) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
(
|
||||
&Ir::If {
|
||||
cond: ac,
|
||||
consq: acs,
|
||||
alter: aa,
|
||||
},
|
||||
&Ir::If {
|
||||
cond: bc,
|
||||
consq: bcs,
|
||||
alter: ba,
|
||||
},
|
||||
) => {
|
||||
ir_content_eq(ac, bc, token)
|
||||
&& ir_content_eq(acs, bcs, token)
|
||||
&& ir_content_eq(aa, ba, token)
|
||||
}
|
||||
(
|
||||
&Ir::Call {
|
||||
func: af, arg: aa, ..
|
||||
},
|
||||
&Ir::Call {
|
||||
func: bf, arg: ba, ..
|
||||
},
|
||||
) => ir_content_eq(af, bf, token) && ir_content_eq(aa, ba, token),
|
||||
(
|
||||
Ir::Assert {
|
||||
assertion: aa,
|
||||
expr: ae,
|
||||
assertion_raw: ar,
|
||||
..
|
||||
},
|
||||
Ir::Assert {
|
||||
assertion: ba,
|
||||
expr: be,
|
||||
assertion_raw: br,
|
||||
..
|
||||
},
|
||||
) => ar == br && ir_content_eq(*aa, *ba, token) && ir_content_eq(*ae, *be, token),
|
||||
(
|
||||
Ir::ConcatStrings {
|
||||
force_string: af,
|
||||
parts: ap,
|
||||
},
|
||||
Ir::ConcatStrings {
|
||||
force_string: bf,
|
||||
parts: bp,
|
||||
},
|
||||
) => {
|
||||
af == bf
|
||||
&& ap.len() == bp.len()
|
||||
&& ap
|
||||
.iter()
|
||||
.zip(bp.iter())
|
||||
.all(|(&a, &b)| ir_content_eq(a, b, token))
|
||||
}
|
||||
(&Ir::Path(a), &Ir::Path(b)) => ir_content_eq(a, b, token),
|
||||
(
|
||||
Ir::Func {
|
||||
body: ab,
|
||||
arg: aa,
|
||||
param: ap,
|
||||
thunks: at,
|
||||
},
|
||||
Ir::Func {
|
||||
body: bb,
|
||||
arg: ba,
|
||||
param: bp,
|
||||
thunks: bt,
|
||||
},
|
||||
) => {
|
||||
ir_content_eq(*ab, *bb, token)
|
||||
&& aa == ba
|
||||
&& match (ap, bp) {
|
||||
(Some(a), Some(b)) => param_content_eq(a, b),
|
||||
(None, None) => true,
|
||||
_ => false,
|
||||
}
|
||||
&& thunks_content_eq(at, bt, token)
|
||||
}
|
||||
(
|
||||
Ir::TopLevel {
|
||||
body: ab,
|
||||
thunks: at,
|
||||
},
|
||||
Ir::TopLevel {
|
||||
body: bb,
|
||||
thunks: bt,
|
||||
},
|
||||
) => ir_content_eq(*ab, *bb, token) && thunks_content_eq(at, bt, token),
|
||||
(Ir::Arg(a), Ir::Arg(b)) => a == b,
|
||||
(Ir::Thunk(a), Ir::Thunk(b)) => a == b,
|
||||
(Ir::Builtins, Ir::Builtins) => true,
|
||||
(Ir::Builtin(a), Ir::Builtin(b)) => a == b,
|
||||
(Ir::CurPos(a), Ir::CurPos(b)) => a == b,
|
||||
(Ir::ReplBinding(a), Ir::ReplBinding(b)) => a == b,
|
||||
(Ir::ScopedImportBinding(a), Ir::ScopedImportBinding(b)) => a == b,
|
||||
(
|
||||
Ir::With {
|
||||
namespace: a_ns,
|
||||
body: a_body,
|
||||
thunks: a_thunks,
|
||||
},
|
||||
Ir::With {
|
||||
namespace: b_ns,
|
||||
body: b_body,
|
||||
thunks: b_thunks,
|
||||
},
|
||||
) => {
|
||||
ir_content_eq(*a_ns, *b_ns, token)
|
||||
&& ir_content_eq(*a_body, *b_body, token)
|
||||
&& thunks_content_eq(a_thunks, b_thunks, token)
|
||||
}
|
||||
(Ir::WithLookup(a), Ir::WithLookup(b)) => a == b,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
+18
-40
@@ -1,9 +1,7 @@
|
||||
use std::hash::BuildHasher;
|
||||
|
||||
use bumpalo::Bump;
|
||||
use gc_arena::{Arena, Rootable};
|
||||
use ghost_cell::{GhostCell, GhostToken};
|
||||
use hashbrown::{DefaultHashBuilder, HashMap, HashSet, HashTable};
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use rnix::TextRange;
|
||||
use string_interner::symbol::SymbolU32;
|
||||
use string_interner::{DefaultStringInterner, Symbol as _};
|
||||
@@ -12,7 +10,7 @@ use crate::codegen::{BytecodeContext, InstructionPtr};
|
||||
use crate::disassembler::{Disassembler, DisassemblerContext};
|
||||
use crate::downgrade::{Downgrade as _, DowngradeContext};
|
||||
use crate::error::{Error, Result, Source};
|
||||
use crate::ir::{ArgId, Ir, IrKey, IrRef, RawIrRef, StringId, ThunkId, ir_content_eq};
|
||||
use crate::ir::{Ir, IrRef, RawIrRef, StringId, ThunkId};
|
||||
use crate::runtime::builtins::init_builtins;
|
||||
use crate::runtime::value::StaticValue;
|
||||
use crate::runtime::vm::{ForceMode, new_gc_root};
|
||||
@@ -47,6 +45,7 @@ pub struct Runtime {
|
||||
fuel: usize,
|
||||
error_contexts: Stack<8192, ErrorFrame>,
|
||||
arena: Arena<Rootable![GcRoot<'_>]>,
|
||||
force_mode: ForceMode,
|
||||
}
|
||||
|
||||
impl Runtime {
|
||||
@@ -81,6 +80,7 @@ impl Runtime {
|
||||
fuel: Self::DEFAULT_FUEL_AMOUNT,
|
||||
error_contexts: Stack::new(),
|
||||
arena,
|
||||
force_mode: ForceMode::default(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -280,26 +280,11 @@ impl<'ctx: 'ir, 'id, 'ir> DowngradeContext<'id, 'ir> for DowngradeCtx<'ctx, 'id,
|
||||
IrRef::new(self.bump.alloc(GhostCell::new(expr)))
|
||||
}
|
||||
|
||||
fn new_arg(&mut self) -> ArgId {
|
||||
self.arg_count += 1;
|
||||
ArgId(self.arg_count - 1)
|
||||
}
|
||||
|
||||
fn maybe_thunk(&mut self, ir: IrRef<'id, 'ir>) -> IrRef<'id, 'ir> {
|
||||
if !should_thunk(ir, &self.token) {
|
||||
return ir;
|
||||
}
|
||||
|
||||
let cached = self
|
||||
.thunk_scopes
|
||||
.last()
|
||||
.expect("no active cache scope")
|
||||
.lookup_cache(ir, &self.token);
|
||||
|
||||
if let Some(id) = cached {
|
||||
return IrRef::alloc(self.bump, Ir::Thunk(id));
|
||||
}
|
||||
|
||||
let id = ThunkId(*self.thunk_count);
|
||||
*self.thunk_count = self.thunk_count.checked_add(1).expect("thunk id overflow");
|
||||
self.thunk_scopes
|
||||
@@ -347,9 +332,14 @@ impl<'ctx: 'ir, 'id, 'ir> DowngradeContext<'id, 'ir> for DowngradeCtx<'ctx, 'id,
|
||||
return Ok(self.new_expr(Ir::Thunk(expr)));
|
||||
}
|
||||
}
|
||||
&Scope::Param(param_sym, id) => {
|
||||
&Scope::Param {
|
||||
sym: param_sym,
|
||||
abs_layer,
|
||||
} => {
|
||||
if param_sym == sym {
|
||||
return Ok(self.new_expr(Ir::Arg(id)));
|
||||
return Ok(self.new_expr(Ir::Arg {
|
||||
layer: self.thunk_scopes.len() - abs_layer,
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -396,11 +386,14 @@ impl<'ctx: 'ir, 'id, 'ir> DowngradeContext<'id, 'ir> for DowngradeCtx<'ctx, 'id,
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
fn with_param_scope<F, R>(&mut self, param: StringId, arg: ArgId, f: F) -> R
|
||||
fn with_param_scope<F, R>(&mut self, sym: StringId, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut Self) -> R,
|
||||
{
|
||||
self.scopes.push(Scope::Param(param, arg));
|
||||
self.scopes.push(Scope::Param {
|
||||
sym,
|
||||
abs_layer: self.thunk_scopes.len(),
|
||||
});
|
||||
let mut guard = ScopeGuard { ctx: self };
|
||||
f(guard.as_ctx())
|
||||
}
|
||||
@@ -456,32 +449,17 @@ impl<'id, 'ir, 'ctx: 'ir> DowngradeCtx<'ctx, 'id, 'ir> {
|
||||
|
||||
struct ThunkScope<'id, 'ir> {
|
||||
bindings: bumpalo::collections::Vec<'ir, (ThunkId, IrRef<'id, 'ir>)>,
|
||||
cache: HashTable<(IrRef<'id, 'ir>, ThunkId)>,
|
||||
hasher: DefaultHashBuilder,
|
||||
}
|
||||
|
||||
impl<'id, 'ir> ThunkScope<'id, 'ir> {
|
||||
fn new_in(bump: &'ir Bump) -> Self {
|
||||
Self {
|
||||
bindings: bumpalo::collections::Vec::new_in(bump),
|
||||
cache: HashTable::new(),
|
||||
hasher: DefaultHashBuilder::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn lookup_cache(&self, key: IrRef<'id, 'ir>, token: &GhostToken<'id>) -> Option<ThunkId> {
|
||||
let hash = self.hasher.hash_one(IrKey(key, token));
|
||||
self.cache
|
||||
.find(hash, |&(ir, _)| ir_content_eq(key, ir, token))
|
||||
.map(|&(_, id)| id)
|
||||
}
|
||||
|
||||
fn add_binding(&mut self, id: ThunkId, ir: IrRef<'id, 'ir>, token: &GhostToken<'id>) {
|
||||
fn add_binding(&mut self, id: ThunkId, ir: IrRef<'id, 'ir>, _token: &GhostToken<'id>) {
|
||||
self.bindings.push((id, ir));
|
||||
let hash = self.hasher.hash_one(IrKey(ir, token));
|
||||
self.cache.insert_unique(hash, (ir, id), |&(ir, _)| {
|
||||
self.hasher.hash_one(IrKey(ir, token))
|
||||
});
|
||||
}
|
||||
|
||||
fn extend_bindings(&mut self, iter: impl IntoIterator<Item = (ThunkId, IrRef<'id, 'ir>)>) {
|
||||
@@ -494,7 +472,7 @@ enum Scope<'ctx> {
|
||||
Repl(&'ctx HashSet<StringId>),
|
||||
ScopedImport(HashSet<StringId>),
|
||||
Let(HashMap<StringId, ThunkId>),
|
||||
Param(StringId, ArgId),
|
||||
Param { sym: StringId, abs_layer: usize },
|
||||
}
|
||||
|
||||
struct ScopeGuard<'a, 'ctx, 'id, 'ir> {
|
||||
|
||||
@@ -478,6 +478,12 @@ impl<'gc> AttrSet<'gc> {
|
||||
pub(crate) struct List<'gc> {
|
||||
pub(crate) inner: SmallVec<[Value<'gc>; 4]>,
|
||||
}
|
||||
impl<'gc> Deref for List<'gc> {
|
||||
type Target = SmallVec<[Value<'gc>; 4]>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type Thunk<'gc> = RefLock<ThunkState<'gc>>;
|
||||
|
||||
|
||||
+593
-242
@@ -8,7 +8,7 @@ use smallvec::SmallVec;
|
||||
use string_interner::{DefaultStringInterner, Symbol as _};
|
||||
|
||||
use super::Runtime;
|
||||
use super::builtins::{BUILTINS, BuiltinId, PrimOpArgs, PrimOpStrictArgs};
|
||||
use super::builtins::{BUILTINS, BuiltinId};
|
||||
use super::stack::Stack;
|
||||
use super::value::*;
|
||||
use crate::codegen::{
|
||||
@@ -32,9 +32,10 @@ impl From<Box<Error>> for VmError {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Collect, Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[derive(Collect, Clone, Copy, Debug, PartialEq, Eq, Default)]
|
||||
#[collect(require_static)]
|
||||
pub(super) enum ForceMode {
|
||||
#[default]
|
||||
AsIs,
|
||||
Shallow,
|
||||
Deep,
|
||||
@@ -77,79 +78,6 @@ pub(super) fn new_gc_root<'gc>(
|
||||
}
|
||||
|
||||
impl<'gc> GcRoot<'gc> {
|
||||
fn init_builtins(
|
||||
mc: &Mutation<'gc>,
|
||||
strings: &mut DefaultStringInterner,
|
||||
) -> (Value<'gc>, HashMap<StringId, PrimOp>) {
|
||||
let mut builtin_lookup = HashMap::new();
|
||||
let mut entries = SmallVec::new();
|
||||
|
||||
for (id, &(name, arity)) in BUILTINS.iter().enumerate() {
|
||||
let Some(sym) = strings.get(name) else {
|
||||
continue;
|
||||
};
|
||||
let sid = StringId(sym);
|
||||
let primop = PrimOp {
|
||||
id: BuiltinId::try_from_primitive(id as u8).expect("invalid BuiltinId??"),
|
||||
arity,
|
||||
};
|
||||
builtin_lookup.insert(sid, primop);
|
||||
|
||||
// Regular primop
|
||||
if arity != 0 {
|
||||
entries.push((sid, Value::new_inline(primop)));
|
||||
}
|
||||
}
|
||||
|
||||
// Add constant entries
|
||||
macro_rules! add_const {
|
||||
($name:expr, $val:expr) => {{
|
||||
let sym = strings.get_or_intern($name);
|
||||
entries.push((StringId(sym), $val));
|
||||
}};
|
||||
}
|
||||
add_const!(
|
||||
"currentSystem",
|
||||
Value::new_gc(Gc::new(mc, NixString::new("x86_64-linux")))
|
||||
);
|
||||
add_const!("langVersion", Value::new_inline(6i32));
|
||||
add_const!(
|
||||
"nixVersion",
|
||||
Value::new_gc(Gc::new(mc, NixString::new("2.24.0")))
|
||||
);
|
||||
add_const!(
|
||||
"storeDir",
|
||||
Value::new_gc(Gc::new(mc, NixString::new("/nix/store")))
|
||||
);
|
||||
add_const!(
|
||||
"nixPath",
|
||||
Value::new_gc(Gc::new(
|
||||
mc,
|
||||
List {
|
||||
inner: SmallVec::new()
|
||||
}
|
||||
))
|
||||
);
|
||||
add_const!("null", Value::new_inline(Null));
|
||||
add_const!("true", Value::new_inline(true));
|
||||
add_const!("false", Value::new_inline(false));
|
||||
|
||||
// Self-reference thunk for builtins.builtins
|
||||
let self_ref_thunk: Gc<'gc, Thunk<'gc>> = Gc::new(mc, RefLock::new(ThunkState::Blackhole));
|
||||
let sym = strings.get_or_intern("builtins");
|
||||
entries.push((StringId(sym), Value::new_gc(self_ref_thunk)));
|
||||
|
||||
entries.sort_by_key(|(k, _)| *k);
|
||||
|
||||
let builtins_set = Gc::new(mc, unsafe { AttrSet::from_sorted_unchecked(entries) });
|
||||
let builtins_val = Value::new_gc(builtins_set);
|
||||
|
||||
// Populate the self-reference
|
||||
*self_ref_thunk.borrow_mut(mc) = ThunkState::Evaluated(builtins_val);
|
||||
|
||||
(builtins_val, builtin_lookup)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn env(&self) -> Gc<'gc, RefLock<Env<'gc>>> {
|
||||
self.current_env.expect("no current env")
|
||||
@@ -166,8 +94,13 @@ impl<'gc> GcRoot<'gc> {
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[track_caller]
|
||||
pub(super) fn pop_stack_forced(&mut self) -> StrictValue<'gc> {
|
||||
self.stack.pop().expect("stack underflow").restrict().expect("forced")
|
||||
self.stack
|
||||
.pop()
|
||||
.expect("stack underflow")
|
||||
.restrict()
|
||||
.expect("forced")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,7 +201,7 @@ enum SelectKeyData {
|
||||
}
|
||||
|
||||
macro_rules! try_vm {
|
||||
($self:ident, $expr:expr) => {
|
||||
($self:ident; $expr:expr) => {
|
||||
match $expr {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Runtime::handle_vm_error($self, e),
|
||||
@@ -395,12 +328,12 @@ impl Runtime {
|
||||
LoadLocal => {
|
||||
let idx = self.read_u32() as usize;
|
||||
self.arena
|
||||
.mutate_root(|mc, root| root.push_stack(root.env().borrow().locals[idx]))
|
||||
.mutate_root(|_, root| root.push_stack(root.env().borrow().locals[idx]))
|
||||
}
|
||||
LoadOuter => {
|
||||
let layer = self.read_u8();
|
||||
let idx = self.read_u32() as usize;
|
||||
self.arena.mutate_root(|mc, root| {
|
||||
self.arena.mutate_root(|_, root| {
|
||||
let mut cur = root.env();
|
||||
for _ in 0..layer {
|
||||
let prev = cur.borrow().prev.expect("LoadOuter: env chain too short");
|
||||
@@ -429,7 +362,6 @@ impl Runtime {
|
||||
|
||||
MakeThunk => {
|
||||
let entry_point = self.read_u32();
|
||||
let _label = self.read_string_id();
|
||||
self.arena.mutate_root(|mc, root| {
|
||||
let thunk = Gc::new(
|
||||
mc,
|
||||
@@ -578,17 +510,180 @@ impl Runtime {
|
||||
let n = self.read_u16() as usize;
|
||||
let _span_id = self.read_u32();
|
||||
let keys = self.read_attr_keys(n);
|
||||
todo!("implement Select (force + lookup)");
|
||||
|
||||
// Move dynamic key values from stack to temp_stack
|
||||
let dyn_count = keys
|
||||
.iter()
|
||||
.filter(|k| matches!(k, SelectKeyData::Dynamic))
|
||||
.count();
|
||||
let temp_base = self.arena.mutate_root(|_, root| {
|
||||
let base = root.temp_stack.len();
|
||||
for _ in 0..dyn_count {
|
||||
let v = root.pop_stack();
|
||||
root.temp_stack.push(v);
|
||||
}
|
||||
base
|
||||
});
|
||||
|
||||
// Force the expr (now TOS)
|
||||
self.force_tos();
|
||||
|
||||
for (i, key) in keys.iter().enumerate() {
|
||||
let key_sid = match key {
|
||||
&SelectKeyData::Static(sid) => sid,
|
||||
SelectKeyData::Dynamic => {
|
||||
self.arena.mutate_root(|_, root| {
|
||||
let v = root.temp_stack.pop().expect("missing dynamic key");
|
||||
root.push_stack(v);
|
||||
});
|
||||
self.force_tos();
|
||||
let key_data: std::result::Result<StringId, String> =
|
||||
self.arena.mutate_root(|_, root| {
|
||||
let v = root.pop_stack();
|
||||
if let Some(sid) = v.as_inline::<StringId>() {
|
||||
Ok(sid)
|
||||
} else if let Some(ns) = v.as_gc::<NixString>() {
|
||||
Err(ns.as_str().to_owned())
|
||||
} else {
|
||||
panic!("dynamic select key must be a string")
|
||||
}
|
||||
});
|
||||
match key_data {
|
||||
Ok(sid) => sid,
|
||||
Err(s) => StringId(self.strings.get_or_intern(&s)),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let result = self.arena.mutate_root(|_, root| {
|
||||
let val = root.pop_stack();
|
||||
let Some(attrset) = val.as_gc::<AttrSet<'_>>() else {
|
||||
return Err(vm_err("value is not a set while a set was expected"));
|
||||
};
|
||||
match attrset.lookup(key_sid) {
|
||||
Some(v) => {
|
||||
root.push_stack(v);
|
||||
Ok(true)
|
||||
}
|
||||
None => Ok(false),
|
||||
}
|
||||
});
|
||||
|
||||
match result {
|
||||
Err(e) => {
|
||||
self.arena
|
||||
.mutate_root(|_, root| root.temp_stack.truncate(temp_base));
|
||||
return Runtime::handle_vm_error(self, e);
|
||||
}
|
||||
Ok(false) => {
|
||||
self.arena
|
||||
.mutate_root(|_, root| root.temp_stack.truncate(temp_base));
|
||||
let name = self.strings.resolve(key_sid.0).unwrap_or("«unknown»");
|
||||
return Runtime::handle_vm_error(
|
||||
self,
|
||||
vm_err(format!("attribute '{name}' missing")),
|
||||
);
|
||||
}
|
||||
Ok(true) => {
|
||||
if i < n - 1 {
|
||||
self.force_tos();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.arena
|
||||
.mutate_root(|_, root| root.temp_stack.truncate(temp_base));
|
||||
}
|
||||
SelectDefault => {
|
||||
let n = self.read_u16() as usize;
|
||||
let _span_id = self.read_u32();
|
||||
let keys = self.read_attr_keys(n);
|
||||
todo!("implement SelectDefault (force + lookup with default)");
|
||||
|
||||
let dyn_count = keys
|
||||
.iter()
|
||||
.filter(|k| matches!(k, SelectKeyData::Dynamic))
|
||||
.count();
|
||||
let temp_base = self.arena.mutate_root(|_, root| {
|
||||
let base = root.temp_stack.len();
|
||||
// Default value is on top of the stack (pushed last by codegen)
|
||||
let default_val = root.pop_stack();
|
||||
root.temp_stack.push(default_val);
|
||||
// Then dynamic keys
|
||||
for _ in 0..dyn_count {
|
||||
let v = root.pop_stack();
|
||||
root.temp_stack.push(v);
|
||||
}
|
||||
base
|
||||
});
|
||||
|
||||
// Force the expr (now TOS)
|
||||
self.force_tos();
|
||||
|
||||
let mut use_default = false;
|
||||
for (i, key) in keys.iter().enumerate() {
|
||||
let key_sid = match key {
|
||||
&SelectKeyData::Static(sid) => sid,
|
||||
SelectKeyData::Dynamic => {
|
||||
self.arena.mutate_root(|_, root| {
|
||||
let v = root.temp_stack.pop().expect("missing dynamic key");
|
||||
root.push_stack(v);
|
||||
});
|
||||
self.force_tos();
|
||||
let key_data: std::result::Result<StringId, String> =
|
||||
self.arena.mutate_root(|_, root| {
|
||||
let v = root.pop_stack();
|
||||
if let Some(sid) = v.as_inline::<StringId>() {
|
||||
Ok(sid)
|
||||
} else if let Some(ns) = v.as_gc::<NixString>() {
|
||||
Err(ns.as_str().to_owned())
|
||||
} else {
|
||||
panic!("dynamic select key must be a string")
|
||||
}
|
||||
});
|
||||
match key_data {
|
||||
Ok(sid) => sid,
|
||||
Err(s) => StringId(self.strings.get_or_intern(&s)),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let found = self.arena.mutate_root(|_, root| {
|
||||
let val = root.pop_stack();
|
||||
if let Some(attrset) = val.as_gc::<AttrSet<'_>>()
|
||||
&& let Some(v) = attrset.lookup(key_sid)
|
||||
{
|
||||
root.push_stack(v);
|
||||
return true;
|
||||
}
|
||||
// Not a set or key missing: use default
|
||||
false
|
||||
});
|
||||
|
||||
if !found {
|
||||
use_default = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if i < n - 1 {
|
||||
self.force_tos();
|
||||
}
|
||||
}
|
||||
|
||||
if use_default {
|
||||
self.arena.mutate_root(|_, root| {
|
||||
let default_val = root.temp_stack[temp_base];
|
||||
root.temp_stack.truncate(temp_base);
|
||||
root.push_stack(default_val);
|
||||
});
|
||||
} else {
|
||||
self.arena
|
||||
.mutate_root(|_, root| root.temp_stack.truncate(temp_base));
|
||||
}
|
||||
}
|
||||
HasAttr => {
|
||||
let n = self.read_u16() as usize;
|
||||
let keys = self.read_attr_keys(n);
|
||||
let _keys = self.read_attr_keys(n);
|
||||
todo!("implement HasAttr (force + check)");
|
||||
}
|
||||
|
||||
@@ -611,24 +706,118 @@ impl Runtime {
|
||||
self.push_empty_list();
|
||||
}
|
||||
|
||||
OpAdd | OpSub | OpMul | OpDiv | OpEq | OpNeq | OpLt | OpGt | OpLeq | OpGeq
|
||||
| OpConcat | OpUpdate => {
|
||||
let tag = match op {
|
||||
OpAdd => BinOpTag::Add,
|
||||
OpSub => BinOpTag::Sub,
|
||||
OpMul => BinOpTag::Mul,
|
||||
OpDiv => BinOpTag::Div,
|
||||
OpEq => BinOpTag::Eq,
|
||||
OpNeq => BinOpTag::Neq,
|
||||
OpLt => BinOpTag::Lt,
|
||||
OpGt => BinOpTag::Gt,
|
||||
OpLeq => BinOpTag::Leq,
|
||||
OpGeq => BinOpTag::Geq,
|
||||
OpConcat => BinOpTag::Concat,
|
||||
OpUpdate => BinOpTag::Update,
|
||||
OpAdd => {
|
||||
self.force_n(2);
|
||||
let strings = &self.strings;
|
||||
let res = self.arena.mutate_root(|mc, root| {
|
||||
let rhs = root.pop_stack_forced();
|
||||
let lhs = root.pop_stack_forced();
|
||||
// FIXME: path & string context
|
||||
if let (Some(ls), Some(rs)) = (
|
||||
Self::get_string(strings, lhs),
|
||||
Self::get_string(strings, rhs),
|
||||
) {
|
||||
let ns = Gc::new(mc, NixString::new(format!("{ls}{rs}")));
|
||||
root.push_stack(Value::new_gc(ns));
|
||||
return Ok(());
|
||||
}
|
||||
let res = Self::numeric_binop(lhs, rhs, mc, i64::wrapping_add, |a, b| a + b)?;
|
||||
root.push_stack(res);
|
||||
VmResult::Ok(())
|
||||
});
|
||||
try_vm!(self; res);
|
||||
}
|
||||
OpSub | OpMul => {
|
||||
self.force_n(2);
|
||||
#[allow(clippy::type_complexity)]
|
||||
let func: (fn(i64, i64) -> i64, fn(f64, f64) -> f64) = match op {
|
||||
OpSub => (i64::wrapping_sub, |a, b| a - b),
|
||||
OpMul => (i64::wrapping_mul, |a, b| a * b),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
try_vm!(self, self.compute_binop(tag))
|
||||
let res = self.arena.mutate_root(|mc, root| {
|
||||
let rhs = root.pop_stack_forced();
|
||||
let lhs = root.pop_stack_forced();
|
||||
let res = Self::numeric_binop(lhs, rhs, mc, func.0, func.1)?;
|
||||
root.push_stack(res);
|
||||
VmResult::Ok(())
|
||||
});
|
||||
try_vm!(self; res);
|
||||
}
|
||||
OpDiv => {
|
||||
self.force_n(2);
|
||||
let res = self.arena.mutate_root(|mc, root| {
|
||||
let rhs = root.pop_stack_forced();
|
||||
let lhs = root.pop_stack_forced();
|
||||
match (Self::get_num(lhs), Self::get_num(rhs)) {
|
||||
(_, Some(NixNum::Int(0))) => Err(vm_err("division by zero")),
|
||||
(_, Some(NixNum::Float(0.))) => Err(vm_err("division by zero")),
|
||||
_ => Ok(()),
|
||||
}?;
|
||||
let res = Self::numeric_binop(lhs, rhs, mc, |a, b| a / b, |a, b| a / b)?;
|
||||
root.push_stack(res);
|
||||
VmResult::Ok(())
|
||||
});
|
||||
try_vm!(self; res);
|
||||
}
|
||||
OpEq | OpNeq => {
|
||||
self.force_n(2);
|
||||
let map: fn(bool) -> bool = match op {
|
||||
OpEq => |a| a,
|
||||
OpNeq => |a| !a,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let eq = try_vm!(self; self.values_equal());
|
||||
self.push_stack(|_| Value::new_inline(map(eq)));
|
||||
}
|
||||
OpLt | OpGt | OpLeq | OpGeq => {
|
||||
use std::cmp::Ordering;
|
||||
self.force_n(2);
|
||||
let pred: fn(Ordering) -> bool = match op {
|
||||
OpLt => Ordering::is_lt,
|
||||
OpGt => Ordering::is_gt,
|
||||
OpLeq => Ordering::is_le,
|
||||
OpGeq => Ordering::is_ge,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
try_vm!(self; self.compare_values(pred));
|
||||
}
|
||||
OpConcat => {
|
||||
self.force_n(2);
|
||||
let res = self.arena.mutate_root(|mc, root| {
|
||||
let rhs = root.pop_stack_forced();
|
||||
let lhs = root.pop_stack_forced();
|
||||
// TODO: better type-assert ergonomic
|
||||
let Some(l) = lhs.as_gc::<List<'_>>() else {
|
||||
return Err(vm_err("cannot concatenate: left operand is not a list"));
|
||||
};
|
||||
let Some(r) = rhs.as_gc::<List<'_>>() else {
|
||||
return Err(vm_err("cannot concatenate: right operand is not a list"));
|
||||
};
|
||||
let mut items = SmallVec::new();
|
||||
items.extend_from_slice(&l);
|
||||
items.extend_from_slice(&r);
|
||||
root.push_stack(Value::new_gc(Gc::new(mc, List { inner: items })));
|
||||
VmResult::Ok(())
|
||||
});
|
||||
try_vm!(self; res);
|
||||
}
|
||||
OpUpdate => {
|
||||
self.force_n(2);
|
||||
let res = self.arena.mutate_root(|mc, root| {
|
||||
let rhs = root.pop_stack_forced();
|
||||
let lhs = root.pop_stack_forced();
|
||||
// TODO: better type-assert ergonomic
|
||||
let Some(l) = lhs.as_gc::<AttrSet<'_>>() else {
|
||||
return Err(vm_err("cannot update: left operand is not a set"));
|
||||
};
|
||||
let Some(r) = rhs.as_gc::<AttrSet<'_>>() else {
|
||||
return Err(vm_err("cannot update: right operand is not a set"));
|
||||
};
|
||||
root.push_stack(Value::new_gc(l.merge(&r, mc)));
|
||||
VmResult::Ok(())
|
||||
});
|
||||
try_vm!(self; res);
|
||||
}
|
||||
|
||||
OpNeg => {
|
||||
@@ -677,8 +866,8 @@ impl Runtime {
|
||||
}
|
||||
|
||||
Assert => {
|
||||
let raw_idx = self.read_u32();
|
||||
let span_id = self.read_u32();
|
||||
let _raw_idx = self.read_u32();
|
||||
let _span_id = self.read_u32();
|
||||
todo!("implement Assert (force TOS)");
|
||||
}
|
||||
|
||||
@@ -703,7 +892,59 @@ impl Runtime {
|
||||
}),
|
||||
WithLookup => {
|
||||
let name = self.read_string_id();
|
||||
todo!("implement WithLookup (force with_scope)");
|
||||
let mut depth = 0;
|
||||
|
||||
loop {
|
||||
let found_scope = self.arena.mutate_root(|_, root| {
|
||||
let mut cur = root.with_scope;
|
||||
for _ in 0..depth {
|
||||
if let Some(s) = cur {
|
||||
cur = s.prev;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(scope) = cur {
|
||||
root.push_stack(scope.env);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
if !found_scope {
|
||||
let name_str = self.strings.resolve(name.0).unwrap_or("«unknown»");
|
||||
return Runtime::handle_vm_error(
|
||||
self,
|
||||
vm_err(format!("undefined variable '{name_str}'")),
|
||||
);
|
||||
}
|
||||
|
||||
if let err @ Action::Done(Err(_)) = self.force_tos() {
|
||||
return err;
|
||||
}
|
||||
|
||||
let lookup_result = self.arena.mutate_root(|_, root| {
|
||||
let val = root.pop_stack();
|
||||
let Some(attrs) = val.as_gc::<AttrSet<'_>>() else {
|
||||
return Err(vm_err("value in 'with' scope must be a set"));
|
||||
};
|
||||
|
||||
if let Some(v) = attrs.lookup(name) {
|
||||
root.push_stack(v);
|
||||
Ok(true) // Found it
|
||||
} else {
|
||||
Ok(false) // Not in this scope, try the next one
|
||||
}
|
||||
});
|
||||
|
||||
match lookup_result {
|
||||
Ok(true) => break, // Successfully resolved and pushed to stack
|
||||
Ok(false) => depth += 1, // Move to the parent 'with' scope
|
||||
Err(e) => return Runtime::handle_vm_error(self, e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LoadBuiltins => {
|
||||
@@ -763,119 +1004,7 @@ impl Runtime {
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_binop(&mut self, op: BinOpTag) -> VmResult<()> {
|
||||
self.force_n(2);
|
||||
|
||||
match op {
|
||||
BinOpTag::Add => {
|
||||
let strings = &self.strings;
|
||||
self.arena.mutate_root(|mc, root| {
|
||||
let rhs = root.pop_stack_forced();
|
||||
let lhs = root.pop_stack_forced();
|
||||
// FIXME: path & string context
|
||||
if let (Some(ls), Some(rs)) = (
|
||||
Self::get_string(strings, lhs),
|
||||
Self::get_string(strings, rhs),
|
||||
) {
|
||||
let ns = Gc::new(mc, NixString::new(format!("{ls}{rs}")));
|
||||
root.push_stack(Value::new_gc(ns));
|
||||
return Ok(());
|
||||
}
|
||||
let res = Self::numeric_binop(lhs, rhs, mc, i64::wrapping_add, |a, b| a + b)?;
|
||||
root.push_stack(res);
|
||||
VmResult::Ok(())
|
||||
})?;
|
||||
}
|
||||
BinOpTag::Sub => {
|
||||
self.arena.mutate_root(|mc, root| {
|
||||
let rhs = root.pop_stack_forced();
|
||||
let lhs = root.pop_stack_forced();
|
||||
let res = Self::numeric_binop(lhs, rhs, mc, i64::wrapping_sub, |a, b| a - b)?;
|
||||
root.push_stack(res);
|
||||
VmResult::Ok(())
|
||||
})?;
|
||||
}
|
||||
BinOpTag::Mul => {
|
||||
self.arena.mutate_root(|mc, root| {
|
||||
let rhs = root.pop_stack_forced();
|
||||
let lhs = root.pop_stack_forced();
|
||||
let res = Self::numeric_binop(lhs, rhs, mc, i64::wrapping_mul, |a, b| a * b)?;
|
||||
root.push_stack(res);
|
||||
VmResult::Ok(())
|
||||
})?;
|
||||
}
|
||||
BinOpTag::Div => {
|
||||
self.arena.mutate_root(|mc, root| {
|
||||
let rhs = root.pop_stack_forced();
|
||||
let lhs = root.pop_stack_forced();
|
||||
let res = match (Self::get_num(lhs), Self::get_num(rhs)) {
|
||||
(_, Some(NixNum::Int(0))) => Err(vm_err("division by zero")),
|
||||
(_, Some(NixNum::Float(0.))) => Err(vm_err("division by zero")),
|
||||
(Some(NixNum::Int(a)), Some(NixNum::Int(b))) => {
|
||||
Ok(Value::make_int(a.wrapping_div(b), mc))
|
||||
}
|
||||
(Some(NixNum::Float(a)), Some(NixNum::Float(b))) => {
|
||||
Ok(Value::new_float(a / b))
|
||||
}
|
||||
(Some(NixNum::Int(a)), Some(NixNum::Float(b))) => {
|
||||
Ok(Value::new_float(a as f64 / b))
|
||||
}
|
||||
(Some(NixNum::Float(a)), Some(NixNum::Int(b))) => {
|
||||
Ok(Value::new_float(a / b as f64))
|
||||
}
|
||||
_ => Err(vm_err("cannot divide non-numbers")),
|
||||
}?;
|
||||
root.push_stack(res);
|
||||
VmResult::Ok(())
|
||||
})?;
|
||||
}
|
||||
BinOpTag::Eq => {
|
||||
let eq = self.values_equal()?;
|
||||
self.push_stack(|_| Value::new_inline(eq));
|
||||
}
|
||||
BinOpTag::Neq => {
|
||||
let eq = self.values_equal()?;
|
||||
self.push_stack(|_| Value::new_inline(!eq));
|
||||
}
|
||||
BinOpTag::Lt => self.compare_values(|o| o.is_lt())?,
|
||||
BinOpTag::Gt => self.compare_values(|o| o.is_gt())?,
|
||||
BinOpTag::Leq => self.compare_values(|o| o.is_le())?,
|
||||
BinOpTag::Geq => self.compare_values(|o| o.is_ge())?,
|
||||
BinOpTag::Concat => {
|
||||
self.arena.mutate_root(|mc, root| {
|
||||
let rhs = root.pop_stack_forced();
|
||||
let lhs = root.pop_stack_forced();
|
||||
let Some(l) = lhs.as_gc::<List<'_>>() else {
|
||||
return Err(vm_err("cannot concatenate: left operand is not a list"));
|
||||
};
|
||||
let Some(r) = rhs.as_gc::<List<'_>>() else {
|
||||
return Err(vm_err("cannot concatenate: right operand is not a list"));
|
||||
};
|
||||
let mut items = SmallVec::new();
|
||||
items.extend(l.inner.iter().cloned());
|
||||
items.extend(r.inner.iter().cloned());
|
||||
root.push_stack(Value::new_gc(Gc::new(mc, List { inner: items })));
|
||||
VmResult::Ok(())
|
||||
})?;
|
||||
}
|
||||
BinOpTag::Update => {
|
||||
self.arena.mutate_root(|mc, root| {
|
||||
let rhs = root.pop_stack_forced();
|
||||
let lhs = root.pop_stack_forced();
|
||||
let Some(l) = lhs.as_gc::<AttrSet<'_>>() else {
|
||||
return Err(vm_err("cannot update: left operand is not a set"));
|
||||
};
|
||||
let Some(r) = rhs.as_gc::<AttrSet<'_>>() else {
|
||||
return Err(vm_err("cannot update: right operand is not a set"));
|
||||
};
|
||||
root.push_stack(Value::new_gc(l.merge(&r, mc)));
|
||||
VmResult::Ok(())
|
||||
})?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn numeric_binop<'gc>(
|
||||
lhs: StrictValue<'gc>,
|
||||
rhs: StrictValue<'gc>,
|
||||
@@ -961,8 +1090,8 @@ impl Runtime {
|
||||
State::List(len) | State::AttrSet(len) => {
|
||||
for i in 0..len {
|
||||
self.arena.mutate_root(|_, root| {
|
||||
let y = root.temp_stack.pop().unwrap();
|
||||
let x = root.temp_stack.pop().unwrap();
|
||||
let y = root.temp_stack.pop().expect("stack underflow");
|
||||
let x = root.temp_stack.pop().expect("stack underflow");
|
||||
root.push_stack(x);
|
||||
root.push_stack(y);
|
||||
});
|
||||
@@ -1040,9 +1169,10 @@ impl Runtime {
|
||||
pub(super) fn run(
|
||||
&mut self,
|
||||
ip: InstructionPtr,
|
||||
_mode: ForceMode,
|
||||
mode: ForceMode,
|
||||
) -> Result<crate::value::Value> {
|
||||
self.pc = ip.0;
|
||||
self.force_mode = mode;
|
||||
self.arena.mutate_root(|mc, root| {
|
||||
if root.current_env.is_none() {
|
||||
root.current_env = Some(Gc::new(mc, RefLock::new(Env::empty())));
|
||||
@@ -1087,16 +1217,13 @@ impl Runtime {
|
||||
});
|
||||
}
|
||||
|
||||
pub(super) fn force_tos(&mut self) {
|
||||
pub(super) fn force_tos(&mut self) -> Action {
|
||||
loop {
|
||||
let run = self.arena.mutate_root(|_mc, root| {
|
||||
let thunk = root
|
||||
.stack
|
||||
.tos_mut()
|
||||
.expect("stack underflow");
|
||||
let (run, target_depth) = self.arena.mutate_root(|_mc, root| {
|
||||
let thunk = root.stack.tos_mut().expect("stack underflow");
|
||||
|
||||
let Some(thunk_state) = thunk.as_gc::<Thunk>() else {
|
||||
return false
|
||||
return (false, 0);
|
||||
};
|
||||
match *thunk_state.borrow() {
|
||||
ThunkState::Pending { ip, env } => {
|
||||
@@ -1108,25 +1235,32 @@ impl Runtime {
|
||||
.expect("call stack overflow");
|
||||
self.pc = ip;
|
||||
root.current_env = Some(env);
|
||||
true
|
||||
(true, root.frames.len())
|
||||
}
|
||||
ThunkState::Apply { .. } => todo!("force_tos"),
|
||||
ThunkState::Evaluated(val) => {
|
||||
*thunk = val;
|
||||
false
|
||||
},
|
||||
(false, 0)
|
||||
}
|
||||
ThunkState::Blackhole => todo!("force_tos"),
|
||||
}
|
||||
});
|
||||
if !run {
|
||||
return;
|
||||
return Action::Continue;
|
||||
}
|
||||
loop {
|
||||
self.check_gc();
|
||||
match self.execute_one() {
|
||||
Action::Continue => (),
|
||||
Action::Return => break,
|
||||
Action::Done(_) => unreachable!(),
|
||||
Action::Return => {
|
||||
let current_depth = self.arena.mutate_root(|_, root| root.frames.len());
|
||||
if current_depth < target_depth {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// FIXME: poison thunk
|
||||
Action::Done(err @ Err(_)) => return Action::Done(err),
|
||||
Action::Done(Ok(_)) => unreachable!(),
|
||||
}
|
||||
}
|
||||
self.arena.mutate_root(|mc, root| {
|
||||
@@ -1137,7 +1271,7 @@ impl Runtime {
|
||||
*thunk.borrow_mut(mc) = ThunkState::Evaluated(val);
|
||||
}
|
||||
*thunk = val;
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1163,21 +1297,238 @@ impl Runtime {
|
||||
|
||||
pub(super) fn handle_return(&mut self) -> Action {
|
||||
self.force_tos();
|
||||
self.arena.mutate_root(|_, root| {
|
||||
let done = self.arena.mutate_root(|_, root| {
|
||||
let Some(frame) = root.frames.pop() else {
|
||||
// FIXME: ForceMode
|
||||
root.current_env = None;
|
||||
return Action::Done(Ok(convert_value(
|
||||
root.stack.pop().expect("what the heck"),
|
||||
&self.strings,
|
||||
)));
|
||||
return true;
|
||||
};
|
||||
|
||||
self.pc = frame.pc;
|
||||
root.current_env = Some(frame.env);
|
||||
false
|
||||
});
|
||||
if !done {
|
||||
return Action::Return;
|
||||
}
|
||||
match self.force_mode {
|
||||
ForceMode::AsIs => (),
|
||||
ForceMode::Shallow => {
|
||||
if let done @ Action::Done(_) = self.force_tos_shallow() {
|
||||
return done;
|
||||
}
|
||||
}
|
||||
ForceMode::Deep => {
|
||||
if let done @ Action::Done(_) = self.force_tos_shallow() {
|
||||
return done;
|
||||
}
|
||||
}
|
||||
}
|
||||
let val = self.arena.mutate_root(|_, root| {
|
||||
root.current_env = None;
|
||||
convert_value(root.stack.pop().expect("stack underflow"), &self.strings)
|
||||
});
|
||||
Action::Done(Ok(val))
|
||||
}
|
||||
|
||||
Action::Return
|
||||
})
|
||||
pub(super) fn force_tos_shallow(&mut self) -> Action {
|
||||
if let err @ Action::Done(Err(_)) = self.force_tos() {
|
||||
return err;
|
||||
}
|
||||
|
||||
let (is_list, is_attrs) = self.arena.mutate_root(|_, root| {
|
||||
let tos = *root.stack.tos().expect("stack underflow");
|
||||
(
|
||||
tos.as_gc::<List<'_>>().is_some(),
|
||||
tos.as_gc::<AttrSet<'_>>().is_some(),
|
||||
)
|
||||
});
|
||||
|
||||
if is_list {
|
||||
let len = self.arena.mutate_root(|_, root| {
|
||||
let list = root.pop_stack().as_gc::<List<'_>>().unwrap();
|
||||
for &item in list.inner.iter() {
|
||||
root.temp_stack.push(item);
|
||||
}
|
||||
list.inner.len()
|
||||
});
|
||||
|
||||
let eval_base = self.arena.mutate_root(|_, root| root.temp_stack.len());
|
||||
|
||||
for i in 0..len {
|
||||
self.arena.mutate_root(|_, root| {
|
||||
let item = root.temp_stack[eval_base - len + i];
|
||||
root.push_stack(item);
|
||||
});
|
||||
|
||||
if let err @ Action::Done(Err(_)) = self.force_tos() {
|
||||
self.arena
|
||||
.mutate_root(|_, root| root.temp_stack.truncate(eval_base - len));
|
||||
return err;
|
||||
}
|
||||
|
||||
self.arena.mutate_root(|_, root| {
|
||||
let eval_item = root.pop_stack();
|
||||
root.temp_stack[eval_base - len + i] = eval_item;
|
||||
});
|
||||
}
|
||||
|
||||
self.arena.mutate_root(|mc, root| {
|
||||
let items: SmallVec<[Value; 4]> = root.temp_stack[eval_base - len..eval_base]
|
||||
.iter()
|
||||
.copied()
|
||||
.collect();
|
||||
root.temp_stack.truncate(eval_base - len);
|
||||
|
||||
// Reconstruct List
|
||||
let new_list = Gc::new(mc, List { inner: items });
|
||||
root.push_stack(Value::new_gc(new_list));
|
||||
});
|
||||
} else if is_attrs {
|
||||
let len = self.arena.mutate_root(|_, root| {
|
||||
let attrs = root.pop_stack().as_gc::<AttrSet<'_>>().unwrap();
|
||||
for &(key, item) in attrs.iter() {
|
||||
root.temp_stack.push(Value::new_inline(key));
|
||||
root.temp_stack.push(item);
|
||||
}
|
||||
attrs.len()
|
||||
});
|
||||
|
||||
let eval_base = self.arena.mutate_root(|_, root| root.temp_stack.len());
|
||||
|
||||
for i in 0..len {
|
||||
self.arena.mutate_root(|_, root| {
|
||||
let item = root.temp_stack[eval_base - len * 2 + i * 2 + 1];
|
||||
root.push_stack(item);
|
||||
});
|
||||
|
||||
if let err @ Action::Done(Err(_)) = self.force_tos() {
|
||||
self.arena
|
||||
.mutate_root(|_, root| root.temp_stack.truncate(eval_base - len * 2));
|
||||
return err;
|
||||
}
|
||||
|
||||
self.arena.mutate_root(|_, root| {
|
||||
let eval_item = root.pop_stack();
|
||||
root.temp_stack[eval_base - len * 2 + i * 2 + 1] = eval_item;
|
||||
});
|
||||
}
|
||||
|
||||
self.arena.mutate_root(|mc, root| {
|
||||
let mut kv = SmallVec::with_capacity(len);
|
||||
let mut i = eval_base - len * 2;
|
||||
while i < eval_base {
|
||||
let key = root.temp_stack[i].as_inline::<StringId>().unwrap();
|
||||
let val = root.temp_stack[i + 1];
|
||||
kv.push((key, val));
|
||||
i += 2;
|
||||
}
|
||||
kv.sort_by_key(|(k, _)| *k);
|
||||
root.temp_stack.truncate(eval_base - len * 2);
|
||||
|
||||
let new_attrs = Gc::new(mc, unsafe { AttrSet::from_sorted_unchecked(kv) });
|
||||
root.push_stack(Value::new_gc(new_attrs));
|
||||
});
|
||||
}
|
||||
|
||||
Action::Continue
|
||||
}
|
||||
|
||||
pub(super) fn force_tos_deep(&mut self) -> Action {
|
||||
if let err @ Action::Done(Err(_)) = self.force_tos() {
|
||||
return err;
|
||||
}
|
||||
|
||||
let (is_list, is_attrs) = self.arena.mutate_root(|_, root| {
|
||||
let tos = *root.stack.tos().expect("stack underflow");
|
||||
(
|
||||
tos.as_gc::<List<'_>>().is_some(),
|
||||
tos.as_gc::<AttrSet<'_>>().is_some(),
|
||||
)
|
||||
});
|
||||
|
||||
if is_list {
|
||||
let len = self.arena.mutate_root(|_, root| {
|
||||
let list = root.pop_stack().as_gc::<List<'_>>().unwrap();
|
||||
for &item in list.inner.iter() {
|
||||
root.temp_stack.push(item);
|
||||
}
|
||||
list.inner.len()
|
||||
});
|
||||
|
||||
let eval_base = self.arena.mutate_root(|_, root| root.temp_stack.len());
|
||||
|
||||
for i in 0..len {
|
||||
self.arena.mutate_root(|_, root| {
|
||||
let item = root.temp_stack[eval_base - len + i];
|
||||
root.push_stack(item);
|
||||
});
|
||||
|
||||
if let err @ Action::Done(Err(_)) = self.force_tos_deep() {
|
||||
self.arena
|
||||
.mutate_root(|_, root| root.temp_stack.truncate(eval_base - len));
|
||||
return err;
|
||||
}
|
||||
|
||||
self.arena.mutate_root(|_, root| {
|
||||
let eval_item = root.pop_stack();
|
||||
root.temp_stack[eval_base - len + i] = eval_item;
|
||||
});
|
||||
}
|
||||
|
||||
self.arena.mutate_root(|mc, root| {
|
||||
let items: SmallVec<[Value; 4]> = root.temp_stack[eval_base - len..eval_base]
|
||||
.iter()
|
||||
.copied()
|
||||
.collect();
|
||||
root.temp_stack.truncate(eval_base - len);
|
||||
let new_list = Gc::new(mc, List { inner: items });
|
||||
root.push_stack(Value::new_gc(new_list));
|
||||
});
|
||||
} else if is_attrs {
|
||||
let len = self.arena.mutate_root(|_, root| {
|
||||
let attrs = root.pop_stack().as_gc::<AttrSet<'_>>().unwrap();
|
||||
for &(key, item) in attrs.iter() {
|
||||
root.temp_stack.push(Value::new_inline(key));
|
||||
root.temp_stack.push(item);
|
||||
}
|
||||
attrs.len()
|
||||
});
|
||||
|
||||
let eval_base = self.arena.mutate_root(|_, root| root.temp_stack.len());
|
||||
|
||||
for i in 0..len {
|
||||
self.arena.mutate_root(|_, root| {
|
||||
let item = root.temp_stack[eval_base - len * 2 + i * 2 + 1];
|
||||
root.push_stack(item);
|
||||
});
|
||||
|
||||
if let err @ Action::Done(Err(_)) = self.force_tos_deep() {
|
||||
self.arena
|
||||
.mutate_root(|_, root| root.temp_stack.truncate(eval_base - len * 2));
|
||||
return err;
|
||||
}
|
||||
|
||||
self.arena.mutate_root(|_, root| {
|
||||
let eval_item = root.pop_stack();
|
||||
root.temp_stack[eval_base - len * 2 + i * 2 + 1] = eval_item;
|
||||
});
|
||||
}
|
||||
|
||||
self.arena.mutate_root(|mc, root| {
|
||||
let mut kv = SmallVec::with_capacity(len);
|
||||
let mut i = eval_base - len * 2;
|
||||
while i < eval_base {
|
||||
let key = root.temp_stack[i].as_inline::<StringId>().unwrap();
|
||||
let val = root.temp_stack[i + 1];
|
||||
kv.push((key, val));
|
||||
i += 2;
|
||||
}
|
||||
kv.sort_by_key(|(k, _)| *k);
|
||||
root.temp_stack.truncate(eval_base - len * 2);
|
||||
let new_attrs = Gc::new(mc, unsafe { AttrSet::from_sorted_unchecked(kv) });
|
||||
root.push_stack(Value::new_gc(new_attrs));
|
||||
});
|
||||
}
|
||||
|
||||
Action::Continue
|
||||
}
|
||||
|
||||
fn handle_vm_error(&mut self, e: VmError) -> Action {
|
||||
|
||||
+82
-74
@@ -89,6 +89,9 @@ macro_rules! eval_fail_test {
|
||||
};
|
||||
}
|
||||
|
||||
mod okay {
|
||||
use super::*;
|
||||
|
||||
eval_okay_test!(any_all);
|
||||
eval_okay_test!(arithmetic);
|
||||
eval_okay_test!(attrnames);
|
||||
@@ -241,78 +244,83 @@ eval_okay_test!(types);
|
||||
eval_okay_test!(versions);
|
||||
eval_okay_test!(with);
|
||||
eval_okay_test!(zipAttrsWith);
|
||||
}
|
||||
|
||||
eval_fail_test!(fail_abort);
|
||||
eval_fail_test!(fail_addDrvOutputDependencies_empty_context);
|
||||
eval_fail_test!(fail_addDrvOutputDependencies_multi_elem_context);
|
||||
eval_fail_test!(fail_addDrvOutputDependencies_wrong_element_kind);
|
||||
eval_fail_test!(fail_addErrorRuntime_example);
|
||||
eval_fail_test!(fail_assert);
|
||||
eval_fail_test!(fail_assert_equal_attrs_names);
|
||||
eval_fail_test!(fail_assert_equal_attrs_names_2);
|
||||
eval_fail_test!(fail_assert_equal_derivations);
|
||||
eval_fail_test!(fail_assert_equal_derivations_extra);
|
||||
eval_fail_test!(fail_assert_equal_floats);
|
||||
eval_fail_test!(fail_assert_equal_function_direct);
|
||||
eval_fail_test!(fail_assert_equal_int_float);
|
||||
eval_fail_test!(fail_assert_equal_ints);
|
||||
eval_fail_test!(fail_assert_equal_list_length);
|
||||
eval_fail_test!(fail_assert_equal_paths);
|
||||
eval_fail_test!(fail_assert_equal_type);
|
||||
eval_fail_test!(fail_assert_equal_type_nested);
|
||||
eval_fail_test!(fail_assert_nested_bool);
|
||||
eval_fail_test!(fail_attr_name_type);
|
||||
eval_fail_test!(fail_attrset_merge_drops_later_rec);
|
||||
eval_fail_test!(fail_bad_string_interpolation_1);
|
||||
eval_fail_test!(fail_bad_string_interpolation_2);
|
||||
eval_fail_test!(fail_bad_string_interpolation_3);
|
||||
eval_fail_test!(fail_bad_string_interpolation_4);
|
||||
eval_fail_test!(fail_blackhole);
|
||||
eval_fail_test!(fail_call_primop);
|
||||
eval_fail_test!(fail_deepseq);
|
||||
eval_fail_test!(fail_derivation_name);
|
||||
eval_fail_test!(fail_dup_dynamic_attrs);
|
||||
eval_fail_test!(fail_duplicate_traces);
|
||||
eval_fail_test!(fail_eol_1);
|
||||
eval_fail_test!(fail_eol_2);
|
||||
eval_fail_test!(fail_eol_3);
|
||||
eval_fail_test!(fail_fetchTree_negative);
|
||||
eval_fail_test!(fail_fetchurl_baseName);
|
||||
eval_fail_test!(fail_fetchurl_baseName_attrs);
|
||||
eval_fail_test!(fail_fetchurl_baseName_attrs_name);
|
||||
eval_fail_test!(fail_flake_ref_to_string_negative_integer);
|
||||
eval_fail_test!(fail_foldlStrict_strict_op_application);
|
||||
eval_fail_test!(fail_fromJSON_keyWithNullByte);
|
||||
eval_fail_test!(fail_fromJSON_overflowing);
|
||||
eval_fail_test!(fail_fromJSON_valueWithNullByte);
|
||||
eval_fail_test!(fail_fromTOML_keyWithNullByte);
|
||||
eval_fail_test!(fail_fromTOML_timestamps);
|
||||
eval_fail_test!(fail_fromTOML_valueWithNullByte);
|
||||
eval_fail_test!(fail_hashfile_missing);
|
||||
eval_fail_test!(fail_infinite_recursion_lambda);
|
||||
eval_fail_test!(fail_list);
|
||||
eval_fail_test!(fail_missing_arg);
|
||||
eval_fail_test!(fail_mutual_recursion);
|
||||
eval_fail_test!(fail_nested_list_items);
|
||||
eval_fail_test!(fail_nonexist_path);
|
||||
eval_fail_test!(fail_not_throws);
|
||||
eval_fail_test!(fail_overflowing_add);
|
||||
eval_fail_test!(fail_overflowing_div);
|
||||
eval_fail_test!(fail_overflowing_mul);
|
||||
eval_fail_test!(fail_overflowing_sub);
|
||||
eval_fail_test!(fail_path_slash);
|
||||
eval_fail_test!(fail_pipe_operators);
|
||||
eval_fail_test!(fail_recursion);
|
||||
eval_fail_test!(fail_remove);
|
||||
eval_fail_test!(fail_scope_5);
|
||||
eval_fail_test!(fail_seq);
|
||||
eval_fail_test!(fail_set);
|
||||
eval_fail_test!(fail_set_override);
|
||||
eval_fail_test!(fail_string_nul_1);
|
||||
eval_fail_test!(fail_string_nul_2);
|
||||
eval_fail_test!(fail_substring);
|
||||
eval_fail_test!(fail_toJSON);
|
||||
eval_fail_test!(fail_toJSON_non_utf_8);
|
||||
eval_fail_test!(fail_to_path);
|
||||
eval_fail_test!(fail_undeclared_arg);
|
||||
eval_fail_test!(fail_using_set_as_attr_name);
|
||||
mod fail {
|
||||
use super::*;
|
||||
|
||||
eval_fail_test!(abort);
|
||||
eval_fail_test!(addDrvOutputDependencies_empty_context);
|
||||
eval_fail_test!(addDrvOutputDependencies_multi_elem_context);
|
||||
eval_fail_test!(addDrvOutputDependencies_wrong_element_kind);
|
||||
eval_fail_test!(addErrorRuntime_example);
|
||||
eval_fail_test!(assert);
|
||||
eval_fail_test!(assert_equal_attrs_names);
|
||||
eval_fail_test!(assert_equal_attrs_names_2);
|
||||
eval_fail_test!(assert_equal_derivations);
|
||||
eval_fail_test!(assert_equal_derivations_extra);
|
||||
eval_fail_test!(assert_equal_floats);
|
||||
eval_fail_test!(assert_equal_function_direct);
|
||||
eval_fail_test!(assert_equal_int_float);
|
||||
eval_fail_test!(assert_equal_ints);
|
||||
eval_fail_test!(assert_equal_list_length);
|
||||
eval_fail_test!(assert_equal_paths);
|
||||
eval_fail_test!(assert_equal_type);
|
||||
eval_fail_test!(assert_equal_type_nested);
|
||||
eval_fail_test!(assert_nested_bool);
|
||||
eval_fail_test!(attr_name_type);
|
||||
eval_fail_test!(attrset_merge_drops_later_rec);
|
||||
eval_fail_test!(bad_string_interpolation_1);
|
||||
eval_fail_test!(bad_string_interpolation_2);
|
||||
eval_fail_test!(bad_string_interpolation_3);
|
||||
eval_fail_test!(bad_string_interpolation_4);
|
||||
eval_fail_test!(blackhole);
|
||||
eval_fail_test!(call_primop);
|
||||
eval_fail_test!(deepseq);
|
||||
eval_fail_test!(derivation_name);
|
||||
eval_fail_test!(dup_dynamic_attrs);
|
||||
eval_fail_test!(duplicate_traces);
|
||||
eval_fail_test!(eol_1);
|
||||
eval_fail_test!(eol_2);
|
||||
eval_fail_test!(eol_3);
|
||||
eval_fail_test!(fetchTree_negative);
|
||||
eval_fail_test!(fetchurl_baseName);
|
||||
eval_fail_test!(fetchurl_baseName_attrs);
|
||||
eval_fail_test!(fetchurl_baseName_attrs_name);
|
||||
eval_fail_test!(flake_ref_to_string_negative_integer);
|
||||
eval_fail_test!(foldlStrict_strict_op_application);
|
||||
eval_fail_test!(fromJSON_keyWithNullByte);
|
||||
eval_fail_test!(fromJSON_overflowing);
|
||||
eval_fail_test!(fromJSON_valueWithNullByte);
|
||||
eval_fail_test!(fromTOML_keyWithNullByte);
|
||||
eval_fail_test!(fromTOML_timestamps);
|
||||
eval_fail_test!(fromTOML_valueWithNullByte);
|
||||
eval_fail_test!(hashfile_missing);
|
||||
eval_fail_test!(infinite_recursion_lambda);
|
||||
eval_fail_test!(list);
|
||||
eval_fail_test!(missing_arg);
|
||||
eval_fail_test!(mutual_recursion);
|
||||
eval_fail_test!(nested_list_items);
|
||||
eval_fail_test!(nonexist_path);
|
||||
eval_fail_test!(not_throws);
|
||||
eval_fail_test!(overflowing_add);
|
||||
eval_fail_test!(overflowing_div);
|
||||
eval_fail_test!(overflowing_mul);
|
||||
eval_fail_test!(overflowing_sub);
|
||||
eval_fail_test!(path_slash);
|
||||
eval_fail_test!(pipe_operators);
|
||||
eval_fail_test!(recursion);
|
||||
eval_fail_test!(remove);
|
||||
eval_fail_test!(scope_5);
|
||||
eval_fail_test!(seq);
|
||||
eval_fail_test!(set);
|
||||
eval_fail_test!(set_override);
|
||||
eval_fail_test!(string_nul_1);
|
||||
eval_fail_test!(string_nul_2);
|
||||
eval_fail_test!(substring);
|
||||
eval_fail_test!(toJSON);
|
||||
eval_fail_test!(toJSON_non_utf_8);
|
||||
eval_fail_test!(to_path);
|
||||
eval_fail_test!(undeclared_arg);
|
||||
eval_fail_test!(using_set_as_attr_name);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user