rewrite VM to support reentry (WIP)

This commit is contained in:
2026-03-22 16:41:13 +08:00
parent b3f1f4f6ff
commit 6567ed4058
18 changed files with 1728 additions and 3315 deletions
+6
View File
@@ -459,6 +459,12 @@ impl RawBox {
pub(crate) fn into_float_unchecked(self) -> f64 {
unsafe { self.float }
}
#[inline]
#[must_use]
pub(crate) fn to_bits(self) -> u64 {
unsafe { self.bits }
}
}
impl fmt::Debug for RawBox {
+149 -75
View File
@@ -7,8 +7,10 @@ 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::runtime::value::{Null, PrimOp, StaticValue};
pub(crate) struct InstructionPtr(pub usize);
pub struct InstructionPtr(pub(crate) usize);
#[derive(Collect)]
#[collect(require_static)]
@@ -22,6 +24,7 @@ pub(crate) trait BytecodeContext {
fn register_span(&mut self, range: TextRange) -> u32;
fn get_code(&self) -> &[u8];
fn get_code_mut(&mut self) -> &mut Vec<u8>;
fn add_constant(&mut self, val: crate::runtime::value::StaticValue) -> u32;
}
#[repr(u8)]
@@ -49,13 +52,13 @@ pub enum Op {
CallNoSpan,
MakeAttrs,
MakeAttrsDyn,
MakeEmptyAttrs,
Select,
SelectDefault,
HasAttr,
MakeList,
MakeEmptyList,
OpAdd,
OpSub,
@@ -73,7 +76,6 @@ pub enum Op {
OpNeg,
OpNot,
ForceBool,
JumpIfFalse,
JumpIfTrue,
Jump,
@@ -109,6 +111,21 @@ struct BytecodeEmitter<'a, Ctx: BytecodeContext> {
scope_stack: Vec<ScopeInfo>,
}
pub(crate) const OPERAND_CONST: u8 = 0;
pub(crate) const OPERAND_LOCAL: u8 = 1;
pub(crate) const OPERAND_BUILTINS: u8 = 2;
pub(crate) const OPERAND_BIGINT: u8 = 3;
pub(crate) const KEY_STATIC: u8 = 0;
pub(crate) const KEY_DYNAMIC: u8 = 1;
enum InlineOperand {
Const(StaticValue),
Local { layer: u16, local: u32 },
Builtins,
BigInt(i64),
}
pub(crate) fn compile_bytecode(ir: RawIrRef<'_>, ctx: &mut impl BytecodeContext) -> InstructionPtr {
let ip = ctx.get_code().len();
let mut emitter = BytecodeEmitter::new(ctx);
@@ -124,6 +141,61 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
}
}
fn classify_value(&mut self, ir: RawIrRef<'_>) -> InlineOperand {
match ir.deref() {
&Ir::Int(x) => {
if x <= i32::MAX as i64 {
InlineOperand::Const(StaticValue::new_inline(x as i32))
} else {
InlineOperand::BigInt(x)
}
}
&Ir::Float(x) => InlineOperand::Const(StaticValue::new_float(x)),
&Ir::Bool(b) => InlineOperand::Const(StaticValue::new_inline(b)),
Ir::Null => InlineOperand::Const(StaticValue::new_inline(Null)),
Ir::Str(s) => {
let sid = self.ctx.intern_string(s.deref());
InlineOperand::Const(StaticValue::new_inline(sid))
}
&Ir::Thunk(id) => {
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::Builtin(id) => {
let arity = BUILTINS[id as usize].1;
InlineOperand::Const(StaticValue::new_inline(PrimOp { id, arity }))
}
Ir::Builtins => InlineOperand::Builtins,
_ => panic!("cannot classify IR node as inline operand"),
}
}
fn emit_inline_operand(&mut self, operand: InlineOperand) {
match operand {
InlineOperand::Const(val) => {
let idx = self.ctx.add_constant(val);
self.emit_u8(OPERAND_CONST);
self.emit_u32(idx);
}
InlineOperand::Local { layer, local } => {
self.emit_u8(OPERAND_LOCAL);
self.emit_u8(layer as u8);
self.emit_u32(local);
}
InlineOperand::Builtins => {
self.emit_u8(OPERAND_BUILTINS);
}
InlineOperand::BigInt(val) => {
self.emit_u8(OPERAND_BIGINT);
self.emit_i64(val);
}
}
}
#[inline]
fn emit_op(&mut self, op: Op) {
self.ctx.get_code_mut().push(op as u8);
@@ -382,11 +454,11 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
fn emit_toplevel(&mut self, ir: RawIrRef<'_>) {
match ir.deref() {
Ir::TopLevel { body, thunks } => {
let with_thunk_count = self.count_with_thunks(*body);
&Ir::TopLevel { body, ref thunks } => {
let with_thunk_count = self.count_with_thunks(body);
let total_slots = thunks.len() + with_thunk_count;
let all_thunks = self.collect_all_thunks(thunks, *body);
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);
@@ -397,7 +469,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
}
self.emit_scope_thunks(thunks);
self.emit_expr(*body);
self.emit_expr(body);
self.emit_op(Op::Return);
self.pop_scope();
@@ -459,7 +531,6 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
}
&Ir::If { cond, consq, alter } => {
self.emit_expr(cond);
self.emit_op(Op::ForceBool);
self.emit_op(Op::JumpIfFalse);
let else_placeholder = self.emit_i32_placeholder();
@@ -504,15 +575,20 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
self.emit_attrset(stcs, dyns);
}
Ir::List { items } => {
for &item in items.iter() {
self.emit_expr(item);
if items.is_empty() {
self.emit_op(Op::MakeEmptyList);
} else {
self.emit_op(Op::MakeList);
self.emit_u32(items.len() as u32);
for &item in items.iter() {
let operand = self.classify_value(item);
self.emit_inline_operand(operand);
}
}
self.emit_op(Op::MakeList);
self.emit_u32(items.len() as u32);
}
&Ir::Call { func, arg, span } => {
self.emit_expr(func);
self.emit_expr(arg);
self.emit_expr(func);
let span_id = self.ctx.register_span(span);
self.emit_op(Op::Call);
self.emit_u32(span_id);
@@ -539,20 +615,29 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
Ir::Builtins => {
self.emit_op(Op::LoadBuiltins);
}
&Ir::Builtin(name) => {
&Ir::Builtin(id) => {
self.emit_op(Op::LoadBuiltin);
self.emit_u32(name.0.to_usize() as u32);
self.emit_u8(id as u8);
}
&Ir::BuiltinConst(id) => {
self.emit_select(
RawIrRef(&Ir::Builtins),
&[Attr::Str(id, TextRange::default())],
None,
TextRange::default(),
);
}
&Ir::ConcatStrings {
ref parts,
force_string,
} => {
for &part in parts.iter() {
self.emit_expr(part);
}
self.emit_op(Op::ConcatStrings);
self.emit_u16(parts.len() as u16);
self.emit_u8(if force_string { 1 } else { 0 });
for &part in parts.iter() {
let operand = self.classify_value(part);
self.emit_inline_operand(operand);
}
}
&Ir::HasAttr { lhs, ref rhs } => {
self.emit_has_attr(lhs, rhs);
@@ -603,13 +688,11 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
match kind {
And => {
self.emit_expr(lhs);
self.emit_op(Op::ForceBool);
self.emit_op(Op::JumpIfFalse);
let skip_placeholder = self.emit_i32_placeholder();
let after_jif = self.ctx.get_code_mut().len();
self.emit_expr(rhs);
self.emit_op(Op::ForceBool);
self.emit_op(Op::Jump);
let end_placeholder = self.emit_i32_placeholder();
let after_jump = self.ctx.get_code_mut().len();
@@ -624,13 +707,11 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
}
Or => {
self.emit_expr(lhs);
self.emit_op(Op::ForceBool);
self.emit_op(Op::JumpIfTrue);
let skip_placeholder = self.emit_i32_placeholder();
let after_jit = self.ctx.get_code_mut().len();
self.emit_expr(rhs);
self.emit_op(Op::ForceBool);
self.emit_op(Op::Jump);
let end_placeholder = self.emit_i32_placeholder();
let after_jump = self.ctx.get_code_mut().len();
@@ -645,13 +726,11 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
}
Impl => {
self.emit_expr(lhs);
self.emit_op(Op::ForceBool);
self.emit_op(Op::JumpIfFalse);
let skip_placeholder = self.emit_i32_placeholder();
let after_jif = self.ctx.get_code_mut().len();
self.emit_expr(rhs);
self.emit_op(Op::ForceBool);
self.emit_op(Op::Jump);
let end_placeholder = self.emit_i32_placeholder();
let after_jump = self.ctx.get_code_mut().len();
@@ -759,40 +838,26 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
return;
}
if !dyns.is_empty() {
for (&sym, &(val, _)) in stcs.iter() {
self.emit_op(Op::PushString);
self.emit_str_id(sym);
self.emit_expr(val);
}
for (_, &(_, span)) in stcs.iter() {
let span_id = self.ctx.register_span(span);
self.emit_op(Op::PushSmi);
self.emit_u32(span_id);
}
for &(key, val, span) in dyns.iter() {
self.emit_expr(key);
self.emit_expr(val);
let span_id = self.ctx.register_span(span);
self.emit_op(Op::PushSmi);
self.emit_u32(span_id);
}
self.emit_op(Op::MakeAttrsDyn);
self.emit_u32(stcs.len() as u32);
self.emit_u32(dyns.len() as u32);
} else {
for (&sym, &(val, _)) in stcs.iter() {
self.emit_op(Op::PushString);
self.emit_str_id(sym);
self.emit_expr(val);
}
for (_, &(_, span)) in stcs.iter() {
let span_id = self.ctx.register_span(span);
self.emit_op(Op::PushSmi);
self.emit_u32(span_id);
}
self.emit_op(Op::MakeAttrs);
self.emit_u32(stcs.len() as u32);
let total = stcs.len() + dyns.len();
self.emit_op(Op::MakeAttrs);
self.emit_u32(total as u32);
for (&sym, &(val, span)) in stcs.iter() {
self.emit_u8(KEY_STATIC);
self.emit_str_id(sym);
let val_operand = self.classify_value(val);
self.emit_inline_operand(val_operand);
let span_id = self.ctx.register_span(span);
self.emit_u32(span_id);
}
for &(key, val, span) in dyns.iter() {
self.emit_u8(KEY_DYNAMIC);
let key_operand = self.classify_value(key);
self.emit_inline_operand(key_operand);
let val_operand = self.classify_value(val);
self.emit_inline_operand(val_operand);
let span_id = self.ctx.register_span(span);
self.emit_u32(span_id);
}
}
@@ -805,17 +870,10 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
) {
self.emit_expr(expr);
for attr in attrpath.iter() {
match *attr {
Attr::Str(sym, _) => {
self.emit_op(Op::PushString);
self.emit_str_id(sym);
}
Attr::Dynamic(expr, _) => {
self.emit_expr(expr);
}
if let Attr::Dynamic(expr, _) = *attr {
self.emit_expr(expr);
}
}
if let Some(default) = default {
self.emit_expr(default);
let span_id = self.ctx.register_span(span);
@@ -828,23 +886,39 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
self.emit_u16(attrpath.len() as u16);
self.emit_u32(span_id);
}
for attr in attrpath.iter() {
match *attr {
Attr::Str(sym, _) => {
self.emit_u8(KEY_STATIC);
self.emit_str_id(sym);
}
Attr::Dynamic(_, _) => {
self.emit_u8(KEY_DYNAMIC);
}
}
}
}
fn emit_has_attr(&mut self, lhs: RawIrRef<'_>, rhs: &[Attr<RawIrRef<'_>>]) {
self.emit_expr(lhs);
for attr in rhs.iter() {
match *attr {
Attr::Str(sym, _) => {
self.emit_op(Op::PushString);
self.emit_str_id(sym);
}
Attr::Dynamic(expr, _) => {
self.emit_expr(expr);
}
if let Attr::Dynamic(expr, _) = *attr {
self.emit_expr(expr);
}
}
self.emit_op(Op::HasAttr);
self.emit_u16(rhs.len() as u16);
for attr in rhs.iter() {
match *attr {
Attr::Str(sym, _) => {
self.emit_u8(KEY_STATIC);
self.emit_str_id(sym);
}
Attr::Dynamic(_, _) => {
self.emit_u8(KEY_DYNAMIC);
}
}
}
}
fn emit_with(
+66 -75
View File
@@ -3,70 +3,71 @@ use std::fmt::Write;
use colored::Colorize;
use num_enum::TryFromPrimitive;
use crate::codegen::{Bytecode, Op};
use crate::codegen::{InstructionPtr, Op};
pub(crate) trait DisassemblerContext {
fn lookup_string(&self, id: u32) -> &str;
fn get_code(&self) -> &[u8];
}
pub(crate) struct Disassembler<'a, Ctx> {
code: &'a [u8],
ctx: &'a Ctx,
pos: usize,
pc: usize,
}
impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
pub fn new(bytecode: &'a Bytecode, ctx: &'a Ctx) -> Self {
pub fn new(ip: InstructionPtr, ctx: &'a Ctx) -> Self {
Self {
code: &bytecode.code,
code: ctx.get_code(),
ctx,
pos: 0,
pc: ip.0,
}
}
fn read_u8(&mut self) -> u8 {
let b = self.code[self.pos];
self.pos += 1;
let b = self.code[self.pc];
self.pc += 1;
b
}
fn read_u16(&mut self) -> u16 {
let bytes = self.code[self.pos..self.pos + 2]
let bytes = self.code[self.pc..self.pc + 2]
.try_into()
.expect("no enough bytes");
self.pos += 2;
self.pc += 2;
u16::from_le_bytes(bytes)
}
fn read_u32(&mut self) -> u32 {
let bytes = self.code[self.pos..self.pos + 4]
let bytes = self.code[self.pc..self.pc + 4]
.try_into()
.expect("no enough bytes");
self.pos += 4;
self.pc += 4;
u32::from_le_bytes(bytes)
}
fn read_i32(&mut self) -> i32 {
let bytes = self.code[self.pos..self.pos + 4]
let bytes = self.code[self.pc..self.pc + 4]
.try_into()
.expect("no enough bytes");
self.pos += 4;
self.pc += 4;
i32::from_le_bytes(bytes)
}
fn read_i64(&mut self) -> i64 {
let bytes = self.code[self.pos..self.pos + 8]
let bytes = self.code[self.pc..self.pc + 8]
.try_into()
.expect("no enough bytes");
self.pos += 8;
self.pc += 8;
i64::from_le_bytes(bytes)
}
fn read_f64(&mut self) -> f64 {
let bytes = self.code[self.pos..self.pos + 8]
let bytes = self.code[self.pc..self.pc + 8]
.try_into()
.expect("no enough bytes");
self.pos += 8;
self.pc += 8;
f64::from_le_bytes(bytes)
}
@@ -93,62 +94,62 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
let _ = writeln!(out, "Length: {} bytes", self.code.len());
}
while self.pos < self.code.len() {
let start_pos = self.pos;
while self.pc < self.code.len() {
let start_pos = self.pc;
let op_byte = self.read_u8();
let (mnemonic, args) = self.decode_instruction(op_byte, start_pos);
let bytes_slice = &self.code[start_pos + 1..self.pos];
let bytes_slice = &self.code[start_pos + 1..self.pc];
let mut chunks = bytes_slice.chunks(4);
for (i, chunk) in bytes_slice.chunks(4).enumerate() {
let first_chunk = chunks.next().unwrap_or(&[]);
let bytes_str = {
let mut temp = format!("{:02x}", self.code[start_pos]);
for b in first_chunk {
let _ = write!(&mut temp, " {:02x}", b);
}
temp
};
if color {
let sep = if args.is_empty() { "" } else { " " };
let _ = writeln!(
out,
"{} {:<14} | {}{}{}",
format!("{:04x}", start_pos).dimmed(),
bytes_str.green(),
mnemonic.yellow().bold(),
sep,
args.cyan()
);
} else {
let op_str = if args.is_empty() {
mnemonic.to_string()
} else {
format!("{} {}", mnemonic, args)
};
let _ = writeln!(out, "{:04x} {:<14} | {}", start_pos, bytes_str, op_str);
}
for chunk in chunks {
let bytes_str = {
let mut temp = String::new();
if i == 0 {
let _ = write!(&mut temp, "{:02x}", self.code[start_pos]);
} else {
let _ = write!(&mut temp, " ");
}
for b in chunk.iter() {
let mut temp = String::from(" ");
for b in chunk {
let _ = write!(&mut temp, " {:02x}", b);
}
temp
};
if i == 0 {
if color {
let sep = if args.is_empty() { "" } else { " " };
let _ = writeln!(
out,
"{} {:<14} | {}{}{}",
format!("{:04x}", start_pos).dimmed(),
bytes_str.green(),
mnemonic.yellow().bold(),
sep,
args.cyan()
);
} else {
let op_str = if args.is_empty() {
mnemonic.to_string()
} else {
format!("{} {}", mnemonic, args)
};
let _ = writeln!(out, "{:04x} {:<14} | {}", start_pos, bytes_str, op_str);
}
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, " "); }
let _ = writeln!(out, " {:<14} |", bytes_str.green());
} else {
let extra_width = start_pos.ilog2() >> 4;
if color {
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, " ");
}
let _ = writeln!(out, " {:<14} |", bytes_str);
}
let _ = write!(out, " ");
for _ in 0..extra_width { let _ = write!(out, " "); }
let _ = writeln!(out, " {:<14} |", bytes_str);
}
}
}
@@ -258,14 +259,6 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
let count = self.read_u32();
("MakeAttrs", format!("size={}", count))
}
Op::MakeAttrsDyn => {
let static_count = self.read_u32();
let dyn_count = self.read_u32();
(
"MakeAttrsDyn",
format!("static={} dyn={}", static_count, dyn_count),
)
}
Op::MakeEmptyAttrs => ("MakeEmptyAttrs", String::new()),
Op::Select => {
@@ -290,6 +283,7 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
let count = self.read_u32();
("MakeList", format!("size={}", count))
}
Op::MakeEmptyList => ("MakeEmptyList", String::new()),
Op::OpAdd => ("OpAdd", String::new()),
Op::OpSub => ("OpSub", String::new()),
@@ -306,8 +300,6 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
Op::OpNeg => ("OpNeg", String::new()),
Op::OpNot => ("OpNot", String::new()),
Op::ForceBool => ("ForceBool", String::new()),
Op::JumpIfFalse => {
let offset = self.read_i32();
let target = (current_pc as isize + 1 + 4 + offset as isize) as usize;
@@ -348,9 +340,8 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
Op::LoadBuiltins => ("LoadBuiltins", String::new()),
Op::LoadBuiltin => {
let idx = self.read_u32();
let name = self.ctx.lookup_string(idx);
("LoadBuiltin", format!("{:?}", name))
let id = self.read_u8();
("LoadBuiltin", format!("id={}", id))
}
Op::MkPos => {
let span_id = self.read_u32();
+4 -3
View File
@@ -8,6 +8,7 @@ use rowan::ast::AstNode;
use crate::error::{Error, Result, Source};
use crate::ir::*;
use crate::runtime::BuiltinId;
use crate::value::Symbol;
trait Require<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>, T> {
@@ -170,10 +171,10 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
text[1..text.len() - 1].to_string().box_in(ctx.bump()),
))
};
let sym = ctx.new_sym("findFile".into());
let find_file = ctx.new_expr(Ir::Builtin(sym));
// HACK: disgusting eww
let find_file = ctx.new_expr(Ir::Builtin(BuiltinId::FindFile));
let sym = ctx.new_sym("nixPath".into());
let nix_path = ctx.new_expr(Ir::Builtin(sym));
let nix_path = ctx.new_expr(Ir::BuiltinConst(sym));
let call = ctx.new_expr(Ir::Call {
func: find_file,
arg: nix_path,
+13 -8
View File
@@ -1,14 +1,16 @@
use std::{
hash::{Hash, Hasher},
ops::Deref,
};
use std::hash::{Hash, Hasher};
use std::ops::Deref;
use bumpalo::{Bump, boxed::Box, collections::Vec};
use bumpalo::Bump;
use bumpalo::boxed::Box;
use bumpalo::collections::Vec;
use gc_arena::Collect;
use ghost_cell::{GhostCell, GhostToken};
use rnix::{TextRange, ast};
use string_interner::symbol::SymbolU32;
use crate::runtime::BuiltinId;
pub type HashMap<'ir, K, V> = hashbrown::HashMap<K, V, hashbrown::DefaultHashBuilder, &'ir Bump>;
#[repr(transparent)]
@@ -48,8 +50,8 @@ impl<'id, 'ir> IrRef<'id, 'ir> {
}
#[repr(transparent)]
#[derive(Clone, Copy)]
pub struct RawIrRef<'ir>(&'ir Ir<'ir, Self>);
#[derive(Clone, Copy, Debug)]
pub struct RawIrRef<'ir>(pub &'ir Ir<'ir, Self>);
impl<'ir> Deref for RawIrRef<'ir> {
type Target = Ir<'ir, RawIrRef<'ir>>;
@@ -59,6 +61,7 @@ impl<'ir> Deref for RawIrRef<'ir> {
}
#[repr(C)]
#[derive(Debug)]
pub enum Ir<'ir, Ref> {
Int(i64),
Float(f64),
@@ -135,7 +138,8 @@ pub enum Ir<'ir, Ref> {
// Builtins
Builtins,
Builtin(StringId),
Builtin(BuiltinId),
BuiltinConst(StringId),
// Misc
TopLevel {
@@ -463,6 +467,7 @@ fn ir_content_hash<'id>(ir: IrRef<'id, '_>, token: &GhostToken<'id>, state: &mut
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),
+3 -1
View File
@@ -1,7 +1,9 @@
use std::env;
use std::io::IsTerminal;
use tracing_subscriber::{EnvFilter, Layer, fmt, layer::SubscriberExt, util::SubscriberInitExt};
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::{EnvFilter, Layer, fmt};
pub fn init_logging() {
let is_terminal = std::io::stderr().is_terminal();
+6 -7
View File
@@ -40,19 +40,18 @@ struct ExprSource {
file: Option<PathBuf>,
}
fn run_compile(_runtime: &mut Runtime, src: ExprSource, _silent: bool) -> Result<()> {
let _src = if let Some(expr) = src.expr {
fn run_compile(runtime: &mut Runtime, src: ExprSource, silent: bool) -> Result<()> {
let src = if let Some(expr) = src.expr {
Source::new_eval(expr)?
} else if let Some(file) = src.file {
Source::new_file(file)?
} else {
unreachable!()
};
todo!()
/* match runtime.compile_bytecode(src) {
Ok(compiled) => {
match runtime.compile_bytecode(src) {
Ok(ip) => {
if !silent {
println!("{}", runtime.disassemble_colored(&compiled));
println!("{}", runtime.disassemble_colored(ip));
}
}
Err(err) => {
@@ -60,7 +59,7 @@ fn run_compile(_runtime: &mut Runtime, src: ExprSource, _silent: bool) -> Result
exit(1);
}
};
Ok(()) */
Ok(())
}
fn run_eval(runtime: &mut Runtime, src: ExprSource) -> Result<()> {
+99 -57
View File
@@ -1,75 +1,99 @@
use std::hash::BuildHasher;
use bumpalo::Bump;
use gc_arena::{Arena, Rootable, arena::CollectionPhase};
use gc_arena::{Arena, Rootable};
use ghost_cell::{GhostCell, GhostToken};
use hashbrown::{DefaultHashBuilder, HashMap, HashSet, HashTable};
use rnix::TextRange;
use string_interner::DefaultStringInterner;
use string_interner::symbol::SymbolU32;
use string_interner::{DefaultStringInterner, Symbol as _};
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::runtime::builtins::new_builtins_env;
use crate::runtime::builtins::init_builtins;
use crate::runtime::value::StaticValue;
use crate::runtime::vm::{ForceMode, new_gc_root};
use crate::store::{DaemonStore, StoreConfig};
use crate::value::Symbol;
mod builtins;
mod primops;
mod stack;
mod value;
pub(crate) mod value;
mod vm;
use vm::{Action, VM};
pub(crate) use builtins::{BUILTINS, BuiltinId};
use stack::Stack;
use vm::{ErrorFrame, GcRoot};
pub struct Runtime {
bytecode: Vec<u8>,
global_env: HashMap<StringId, Ir<'static, RawIrRef<'static>>>,
// global
sources: Vec<Source>,
store: DaemonStore,
spans: Vec<(usize, TextRange)>,
thunk_count: usize,
store: DaemonStore,
strings: DefaultStringInterner,
arena: Arena<Rootable![VM<'_>]>,
// FIXME: remove?
thunk_count: usize,
bytecode: Vec<u8>,
pub(crate) constants: Vec<StaticValue>,
constant_dedup: HashMap<u64, u32>,
// downgrade
global_env: HashMap<StringId, Ir<'static, RawIrRef<'static>>>,
// eval
pc: usize,
fuel: usize,
error_contexts: Stack<8192, ErrorFrame>,
arena: Arena<Rootable![GcRoot<'_>]>,
}
impl Runtime {
const COLLECTOR_GRANULARITY: f64 = 1024.0;
const DEFAULT_FUEL_AMOUNT: usize = 2048;
pub fn new() -> Result<Self> {
// HACK: what the heck...
let mut global_env = HashMap::new();
let mut strings = DefaultStringInterner::new();
let global_env = new_builtins_env(&mut strings);
let arena = Arena::new(|mc| {
let (root, env) = new_gc_root(mc, &mut strings);
global_env = env;
root
});
let config = StoreConfig::from_env();
let store = DaemonStore::connect(&config.daemon_socket)?;
Ok(Self {
arena: Arena::new(|mc| VM::new(mc, &mut strings)),
global_env,
sources: Vec::new(),
spans: Vec::new(),
store,
strings,
thunk_count: 0,
bytecode: Vec::new(),
sources: Vec::new(),
spans: Vec::new(),
constants: Vec::new(),
constant_dedup: HashMap::new(),
global_env,
pc: 0,
fuel: Self::DEFAULT_FUEL_AMOUNT,
error_contexts: Stack::new(),
arena,
})
}
pub fn eval(&mut self, source: Source) -> Result<crate::value::Value> {
let root = self.downgrade(source, None)?;
let ip = crate::codegen::compile_bytecode(root.as_ref(), self);
self.run(ip)
self.do_eval(source, None, ForceMode::AsIs)
}
pub fn eval_shallow(&mut self, _source: Source) -> Result<crate::value::Value> {
todo!()
pub fn eval_shallow(&mut self, source: Source) -> Result<crate::value::Value> {
self.do_eval(source, None, ForceMode::Shallow)
}
pub fn eval_deep(&mut self, source: Source) -> Result<crate::value::Value> {
// FIXME: deep
let root = self.downgrade(source, None)?;
let ip = crate::codegen::compile_bytecode(root.as_ref(), self);
self.run(ip)
self.do_eval(source, None, ForceMode::Deep)
}
pub fn eval_repl(
@@ -77,10 +101,18 @@ impl Runtime {
source: Source,
scope: &HashSet<StringId>,
) -> Result<crate::value::Value> {
// FIXME: shallow
let root = self.downgrade(source, Some(Scope::Repl(scope)))?;
self.do_eval(source, Some(Scope::Repl(scope)), ForceMode::Shallow)
}
fn do_eval<'ctx>(
&'ctx mut self,
source: Source,
extra_scope: Option<Scope<'ctx>>,
force_mode: ForceMode,
) -> Result<crate::value::Value> {
let root = self.downgrade(source, extra_scope)?;
let ip = crate::codegen::compile_bytecode(root.as_ref(), self);
self.run(ip)
self.run(ip, force_mode)
}
pub fn add_binding(
@@ -92,6 +124,20 @@ impl Runtime {
todo!()
}
pub fn compile_bytecode(&mut self, source: Source) -> Result<InstructionPtr> {
let root = self.downgrade(source, None)?;
let ip = crate::codegen::compile_bytecode(root.as_ref(), self);
Ok(ip)
}
pub fn disassemble_colored(&mut self, ip: InstructionPtr) -> String {
Disassembler::new(ip, self).disassemble_colored()
}
pub fn disassemble(&mut self, ip: InstructionPtr) -> String {
Disassembler::new(ip, self).disassemble()
}
fn downgrade_ctx<'a, 'bump, 'id>(
&'a mut self,
bump: &'bump Bump,
@@ -147,34 +193,6 @@ impl Runtime {
Ok(OwnedIr { _bump: bump, ir })
})
}
fn run(&mut self, ip: InstructionPtr) -> Result<crate::value::Value> {
let mut pc = ip.0;
loop {
let Runtime {
bytecode,
strings,
arena,
..
} = self;
let action =
arena.mutate_root(|mc, root| root.run_batch(bytecode, &mut pc, mc, strings));
match action {
Action::NeedGc => {
if self.arena.collection_phase() == CollectionPhase::Sweeping {
self.arena.collect_debt();
} else if let Some(marked) = self.arena.mark_debt() {
marked.start_sweeping();
}
}
Action::Done(done) => {
break done;
}
Action::Continue => (),
Action::IoRequest(_) => todo!(),
}
}
}
}
fn parse_error_span(error: &rnix::ParseError) -> Option<rnix::TextRange> {
@@ -536,4 +554,28 @@ impl BytecodeContext for Runtime {
fn get_code_mut(&mut self) -> &mut Vec<u8> {
&mut self.bytecode
}
fn add_constant(&mut self, val: StaticValue) -> u32 {
let bits = val.to_bits();
*self.constant_dedup.entry(bits).or_insert_with(|| {
let idx = self.constants.len() as u32;
self.constants.push(val);
idx
})
}
}
impl DisassemblerContext for Runtime {
fn lookup_string(&self, id: u32) -> &str {
self.strings
.resolve(SymbolU32::try_from_usize(id as usize).expect("invalid string id"))
.expect("string not found")
}
fn get_code(&self) -> &[u8] {
&self.bytecode
}
}
struct WellKnownSymbols {
// TODO:
}
+290 -127
View File
@@ -1,7 +1,9 @@
use gc_arena::Collect;
use gc_arena::{Collect, Gc, Mutation, RefLock};
use hashbrown::HashMap;
use num_enum::TryFromPrimitive;
use smallvec::SmallVec;
use string_interner::DefaultStringInterner;
use string_interner::symbol::SymbolU32;
use super::value::*;
use crate::ir::{Ir, RawIrRef, StringId};
@@ -12,14 +14,14 @@ macro_rules! define_builtins {
($(($name:literal, $variant:ident, $arity:expr)),* $(,)?) => {
/// Builtin function registry.
/// Array index IS the PrimOp id. (name, arity) pairs.
pub(super) const BUILTINS: &[(&str, u8)] = &[
pub(crate) const BUILTINS: &[(&str, u8)] = &[
$(($name, $arity),)*
];
#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, Collect)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, TryFromPrimitive, Collect)]
#[repr(u8)]
#[collect(require_static)]
pub(super) enum BuiltinId {
pub(crate) enum BuiltinId {
$($variant,)*
}
};
@@ -27,108 +29,108 @@ macro_rules! define_builtins {
define_builtins! {
("abort", Abort, 1),
("add", Add, 2),
("addErrorContext", AddErrorContext, 2),
("all", All, 2),
("any", Any, 2),
("appendContext", AppendContext, 2),
("attrNames", AttrNames, 1),
("attrValues", AttrValues, 1),
("__add", Add, 2),
("__addErrorContext", AddErrorContext, 2),
("__all", All, 2),
("__any", Any, 2),
("__appendContext", AppendContext, 2),
("__attrNames", AttrNames, 1),
("__attrValues", AttrValues, 1),
("baseNameOf", BaseNameOf, 1),
("bitAnd", BitAnd, 2),
("bitOr", BitOr, 2),
("bitXor", BitXor, 2),
("catAttrs", CatAttrs, 2),
("ceil", Ceil, 1),
("compareVersions", CompareVersions, 2),
("concatLists", ConcatLists, 1),
("concatMap", ConcatMap, 2),
("concatStringsSep", ConcatStringsSep, 2),
("convertHash", ConvertHash, 1),
("deepSeq", DeepSeq, 2),
("__bitAnd", BitAnd, 2),
("__bitOr", BitOr, 2),
("__bitXor", BitXor, 2),
("break", Break, 1),
("__catAttrs", CatAttrs, 2),
("__ceil", Ceil, 1),
("__compareVersions", CompareVersions, 2),
("__concatLists", ConcatLists, 1),
("__concatMap", ConcatMap, 2),
("__concatStringsSep", ConcatStringsSep, 2),
("__convertHash", ConvertHash, 1),
("__deepSeq", DeepSeq, 2),
("derivation", Derivation, 1),
("derivationStrict", DerivationStrict, 1),
("dirOf", DirOf, 1),
("div", Div, 2),
("elem", Elem, 2),
("elemAt", ElemAt, 2),
("__div", Div, 2),
("__elem", Elem, 2),
("__elemAt", ElemAt, 2),
("fetchGit", FetchGit, 1),
("fetchMercurial", FetchMercurial, 1),
("fetchTarball", FetchTarball, 1),
("fetchTree", FetchTree, 1),
("fetchurl", FetchUrl, 1),
("filter", Filter, 2),
("filterSource", FilterSource, 2),
("findFile", FindFile, 2),
("floor", Floor, 1),
("foldl'", FoldlStrict, 3),
("fromJSON", FromJSON, 1),
("__fetchurl", FetchUrl, 1),
("__filter", Filter, 2),
("__filterSource", FilterSource, 2),
("__findFile", FindFile, 2),
("__floor", Floor, 1),
("__foldl'", FoldlStrict, 3),
("__fromJSON", FromJSON, 1),
("fromTOML", FromTOML, 1),
("functionArgs", FunctionArgs, 1),
("genList", GenList, 2),
("genericClosure", GenericClosure, 1),
("getAttr", GetAttr, 2),
("getContext", GetContext, 1),
("getEnv", GetEnv, 1),
("groupBy", GroupBy, 2),
("hasAttr", HasAttr, 2),
("hasContext", HasContext, 1),
("hashFile", HashFile, 2),
("hashString", HashString, 2),
("head", Head, 1),
("__functionArgs", FunctionArgs, 1),
("__genList", GenList, 2),
("__genericClosure", GenericClosure, 1),
("__getAttr", GetAttr, 2),
("__getContext", GetContext, 1),
("__getEnv", GetEnv, 1),
("__groupBy", GroupBy, 2),
("__hasAttr", HasAttr, 2),
("__hasContext", HasContext, 1),
("__hashFile", HashFile, 2),
("__hashString", HashString, 2),
("__head", Head, 1),
("import", Import, 1),
("intersectAttrs", IntersectAttrs, 2),
("isAttrs", IsAttrs, 1),
("isBool", IsBool, 1),
("isFloat", IsFloat, 1),
("isFunction", IsFunction, 1),
("isInt", IsInt, 1),
("isList", IsList, 1),
("__intersectAttrs", IntersectAttrs, 2),
("__isAttrs", IsAttrs, 1),
("__isBool", IsBool, 1),
("__isFloat", IsFloat, 1),
("__isFunction", IsFunction, 1),
("__isInt", IsInt, 1),
("__isList", IsList, 1),
("isNull", IsNull, 1),
("isPath", IsPath, 1),
("isString", IsString, 1),
("length", Length, 1),
("lessThan", LessThan, 2),
("listToAttrs", ListToAttrs, 1),
("__isPath", IsPath, 1),
("__isString", IsString, 1),
("__length", Length, 1),
("__lessThan", LessThan, 2),
("__listToAttrs", ListToAttrs, 1),
("map", Map, 2),
("mapAttrs", MapAttrs, 2),
("match", Match, 2),
("mul", Mul, 2),
("__mapAttrs", MapAttrs, 2),
("__match", Match, 2),
("__mul", Mul, 2),
("null", Null, 0), // constant, not a function
("parseDrvName", ParseDrvName, 1),
("partition", Partition, 2),
("path", Path, 1),
("pathExists", PathExists, 1),
("__parseDrvName", ParseDrvName, 1),
("__partition", Partition, 2),
("__path", Path, 1),
("__pathExists", PathExists, 1),
("placeholder", Placeholder, 1),
("readDir", ReadDir, 1),
("readFile", ReadFile, 1),
("readFileType", ReadFileType, 1),
("__readDir", ReadDir, 1),
("__readFile", ReadFile, 1),
("__readFileType", ReadFileType, 1),
("removeAttrs", RemoveAttrs, 2),
("replaceStrings", ReplaceStrings, 3),
("__replaceStrings", ReplaceStrings, 3),
("scopedImport", ScopedImport, 2),
("seq", Seq, 2),
("sort", Sort, 2),
("split", Split, 2),
("splitVersion", SplitVersion, 1),
("storePath", StorePath, 1),
("stringLength", StringLength, 1),
("sub", Sub, 2),
("substring", Substring, 3),
("tail", Tail, 1),
("__seq", Seq, 2),
("__sort", Sort, 2),
("__split", Split, 2),
("__splitVersion", SplitVersion, 1),
("__storePath", StorePath, 1),
("__stringLength", StringLength, 1),
("__sub", Sub, 2),
("__substring", Substring, 3),
("__tail", Tail, 1),
("throw", Throw, 1),
("toFile", ToFile, 2),
("toJSON", ToJSON, 1),
("toPath", ToPath, 1),
("__toFile", ToFile, 2),
("__toJSON", ToJSON, 1),
("__toPath", ToPath, 1),
("toString", ToString, 1),
("toXML", ToXML, 1),
("trace", Trace, 2),
("tryEval", TryEval, 1),
("typeOf", TypeOf, 1),
("unsafeDiscardStringContext", UnsafeDiscardStringContext, 1),
("unsafeGetAttrPos", UnsafeGetAttrPos, 2),
("warn", Warn, 2),
("zipAttrsWith", ZipAttrsWith, 2),
("break", Break, 1),
("__toXML", ToXML, 1),
("__trace", Trace, 2),
("__tryEval", TryEval, 1),
("__typeOf", TypeOf, 1),
("__unsafeDiscardStringContext", UnsafeDiscardStringContext, 1),
("__unsafeGetAttrPos", UnsafeGetAttrPos, 2),
("__warn", Warn, 2),
("__zipAttrsWith", ZipAttrsWith, 2),
}
/// Names that need to be pre-interned for builtin implementations.
@@ -194,54 +196,215 @@ fn intern_all_builtins(interner: &mut DefaultStringInterner) {
}
}
pub(super) fn new_builtins_env(
interner: &mut DefaultStringInterner,
) -> HashMap<StringId, Ir<'static, RawIrRef<'static>>> {
intern_all_builtins(interner);
pub(super) fn init_builtins<'gc>(
mc: &Mutation<'gc>,
strings: &mut DefaultStringInterner,
) -> (
HashMap<StringId, Ir<'static, RawIrRef<'static>>>,
Value<'gc>,
) {
let mut global_env = HashMap::new();
let builtins_sym = StringId(strings.get_or_intern("builtins"));
global_env.insert(builtins_sym, Ir::Builtins);
let mut entries = SmallVec::with_capacity(BUILTINS.len());
let mut builtins = HashMap::new();
let builtins_sym = StringId(interner.get_or_intern("builtins"));
builtins.insert(builtins_sym, Ir::Builtins);
for (idx, &(name, arity)) in BUILTINS.iter().enumerate() {
let id = BuiltinId::try_from_primitive(idx as u8).expect("infallible");
if let Some(local_name) = name.strip_prefix("__") {
// scoped builtins
let name = StringId(strings.get_or_intern(name));
let local_name = StringId(strings.get_or_intern(local_name));
global_env.insert(name, Ir::Builtin(id));
entries.push((local_name, Value::new_inline(PrimOp { id, arity })));
} else {
// global builtins
let name = StringId(strings.get_or_intern(name));
global_env.insert(name, Ir::Builtin(id));
entries.push((name, Value::new_inline(PrimOp { id, arity })));
}
}
let free_globals = [
"abort",
"baseNameOf",
"break",
"dirOf",
"derivation",
"derivationStrict",
"fetchGit",
"fetchMercurial",
"fetchTarball",
"fetchTree",
"fromTOML",
"import",
"isNull",
"map",
"placeholder",
"removeAttrs",
"scopedImport",
"throw",
"toString",
];
let consts = [
("true", Ir::Bool(true)),
("false", Ir::Bool(false)),
("null", Ir::Null),
(
"__currentSystem",
Ir::BuiltinConst(StringId(strings.get_or_intern("currentSystem"))),
// FIXME: detect currentSystem
Value::new_gc(Gc::new(mc, NixString::new("x86_64-linux"))),
),
("__langVersion", Ir::Int(6), Value::new_inline(6i32)),
(
"__nixVersion",
Ir::BuiltinConst(StringId(strings.get_or_intern("nixVersion"))),
Value::new_gc(Gc::new(mc, NixString::new("2.24.0"))),
),
(
"__storeDir",
Ir::BuiltinConst(StringId(strings.get_or_intern("storeDir"))),
Value::new_gc(Gc::new(mc, NixString::new("/nix/store"))),
),
(
"__nixPath",
Ir::BuiltinConst(StringId(strings.get_or_intern("nixPath"))),
// FIXME: get from config
Value::new_gc(Gc::new(
mc,
List {
inner: SmallVec::new(),
},
)),
),
("null", Ir::Null, Value::new_inline(Null)),
("true", Ir::Bool(true), Value::new_inline(true)),
("false", Ir::Bool(false), Value::new_inline(false)),
];
for name in free_globals {
let name = StringId(interner.get_or_intern(name));
let value = Ir::Builtin(name);
builtins.insert(name, value);
for (idx, &(name, arity)) in BUILTINS.iter().enumerate() {
let id = BuiltinId::try_from_primitive(idx as u8).expect("infallible");
if let Some(local_name) = name.strip_prefix("__") {
// scoped builtins
let name = StringId(strings.get_or_intern(name));
let local_name = StringId(strings.get_or_intern(local_name));
global_env.insert(name, Ir::Builtin(id));
entries.push((local_name, Value::new_inline(PrimOp { id, arity })));
} else {
// global builtins
let name = StringId(strings.get_or_intern(name));
global_env.insert(name, Ir::Builtin(id));
entries.push((name, Value::new_inline(PrimOp { id, arity })));
}
}
for (name, value) in consts {
let name = StringId(interner.get_or_intern(name));
builtins.insert(name, value);
for (name, ir, val) in consts {
if let Some(local_name) = name.strip_prefix("__") {
let name = StringId(strings.get_or_intern(name));
let local_name = StringId(strings.get_or_intern(local_name));
global_env.insert(name, ir);
entries.push((local_name, val));
} else {
let name = StringId(strings.get_or_intern(name));
global_env.insert(name, ir);
entries.push((name, val));
}
}
builtins
// 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);
(global_env, builtins_val)
}
pub(super) type PrimOpArgs<'gc> = [Value<'gc>; 3];
pub(super) type PrimOpStrictArgs<'gc> = [StrictValue<'gc>; 3];
#[allow(non_snake_case)]
pub(super) struct WellKnownSymbols {
abort: SymbolU32,
add: SymbolU32,
addErrorContext: SymbolU32,
all: SymbolU32,
any: SymbolU32,
appendContext: SymbolU32,
attrNames: SymbolU32,
attrValues: SymbolU32,
baseNameOf: SymbolU32,
bitAnd: SymbolU32,
bitOr: SymbolU32,
bitXor: SymbolU32,
catAttrs: SymbolU32,
ceil: SymbolU32,
compareVersions: SymbolU32,
concatLists: SymbolU32,
concatMap: SymbolU32,
concatStringsSep: SymbolU32,
convertHash: SymbolU32,
deepSeq: SymbolU32,
derivation: SymbolU32,
derivationStrict: SymbolU32,
dirOf: SymbolU32,
div: SymbolU32,
elem: SymbolU32,
elemAt: SymbolU32,
fetchGit: SymbolU32,
fetchMercurial: SymbolU32,
fetchTarball: SymbolU32,
fetchTree: SymbolU32,
fetchurl: SymbolU32,
filter: SymbolU32,
filterSource: SymbolU32,
findFile: SymbolU32,
floor: SymbolU32,
foldl: SymbolU32,
fromJSON: SymbolU32,
fromTOML: SymbolU32,
functionArgs: SymbolU32,
genList: SymbolU32,
genericClosure: SymbolU32,
getAttr: SymbolU32,
getContext: SymbolU32,
getEnv: SymbolU32,
groupBy: SymbolU32,
hasAttr: SymbolU32,
hasContext: SymbolU32,
hashFile: SymbolU32,
hashString: SymbolU32,
head: SymbolU32,
import: SymbolU32,
intersectAttrs: SymbolU32,
isAttrs: SymbolU32,
isBool: SymbolU32,
isFloat: SymbolU32,
isFunction: SymbolU32,
isInt: SymbolU32,
isList: SymbolU32,
isNull: SymbolU32,
isPath: SymbolU32,
isString: SymbolU32,
length: SymbolU32,
lessThan: SymbolU32,
listToAttrs: SymbolU32,
map: SymbolU32,
mapAttrs: SymbolU32,
match_: SymbolU32,
mul: SymbolU32,
null: SymbolU32,
parseDrvName: SymbolU32,
partition: SymbolU32,
path: SymbolU32,
pathExists: SymbolU32,
placeholder: SymbolU32,
readDir: SymbolU32,
readFile: SymbolU32,
readFileType: SymbolU32,
removeAttrs: SymbolU32,
replaceStrings: SymbolU32,
scopedImport: SymbolU32,
seq: SymbolU32,
sort: SymbolU32,
split: SymbolU32,
splitVersion: SymbolU32,
storePath: SymbolU32,
stringLength: SymbolU32,
sub: SymbolU32,
substring: SymbolU32,
tail: SymbolU32,
throw: SymbolU32,
toFile: SymbolU32,
toJSON: SymbolU32,
toPath: SymbolU32,
toString: SymbolU32,
toXML: SymbolU32,
trace: SymbolU32,
tryEval: SymbolU32,
typeOf: SymbolU32,
unsafeDiscardStringContext: SymbolU32,
unsafeGetAttrPos: SymbolU32,
warn: SymbolU32,
zipAttrsWith: SymbolU32,
break_: SymbolU32,
}
-929
View File
@@ -1,929 +0,0 @@
use gc_arena::{Collect, Gc, Mutation, RefLock};
use smallvec::SmallVec;
use string_interner::DefaultStringInterner;
use super::builtins::{BuiltinId, PrimOpArgs, PrimOpStrictArgs};
use super::value::*;
use super::vm::{ForceResult, VM, VmError};
use crate::ir::StringId;
pub(super) enum BuiltinResult<'gc> {
Done(Value<'gc>),
Force(BuiltinState<'gc>, Value<'gc>),
Call(BuiltinState<'gc>, StrictValue<'gc>, Value<'gc>),
CallAndForce(BuiltinState<'gc>, StrictValue<'gc>, Value<'gc>),
Error(VmError),
}
#[derive(Collect, Debug)]
#[collect(no_drop)]
pub(super) enum BuiltinState<'gc> {
FoldlStrict(FoldlStrict<'gc>),
// future: Filter, GenericClosure, Sort, All, Any, ConcatMap, ...
}
impl<'gc> BuiltinState<'gc> {
pub(super) fn resume(
self,
val: StrictValue<'gc>,
ctx: &PrimOpCtx<'_, 'gc>,
) -> BuiltinResult<'gc> {
match self {
BuiltinState::FoldlStrict(s) => s.resume(val, ctx),
}
}
}
pub(super) struct PrimOpCtx<'a, 'gc> {
pub(super) vm: &'a VM<'gc>,
pub(super) mc: &'a Mutation<'gc>,
pub(super) strings: &'a DefaultStringInterner,
}
macro_rules! force_inline_or_err {
($ctx:expr, $val:expr) => {{
let val = $val;
match $ctx.vm.force_inline(val) {
Ok(ForceResult::Ready(v)) => v,
Ok(_) => {
return BuiltinResult::Error(VM::err(
"value requires evaluation in non-stateful builtin context",
));
}
Err(e) => return BuiltinResult::Error(e),
}
}};
}
macro_rules! force {
($ctx:expr, $state:expr, $val:expr) => {{
let val = $val;
match $ctx.vm.force_inline(val) {
Ok(ForceResult::Ready(v)) => v,
Ok(ForceResult::NeedEval { .. } | ForceResult::NeedApply(_)) => {
return BuiltinResult::Force($state, val);
}
Err(e) => return BuiltinResult::Error(e),
}
}};
}
macro_rules! call {
($ctx:expr, $state:expr, $func:expr, $arg:expr) => {{
let func = $func;
let arg = $arg;
return BuiltinResult::Call($state, func, arg);
}};
}
macro_rules! call_and_force {
($ctx:expr, $state:expr, $func:expr, $arg:expr) => {{
let func = $func;
let arg = $arg;
return BuiltinResult::CallAndForce($state, func, arg);
}};
}
pub(super) fn dispatch_strict_builtin<'gc>(
id: BuiltinId,
args: PrimOpStrictArgs<'gc>,
_arity: u8,
ctx: &PrimOpCtx<'_, 'gc>,
) -> BuiltinResult<'gc> {
match id {
BuiltinId::TypeOf => {
let val = args[0];
let name = if val.as_inline::<i32>().is_some() || val.as_gc::<i64>().is_some() {
"int"
} else if val.as_float().is_some() {
"float"
} else if val.as_inline::<bool>().is_some() {
"bool"
} else if VM::get_string(val, ctx.strings).is_some() {
"string"
} else if val.is::<Null>() {
"null"
} else if val.as_gc::<AttrSet<'gc>>().is_some() {
"set"
} else if val.as_gc::<List<'gc>>().is_some() {
"list"
} else if val.as_gc::<Closure<'gc>>().is_some()
|| val.as_inline::<PrimOp>().is_some()
|| val.as_gc::<PrimOpApp<'gc>>().is_some()
{
"lambda"
} else {
return BuiltinResult::Error(VM::err("typeOf: unknown type"));
};
let sid = ctx.strings.get(name).expect("typeOf string not interned");
BuiltinResult::Done(Value::new_inline(StringId(sid)))
}
BuiltinId::IsNull => BuiltinResult::Done(Value::new_inline(args[0].is::<Null>())),
BuiltinId::IsAttrs => {
BuiltinResult::Done(Value::new_inline(args[0].as_gc::<AttrSet<'gc>>().is_some()))
}
BuiltinId::IsBool => {
BuiltinResult::Done(Value::new_inline(args[0].as_inline::<bool>().is_some()))
}
BuiltinId::IsFloat => BuiltinResult::Done(Value::new_inline(args[0].as_float().is_some())),
BuiltinId::IsFunction => {
let v = args[0];
let is_func = v.as_gc::<Closure<'gc>>().is_some()
|| v.as_inline::<PrimOp>().is_some()
|| v.as_gc::<PrimOpApp<'gc>>().is_some();
BuiltinResult::Done(Value::new_inline(is_func))
}
BuiltinId::IsInt => {
let v = args[0];
let is_int = v.as_inline::<i32>().is_some() || v.as_gc::<i64>().is_some();
BuiltinResult::Done(Value::new_inline(is_int))
}
BuiltinId::IsList => {
BuiltinResult::Done(Value::new_inline(args[0].as_gc::<List<'gc>>().is_some()))
}
BuiltinId::IsString => {
let v = args[0];
let is_str = v.as_inline::<StringId>().is_some() || v.as_gc::<NixString>().is_some();
BuiltinResult::Done(Value::new_inline(is_str))
}
BuiltinId::IsPath => BuiltinResult::Done(Value::new_inline(false)),
BuiltinId::Length => {
let Some(list) = args[0].as_gc::<List<'gc>>() else {
return BuiltinResult::Error(VM::err("builtins.length: not a list"));
};
BuiltinResult::Done(VM::make_int(list.inner.len() as i64, ctx.mc))
}
BuiltinId::Head => {
let Some(list) = args[0].as_gc::<List<'gc>>() else {
return BuiltinResult::Error(VM::err("builtins.head: not a list"));
};
if list.inner.is_empty() {
return BuiltinResult::Error(VM::err("builtins.head: empty list"));
}
BuiltinResult::Done(list.inner[0])
}
BuiltinId::Tail => {
let Some(list) = args[0].as_gc::<List<'gc>>() else {
return BuiltinResult::Error(VM::err("builtins.tail: not a list"));
};
if list.inner.is_empty() {
return BuiltinResult::Error(VM::err("builtins.tail: empty list"));
}
let tail = List {
inner: SmallVec::from_slice(&list.inner[1..]),
};
BuiltinResult::Done(Value::new_gc(Gc::new(ctx.mc, tail)))
}
BuiltinId::AttrNames => {
let Some(attrs) = args[0].as_gc::<AttrSet<'gc>>() else {
return BuiltinResult::Error(VM::err("builtins.attrNames: not a set"));
};
let items: SmallVec<[Value<'gc>; 4]> =
attrs.iter().map(|(k, _)| Value::new_inline(*k)).collect();
BuiltinResult::Done(Value::new_gc(Gc::new(ctx.mc, List { inner: items })))
}
BuiltinId::AttrValues => {
let Some(attrs) = args[0].as_gc::<AttrSet<'gc>>() else {
return BuiltinResult::Error(VM::err("builtins.attrValues: not a set"));
};
let items: SmallVec<[Value<'gc>; 4]> = attrs.iter().map(|(_, v)| *v).collect();
BuiltinResult::Done(Value::new_gc(Gc::new(ctx.mc, List { inner: items })))
}
BuiltinId::Map => {
let f = args[0];
let Some(list) = args[1].as_gc::<List<'gc>>() else {
return BuiltinResult::Error(VM::err(
"builtins.map: second argument is not a list",
));
};
if list.inner.is_empty() {
return BuiltinResult::Done(Value::new_gc(Gc::new(
ctx.mc,
List {
inner: SmallVec::new(),
},
)));
}
let new_elems: SmallVec<[Value<'gc>; 4]> = list
.inner
.iter()
.map(|elem| {
let thunk: Gc<'gc, Thunk<'gc>> = Gc::new(
ctx.mc,
RefLock::new(ThunkState::Apply {
func: f.relax(),
arg: *elem,
}),
);
Value::new_gc(thunk)
})
.collect();
BuiltinResult::Done(Value::new_gc(Gc::new(ctx.mc, List { inner: new_elems })))
}
BuiltinId::GenList => {
let f = args[0];
let len_val = args[1];
let Some(len) = VM::as_num(len_val) else {
return BuiltinResult::Error(VM::err(
"builtins.genList: second argument is not a number",
));
};
let super::vm::NixNum::Int(len) = len else {
return BuiltinResult::Error(VM::err(
"builtins.genList: second argument is not an integer",
));
};
if len < 0 {
return BuiltinResult::Error(VM::err("builtins.genList: negative length"));
}
let items: SmallVec<[Value<'gc>; 4]> = (0..len)
.map(|i| {
let arg = VM::make_int(i, ctx.mc);
let thunk: Gc<'gc, Thunk<'gc>> = Gc::new(
ctx.mc,
RefLock::new(ThunkState::Apply {
func: f.relax(),
arg,
}),
);
Value::new_gc(thunk)
})
.collect();
BuiltinResult::Done(Value::new_gc(Gc::new(ctx.mc, List { inner: items })))
}
BuiltinId::ElemAt => {
let Some(list) = args[0].as_gc::<List<'gc>>() else {
return BuiltinResult::Error(VM::err("builtins.elemAt: not a list"));
};
let Some(idx) = VM::as_num(args[1]) else {
return BuiltinResult::Error(VM::err("builtins.elemAt: index is not a number"));
};
let super::vm::NixNum::Int(idx) = idx else {
return BuiltinResult::Error(VM::err("builtins.elemAt: index is not an integer"));
};
if idx < 0 || idx as usize >= list.inner.len() {
return BuiltinResult::Error(VM::err(format!(
"builtins.elemAt: index {} out of bounds for list of length {}",
idx,
list.inner.len()
)));
}
BuiltinResult::Done(list.inner[idx as usize])
}
BuiltinId::GetAttr => {
let Some(name) = VM::get_string(args[0], ctx.strings) else {
return BuiltinResult::Error(VM::err(
"builtins.getAttr: first argument is not a string",
));
};
let Some(sid) = ctx.strings.get(name) else {
return BuiltinResult::Error(VM::err(format!(
"builtins.getAttr: attribute '{}' not found",
name
)));
};
let Some(attrs) = args[1].as_gc::<AttrSet<'gc>>() else {
return BuiltinResult::Error(VM::err(
"builtins.getAttr: second argument is not a set",
));
};
match attrs.lookup(StringId(sid)) {
Some(v) => BuiltinResult::Done(v),
None => BuiltinResult::Error(VM::err(format!("attribute '{}' missing", name))),
}
}
BuiltinId::HasAttr => {
let Some(name) = VM::get_string(args[0], ctx.strings) else {
return BuiltinResult::Error(VM::err(
"builtins.hasAttr: first argument is not a string",
));
};
let Some(attrs) = args[1].as_gc::<AttrSet<'gc>>() else {
return BuiltinResult::Error(VM::err(
"builtins.hasAttr: second argument is not a set",
));
};
let has = ctx
.strings
.get(name)
.map(|sid| attrs.has(StringId(sid)))
.unwrap_or(false);
BuiltinResult::Done(Value::new_inline(has))
}
BuiltinId::RemoveAttrs => {
let Some(attrs) = args[0].as_gc::<AttrSet<'gc>>() else {
return BuiltinResult::Error(VM::err(
"builtins.removeAttrs: first argument is not a set",
));
};
let Some(remove_list) = args[1].as_gc::<List<'gc>>() else {
return BuiltinResult::Error(VM::err(
"builtins.removeAttrs: second argument is not a list",
));
};
let mut to_remove = Vec::new();
for item in remove_list.inner.iter() {
let sv = force_inline_or_err!(ctx, *item);
if let Some(s) = VM::get_string(sv, ctx.strings)
&& let Some(sid) = ctx.strings.get(s)
{
to_remove.push(StringId(sid));
}
}
let entries: SmallVec<[(StringId, Value<'gc>); 4]> = attrs
.iter()
.filter(|(k, _)| !to_remove.contains(k))
.cloned()
.collect();
let new_attrs = Gc::new(ctx.mc, unsafe { AttrSet::from_sorted_unchecked(entries) });
BuiltinResult::Done(Value::new_gc(new_attrs))
}
BuiltinId::IntersectAttrs => {
let Some(a) = args[0].as_gc::<AttrSet<'gc>>() else {
return BuiltinResult::Error(VM::err(
"builtins.intersectAttrs: first argument is not a set",
));
};
let Some(b) = args[1].as_gc::<AttrSet<'gc>>() else {
return BuiltinResult::Error(VM::err(
"builtins.intersectAttrs: second argument is not a set",
));
};
let entries: SmallVec<[(StringId, Value<'gc>); 4]> =
b.iter().filter(|(k, _)| a.has(*k)).cloned().collect();
let new_attrs = Gc::new(ctx.mc, unsafe { AttrSet::from_sorted_unchecked(entries) });
BuiltinResult::Done(Value::new_gc(new_attrs))
}
BuiltinId::ListToAttrs => {
let Some(list) = args[0].as_gc::<List<'gc>>() else {
return BuiltinResult::Error(VM::err("builtins.listToAttrs: not a list"));
};
let name_sid = ctx.strings.get("name").expect("'name' not interned");
let value_sid = ctx.strings.get("value").expect("'value' not interned");
let mut entries = SmallVec::<[(StringId, Value<'gc>); 4]>::new();
for item in list.inner.iter() {
let sv = force_inline_or_err!(ctx, *item);
let Some(attr_set) = sv.as_gc::<AttrSet<'gc>>() else {
return BuiltinResult::Error(VM::err(
"builtins.listToAttrs: element is not a set",
));
};
let Some(name_val) = attr_set.lookup(StringId(name_sid)) else {
return BuiltinResult::Error(VM::err(
"builtins.listToAttrs: element missing 'name'",
));
};
let name_sv = force_inline_or_err!(ctx, name_val);
let Some(name_str) = VM::get_string(name_sv, ctx.strings) else {
return BuiltinResult::Error(VM::err(
"builtins.listToAttrs: 'name' is not a string",
));
};
let Some(value_val) = attr_set.lookup(StringId(value_sid)) else {
return BuiltinResult::Error(VM::err(
"builtins.listToAttrs: element missing 'value'",
));
};
let Some(key_sym) = ctx.strings.get(name_str) else {
return BuiltinResult::Error(VM::err(
"builtins.listToAttrs: name not interned",
));
};
entries.push((StringId(key_sym), value_val));
}
entries.sort_by_key(|(k, _)| *k);
entries.dedup_by_key(|(k, _)| *k);
let new_attrs = Gc::new(ctx.mc, unsafe { AttrSet::from_sorted_unchecked(entries) });
BuiltinResult::Done(Value::new_gc(new_attrs))
}
BuiltinId::ConcatLists => {
let Some(list) = args[0].as_gc::<List<'gc>>() else {
return BuiltinResult::Error(VM::err("builtins.concatLists: not a list"));
};
let mut result = SmallVec::<[Value<'gc>; 4]>::new();
for item in list.inner.iter() {
let sv = force_inline_or_err!(ctx, *item);
let Some(inner) = sv.as_gc::<List<'gc>>() else {
return BuiltinResult::Error(VM::err(
"builtins.concatLists: element is not a list",
));
};
result.extend(inner.inner.iter().cloned());
}
BuiltinResult::Done(Value::new_gc(Gc::new(ctx.mc, List { inner: result })))
}
BuiltinId::LessThan => {
match ctx
.vm
.compare_values(args[0], args[1], ctx.strings, |o| o.is_lt())
{
Ok(v) => BuiltinResult::Done(v),
Err(e) => BuiltinResult::Error(e),
}
}
BuiltinId::Add => {
match ctx.vm.compute_binop(
super::vm::BinOpTag::Add,
args[0],
args[1],
ctx.mc,
ctx.strings,
) {
Ok(v) => BuiltinResult::Done(v),
Err(e) => BuiltinResult::Error(e),
}
}
BuiltinId::Sub => {
match ctx
.vm
.numeric_binop(args[0], args[1], ctx.mc, i64::wrapping_sub, |a, b| a - b)
{
Ok(v) => BuiltinResult::Done(v),
Err(e) => BuiltinResult::Error(e),
}
}
BuiltinId::Mul => {
match ctx
.vm
.numeric_binop(args[0], args[1], ctx.mc, i64::wrapping_mul, |a, b| a * b)
{
Ok(v) => BuiltinResult::Done(v),
Err(e) => BuiltinResult::Error(e),
}
}
BuiltinId::Div => {
match ctx.vm.compute_binop(
super::vm::BinOpTag::Div,
args[0],
args[1],
ctx.mc,
ctx.strings,
) {
Ok(v) => BuiltinResult::Done(v),
Err(e) => BuiltinResult::Error(e),
}
}
BuiltinId::BitAnd => {
let (Some(super::vm::NixNum::Int(a)), Some(super::vm::NixNum::Int(b))) =
(VM::as_num(args[0]), VM::as_num(args[1]))
else {
return BuiltinResult::Error(VM::err(
"builtins.bitAnd: arguments must be integers",
));
};
BuiltinResult::Done(VM::make_int(a & b, ctx.mc))
}
BuiltinId::BitOr => {
let (Some(super::vm::NixNum::Int(a)), Some(super::vm::NixNum::Int(b))) =
(VM::as_num(args[0]), VM::as_num(args[1]))
else {
return BuiltinResult::Error(VM::err("builtins.bitOr: arguments must be integers"));
};
BuiltinResult::Done(VM::make_int(a | b, ctx.mc))
}
BuiltinId::BitXor => {
let (Some(super::vm::NixNum::Int(a)), Some(super::vm::NixNum::Int(b))) =
(VM::as_num(args[0]), VM::as_num(args[1]))
else {
return BuiltinResult::Error(VM::err(
"builtins.bitXor: arguments must be integers",
));
};
BuiltinResult::Done(VM::make_int(a ^ b, ctx.mc))
}
BuiltinId::Ceil => {
if let Some(f) = args[0].as_float() {
BuiltinResult::Done(VM::make_int(f.ceil() as i64, ctx.mc))
} else if VM::as_num(args[0]).is_some() {
BuiltinResult::Done(args[0].relax())
} else {
BuiltinResult::Error(VM::err("builtins.ceil: not a number"))
}
}
BuiltinId::Floor => {
if let Some(f) = args[0].as_float() {
BuiltinResult::Done(VM::make_int(f.floor() as i64, ctx.mc))
} else if VM::as_num(args[0]).is_some() {
BuiltinResult::Done(args[0].relax())
} else {
BuiltinResult::Error(VM::err("builtins.floor: not a number"))
}
}
BuiltinId::StringLength => {
let Some(s) = VM::get_string(args[0], ctx.strings) else {
return BuiltinResult::Error(VM::err("builtins.stringLength: not a string"));
};
BuiltinResult::Done(VM::make_int(s.len() as i64, ctx.mc))
}
BuiltinId::Substring => {
let Some(super::vm::NixNum::Int(start)) = VM::as_num(args[0]) else {
return BuiltinResult::Error(VM::err(
"builtins.substring: start is not an integer",
));
};
let Some(super::vm::NixNum::Int(len)) = VM::as_num(args[1]) else {
return BuiltinResult::Error(VM::err(
"builtins.substring: length is not an integer",
));
};
let Some(s) = VM::get_string(args[2], ctx.strings) else {
return BuiltinResult::Error(VM::err(
"builtins.substring: third argument is not a string",
));
};
let start = start.max(0) as usize;
if start >= s.len() {
let ns = Gc::new(ctx.mc, NixString::new(""));
return BuiltinResult::Done(Value::new_gc(ns));
}
let end = if len < 0 {
s.len()
} else {
(start + len as usize).min(s.len())
};
let result = &s[start..end];
let ns = Gc::new(ctx.mc, NixString::new(result));
BuiltinResult::Done(Value::new_gc(ns))
}
BuiltinId::ToString => {
let v = args[0];
if let Some(s) = VM::get_string(v, ctx.strings) {
let ns = Gc::new(ctx.mc, NixString::new(s));
BuiltinResult::Done(Value::new_gc(ns))
} else if let Some(b) = v.as_inline::<bool>() {
let s = if b { "1" } else { "" };
let ns = Gc::new(ctx.mc, NixString::new(s));
BuiltinResult::Done(Value::new_gc(ns))
} else if v.is::<Null>() {
let ns = Gc::new(ctx.mc, NixString::new(""));
BuiltinResult::Done(Value::new_gc(ns))
} else if let Some(n) = VM::as_num(v) {
let s = match n {
super::vm::NixNum::Int(i) => i.to_string(),
super::vm::NixNum::Float(f) => format!("{f}"),
};
let ns = Gc::new(ctx.mc, NixString::new(s));
BuiltinResult::Done(Value::new_gc(ns))
} else {
BuiltinResult::Error(VM::err("builtins.toString: cannot coerce to string"))
}
}
BuiltinId::Abort => {
let Some(msg) = VM::get_string(args[0], ctx.strings) else {
return BuiltinResult::Error(VM::err("builtins.abort: argument is not a string"));
};
BuiltinResult::Error(VmError::Uncatchable(crate::error::Error::eval_error(
format!("evaluation aborted with the following error message: '{msg}'"),
)))
}
BuiltinId::Throw => {
let Some(msg) = VM::get_string(args[0], ctx.strings) else {
return BuiltinResult::Error(VM::err("builtins.throw: argument is not a string"));
};
BuiltinResult::Error(VmError::Catchable(msg.to_owned()))
}
BuiltinId::FunctionArgs => {
let v = args[0];
if let Some(closure) = v.as_gc::<Closure<'gc>>() {
if let Some(ref pattern) = closure.pattern {
let mut entries = SmallVec::<[(StringId, Value<'gc>); 4]>::new();
for &name in &pattern.required {
entries.push((name, Value::new_inline(false)));
}
for &name in &pattern.optional {
entries.push((name, Value::new_inline(true)));
}
entries.sort_by_key(|(k, _)| *k);
let attrs = Gc::new(ctx.mc, unsafe { AttrSet::from_sorted_unchecked(entries) });
BuiltinResult::Done(Value::new_gc(attrs))
} else {
BuiltinResult::Done(Value::new_gc(Gc::new(ctx.mc, AttrSet::default())))
}
} else {
BuiltinResult::Done(Value::new_gc(Gc::new(ctx.mc, AttrSet::default())))
}
}
BuiltinId::FoldlStrict => FoldlStrict::call(args, ctx),
BuiltinId::Elem => {
let Some(list) = args[1].as_gc::<List<'gc>>() else {
return BuiltinResult::Error(VM::err(
"builtins.elem: second argument is not a list",
));
};
let needle = args[0];
for item in list.inner.iter() {
let sv = force_inline_or_err!(ctx, *item);
if ctx.vm.values_equal(needle, sv, ctx.strings) {
return BuiltinResult::Done(Value::new_inline(true));
}
}
BuiltinResult::Done(Value::new_inline(false))
}
BuiltinId::ReplaceStrings => {
let Some(from_list) = args[0].as_gc::<List<'gc>>() else {
return BuiltinResult::Error(VM::err(
"builtins.replaceStrings: first argument is not a list",
));
};
let Some(to_list) = args[1].as_gc::<List<'gc>>() else {
return BuiltinResult::Error(VM::err(
"builtins.replaceStrings: second argument is not a list",
));
};
let Some(s) = VM::get_string(args[2], ctx.strings) else {
return BuiltinResult::Error(VM::err(
"builtins.replaceStrings: third argument is not a string",
));
};
if from_list.inner.len() != to_list.inner.len() {
return BuiltinResult::Error(VM::err(
"builtins.replaceStrings: lists must have same length",
));
}
let mut from_strs = Vec::new();
let mut to_strs = Vec::new();
for (f, t) in from_list.inner.iter().zip(to_list.inner.iter()) {
let fv = force_inline_or_err!(ctx, *f);
let tv = force_inline_or_err!(ctx, *t);
let Some(fs) = VM::get_string(fv, ctx.strings) else {
return BuiltinResult::Error(VM::err(
"builtins.replaceStrings: from element is not a string",
));
};
let Some(ts) = VM::get_string(tv, ctx.strings) else {
return BuiltinResult::Error(VM::err(
"builtins.replaceStrings: to element is not a string",
));
};
from_strs.push(fs.to_owned());
to_strs.push(ts.to_owned());
}
let s = s.to_owned();
let mut result = String::new();
let mut i = 0;
while i < s.len() {
let mut found = false;
for (j, from) in from_strs.iter().enumerate() {
if from.is_empty() {
result.push_str(&to_strs[j]);
result.push(s.as_bytes()[i] as char);
i += 1;
found = true;
break;
}
if s[i..].starts_with(from.as_str()) {
result.push_str(&to_strs[j]);
i += from.len();
found = true;
break;
}
}
if !found {
result.push(s.as_bytes()[i] as char);
i += 1;
}
}
if from_strs.iter().any(|f| f.is_empty()) {
let j = from_strs
.iter()
.position(|f| f.is_empty())
.expect("just checked");
result.push_str(&to_strs[j]);
}
let ns = Gc::new(ctx.mc, NixString::new(result));
BuiltinResult::Done(Value::new_gc(ns))
}
BuiltinId::ConcatStringsSep => {
let Some(sep) = VM::get_string(args[0], ctx.strings) else {
return BuiltinResult::Error(VM::err(
"builtins.concatStringsSep: first argument is not a string",
));
};
let sep = sep.to_owned();
let Some(list) = args[1].as_gc::<List<'gc>>() else {
return BuiltinResult::Error(VM::err(
"builtins.concatStringsSep: second argument is not a list",
));
};
let mut result = String::new();
for (i, item) in list.inner.iter().enumerate() {
if i > 0 {
result.push_str(&sep);
}
let sv = force_inline_or_err!(ctx, *item);
let Some(s) = VM::get_string(sv, ctx.strings) else {
return BuiltinResult::Error(VM::err(
"builtins.concatStringsSep: element is not a string",
));
};
result.push_str(s);
}
let ns = Gc::new(ctx.mc, NixString::new(result));
BuiltinResult::Done(Value::new_gc(ns))
}
BuiltinId::FromJSON => {
let Some(s) = VM::get_string(args[0], ctx.strings) else {
return BuiltinResult::Error(VM::err("builtins.fromJSON: not a string"));
};
match serde_json::from_str::<serde_json::Value>(s) {
Ok(json) => {
let v = json_to_nix(&json, ctx.mc, ctx.strings);
BuiltinResult::Done(v)
}
Err(e) => BuiltinResult::Error(VM::err(format!("builtins.fromJSON: {e}"))),
}
}
BuiltinId::ToJSON => BuiltinResult::Error(VM::err("builtins.toJSON: not yet implemented")),
BuiltinId::Null => BuiltinResult::Done(Value::new_inline(Null)),
_ => BuiltinResult::Error(VM::err(format!("builtin {:?} not yet implemented", id))),
}
}
pub(super) fn dispatch_lazy_builtin<'gc>(
id: BuiltinId,
args: &PrimOpArgs<'gc>,
_arity: u8,
ctx: &PrimOpCtx<'_, 'gc>,
) -> BuiltinResult<'gc> {
match id {
BuiltinId::Seq => {
let _ = force_inline_or_err!(ctx, args[0]);
BuiltinResult::Done(args[1])
}
BuiltinId::DeepSeq => {
// TODO: deep force
let _ = force_inline_or_err!(ctx, args[0]);
BuiltinResult::Done(args[1])
}
BuiltinId::Trace => {
let sv = force_inline_or_err!(ctx, args[0]);
if let Some(s) = VM::get_string(sv, ctx.strings) {
eprintln!("trace: {s}");
} else {
eprintln!("trace: <non-string>");
}
BuiltinResult::Done(args[1])
}
BuiltinId::Warn => {
let sv = force_inline_or_err!(ctx, args[0]);
if let Some(s) = VM::get_string(sv, ctx.strings) {
eprintln!("warning: {s}");
} else {
eprintln!("warning: <non-string>");
}
BuiltinResult::Done(args[1])
}
BuiltinId::TryEval => BuiltinResult::Error(VM::err(
"builtins.tryEval: requires catch frame support (TODO)",
)),
BuiltinId::AddErrorContext => BuiltinResult::Done(args[1]),
BuiltinId::Break => BuiltinResult::Done(args[0]),
_ => BuiltinResult::Error(VM::err(format!(
"lazy builtin {:?} not yet implemented",
id
))),
}
}
fn json_to_nix<'gc>(
json: &serde_json::Value,
mc: &Mutation<'gc>,
strings: &DefaultStringInterner,
) -> Value<'gc> {
match json {
serde_json::Value::Null => Value::new_inline(Null),
serde_json::Value::Bool(b) => Value::new_inline(*b),
serde_json::Value::Number(n) => {
if let Some(i) = n.as_i64() {
VM::make_int(i, mc)
} else if let Some(f) = n.as_f64() {
Value::new_float(f)
} else {
Value::new_inline(Null)
}
}
serde_json::Value::String(s) => {
let ns = Gc::new(mc, NixString::new(s.as_str()));
Value::new_gc(ns)
}
serde_json::Value::Array(arr) => {
let items: SmallVec<[Value<'gc>; 4]> =
arr.iter().map(|v| json_to_nix(v, mc, strings)).collect();
Value::new_gc(Gc::new(mc, List { inner: items }))
}
serde_json::Value::Object(obj) => {
let mut entries = SmallVec::<[(StringId, Value<'gc>); 4]>::new();
for (k, v) in obj {
if let Some(sym) = strings.get(k.as_str()) {
entries.push((StringId(sym), json_to_nix(v, mc, strings)));
}
}
entries.sort_by_key(|(k, _)| *k);
let attrs = Gc::new(mc, unsafe { AttrSet::from_sorted_unchecked(entries) });
Value::new_gc(attrs)
}
}
}
#[derive(Collect, Debug)]
#[collect(no_drop)]
pub(super) struct FoldlStrict<'gc> {
op: StrictValue<'gc>,
list: Gc<'gc, List<'gc>>,
acc: StrictValue<'gc>,
index: usize,
phase: FoldlPhase<'gc>,
}
#[derive(Collect, Debug)]
#[collect(no_drop)]
enum FoldlPhase<'gc> {
CallOp,
CallPartial(StrictValue<'gc>),
}
impl<'gc> FoldlStrict<'gc> {
fn call(args: PrimOpStrictArgs<'gc>, ctx: &PrimOpCtx<'_, 'gc>) -> BuiltinResult<'gc> {
let op = args[0];
let nul = args[1];
let Some(list) = args[2].as_gc::<List<'gc>>() else {
return BuiltinResult::Error(VM::err("builtins.foldl': third argument is not a list"));
};
if list.inner.is_empty() {
return BuiltinResult::Done(nul.relax());
}
let state = FoldlStrict {
op,
list,
acc: nul,
index: 0,
phase: FoldlPhase::CallOp,
};
state.step(ctx)
}
fn step(self, _ctx: &PrimOpCtx<'_, 'gc>) -> BuiltinResult<'gc> {
let state = BuiltinState::FoldlStrict(FoldlStrict {
op: self.op,
list: self.list,
acc: self.acc,
index: self.index,
phase: FoldlPhase::CallOp,
});
call!(ctx, state, self.op, self.acc.relax())
}
fn resume(mut self, val: StrictValue<'gc>, ctx: &PrimOpCtx<'_, 'gc>) -> BuiltinResult<'gc> {
match self.phase {
FoldlPhase::CallOp => {
let partial = val;
let elem = self.list.inner[self.index];
self.phase = FoldlPhase::CallPartial(partial);
let state = BuiltinState::FoldlStrict(self);
call_and_force!(ctx, state, partial, elem)
}
FoldlPhase::CallPartial(_) => {
self.acc = val;
self.index += 1;
self.phase = FoldlPhase::CallOp;
if self.index >= self.list.inner.len() {
return BuiltinResult::Done(self.acc.relax());
}
self.step(ctx)
}
}
}
}
-7
View File
@@ -33,13 +33,6 @@ impl<const N: usize, T> Stack<N, T> {
}
}
pub(super) unsafe fn push_unchecked(&mut self, val: T) {
unsafe {
self.inner.get_unchecked_mut(self.len).write(val);
}
self.len += 1;
}
pub(super) fn push(&mut self, val: T) -> Result<(), T> {
if self.len == N {
return Err(val);
+135 -23
View File
@@ -3,18 +3,22 @@ use std::marker::PhantomData;
use std::mem::size_of;
use std::ops::Deref;
use gc_arena::{Collect, Gc, Mutation, RefLock, collect::Trace};
use gc_arena::collect::Trace;
use gc_arena::{Collect, Gc, Mutation, RefLock};
use num_enum::TryFromPrimitive;
use sealed::sealed;
use smallvec::SmallVec;
use string_interner::{Symbol, symbol::SymbolU32};
use string_interner::Symbol;
use string_interner::symbol::SymbolU32;
use crate::boxing::{RawBox, RawStore, RawTag, Value as RawValue};
use crate::{ir::StringId, runtime::builtins::BuiltinId};
use crate::ir::StringId;
use crate::runtime::builtins::BuiltinId;
#[sealed]
/// # Safety
/// TAG must be unique among all implementors.
/// 1. TAG must be unique among all implementors.
/// 2. TAG must be within 1..=7
pub(crate) unsafe trait Storable {
const TAG: (bool, u8);
}
@@ -89,7 +93,21 @@ macro_rules! define_value_types {
self.as_inline::<$itype>().unwrap_unchecked()
}),)*
$(Some(<$gtype as Storable>::TAG) =>
write!(f, "{}({:?})", $gname, unsafe { self.as_gc::<$gtype>().unwrap_unchecked() }),)*
write!(f, "{}(..)", $gname),)*
Some((neg, val)) => write!(f, "Unknown(neg={neg}, val={val})"),
}
}
}
impl fmt::Debug for StaticValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.tag() {
None => write!(f, "Float({:?})", unsafe {
self.raw.float().unwrap_unchecked()
}),
$(Some(<$itype as Storable>::TAG) => write!(f, "{}({:?})", $iname, unsafe {
self.as_inline::<$itype>().unwrap_unchecked()
}),)*
Some((neg, val)) => write!(f, "Unknown(neg={neg}, val={val})"),
}
}
@@ -134,12 +152,6 @@ impl Default for Value<'_> {
}
impl<'gc> Value<'gc> {
#[inline(always)]
fn mk_tag(neg: bool, val: u8) -> RawTag {
// Safety: val is asserted to be in 1..=7.
unsafe { RawTag::new_unchecked(neg, val) }
}
#[inline(always)]
fn from_raw_value(rv: RawValue) -> Self {
Self {
@@ -180,13 +192,28 @@ impl<'gc> Value<'gc> {
#[inline]
pub(crate) fn new_inline<T: InlineStorable>(val: T) -> Self {
Self::from_raw_value(RawValue::store(Self::mk_tag(T::TAG.0, T::TAG.1), val))
Self::from_raw_value(RawValue::store(
unsafe { RawTag::new_unchecked(T::TAG.0, T::TAG.1) },
val,
))
}
#[inline]
pub(crate) fn new_gc<T: GcStorable>(gc: Gc<'gc, T>) -> Self {
let ptr = Gc::as_ptr(gc);
Self::from_raw_value(RawValue::store(Self::mk_tag(T::TAG.0, T::TAG.1), ptr))
Self::from_raw_value(RawValue::store(
unsafe { RawTag::new_unchecked(T::TAG.0, T::TAG.1) },
ptr,
))
}
#[inline]
pub(crate) fn make_int(val: i64, mc: &Mutation<'gc>) -> Self {
if val >= i32::MIN as i64 && val <= i32::MAX as i64 {
Value::new_inline(val as i32)
} else {
Value::new_gc(Gc::new(mc, val))
}
}
}
@@ -232,6 +259,100 @@ impl<'gc> Value<'gc> {
None
}
}
#[inline]
pub(crate) fn restrict(self) -> Option<StrictValue<'gc>> {
if !self.is::<Thunk<'gc>>() {
Some(StrictValue(self))
} else {
None
}
}
}
#[derive(Copy, Clone)]
#[repr(transparent)]
pub(crate) struct StaticValue {
raw: RawBox,
}
impl Default for StaticValue {
#[inline(always)]
fn default() -> Self {
Self::new_inline(Null)
}
}
impl From<StaticValue> for Value<'_> {
#[inline]
fn from(value: StaticValue) -> Self {
Self {
raw: value.raw,
_marker: PhantomData,
}
}
}
impl StaticValue {
#[inline(always)]
fn from_raw_value(rv: RawValue) -> Self {
Self {
raw: RawBox::from_value(rv),
}
}
#[inline]
pub(crate) fn to_bits(self) -> u64 {
self.raw.to_bits()
}
#[inline]
pub(crate) fn new_float(val: f64) -> Self {
Self {
raw: RawBox::from_float(val),
}
}
#[inline]
pub(crate) fn new_inline<T: InlineStorable>(val: T) -> Self {
Self::from_raw_value(RawValue::store(
unsafe { RawTag::new_unchecked(T::TAG.0, T::TAG.1) },
val,
))
}
#[inline]
pub(crate) fn is_float(&self) -> bool {
self.raw.is_float()
}
#[inline]
pub(crate) fn is<T: InlineStorable>(&self) -> bool {
self.tag() == Some(T::TAG)
}
#[inline]
pub(crate) fn as_float(&self) -> Option<f64> {
self.raw.float().copied()
}
#[inline]
pub(crate) fn as_inline<T: InlineStorable>(&self) -> Option<T> {
if self.is::<T>() {
Some(unsafe {
let rv = self.raw.value().unwrap_unchecked();
T::from_val(rv)
})
} else {
None
}
}
/// Returns the `(negative, val)` tag, or `None` for a float.
#[inline(always)]
fn tag(&self) -> Option<(bool, u8)> {
self.raw.tag().map(|t| t.neg_val())
}
}
#[derive(Clone, Copy, Debug)]
@@ -364,7 +485,7 @@ pub(crate) type Thunk<'gc> = RefLock<ThunkState<'gc>>;
#[collect(no_drop)]
pub(crate) enum ThunkState<'gc> {
Pending {
ip: u32,
ip: usize,
env: Gc<'gc, RefLock<Env<'gc>>>,
},
Apply {
@@ -454,15 +575,6 @@ pub(crate) struct PrimOpApp<'gc> {
pub(crate) struct StrictValue<'gc>(Value<'gc>);
impl<'gc> StrictValue<'gc> {
#[inline]
pub(crate) fn try_from_forced(val: Value<'gc>) -> Option<Self> {
if !val.is::<Thunk<'gc>>() {
Some(Self(val))
} else {
None
}
}
#[inline]
pub(crate) fn relax(self) -> Value<'gc> {
self.0
+943 -1977
View File
File diff suppressed because it is too large Load Diff