refactor
This commit is contained in:
Generated
+193
-2163
File diff suppressed because it is too large
Load Diff
+27
@@ -2,8 +2,35 @@
|
|||||||
resolver = "3"
|
resolver = "3"
|
||||||
members = [
|
members = [
|
||||||
"fix",
|
"fix",
|
||||||
|
"fix-builtins",
|
||||||
|
"fix-codegen",
|
||||||
|
"fix-common",
|
||||||
|
"fix-error",
|
||||||
|
"fix-ir",
|
||||||
|
"fix-vm",
|
||||||
]
|
]
|
||||||
|
|
||||||
[profile.profiling]
|
[profile.profiling]
|
||||||
inherits = "release"
|
inherits = "release"
|
||||||
debug = true
|
debug = true
|
||||||
|
|
||||||
|
[workspace.dependencies]
|
||||||
|
bumpalo = { version = "3.20", features = [
|
||||||
|
"allocator-api2",
|
||||||
|
"boxed",
|
||||||
|
"collections",
|
||||||
|
] }
|
||||||
|
ghost-cell = "0.2"
|
||||||
|
hashbrown = "0.16"
|
||||||
|
num_enum = "0.7.5"
|
||||||
|
smallvec = "1.15"
|
||||||
|
ere = "0.2"
|
||||||
|
string-interner = "0.19"
|
||||||
|
rnix = "0.14"
|
||||||
|
rowan = "0.16"
|
||||||
|
likely_stable = "0.1"
|
||||||
|
|
||||||
|
[workspace.dependencies.gc-arena]
|
||||||
|
git = "https://github.com/kyren/gc-arena"
|
||||||
|
rev = "75671ae03f53718357b741ed4027560f14e90836"
|
||||||
|
features = ["allocator-api2", "hashbrown", "smallvec"]
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
[package]
|
||||||
|
name = "fix-builtins"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
num_enum = { workspace = true }
|
||||||
|
gc-arena = { workspace = true }
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
use gc_arena::Collect;
|
||||||
|
use num_enum::TryFromPrimitive;
|
||||||
|
|
||||||
|
macro_rules! define_builtins {
|
||||||
|
($(($name:literal, $variant:ident, $arity:expr)),* $(,)?) => {
|
||||||
|
/// Builtin function registry.
|
||||||
|
/// Array index IS the PrimOp id. (name, arity) pairs.
|
||||||
|
pub const BUILTINS: &[(&str, u8)] = &[
|
||||||
|
$(($name, $arity),)*
|
||||||
|
];
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, TryFromPrimitive, Collect)]
|
||||||
|
#[repr(u8)]
|
||||||
|
#[collect(require_static)]
|
||||||
|
pub enum BuiltinId {
|
||||||
|
$($variant,)*
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
("baseNameOf", BaseNameOf, 1),
|
||||||
|
("__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),
|
||||||
|
("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),
|
||||||
|
("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),
|
||||||
|
("import", Import, 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),
|
||||||
|
("map", Map, 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),
|
||||||
|
("placeholder", Placeholder, 1),
|
||||||
|
("__readDir", ReadDir, 1),
|
||||||
|
("__readFile", ReadFile, 1),
|
||||||
|
("__readFileType", ReadFileType, 1),
|
||||||
|
("removeAttrs", RemoveAttrs, 2),
|
||||||
|
("__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),
|
||||||
|
("throw", Throw, 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),
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "fix-codegen"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
gc-arena = { workspace = true }
|
||||||
|
hashbrown = { workspace = true }
|
||||||
|
num_enum = { workspace = true }
|
||||||
|
rnix = { workspace = true }
|
||||||
|
string-interner = { workspace = true }
|
||||||
|
|
||||||
|
fix-builtins = { path = "../fix-builtins" }
|
||||||
|
fix-common = { path = "../fix-common" }
|
||||||
|
fix-ir = { path = "../fix-ir" }
|
||||||
@@ -1,30 +1,21 @@
|
|||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
use gc_arena::Collect;
|
use fix_builtins::{BUILTINS, BuiltinId};
|
||||||
|
use fix_common::StringId;
|
||||||
|
use fix_ir::{Attr, BinOpKind, Ir, Param, RawIrRef, ThunkId, UnOpKind};
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use num_enum::TryFromPrimitive;
|
use num_enum::TryFromPrimitive;
|
||||||
use rnix::TextRange;
|
use rnix::TextRange;
|
||||||
use string_interner::Symbol as _;
|
use string_interner::Symbol as _;
|
||||||
|
|
||||||
use crate::ir::{Attr, BinOpKind, Ir, Param, RawIrRef, StringId, ThunkId, UnOpKind};
|
pub struct InstructionPtr(pub usize);
|
||||||
use crate::runtime::BUILTINS;
|
|
||||||
use crate::runtime::value::{Null, PrimOp, StaticValue};
|
|
||||||
|
|
||||||
pub struct InstructionPtr(pub(crate) usize);
|
pub trait BytecodeContext {
|
||||||
|
|
||||||
#[derive(Collect)]
|
|
||||||
#[collect(require_static)]
|
|
||||||
pub struct Bytecode {
|
|
||||||
pub code: Box<[u8]>,
|
|
||||||
pub current_dir: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) trait BytecodeContext {
|
|
||||||
fn intern_string(&mut self, s: &str) -> StringId;
|
fn intern_string(&mut self, s: &str) -> StringId;
|
||||||
fn register_span(&mut self, range: TextRange) -> u32;
|
fn register_span(&mut self, range: TextRange) -> u32;
|
||||||
fn get_code(&self) -> &[u8];
|
fn get_code(&self) -> &[u8];
|
||||||
fn get_code_mut(&mut self) -> &mut Vec<u8>;
|
fn get_code_mut(&mut self) -> &mut Vec<u8>;
|
||||||
fn add_constant(&mut self, val: crate::runtime::value::StaticValue) -> u32;
|
fn add_constant(&mut self, val: Const) -> u32;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
@@ -49,7 +40,6 @@ pub enum Op {
|
|||||||
MakePatternClosure,
|
MakePatternClosure,
|
||||||
|
|
||||||
Call,
|
Call,
|
||||||
CallNoSpan,
|
|
||||||
|
|
||||||
MakeAttrs,
|
MakeAttrs,
|
||||||
MakeEmptyAttrs,
|
MakeEmptyAttrs,
|
||||||
@@ -98,6 +88,8 @@ pub enum Op {
|
|||||||
LoadScopedBinding,
|
LoadScopedBinding,
|
||||||
|
|
||||||
Return,
|
Return,
|
||||||
|
|
||||||
|
Illegal,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ScopeInfo {
|
struct ScopeInfo {
|
||||||
@@ -110,22 +102,39 @@ struct BytecodeEmitter<'a, Ctx: BytecodeContext> {
|
|||||||
scope_stack: Vec<ScopeInfo>,
|
scope_stack: Vec<ScopeInfo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) const OPERAND_CONST: u8 = 0;
|
#[repr(u8)]
|
||||||
pub(crate) const OPERAND_LOCAL: u8 = 1;
|
#[derive(Debug, Clone, Copy, TryFromPrimitive)]
|
||||||
pub(crate) const OPERAND_BUILTINS: u8 = 2;
|
pub enum OperandType {
|
||||||
pub(crate) const OPERAND_BIGINT: u8 = 3;
|
Const,
|
||||||
|
Local,
|
||||||
|
Builtins,
|
||||||
|
BigInt,
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) const KEY_STATIC: u8 = 0;
|
#[repr(u8)]
|
||||||
pub(crate) const KEY_DYNAMIC: u8 = 1;
|
#[derive(Debug, Clone, Copy, TryFromPrimitive)]
|
||||||
|
pub enum AttrKeyType {
|
||||||
|
Static,
|
||||||
|
Dynamic,
|
||||||
|
}
|
||||||
|
|
||||||
enum InlineOperand {
|
pub enum Const {
|
||||||
Const(StaticValue),
|
Smi(i32),
|
||||||
|
Float(f64),
|
||||||
|
Bool(bool),
|
||||||
|
String(StringId),
|
||||||
|
PrimOp { id: BuiltinId, arity: u8 },
|
||||||
|
Null,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum InlineOperand {
|
||||||
|
Const(Const),
|
||||||
Local { layer: u16, local: u32 },
|
Local { layer: u16, local: u32 },
|
||||||
Builtins,
|
Builtins,
|
||||||
BigInt(i64),
|
BigInt(i64),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn compile_bytecode(ir: RawIrRef<'_>, ctx: &mut impl BytecodeContext) -> InstructionPtr {
|
pub fn compile_bytecode(ir: RawIrRef<'_>, ctx: &mut impl BytecodeContext) -> InstructionPtr {
|
||||||
let ip = ctx.get_code().len();
|
let ip = ctx.get_code().len();
|
||||||
let mut emitter = BytecodeEmitter::new(ctx);
|
let mut emitter = BytecodeEmitter::new(ctx);
|
||||||
emitter.emit_toplevel(ir);
|
emitter.emit_toplevel(ir);
|
||||||
@@ -144,17 +153,17 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
match ir.deref() {
|
match ir.deref() {
|
||||||
&Ir::Int(x) => {
|
&Ir::Int(x) => {
|
||||||
if x <= i32::MAX as i64 {
|
if x <= i32::MAX as i64 {
|
||||||
InlineOperand::Const(StaticValue::new_inline(x as i32))
|
InlineOperand::Const(Const::Smi(x as i32))
|
||||||
} else {
|
} else {
|
||||||
InlineOperand::BigInt(x)
|
InlineOperand::BigInt(x)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&Ir::Float(x) => InlineOperand::Const(StaticValue::new_float(x)),
|
&Ir::Float(x) => InlineOperand::Const(Const::Float(x)),
|
||||||
&Ir::Bool(b) => InlineOperand::Const(StaticValue::new_inline(b)),
|
&Ir::Bool(b) => InlineOperand::Const(Const::Bool(b)),
|
||||||
Ir::Null => InlineOperand::Const(StaticValue::new_inline(Null)),
|
Ir::Null => InlineOperand::Const(Const::Null),
|
||||||
Ir::Str(s) => {
|
Ir::Str(s) => {
|
||||||
let sid = self.ctx.intern_string(s.deref());
|
let sid = self.ctx.intern_string(s.deref());
|
||||||
InlineOperand::Const(StaticValue::new_inline(sid))
|
InlineOperand::Const(Const::String(sid))
|
||||||
}
|
}
|
||||||
&Ir::Thunk(id) => {
|
&Ir::Thunk(id) => {
|
||||||
let (layer, local) = self.resolve_thunk(id);
|
let (layer, local) = self.resolve_thunk(id);
|
||||||
@@ -166,7 +175,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
},
|
},
|
||||||
&Ir::Builtin(id) => {
|
&Ir::Builtin(id) => {
|
||||||
let arity = BUILTINS[id as usize].1;
|
let arity = BUILTINS[id as usize].1;
|
||||||
InlineOperand::Const(StaticValue::new_inline(PrimOp { id, arity }))
|
InlineOperand::Const(Const::PrimOp { id, arity })
|
||||||
}
|
}
|
||||||
Ir::Builtins => InlineOperand::Builtins,
|
Ir::Builtins => InlineOperand::Builtins,
|
||||||
_ => panic!("cannot classify IR node as inline operand"),
|
_ => panic!("cannot classify IR node as inline operand"),
|
||||||
@@ -177,19 +186,19 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
match operand {
|
match operand {
|
||||||
InlineOperand::Const(val) => {
|
InlineOperand::Const(val) => {
|
||||||
let idx = self.ctx.add_constant(val);
|
let idx = self.ctx.add_constant(val);
|
||||||
self.emit_u8(OPERAND_CONST);
|
self.emit_u8(OperandType::Const as u8);
|
||||||
self.emit_u32(idx);
|
self.emit_u32(idx);
|
||||||
}
|
}
|
||||||
InlineOperand::Local { layer, local } => {
|
InlineOperand::Local { layer, local } => {
|
||||||
self.emit_u8(OPERAND_LOCAL);
|
self.emit_u8(OperandType::Local as u8);
|
||||||
self.emit_u8(layer as u8);
|
self.emit_u8(layer as u8);
|
||||||
self.emit_u32(local);
|
self.emit_u32(local);
|
||||||
}
|
}
|
||||||
InlineOperand::Builtins => {
|
InlineOperand::Builtins => {
|
||||||
self.emit_u8(OPERAND_BUILTINS);
|
self.emit_u8(OperandType::Builtins as u8);
|
||||||
}
|
}
|
||||||
InlineOperand::BigInt(val) => {
|
InlineOperand::BigInt(val) => {
|
||||||
self.emit_u8(OPERAND_BIGINT);
|
self.emit_u8(OperandType::BigInt as u8);
|
||||||
self.emit_i64(val);
|
self.emit_i64(val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -566,12 +575,10 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&Ir::Call { func, arg, span } => {
|
&Ir::Call { func, arg, .. } => {
|
||||||
self.emit_expr(arg);
|
self.emit_expr(arg);
|
||||||
self.emit_expr(func);
|
self.emit_expr(func);
|
||||||
let span_id = self.ctx.register_span(span);
|
|
||||||
self.emit_op(Op::Call);
|
self.emit_op(Op::Call);
|
||||||
self.emit_u32(span_id);
|
|
||||||
}
|
}
|
||||||
&Ir::Arg { layer } => {
|
&Ir::Arg { layer } => {
|
||||||
self.emit_load(layer.try_into().expect("scope too deep!"), 0);
|
self.emit_load(layer.try_into().expect("scope too deep!"), 0);
|
||||||
@@ -725,12 +732,12 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
PipeL => {
|
PipeL => {
|
||||||
self.emit_expr(rhs);
|
self.emit_expr(rhs);
|
||||||
self.emit_expr(lhs);
|
self.emit_expr(lhs);
|
||||||
self.emit_op(Op::CallNoSpan);
|
self.emit_op(Op::Call);
|
||||||
}
|
}
|
||||||
PipeR => {
|
PipeR => {
|
||||||
self.emit_expr(lhs);
|
self.emit_expr(lhs);
|
||||||
self.emit_expr(rhs);
|
self.emit_expr(rhs);
|
||||||
self.emit_op(Op::CallNoSpan);
|
self.emit_op(Op::Call);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
self.emit_expr(lhs);
|
self.emit_expr(lhs);
|
||||||
@@ -808,7 +815,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
|
|
||||||
fn emit_attrset(
|
fn emit_attrset(
|
||||||
&mut self,
|
&mut self,
|
||||||
stcs: &crate::ir::HashMap<'_, StringId, (RawIrRef<'_>, TextRange)>,
|
stcs: &fix_ir::HashMap<'_, StringId, (RawIrRef<'_>, TextRange)>,
|
||||||
dyns: &[(RawIrRef<'_>, RawIrRef<'_>, TextRange)],
|
dyns: &[(RawIrRef<'_>, RawIrRef<'_>, TextRange)],
|
||||||
) {
|
) {
|
||||||
if stcs.is_empty() && dyns.is_empty() {
|
if stcs.is_empty() && dyns.is_empty() {
|
||||||
@@ -821,7 +828,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
self.emit_u32(total as u32);
|
self.emit_u32(total as u32);
|
||||||
|
|
||||||
for (&sym, &(val, span)) in stcs.iter() {
|
for (&sym, &(val, span)) in stcs.iter() {
|
||||||
self.emit_u8(KEY_STATIC);
|
self.emit_u8(AttrKeyType::Static as u8);
|
||||||
self.emit_str_id(sym);
|
self.emit_str_id(sym);
|
||||||
let val_operand = self.classify_value(val);
|
let val_operand = self.classify_value(val);
|
||||||
self.emit_inline_operand(val_operand);
|
self.emit_inline_operand(val_operand);
|
||||||
@@ -829,7 +836,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
self.emit_u32(span_id);
|
self.emit_u32(span_id);
|
||||||
}
|
}
|
||||||
for &(key, val, span) in dyns.iter() {
|
for &(key, val, span) in dyns.iter() {
|
||||||
self.emit_u8(KEY_DYNAMIC);
|
self.emit_u8(AttrKeyType::Dynamic as u8);
|
||||||
let key_operand = self.classify_value(key);
|
let key_operand = self.classify_value(key);
|
||||||
self.emit_inline_operand(key_operand);
|
self.emit_inline_operand(key_operand);
|
||||||
let val_operand = self.classify_value(val);
|
let val_operand = self.classify_value(val);
|
||||||
@@ -867,11 +874,11 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
for attr in attrpath.iter() {
|
for attr in attrpath.iter() {
|
||||||
match *attr {
|
match *attr {
|
||||||
Attr::Str(sym, _) => {
|
Attr::Str(sym, _) => {
|
||||||
self.emit_u8(KEY_STATIC);
|
self.emit_u8(AttrKeyType::Static as u8);
|
||||||
self.emit_str_id(sym);
|
self.emit_str_id(sym);
|
||||||
}
|
}
|
||||||
Attr::Dynamic(_, _) => {
|
Attr::Dynamic(_, _) => {
|
||||||
self.emit_u8(KEY_DYNAMIC);
|
self.emit_u8(AttrKeyType::Dynamic as u8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -889,11 +896,11 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
for attr in rhs.iter() {
|
for attr in rhs.iter() {
|
||||||
match *attr {
|
match *attr {
|
||||||
Attr::Str(sym, _) => {
|
Attr::Str(sym, _) => {
|
||||||
self.emit_u8(KEY_STATIC);
|
self.emit_u8(AttrKeyType::Static as u8);
|
||||||
self.emit_str_id(sym);
|
self.emit_str_id(sym);
|
||||||
}
|
}
|
||||||
Attr::Dynamic(_, _) => {
|
Attr::Dynamic(_, _) => {
|
||||||
self.emit_u8(KEY_DYNAMIC);
|
self.emit_u8(AttrKeyType::Dynamic as u8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "fix-common"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
gc-arena = { workspace = true }
|
||||||
|
string-interner = { workspace = true }
|
||||||
|
ere = { workspace = true }
|
||||||
@@ -1,14 +1,17 @@
|
|||||||
use core::fmt::{Debug, Display, Formatter, Result as FmtResult};
|
|
||||||
use core::hash::Hash;
|
|
||||||
use core::ops::Deref;
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::ops::DerefMut;
|
use std::fmt::{Debug, Display, Formatter, Result as FmtResult};
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
use derive_more::{Constructor, IsVariant, Unwrap};
|
use gc_arena::Collect;
|
||||||
|
|
||||||
|
#[repr(transparent)]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Collect)]
|
||||||
|
#[collect(require_static)]
|
||||||
|
pub struct StringId(pub string_interner::symbol::SymbolU32);
|
||||||
|
|
||||||
/// Represents a Nix symbol, which is used as a key in attribute sets.
|
/// Represents a Nix symbol, which is used as a key in attribute sets.
|
||||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Constructor)]
|
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct Symbol<'a>(Cow<'a, str>);
|
pub struct Symbol<'a>(Cow<'a, str>);
|
||||||
|
|
||||||
pub type StaticSymbol = Symbol<'static>;
|
pub type StaticSymbol = Symbol<'static>;
|
||||||
@@ -60,13 +63,36 @@ impl Deref for Symbol<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn escape_quote_string(s: &str) -> String {
|
||||||
|
let mut ret = String::with_capacity(s.len() + 2);
|
||||||
|
ret.push('"');
|
||||||
|
let mut iter = s.chars().peekable();
|
||||||
|
while let Some(c) = iter.next() {
|
||||||
|
match c {
|
||||||
|
'\\' => ret.push_str("\\\\"),
|
||||||
|
'"' => ret.push_str("\\\""),
|
||||||
|
'\n' => ret.push_str("\\n"),
|
||||||
|
'\r' => ret.push_str("\\r"),
|
||||||
|
'\t' => ret.push_str("\\t"),
|
||||||
|
'$' if iter.peek() == Some(&'{') => ret.push_str("\\$"),
|
||||||
|
c => ret.push(c),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret.push('"');
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
/// Represents a Nix attribute set, which is a map from symbols to values.
|
/// Represents a Nix attribute set, which is a map from symbols to values.
|
||||||
#[derive(Constructor, Default, Clone, PartialEq)]
|
#[derive(Default, Clone, PartialEq)]
|
||||||
pub struct AttrSet {
|
pub struct AttrSet {
|
||||||
data: BTreeMap<StaticSymbol, Value>,
|
data: BTreeMap<StaticSymbol, Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AttrSet {
|
impl AttrSet {
|
||||||
|
pub fn new(data: BTreeMap<StaticSymbol, Value>) -> Self {
|
||||||
|
Self { data }
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets a value by key (string or Symbol).
|
/// Gets a value by key (string or Symbol).
|
||||||
pub fn get<'a, 'sym: 'a>(&'a self, key: impl Into<Symbol<'sym>>) -> Option<&'a Value> {
|
pub fn get<'a, 'sym: 'a>(&'a self, key: impl Into<Symbol<'sym>>) -> Option<&'a Value> {
|
||||||
self.data.get(&key.into())
|
self.data.get(&key.into())
|
||||||
@@ -154,11 +180,17 @@ impl Display for AttrSetCompatDisplay<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a Nix list, which is a vector of values.
|
/// Represents a Nix list, which is a vector of values.
|
||||||
#[derive(Constructor, Default, Clone, Debug, PartialEq)]
|
#[derive(Default, Clone, Debug, PartialEq)]
|
||||||
pub struct List {
|
pub struct List {
|
||||||
data: Vec<Value>,
|
data: Vec<Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl List {
|
||||||
|
pub fn new(data: Vec<Value>) -> Self {
|
||||||
|
Self { data }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Deref for List {
|
impl Deref for List {
|
||||||
type Target = Vec<Value>;
|
type Target = Vec<Value>;
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
@@ -217,7 +249,7 @@ impl Display for ListCompatDisplay<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Represents any possible Nix value that can be returned from an evaluation.
|
/// Represents any possible Nix value that can be returned from an evaluation.
|
||||||
#[derive(IsVariant, Unwrap, Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum Value {
|
pub enum Value {
|
||||||
/// An integer value.
|
/// An integer value.
|
||||||
Int(i64),
|
Int(i64),
|
||||||
@@ -248,27 +280,8 @@ pub enum Value {
|
|||||||
Repeated,
|
Repeated,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn escape_quote_string(s: &str) -> String {
|
|
||||||
let mut ret = String::with_capacity(s.len() + 2);
|
|
||||||
ret.push('"');
|
|
||||||
let mut iter = s.chars().peekable();
|
|
||||||
while let Some(c) = iter.next() {
|
|
||||||
match c {
|
|
||||||
'\\' => ret.push_str("\\\\"),
|
|
||||||
'"' => ret.push_str("\\\""),
|
|
||||||
'\n' => ret.push_str("\\n"),
|
|
||||||
'\r' => ret.push_str("\\r"),
|
|
||||||
'\t' => ret.push_str("\\t"),
|
|
||||||
'$' if iter.peek() == Some(&'{') => ret.push_str("\\$"),
|
|
||||||
c => ret.push(c),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ret.push('"');
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper to format a float in Nix style (C printf `%g` with precision 6).
|
/// Wrapper to format a float in Nix style (C printf `%g` with precision 6).
|
||||||
pub(crate) struct NixFloat(pub f64);
|
pub struct NixFloat(pub f64);
|
||||||
|
|
||||||
impl Display for NixFloat {
|
impl Display for NixFloat {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "fix-error"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
miette = { version = "7.6", features = ["fancy"] }
|
||||||
|
thiserror = "2.0"
|
||||||
|
rnix = { workspace = true }
|
||||||
@@ -0,0 +1,223 @@
|
|||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use miette::{Diagnostic, NamedSource, SourceSpan};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
pub type Result<T> = core::result::Result<T, Box<Error>>;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum SourceType {
|
||||||
|
/// dir
|
||||||
|
Eval(Arc<PathBuf>),
|
||||||
|
/// dir
|
||||||
|
Repl(Arc<PathBuf>),
|
||||||
|
/// file
|
||||||
|
File(Arc<PathBuf>),
|
||||||
|
/// virtual (name, no path)
|
||||||
|
Virtual(Arc<str>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Source {
|
||||||
|
pub ty: SourceType,
|
||||||
|
pub src: Arc<str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&str> for Source {
|
||||||
|
type Error = Box<Error>;
|
||||||
|
fn try_from(value: &str) -> Result<Self> {
|
||||||
|
Source::new_eval(value.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Source> for NamedSource<Arc<str>> {
|
||||||
|
fn from(value: Source) -> Self {
|
||||||
|
let name = value.get_name();
|
||||||
|
NamedSource::new(name, value.src.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Source {
|
||||||
|
pub fn new_file(path: PathBuf) -> std::io::Result<Self> {
|
||||||
|
Ok(Source {
|
||||||
|
src: std::fs::read_to_string(&path)?.into(),
|
||||||
|
ty: SourceType::File(Arc::new(path)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_eval(src: String) -> Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
ty: std::env::current_dir()
|
||||||
|
.map_err(|err| Error::internal(format!("Failed to get current working dir: {err}")))
|
||||||
|
.map(Arc::new)
|
||||||
|
.map(SourceType::Eval)?,
|
||||||
|
src: src.into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_repl(src: String) -> Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
ty: std::env::current_dir()
|
||||||
|
.map_err(|err| Error::internal(format!("Failed to get current working dir: {err}")))
|
||||||
|
.map(Arc::new)
|
||||||
|
.map(SourceType::Repl)?,
|
||||||
|
src: src.into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_virtual(name: Arc<str>, src: String) -> Self {
|
||||||
|
Self {
|
||||||
|
ty: SourceType::Virtual(name),
|
||||||
|
src: src.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_dir(&self) -> &Path {
|
||||||
|
use SourceType::*;
|
||||||
|
match &self.ty {
|
||||||
|
Eval(dir) | Repl(dir) => dir.as_ref(),
|
||||||
|
File(file) => file
|
||||||
|
.as_path()
|
||||||
|
.parent()
|
||||||
|
.expect("source file must have a parent dir"),
|
||||||
|
Virtual(_) => Path::new("/"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_name(&self) -> String {
|
||||||
|
match &self.ty {
|
||||||
|
SourceType::Eval(_) => "«eval»".into(),
|
||||||
|
SourceType::Repl(_) => "«repl»".into(),
|
||||||
|
SourceType::File(path) => path.as_os_str().to_string_lossy().to_string(),
|
||||||
|
SourceType::Virtual(name) => name.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug, Diagnostic)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("Parse error: {message}")]
|
||||||
|
#[diagnostic(code(nix::parse))]
|
||||||
|
ParseError {
|
||||||
|
#[source_code]
|
||||||
|
src: Option<NamedSource<Arc<str>>>,
|
||||||
|
#[label("error occurred here")]
|
||||||
|
span: Option<SourceSpan>,
|
||||||
|
message: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("Downgrade error: {message}")]
|
||||||
|
#[diagnostic(code(nix::downgrade))]
|
||||||
|
DowngradeError {
|
||||||
|
#[source_code]
|
||||||
|
src: Option<NamedSource<Arc<str>>>,
|
||||||
|
#[label("{message}")]
|
||||||
|
span: Option<SourceSpan>,
|
||||||
|
message: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("Evaluation error: {message}")]
|
||||||
|
#[diagnostic(code(nix::eval))]
|
||||||
|
EvalError {
|
||||||
|
#[source_code]
|
||||||
|
src: Option<NamedSource<Arc<str>>>,
|
||||||
|
#[label("error occurred here")]
|
||||||
|
span: Option<SourceSpan>,
|
||||||
|
message: String,
|
||||||
|
#[related]
|
||||||
|
stack_trace: Vec<StackFrame>,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("Internal error: {message}")]
|
||||||
|
#[diagnostic(code(nix::internal))]
|
||||||
|
InternalError { message: String },
|
||||||
|
|
||||||
|
#[error("{message}")]
|
||||||
|
#[diagnostic(code(nix::catchable))]
|
||||||
|
Catchable { message: String },
|
||||||
|
|
||||||
|
#[error("Unknown error")]
|
||||||
|
#[diagnostic(code(nix::unknown))]
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error {
|
||||||
|
pub fn parse_error(msg: String) -> Box<Self> {
|
||||||
|
Error::ParseError {
|
||||||
|
src: None,
|
||||||
|
span: None,
|
||||||
|
message: msg,
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn downgrade_error(msg: String, src: Source, span: rnix::TextRange) -> Box<Self> {
|
||||||
|
Error::DowngradeError {
|
||||||
|
src: Some(src.into()),
|
||||||
|
span: Some(text_range_to_source_span(span)),
|
||||||
|
message: msg,
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eval_error(msg: impl Into<String>) -> Box<Self> {
|
||||||
|
Error::EvalError {
|
||||||
|
src: None,
|
||||||
|
span: None,
|
||||||
|
message: msg.into(),
|
||||||
|
stack_trace: Vec::new(),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn internal(msg: String) -> Box<Self> {
|
||||||
|
Error::InternalError { message: msg }.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn catchable(msg: String) -> Box<Self> {
|
||||||
|
Error::Catchable { message: msg }.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_span(mut self: Box<Self>, span: rnix::TextRange) -> Box<Self> {
|
||||||
|
use Error::*;
|
||||||
|
let source_span = Some(text_range_to_source_span(span));
|
||||||
|
let (ParseError { span, .. } | DowngradeError { span, .. } | EvalError { span, .. }) =
|
||||||
|
self.as_mut()
|
||||||
|
else {
|
||||||
|
return self;
|
||||||
|
};
|
||||||
|
*span = source_span;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_source(mut self: Box<Self>, source: Source) -> Box<Self> {
|
||||||
|
use Error::*;
|
||||||
|
let new_src = Some(source.into());
|
||||||
|
let (ParseError { src, .. } | DowngradeError { src, .. } | EvalError { src, .. }) =
|
||||||
|
self.as_mut()
|
||||||
|
else {
|
||||||
|
return self;
|
||||||
|
};
|
||||||
|
*src = new_src;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn text_range_to_source_span(range: rnix::TextRange) -> SourceSpan {
|
||||||
|
let start = usize::from(range.start());
|
||||||
|
let len = usize::from(range.end()) - start;
|
||||||
|
SourceSpan::new(start.into(), len)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stack frame types from Nix evaluation
|
||||||
|
#[derive(Debug, Clone, Error, Diagnostic)]
|
||||||
|
#[error("{message}")]
|
||||||
|
pub struct StackFrame {
|
||||||
|
#[label]
|
||||||
|
pub span: SourceSpan,
|
||||||
|
#[help]
|
||||||
|
pub message: String,
|
||||||
|
#[source_code]
|
||||||
|
pub src: NamedSource<Arc<str>>,
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
[package]
|
||||||
|
name = "fix-ir"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bumpalo = { workspace = true }
|
||||||
|
ghost-cell = { workspace = true }
|
||||||
|
rnix = { workspace = true }
|
||||||
|
rowan = { workspace = true }
|
||||||
|
string-interner = { workspace = true }
|
||||||
|
hashbrown = { workspace = true }
|
||||||
|
num_enum = { workspace = true }
|
||||||
|
|
||||||
|
fix-builtins = { path = "../fix-builtins" }
|
||||||
|
fix-common = { path = "../fix-common" }
|
||||||
|
fix-error = { path = "../fix-error" }
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
use bumpalo::boxed::Box;
|
use bumpalo::boxed::Box;
|
||||||
use bumpalo::collections::{CollectIn, Vec};
|
use bumpalo::collections::{CollectIn, Vec};
|
||||||
|
use fix_builtins::BuiltinId;
|
||||||
|
use fix_common::Symbol;
|
||||||
|
use fix_error::{Error, Result, Source};
|
||||||
use hashbrown::HashSet;
|
use hashbrown::HashSet;
|
||||||
use hashbrown::hash_map::Entry;
|
use hashbrown::hash_map::Entry;
|
||||||
use rnix::TextRange;
|
use rnix::TextRange;
|
||||||
use rnix::ast::{self, Expr, HasEntry};
|
use rnix::ast::{self, Expr, HasEntry};
|
||||||
use rowan::ast::AstNode;
|
use rowan::ast::AstNode;
|
||||||
|
|
||||||
use crate::error::{Error, Result, Source};
|
use super::*;
|
||||||
use crate::ir::*;
|
|
||||||
use crate::runtime::BuiltinId;
|
|
||||||
use crate::value::Symbol;
|
|
||||||
|
|
||||||
trait Require<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>, T> {
|
trait Require<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>, T> {
|
||||||
fn require(self, ctx: &Ctx, span: TextRange) -> Result<T>;
|
fn require(self, ctx: &Ctx, span: TextRange) -> Result<T>;
|
||||||
@@ -151,8 +151,7 @@ macro_rules! path {
|
|||||||
($ty:ident) => {
|
($ty:ident) => {
|
||||||
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::$ty {
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::$ty {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>> {
|
||||||
let span = self.syntax().text_range();
|
downgrade_path(self.parts(), ctx)
|
||||||
downgrade_path(self.parts(), span, ctx)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1215,7 +1214,6 @@ fn finalize_pending_value<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW
|
|||||||
|
|
||||||
fn downgrade_path<'id, 'ir>(
|
fn downgrade_path<'id, 'ir>(
|
||||||
parts: impl IntoIterator<Item = ast::InterpolPart<ast::PathContent>>,
|
parts: impl IntoIterator<Item = ast::InterpolPart<ast::PathContent>>,
|
||||||
_span: rnix::TextRange,
|
|
||||||
ctx: &mut impl DowngradeContext<'id, 'ir>,
|
ctx: &mut impl DowngradeContext<'id, 'ir>,
|
||||||
) -> Result<IrRef<'id, 'ir>> {
|
) -> Result<IrRef<'id, 'ir>> {
|
||||||
let bump = ctx.bump();
|
let bump = ctx.bump();
|
||||||
@@ -4,12 +4,14 @@ use std::ops::Deref;
|
|||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use bumpalo::boxed::Box;
|
use bumpalo::boxed::Box;
|
||||||
use bumpalo::collections::Vec;
|
use bumpalo::collections::Vec;
|
||||||
use gc_arena::Collect;
|
use fix_builtins::{BUILTINS, BuiltinId};
|
||||||
|
use fix_common::StringId;
|
||||||
use ghost_cell::{GhostCell, GhostToken};
|
use ghost_cell::{GhostCell, GhostToken};
|
||||||
|
use num_enum::TryFromPrimitive as _;
|
||||||
use rnix::{TextRange, ast};
|
use rnix::{TextRange, ast};
|
||||||
use string_interner::symbol::SymbolU32;
|
use string_interner::DefaultStringInterner;
|
||||||
|
|
||||||
use crate::runtime::BuiltinId;
|
pub mod downgrade;
|
||||||
|
|
||||||
pub type HashMap<'ir, K, V> = hashbrown::HashMap<K, V, hashbrown::DefaultHashBuilder, &'ir Bump>;
|
pub type HashMap<'ir, K, V> = hashbrown::HashMap<K, V, hashbrown::DefaultHashBuilder, &'ir Bump>;
|
||||||
|
|
||||||
@@ -157,11 +159,6 @@ pub enum Ir<'ir, Ref> {
|
|||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
pub struct ThunkId(pub usize);
|
pub struct ThunkId(pub usize);
|
||||||
|
|
||||||
#[repr(transparent)]
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Collect)]
|
|
||||||
#[collect(require_static)]
|
|
||||||
pub struct StringId(pub SymbolU32);
|
|
||||||
|
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
pub struct SpanId(pub u32);
|
pub struct SpanId(pub u32);
|
||||||
@@ -258,3 +255,47 @@ pub struct Param<'ir> {
|
|||||||
pub optional: Vec<'ir, (StringId, TextRange)>,
|
pub optional: Vec<'ir, (StringId, TextRange)>,
|
||||||
pub ellipsis: bool,
|
pub ellipsis: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_global_env(
|
||||||
|
strings: &mut DefaultStringInterner,
|
||||||
|
) -> hashbrown::HashMap<StringId, Ir<'static, RawIrRef<'static>>> {
|
||||||
|
let mut global_env = hashbrown::HashMap::new();
|
||||||
|
let builtins_sym = StringId(strings.get_or_intern("builtins"));
|
||||||
|
global_env.insert(builtins_sym, Ir::Builtins);
|
||||||
|
|
||||||
|
for (idx, &(name, _)) in BUILTINS.iter().enumerate() {
|
||||||
|
let id = BuiltinId::try_from_primitive(idx as u8).expect("infallible");
|
||||||
|
let name = StringId(strings.get_or_intern(name));
|
||||||
|
global_env.insert(name, Ir::Builtin(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
let consts = [
|
||||||
|
(
|
||||||
|
"__currentSystem",
|
||||||
|
Ir::BuiltinConst(StringId(strings.get_or_intern("currentSystem"))),
|
||||||
|
),
|
||||||
|
("__langVersion", Ir::Int(6)),
|
||||||
|
(
|
||||||
|
"__nixVersion",
|
||||||
|
Ir::BuiltinConst(StringId(strings.get_or_intern("nixVersion"))),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"__storeDir",
|
||||||
|
Ir::BuiltinConst(StringId(strings.get_or_intern("storeDir"))),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"__nixPath",
|
||||||
|
Ir::BuiltinConst(StringId(strings.get_or_intern("nixPath"))),
|
||||||
|
),
|
||||||
|
("null", Ir::Null),
|
||||||
|
("true", Ir::Bool(true)),
|
||||||
|
("false", Ir::Bool(false)),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (name, ir) in consts {
|
||||||
|
let name = StringId(strings.get_or_intern(name));
|
||||||
|
global_env.insert(name, ir);
|
||||||
|
}
|
||||||
|
|
||||||
|
global_env
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
[package]
|
||||||
|
name = "fix-vm"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
gc-arena = { workspace = true }
|
||||||
|
hashbrown = { workspace = true }
|
||||||
|
num_enum = { workspace = true }
|
||||||
|
smallvec = { workspace = true }
|
||||||
|
string-interner = { workspace = true }
|
||||||
|
likely_stable = { workspace = true }
|
||||||
|
sptr = "0.3"
|
||||||
|
|
||||||
|
fix-builtins = { path = "../fix-builtins" }
|
||||||
|
fix-codegen = { path = "../fix-codegen" }
|
||||||
|
fix-common = { path = "../fix-common" }
|
||||||
|
fix-error = { path = "../fix-error" }
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::num::NonZeroU8;
|
use std::num::NonZeroU8;
|
||||||
|
|
||||||
@@ -22,7 +24,7 @@ impl<T: Default + Copy, const N: usize> ArrayExt<N> for [T; N] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait RawStore: Sized {
|
pub trait RawStore: Sized {
|
||||||
fn to_val(self, value: &mut Value);
|
fn to_val(self, value: &mut Value);
|
||||||
fn from_val(value: &Value) -> Self;
|
fn from_val(value: &Value) -> Self;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
use fix_error::Error;
|
||||||
|
|
||||||
|
use crate::value::StrictValue;
|
||||||
|
use crate::{NixNum, VmError};
|
||||||
|
|
||||||
|
pub(super) fn vm_err(msg: impl Into<String>) -> VmError {
|
||||||
|
VmError::Uncatchable(Error::eval_error(msg.into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn get_num(val: StrictValue<'_>) -> Option<NixNum> {
|
||||||
|
if let Some(i) = val.as_inline::<i32>() {
|
||||||
|
Some(NixNum::Int(i as i64))
|
||||||
|
} else if let Some(gc_i) = val.as_gc::<i64>() {
|
||||||
|
Some(NixNum::Int(*gc_i))
|
||||||
|
} else {
|
||||||
|
val.as_float().map(NixNum::Float)
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,6 @@
|
|||||||
use std::mem::MaybeUninit;
|
use std::mem::MaybeUninit;
|
||||||
|
|
||||||
use gc_arena::Collect;
|
use gc_arena::Collect;
|
||||||
use smallvec::SmallVec;
|
|
||||||
|
|
||||||
pub(super) struct Stack<const N: usize, T> {
|
pub(super) struct Stack<const N: usize, T> {
|
||||||
inner: Box<[MaybeUninit<T>; N]>,
|
inner: Box<[MaybeUninit<T>; N]>,
|
||||||
@@ -74,15 +73,4 @@ impl<const N: usize, T> Stack<N, T> {
|
|||||||
pub(super) fn len(&self) -> usize {
|
pub(super) fn len(&self) -> usize {
|
||||||
self.len
|
self.len
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn pop_n<const M: usize>(&mut self, n: usize) -> SmallVec<[T; M]> {
|
|
||||||
assert!(n <= self.len, "pop_n: not enough items on stack");
|
|
||||||
let mut result = SmallVec::new();
|
|
||||||
let start = self.len - n;
|
|
||||||
for i in start..self.len {
|
|
||||||
result.push(unsafe { self.inner.get_unchecked(i).assume_init_read() });
|
|
||||||
}
|
|
||||||
self.len = start;
|
|
||||||
result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,25 +1,30 @@
|
|||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
use fix_builtins::BuiltinId;
|
||||||
|
use fix_common::*;
|
||||||
use gc_arena::collect::Trace;
|
use gc_arena::collect::Trace;
|
||||||
use gc_arena::{Collect, Gc, Mutation, RefLock};
|
use gc_arena::{Collect, Gc, GcRefLock, Mutation, RefLock};
|
||||||
use num_enum::TryFromPrimitive;
|
use num_enum::TryFromPrimitive;
|
||||||
use sealed::sealed;
|
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use string_interner::Symbol;
|
use string_interner::Symbol;
|
||||||
use string_interner::symbol::SymbolU32;
|
use string_interner::symbol::SymbolU32;
|
||||||
|
|
||||||
|
use crate::NixNum;
|
||||||
use crate::boxing::{RawBox, RawStore, RawTag, Value as RawValue};
|
use crate::boxing::{RawBox, RawStore, RawTag, Value as RawValue};
|
||||||
use crate::ir::StringId;
|
|
||||||
use crate::runtime::builtins::BuiltinId;
|
|
||||||
|
|
||||||
#[sealed]
|
mod private {
|
||||||
|
pub trait Cealed {}
|
||||||
|
}
|
||||||
|
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// 1. TAG must be unique among all implementors.
|
/// 1. TAG must be unique among all implementors.
|
||||||
/// 2. TAG must be within 1..=7
|
/// 2. TAG must be within 1..=7
|
||||||
pub(crate) unsafe trait Storable {
|
pub(crate) unsafe trait Storable: private::Cealed {
|
||||||
const TAG: (bool, u8);
|
const TAG: (bool, u8);
|
||||||
}
|
}
|
||||||
pub(crate) trait InlineStorable: Storable + RawStore {}
|
pub(crate) trait InlineStorable: Storable + RawStore {}
|
||||||
@@ -31,18 +36,18 @@ macro_rules! define_value_types {
|
|||||||
gc { $($gtype:ty => $gtag:expr, $gname:literal;)* }
|
gc { $($gtype:ty => $gtag:expr, $gname:literal;)* }
|
||||||
) => {
|
) => {
|
||||||
$(
|
$(
|
||||||
#[sealed]
|
|
||||||
unsafe impl Storable for $itype {
|
unsafe impl Storable for $itype {
|
||||||
const TAG: (bool, u8) = $itag;
|
const TAG: (bool, u8) = $itag;
|
||||||
}
|
}
|
||||||
impl InlineStorable for $itype {}
|
impl InlineStorable for $itype {}
|
||||||
|
impl private::Cealed for $itype {}
|
||||||
)*
|
)*
|
||||||
$(
|
$(
|
||||||
#[sealed]
|
|
||||||
unsafe impl Storable for $gtype {
|
unsafe impl Storable for $gtype {
|
||||||
const TAG: (bool, u8) = $gtag;
|
const TAG: (bool, u8) = $gtag;
|
||||||
}
|
}
|
||||||
impl GcStorable for $gtype {}
|
impl GcStorable for $gtype {}
|
||||||
|
impl private::Cealed for $gtype {}
|
||||||
)*
|
)*
|
||||||
|
|
||||||
const _: () = assert!(size_of::<Value<'static>>() == 8);
|
const _: () = assert!(size_of::<Value<'static>>() == 8);
|
||||||
@@ -98,20 +103,6 @@ macro_rules! define_value_types {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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})"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,7 +157,7 @@ impl<'gc> Value<'gc> {
|
|||||||
///
|
///
|
||||||
/// The value must actually store a `Gc<'gc, T>` with the matching type.
|
/// The value must actually store a `Gc<'gc, T>` with the matching type.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
unsafe fn load_gc<T: GcStorable>(&self) -> Gc<'gc, T> {
|
unsafe fn load_gc<T: GcStorable>(self) -> Gc<'gc, T> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let rv = self.raw.value().unwrap_unchecked();
|
let rv = self.raw.value().unwrap_unchecked();
|
||||||
let ptr: *const T = <*const T as RawStore>::from_val(rv);
|
let ptr: *const T = <*const T as RawStore>::from_val(rv);
|
||||||
@@ -176,7 +167,7 @@ impl<'gc> Value<'gc> {
|
|||||||
|
|
||||||
/// Returns the `(negative, val)` tag, or `None` for a float.
|
/// Returns the `(negative, val)` tag, or `None` for a float.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn tag(&self) -> Option<(bool, u8)> {
|
fn tag(self) -> Option<(bool, u8)> {
|
||||||
self.raw.tag().map(|t| t.neg_val())
|
self.raw.tag().map(|t| t.neg_val())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -219,24 +210,24 @@ impl<'gc> Value<'gc> {
|
|||||||
|
|
||||||
impl<'gc> Value<'gc> {
|
impl<'gc> Value<'gc> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn is_float(&self) -> bool {
|
pub(crate) fn is_float(self) -> bool {
|
||||||
self.raw.is_float()
|
self.raw.is_float()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn is<T: Storable>(&self) -> bool {
|
pub(crate) fn is<T: Storable>(self) -> bool {
|
||||||
self.tag() == Some(T::TAG)
|
self.tag() == Some(T::TAG)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'gc> Value<'gc> {
|
impl<'gc> Value<'gc> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn as_float(&self) -> Option<f64> {
|
pub(crate) fn as_float(self) -> Option<f64> {
|
||||||
self.raw.float().copied()
|
self.raw.float().copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn as_inline<T: InlineStorable>(&self) -> Option<T> {
|
pub(crate) fn as_inline<T: InlineStorable>(self) -> Option<T> {
|
||||||
if self.is::<T>() {
|
if self.is::<T>() {
|
||||||
Some(unsafe {
|
Some(unsafe {
|
||||||
let rv = self.raw.value().unwrap_unchecked();
|
let rv = self.raw.value().unwrap_unchecked();
|
||||||
@@ -248,7 +239,7 @@ impl<'gc> Value<'gc> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn as_gc<T: GcStorable>(&self) -> Option<Gc<'gc, T>> {
|
pub(crate) fn as_gc<T: GcStorable>(self) -> Option<Gc<'gc, T>> {
|
||||||
if self.is::<T>() {
|
if self.is::<T>() {
|
||||||
Some(unsafe {
|
Some(unsafe {
|
||||||
let rv = self.raw.value().unwrap_unchecked();
|
let rv = self.raw.value().unwrap_unchecked();
|
||||||
@@ -260,6 +251,17 @@ impl<'gc> Value<'gc> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn as_num(self) -> Option<NixNum> {
|
||||||
|
if let Some(i) = self.as_inline::<i32>() {
|
||||||
|
Some(NixNum::Int(i as i64))
|
||||||
|
} else if let Some(gc_i) = self.as_gc::<i64>() {
|
||||||
|
Some(NixNum::Int(*gc_i))
|
||||||
|
} else {
|
||||||
|
self.as_float().map(NixNum::Float)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn restrict(self) -> Option<StrictValue<'gc>> {
|
pub(crate) fn restrict(self) -> Option<StrictValue<'gc>> {
|
||||||
if !self.is::<Thunk<'gc>>() {
|
if !self.is::<Thunk<'gc>>() {
|
||||||
@@ -270,88 +272,50 @@ impl<'gc> Value<'gc> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone, Default)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub(crate) struct StaticValue {
|
pub struct StaticValue(Value<'static>);
|
||||||
raw: RawBox,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for StaticValue {
|
impl<'gc> From<StaticValue> for Value<'gc> {
|
||||||
#[inline(always)]
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new_inline(Null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<StaticValue> for Value<'_> {
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn from(value: StaticValue) -> Self {
|
fn from(value: StaticValue) -> Self {
|
||||||
Self {
|
// SAFETY: StaticValue is guaranteed to not contain any `Gc`.
|
||||||
raw: value.raw,
|
unsafe { std::mem::transmute::<Value<'static>, Value<'gc>>(value.0) }
|
||||||
_marker: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StaticValue {
|
impl StaticValue {
|
||||||
#[inline(always)]
|
|
||||||
fn from_raw_value(rv: RawValue) -> Self {
|
|
||||||
Self {
|
|
||||||
raw: RawBox::from_value(rv),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn to_bits(self) -> u64 {
|
pub fn new_float(val: f64) -> Self {
|
||||||
self.raw.to_bits()
|
Self(Value::new_float(val))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn new_float(val: f64) -> Self {
|
pub fn new_inline<T: InlineStorable>(val: T) -> Self {
|
||||||
Self {
|
Self(Value::new_inline(val))
|
||||||
raw: RawBox::from_float(val),
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn new_inline<T: InlineStorable>(val: T) -> Self {
|
pub fn new_primop(id: BuiltinId, arity: u8) -> Self {
|
||||||
Self::from_raw_value(RawValue::store(
|
Self(Value::new_inline(PrimOp { id, arity }))
|
||||||
unsafe { RawTag::new_unchecked(T::TAG.0, T::TAG.1) },
|
|
||||||
val,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn is_float(&self) -> bool {
|
pub fn is_float(self) -> bool {
|
||||||
self.raw.is_float()
|
self.0.is_float()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn is<T: InlineStorable>(&self) -> bool {
|
pub fn is<T: InlineStorable>(self) -> bool {
|
||||||
self.tag() == Some(T::TAG)
|
self.0.is::<T>()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn as_float(&self) -> Option<f64> {
|
pub fn as_float(self) -> Option<f64> {
|
||||||
self.raw.float().copied()
|
self.0.as_float()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn as_inline<T: InlineStorable>(&self) -> Option<T> {
|
pub fn as_inline<T: InlineStorable>(self) -> Option<T> {
|
||||||
if self.is::<T>() {
|
self.0.as_inline::<T>()
|
||||||
Some(unsafe {
|
|
||||||
let rv = self.raw.value().unwrap_unchecked();
|
|
||||||
T::from_val(rv)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
}
|
#[inline]
|
||||||
|
pub fn to_bits(self) -> u64 {
|
||||||
/// Returns the `(negative, val)` tag, or `None` for a float.
|
self.0.raw.to_bits()
|
||||||
#[inline(always)]
|
|
||||||
fn tag(&self) -> Option<(bool, u8)> {
|
|
||||||
self.raw.tag().map(|t| t.neg_val())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -420,7 +384,7 @@ impl<'gc> Deref for AttrSet<'gc> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'gc> AttrSet<'gc> {
|
impl<'gc> AttrSet<'gc> {
|
||||||
pub(crate) unsafe fn from_sorted_unchecked(
|
pub(crate) fn from_sorted_unchecked(
|
||||||
entries: SmallVec<[(StringId, Value<'gc>); 4]>,
|
entries: SmallVec<[(StringId, Value<'gc>); 4]>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
debug_assert!(entries.is_sorted_by_key(|(key, _)| *key));
|
debug_assert!(entries.is_sorted_by_key(|(key, _)| *key));
|
||||||
@@ -492,7 +456,8 @@ pub(crate) type Thunk<'gc> = RefLock<ThunkState<'gc>>;
|
|||||||
pub(crate) enum ThunkState<'gc> {
|
pub(crate) enum ThunkState<'gc> {
|
||||||
Pending {
|
Pending {
|
||||||
ip: usize,
|
ip: usize,
|
||||||
env: Gc<'gc, RefLock<Env<'gc>>>,
|
env: GcEnv<'gc>,
|
||||||
|
with_env: Option<GcWithEnv<'gc>>,
|
||||||
},
|
},
|
||||||
Apply {
|
Apply {
|
||||||
func: Value<'gc>,
|
func: Value<'gc>,
|
||||||
@@ -506,8 +471,17 @@ pub(crate) enum ThunkState<'gc> {
|
|||||||
#[collect(no_drop)]
|
#[collect(no_drop)]
|
||||||
pub(crate) struct Env<'gc> {
|
pub(crate) struct Env<'gc> {
|
||||||
pub(crate) locals: SmallVec<[Value<'gc>; 4]>,
|
pub(crate) locals: SmallVec<[Value<'gc>; 4]>,
|
||||||
pub(crate) prev: Option<Gc<'gc, RefLock<Env<'gc>>>>,
|
pub(crate) prev: Option<GcEnv<'gc>>,
|
||||||
}
|
}
|
||||||
|
pub(crate) type GcEnv<'gc> = GcRefLock<'gc, Env<'gc>>;
|
||||||
|
|
||||||
|
#[derive(Collect, Debug)]
|
||||||
|
#[collect(no_drop)]
|
||||||
|
pub(crate) struct WithEnv<'gc> {
|
||||||
|
pub(crate) env: Value<'gc>,
|
||||||
|
pub(crate) prev: Option<GcWithEnv<'gc>>,
|
||||||
|
}
|
||||||
|
pub(crate) type GcWithEnv<'gc> = Gc<'gc, WithEnv<'gc>>;
|
||||||
|
|
||||||
impl<'gc> Env<'gc> {
|
impl<'gc> Env<'gc> {
|
||||||
pub(crate) fn empty() -> Self {
|
pub(crate) fn empty() -> Self {
|
||||||
+17
-63
@@ -6,20 +6,9 @@ edition = "2024"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
mimalloc = "0.1"
|
mimalloc = "0.1"
|
||||||
|
|
||||||
tokio = { version = "1.41", features = [
|
|
||||||
"rt-multi-thread",
|
|
||||||
"sync",
|
|
||||||
"net",
|
|
||||||
"io-util",
|
|
||||||
] }
|
|
||||||
nix-compat = { git = "https://git.snix.dev/snix/snix.git", version = "0.1.0", features = [
|
|
||||||
"wire",
|
|
||||||
"async",
|
|
||||||
] }
|
|
||||||
|
|
||||||
# REPL
|
# REPL
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
rustyline = "17.0"
|
rustyline = "18.0"
|
||||||
|
|
||||||
# CLI
|
# CLI
|
||||||
clap = { version = "4", features = ["derive"] }
|
clap = { version = "4", features = ["derive"] }
|
||||||
@@ -28,67 +17,32 @@ clap = { version = "4", features = ["derive"] }
|
|||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
||||||
derive_more = { version = "2", features = ["full"] }
|
# Error Reporting
|
||||||
thiserror = "2"
|
thiserror = "2"
|
||||||
miette = { version = "7.4", features = ["fancy"] }
|
miette = { version = "7.4", features = ["fancy"] }
|
||||||
|
|
||||||
hashbrown = "0.16"
|
# Data Structure
|
||||||
string-interner = "0.19"
|
hashbrown = { workspace = true }
|
||||||
bumpalo = { version = "3.20", features = [
|
smallvec = { workspace = true }
|
||||||
"allocator-api2",
|
string-interner = { workspace = true }
|
||||||
"boxed",
|
|
||||||
"collections",
|
|
||||||
] }
|
|
||||||
|
|
||||||
rust-embed = "8.11"
|
# Memory Management
|
||||||
|
bumpalo = { workspace = true }
|
||||||
|
|
||||||
regex = "1.11"
|
rnix = { workspace = true }
|
||||||
|
|
||||||
nix-nar = "0.3"
|
ere = { workspace = true }
|
||||||
sha2 = "0.10"
|
ghost-cell = { workspace = true }
|
||||||
sha1 = "0.10"
|
|
||||||
md5 = "0.8"
|
|
||||||
hex = "0.4"
|
|
||||||
|
|
||||||
base64 = "0.22"
|
fix-common = { path = "../fix-common" }
|
||||||
|
fix-codegen = { path = "../fix-codegen" }
|
||||||
reqwest = { version = "0.13", features = [
|
fix-error = { path = "../fix-error" }
|
||||||
"blocking",
|
fix-ir = { path = "../fix-ir" }
|
||||||
"rustls",
|
fix-vm = { path = "../fix-vm" }
|
||||||
], default-features = false }
|
|
||||||
tar = "0.4"
|
|
||||||
flate2 = "1.0"
|
|
||||||
xz2 = "0.1"
|
|
||||||
bzip2 = "0.6"
|
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
|
||||||
serde_json = "1.0"
|
|
||||||
# spec 1.0.0
|
|
||||||
toml = "=0.9.9"
|
|
||||||
dirs = "6.0"
|
|
||||||
tempfile = "3.24"
|
|
||||||
rusqlite = { version = "0.38", features = ["bundled"] }
|
|
||||||
|
|
||||||
rnix = "0.14"
|
|
||||||
rowan = "0.16"
|
|
||||||
|
|
||||||
ere = "0.2.4"
|
|
||||||
num_enum = "0.7.5"
|
|
||||||
tap = "1.0.1"
|
|
||||||
|
|
||||||
ghost-cell = "0.2"
|
|
||||||
colored = "3.1"
|
|
||||||
sptr = "0.3"
|
|
||||||
sealed = "0.6"
|
|
||||||
small-map = "0.1"
|
|
||||||
smallvec = "1.15"
|
|
||||||
|
|
||||||
[dependencies.gc-arena]
|
|
||||||
git = "https://github.com/kyren/gc-arena"
|
|
||||||
rev = "75671ae03f53718357b741ed4027560f14e90836"
|
|
||||||
features = ["allocator-api2", "hashbrown", "smallvec"]
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = { version = "0.8", features = ["html_reports"] }
|
criterion = { version = "0.8", features = ["html_reports"] }
|
||||||
|
tempfile = "3.24"
|
||||||
test-log = { version = "0.2", features = ["trace"] }
|
test-log = { version = "0.2", features = ["trace"] }
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
|
|||||||
@@ -1,18 +1,15 @@
|
|||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use fix::error::{Result, Source};
|
use fix::Evaluator;
|
||||||
use fix::runtime::Runtime;
|
use fix_common::Value;
|
||||||
use fix::value::Value;
|
use fix_error::{Result, Source};
|
||||||
|
|
||||||
pub fn eval(expr: &str) -> Value {
|
pub fn eval(expr: &str) -> Value {
|
||||||
Runtime::new()
|
Evaluator::new()
|
||||||
.unwrap()
|
|
||||||
.eval(Source::new_eval(expr.into()).unwrap())
|
.eval(Source::new_eval(expr.into()).unwrap())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn eval_result(expr: &str) -> Result<Value> {
|
pub fn eval_result(expr: &str) -> Result<Value> {
|
||||||
Runtime::new()
|
Evaluator::new().eval(Source::new_eval(expr.into()).unwrap())
|
||||||
.unwrap()
|
|
||||||
.eval(Source::new_eval(expr.into()).unwrap())
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,369 +0,0 @@
|
|||||||
use std::fmt::Write;
|
|
||||||
|
|
||||||
use colored::Colorize;
|
|
||||||
use num_enum::TryFromPrimitive;
|
|
||||||
|
|
||||||
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,
|
|
||||||
pc: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
|
||||||
pub fn new(ip: InstructionPtr, ctx: &'a Ctx) -> Self {
|
|
||||||
Self {
|
|
||||||
code: ctx.get_code(),
|
|
||||||
ctx,
|
|
||||||
pc: ip.0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_u8(&mut self) -> u8 {
|
|
||||||
let b = self.code[self.pc];
|
|
||||||
self.pc += 1;
|
|
||||||
b
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_u16(&mut self) -> u16 {
|
|
||||||
let bytes = self.code[self.pc..self.pc + 2]
|
|
||||||
.try_into()
|
|
||||||
.expect("no enough bytes");
|
|
||||||
self.pc += 2;
|
|
||||||
u16::from_le_bytes(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_u32(&mut self) -> u32 {
|
|
||||||
let bytes = self.code[self.pc..self.pc + 4]
|
|
||||||
.try_into()
|
|
||||||
.expect("no enough bytes");
|
|
||||||
self.pc += 4;
|
|
||||||
u32::from_le_bytes(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_i32(&mut self) -> i32 {
|
|
||||||
let bytes = self.code[self.pc..self.pc + 4]
|
|
||||||
.try_into()
|
|
||||||
.expect("no enough bytes");
|
|
||||||
self.pc += 4;
|
|
||||||
i32::from_le_bytes(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_i64(&mut self) -> i64 {
|
|
||||||
let bytes = self.code[self.pc..self.pc + 8]
|
|
||||||
.try_into()
|
|
||||||
.expect("no enough bytes");
|
|
||||||
self.pc += 8;
|
|
||||||
i64::from_le_bytes(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_f64(&mut self) -> f64 {
|
|
||||||
let bytes = self.code[self.pc..self.pc + 8]
|
|
||||||
.try_into()
|
|
||||||
.expect("no enough bytes");
|
|
||||||
self.pc += 8;
|
|
||||||
f64::from_le_bytes(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn disassemble(&mut self) -> String {
|
|
||||||
self.disassemble_impl(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn disassemble_colored(&mut self) -> String {
|
|
||||||
self.disassemble_impl(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn disassemble_impl(&mut self, color: bool) -> String {
|
|
||||||
let mut out = String::new();
|
|
||||||
if color {
|
|
||||||
let _ = writeln!(out, "{}", "=== Bytecode Disassembly ===".bold().white());
|
|
||||||
let _ = writeln!(
|
|
||||||
out,
|
|
||||||
"{} {}",
|
|
||||||
"Length:".white(),
|
|
||||||
format!("{} bytes", self.code.len()).cyan()
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
let _ = writeln!(out, "=== Bytecode Disassembly ===");
|
|
||||||
let _ = writeln!(out, "Length: {} bytes", self.code.len());
|
|
||||||
}
|
|
||||||
|
|
||||||
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.pc];
|
|
||||||
let mut chunks = bytes_slice.chunks(4);
|
|
||||||
|
|
||||||
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::from(" ");
|
|
||||||
for b in chunk {
|
|
||||||
let _ = write!(&mut temp, " {:02x}", b);
|
|
||||||
}
|
|
||||||
temp
|
|
||||||
};
|
|
||||||
|
|
||||||
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 _ = write!(out, " ");
|
|
||||||
for _ in 0..extra_width {
|
|
||||||
let _ = write!(out, " ");
|
|
||||||
}
|
|
||||||
let _ = writeln!(out, " {:<14} |", bytes_str);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out
|
|
||||||
}
|
|
||||||
|
|
||||||
fn decode_instruction(&mut self, op_byte: u8, current_pc: usize) -> (&'static str, String) {
|
|
||||||
let op = Op::try_from_primitive(op_byte).expect("invalid op code");
|
|
||||||
|
|
||||||
match op {
|
|
||||||
Op::PushSmi => {
|
|
||||||
let val = self.read_i32();
|
|
||||||
("PushSmi", format!("{}", val))
|
|
||||||
}
|
|
||||||
Op::PushBigInt => {
|
|
||||||
let val = self.read_i64();
|
|
||||||
("PushBigInt", format!("{}", val))
|
|
||||||
}
|
|
||||||
Op::PushFloat => {
|
|
||||||
let val = self.read_f64();
|
|
||||||
("PushFloat", format!("{}", val))
|
|
||||||
}
|
|
||||||
Op::PushString => {
|
|
||||||
let idx = self.read_u32();
|
|
||||||
let s = self.ctx.lookup_string(idx);
|
|
||||||
let len = s.len();
|
|
||||||
let mut s_fmt = format!("{:?}", s);
|
|
||||||
if s_fmt.len() > 60 {
|
|
||||||
s_fmt.truncate(57);
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
write!(s_fmt, "...\" (total {len} bytes)").unwrap();
|
|
||||||
}
|
|
||||||
("PushString", format!("@{} {}", idx, s_fmt))
|
|
||||||
}
|
|
||||||
Op::PushNull => ("PushNull", String::new()),
|
|
||||||
Op::PushTrue => ("PushTrue", String::new()),
|
|
||||||
Op::PushFalse => ("PushFalse", String::new()),
|
|
||||||
|
|
||||||
Op::LoadLocal => {
|
|
||||||
let idx = self.read_u32();
|
|
||||||
("LoadLocal", format!("[{}]", idx))
|
|
||||||
}
|
|
||||||
Op::LoadOuter => {
|
|
||||||
let depth = self.read_u8();
|
|
||||||
let idx = self.read_u32();
|
|
||||||
("LoadOuter", format!("depth={} [{}]", depth, idx))
|
|
||||||
}
|
|
||||||
Op::StoreLocal => {
|
|
||||||
let idx = self.read_u32();
|
|
||||||
("StoreLocal", format!("[{}]", idx))
|
|
||||||
}
|
|
||||||
Op::AllocLocals => {
|
|
||||||
let count = self.read_u32();
|
|
||||||
("AllocLocals", format!("count={}", count))
|
|
||||||
}
|
|
||||||
|
|
||||||
Op::MakeThunk => {
|
|
||||||
let offset = self.read_u32();
|
|
||||||
("MakeThunk", format!("-> {:04x}", offset))
|
|
||||||
}
|
|
||||||
Op::MakeClosure => {
|
|
||||||
let offset = self.read_u32();
|
|
||||||
let slots = self.read_u32();
|
|
||||||
("MakeClosure", format!("-> {:04x} slots={}", offset, slots))
|
|
||||||
}
|
|
||||||
Op::MakePatternClosure => {
|
|
||||||
let offset = self.read_u32();
|
|
||||||
let slots = self.read_u32();
|
|
||||||
let req_count = self.read_u16();
|
|
||||||
let opt_count = self.read_u16();
|
|
||||||
let ellipsis = self.read_u8() != 0;
|
|
||||||
|
|
||||||
let mut arg_str = format!(
|
|
||||||
"-> {:04x} slots={} req={} opt={} ...={})",
|
|
||||||
offset, slots, req_count, opt_count, ellipsis
|
|
||||||
);
|
|
||||||
|
|
||||||
arg_str.push_str(" Args=[");
|
|
||||||
for _ in 0..req_count {
|
|
||||||
let idx = self.read_u32();
|
|
||||||
arg_str.push_str(&format!("Req({}) ", self.ctx.lookup_string(idx)));
|
|
||||||
}
|
|
||||||
for _ in 0..opt_count {
|
|
||||||
let idx = self.read_u32();
|
|
||||||
arg_str.push_str(&format!("Opt({}) ", self.ctx.lookup_string(idx)));
|
|
||||||
}
|
|
||||||
|
|
||||||
let total_args = req_count + opt_count;
|
|
||||||
for _ in 0..total_args {
|
|
||||||
let _name_idx = self.read_u32();
|
|
||||||
let _span_id = self.read_u32();
|
|
||||||
}
|
|
||||||
arg_str.push(']');
|
|
||||||
|
|
||||||
("MakePatternClosure", arg_str)
|
|
||||||
}
|
|
||||||
|
|
||||||
Op::Call => {
|
|
||||||
let span_id = self.read_u32();
|
|
||||||
("Call", format!("span={}", span_id))
|
|
||||||
}
|
|
||||||
Op::CallNoSpan => ("CallNoSpan", String::new()),
|
|
||||||
|
|
||||||
Op::MakeAttrs => {
|
|
||||||
let count = self.read_u32();
|
|
||||||
("MakeAttrs", format!("size={}", count))
|
|
||||||
}
|
|
||||||
Op::MakeEmptyAttrs => ("MakeEmptyAttrs", String::new()),
|
|
||||||
|
|
||||||
Op::Select => {
|
|
||||||
let path_len = self.read_u16();
|
|
||||||
let span_id = self.read_u32();
|
|
||||||
("Select", format!("path_len={} span={}", path_len, span_id))
|
|
||||||
}
|
|
||||||
Op::SelectDefault => {
|
|
||||||
let path_len = self.read_u16();
|
|
||||||
let span_id = self.read_u32();
|
|
||||||
(
|
|
||||||
"SelectDefault",
|
|
||||||
format!("path_len={} span={}", path_len, span_id),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Op::HasAttr => {
|
|
||||||
let path_len = self.read_u16();
|
|
||||||
("HasAttr", format!("path_len={}", path_len))
|
|
||||||
}
|
|
||||||
|
|
||||||
Op::MakeList => {
|
|
||||||
let count = self.read_u32();
|
|
||||||
("MakeList", format!("size={}", count))
|
|
||||||
}
|
|
||||||
Op::MakeEmptyList => ("MakeEmptyList", String::new()),
|
|
||||||
|
|
||||||
Op::OpAdd => ("OpAdd", String::new()),
|
|
||||||
Op::OpSub => ("OpSub", String::new()),
|
|
||||||
Op::OpMul => ("OpMul", String::new()),
|
|
||||||
Op::OpDiv => ("OpDiv", String::new()),
|
|
||||||
Op::OpEq => ("OpEq", String::new()),
|
|
||||||
Op::OpNeq => ("OpNeq", String::new()),
|
|
||||||
Op::OpLt => ("OpLt", String::new()),
|
|
||||||
Op::OpGt => ("OpGt", String::new()),
|
|
||||||
Op::OpLeq => ("OpLeq", String::new()),
|
|
||||||
Op::OpGeq => ("OpGeq", String::new()),
|
|
||||||
Op::OpConcat => ("OpConcat", String::new()),
|
|
||||||
Op::OpUpdate => ("OpUpdate", String::new()),
|
|
||||||
Op::OpNeg => ("OpNeg", String::new()),
|
|
||||||
Op::OpNot => ("OpNot", String::new()),
|
|
||||||
|
|
||||||
Op::JumpIfFalse => {
|
|
||||||
let offset = self.read_i32();
|
|
||||||
let target = (current_pc as isize + 1 + 4 + offset as isize) as usize;
|
|
||||||
(
|
|
||||||
"JumpIfFalse",
|
|
||||||
format!("-> {:04x} offset={}", target, offset),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Op::JumpIfTrue => {
|
|
||||||
let offset = self.read_i32();
|
|
||||||
let target = (current_pc as isize + 1 + 4 + offset as isize) as usize;
|
|
||||||
("JumpIfTrue", format!("-> {:04x} offset={}", target, offset))
|
|
||||||
}
|
|
||||||
Op::Jump => {
|
|
||||||
let offset = self.read_i32();
|
|
||||||
let target = (current_pc as isize + 1 + 4 + offset as isize) as usize;
|
|
||||||
("Jump", format!("-> {:04x} offset={}", target, offset))
|
|
||||||
}
|
|
||||||
|
|
||||||
Op::ConcatStrings => {
|
|
||||||
let count = self.read_u16();
|
|
||||||
let force = self.read_u8();
|
|
||||||
("ConcatStrings", format!("count={} force={}", count, force))
|
|
||||||
}
|
|
||||||
Op::ResolvePath => ("ResolvePath", String::new()),
|
|
||||||
Op::Assert => {
|
|
||||||
let raw_idx = self.read_u32();
|
|
||||||
let span_id = self.read_u32();
|
|
||||||
("Assert", format!("text_id={} span={}", raw_idx, span_id))
|
|
||||||
}
|
|
||||||
Op::PushWith => ("PushWith", String::new()),
|
|
||||||
Op::PopWith => ("PopWith", String::new()),
|
|
||||||
Op::WithLookup => {
|
|
||||||
let idx = self.read_u32();
|
|
||||||
let name = self.ctx.lookup_string(idx);
|
|
||||||
("WithLookup", format!("{:?}", name))
|
|
||||||
}
|
|
||||||
|
|
||||||
Op::LoadBuiltins => ("LoadBuiltins", String::new()),
|
|
||||||
Op::LoadBuiltin => {
|
|
||||||
let id = self.read_u8();
|
|
||||||
("LoadBuiltin", format!("id={}", id))
|
|
||||||
}
|
|
||||||
Op::MkPos => {
|
|
||||||
let span_id = self.read_u32();
|
|
||||||
("MkPos", format!("id={}", span_id))
|
|
||||||
}
|
|
||||||
Op::LoadReplBinding => {
|
|
||||||
let idx = self.read_u32();
|
|
||||||
let name = self.ctx.lookup_string(idx);
|
|
||||||
("LoadReplBinding", format!("{:?}", name))
|
|
||||||
}
|
|
||||||
Op::LoadScopedBinding => {
|
|
||||||
let idx = self.read_u32();
|
|
||||||
let name = self.ctx.lookup_string(idx);
|
|
||||||
("LoadScopedBinding", format!("{:?}", name))
|
|
||||||
}
|
|
||||||
Op::Return => ("Return", String::new()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+547
-14
@@ -1,22 +1,555 @@
|
|||||||
#![warn(clippy::unwrap_used)]
|
#![warn(clippy::unwrap_used)]
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
mod boxing;
|
use bumpalo::Bump;
|
||||||
pub mod error;
|
use fix_codegen::{BytecodeContext, InstructionPtr};
|
||||||
pub mod logging;
|
use fix_common::{StringId, Symbol};
|
||||||
pub mod runtime;
|
use fix_error::{Error, Result, Source};
|
||||||
pub mod value;
|
use fix_ir::downgrade::{Downgrade as _, DowngradeContext};
|
||||||
|
use fix_ir::{Ir, IrRef, RawIrRef, ThunkId};
|
||||||
|
use fix_vm::{ForceMode, StaticValue, Vm, VmContext};
|
||||||
|
use ghost_cell::{GhostCell, GhostToken};
|
||||||
|
use hashbrown::{HashMap, HashSet};
|
||||||
|
use string_interner::DefaultStringInterner;
|
||||||
|
|
||||||
mod codegen;
|
|
||||||
mod derivation;
|
|
||||||
mod disassembler;
|
|
||||||
mod downgrade;
|
|
||||||
// mod fetcher;
|
// mod fetcher;
|
||||||
mod ir;
|
// mod nar;
|
||||||
mod nar;
|
// mod nix_utils;
|
||||||
mod nix_utils;
|
// mod store;
|
||||||
mod store;
|
// mod string_context;
|
||||||
mod string_context;
|
mod derivation;
|
||||||
|
pub mod logging;
|
||||||
|
|
||||||
#[global_allocator]
|
#[global_allocator]
|
||||||
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
||||||
|
|
||||||
|
pub struct Evaluator {
|
||||||
|
bytecode: Vec<u8>,
|
||||||
|
constants: Constants,
|
||||||
|
strings: DefaultStringInterner,
|
||||||
|
|
||||||
|
sources: Vec<Source>,
|
||||||
|
spans: Vec<(usize, rnix::TextRange)>,
|
||||||
|
// FIXME: remove?
|
||||||
|
thunk_count: usize,
|
||||||
|
|
||||||
|
global_env: HashMap<StringId, Ir<'static, RawIrRef<'static>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Evaluator {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Evaluator {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let mut strings = DefaultStringInterner::new();
|
||||||
|
let global_env = fix_ir::new_global_env(&mut strings);
|
||||||
|
Self {
|
||||||
|
sources: Vec::new(),
|
||||||
|
spans: Vec::new(),
|
||||||
|
strings,
|
||||||
|
thunk_count: 0,
|
||||||
|
bytecode: Vec::new(),
|
||||||
|
constants: Constants::default(),
|
||||||
|
|
||||||
|
global_env,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eval(&mut self, source: Source) -> Result<fix_common::Value> {
|
||||||
|
self.do_eval(source, None, ForceMode::AsIs)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eval_shallow(&mut self, source: Source) -> Result<fix_common::Value> {
|
||||||
|
self.do_eval(source, None, ForceMode::Shallow)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eval_deep(&mut self, source: Source) -> Result<fix_common::Value> {
|
||||||
|
self.do_eval(source, None, ForceMode::Deep)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eval_repl(
|
||||||
|
&mut self,
|
||||||
|
source: Source,
|
||||||
|
scope: &HashSet<StringId>,
|
||||||
|
) -> Result<fix_common::Value> {
|
||||||
|
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<fix_common::Value> {
|
||||||
|
let root = self.downgrade(source, extra_scope)?;
|
||||||
|
let ip = fix_codegen::compile_bytecode(root.as_ref(), self);
|
||||||
|
let vm = Vm::new(self, ip, force_mode);
|
||||||
|
vm.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_binding(
|
||||||
|
&mut self,
|
||||||
|
_ident: &str,
|
||||||
|
_expr: &str,
|
||||||
|
_scope: &mut HashSet<StringId>,
|
||||||
|
) -> Result<fix_common::Value> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn compile_bytecode(&mut self, source: Source) -> Result<InstructionPtr> {
|
||||||
|
let root = self.downgrade(source, None)?;
|
||||||
|
let ip = fix_codegen::compile_bytecode(root.as_ref(), self);
|
||||||
|
Ok(ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn downgrade_ctx<'a, 'bump, 'id>(
|
||||||
|
&'a mut self,
|
||||||
|
bump: &'bump Bump,
|
||||||
|
token: GhostToken<'id>,
|
||||||
|
extra_scope: Option<Scope<'a>>,
|
||||||
|
) -> DowngradeCtx<'a, 'id, 'bump> {
|
||||||
|
let Self {
|
||||||
|
global_env,
|
||||||
|
sources,
|
||||||
|
thunk_count,
|
||||||
|
strings,
|
||||||
|
..
|
||||||
|
} = self;
|
||||||
|
DowngradeCtx {
|
||||||
|
bump,
|
||||||
|
token,
|
||||||
|
strings,
|
||||||
|
source: sources.last().expect("no current source").clone(),
|
||||||
|
scopes: [Scope::Global(global_env)]
|
||||||
|
.into_iter()
|
||||||
|
.chain(extra_scope)
|
||||||
|
.collect(),
|
||||||
|
with_scope_count: 0,
|
||||||
|
arg_count: 0,
|
||||||
|
thunk_count,
|
||||||
|
thunk_scopes: vec![ThunkScope::new_in(bump)],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn downgrade<'a>(
|
||||||
|
&'a mut self,
|
||||||
|
source: Source,
|
||||||
|
extra_scope: Option<Scope<'a>>,
|
||||||
|
) -> Result<OwnedIr> {
|
||||||
|
tracing::debug!("Parsing Nix expression");
|
||||||
|
|
||||||
|
self.sources.push(source.clone());
|
||||||
|
|
||||||
|
let root = rnix::Root::parse(&source.src);
|
||||||
|
handle_parse_error(root.errors(), source).map_or(Ok(()), Err)?;
|
||||||
|
|
||||||
|
tracing::debug!("Downgrading Nix expression");
|
||||||
|
let expr = root
|
||||||
|
.tree()
|
||||||
|
.expr()
|
||||||
|
.ok_or_else(|| Error::parse_error("unexpected EOF".into()))?;
|
||||||
|
let bump = Bump::new();
|
||||||
|
GhostToken::new(|token| {
|
||||||
|
let ir = self
|
||||||
|
.downgrade_ctx(&bump, token, extra_scope)
|
||||||
|
.downgrade_toplevel(expr)?;
|
||||||
|
let ir = unsafe { std::mem::transmute::<RawIrRef<'_>, RawIrRef<'static>>(ir) };
|
||||||
|
Ok(OwnedIr { _bump: bump, ir })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VmContext for &mut Evaluator {
|
||||||
|
fn intern_string(&mut self, s: impl AsRef<str>) -> StringId {
|
||||||
|
StringId(self.strings.get_or_intern(s))
|
||||||
|
}
|
||||||
|
fn resolve_string(&self, id: StringId) -> &str {
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
self.strings.resolve(id.0).unwrap()
|
||||||
|
}
|
||||||
|
fn bytecode(&self) -> &[u8] {
|
||||||
|
&self.bytecode
|
||||||
|
}
|
||||||
|
fn get_const(&self, id: u32) -> StaticValue {
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
self.constants.get(id).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile(&mut self, _source: Source) {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Constants {
|
||||||
|
data: Vec<StaticValue>,
|
||||||
|
dedup: HashMap<u64, u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Constants {
|
||||||
|
fn insert(&mut self, val: StaticValue) -> u32 {
|
||||||
|
let bits = val.to_bits();
|
||||||
|
*self.dedup.entry(bits).or_insert_with(|| {
|
||||||
|
let idx = self.data.len() as u32;
|
||||||
|
self.data.push(val);
|
||||||
|
idx
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&self, id: u32) -> Option<StaticValue> {
|
||||||
|
self.data.get(id as usize).copied()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_error_span(error: &rnix::ParseError) -> Option<rnix::TextRange> {
|
||||||
|
use rnix::ParseError::*;
|
||||||
|
match error {
|
||||||
|
Unexpected(range)
|
||||||
|
| UnexpectedExtra(range)
|
||||||
|
| UnexpectedWanted(_, range, _)
|
||||||
|
| UnexpectedDoubleBind(range)
|
||||||
|
| DuplicatedArgs(range, _) => Some(*range),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_parse_error<'a>(
|
||||||
|
errors: impl IntoIterator<Item = &'a rnix::ParseError>,
|
||||||
|
source: Source,
|
||||||
|
) -> Option<Box<Error>> {
|
||||||
|
for err in errors {
|
||||||
|
if let Some(span) = parse_error_span(err) {
|
||||||
|
return Some(
|
||||||
|
Error::parse_error(err.to_string())
|
||||||
|
.with_source(source)
|
||||||
|
.with_span(span),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DowngradeCtx<'ctx, 'id, 'ir> {
|
||||||
|
bump: &'ir Bump,
|
||||||
|
token: GhostToken<'id>,
|
||||||
|
strings: &'ctx mut DefaultStringInterner,
|
||||||
|
source: Source,
|
||||||
|
scopes: Vec<Scope<'ctx>>,
|
||||||
|
with_scope_count: u32,
|
||||||
|
arg_count: u32,
|
||||||
|
thunk_count: &'ctx mut usize,
|
||||||
|
thunk_scopes: Vec<ThunkScope<'id, 'ir>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_thunk<'id>(ir: IrRef<'id, '_>, token: &GhostToken<'id>) -> bool {
|
||||||
|
!matches!(
|
||||||
|
ir.borrow(token),
|
||||||
|
Ir::Builtin(_)
|
||||||
|
| Ir::Builtins
|
||||||
|
| Ir::Int(_)
|
||||||
|
| Ir::Float(_)
|
||||||
|
| Ir::Bool(_)
|
||||||
|
| Ir::Null
|
||||||
|
| Ir::Str(_)
|
||||||
|
| Ir::Thunk(_)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'ctx, 'id, 'ir> DowngradeCtx<'ctx, 'id, 'ir> {
|
||||||
|
fn new(
|
||||||
|
bump: &'ir Bump,
|
||||||
|
token: GhostToken<'id>,
|
||||||
|
symbols: &'ctx mut DefaultStringInterner,
|
||||||
|
global: &'ctx HashMap<StringId, Ir<'static, RawIrRef<'static>>>,
|
||||||
|
extra_scope: Option<Scope<'ctx>>,
|
||||||
|
thunk_count: &'ctx mut usize,
|
||||||
|
source: Source,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
bump,
|
||||||
|
token,
|
||||||
|
strings: symbols,
|
||||||
|
source,
|
||||||
|
scopes: std::iter::once(Scope::Global(global))
|
||||||
|
.chain(extra_scope)
|
||||||
|
.collect(),
|
||||||
|
thunk_count,
|
||||||
|
arg_count: 0,
|
||||||
|
with_scope_count: 0,
|
||||||
|
thunk_scopes: vec![ThunkScope::new_in(bump)],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'ctx: 'ir, 'id, 'ir> DowngradeContext<'id, 'ir> for DowngradeCtx<'ctx, 'id, 'ir> {
|
||||||
|
fn new_expr(&self, expr: Ir<'ir, IrRef<'id, 'ir>>) -> IrRef<'id, 'ir> {
|
||||||
|
IrRef::new(self.bump.alloc(GhostCell::new(expr)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn maybe_thunk(&mut self, ir: IrRef<'id, 'ir>) -> IrRef<'id, 'ir> {
|
||||||
|
if !should_thunk(ir, &self.token) {
|
||||||
|
return ir;
|
||||||
|
}
|
||||||
|
|
||||||
|
let id = ThunkId(*self.thunk_count);
|
||||||
|
*self.thunk_count = self.thunk_count.checked_add(1).expect("thunk id overflow");
|
||||||
|
self.thunk_scopes
|
||||||
|
.last_mut()
|
||||||
|
.expect("no active cache scope")
|
||||||
|
.add_binding(id, ir, &self.token);
|
||||||
|
IrRef::alloc(self.bump, Ir::Thunk(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_sym(&mut self, sym: String) -> StringId {
|
||||||
|
StringId(self.strings.get_or_intern(sym))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_sym(&self, id: StringId) -> Symbol<'_> {
|
||||||
|
self.strings.resolve(id.0).expect("no symbol found").into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lookup(&self, sym: StringId, span: rnix::TextRange) -> Result<IrRef<'id, 'ir>> {
|
||||||
|
for scope in self.scopes.iter().rev() {
|
||||||
|
match scope {
|
||||||
|
&Scope::Global(global_scope) => {
|
||||||
|
if let Some(expr) = global_scope.get(&sym) {
|
||||||
|
let ir = match expr {
|
||||||
|
Ir::Builtins => Ir::Builtins,
|
||||||
|
Ir::Builtin(s) => Ir::Builtin(*s),
|
||||||
|
Ir::Bool(b) => Ir::Bool(*b),
|
||||||
|
Ir::Null => Ir::Null,
|
||||||
|
_ => unreachable!("globals should only contain leaf IR nodes"),
|
||||||
|
};
|
||||||
|
return Ok(self.new_expr(ir));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&Scope::Repl(repl_bindings) => {
|
||||||
|
if repl_bindings.contains(&sym) {
|
||||||
|
return Ok(self.new_expr(Ir::ReplBinding(sym)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Scope::ScopedImport(scoped_bindings) => {
|
||||||
|
if scoped_bindings.contains(&sym) {
|
||||||
|
return Ok(self.new_expr(Ir::ScopedImportBinding(sym)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Scope::Let(let_scope) => {
|
||||||
|
if let Some(&expr) = let_scope.get(&sym) {
|
||||||
|
return Ok(self.new_expr(Ir::Thunk(expr)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&Scope::Param {
|
||||||
|
sym: param_sym,
|
||||||
|
abs_layer,
|
||||||
|
} => {
|
||||||
|
if param_sym == sym {
|
||||||
|
return Ok(self.new_expr(Ir::Arg {
|
||||||
|
layer: self.thunk_scopes.len() - abs_layer,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.with_scope_count > 0 {
|
||||||
|
Ok(self.new_expr(Ir::WithLookup(sym)))
|
||||||
|
} else {
|
||||||
|
Err(Error::downgrade_error(
|
||||||
|
format!("'{}' not found", self.get_sym(sym)),
|
||||||
|
self.get_current_source(),
|
||||||
|
span,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_current_source(&self) -> Source {
|
||||||
|
self.source.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_let_scope<F, R>(&mut self, keys: &[StringId], f: F) -> Result<R>
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut Self) -> Result<(bumpalo::collections::Vec<'ir, IrRef<'id, 'ir>>, R)>,
|
||||||
|
{
|
||||||
|
let base = *self.thunk_count;
|
||||||
|
*self.thunk_count = self
|
||||||
|
.thunk_count
|
||||||
|
.checked_add(keys.len())
|
||||||
|
.expect("thunk id overflow");
|
||||||
|
let iter = keys
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(offset, &key)| (key, ThunkId(base + offset)));
|
||||||
|
self.scopes.push(Scope::Let(iter.collect()));
|
||||||
|
let (vals, ret) = {
|
||||||
|
let mut guard = ScopeGuard { ctx: self };
|
||||||
|
f(guard.as_ctx())?
|
||||||
|
};
|
||||||
|
assert_eq!(keys.len(), vals.len());
|
||||||
|
let scope = self.thunk_scopes.last_mut().expect("no active thunk scope");
|
||||||
|
scope.extend_bindings((base..base + keys.len()).map(ThunkId).zip(vals));
|
||||||
|
Ok(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_param_scope<F, R>(&mut self, sym: StringId, f: F) -> R
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut Self) -> R,
|
||||||
|
{
|
||||||
|
self.scopes.push(Scope::Param {
|
||||||
|
sym,
|
||||||
|
abs_layer: self.thunk_scopes.len(),
|
||||||
|
});
|
||||||
|
let mut guard = ScopeGuard { ctx: self };
|
||||||
|
f(guard.as_ctx())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_with_scope<F, R>(&mut self, f: F) -> R
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut Self) -> R,
|
||||||
|
{
|
||||||
|
self.with_scope_count += 1;
|
||||||
|
let ret = f(self);
|
||||||
|
self.with_scope_count -= 1;
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_thunk_scope<F, R>(
|
||||||
|
&mut self,
|
||||||
|
f: F,
|
||||||
|
) -> (
|
||||||
|
R,
|
||||||
|
bumpalo::collections::Vec<'ir, (ThunkId, IrRef<'id, 'ir>)>,
|
||||||
|
)
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut Self) -> R,
|
||||||
|
{
|
||||||
|
self.thunk_scopes.push(ThunkScope::new_in(self.bump));
|
||||||
|
let ret = f(self);
|
||||||
|
(
|
||||||
|
ret,
|
||||||
|
self.thunk_scopes
|
||||||
|
.pop()
|
||||||
|
.expect("no thunk scope left???")
|
||||||
|
.bindings,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bump(&self) -> &'ir bumpalo::Bump {
|
||||||
|
self.bump
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'id, 'ir, 'ctx: 'ir> DowngradeCtx<'ctx, 'id, 'ir> {
|
||||||
|
fn downgrade_toplevel(mut self, root: rnix::ast::Expr) -> Result<RawIrRef<'ir>> {
|
||||||
|
let body = root.downgrade(&mut self)?;
|
||||||
|
let thunks = self
|
||||||
|
.thunk_scopes
|
||||||
|
.pop()
|
||||||
|
.expect("no thunk scope left???")
|
||||||
|
.bindings;
|
||||||
|
let ir = IrRef::alloc(self.bump, Ir::TopLevel { body, thunks });
|
||||||
|
Ok(ir.freeze(self.token))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ThunkScope<'id, 'ir> {
|
||||||
|
bindings: bumpalo::collections::Vec<'ir, (ThunkId, IrRef<'id, 'ir>)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'id, 'ir> ThunkScope<'id, 'ir> {
|
||||||
|
fn new_in(bump: &'ir Bump) -> Self {
|
||||||
|
Self {
|
||||||
|
bindings: bumpalo::collections::Vec::new_in(bump),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_binding(&mut self, id: ThunkId, ir: IrRef<'id, 'ir>, _token: &GhostToken<'id>) {
|
||||||
|
self.bindings.push((id, ir));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extend_bindings(&mut self, iter: impl IntoIterator<Item = (ThunkId, IrRef<'id, 'ir>)>) {
|
||||||
|
self.bindings.extend(iter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Scope<'ctx> {
|
||||||
|
Global(&'ctx HashMap<StringId, Ir<'static, RawIrRef<'static>>>),
|
||||||
|
Repl(&'ctx HashSet<StringId>),
|
||||||
|
ScopedImport(HashSet<StringId>),
|
||||||
|
Let(HashMap<StringId, ThunkId>),
|
||||||
|
Param { sym: StringId, abs_layer: usize },
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ScopeGuard<'a, 'ctx, 'id, 'ir> {
|
||||||
|
ctx: &'a mut DowngradeCtx<'ctx, 'id, 'ir>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for ScopeGuard<'_, '_, '_, '_> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.ctx.scopes.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'id, 'ir, 'ctx> ScopeGuard<'_, 'ctx, 'id, 'ir> {
|
||||||
|
fn as_ctx(&mut self) -> &mut DowngradeCtx<'ctx, 'id, 'ir> {
|
||||||
|
self.ctx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct OwnedIr {
|
||||||
|
_bump: Bump,
|
||||||
|
ir: RawIrRef<'static>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OwnedIr {
|
||||||
|
unsafe fn new(ir: RawIrRef<'_>, bump: Bump) -> Self {
|
||||||
|
Self {
|
||||||
|
_bump: bump,
|
||||||
|
ir: unsafe { std::mem::transmute::<RawIrRef<'_>, RawIrRef<'static>>(ir) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_ref(&self) -> RawIrRef<'_> {
|
||||||
|
self.ir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BytecodeContext for Evaluator {
|
||||||
|
fn intern_string(&mut self, s: &str) -> StringId {
|
||||||
|
StringId(self.strings.get_or_intern(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_span(&mut self, range: rnix::TextRange) -> u32 {
|
||||||
|
let id = self.spans.len();
|
||||||
|
let source_id = self
|
||||||
|
.sources
|
||||||
|
.len()
|
||||||
|
.checked_sub(1)
|
||||||
|
.expect("current_source not set");
|
||||||
|
self.spans.push((source_id, range));
|
||||||
|
id as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_code(&self) -> &[u8] {
|
||||||
|
&self.bytecode
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_code_mut(&mut self) -> &mut Vec<u8> {
|
||||||
|
&mut self.bytecode
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_constant(&mut self, val: fix_codegen::Const) -> u32 {
|
||||||
|
use fix_codegen::Const::*;
|
||||||
|
let val = match val {
|
||||||
|
Smi(x) => StaticValue::new_inline(x),
|
||||||
|
Float(x) => StaticValue::new_float(x),
|
||||||
|
Bool(x) => StaticValue::new_inline(x),
|
||||||
|
String(x) => StaticValue::new_inline(x),
|
||||||
|
PrimOp { id, arity } => StaticValue::new_primop(id, arity),
|
||||||
|
Null => StaticValue::default(),
|
||||||
|
};
|
||||||
|
self.constants.insert(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+18
-18
@@ -3,8 +3,8 @@ use std::process::exit;
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::{Args, Parser, Subcommand};
|
use clap::{Args, Parser, Subcommand};
|
||||||
use fix::error::Source;
|
use fix::Evaluator;
|
||||||
use fix::runtime::Runtime;
|
use fix_error::Source;
|
||||||
use hashbrown::HashSet;
|
use hashbrown::HashSet;
|
||||||
use rustyline::DefaultEditor;
|
use rustyline::DefaultEditor;
|
||||||
use rustyline::error::ReadlineError;
|
use rustyline::error::ReadlineError;
|
||||||
@@ -40,7 +40,7 @@ struct ExprSource {
|
|||||||
file: Option<PathBuf>,
|
file: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_compile(runtime: &mut Runtime, src: ExprSource, silent: bool) -> Result<()> {
|
fn run_compile(eval: &mut Evaluator, src: ExprSource, _silent: bool) -> Result<()> {
|
||||||
let src = if let Some(expr) = src.expr {
|
let src = if let Some(expr) = src.expr {
|
||||||
Source::new_eval(expr)?
|
Source::new_eval(expr)?
|
||||||
} else if let Some(file) = src.file {
|
} else if let Some(file) = src.file {
|
||||||
@@ -48,11 +48,11 @@ fn run_compile(runtime: &mut Runtime, src: ExprSource, silent: bool) -> Result<(
|
|||||||
} else {
|
} else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
match runtime.compile_bytecode(src) {
|
match eval.compile_bytecode(src) {
|
||||||
Ok(ip) => {
|
Ok(_ip) => {
|
||||||
if !silent {
|
// if !silent {
|
||||||
println!("{}", runtime.disassemble_colored(ip));
|
// println!("{}", eval.disassemble_colored(ip));
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("{:?}", miette::Report::new(*err));
|
eprintln!("{:?}", miette::Report::new(*err));
|
||||||
@@ -62,7 +62,7 @@ fn run_compile(runtime: &mut Runtime, src: ExprSource, silent: bool) -> Result<(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_eval(runtime: &mut Runtime, src: ExprSource) -> Result<()> {
|
fn run_eval(eval: &mut Evaluator, src: ExprSource) -> Result<()> {
|
||||||
let src = if let Some(expr) = src.expr {
|
let src = if let Some(expr) = src.expr {
|
||||||
Source::new_eval(expr)?
|
Source::new_eval(expr)?
|
||||||
} else if let Some(file) = src.file {
|
} else if let Some(file) = src.file {
|
||||||
@@ -70,7 +70,7 @@ fn run_eval(runtime: &mut Runtime, src: ExprSource) -> Result<()> {
|
|||||||
} else {
|
} else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
match runtime.eval_deep(src) {
|
match eval.eval_deep(src) {
|
||||||
Ok(value) => {
|
Ok(value) => {
|
||||||
println!("{}", value.display_compat());
|
println!("{}", value.display_compat());
|
||||||
}
|
}
|
||||||
@@ -82,7 +82,7 @@ fn run_eval(runtime: &mut Runtime, src: ExprSource) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_repl(runtime: &mut Runtime) -> Result<()> {
|
fn run_repl(eval: &mut Evaluator) -> Result<()> {
|
||||||
let mut rl = DefaultEditor::new()?;
|
let mut rl = DefaultEditor::new()?;
|
||||||
let mut scope = HashSet::new();
|
let mut scope = HashSet::new();
|
||||||
const RE: ere::Regex<3> = ere::compile_regex!("^[ \t]*([a-zA-Z_][a-zA-Z0-9_'-]*)[ \t]*(.*)$");
|
const RE: ere::Regex<3> = ere::compile_regex!("^[ \t]*([a-zA-Z_][a-zA-Z0-9_'-]*)[ \t]*(.*)$");
|
||||||
@@ -101,20 +101,20 @@ fn run_repl(runtime: &mut Runtime) -> Result<()> {
|
|||||||
eprintln!("Error: missing expression after '='");
|
eprintln!("Error: missing expression after '='");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
match runtime.add_binding(ident, expr, &mut scope) {
|
match eval.add_binding(ident, expr, &mut scope) {
|
||||||
Ok(value) => println!("{} = {}", ident, value),
|
Ok(value) => println!("{} = {}", ident, value),
|
||||||
Err(err) => eprintln!("{:?}", miette::Report::new(*err)),
|
Err(err) => eprintln!("{:?}", miette::Report::new(*err)),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let src = Source::new_repl(line)?;
|
let src = Source::new_repl(line)?;
|
||||||
match runtime.eval_repl(src, &scope) {
|
match eval.eval_repl(src, &scope) {
|
||||||
Ok(value) => println!("{value}"),
|
Ok(value) => println!("{value}"),
|
||||||
Err(err) => eprintln!("{:?}", miette::Report::new(*err)),
|
Err(err) => eprintln!("{:?}", miette::Report::new(*err)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let src = Source::new_repl(line)?;
|
let src = Source::new_repl(line)?;
|
||||||
match runtime.eval_repl(src, &scope) {
|
match eval.eval_repl(src, &scope) {
|
||||||
Ok(value) => println!("{value}"),
|
Ok(value) => println!("{value}"),
|
||||||
Err(err) => eprintln!("{:?}", miette::Report::new(*err)),
|
Err(err) => eprintln!("{:?}", miette::Report::new(*err)),
|
||||||
}
|
}
|
||||||
@@ -141,11 +141,11 @@ fn main() -> Result<()> {
|
|||||||
|
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
|
|
||||||
let mut runtime = Runtime::new()?;
|
let mut eval = Evaluator::new();
|
||||||
|
|
||||||
match cli.command {
|
match cli.command {
|
||||||
Command::Compile { source, silent } => run_compile(&mut runtime, source, silent),
|
Command::Compile { source, silent } => run_compile(&mut eval, source, silent),
|
||||||
Command::Eval { source } => run_eval(&mut runtime, source),
|
Command::Eval { source } => run_eval(&mut eval, source),
|
||||||
Command::Repl => run_repl(&mut runtime),
|
Command::Repl => run_repl(&mut eval),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ use std::path::Path;
|
|||||||
use nix_nar::Encoder;
|
use nix_nar::Encoder;
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
use crate::error::{Error, Result};
|
use fix_error::{Error, Result};
|
||||||
|
|
||||||
pub fn compute_nar_hash(path: &Path) -> Result<[u8; 32]> {
|
pub fn compute_nar_hash(path: &Path) -> Result<[u8; 32]> {
|
||||||
let mut hasher = Sha256::new();
|
let mut hasher = Sha256::new();
|
||||||
|
|||||||
@@ -1,559 +0,0 @@
|
|||||||
use bumpalo::Bump;
|
|
||||||
use gc_arena::{Arena, Rootable};
|
|
||||||
use ghost_cell::{GhostCell, GhostToken};
|
|
||||||
use hashbrown::{HashMap, HashSet};
|
|
||||||
use rnix::TextRange;
|
|
||||||
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::{Ir, IrRef, RawIrRef, StringId, ThunkId};
|
|
||||||
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 stack;
|
|
||||||
pub(crate) mod value;
|
|
||||||
mod vm;
|
|
||||||
pub(crate) use builtins::{BUILTINS, BuiltinId};
|
|
||||||
use stack::Stack;
|
|
||||||
use vm::{ErrorFrame, GcRoot};
|
|
||||||
|
|
||||||
pub struct Runtime {
|
|
||||||
// global
|
|
||||||
sources: Vec<Source>,
|
|
||||||
spans: Vec<(usize, TextRange)>,
|
|
||||||
store: DaemonStore,
|
|
||||||
strings: DefaultStringInterner,
|
|
||||||
// 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<'_>]>,
|
|
||||||
force_mode: ForceMode,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Runtime {
|
|
||||||
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 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 {
|
|
||||||
sources: Vec::new(),
|
|
||||||
spans: Vec::new(),
|
|
||||||
store,
|
|
||||||
strings,
|
|
||||||
thunk_count: 0,
|
|
||||||
bytecode: Vec::new(),
|
|
||||||
constants: Vec::new(),
|
|
||||||
constant_dedup: HashMap::new(),
|
|
||||||
|
|
||||||
global_env,
|
|
||||||
|
|
||||||
pc: 0,
|
|
||||||
fuel: Self::DEFAULT_FUEL_AMOUNT,
|
|
||||||
error_contexts: Stack::new(),
|
|
||||||
arena,
|
|
||||||
force_mode: ForceMode::default(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn eval(&mut self, source: Source) -> Result<crate::value::Value> {
|
|
||||||
self.do_eval(source, None, ForceMode::AsIs)
|
|
||||||
}
|
|
||||||
|
|
||||||
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> {
|
|
||||||
self.do_eval(source, None, ForceMode::Deep)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn eval_repl(
|
|
||||||
&mut self,
|
|
||||||
source: Source,
|
|
||||||
scope: &HashSet<StringId>,
|
|
||||||
) -> Result<crate::value::Value> {
|
|
||||||
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, force_mode)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_binding(
|
|
||||||
&mut self,
|
|
||||||
_ident: &str,
|
|
||||||
_expr: &str,
|
|
||||||
_scope: &mut HashSet<StringId>,
|
|
||||||
) -> Result<crate::value::Value> {
|
|
||||||
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,
|
|
||||||
token: GhostToken<'id>,
|
|
||||||
extra_scope: Option<Scope<'a>>,
|
|
||||||
) -> DowngradeCtx<'a, 'id, 'bump> {
|
|
||||||
let Runtime {
|
|
||||||
global_env,
|
|
||||||
sources,
|
|
||||||
thunk_count,
|
|
||||||
strings,
|
|
||||||
..
|
|
||||||
} = self;
|
|
||||||
DowngradeCtx {
|
|
||||||
bump,
|
|
||||||
token,
|
|
||||||
strings,
|
|
||||||
source: sources.last().expect("no current source").clone(),
|
|
||||||
scopes: [Scope::Global(global_env)]
|
|
||||||
.into_iter()
|
|
||||||
.chain(extra_scope)
|
|
||||||
.collect(),
|
|
||||||
with_scope_count: 0,
|
|
||||||
arg_count: 0,
|
|
||||||
thunk_count,
|
|
||||||
thunk_scopes: vec![ThunkScope::new_in(bump)],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn downgrade<'a>(
|
|
||||||
&'a mut self,
|
|
||||||
source: Source,
|
|
||||||
extra_scope: Option<Scope<'a>>,
|
|
||||||
) -> Result<OwnedIr> {
|
|
||||||
tracing::debug!("Parsing Nix expression");
|
|
||||||
|
|
||||||
self.sources.push(source.clone());
|
|
||||||
|
|
||||||
let root = rnix::Root::parse(&source.src);
|
|
||||||
handle_parse_error(root.errors(), source).map_or(Ok(()), Err)?;
|
|
||||||
|
|
||||||
tracing::debug!("Downgrading Nix expression");
|
|
||||||
let expr = root
|
|
||||||
.tree()
|
|
||||||
.expr()
|
|
||||||
.ok_or_else(|| Error::parse_error("unexpected EOF".into()))?;
|
|
||||||
let bump = Bump::new();
|
|
||||||
GhostToken::new(|token| {
|
|
||||||
let ir = self
|
|
||||||
.downgrade_ctx(&bump, token, extra_scope)
|
|
||||||
.downgrade_toplevel(expr)?;
|
|
||||||
let ir = unsafe { std::mem::transmute::<RawIrRef<'_>, RawIrRef<'static>>(ir) };
|
|
||||||
Ok(OwnedIr { _bump: bump, ir })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_error_span(error: &rnix::ParseError) -> Option<rnix::TextRange> {
|
|
||||||
use rnix::ParseError::*;
|
|
||||||
match error {
|
|
||||||
Unexpected(range)
|
|
||||||
| UnexpectedExtra(range)
|
|
||||||
| UnexpectedWanted(_, range, _)
|
|
||||||
| UnexpectedDoubleBind(range)
|
|
||||||
| DuplicatedArgs(range, _) => Some(*range),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_parse_error<'a>(
|
|
||||||
errors: impl IntoIterator<Item = &'a rnix::ParseError>,
|
|
||||||
source: Source,
|
|
||||||
) -> Option<Box<Error>> {
|
|
||||||
for err in errors {
|
|
||||||
if let Some(span) = parse_error_span(err) {
|
|
||||||
return Some(
|
|
||||||
Error::parse_error(err.to_string())
|
|
||||||
.with_source(source)
|
|
||||||
.with_span(span),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DowngradeCtx<'ctx, 'id, 'ir> {
|
|
||||||
bump: &'ir Bump,
|
|
||||||
token: GhostToken<'id>,
|
|
||||||
strings: &'ctx mut DefaultStringInterner,
|
|
||||||
source: Source,
|
|
||||||
scopes: Vec<Scope<'ctx>>,
|
|
||||||
with_scope_count: u32,
|
|
||||||
arg_count: u32,
|
|
||||||
thunk_count: &'ctx mut usize,
|
|
||||||
thunk_scopes: Vec<ThunkScope<'id, 'ir>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn should_thunk<'id>(ir: IrRef<'id, '_>, token: &GhostToken<'id>) -> bool {
|
|
||||||
!matches!(
|
|
||||||
ir.borrow(token),
|
|
||||||
Ir::Builtin(_)
|
|
||||||
| Ir::Builtins
|
|
||||||
| Ir::Int(_)
|
|
||||||
| Ir::Float(_)
|
|
||||||
| Ir::Bool(_)
|
|
||||||
| Ir::Null
|
|
||||||
| Ir::Str(_)
|
|
||||||
| Ir::Thunk(_)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'ctx, 'id, 'ir> DowngradeCtx<'ctx, 'id, 'ir> {
|
|
||||||
fn new(
|
|
||||||
bump: &'ir Bump,
|
|
||||||
token: GhostToken<'id>,
|
|
||||||
symbols: &'ctx mut DefaultStringInterner,
|
|
||||||
global: &'ctx HashMap<StringId, Ir<'static, RawIrRef<'static>>>,
|
|
||||||
extra_scope: Option<Scope<'ctx>>,
|
|
||||||
thunk_count: &'ctx mut usize,
|
|
||||||
source: Source,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
bump,
|
|
||||||
token,
|
|
||||||
strings: symbols,
|
|
||||||
source,
|
|
||||||
scopes: std::iter::once(Scope::Global(global))
|
|
||||||
.chain(extra_scope)
|
|
||||||
.collect(),
|
|
||||||
thunk_count,
|
|
||||||
arg_count: 0,
|
|
||||||
with_scope_count: 0,
|
|
||||||
thunk_scopes: vec![ThunkScope::new_in(bump)],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'ctx: 'ir, 'id, 'ir> DowngradeContext<'id, 'ir> for DowngradeCtx<'ctx, 'id, 'ir> {
|
|
||||||
fn new_expr(&self, expr: Ir<'ir, IrRef<'id, 'ir>>) -> IrRef<'id, 'ir> {
|
|
||||||
IrRef::new(self.bump.alloc(GhostCell::new(expr)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn maybe_thunk(&mut self, ir: IrRef<'id, 'ir>) -> IrRef<'id, 'ir> {
|
|
||||||
if !should_thunk(ir, &self.token) {
|
|
||||||
return ir;
|
|
||||||
}
|
|
||||||
|
|
||||||
let id = ThunkId(*self.thunk_count);
|
|
||||||
*self.thunk_count = self.thunk_count.checked_add(1).expect("thunk id overflow");
|
|
||||||
self.thunk_scopes
|
|
||||||
.last_mut()
|
|
||||||
.expect("no active cache scope")
|
|
||||||
.add_binding(id, ir, &self.token);
|
|
||||||
IrRef::alloc(self.bump, Ir::Thunk(id))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_sym(&mut self, sym: String) -> StringId {
|
|
||||||
StringId(self.strings.get_or_intern(sym))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_sym(&self, id: StringId) -> Symbol<'_> {
|
|
||||||
self.strings.resolve(id.0).expect("no symbol found").into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn lookup(&self, sym: StringId, span: TextRange) -> Result<IrRef<'id, 'ir>> {
|
|
||||||
for scope in self.scopes.iter().rev() {
|
|
||||||
match scope {
|
|
||||||
&Scope::Global(global_scope) => {
|
|
||||||
if let Some(expr) = global_scope.get(&sym) {
|
|
||||||
let ir = match expr {
|
|
||||||
Ir::Builtins => Ir::Builtins,
|
|
||||||
Ir::Builtin(s) => Ir::Builtin(*s),
|
|
||||||
Ir::Bool(b) => Ir::Bool(*b),
|
|
||||||
Ir::Null => Ir::Null,
|
|
||||||
_ => unreachable!("globals should only contain leaf IR nodes"),
|
|
||||||
};
|
|
||||||
return Ok(self.new_expr(ir));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&Scope::Repl(repl_bindings) => {
|
|
||||||
if repl_bindings.contains(&sym) {
|
|
||||||
return Ok(self.new_expr(Ir::ReplBinding(sym)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Scope::ScopedImport(scoped_bindings) => {
|
|
||||||
if scoped_bindings.contains(&sym) {
|
|
||||||
return Ok(self.new_expr(Ir::ScopedImportBinding(sym)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Scope::Let(let_scope) => {
|
|
||||||
if let Some(&expr) = let_scope.get(&sym) {
|
|
||||||
return Ok(self.new_expr(Ir::Thunk(expr)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&Scope::Param {
|
|
||||||
sym: param_sym,
|
|
||||||
abs_layer,
|
|
||||||
} => {
|
|
||||||
if param_sym == sym {
|
|
||||||
return Ok(self.new_expr(Ir::Arg {
|
|
||||||
layer: self.thunk_scopes.len() - abs_layer,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.with_scope_count > 0 {
|
|
||||||
Ok(self.new_expr(Ir::WithLookup(sym)))
|
|
||||||
} else {
|
|
||||||
Err(Error::downgrade_error(
|
|
||||||
format!("'{}' not found", self.get_sym(sym)),
|
|
||||||
self.get_current_source(),
|
|
||||||
span,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_current_source(&self) -> Source {
|
|
||||||
self.source.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn with_let_scope<F, R>(&mut self, keys: &[StringId], f: F) -> Result<R>
|
|
||||||
where
|
|
||||||
F: FnOnce(&mut Self) -> Result<(bumpalo::collections::Vec<'ir, IrRef<'id, 'ir>>, R)>,
|
|
||||||
{
|
|
||||||
let base = *self.thunk_count;
|
|
||||||
*self.thunk_count = self
|
|
||||||
.thunk_count
|
|
||||||
.checked_add(keys.len())
|
|
||||||
.expect("thunk id overflow");
|
|
||||||
let iter = keys.iter().enumerate().map(|(offset, &key)| {
|
|
||||||
(
|
|
||||||
key,
|
|
||||||
ThunkId(unsafe { base.checked_add(offset).unwrap_unchecked() }),
|
|
||||||
)
|
|
||||||
});
|
|
||||||
self.scopes.push(Scope::Let(iter.collect()));
|
|
||||||
let (vals, ret) = {
|
|
||||||
let mut guard = ScopeGuard { ctx: self };
|
|
||||||
f(guard.as_ctx())?
|
|
||||||
};
|
|
||||||
assert_eq!(keys.len(), vals.len());
|
|
||||||
let scope = self.thunk_scopes.last_mut().expect("no active thunk scope");
|
|
||||||
scope.extend_bindings((base..base + keys.len()).map(ThunkId).zip(vals));
|
|
||||||
Ok(ret)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn with_param_scope<F, R>(&mut self, sym: StringId, f: F) -> R
|
|
||||||
where
|
|
||||||
F: FnOnce(&mut Self) -> R,
|
|
||||||
{
|
|
||||||
self.scopes.push(Scope::Param {
|
|
||||||
sym,
|
|
||||||
abs_layer: self.thunk_scopes.len(),
|
|
||||||
});
|
|
||||||
let mut guard = ScopeGuard { ctx: self };
|
|
||||||
f(guard.as_ctx())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn with_with_scope<F, R>(&mut self, f: F) -> R
|
|
||||||
where
|
|
||||||
F: FnOnce(&mut Self) -> R,
|
|
||||||
{
|
|
||||||
self.with_scope_count += 1;
|
|
||||||
let ret = f(self);
|
|
||||||
self.with_scope_count -= 1;
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
|
|
||||||
fn with_thunk_scope<F, R>(
|
|
||||||
&mut self,
|
|
||||||
f: F,
|
|
||||||
) -> (
|
|
||||||
R,
|
|
||||||
bumpalo::collections::Vec<'ir, (ThunkId, IrRef<'id, 'ir>)>,
|
|
||||||
)
|
|
||||||
where
|
|
||||||
F: FnOnce(&mut Self) -> R,
|
|
||||||
{
|
|
||||||
self.thunk_scopes.push(ThunkScope::new_in(self.bump));
|
|
||||||
let ret = f(self);
|
|
||||||
(
|
|
||||||
ret,
|
|
||||||
self.thunk_scopes
|
|
||||||
.pop()
|
|
||||||
.expect("no thunk scope left???")
|
|
||||||
.bindings,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bump(&self) -> &'ir bumpalo::Bump {
|
|
||||||
self.bump
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'id, 'ir, 'ctx: 'ir> DowngradeCtx<'ctx, 'id, 'ir> {
|
|
||||||
fn downgrade_toplevel(mut self, root: rnix::ast::Expr) -> Result<RawIrRef<'ir>> {
|
|
||||||
let body = root.downgrade(&mut self)?;
|
|
||||||
let thunks = self
|
|
||||||
.thunk_scopes
|
|
||||||
.pop()
|
|
||||||
.expect("no thunk scope left???")
|
|
||||||
.bindings;
|
|
||||||
let ir = IrRef::alloc(self.bump, Ir::TopLevel { body, thunks });
|
|
||||||
Ok(ir.freeze(self.token))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ThunkScope<'id, 'ir> {
|
|
||||||
bindings: bumpalo::collections::Vec<'ir, (ThunkId, IrRef<'id, 'ir>)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'id, 'ir> ThunkScope<'id, 'ir> {
|
|
||||||
fn new_in(bump: &'ir Bump) -> Self {
|
|
||||||
Self {
|
|
||||||
bindings: bumpalo::collections::Vec::new_in(bump),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_binding(&mut self, id: ThunkId, ir: IrRef<'id, 'ir>, _token: &GhostToken<'id>) {
|
|
||||||
self.bindings.push((id, ir));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extend_bindings(&mut self, iter: impl IntoIterator<Item = (ThunkId, IrRef<'id, 'ir>)>) {
|
|
||||||
self.bindings.extend(iter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Scope<'ctx> {
|
|
||||||
Global(&'ctx HashMap<StringId, Ir<'static, RawIrRef<'static>>>),
|
|
||||||
Repl(&'ctx HashSet<StringId>),
|
|
||||||
ScopedImport(HashSet<StringId>),
|
|
||||||
Let(HashMap<StringId, ThunkId>),
|
|
||||||
Param { sym: StringId, abs_layer: usize },
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ScopeGuard<'a, 'ctx, 'id, 'ir> {
|
|
||||||
ctx: &'a mut DowngradeCtx<'ctx, 'id, 'ir>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for ScopeGuard<'_, '_, '_, '_> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
self.ctx.scopes.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'id, 'ir, 'ctx> ScopeGuard<'_, 'ctx, 'id, 'ir> {
|
|
||||||
fn as_ctx(&mut self) -> &mut DowngradeCtx<'ctx, 'id, 'ir> {
|
|
||||||
self.ctx
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct OwnedIr {
|
|
||||||
_bump: Bump,
|
|
||||||
ir: RawIrRef<'static>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OwnedIr {
|
|
||||||
unsafe fn new(ir: RawIrRef<'_>, bump: Bump) -> Self {
|
|
||||||
Self {
|
|
||||||
_bump: bump,
|
|
||||||
ir: unsafe { std::mem::transmute::<RawIrRef<'_>, RawIrRef<'static>>(ir) },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_ref(&self) -> RawIrRef<'_> {
|
|
||||||
self.ir
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BytecodeContext for Runtime {
|
|
||||||
fn intern_string(&mut self, s: &str) -> StringId {
|
|
||||||
StringId(self.strings.get_or_intern(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn register_span(&mut self, range: TextRange) -> u32 {
|
|
||||||
let id = self.spans.len();
|
|
||||||
let source_id = self
|
|
||||||
.sources
|
|
||||||
.len()
|
|
||||||
.checked_sub(1)
|
|
||||||
.expect("current_source not set");
|
|
||||||
self.spans.push((source_id, range));
|
|
||||||
id as u32
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_code(&self) -> &[u8] {
|
|
||||||
&self.bytecode
|
|
||||||
}
|
|
||||||
|
|
||||||
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:
|
|
||||||
}
|
|
||||||
@@ -1,410 +0,0 @@
|
|||||||
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};
|
|
||||||
|
|
||||||
/// Generates both the BUILTINS const table and the BuiltinId enum
|
|
||||||
/// from a single source of truth, preventing index desync.
|
|
||||||
macro_rules! define_builtins {
|
|
||||||
($(($name:literal, $variant:ident, $arity:expr)),* $(,)?) => {
|
|
||||||
/// Builtin function registry.
|
|
||||||
/// Array index IS the PrimOp id. (name, arity) pairs.
|
|
||||||
pub(crate) const BUILTINS: &[(&str, u8)] = &[
|
|
||||||
$(($name, $arity),)*
|
|
||||||
];
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, TryFromPrimitive, Collect)]
|
|
||||||
#[repr(u8)]
|
|
||||||
#[collect(require_static)]
|
|
||||||
pub(crate) enum BuiltinId {
|
|
||||||
$($variant,)*
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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),
|
|
||||||
("baseNameOf", BaseNameOf, 1),
|
|
||||||
("__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),
|
|
||||||
("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),
|
|
||||||
("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),
|
|
||||||
("import", Import, 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),
|
|
||||||
("map", Map, 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),
|
|
||||||
("placeholder", Placeholder, 1),
|
|
||||||
("__readDir", ReadDir, 1),
|
|
||||||
("__readFile", ReadFile, 1),
|
|
||||||
("__readFileType", ReadFileType, 1),
|
|
||||||
("removeAttrs", RemoveAttrs, 2),
|
|
||||||
("__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),
|
|
||||||
("throw", Throw, 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),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Names that need to be pre-interned for builtin implementations.
|
|
||||||
const EXTRA_INTERN_NAMES: &[&str] = &[
|
|
||||||
"builtins",
|
|
||||||
"currentSystem",
|
|
||||||
"langVersion",
|
|
||||||
"nixVersion",
|
|
||||||
"storeDir",
|
|
||||||
"nixPath",
|
|
||||||
"true",
|
|
||||||
"false",
|
|
||||||
// typeOf return values
|
|
||||||
"int",
|
|
||||||
"float",
|
|
||||||
"bool",
|
|
||||||
"string",
|
|
||||||
"path",
|
|
||||||
"null",
|
|
||||||
"set",
|
|
||||||
"list",
|
|
||||||
"lambda",
|
|
||||||
// attrset keys used by builtins
|
|
||||||
"name",
|
|
||||||
"value",
|
|
||||||
"success",
|
|
||||||
"right",
|
|
||||||
"wrong",
|
|
||||||
"key",
|
|
||||||
"operator",
|
|
||||||
"startSet",
|
|
||||||
"__toString",
|
|
||||||
"outPath",
|
|
||||||
"__functor",
|
|
||||||
"drvPath",
|
|
||||||
"type",
|
|
||||||
"derivation",
|
|
||||||
"version",
|
|
||||||
];
|
|
||||||
|
|
||||||
/// Returns true if this builtin has lazy argument semantics
|
|
||||||
/// (not all args should be forced before dispatch).
|
|
||||||
pub(super) fn is_lazy_builtin(id: BuiltinId) -> bool {
|
|
||||||
matches!(
|
|
||||||
id,
|
|
||||||
BuiltinId::Seq
|
|
||||||
| BuiltinId::DeepSeq
|
|
||||||
| BuiltinId::Trace
|
|
||||||
| BuiltinId::Warn
|
|
||||||
| BuiltinId::TryEval
|
|
||||||
| BuiltinId::AddErrorContext
|
|
||||||
| BuiltinId::Break
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Intern all builtin names and extra names needed at runtime.
|
|
||||||
fn intern_all_builtins(interner: &mut DefaultStringInterner) {
|
|
||||||
for &(name, _) in BUILTINS {
|
|
||||||
interner.get_or_intern(name);
|
|
||||||
}
|
|
||||||
for &name in EXTRA_INTERN_NAMES {
|
|
||||||
interner.get_or_intern(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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());
|
|
||||||
|
|
||||||
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 consts = [
|
|
||||||
(
|
|
||||||
"__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 (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, 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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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,
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
drvAttrs@{
|
|
||||||
outputs ? [ "out" ],
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
|
|
||||||
let
|
|
||||||
|
|
||||||
strict = derivationStrict drvAttrs;
|
|
||||||
|
|
||||||
commonAttrs =
|
|
||||||
drvAttrs
|
|
||||||
// (builtins.listToAttrs outputsList)
|
|
||||||
// {
|
|
||||||
all = map (x: x.value) outputsList;
|
|
||||||
inherit drvAttrs;
|
|
||||||
};
|
|
||||||
|
|
||||||
outputToAttrListElement = outputName: {
|
|
||||||
name = outputName;
|
|
||||||
value = commonAttrs // {
|
|
||||||
outPath = builtins.getAttr outputName strict;
|
|
||||||
drvPath = strict.drvPath;
|
|
||||||
type = "derivation";
|
|
||||||
inherit outputName;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
outputsList = map outputToAttrListElement outputs;
|
|
||||||
|
|
||||||
in
|
|
||||||
(builtins.head outputsList).value
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
{
|
|
||||||
system ? "", # obsolete
|
|
||||||
url,
|
|
||||||
hash ? "", # an SRI hash
|
|
||||||
|
|
||||||
# Legacy hash specification
|
|
||||||
md5 ? "",
|
|
||||||
sha1 ? "",
|
|
||||||
sha256 ? "",
|
|
||||||
sha512 ? "",
|
|
||||||
outputHash ?
|
|
||||||
if hash != "" then
|
|
||||||
hash
|
|
||||||
else if sha512 != "" then
|
|
||||||
sha512
|
|
||||||
else if sha1 != "" then
|
|
||||||
sha1
|
|
||||||
else if md5 != "" then
|
|
||||||
md5
|
|
||||||
else
|
|
||||||
sha256,
|
|
||||||
outputHashAlgo ?
|
|
||||||
if hash != "" then
|
|
||||||
""
|
|
||||||
else if sha512 != "" then
|
|
||||||
"sha512"
|
|
||||||
else if sha1 != "" then
|
|
||||||
"sha1"
|
|
||||||
else if md5 != "" then
|
|
||||||
"md5"
|
|
||||||
else
|
|
||||||
"sha256",
|
|
||||||
|
|
||||||
executable ? false,
|
|
||||||
unpack ? false,
|
|
||||||
name ? baseNameOf (toString url),
|
|
||||||
# still translates to __impure to trigger derivationStrict error checks.
|
|
||||||
impure ? false,
|
|
||||||
}:
|
|
||||||
|
|
||||||
derivation (
|
|
||||||
{
|
|
||||||
builder = "builtin:fetchurl";
|
|
||||||
|
|
||||||
# New-style output content requirements.
|
|
||||||
outputHashMode = if unpack || executable then "recursive" else "flat";
|
|
||||||
|
|
||||||
inherit
|
|
||||||
name
|
|
||||||
url
|
|
||||||
executable
|
|
||||||
unpack
|
|
||||||
;
|
|
||||||
|
|
||||||
system = "builtin";
|
|
||||||
|
|
||||||
# No need to double the amount of network traffic
|
|
||||||
preferLocalBuild = true;
|
|
||||||
|
|
||||||
impureEnvVars = [
|
|
||||||
# We borrow these environment variables from the caller to allow
|
|
||||||
# easy proxy configuration. This is impure, but a fixed-output
|
|
||||||
# derivation like fetchurl is allowed to do so since its result is
|
|
||||||
# by definition pure.
|
|
||||||
"http_proxy"
|
|
||||||
"https_proxy"
|
|
||||||
"ftp_proxy"
|
|
||||||
"all_proxy"
|
|
||||||
"no_proxy"
|
|
||||||
];
|
|
||||||
|
|
||||||
# To make "nix-prefetch-url" work.
|
|
||||||
urls = [ url ];
|
|
||||||
}
|
|
||||||
// (if impure then { __impure = true; } else { inherit outputHashAlgo outputHash; })
|
|
||||||
)
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
use fix::value::Value;
|
use fix_common::Value;
|
||||||
|
|
||||||
use crate::utils::{eval_deep, eval_deep_result};
|
use crate::utils::{eval_deep, eval_deep_result};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use fix::error::Source;
|
use fix::Evaluator;
|
||||||
use fix::runtime::Runtime;
|
use fix_common::Value;
|
||||||
use fix::value::Value;
|
use fix_error::Source;
|
||||||
|
|
||||||
use crate::utils::{eval, eval_result};
|
use crate::utils::{eval, eval_result};
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@ fn import_with_complex_dependency_graph() {
|
|||||||
|
|
||||||
#[test_log::test]
|
#[test_log::test]
|
||||||
fn path_with_file() {
|
fn path_with_file() {
|
||||||
let mut ctx = Runtime::new().unwrap();
|
let mut ctx = Evaluator::new();
|
||||||
let temp_dir = tempfile::tempdir().unwrap();
|
let temp_dir = tempfile::tempdir().unwrap();
|
||||||
let test_file = temp_dir.path().join("test.txt");
|
let test_file = temp_dir.path().join("test.txt");
|
||||||
std::fs::write(&test_file, "Hello, World!").unwrap();
|
std::fs::write(&test_file, "Hello, World!").unwrap();
|
||||||
@@ -136,7 +136,7 @@ fn path_with_custom_name() {
|
|||||||
|
|
||||||
#[test_log::test]
|
#[test_log::test]
|
||||||
fn path_with_directory_recursive() {
|
fn path_with_directory_recursive() {
|
||||||
let mut ctx = Runtime::new().unwrap();
|
let mut ctx = Evaluator::new();
|
||||||
let temp_dir = tempfile::tempdir().unwrap();
|
let temp_dir = tempfile::tempdir().unwrap();
|
||||||
let test_dir = temp_dir.path().join("mydir");
|
let test_dir = temp_dir.path().join("mydir");
|
||||||
std::fs::create_dir_all(&test_dir).unwrap();
|
std::fs::create_dir_all(&test_dir).unwrap();
|
||||||
@@ -159,7 +159,7 @@ fn path_with_directory_recursive() {
|
|||||||
|
|
||||||
#[test_log::test]
|
#[test_log::test]
|
||||||
fn path_flat_with_file() {
|
fn path_flat_with_file() {
|
||||||
let mut ctx = Runtime::new().unwrap();
|
let mut ctx = Evaluator::new();
|
||||||
let temp_dir = tempfile::tempdir().unwrap();
|
let temp_dir = tempfile::tempdir().unwrap();
|
||||||
let test_file = temp_dir.path().join("flat.txt");
|
let test_file = temp_dir.path().join("flat.txt");
|
||||||
std::fs::write(&test_file, "Flat content").unwrap();
|
std::fs::write(&test_file, "Flat content").unwrap();
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use fix::error::Source;
|
use fix::Evaluator;
|
||||||
use fix::runtime::Runtime;
|
use fix_common::Value;
|
||||||
use fix::value::Value;
|
use fix_error::{Source, SourceType};
|
||||||
|
|
||||||
fn get_lang_dir() -> PathBuf {
|
fn get_lang_dir() -> PathBuf {
|
||||||
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/tests/lang")
|
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/tests/lang")
|
||||||
@@ -14,10 +14,10 @@ fn eval_file(name: &str) -> Result<(Value, Source), String> {
|
|||||||
let lang_dir = get_lang_dir();
|
let lang_dir = get_lang_dir();
|
||||||
let nix_path = lang_dir.join(format!("{name}.nix"));
|
let nix_path = lang_dir.join(format!("{name}.nix"));
|
||||||
|
|
||||||
let mut ctx = Runtime::new().map_err(|e| e.to_string())?;
|
let mut ctx = Evaluator::new();
|
||||||
let content = std::fs::read_to_string(&nix_path).unwrap();
|
let content = std::fs::read_to_string(&nix_path).unwrap();
|
||||||
let source = Source {
|
let source = Source {
|
||||||
ty: fix::error::SourceType::File(nix_path.into()),
|
ty: SourceType::File(nix_path.into()),
|
||||||
src: content.into(),
|
src: content.into(),
|
||||||
};
|
};
|
||||||
ctx.eval_deep(source.clone())
|
ctx.eval_deep(source.clone())
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use fix::runtime::Runtime;
|
use fix::Evaluator;
|
||||||
use fix::value::Value;
|
use fix_common::Value;
|
||||||
|
|
||||||
use crate::utils::eval_result;
|
use crate::utils::eval_result;
|
||||||
|
|
||||||
@@ -153,7 +153,7 @@ fn string_add_merges_context() {
|
|||||||
|
|
||||||
#[test_log::test]
|
#[test_log::test]
|
||||||
fn context_in_derivation_args() {
|
fn context_in_derivation_args() {
|
||||||
let mut rt = Runtime::new().unwrap();
|
let mut rt = Evaluator::new();
|
||||||
let result = rt
|
let result = rt
|
||||||
.eval(
|
.eval(
|
||||||
r#"
|
r#"
|
||||||
@@ -182,7 +182,7 @@ fn context_in_derivation_args() {
|
|||||||
|
|
||||||
#[test_log::test]
|
#[test_log::test]
|
||||||
fn context_in_derivation_env() {
|
fn context_in_derivation_env() {
|
||||||
let mut rt = Runtime::new().unwrap();
|
let mut rt = Evaluator::new();
|
||||||
let result = rt
|
let result = rt
|
||||||
.eval(
|
.eval(
|
||||||
r#"
|
r#"
|
||||||
@@ -224,7 +224,7 @@ fn tostring_preserves_context() {
|
|||||||
|
|
||||||
#[test_log::test]
|
#[test_log::test]
|
||||||
fn interpolation_derivation_returns_outpath() {
|
fn interpolation_derivation_returns_outpath() {
|
||||||
let mut rt = Runtime::new().unwrap();
|
let mut rt = Evaluator::new();
|
||||||
let result = rt
|
let result = rt
|
||||||
.eval(
|
.eval(
|
||||||
r#"
|
r#"
|
||||||
|
|||||||
@@ -1,38 +1,31 @@
|
|||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use fix::error::{Result, Source};
|
use fix::Evaluator;
|
||||||
use fix::runtime::Runtime;
|
use fix_common::Value;
|
||||||
use fix::value::Value;
|
use fix_error::{Result, Source};
|
||||||
|
|
||||||
pub fn eval(expr: &str) -> Value {
|
pub fn eval(expr: &str) -> Value {
|
||||||
Runtime::new()
|
Evaluator::new()
|
||||||
.unwrap()
|
|
||||||
.eval(Source::new_eval(expr.into()).unwrap())
|
.eval(Source::new_eval(expr.into()).unwrap())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn eval_shallow(expr: &str) -> Value {
|
pub fn eval_shallow(expr: &str) -> Value {
|
||||||
Runtime::new()
|
Evaluator::new()
|
||||||
.unwrap()
|
|
||||||
.eval_shallow(Source::new_eval(expr.into()).unwrap())
|
.eval_shallow(Source::new_eval(expr.into()).unwrap())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn eval_deep(expr: &str) -> Value {
|
pub fn eval_deep(expr: &str) -> Value {
|
||||||
Runtime::new()
|
Evaluator::new()
|
||||||
.unwrap()
|
|
||||||
.eval_deep(Source::new_eval(expr.into()).unwrap())
|
.eval_deep(Source::new_eval(expr.into()).unwrap())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn eval_deep_result(expr: &str) -> Result<Value> {
|
pub fn eval_deep_result(expr: &str) -> Result<Value> {
|
||||||
Runtime::new()
|
Evaluator::new().eval_deep(Source::new_eval(expr.into()).unwrap())
|
||||||
.unwrap()
|
|
||||||
.eval_deep(Source::new_eval(expr.into()).unwrap())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn eval_result(expr: &str) -> Result<Value> {
|
pub fn eval_result(expr: &str) -> Result<Value> {
|
||||||
Runtime::new()
|
Evaluator::new().eval(Source::new_eval(expr.into()).unwrap())
|
||||||
.unwrap()
|
|
||||||
.eval(Source::new_eval(expr.into()).unwrap())
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user