From 81ac08fb5ae4ec49f65b927ae4a1d0a40c9deecc Mon Sep 17 00:00:00 2001 From: imxyy_soope_ Date: Sat, 6 Jun 2026 20:53:02 +0800 Subject: [PATCH] refactor: reorganize crate hierarchy --- Cargo.lock | 92 +-- Cargo.toml | 10 +- fix-abstract-vm/src/bytecode_reader.rs | 142 ---- fix-builtins/Cargo.toml | 8 - fix-builtins/src/lib.rs | 406 ------------ fix-bytecode/Cargo.toml | 12 + .../src/disassembler.rs | 7 +- fix-bytecode/src/lib.rs | 531 +++++++++++++++ fix-codegen/Cargo.toml | 15 - {fix-ir => fix-compiler}/Cargo.toml | 13 +- fix-compiler/src/context.rs | 542 ++++++++++++++++ fix-ir/src/lib.rs => fix-compiler/src/ir.rs | 14 +- .../src => fix-compiler/src/ir}/downgrade.rs | 3 +- {fix-codegen => fix-compiler}/src/lib.rs | 165 +---- {fix-common => fix-lang}/Cargo.toml | 3 +- {fix-common => fix-lang}/src/lib.rs | 122 ++++ fix-primops/Cargo.toml | 17 - {fix-abstract-vm => fix-runtime}/Cargo.toml | 7 +- .../src/boxing.rs | 0 .../src/forced.rs | 2 +- {fix-abstract-vm => fix-runtime}/src/host.rs | 27 +- {fix-abstract-vm => fix-runtime}/src/lib.rs | 3 +- .../src/machine.rs | 4 +- .../src/path_util.rs | 0 .../src/resolve.rs | 6 +- {fix-abstract-vm => fix-runtime}/src/state.rs | 14 +- .../src/string_context.rs | 2 +- {fix-abstract-vm => fix-runtime}/src/value.rs | 3 +- fix-vm/Cargo.toml | 8 +- fix-vm/src/dispatch_tailcall.rs | 8 +- fix-vm/src/instructions/arithmetic.rs | 14 +- fix-vm/src/instructions/calls.rs | 8 +- fix-vm/src/instructions/collections.rs | 14 +- fix-vm/src/instructions/control.rs | 2 +- fix-vm/src/instructions/misc.rs | 12 +- fix-vm/src/instructions/with_scope.rs | 6 +- fix-vm/src/lib.rs | 22 +- .../src => fix-vm/src/primops}/context.rs | 10 +- .../src => fix-vm/src/primops}/control.rs | 6 +- .../src => fix-vm/src/primops}/conv.rs | 6 +- {fix-primops/src => fix-vm/src/primops}/eq.rs | 23 +- {fix-primops/src => fix-vm/src/primops}/io.rs | 8 +- .../src => fix-vm/src/primops}/list.rs | 6 +- .../src/lib.rs => fix-vm/src/primops/mod.rs | 8 +- .../src => fix-vm/src/primops}/path.rs | 4 +- fix/Cargo.toml | 15 +- fix/benches/utils.rs | 2 +- fix/src/lib.rs | 607 +----------------- fix/tests/tests/derivation.rs | 2 +- fix/tests/tests/io_operations.rs | 2 +- fix/tests/tests/lang.rs | 2 +- fix/tests/tests/string_context.rs | 2 +- fix/tests/tests/utils.rs | 2 +- 53 files changed, 1422 insertions(+), 1547 deletions(-) delete mode 100644 fix-abstract-vm/src/bytecode_reader.rs delete mode 100644 fix-builtins/Cargo.toml delete mode 100644 fix-builtins/src/lib.rs create mode 100644 fix-bytecode/Cargo.toml rename {fix-codegen => fix-bytecode}/src/disassembler.rs (98%) create mode 100644 fix-bytecode/src/lib.rs delete mode 100644 fix-codegen/Cargo.toml rename {fix-ir => fix-compiler}/Cargo.toml (64%) create mode 100644 fix-compiler/src/context.rs rename fix-ir/src/lib.rs => fix-compiler/src/ir.rs (97%) rename {fix-ir/src => fix-compiler/src/ir}/downgrade.rs (99%) rename {fix-codegen => fix-compiler}/src/lib.rs (89%) rename {fix-common => fix-lang}/Cargo.toml (76%) rename {fix-common => fix-lang}/src/lib.rs (61%) delete mode 100644 fix-primops/Cargo.toml rename {fix-abstract-vm => fix-runtime}/Cargo.toml (67%) rename {fix-abstract-vm => fix-runtime}/src/boxing.rs (100%) rename {fix-abstract-vm => fix-runtime}/src/forced.rs (99%) rename {fix-abstract-vm => fix-runtime}/src/host.rs (88%) rename {fix-abstract-vm => fix-runtime}/src/lib.rs (84%) rename {fix-abstract-vm => fix-runtime}/src/machine.rs (98%) rename {fix-abstract-vm => fix-runtime}/src/path_util.rs (100%) rename {fix-abstract-vm => fix-runtime}/src/resolve.rs (87%) rename {fix-abstract-vm => fix-runtime}/src/state.rs (85%) rename {fix-abstract-vm => fix-runtime}/src/string_context.rs (98%) rename {fix-abstract-vm => fix-runtime}/src/value.rs (99%) rename {fix-primops/src => fix-vm/src/primops}/context.rs (99%) rename {fix-primops/src => fix-vm/src/primops}/control.rs (99%) rename {fix-primops/src => fix-vm/src/primops}/conv.rs (96%) rename {fix-primops/src => fix-vm/src/primops}/eq.rs (93%) rename {fix-primops/src => fix-vm/src/primops}/io.rs (98%) rename {fix-primops/src => fix-vm/src/primops}/list.rs (98%) rename fix-primops/src/lib.rs => fix-vm/src/primops/mod.rs (94%) rename {fix-primops/src => fix-vm/src/primops}/path.rs (98%) diff --git a/Cargo.lock b/Cargo.lock index 4063ce5..4218c53 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -445,22 +445,18 @@ name = "fix" version = "0.1.0" dependencies = [ "anyhow", - "bumpalo", "clap", "criterion", "ere", - "fix-abstract-vm", - "fix-builtins", - "fix-codegen", - "fix-common", + "fix-bytecode", + "fix-compiler", "fix-error", - "fix-ir", + "fix-lang", + "fix-runtime", "fix-vm", - "ghost-cell", "hashbrown 0.16.1", "miette", "mimalloc", - "rnix", "rustyline", "string-interner", "tempfile", @@ -471,51 +467,33 @@ dependencies = [ ] [[package]] -name = "fix-abstract-vm" -version = "0.1.0" -dependencies = [ - "fix-builtins", - "fix-codegen", - "fix-common", - "fix-error", - "gc-arena", - "hashbrown 0.16.1", - "likely_stable", - "num_enum", - "smallvec", - "sptr", - "string-interner", -] - -[[package]] -name = "fix-builtins" -version = "0.1.0" -dependencies = [ - "gc-arena", - "num_enum", -] - -[[package]] -name = "fix-codegen" +name = "fix-bytecode" version = "0.1.0" dependencies = [ "colored", - "fix-builtins", - "fix-common", - "fix-ir", - "hashbrown 0.16.1", + "fix-lang", + "likely_stable", "num_enum", - "rnix", "string-interner", ] [[package]] -name = "fix-common" +name = "fix-compiler" version = "0.1.0" dependencies = [ - "ere", - "gc-arena", + "bumpalo", + "colored", + "fix-bytecode", + "fix-error", + "fix-lang", + "fix-runtime", + "ghost-cell", + "hashbrown 0.16.1", + "num_enum", + "rnix", + "rowan", "string-interner", + "tracing", ] [[package]] @@ -528,34 +506,28 @@ dependencies = [ ] [[package]] -name = "fix-ir" +name = "fix-lang" version = "0.1.0" dependencies = [ - "bumpalo", - "fix-builtins", - "fix-common", - "fix-error", - "ghost-cell", - "hashbrown 0.16.1", + "ere", + "gc-arena", "num_enum", - "rnix", - "rowan", "string-interner", ] [[package]] -name = "fix-primops" +name = "fix-runtime" version = "0.1.0" dependencies = [ - "fix-abstract-vm", - "fix-builtins", - "fix-codegen", - "fix-common", + "fix-bytecode", "fix-error", + "fix-lang", "gc-arena", "hashbrown 0.16.1", + "likely_stable", "num_enum", "smallvec", + "sptr", "string-interner", ] @@ -563,12 +535,10 @@ dependencies = [ name = "fix-vm" version = "0.1.0" dependencies = [ - "fix-abstract-vm", - "fix-builtins", - "fix-codegen", - "fix-common", + "fix-bytecode", "fix-error", - "fix-primops", + "fix-lang", + "fix-runtime", "gc-arena", "hashbrown 0.16.1", "likely_stable", diff --git a/Cargo.toml b/Cargo.toml index 04b375d..64624b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,13 +2,11 @@ resolver = "3" members = [ "fix", - "fix-abstract-vm", - "fix-builtins", - "fix-codegen", - "fix-common", + "fix-bytecode", + "fix-compiler", "fix-error", - "fix-ir", - "fix-primops", + "fix-lang", + "fix-runtime", "fix-vm", ] diff --git a/fix-abstract-vm/src/bytecode_reader.rs b/fix-abstract-vm/src/bytecode_reader.rs deleted file mode 100644 index bab00c6..0000000 --- a/fix-abstract-vm/src/bytecode_reader.rs +++ /dev/null @@ -1,142 +0,0 @@ -#![allow(dead_code)] - -use fix_codegen::OperandType; -use fix_common::StringId; -use num_enum::TryFromPrimitive; -use string_interner::Symbol as _; - -use crate::{OperandData, VmRuntimeCtx}; - -pub struct BytecodeReader<'a> { - bytecode: &'a [u8], - pc: usize, - inst_start_pc: usize, -} - -impl<'a> BytecodeReader<'a> { - pub fn new(bytecode: &'a [u8], pc: usize) -> Self { - Self { - bytecode, - pc, - inst_start_pc: pc, - } - } - - #[inline(always)] - pub fn from_after_op(bytecode: &'a [u8], inst_start_pc: usize) -> Self { - Self { - bytecode, - pc: inst_start_pc + 1, - inst_start_pc, - } - } - - #[inline(always)] - #[cfg_attr(debug_assertions, track_caller)] - fn read_array(&mut self) -> [u8; N] { - let ret = self.bytecode[self.pc..self.pc + N] - .try_into() - .expect("read_array failed"); - self.pc += N; - ret - } - - #[inline(always)] - pub fn read_op(&mut self) -> fix_codegen::Op { - use fix_codegen::Op; - self.inst_start_pc = self.pc; - let byte = self.bytecode[self.pc]; - if !likely_stable::likely((0..Op::Illegal as u8).contains(&byte)) { - panic!("unknown opcode: {byte:#04x}") - } - self.pc += 1; - unsafe { std::mem::transmute::(byte) } - } - - #[inline(always)] - pub fn read_u8(&mut self) -> u8 { - let val = self.bytecode[self.pc]; - self.pc += 1; - val - } - - #[inline(always)] - pub fn read_u16(&mut self) -> u16 { - u16::from_le_bytes(self.read_array()) - } - - #[inline(always)] - pub fn read_u32(&mut self) -> u32 { - u32::from_le_bytes(self.read_array()) - } - - #[inline(always)] - pub fn read_i32(&mut self) -> i32 { - i32::from_le_bytes(self.read_array()) - } - - #[inline(always)] - pub fn read_i64(&mut self) -> i64 { - i64::from_le_bytes(self.read_array()) - } - - #[inline(always)] - pub fn read_f64(&mut self) -> f64 { - f64::from_le_bytes(self.read_array()) - } - - #[inline(always)] - pub fn read_string_id(&mut self) -> StringId { - let raw = self.read_u32(); - #[allow(clippy::unwrap_used)] - StringId(string_interner::symbol::SymbolU32::try_from_usize(raw as usize).unwrap()) - } - - #[inline(always)] - pub fn read_operand_data(&mut self, ctx: &C) -> OperandData { - let tag = self.read_u8(); - let Ok(ty) = OperandType::try_from_primitive(tag) - .map_err(|err| panic!("unknown operand tag: {:#04x}", err.number)); - match ty { - OperandType::Const => { - let id = self.read_u32(); - OperandData::Const(ctx.get_const(id)) - } - OperandType::BigInt => { - let val = self.read_i64(); - OperandData::BigInt(val) - } - OperandType::Local => { - let layer = self.read_u8(); - let idx = self.read_u32(); - OperandData::Local { layer, idx } - } - OperandType::BuiltinConst => { - let id = self.read_string_id(); - OperandData::BuiltinConst(id) - } - OperandType::Builtins => OperandData::Builtins, - OperandType::ReplBinding => { - let id = self.read_string_id(); - OperandData::ReplBinding(id) - } - OperandType::ScopedImportBinding => { - let slot_id = self.read_u32(); - let name = self.read_string_id(); - OperandData::ScopedImportBinding { slot_id, name } - } - } - } - - pub fn pc(&self) -> usize { - self.pc - } - - pub fn set_pc(&mut self, pc: usize) { - self.pc = pc; - } - - pub fn inst_start_pc(&self) -> usize { - self.inst_start_pc - } -} diff --git a/fix-builtins/Cargo.toml b/fix-builtins/Cargo.toml deleted file mode 100644 index d84418a..0000000 --- a/fix-builtins/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "fix-builtins" -version = "0.1.0" -edition = "2024" - -[dependencies] -num_enum = { workspace = true } -gc-arena = { workspace = true } diff --git a/fix-builtins/src/lib.rs b/fix-builtins/src/lib.rs deleted file mode 100644 index 561e245..0000000 --- a/fix-builtins/src/lib.rs +++ /dev/null @@ -1,406 +0,0 @@ -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), - ("__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), - ("__unsafeDiscardOutputDependency", UnsafeDiscardOutputDependency, 1), - ("__unsafeGetAttrPos", UnsafeGetAttrPos, 2), - ("__warn", Warn, 2), - ("__zipAttrsWith", ZipAttrsWith, 2), -} - -#[repr(u8)] -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] -pub enum PrimOpPhase { - Abort, - Add, - AddErrorContext, - - All, - AllCallPred, - AllCheck, - - Any, - AnyCallPred, - AnyCheck, - - AppendContext, - AttrNames, - AttrValues, - BaseNameOf, - BitAnd, - BitOr, - BitXor, - Break, - CatAttrs, - Ceil, - CompareVersions, - ConcatLists, - ConcatMap, - ConcatStringsSep, - ConvertHash, - - DeepSeq, - DeepSeqPush, - DeepSeqLoop, - - Derivation, - DerivationStrict, - DirOf, - Div, - Elem, - ElemAt, - FetchGit, - FetchMercurial, - FetchTarball, - FetchTree, - FetchUrl, - - FilterForceList, - FilterCallPred, - FilterCheck, - - FilterSource, - FindFile, - Floor, - FoldlStrict, - FoldlStrictEmpty, - FoldlStrictCall1, - FoldlStrictCall2, - FoldlStrictUpdate, - FromJSON, - FromTOML, - FunctionArgs, - GenList, - GenericClosure, - GetAttr, - GetContext, - GetEnv, - GroupBy, - HasAttr, - HasContext, - HashFile, - HashString, - Head, - Import, - IntersectAttrs, - IsAttrs, - IsBool, - IsFloat, - IsFunction, - IsInt, - IsList, - IsNull, - IsPath, - IsString, - Length, - LessThan, - ListToAttrs, - Map, - MapAttrs, - Match, - Mul, - ParseDrvName, - Partition, - Path, - PathExists, - Placeholder, - ReadDir, - ReadFile, - ReadFileType, - RemoveAttrs, - ReplaceStrings, - ScopedImport, - Seq, - Sort, - Split, - SplitVersion, - StorePath, - StringLength, - Sub, - Substring, - Tail, - Throw, - ToFile, - ToJSON, - ToPath, - ToString, - ToXML, - Trace, - TryEval, - TypeOf, - UnsafeDiscardStringContext, - UnsafeGetAttrPos, - Warn, - ZipAttrsWith, - - ForceResultShallow, - ForceResultShallowPush, - ForceResultShallowLoop, - ForceResultDeepFinish, - - EqStep, - EqForce, - - // TODO: split into separate enums - CallPattern, - CallFunctor1, - CallFunctor2, - - ImportFinalize, - ScopedImportFinalize, - - AppendContextLoop, - AppendContextEntryForced, - AppendContextOutputsForced, - AppendContextOutputElementLoop, - AppendContextOutputElementForced, - - UnsafeDiscardOutputDependency, - - Illegal, -} - -impl TryFrom for PrimOpPhase { - type Error = u8; - fn try_from(value: u8) -> Result { - if (0..Self::Illegal as u8).contains(&value) { - Ok(unsafe { std::mem::transmute::(value) }) - } else { - Err(value) - } - } -} - -impl BuiltinId { - #[inline(always)] - pub fn entry_phase(self) -> PrimOpPhase { - use BuiltinId::*; - match self { - Abort => PrimOpPhase::Abort, - Add => PrimOpPhase::Add, - AddErrorContext => PrimOpPhase::AddErrorContext, - All => PrimOpPhase::All, - Any => PrimOpPhase::Any, - AppendContext => PrimOpPhase::AppendContext, - AttrNames => PrimOpPhase::AttrNames, - AttrValues => PrimOpPhase::AttrValues, - BaseNameOf => PrimOpPhase::BaseNameOf, - BitAnd => PrimOpPhase::BitAnd, - BitOr => PrimOpPhase::BitOr, - BitXor => PrimOpPhase::BitXor, - Break => PrimOpPhase::Break, - CatAttrs => PrimOpPhase::CatAttrs, - Ceil => PrimOpPhase::Ceil, - CompareVersions => PrimOpPhase::CompareVersions, - ConcatLists => PrimOpPhase::ConcatLists, - ConcatMap => PrimOpPhase::ConcatMap, - ConcatStringsSep => PrimOpPhase::ConcatStringsSep, - ConvertHash => PrimOpPhase::ConvertHash, - DeepSeq => PrimOpPhase::DeepSeq, - Derivation => PrimOpPhase::Derivation, - DerivationStrict => PrimOpPhase::DerivationStrict, - DirOf => PrimOpPhase::DirOf, - Div => PrimOpPhase::Div, - Elem => PrimOpPhase::Elem, - ElemAt => PrimOpPhase::ElemAt, - FetchGit => PrimOpPhase::FetchGit, - FetchMercurial => PrimOpPhase::FetchMercurial, - FetchTarball => PrimOpPhase::FetchTarball, - FetchTree => PrimOpPhase::FetchTree, - FetchUrl => PrimOpPhase::FetchUrl, - Filter => PrimOpPhase::FilterForceList, - FilterSource => PrimOpPhase::FilterSource, - FindFile => PrimOpPhase::FindFile, - Floor => PrimOpPhase::Floor, - FoldlStrict => PrimOpPhase::FoldlStrict, - FromJSON => PrimOpPhase::FromJSON, - FromTOML => PrimOpPhase::FromTOML, - FunctionArgs => PrimOpPhase::FunctionArgs, - GenList => PrimOpPhase::GenList, - GenericClosure => PrimOpPhase::GenericClosure, - GetAttr => PrimOpPhase::GetAttr, - GetContext => PrimOpPhase::GetContext, - GetEnv => PrimOpPhase::GetEnv, - GroupBy => PrimOpPhase::GroupBy, - HasAttr => PrimOpPhase::HasAttr, - HasContext => PrimOpPhase::HasContext, - HashFile => PrimOpPhase::HashFile, - HashString => PrimOpPhase::HashString, - Head => PrimOpPhase::Head, - Import => PrimOpPhase::Import, - IntersectAttrs => PrimOpPhase::IntersectAttrs, - IsAttrs => PrimOpPhase::IsAttrs, - IsBool => PrimOpPhase::IsBool, - IsFloat => PrimOpPhase::IsFloat, - IsFunction => PrimOpPhase::IsFunction, - IsInt => PrimOpPhase::IsInt, - IsList => PrimOpPhase::IsList, - IsNull => PrimOpPhase::IsNull, - IsPath => PrimOpPhase::IsPath, - IsString => PrimOpPhase::IsString, - Length => PrimOpPhase::Length, - LessThan => PrimOpPhase::LessThan, - ListToAttrs => PrimOpPhase::ListToAttrs, - Map => PrimOpPhase::Map, - MapAttrs => PrimOpPhase::MapAttrs, - Match => PrimOpPhase::Match, - Mul => PrimOpPhase::Mul, - ParseDrvName => PrimOpPhase::ParseDrvName, - Partition => PrimOpPhase::Partition, - Path => PrimOpPhase::Path, - PathExists => PrimOpPhase::PathExists, - Placeholder => PrimOpPhase::Placeholder, - ReadDir => PrimOpPhase::ReadDir, - ReadFile => PrimOpPhase::ReadFile, - ReadFileType => PrimOpPhase::ReadFileType, - RemoveAttrs => PrimOpPhase::RemoveAttrs, - ReplaceStrings => PrimOpPhase::ReplaceStrings, - ScopedImport => PrimOpPhase::ScopedImport, - Seq => PrimOpPhase::Seq, - Sort => PrimOpPhase::Sort, - Split => PrimOpPhase::Split, - SplitVersion => PrimOpPhase::SplitVersion, - StorePath => PrimOpPhase::StorePath, - StringLength => PrimOpPhase::StringLength, - Sub => PrimOpPhase::Sub, - Substring => PrimOpPhase::Substring, - Tail => PrimOpPhase::Tail, - Throw => PrimOpPhase::Throw, - ToFile => PrimOpPhase::ToFile, - ToJSON => PrimOpPhase::ToJSON, - ToPath => PrimOpPhase::ToPath, - ToString => PrimOpPhase::ToString, - ToXML => PrimOpPhase::ToXML, - Trace => PrimOpPhase::Trace, - TryEval => PrimOpPhase::TryEval, - TypeOf => PrimOpPhase::TypeOf, - UnsafeDiscardStringContext => PrimOpPhase::UnsafeDiscardStringContext, - UnsafeDiscardOutputDependency => PrimOpPhase::UnsafeDiscardOutputDependency, - UnsafeGetAttrPos => PrimOpPhase::UnsafeGetAttrPos, - Warn => PrimOpPhase::Warn, - ZipAttrsWith => PrimOpPhase::ZipAttrsWith, - } - } -} - -impl PrimOpPhase { - pub fn ip(self) -> u32 { - self as u32 * 2 - } -} diff --git a/fix-bytecode/Cargo.toml b/fix-bytecode/Cargo.toml new file mode 100644 index 0000000..6a9608b --- /dev/null +++ b/fix-bytecode/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "fix-bytecode" +version = "0.1.0" +edition = "2024" + +[dependencies] +colored = "3.1.1" +likely_stable = { workspace = true } +num_enum = { workspace = true } +string-interner = { workspace = true } + +fix-lang = { path = "../fix-lang" } diff --git a/fix-codegen/src/disassembler.rs b/fix-bytecode/src/disassembler.rs similarity index 98% rename from fix-codegen/src/disassembler.rs rename to fix-bytecode/src/disassembler.rs index 7ce53fc..a4682f1 100644 --- a/fix-codegen/src/disassembler.rs +++ b/fix-bytecode/src/disassembler.rs @@ -1,10 +1,9 @@ use std::fmt::Write; use colored::Colorize; -use fix_builtins::BuiltinId; use num_enum::TryFromPrimitive; -use crate::{InstructionPtr, Op, OperandType}; +use crate::{InstructionPtr, Op, OperandType, PrimOpPhase}; pub trait DisassemblerContext { fn resolve_string(&self, id: u32) -> &str; @@ -297,8 +296,8 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> { ("Call", "arg=?".into()) } Op::DispatchPrimOp => { - let id = BuiltinId::try_from_primitive(self.read_u8()).expect("invalid builtin id"); - ("DispatchPrimOp", format!("id={id:?}")) + let phase = PrimOpPhase::try_from(self.read_u8()).expect("invalid primop phase"); + ("DispatchPrimOp", format!("phase={phase:?}")) } Op::MakeAttrs => { diff --git a/fix-bytecode/src/lib.rs b/fix-bytecode/src/lib.rs new file mode 100644 index 0000000..1a524c6 --- /dev/null +++ b/fix-bytecode/src/lib.rs @@ -0,0 +1,531 @@ +#![allow(dead_code)] + +use fix_lang::{BuiltinId, StringId}; +use num_enum::TryFromPrimitive; +use string_interner::Symbol as _; + +pub mod disassembler; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct InstructionPtr(pub usize); + +#[repr(u8)] +#[derive(Debug, Clone, Copy, TryFromPrimitive)] +#[allow(clippy::enum_variant_names)] +pub enum Op { + PushSmi, + PushBigInt, + PushFloat, + PushString, + PushNull, + PushTrue, + PushFalse, + + LoadLocal, + LoadOuter, + StoreLocal, + AllocLocals, + + MakeThunk, + MakeClosure, + MakePatternClosure, + + Call, + DispatchPrimOp, + + MakeAttrs, + MakeEmptyAttrs, + SelectStatic, + SelectDynamic, + HasAttrPathStatic, + HasAttrPathDynamic, + HasAttrStatic, + HasAttrDynamic, + HasAttrResolve, + JumpIfSelectSucceeded, + JumpIfSelectFailed, + + MakeList, + MakeEmptyList, + + OpAdd, + OpSub, + OpMul, + OpDiv, + OpEq, + OpNeq, + OpLt, + OpGt, + OpLeq, + OpGeq, + OpConcat, + OpUpdate, + + OpNeg, + OpNot, + + JumpIfFalse, + JumpIfTrue, + Jump, + + CoerceToString, + + ConcatStrings, + ResolvePath, + + Assert, + + LookupWith, + + LoadBuiltins, + LoadBuiltin, + + LoadReplBinding, + LoadScopedBinding, + + Return, + + Illegal, +} + +#[repr(u8)] +#[derive(Debug, Clone, Copy, TryFromPrimitive)] +pub enum OperandType { + Const, + BigInt, + Local, + BuiltinConst, + Builtins, + ReplBinding, + ScopedImportBinding, +} + +pub enum Const { + Smi(i32), + Float(f64), + Bool(bool), + String(StringId), + Path(StringId), + PrimOp { + id: BuiltinId, + arity: u8, + dispatch_ip: u32, + }, + Null, +} + +#[repr(u8)] +#[derive(Debug, Clone, Copy, TryFromPrimitive)] +pub enum AttrKeyType { + Static, + Dynamic, +} + +pub enum OperandData { + Const(u32), + BigInt(i64), + Local { layer: u8, idx: u32 }, + BuiltinConst(StringId), + Builtins, + ReplBinding(StringId), + ScopedImportBinding { slot_id: u32, name: StringId }, +} + +#[repr(u8)] +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +pub enum PrimOpPhase { + Abort, + Add, + AddErrorContext, + + All, + AllCallPred, + AllCheck, + + Any, + AnyCallPred, + AnyCheck, + + AppendContext, + AttrNames, + AttrValues, + BaseNameOf, + BitAnd, + BitOr, + BitXor, + Break, + CatAttrs, + Ceil, + CompareVersions, + ConcatLists, + ConcatMap, + ConcatStringsSep, + ConvertHash, + + DeepSeq, + DeepSeqPush, + DeepSeqLoop, + + Derivation, + DerivationStrict, + DirOf, + Div, + Elem, + ElemAt, + FetchGit, + FetchMercurial, + FetchTarball, + FetchTree, + FetchUrl, + + FilterForceList, + FilterCallPred, + FilterCheck, + + FilterSource, + FindFile, + Floor, + FoldlStrict, + FoldlStrictEmpty, + FoldlStrictCall1, + FoldlStrictCall2, + FoldlStrictUpdate, + FromJSON, + FromTOML, + FunctionArgs, + GenList, + GenericClosure, + GetAttr, + GetContext, + GetEnv, + GroupBy, + HasAttr, + HasContext, + HashFile, + HashString, + Head, + Import, + IntersectAttrs, + IsAttrs, + IsBool, + IsFloat, + IsFunction, + IsInt, + IsList, + IsNull, + IsPath, + IsString, + Length, + LessThan, + ListToAttrs, + Map, + MapAttrs, + Match, + Mul, + ParseDrvName, + Partition, + Path, + PathExists, + Placeholder, + ReadDir, + ReadFile, + ReadFileType, + RemoveAttrs, + ReplaceStrings, + ScopedImport, + Seq, + Sort, + Split, + SplitVersion, + StorePath, + StringLength, + Sub, + Substring, + Tail, + Throw, + ToFile, + ToJSON, + ToPath, + ToString, + ToXML, + Trace, + TryEval, + TypeOf, + UnsafeDiscardStringContext, + UnsafeGetAttrPos, + Warn, + ZipAttrsWith, + + ForceResultShallow, + ForceResultShallowPush, + ForceResultShallowLoop, + ForceResultDeepFinish, + + EqStep, + EqForce, + + CallPattern, + CallFunctor1, + CallFunctor2, + + ImportFinalize, + ScopedImportFinalize, + + AppendContextLoop, + AppendContextEntryForced, + AppendContextOutputsForced, + AppendContextOutputElementLoop, + AppendContextOutputElementForced, + + UnsafeDiscardOutputDependency, + + Illegal, +} + +impl TryFrom for PrimOpPhase { + type Error = u8; + + fn try_from(value: u8) -> Result { + if (0..Self::Illegal as u8).contains(&value) { + Ok(unsafe { std::mem::transmute::(value) }) + } else { + Err(value) + } + } +} + +impl PrimOpPhase { + pub fn entry_for_builtin(id: BuiltinId) -> Self { + use BuiltinId::*; + match id { + Abort => Self::Abort, + Add => Self::Add, + AddErrorContext => Self::AddErrorContext, + All => Self::All, + Any => Self::Any, + AppendContext => Self::AppendContext, + AttrNames => Self::AttrNames, + AttrValues => Self::AttrValues, + BaseNameOf => Self::BaseNameOf, + BitAnd => Self::BitAnd, + BitOr => Self::BitOr, + BitXor => Self::BitXor, + Break => Self::Break, + CatAttrs => Self::CatAttrs, + Ceil => Self::Ceil, + CompareVersions => Self::CompareVersions, + ConcatLists => Self::ConcatLists, + ConcatMap => Self::ConcatMap, + ConcatStringsSep => Self::ConcatStringsSep, + ConvertHash => Self::ConvertHash, + DeepSeq => Self::DeepSeq, + Derivation => Self::Derivation, + DerivationStrict => Self::DerivationStrict, + DirOf => Self::DirOf, + Div => Self::Div, + Elem => Self::Elem, + ElemAt => Self::ElemAt, + FetchGit => Self::FetchGit, + FetchMercurial => Self::FetchMercurial, + FetchTarball => Self::FetchTarball, + FetchTree => Self::FetchTree, + FetchUrl => Self::FetchUrl, + Filter => Self::FilterForceList, + FilterSource => Self::FilterSource, + FindFile => Self::FindFile, + Floor => Self::Floor, + FoldlStrict => Self::FoldlStrict, + FromJSON => Self::FromJSON, + FromTOML => Self::FromTOML, + FunctionArgs => Self::FunctionArgs, + GenList => Self::GenList, + GenericClosure => Self::GenericClosure, + GetAttr => Self::GetAttr, + GetContext => Self::GetContext, + GetEnv => Self::GetEnv, + GroupBy => Self::GroupBy, + HasAttr => Self::HasAttr, + HasContext => Self::HasContext, + HashFile => Self::HashFile, + HashString => Self::HashString, + Head => Self::Head, + Import => Self::Import, + IntersectAttrs => Self::IntersectAttrs, + IsAttrs => Self::IsAttrs, + IsBool => Self::IsBool, + IsFloat => Self::IsFloat, + IsFunction => Self::IsFunction, + IsInt => Self::IsInt, + IsList => Self::IsList, + IsNull => Self::IsNull, + IsPath => Self::IsPath, + IsString => Self::IsString, + Length => Self::Length, + LessThan => Self::LessThan, + ListToAttrs => Self::ListToAttrs, + Map => Self::Map, + MapAttrs => Self::MapAttrs, + Match => Self::Match, + Mul => Self::Mul, + ParseDrvName => Self::ParseDrvName, + Partition => Self::Partition, + Path => Self::Path, + PathExists => Self::PathExists, + Placeholder => Self::Placeholder, + ReadDir => Self::ReadDir, + ReadFile => Self::ReadFile, + ReadFileType => Self::ReadFileType, + RemoveAttrs => Self::RemoveAttrs, + ReplaceStrings => Self::ReplaceStrings, + ScopedImport => Self::ScopedImport, + Seq => Self::Seq, + Sort => Self::Sort, + Split => Self::Split, + SplitVersion => Self::SplitVersion, + StorePath => Self::StorePath, + StringLength => Self::StringLength, + Sub => Self::Sub, + Substring => Self::Substring, + Tail => Self::Tail, + Throw => Self::Throw, + ToFile => Self::ToFile, + ToJSON => Self::ToJSON, + ToPath => Self::ToPath, + ToString => Self::ToString, + ToXML => Self::ToXML, + Trace => Self::Trace, + TryEval => Self::TryEval, + TypeOf => Self::TypeOf, + UnsafeDiscardStringContext => Self::UnsafeDiscardStringContext, + UnsafeDiscardOutputDependency => Self::UnsafeDiscardOutputDependency, + UnsafeGetAttrPos => Self::UnsafeGetAttrPos, + Warn => Self::Warn, + ZipAttrsWith => Self::ZipAttrsWith, + } + } + + pub fn ip(self) -> u32 { + self as u32 * 2 + } +} + +pub struct BytecodeReader<'a> { + bytecode: &'a [u8], + pc: usize, + inst_start_pc: usize, +} + +impl<'a> BytecodeReader<'a> { + pub fn new(bytecode: &'a [u8], pc: usize) -> Self { + Self { + bytecode, + pc, + inst_start_pc: pc, + } + } + + #[inline(always)] + pub fn from_after_op(bytecode: &'a [u8], inst_start_pc: usize) -> Self { + Self { + bytecode, + pc: inst_start_pc + 1, + inst_start_pc, + } + } + + #[inline(always)] + #[cfg_attr(debug_assertions, track_caller)] + fn read_array(&mut self) -> [u8; N] { + let ret = self.bytecode[self.pc..self.pc + N] + .try_into() + .expect("read_array failed"); + self.pc += N; + ret + } + + #[inline(always)] + pub fn read_op(&mut self) -> Op { + self.inst_start_pc = self.pc; + let byte = self.bytecode[self.pc]; + if !likely_stable::likely((0..Op::Illegal as u8).contains(&byte)) { + panic!("unknown opcode: {byte:#04x}") + } + self.pc += 1; + unsafe { std::mem::transmute::(byte) } + } + + #[inline(always)] + pub fn read_u8(&mut self) -> u8 { + let val = self.bytecode[self.pc]; + self.pc += 1; + val + } + + #[inline(always)] + pub fn read_u16(&mut self) -> u16 { + u16::from_le_bytes(self.read_array()) + } + + #[inline(always)] + pub fn read_u32(&mut self) -> u32 { + u32::from_le_bytes(self.read_array()) + } + + #[inline(always)] + pub fn read_i32(&mut self) -> i32 { + i32::from_le_bytes(self.read_array()) + } + + #[inline(always)] + pub fn read_i64(&mut self) -> i64 { + i64::from_le_bytes(self.read_array()) + } + + #[inline(always)] + pub fn read_f64(&mut self) -> f64 { + f64::from_le_bytes(self.read_array()) + } + + #[inline(always)] + pub fn read_string_id(&mut self) -> StringId { + let raw = self.read_u32(); + #[allow(clippy::unwrap_used)] + StringId(string_interner::symbol::SymbolU32::try_from_usize(raw as usize).unwrap()) + } + + #[inline(always)] + pub fn read_operand_data(&mut self) -> OperandData { + let tag = self.read_u8(); + let Ok(ty) = OperandType::try_from_primitive(tag) + .map_err(|err| panic!("unknown operand tag: {:#04x}", err.number)); + match ty { + OperandType::Const => OperandData::Const(self.read_u32()), + OperandType::BigInt => OperandData::BigInt(self.read_i64()), + OperandType::Local => { + let layer = self.read_u8(); + let idx = self.read_u32(); + OperandData::Local { layer, idx } + } + OperandType::BuiltinConst => OperandData::BuiltinConst(self.read_string_id()), + OperandType::Builtins => OperandData::Builtins, + OperandType::ReplBinding => OperandData::ReplBinding(self.read_string_id()), + OperandType::ScopedImportBinding => { + let slot_id = self.read_u32(); + let name = self.read_string_id(); + OperandData::ScopedImportBinding { slot_id, name } + } + } + } + + pub fn pc(&self) -> usize { + self.pc + } + + pub fn set_pc(&mut self, pc: usize) { + self.pc = pc; + } + + pub fn inst_start_pc(&self) -> usize { + self.inst_start_pc + } +} diff --git a/fix-codegen/Cargo.toml b/fix-codegen/Cargo.toml deleted file mode 100644 index 24981d0..0000000 --- a/fix-codegen/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "fix-codegen" -version = "0.1.0" -edition = "2024" - -[dependencies] -hashbrown = { workspace = true } -num_enum = { workspace = true } -rnix = { workspace = true } -string-interner = { workspace = true } -colored = "3.1.1" - -fix-builtins = { path = "../fix-builtins" } -fix-common = { path = "../fix-common" } -fix-ir = { path = "../fix-ir" } diff --git a/fix-ir/Cargo.toml b/fix-compiler/Cargo.toml similarity index 64% rename from fix-ir/Cargo.toml rename to fix-compiler/Cargo.toml index f543bd1..b36ab7c 100644 --- a/fix-ir/Cargo.toml +++ b/fix-compiler/Cargo.toml @@ -1,17 +1,20 @@ [package] -name = "fix-ir" +name = "fix-compiler" version = "0.1.0" edition = "2024" [dependencies] bumpalo = { workspace = true } ghost-cell = { workspace = true } +hashbrown = { workspace = true } +num_enum = { workspace = true } rnix = { workspace = true } rowan = { workspace = true } string-interner = { workspace = true } -hashbrown = { workspace = true } -num_enum = { workspace = true } +colored = "3.1.1" -fix-builtins = { path = "../fix-builtins" } -fix-common = { path = "../fix-common" } +fix-bytecode = { path = "../fix-bytecode" } fix-error = { path = "../fix-error" } +fix-lang = { path = "../fix-lang" } +fix-runtime = { path = "../fix-runtime" } +tracing = "0.1" diff --git a/fix-compiler/src/context.rs b/fix-compiler/src/context.rs new file mode 100644 index 0000000..c937215 --- /dev/null +++ b/fix-compiler/src/context.rs @@ -0,0 +1,542 @@ +use bumpalo::Bump; +use fix_bytecode::{Const, InstructionPtr, Op, PrimOpPhase}; +use fix_error::{Error, Result, Source}; +use fix_lang::{StringId, Symbol}; +use fix_runtime::{StaticValue, VmCode, VmRuntimeCtx}; +use ghost_cell::{GhostCell, GhostToken}; +use hashbrown::{HashMap, HashSet}; +use string_interner::DefaultStringInterner; + +use crate::BytecodeContext; +use crate::ir::downgrade::{Downgrade as _, DowngradeContext}; +use crate::ir::{ + GhostMaybeThunkRef, GhostRoIrRef, GhostRoMaybeThunkRef, GhostRoRef, Ir, MaybeThunk, RawIrRef, + ThunkId, +}; + +pub struct CodeState { + pub bytecode: Vec, + pub sources: Vec, + pub spans: Vec<(usize, rnix::TextRange)>, + pub thunk_count: usize, + pub global_env: HashMap, +} + +impl CodeState { + pub fn new(strings: &mut DefaultStringInterner) -> Self { + let global_env = crate::ir::new_global_env(strings); + let mut bytecode = Vec::with_capacity(PrimOpPhase::Illegal as usize * 2); + for phase in 0..=PrimOpPhase::Illegal as u8 { + bytecode.push(Op::DispatchPrimOp as u8); + bytecode.push(phase); + } + Self { + sources: Vec::new(), + spans: Vec::new(), + thunk_count: 0, + bytecode, + global_env, + } + } + + pub fn compile_bytecode<'ctx>( + &'ctx mut self, + source: Source, + extra_scope: Option>, + runtime: &'ctx mut impl VmRuntimeCtx, + ) -> Result { + let mut compiler = CompilerCtx { + code: self, + runtime, + }; + compiler.compile_bytecode(source, extra_scope) + } +} + +impl VmCode for CodeState { + fn bytecode(&self) -> &[u8] { + &self.bytecode + } + + fn compile_with_scope( + &mut self, + source: Source, + extra_scope: Option, + runtime: &mut impl VmRuntimeCtx, + ) -> Result { + let extra = extra_scope.map(|s| match s { + fix_runtime::ExtraScope::ScopedImport { keys, slot_id } => { + ExtraScope::ScopedImport { keys, slot_id } + } + }); + CodeState::compile_bytecode(self, source, extra, runtime) + } +} + +struct CompilerCtx<'a, R: VmRuntimeCtx> { + code: &'a mut CodeState, + runtime: &'a mut R, +} + +impl<'a, R: VmRuntimeCtx> CompilerCtx<'a, R> { + fn compile_bytecode( + &mut self, + source: Source, + extra_scope: Option, + ) -> Result { + let root = self.downgrade(source, extra_scope)?; + let ip = crate::compile_bytecode(root.as_ref(), self); + Ok(ip) + } + + fn downgrade(&mut self, source: Source, extra_scope: Option) -> Result { + tracing::debug!("Parsing Nix expression"); + + self.code.sources.push(source.clone()); + + let root = rnix::Root::parse(&source.src); + handle_parse_error(root.errors(), source.clone()).map_or(Ok(()), Err)?; + + tracing::debug!("Downgrading Nix expression"); + let expr = root + .tree() + .expr() + .ok_or_else(|| Error::parse_error("unexpected EOF".into()))?; + let bump = Bump::new(); + GhostToken::new(|token| { + let downgrade_ctx = DowngradeCtx::new( + &bump, + token, + self.runtime, + &self.code.global_env, + extra_scope.map(Into::into), + &mut self.code.thunk_count, + source, + ); + let ir = downgrade_ctx.downgrade_toplevel(expr)?; + let ir = unsafe { std::mem::transmute::, RawIrRef<'static>>(ir) }; + Ok(OwnedIr { _bump: bump, ir }) + }) + } +} + +impl<'a, R: VmRuntimeCtx> BytecodeContext for CompilerCtx<'a, R> { + fn intern_string(&mut self, s: &str) -> StringId { + self.runtime.intern_string(s) + } + + fn register_span(&mut self, range: rnix::TextRange) -> u32 { + let id = self.code.spans.len(); + let source_id = self + .code + .sources + .len() + .checked_sub(1) + .expect("current_source not set"); + self.code.spans.push((source_id, range)); + id as u32 + } + + fn get_code(&self) -> &[u8] { + &self.code.bytecode + } + + fn get_code_mut(&mut self) -> &mut Vec { + &mut self.code.bytecode + } + + fn add_constant(&mut self, val: Const) -> u32 { + use Const::*; + let val = match val { + Smi(x) => StaticValue::new_inline(x), + Float(x) => StaticValue::new_float(x), + Bool(x) => StaticValue::new_inline(x), + String(x) => StaticValue::new_inline(x), + Path(x) => StaticValue::new_inline(fix_runtime::Path(x)), + PrimOp { + id, + arity, + dispatch_ip, + } => StaticValue::new_primop(id, arity, dispatch_ip), + Null => StaticValue::default(), + }; + self.runtime.add_const(val) + } + + fn current_source_dir(&mut self) -> StringId { + let dir = self + .code + .sources + .last() + .expect("current_source not set") + .get_dir() + .to_string_lossy() + .into_owned(); + self.runtime.intern_string(dir) + } +} + +fn parse_error_span(error: &rnix::ParseError) -> Option { + 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, + source: Source, +) -> Option> { + for err in errors { + if let Some(span) = parse_error_span(err) { + return Some( + Error::parse_error(err.to_string()) + .with_source(source) + .with_span(span), + ); + } + } + None +} + +struct DowngradeCtx<'ctx, 'id, 'ir, R: VmRuntimeCtx> { + bump: &'ir Bump, + token: GhostToken<'id>, + runtime: &'ctx mut R, + source: Source, + scopes: Vec>, + with_stack: Vec>, + thunk_count: &'ctx mut usize, + thunk_scopes: Vec>, +} + +impl<'ctx, 'id, 'ir, R: VmRuntimeCtx> DowngradeCtx<'ctx, 'id, 'ir, R> { + fn new( + bump: &'ir Bump, + token: GhostToken<'id>, + runtime: &'ctx mut R, + global: &'ctx HashMap, + extra_scope: Option>, + thunk_count: &'ctx mut usize, + source: Source, + ) -> Self { + Self { + bump, + token, + runtime, + source, + scopes: std::iter::once(Scope::Global(global)) + .chain(extra_scope) + .collect(), + thunk_count, + with_stack: Vec::new(), + thunk_scopes: vec![ThunkScope::new_in(bump)], + } + } +} + +impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir> + for DowngradeCtx<'ctx, 'id, 'ir, R> +{ + fn new_expr(&self, expr: Ir<'ir, GhostRoRef<'id, 'ir>>) -> GhostRoIrRef<'id, 'ir> { + self.bump.alloc(GhostCell::new(expr).into()) + } + + fn maybe_thunk(&mut self, ir: GhostRoIrRef<'id, 'ir>) -> GhostRoMaybeThunkRef<'id, 'ir> { + use MaybeThunk::*; + let expr = (|| { + let expr = match *ir.borrow(&self.token) { + Ir::Builtin(x) => Builtin(x), + Ir::Int(x) => Int(x), + Ir::Float(x) => Float(x), + Ir::Bool(x) => Bool(x), + Ir::Str(x) => Str(x), + Ir::Arg { layer } => Arg { layer }, + Ir::Builtins => Builtins, + Ir::Null => Null, + Ir::MaybeThunk(thunk) => return Some(thunk), + _ => return None, + }; + Some(self.bump.alloc(GhostCell::new(expr).into())) + })(); + if let Some(thunk) = expr { + return thunk; + } + let id = ThunkId(*self.thunk_count); + *self.thunk_count = self.thunk_count.checked_add(1).expect("thunk id overflow"); + self.thunk_scopes + .last_mut() + .expect("no active cache scope") + .add_binding(id, ir); + self.bump.alloc(GhostCell::new(Thunk(id)).into()) + } + + fn intern_string(&mut self, sym: impl AsRef) -> StringId { + self.runtime.intern_string(sym) + } + + fn resolve_sym(&self, id: StringId) -> Symbol<'_> { + self.runtime.resolve_string(id).into() + } + + fn lookup( + &mut self, + sym: StringId, + span: rnix::TextRange, + ) -> Result> { + for scope in self.scopes.iter().rev() { + match scope { + &Scope::Global(global_scope) => { + if let Some(expr) = global_scope.get(&sym) { + return Ok(expr.into()); + } + } + &Scope::Repl(repl_bindings) => { + if repl_bindings.contains(&sym) { + return Ok(self + .bump + .alloc(GhostCell::new(MaybeThunk::ReplBinding(sym)).into())); + } + } + &Scope::ScopedImport { ref keys, slot_id } => { + if keys.contains(&sym) { + return Ok(self.bump.alloc( + GhostCell::new(MaybeThunk::ScopedImportBinding { sym, slot_id }).into(), + )); + } + } + Scope::Let(let_scope) => { + if let Some(&expr) = let_scope.get(&sym) { + return Ok(expr.into()); + } + } + &Scope::Param { + sym: param_sym, + abs_layer, + } => { + if param_sym == sym { + let layers: u8 = + self.thunk_scopes.len().try_into().expect("scope too deep!"); + let layer = layers - abs_layer; + return Ok(self + .bump + .alloc(GhostCell::new(MaybeThunk::Arg { layer }).into())); + } + } + } + } + + if !self.with_stack.is_empty() { + let id = ThunkId(*self.thunk_count); + *self.thunk_count = self.thunk_count.checked_add(1).expect("thunk id overflow"); + let mut namespaces = + bumpalo::collections::Vec::with_capacity_in(self.with_stack.len(), self.bump); + namespaces.extend(self.with_stack.iter().rev().copied()); + let body = self + .bump + .alloc(GhostCell::new(Ir::WithLookup { sym, namespaces }).into()); + self.thunk_scopes + .last_mut() + .expect("no active thunk scope") + .add_binding(id, body); + Ok(self + .bump + .alloc(GhostCell::new(MaybeThunk::Thunk(id)).into())) + } else { + Err(Error::downgrade_error( + format!("'{}' not found", self.resolve_sym(sym)), + self.get_current_source(), + span, + )) + } + } + + fn get_current_source(&self) -> Source { + self.source.clone() + } + + fn with_let_scope(&mut self, keys: &[StringId], f: F) -> Result + where + F: FnOnce( + &mut Self, + ) -> Result<( + bumpalo::collections::Vec<'ir, GhostRoMaybeThunkRef<'id, 'ir>>, + Ret, + )>, + { + let base = *self.thunk_count; + *self.thunk_count = self + .thunk_count + .checked_add(keys.len()) + .expect("thunk id overflow"); + let handles = (base..base + keys.len()) + .map(|id| { + &*self + .bump + .alloc(GhostCell::new(MaybeThunk::Thunk(ThunkId(id)))) + }) + .collect::>(); + let scope = keys.iter().copied().zip(handles.iter().copied()).collect(); + self.scopes.push(Scope::Let(scope)); + let (vals, ret) = { f(self)? }; + self.scopes.pop(); + assert_eq!(keys.len(), vals.len()); + let scope = self.thunk_scopes.last_mut().expect("no active thunk scope"); + for (i, (val, handle)) in vals.into_iter().zip(handles).enumerate() { + let thunk = *val.borrow(&self.token); + *handle.borrow_mut(&mut self.token) = thunk; + let id = ThunkId(base + i); + let ir_ref = self + .bump + .alloc(GhostCell::new(Ir::MaybeThunk(handle.into())).into()); + scope.add_binding(id, ir_ref); + } + Ok(ret) + } + + fn with_param_scope(&mut self, sym: StringId, f: F) -> Ret + where + F: FnOnce(&mut Self) -> Ret, + { + self.scopes.push(Scope::Param { + sym, + abs_layer: self.thunk_scopes.len().try_into().expect("scope too deep!"), + }); + let mut guard = ScopeGuard { ctx: self }; + f(guard.as_ctx()) + } + + fn with_with_scope(&mut self, namespace: GhostRoMaybeThunkRef<'id, 'ir>, f: F) -> Ret + where + F: FnOnce(&mut Self) -> Ret, + { + self.with_stack.push(namespace); + let ret = f(self); + self.with_stack.pop(); + ret + } + + fn with_thunk_scope( + &mut self, + f: F, + ) -> ( + Ret, + bumpalo::collections::Vec<'ir, (ThunkId, GhostRoIrRef<'id, 'ir>)>, + ) + where + F: FnOnce(&mut Self) -> Ret, + { + if self.thunk_scopes.len() == u8::MAX as usize { + panic!("scope too deep!"); + } + self.thunk_scopes.push(ThunkScope::new_in(self.bump)); + let ret = f(self); + ( + ret, + self.thunk_scopes + .pop() + .expect("no thunk scope left???") + .bindings, + ) + } + + fn bump(&self) -> &'ir bumpalo::Bump { + self.bump + } +} + +impl<'id, 'ir, 'ctx: 'ir, R: VmRuntimeCtx> DowngradeCtx<'ctx, 'id, 'ir, R> { + fn downgrade_toplevel(mut self, root: rnix::ast::Expr) -> Result> { + let body = root.downgrade(&mut self)?; + let thunks = self + .thunk_scopes + .pop() + .expect("no thunk scope left???") + .bindings; + Ok(Ir::freeze( + self.new_expr(Ir::TopLevel { body, thunks }), + self.token, + )) + } +} + +struct ThunkScope<'id, 'ir> { + bindings: bumpalo::collections::Vec<'ir, (ThunkId, GhostRoIrRef<'id, 'ir>)>, +} + +impl<'id, 'ir> ThunkScope<'id, 'ir> { + fn new_in(bump: &'ir Bump) -> Self { + Self { + bindings: bumpalo::collections::Vec::new_in(bump), + } + } + + fn add_binding(&mut self, id: ThunkId, ir: GhostRoIrRef<'id, 'ir>) { + self.bindings.push((id, ir)); + } +} + +enum Scope<'ctx, 'id, 'ir> { + Global(&'ctx HashMap), + Repl(&'ctx HashSet), + ScopedImport { + keys: HashSet, + #[allow(dead_code)] + slot_id: u32, + }, + Let(HashMap>), + Param { + sym: StringId, + abs_layer: u8, + }, +} + +pub enum ExtraScope<'ctx> { + Repl(&'ctx HashSet), + ScopedImport { + keys: HashSet, + slot_id: u32, + }, +} + +impl<'ctx> From> for Scope<'ctx, '_, '_> { + fn from(value: ExtraScope<'ctx>) -> Self { + use ExtraScope::*; + match value { + ScopedImport { keys, slot_id } => Scope::ScopedImport { keys, slot_id }, + Repl(scope) => Scope::Repl(scope), + } + } +} + +struct ScopeGuard<'a, 'ctx, 'id, 'ir, R: VmRuntimeCtx> { + ctx: &'a mut DowngradeCtx<'ctx, 'id, 'ir, R>, +} + +impl<'a, 'ctx, 'id, 'ir, R: VmRuntimeCtx> Drop for ScopeGuard<'a, 'ctx, 'id, 'ir, R> { + fn drop(&mut self) { + self.ctx.scopes.pop(); + } +} + +impl<'a, 'ctx, 'id, 'ir, R: VmRuntimeCtx> ScopeGuard<'a, 'ctx, 'id, 'ir, R> { + fn as_ctx(&mut self) -> &mut DowngradeCtx<'ctx, 'id, 'ir, R> { + self.ctx + } +} + +struct OwnedIr { + _bump: Bump, + ir: RawIrRef<'static>, +} + +impl OwnedIr { + fn as_ref<'ir>(&'ir self) -> RawIrRef<'ir> { + unsafe { std::mem::transmute::, RawIrRef<'ir>>(self.ir) } + } +} diff --git a/fix-ir/src/lib.rs b/fix-compiler/src/ir.rs similarity index 97% rename from fix-ir/src/lib.rs rename to fix-compiler/src/ir.rs index e6f21a1..2809a36 100644 --- a/fix-ir/src/lib.rs +++ b/fix-compiler/src/ir.rs @@ -3,8 +3,7 @@ use std::marker::PhantomData; use bumpalo::Bump; use bumpalo::collections::Vec; -use fix_builtins::{BUILTINS, BuiltinId}; -use fix_common::StringId; +use fix_lang::{BUILTINS, BuiltinId, StringId}; use ghost_cell::{GhostCell, GhostToken}; use num_enum::TryFromPrimitive as _; use rnix::{TextRange, ast}; @@ -92,7 +91,7 @@ pub enum MaybeThunk { BuiltinConst(StringId), Builtins, ReplBinding(StringId), - ScopedImportBinding(StringId), + ScopedImportBinding { slot_id: u32, sym: StringId }, } pub trait Ref<'ir> { @@ -213,17 +212,16 @@ pub enum Ir<'ir, R: RefExt<'ir> + ?Sized + 'ir> { }, MaybeThunk(R::MaybeThunkRef), ReplBinding(StringId), - ScopedImportBinding(StringId), + ScopedImportBinding { + sym: StringId, + slot_id: u32, + }, } #[repr(transparent)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct ThunkId(pub usize); -#[repr(transparent)] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct SpanId(pub u32); - /// Represents a key in an attribute path. #[allow(unused)] #[derive(Debug)] diff --git a/fix-ir/src/downgrade.rs b/fix-compiler/src/ir/downgrade.rs similarity index 99% rename from fix-ir/src/downgrade.rs rename to fix-compiler/src/ir/downgrade.rs index 8c63a11..36d11cc 100644 --- a/fix-ir/src/downgrade.rs +++ b/fix-compiler/src/ir/downgrade.rs @@ -1,7 +1,6 @@ use bumpalo::collections::{CollectIn, Vec}; -use fix_builtins::BuiltinId; -use fix_common::Symbol; use fix_error::{Error, Result, Source}; +use fix_lang::{BuiltinId, Symbol}; use hashbrown::HashSet; use hashbrown::hash_map::Entry; use rnix::TextRange; diff --git a/fix-codegen/src/lib.rs b/fix-compiler/src/lib.rs similarity index 89% rename from fix-codegen/src/lib.rs rename to fix-compiler/src/lib.rs index 9eb57fc..c17f5b4 100644 --- a/fix-codegen/src/lib.rs +++ b/fix-compiler/src/lib.rs @@ -1,14 +1,14 @@ -use fix_builtins::{BUILTINS, BuiltinId}; -use fix_common::StringId; -use fix_ir::{Attr, BinOpKind, Ir, MaybeThunk, Param, RawIrRef, ThunkId, UnOpKind}; +use fix_bytecode::{Const, InstructionPtr, Op, OperandType, PrimOpPhase}; +use fix_lang::{BUILTINS, StringId}; use hashbrown::HashMap; -use num_enum::TryFromPrimitive; use rnix::TextRange; use string_interner::Symbol as _; -pub mod disassembler; - -pub struct InstructionPtr(pub usize); +mod context; +pub mod ir; +pub use context::{CodeState, ExtraScope}; +pub use fix_bytecode::disassembler; +pub use ir::{Attr, BinOpKind, Ir, MaybeThunk, Param, RawIrRef, ThunkId, UnOpKind}; pub trait BytecodeContext { fn intern_string(&mut self, s: &str) -> StringId; @@ -17,86 +17,6 @@ pub trait BytecodeContext { fn get_code_mut(&mut self) -> &mut Vec; fn add_constant(&mut self, val: Const) -> u32; fn current_source_dir(&mut self) -> StringId; - fn current_scope_slot(&self) -> Option; -} - -#[repr(u8)] -#[derive(Debug, Clone, Copy, TryFromPrimitive)] -#[allow(clippy::enum_variant_names)] -pub enum Op { - PushSmi, - PushBigInt, - PushFloat, - PushString, - PushNull, - PushTrue, - PushFalse, - - LoadLocal, - LoadOuter, - StoreLocal, - AllocLocals, - - MakeThunk, - MakeClosure, - MakePatternClosure, - - Call, - DispatchPrimOp, - - MakeAttrs, - MakeEmptyAttrs, - SelectStatic, - SelectDynamic, - HasAttrPathStatic, - HasAttrPathDynamic, - HasAttrStatic, - HasAttrDynamic, - HasAttrResolve, - JumpIfSelectSucceeded, - JumpIfSelectFailed, - - MakeList, - MakeEmptyList, - - OpAdd, - OpSub, - OpMul, - OpDiv, - OpEq, - OpNeq, - OpLt, - OpGt, - OpLeq, - OpGeq, - OpConcat, - OpUpdate, - - OpNeg, - OpNot, - - JumpIfFalse, - JumpIfTrue, - Jump, - - CoerceToString, - - ConcatStrings, - ResolvePath, - - Assert, - - LookupWith, - - LoadBuiltins, - LoadBuiltin, - - LoadReplBinding, - LoadScopedBinding, - - Return, - - Illegal, } struct ScopeInfo { @@ -109,39 +29,6 @@ struct BytecodeEmitter<'a, Ctx: BytecodeContext> { scope_stack: Vec, } -#[repr(u8)] -#[derive(Debug, Clone, Copy, TryFromPrimitive)] -pub enum OperandType { - Const, - BigInt, - Local, - BuiltinConst, - Builtins, - ReplBinding, - ScopedImportBinding, -} - -pub enum Const { - Smi(i32), - Float(f64), - Bool(bool), - String(StringId), - Path(StringId), - PrimOp { - id: BuiltinId, - arity: u8, - dispatch_ip: u32, - }, - Null, -} - -#[repr(u8)] -#[derive(Debug, Clone, Copy, TryFromPrimitive)] -pub enum AttrKeyType { - Static, - Dynamic, -} - pub enum InlineOperand { Const(Const), BigInt(i64), @@ -149,7 +36,7 @@ pub enum InlineOperand { BuiltinConst(StringId), Builtins, ReplBinding(StringId), - ScopedImportBinding(StringId), + ScopedImportBinding { id: StringId, slot_id: u32 }, } pub fn compile_bytecode(ir: RawIrRef<'_>, ctx: &mut impl BytecodeContext) -> InstructionPtr { @@ -193,13 +80,15 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> { InlineOperand::Const(Const::PrimOp { id, arity, - dispatch_ip: id.entry_phase().ip(), + dispatch_ip: PrimOpPhase::entry_for_builtin(id).ip(), }) } BuiltinConst(id) => InlineOperand::BuiltinConst(id), Builtins => InlineOperand::Builtins, ReplBinding(id) => InlineOperand::ReplBinding(id), - ScopedImportBinding(id) => InlineOperand::ScopedImportBinding(id), + ScopedImportBinding { slot_id, sym: id } => { + InlineOperand::ScopedImportBinding { slot_id, id } + } } } @@ -232,13 +121,9 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> { self.emit_u8(OperandType::ReplBinding as u8); self.emit_str_id(id); } - ScopedImportBinding(id) => { + ScopedImportBinding { id, slot_id } => { self.emit_u8(OperandType::ScopedImportBinding as u8); - let slot = self - .ctx - .current_scope_slot() - .expect("ScopedImportBinding outside scoped compilation"); - self.emit_u32(slot); + self.emit_u32(slot_id); self.emit_str_id(id); } } @@ -555,14 +440,10 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> { self.emit_op(Op::LoadReplBinding); self.emit_str_id(name); } - &Ir::ScopedImportBinding(name) => { + &Ir::ScopedImportBinding { sym, slot_id } => { self.emit_op(Op::LoadScopedBinding); - let slot = self - .ctx - .current_scope_slot() - .expect("ScopedImportBinding outside scoped compilation"); - self.emit_u32(slot); - self.emit_str_id(name); + self.emit_u32(slot_id); + self.emit_str_id(sym); } Ir::WithLookup { sym, namespaces } => { // counter @@ -629,14 +510,10 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> { self.emit_op(Op::LoadReplBinding); self.emit_str_id(name); } - ScopedImportBinding(name) => { + ScopedImportBinding { slot_id, sym } => { self.emit_op(Op::LoadScopedBinding); - let slot = self - .ctx - .current_scope_slot() - .expect("ScopedImportBinding outside scoped compilation"); - self.emit_u32(slot); - self.emit_str_id(name); + self.emit_u32(slot_id); + self.emit_str_id(sym); } } } @@ -775,7 +652,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> { fn emit_attrset( &mut self, - stcs: &fix_ir::HashMap<'_, StringId, (&MaybeThunk, TextRange)>, + stcs: &ir::HashMap<'_, StringId, (&MaybeThunk, TextRange)>, dyns: &[(RawIrRef<'_>, &MaybeThunk, TextRange)], ) { if stcs.is_empty() && dyns.is_empty() { diff --git a/fix-common/Cargo.toml b/fix-lang/Cargo.toml similarity index 76% rename from fix-common/Cargo.toml rename to fix-lang/Cargo.toml index 98d06bf..7ff8c6f 100644 --- a/fix-common/Cargo.toml +++ b/fix-lang/Cargo.toml @@ -1,9 +1,10 @@ [package] -name = "fix-common" +name = "fix-lang" version = "0.1.0" edition = "2024" [dependencies] gc-arena = { workspace = true } +num_enum = { workspace = true } string-interner = { workspace = true } ere = { workspace = true } diff --git a/fix-common/src/lib.rs b/fix-lang/src/lib.rs similarity index 61% rename from fix-common/src/lib.rs rename to fix-lang/src/lib.rs index 15aeda4..1aceaee 100644 --- a/fix-common/src/lib.rs +++ b/fix-lang/src/lib.rs @@ -4,6 +4,128 @@ use std::fmt::{Debug, Display, Formatter, Result as FmtResult}; use std::ops::{Deref, DerefMut}; use gc_arena::Collect; +use num_enum::TryFromPrimitive; + +macro_rules! define_builtins { + ($(($name:literal, $variant:ident, $arity:expr)),* $(,)?) => { + 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), + ("__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), + ("__unsafeDiscardOutputDependency", UnsafeDiscardOutputDependency, 1), + ("__unsafeGetAttrPos", UnsafeGetAttrPos, 2), + ("__warn", Warn, 2), + ("__zipAttrsWith", ZipAttrsWith, 2), +} #[repr(transparent)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Collect)] diff --git a/fix-primops/Cargo.toml b/fix-primops/Cargo.toml deleted file mode 100644 index 7bd1c06..0000000 --- a/fix-primops/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "fix-primops" -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 } - -fix-abstract-vm = { path = "../fix-abstract-vm" } -fix-builtins = { path = "../fix-builtins" } -fix-codegen = { path = "../fix-codegen" } -fix-common = { path = "../fix-common" } -fix-error = { path = "../fix-error" } diff --git a/fix-abstract-vm/Cargo.toml b/fix-runtime/Cargo.toml similarity index 67% rename from fix-abstract-vm/Cargo.toml rename to fix-runtime/Cargo.toml index c75023a..590bdd4 100644 --- a/fix-abstract-vm/Cargo.toml +++ b/fix-runtime/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "fix-abstract-vm" +name = "fix-runtime" version = "0.1.0" edition = "2024" @@ -12,7 +12,6 @@ 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-bytecode = { path = "../fix-bytecode" } +fix-lang = { path = "../fix-lang" } fix-error = { path = "../fix-error" } diff --git a/fix-abstract-vm/src/boxing.rs b/fix-runtime/src/boxing.rs similarity index 100% rename from fix-abstract-vm/src/boxing.rs rename to fix-runtime/src/boxing.rs diff --git a/fix-abstract-vm/src/forced.rs b/fix-runtime/src/forced.rs similarity index 99% rename from fix-abstract-vm/src/forced.rs rename to fix-runtime/src/forced.rs index 3c1124f..fbd2f96 100644 --- a/fix-abstract-vm/src/forced.rs +++ b/fix-runtime/src/forced.rs @@ -1,4 +1,4 @@ -use fix_common::StringId; +use fix_lang::StringId; use gc_arena::{Gc, Mutation}; use crate::{ diff --git a/fix-abstract-vm/src/host.rs b/fix-runtime/src/host.rs similarity index 88% rename from fix-abstract-vm/src/host.rs rename to fix-runtime/src/host.rs index f3adf89..af75192 100644 --- a/fix-abstract-vm/src/host.rs +++ b/fix-runtime/src/host.rs @@ -1,10 +1,11 @@ -use fix_codegen::InstructionPtr; -use fix_common::StringId; +use fix_bytecode::InstructionPtr; use fix_error::Source; +use fix_lang::{self, BUILTINS, StringId}; use hashbrown::HashSet; use crate::{ - AttrSet, Closure, ExtraScope, List, NixString, NixType, Null, Path, PrimOp, PrimOpApp, StaticValue, StrictValue, StringContext, Thunk, ThunkState, Value + AttrSet, Closure, ExtraScope, List, NixString, NixType, Null, Path, PrimOp, PrimOpApp, + StaticValue, StrictValue, StringContext, Thunk, ThunkState, Value, }; pub trait VmContext { @@ -38,7 +39,7 @@ pub trait VmRuntimeCtxExt: VmRuntimeCtx { /// Returns the string context attached to `val`, or `&[]` if `val` is /// either a non-string or a string without context. fn get_string_context<'gc>(&self, val: StrictValue<'gc>) -> &'gc StringContext; - fn convert_value(&self, val: Value) -> fix_common::Value; + fn convert_value(&self, val: Value) -> fix_lang::Value; } impl VmRuntimeCtxExt for T { @@ -83,18 +84,18 @@ impl VmRuntimeCtxExt for T { } } - fn convert_value(&self, val: Value) -> fix_common::Value { + fn convert_value(&self, val: Value) -> fix_lang::Value { self.convert_value_with_seen(val, &mut HashSet::new()) } } pub(crate) trait ConvertValueWithSeen: VmRuntimeCtx { - fn convert_value_with_seen(&self, val: Value, seen: &mut HashSet) -> fix_common::Value; + fn convert_value_with_seen(&self, val: Value, seen: &mut HashSet) -> fix_lang::Value; } impl ConvertValueWithSeen for T { - fn convert_value_with_seen(&self, val: Value, seen: &mut HashSet) -> fix_common::Value { - use fix_common::Value; + fn convert_value_with_seen(&self, val: Value, seen: &mut HashSet) -> fix_lang::Value { + use fix_lang::Value; if let Some(i) = val.as_inline::() { Value::Int(i as i64) } else if let Some(gc_i) = val.as_gc::() { @@ -124,9 +125,9 @@ impl ConvertValueWithSeen for T { for &(key, val) in attrs.entries.iter() { let key = self.resolve_string(key).to_owned(); let converted = self.convert_value_with_seen(val, seen); - map.insert(fix_common::Symbol::from(key), converted); + map.insert(fix_lang::Symbol::from(key), converted); } - Value::AttrSet(fix_common::AttrSet::new(map)) + Value::AttrSet(fix_lang::AttrSet::new(map)) } else if let Some(list) = val.as_gc::() { let bits = val.to_bits(); if list.inner.borrow().is_empty() { @@ -142,7 +143,7 @@ impl ConvertValueWithSeen for T { .copied() .map(|v| self.convert_value_with_seen(v, seen)) .collect(); - Value::List(fix_common::List::new(items)) + Value::List(fix_lang::List::new(items)) } else if val.is::() { Value::Func } else if let Some(thunk) = val.as_gc::() { @@ -152,10 +153,10 @@ impl ConvertValueWithSeen for T { Value::Thunk } } else if let Some(primop) = val.as_inline::() { - let name = fix_builtins::BUILTINS[primop.id as usize].0; + let name = BUILTINS[primop.id as usize].0; Value::PrimOp(name.strip_prefix("__").unwrap_or(name)) } else if let Some(app) = val.as_gc::() { - let name = fix_builtins::BUILTINS[app.primop.id as usize].0; + let name = BUILTINS[app.primop.id as usize].0; Value::PrimOpApp(name.strip_prefix("__").unwrap_or(name)) } else { Value::Null diff --git a/fix-abstract-vm/src/lib.rs b/fix-runtime/src/lib.rs similarity index 84% rename from fix-abstract-vm/src/lib.rs rename to fix-runtime/src/lib.rs index a1ea484..8aef095 100644 --- a/fix-abstract-vm/src/lib.rs +++ b/fix-runtime/src/lib.rs @@ -1,5 +1,4 @@ mod boxing; -mod bytecode_reader; mod forced; mod host; mod machine; @@ -9,7 +8,7 @@ mod state; mod string_context; mod value; -pub use bytecode_reader::*; +pub use fix_bytecode::{BytecodeReader, OperandData}; pub use forced::*; pub use host::*; pub use machine::*; diff --git a/fix-abstract-vm/src/machine.rs b/fix-runtime/src/machine.rs similarity index 98% rename from fix-abstract-vm/src/machine.rs rename to fix-runtime/src/machine.rs index d6a9c7b..1fe6863 100644 --- a/fix-abstract-vm/src/machine.rs +++ b/fix-runtime/src/machine.rs @@ -1,8 +1,8 @@ use std::ops::ControlFlow; use std::path::{Path, PathBuf}; -use fix_common::StringId; use fix_error::Error; +use fix_lang::{self, StringId}; use gc_arena::Mutation; use crate::{ @@ -96,7 +96,7 @@ pub trait Machine<'gc> { cur.borrow().locals[idx as usize] } - fn finish_ok(&mut self, val: fix_common::Value) -> Step; + fn finish_ok(&mut self, val: fix_lang::Value) -> Step; fn finish_err(&mut self, err: Box) -> Step; fn finish_type_err(&mut self, expected: NixType, got: NixType) -> Step; diff --git a/fix-abstract-vm/src/path_util.rs b/fix-runtime/src/path_util.rs similarity index 100% rename from fix-abstract-vm/src/path_util.rs rename to fix-runtime/src/path_util.rs diff --git a/fix-abstract-vm/src/resolve.rs b/fix-runtime/src/resolve.rs similarity index 87% rename from fix-abstract-vm/src/resolve.rs rename to fix-runtime/src/resolve.rs index 04dcb6a..e7f53e5 100644 --- a/fix-abstract-vm/src/resolve.rs +++ b/fix-runtime/src/resolve.rs @@ -1,6 +1,7 @@ +use fix_bytecode::OperandData; use gc_arena::{Gc, Mutation}; -use crate::{AttrSet, Machine, OperandData, Value}; +use crate::{AttrSet, Machine, Value, VmRuntimeCtx}; /// Resolve a decoded operand into a runtime [`Value`]. /// @@ -11,11 +12,12 @@ use crate::{AttrSet, Machine, OperandData, Value}; pub fn resolve_operand<'gc, M: Machine<'gc>>( op: &OperandData, mc: &Mutation<'gc>, + ctx: &impl VmRuntimeCtx, m: &M, ) -> Value<'gc> { use OperandData::*; match *op { - Const(sv) => sv.into(), + Const(id) => ctx.get_const(id).into(), BigInt(val) => Value::new_gc(Gc::new(mc, val)), Local { layer, idx } => m.local(layer, idx), #[allow(clippy::unwrap_used)] diff --git a/fix-abstract-vm/src/state.rs b/fix-runtime/src/state.rs similarity index 85% rename from fix-abstract-vm/src/state.rs rename to fix-runtime/src/state.rs index 89cf780..8a7483a 100644 --- a/fix-abstract-vm/src/state.rs +++ b/fix-runtime/src/state.rs @@ -1,12 +1,12 @@ use std::ops::ControlFlow; use std::path::PathBuf; -use fix_common::StringId; use fix_error::Error; +use fix_lang::StringId; use gc_arena::{Collect, Gc}; use hashbrown::HashSet; -use crate::{GcEnv, StaticValue, Thunk}; +use crate::{GcEnv, Thunk}; #[allow(dead_code)] pub enum VmError { @@ -87,13 +87,3 @@ pub enum ExtraScope { slot_id: u32, }, } - -pub enum OperandData { - Const(StaticValue), - BigInt(i64), - Local { layer: u8, idx: u32 }, - BuiltinConst(StringId), - Builtins, - ReplBinding(StringId), - ScopedImportBinding { slot_id: u32, name: StringId }, -} diff --git a/fix-abstract-vm/src/string_context.rs b/fix-runtime/src/string_context.rs similarity index 98% rename from fix-abstract-vm/src/string_context.rs rename to fix-runtime/src/string_context.rs index 71f2d95..5edb984 100644 --- a/fix-abstract-vm/src/string_context.rs +++ b/fix-runtime/src/string_context.rs @@ -89,7 +89,7 @@ impl<'a> IntoIterator for &'a mut StringContext { impl FromIterator for StringContext { fn from_iter>(iter: T) -> Self { Self { - data: iter.into_iter().collect() + data: iter.into_iter().collect(), } } } diff --git a/fix-abstract-vm/src/value.rs b/fix-runtime/src/value.rs similarity index 99% rename from fix-abstract-vm/src/value.rs rename to fix-runtime/src/value.rs index ff7eb3b..52c3c2a 100644 --- a/fix-abstract-vm/src/value.rs +++ b/fix-runtime/src/value.rs @@ -6,8 +6,7 @@ use std::marker::PhantomData; use std::mem::size_of; use std::ops::Deref; -use fix_builtins::BuiltinId; -use fix_common::*; +use fix_lang::*; use gc_arena::barrier::Unlock; use gc_arena::collect::Trace; use gc_arena::{Collect, Gc, GcRefLock, Mutation, RefLock}; diff --git a/fix-vm/Cargo.toml b/fix-vm/Cargo.toml index 0e4aa0d..8dbe495 100644 --- a/fix-vm/Cargo.toml +++ b/fix-vm/Cargo.toml @@ -16,9 +16,7 @@ likely_stable = { workspace = true } sptr = "0.3" sysinfo = { version = "0.38", default-features = false, features = ["system"] } -fix-builtins = { path = "../fix-builtins" } -fix-codegen = { path = "../fix-codegen" } -fix-common = { path = "../fix-common" } +fix-bytecode = { path = "../fix-bytecode" } fix-error = { path = "../fix-error" } -fix-abstract-vm = { path = "../fix-abstract-vm" } -fix-primops = { path = "../fix-primops" } +fix-lang = { path = "../fix-lang" } +fix-runtime = { path = "../fix-runtime" } diff --git a/fix-vm/src/dispatch_tailcall.rs b/fix-vm/src/dispatch_tailcall.rs index 50a9113..02135a4 100644 --- a/fix-vm/src/dispatch_tailcall.rs +++ b/fix-vm/src/dispatch_tailcall.rs @@ -202,16 +202,16 @@ macro_rules! table { impl<'gc, C: VmRuntimeCtx> DispatchTable<'gc, C> { pub(crate) const NEW: Self = { let mut arr: [OpFn<'gc, C>; 256] = [op_illegal; 256]; - $( arr[fix_codegen::Op::$variant as usize] = $fn; )* + $( arr[fix_bytecode::Op::$variant as usize] = $fn; )* DispatchTable(arr) }; } - // Exhaustiveness check: fails to compile if `fix_codegen::Op` gains, + // Exhaustiveness check: fails to compile if `fix_bytecode::Op` gains, // loses, or renames a variant that isn't wired up above. #[allow(dead_code)] - const _: fn(fix_codegen::Op) = |op| match op { - $( fix_codegen::Op::$variant => (), )* + const _: fn(fix_bytecode::Op) = |op| match op { + $( fix_bytecode::Op::$variant => (), )* }; }; } diff --git a/fix-vm/src/instructions/arithmetic.rs b/fix-vm/src/instructions/arithmetic.rs index c8864a7..075841a 100644 --- a/fix-vm/src/instructions/arithmetic.rs +++ b/fix-vm/src/instructions/arithmetic.rs @@ -1,6 +1,6 @@ use std::cmp::Ordering; -use fix_abstract_vm::*; +use fix_runtime::*; use gc_arena::{Gc, Mutation, RefLock}; use crate::{BytecodeReader, NixNum, Step, VmError, VmRuntimeCtx}; @@ -29,7 +29,7 @@ impl<'gc> crate::Vm<'gc> { let combined = format!("{ls}{rs}"); let canon = canon_path_str(&combined); let sid = ctx.intern_string(canon); - self.push(Value::new_inline(fix_abstract_vm::Path(sid))); + self.push(Value::new_inline(fix_runtime::Path(sid))); return Step::Continue(()); } if let (Some(ls), Some(rs)) = (ctx.get_string(lhs), ctx.get_string_or_path(rhs)) { @@ -111,7 +111,7 @@ impl<'gc> crate::Vm<'gc> { mc: &Mutation<'gc>, ) -> Step { let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?; - fix_primops::start_eq(self, ctx, reader, mc, lhs, rhs, false) + crate::primops::start_eq(self, ctx, reader, mc, lhs, rhs, false) } #[inline(always)] @@ -122,7 +122,7 @@ impl<'gc> crate::Vm<'gc> { mc: &Mutation<'gc>, ) -> Step { let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?; - fix_primops::start_eq(self, ctx, reader, mc, lhs, rhs, true) + crate::primops::start_eq(self, ctx, reader, mc, lhs, rhs, true) } #[inline(always)] @@ -258,7 +258,11 @@ impl<'gc> crate::Vm<'gc> { return Ok(()); } // TODO: compare other types - Err(crate::vm_err(format!("cannot compare {} with {}", lhs.ty(), rhs.ty()))) + Err(crate::vm_err(format!( + "cannot compare {} with {}", + lhs.ty(), + rhs.ty() + ))) } } diff --git a/fix-vm/src/instructions/calls.rs b/fix-vm/src/instructions/calls.rs index a6e1c71..64708ca 100644 --- a/fix-vm/src/instructions/calls.rs +++ b/fix-vm/src/instructions/calls.rs @@ -1,6 +1,6 @@ -use fix_abstract_vm::{resolve_operand, *}; -use fix_builtins::PrimOpPhase; +use fix_bytecode::PrimOpPhase; use fix_error::Error; +use fix_runtime::{resolve_operand, *}; use gc_arena::{Gc, Mutation, RefLock}; use crate::{ @@ -120,7 +120,7 @@ impl<'gc> crate::Vm<'gc> { reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>, ) -> Step { - let arg = resolve_operand(&reader.read_operand_data(ctx), mc, self); + let arg = resolve_operand(&reader.read_operand_data(), mc, ctx, self); let pc = reader.pc(); self.call(reader, mc, arg, pc) } @@ -178,6 +178,6 @@ impl<'gc> crate::Vm<'gc> { reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>, ) -> Step { - fix_primops::dispatch_primop(self, ctx, reader, mc) + crate::primops::dispatch_primop(self, ctx, reader, mc) } } diff --git a/fix-vm/src/instructions/collections.rs b/fix-vm/src/instructions/collections.rs index d42e84b..75763e1 100644 --- a/fix-vm/src/instructions/collections.rs +++ b/fix-vm/src/instructions/collections.rs @@ -1,6 +1,6 @@ -use fix_abstract_vm::{NixType, resolve_operand}; -use fix_common::StringId; use fix_error::Error; +use fix_lang::StringId; +use fix_runtime::{NixType, resolve_operand}; use gc_arena::{Gc, RefLock}; use smallvec::SmallVec; @@ -43,13 +43,13 @@ impl<'gc> crate::Vm<'gc> { for _ in 0..static_count { let key = reader.read_string_id(); - let val = resolve_operand(&reader.read_operand_data(ctx), mc, self); + let val = resolve_operand(&reader.read_operand_data(), mc, ctx, self); let _span_id = reader.read_u32(); kv.push((key, val)); } for key in dyn_keys { - let val = resolve_operand(&reader.read_operand_data(ctx), mc, self); + let val = resolve_operand(&reader.read_operand_data(), mc, ctx, self); let _span_id = reader.read_u32(); if let Some(key) = key { kv.push((key, val)) @@ -124,7 +124,7 @@ impl<'gc> crate::Vm<'gc> { ctx: &mut impl VmRuntimeCtx, reader: &mut BytecodeReader<'_>, ) -> Step { - use fix_codegen::Op::*; + use fix_bytecode::Op::*; loop { match reader.read_op() { SelectStatic => { @@ -153,7 +153,7 @@ impl<'gc> crate::Vm<'gc> { /// Skip the rest of a **HasAttr** attrpath after an intermediate /// lookup failed. Only recognises HasAttr opcodes and jumps. fn has_attr_skip(&mut self, reader: &mut BytecodeReader<'_>) -> Step { - use fix_codegen::Op::*; + use fix_bytecode::Op::*; loop { match reader.read_op() { HasAttrPathStatic => { @@ -314,7 +314,7 @@ impl<'gc> crate::Vm<'gc> { let count = reader.read_u32() as usize; let mut items: SmallVec<[Value; 4]> = SmallVec::with_capacity(count); for _ in 0..count { - items.push(resolve_operand(&reader.read_operand_data(ctx), mc, self)); + items.push(resolve_operand(&reader.read_operand_data(), mc, ctx, self)); } let list = Gc::new( mc, diff --git a/fix-vm/src/instructions/control.rs b/fix-vm/src/instructions/control.rs index 2540921..4d30b2d 100644 --- a/fix-vm/src/instructions/control.rs +++ b/fix-vm/src/instructions/control.rs @@ -1,5 +1,5 @@ -use fix_abstract_vm::*; use fix_error::Error; +use fix_runtime::*; use gc_arena::Mutation; use crate::{BytecodeReader, Step, VmRuntimeCtx}; diff --git a/fix-vm/src/instructions/misc.rs b/fix-vm/src/instructions/misc.rs index 69cb47d..51db134 100644 --- a/fix-vm/src/instructions/misc.rs +++ b/fix-vm/src/instructions/misc.rs @@ -1,11 +1,9 @@ use std::path::PathBuf; -use fix_abstract_vm::{ - AttrSet, NixString, Path, StrictValue, StringContext, canon_path_str -}; -use fix_builtins::BuiltinId; -use fix_common::StringId; +use fix_bytecode::PrimOpPhase; use fix_error::Error; +use fix_lang::{BUILTINS, BuiltinId, StringId}; +use fix_runtime::{AttrSet, NixString, Path, StrictValue, StringContext, canon_path_str}; use num_enum::TryFromPrimitive; use crate::{BytecodeReader, PrimOp, Step, Value, VmRuntimeCtx, VmRuntimeCtxExt}; @@ -23,8 +21,8 @@ impl<'gc> crate::Vm<'gc> { .map_err(|err| panic!("unknown builtin id: {}", err.number)); self.push(Value::new_inline(PrimOp { id, - arity: fix_builtins::BUILTINS[id as usize].1, - dispatch_ip: id.entry_phase().ip(), + arity: BUILTINS[id as usize].1, + dispatch_ip: PrimOpPhase::entry_for_builtin(id).ip(), })); Step::Continue(()) } diff --git a/fix-vm/src/instructions/with_scope.rs b/fix-vm/src/instructions/with_scope.rs index da608c6..21cb767 100644 --- a/fix-vm/src/instructions/with_scope.rs +++ b/fix-vm/src/instructions/with_scope.rs @@ -1,6 +1,6 @@ -use fix_abstract_vm::{resolve_operand, *}; -use fix_common::Symbol; use fix_error::Error; +use fix_lang::Symbol; +use fix_runtime::{resolve_operand, *}; use smallvec::SmallVec; use crate::{Break, BytecodeReader, CallFrame, Step, VmRuntimeCtx}; @@ -20,7 +20,7 @@ impl<'gc> crate::Vm<'gc> { let n = reader.read_u8(); let mut namespaces = SmallVec::<[_; 2]>::new(); for _ in 0..n { - namespaces.push(resolve_operand(&reader.read_operand_data(ctx), mc, self)); + namespaces.push(resolve_operand(&reader.read_operand_data(), mc, ctx, self)); } let resume_pc = reader.inst_start_pc(); diff --git a/fix-vm/src/lib.rs b/fix-vm/src/lib.rs index b098b82..521d52e 100644 --- a/fix-vm/src/lib.rs +++ b/fix-vm/src/lib.rs @@ -7,10 +7,9 @@ use std::path::PathBuf; -use fix_builtins::{BUILTINS, BuiltinId}; -use fix_codegen::InstructionPtr; -use fix_common::StringId; +use fix_bytecode::{InstructionPtr, PrimOpPhase}; use fix_error::{Error, Result, Source}; +use fix_lang::{BUILTINS, BuiltinId, StringId}; use gc_arena::metrics::Pacing; use gc_arena::{Arena, Collect, Gc, Mutation, RefLock, Rootable}; use hashbrown::HashMap; @@ -19,8 +18,9 @@ use smallvec::SmallVec; #[cfg(feature = "tailcall")] mod dispatch_tailcall; -pub use fix_abstract_vm::*; +pub use fix_runtime::*; mod instructions; +mod primops; type VmResult = std::result::Result; @@ -46,7 +46,7 @@ pub struct Vm<'gc> { force_mode: ForceMode, #[collect(require_static)] - result: Option>, + result: Option>, #[collect(require_static)] pending_load: Option, @@ -61,7 +61,7 @@ fn init_builtins<'gc>(mc: &Mutation<'gc>, ctx: &mut impl VmRuntimeCtx) -> Value< let id = BuiltinId::try_from_primitive(idx as u8).expect("infallible"); let name = name.strip_prefix("__").unwrap_or(name); let name = ctx.intern_string(name); - let dispatch_ip = id.entry_phase().ip(); + let dispatch_ip = PrimOpPhase::entry_for_builtin(id).ip(); entries.push(( name, Value::new_inline(PrimOp { @@ -139,7 +139,7 @@ impl<'gc> Vm<'gc> { } #[inline(always)] - fn finish_ok(&mut self, val: fix_common::Value) -> Step { + fn finish_ok(&mut self, val: fix_lang::Value) -> Step { self.result = Some(Ok(val)); Step::Break(Break::Done) } @@ -409,7 +409,7 @@ impl<'gc> Machine<'gc> for Vm<'gc> { } #[inline(always)] - fn finish_ok(&mut self, val: fix_common::Value) -> Step { + fn finish_ok(&mut self, val: fix_lang::Value) -> Step { self.finish_ok(val) } @@ -481,7 +481,7 @@ impl<'gc> Machine<'gc> for Vm<'gc> { enum Action { Continue { pc: usize }, - Done(Result), + Done(Result), LoadFile(PendingLoad), } @@ -504,7 +504,7 @@ impl Vm<'_> { ctx: &mut C, ip: InstructionPtr, force_mode: ForceMode, - ) -> Result { + ) -> Result { let (code, runtime) = ctx.split(); let mut arena: Arena]> = Arena::new(|mc| Vm::new(force_mode, mc, runtime)); arena.metrics().set_pacing(Pacing { @@ -589,7 +589,7 @@ impl<'gc> Vm<'gc> { pc: usize, mc: &Mutation<'gc>, ) -> Action { - use fix_codegen::Op::*; + use fix_bytecode::Op::*; let mut reader = BytecodeReader::new(bytecode, pc); let mut fuel = Self::DEFAULT_FUEL_AMOUNT; diff --git a/fix-primops/src/context.rs b/fix-vm/src/primops/context.rs similarity index 99% rename from fix-primops/src/context.rs rename to fix-vm/src/primops/context.rs index 0d40b27..794ae4f 100644 --- a/fix-primops/src/context.rs +++ b/fix-vm/src/primops/context.rs @@ -2,16 +2,16 @@ //! `builtins.unsafeDiscardStringContext`, //! `builtins.unsafeDiscardOutputDependency`. //! -//! See `fix-abstract-vm/src/string_context.rs` for the +//! See `fix-runtime/src/string_context.rs` for the //! `StringContextElem` type. -use fix_abstract_vm::{ +use fix_bytecode::PrimOpPhase; +use fix_error::Error; +use fix_lang::StringId; +use fix_runtime::{ AttrSet, BytecodeReader, List as VmList, Machine, MachineExt, NixString, NixType, Step, StrictValue, StringContext, StringContextElem, Value, VmRuntimeCtx, VmRuntimeCtxExt, }; -use fix_builtins::PrimOpPhase; -use fix_common::StringId; -use fix_error::Error; use gc_arena::{Gc, Mutation}; use smallvec::SmallVec; diff --git a/fix-primops/src/control.rs b/fix-vm/src/primops/control.rs similarity index 99% rename from fix-primops/src/control.rs rename to fix-vm/src/primops/control.rs index fee6756..10ebaf2 100644 --- a/fix-primops/src/control.rs +++ b/fix-vm/src/primops/control.rs @@ -1,9 +1,9 @@ -use fix_abstract_vm::{ +use fix_bytecode::PrimOpPhase; +use fix_error::Error; +use fix_runtime::{ AttrSet, BytecodeReader, Closure, Env, List, Machine, MachineExt, Step, StrictValue, Value, VmRuntimeCtx, VmRuntimeCtxExt, }; -use fix_builtins::PrimOpPhase; -use fix_error::Error; use gc_arena::{Gc, Mutation, RefLock}; use smallvec::SmallVec; diff --git a/fix-primops/src/conv.rs b/fix-vm/src/primops/conv.rs similarity index 96% rename from fix-primops/src/conv.rs rename to fix-vm/src/primops/conv.rs index ea8f59c..4dbf8cb 100644 --- a/fix-primops/src/conv.rs +++ b/fix-vm/src/primops/conv.rs @@ -1,9 +1,9 @@ -use fix_abstract_vm::{ +use fix_error::Error; +use fix_lang::StringId; +use fix_runtime::{ BytecodeReader, Machine, MachineExt, NixString, NixType, Path, Step, StrictValue, Value, VmRuntimeCtx, }; -use fix_common::StringId; -use fix_error::Error; use gc_arena::Mutation; pub fn to_string<'gc, M: Machine<'gc>>( diff --git a/fix-primops/src/eq.rs b/fix-vm/src/primops/eq.rs similarity index 93% rename from fix-primops/src/eq.rs rename to fix-vm/src/primops/eq.rs index dc6de28..add5e80 100644 --- a/fix-primops/src/eq.rs +++ b/fix-vm/src/primops/eq.rs @@ -1,8 +1,8 @@ -use fix_abstract_vm::{ +use fix_bytecode::PrimOpPhase; +use fix_runtime::{ AttrSet, BytecodeReader, CallFrame, List, Machine, MachineExt, NixNum, Null, Path, Step, StrictValue, Value, VmRuntimeCtx, VmRuntimeCtxExt, }; -use fix_builtins::PrimOpPhase; use gc_arena::{Gc, Mutation}; use smallvec::SmallVec; @@ -30,10 +30,8 @@ pub fn start_eq<'gc, M: Machine<'gc>>( enter_eq_machine(m, reader, mc, negate, lhs_init, rhs_init) } ShallowEq::RecurseAttrs(a, b) => { - let lhs_init: SmallVec<[Value<'gc>; 4]> = - a.entries.iter().map(|&(_, v)| v).collect(); - let rhs_init: SmallVec<[Value<'gc>; 4]> = - b.entries.iter().map(|&(_, v)| v).collect(); + let lhs_init: SmallVec<[Value<'gc>; 4]> = a.entries.iter().map(|&(_, v)| v).collect(); + let rhs_init: SmallVec<[Value<'gc>; 4]> = b.entries.iter().map(|&(_, v)| v).collect(); enter_eq_machine(m, reader, mc, negate, lhs_init, rhs_init) } } @@ -116,7 +114,12 @@ fn apply_pair<'gc, M: Machine<'gc>>( m.replace(2, Value::new_inline(false)); } ShallowEq::RecurseList(la, lb) => { - extend_queues(m, mc, la.inner.borrow().iter().copied(), lb.inner.borrow().iter().copied()); + extend_queues( + m, + mc, + la.inner.borrow().iter().copied(), + lb.inner.borrow().iter().copied(), + ); } ShallowEq::RecurseAttrs(a, b) => { extend_queues( @@ -230,9 +233,5 @@ fn shallow_eq<'gc>( } fn bool_outcome<'gc>(b: bool) -> ShallowEq<'gc> { - if b { - ShallowEq::True - } else { - ShallowEq::False - } + if b { ShallowEq::True } else { ShallowEq::False } } diff --git a/fix-primops/src/io.rs b/fix-vm/src/primops/io.rs similarity index 98% rename from fix-primops/src/io.rs rename to fix-vm/src/primops/io.rs index a7a8913..fbc9f53 100644 --- a/fix-primops/src/io.rs +++ b/fix-vm/src/primops/io.rs @@ -1,12 +1,12 @@ use std::path::PathBuf; -use fix_abstract_vm::{ +use fix_bytecode::PrimOpPhase; +use fix_error::Error; +use fix_lang::StringId; +use fix_runtime::{ AttrSet, Break, BytecodeReader, CallFrame, Machine, MachineExt, Path, PendingLoad, PendingScope, Step, StrictValue, Value, VmRuntimeCtx, VmRuntimeCtxExt, canon_path_str, }; -use fix_builtins::PrimOpPhase; -use fix_common::StringId; -use fix_error::Error; use gc_arena::{Gc, Mutation}; use hashbrown::HashSet; diff --git a/fix-primops/src/list.rs b/fix-vm/src/primops/list.rs similarity index 98% rename from fix-primops/src/list.rs rename to fix-vm/src/primops/list.rs index 9618767..d80a5bc 100644 --- a/fix-primops/src/list.rs +++ b/fix-vm/src/primops/list.rs @@ -1,7 +1,5 @@ -use fix_abstract_vm::{ - BytecodeReader, List, Machine, MachineExt, NixType, Step, StrictValue, Value, -}; -use fix_builtins::PrimOpPhase; +use fix_bytecode::PrimOpPhase; +use fix_runtime::{BytecodeReader, List, Machine, MachineExt, NixType, Step, StrictValue, Value}; use gc_arena::Mutation; pub fn filter_force_list<'gc, M: Machine<'gc>>( diff --git a/fix-primops/src/lib.rs b/fix-vm/src/primops/mod.rs similarity index 94% rename from fix-primops/src/lib.rs rename to fix-vm/src/primops/mod.rs index 67297ff..89b1904 100644 --- a/fix-primops/src/lib.rs +++ b/fix-vm/src/primops/mod.rs @@ -10,9 +10,9 @@ pub use context::*; pub use control::*; pub use conv::*; pub use eq::*; -use fix_abstract_vm::{BytecodeReader, Machine, Step, VmRuntimeCtx}; -use fix_builtins::PrimOpPhase; +use fix_bytecode::PrimOpPhase; use fix_error::Error; +use fix_runtime::{BytecodeReader, Machine, Step, VmRuntimeCtx}; use gc_arena::Mutation; pub use io::*; pub use list::*; @@ -86,7 +86,9 @@ pub fn dispatch_primop<'gc, M: Machine<'gc>>( AppendContextEntryForced => append_context_entry_forced(m, ctx, reader, mc), AppendContextOutputsForced => append_context_outputs_forced(m, ctx, reader, mc), AppendContextOutputElementLoop => append_context_output_element_loop(m, ctx, reader, mc), - AppendContextOutputElementForced => append_context_output_element_forced(m, ctx, reader, mc), + AppendContextOutputElementForced => { + append_context_output_element_forced(m, ctx, reader, mc) + } UnsafeDiscardStringContext => unsafe_discard_string_context(m, ctx, reader, mc), UnsafeDiscardOutputDependency => unsafe_discard_output_dependency(m, ctx, reader, mc), diff --git a/fix-primops/src/path.rs b/fix-vm/src/primops/path.rs similarity index 98% rename from fix-primops/src/path.rs rename to fix-vm/src/primops/path.rs index 5dba511..b462498 100644 --- a/fix-primops/src/path.rs +++ b/fix-vm/src/primops/path.rs @@ -1,8 +1,8 @@ -use fix_abstract_vm::{ +use fix_error::Error; +use fix_runtime::{ BytecodeReader, Machine, MachineExt, Path, Step, StrictValue, Value, VmRuntimeCtx, VmRuntimeCtxExt, canon_path_str, }; -use fix_error::Error; use gc_arena::Mutation; pub fn to_path<'gc, M: Machine<'gc>>( diff --git a/fix/Cargo.toml b/fix/Cargo.toml index 620a2ec..b452333 100644 --- a/fix/Cargo.toml +++ b/fix/Cargo.toml @@ -25,20 +25,13 @@ miette = { version = "7.4", features = ["fancy"] } hashbrown = { workspace = true } string-interner = { workspace = true } -# Memory Management -bumpalo = { workspace = true } - -rnix = { workspace = true } - ere = { workspace = true } -ghost-cell = { workspace = true } -fix-abstract-vm = { path = "../fix-abstract-vm" } -fix-builtins = { path = "../fix-builtins" } -fix-common = { path = "../fix-common" } -fix-codegen = { path = "../fix-codegen" } +fix-bytecode = { path = "../fix-bytecode" } +fix-compiler = { path = "../fix-compiler" } fix-error = { path = "../fix-error" } -fix-ir = { path = "../fix-ir" } +fix-lang = { path = "../fix-lang" } +fix-runtime = { path = "../fix-runtime" } fix-vm = { path = "../fix-vm" } [dev-dependencies] diff --git a/fix/benches/utils.rs b/fix/benches/utils.rs index 89bf3b8..22348e9 100644 --- a/fix/benches/utils.rs +++ b/fix/benches/utils.rs @@ -1,8 +1,8 @@ #![allow(dead_code)] use fix::Evaluator; -use fix_common::Value; use fix_error::{Result, Source}; +use fix_lang::Value; pub fn eval(expr: &str) -> Value { Evaluator::new() diff --git a/fix/src/lib.rs b/fix/src/lib.rs index 7239630..75b4f20 100644 --- a/fix/src/lib.rs +++ b/fix/src/lib.rs @@ -1,20 +1,13 @@ #![warn(clippy::unwrap_used)] #![allow(dead_code)] -use bumpalo::Bump; -use fix_abstract_vm::{ForceMode, StaticValue, VmCode, VmContext, VmRuntimeCtx}; -use fix_builtins::PrimOpPhase; -use fix_codegen::disassembler::{Disassembler, DisassemblerContext}; -use fix_codegen::{BytecodeContext, InstructionPtr, Op}; -use fix_common::{StringId, Symbol}; -use fix_error::{Error, Result, Source}; -use fix_ir::downgrade::{Downgrade as _, DowngradeContext}; -use fix_ir::{ - GhostMaybeThunkRef, GhostRoIrRef, GhostRoMaybeThunkRef, GhostRoRef, Ir, MaybeThunk, RawIrRef, - ThunkId, -}; +use fix_bytecode::InstructionPtr; +use fix_bytecode::disassembler::{Disassembler, DisassemblerContext}; +use fix_compiler::{CodeState, ExtraScope}; +use fix_error::{Result, Source}; +use fix_lang::StringId; +use fix_runtime::{ForceMode, StaticValue, VmCode, VmContext, VmRuntimeCtx}; use fix_vm::Vm; -use ghost_cell::{GhostCell, GhostToken}; use hashbrown::{HashMap, HashSet}; use string_interner::{DefaultStringInterner, Symbol as _}; @@ -29,15 +22,6 @@ pub struct RuntimeState { pub constants: Constants, } -pub struct CodeState { - pub bytecode: Vec, - pub sources: Vec, - pub spans: Vec<(usize, rnix::TextRange)>, - pub thunk_count: usize, - pub global_env: HashMap, - pub current_scope_slot: Option, -} - pub struct Evaluator { pub runtime: RuntimeState, pub code: CodeState, @@ -52,37 +36,25 @@ impl Default for Evaluator { impl Evaluator { pub fn new() -> Self { let mut strings = DefaultStringInterner::new(); - let global_env = fix_ir::new_global_env(&mut strings); - let mut bytecode = Vec::with_capacity(PrimOpPhase::Illegal as usize * 2); - for phase in 0..=PrimOpPhase::Illegal as u8 { - bytecode.push(Op::DispatchPrimOp as u8); - bytecode.push(phase); - } + let code = CodeState::new(&mut strings); Self { runtime: RuntimeState { strings, constants: Constants::default(), }, - code: CodeState { - sources: Vec::new(), - spans: Vec::new(), - thunk_count: 0, - bytecode, - global_env, - current_scope_slot: None, - }, + code, } } - pub fn eval(&mut self, source: Source) -> Result { + pub fn eval(&mut self, source: Source) -> Result { self.do_eval(source, None, ForceMode::AsIs) } - pub fn eval_shallow(&mut self, source: Source) -> Result { + pub fn eval_shallow(&mut self, source: Source) -> Result { self.do_eval(source, None, ForceMode::Shallow) } - pub fn eval_deep(&mut self, source: Source) -> Result { + pub fn eval_deep(&mut self, source: Source) -> Result { self.do_eval(source, None, ForceMode::Deep) } @@ -90,7 +62,7 @@ impl Evaluator { &mut self, source: Source, scope: &HashSet, - ) -> Result { + ) -> Result { self.do_eval(source, Some(ExtraScope::Repl(scope)), ForceMode::Shallow) } @@ -99,14 +71,10 @@ impl Evaluator { source: Source, extra_scope: Option>, force_mode: ForceMode, - ) -> Result { - let ip = { - let mut compiler = CompilerCtx { - code: &mut self.code, - runtime: &mut self.runtime, - }; - compiler.compile_bytecode(source, extra_scope)? - }; + ) -> Result { + let ip = self + .code + .compile_bytecode(source, extra_scope, &mut self.runtime)?; Vm::run(self, ip, force_mode) } @@ -115,16 +83,12 @@ impl Evaluator { _ident: &str, _expr: &str, _scope: &mut HashSet, - ) -> Result { + ) -> Result { todo!("add_binding") } pub fn compile_bytecode(&mut self, source: Source) -> Result { - let mut compiler = CompilerCtx { - code: &mut self.code, - runtime: &mut self.runtime, - }; - compiler.compile_bytecode(source, None) + self.code.compile_bytecode(source, None, &mut self.runtime) } pub fn disassemble_colored(&self, ip: InstructionPtr) -> String { @@ -149,151 +113,12 @@ impl VmRuntimeCtx for RuntimeState { } } -impl VmCode for CodeState { - fn bytecode(&self) -> &[u8] { - &self.bytecode - } - fn compile_with_scope( - &mut self, - source: Source, - extra_scope: Option, - runtime: &mut impl VmRuntimeCtx, - ) -> Result { - let mut compiler = CompilerCtx { - code: self, - runtime, - }; - let extra = extra_scope.map(|s| match s { - fix_abstract_vm::ExtraScope::ScopedImport { keys, slot_id } => { - ExtraScope::ScopedImport { keys, slot_id } - } - }); - compiler.compile_bytecode(source, extra) - } -} - impl VmContext for Evaluator { fn split(&mut self) -> (&mut impl VmCode, &mut impl VmRuntimeCtx) { (&mut self.code, &mut self.runtime) } } -struct CompilerCtx<'a, R: VmRuntimeCtx> { - code: &'a mut CodeState, - runtime: &'a mut R, -} - -impl<'a, R: VmRuntimeCtx> CompilerCtx<'a, R> { - fn compile_bytecode( - &mut self, - source: Source, - extra_scope: Option, - ) -> Result { - let prev_scope_slot = self.code.current_scope_slot; - self.code.current_scope_slot = match &extra_scope { - Some(ExtraScope::ScopedImport { slot_id, .. }) => Some(*slot_id), - _ => None, - }; - let result = (|| -> Result { - let root = self.downgrade(source, extra_scope)?; - let ip = fix_codegen::compile_bytecode(root.as_ref(), self); - Ok(ip) - })(); - self.code.current_scope_slot = prev_scope_slot; - result - } - - fn downgrade(&mut self, source: Source, extra_scope: Option) -> Result { - tracing::debug!("Parsing Nix expression"); - - self.code.sources.push(source.clone()); - - let root = rnix::Root::parse(&source.src); - handle_parse_error(root.errors(), source.clone()).map_or(Ok(()), Err)?; - - tracing::debug!("Downgrading Nix expression"); - let expr = root - .tree() - .expr() - .ok_or_else(|| Error::parse_error("unexpected EOF".into()))?; - let bump = Bump::new(); - GhostToken::new(|token| { - let downgrade_ctx = DowngradeCtx::new( - &bump, - token, - self.runtime, - &self.code.global_env, - extra_scope.map(Into::into), - &mut self.code.thunk_count, - source, - ); - let ir = downgrade_ctx.downgrade_toplevel(expr)?; - let ir = unsafe { std::mem::transmute::, RawIrRef<'static>>(ir) }; - Ok(OwnedIr { _bump: bump, ir }) - }) - } -} - -impl<'a, R: VmRuntimeCtx> BytecodeContext for CompilerCtx<'a, R> { - fn intern_string(&mut self, s: &str) -> StringId { - self.runtime.intern_string(s) - } - - fn register_span(&mut self, range: rnix::TextRange) -> u32 { - let id = self.code.spans.len(); - let source_id = self - .code - .sources - .len() - .checked_sub(1) - .expect("current_source not set"); - self.code.spans.push((source_id, range)); - id as u32 - } - - fn get_code(&self) -> &[u8] { - &self.code.bytecode - } - - fn get_code_mut(&mut self) -> &mut Vec { - &mut self.code.bytecode - } - - fn add_constant(&mut self, val: fix_codegen::Const) -> u32 { - use fix_codegen::Const::*; - let val = match val { - Smi(x) => StaticValue::new_inline(x), - Float(x) => StaticValue::new_float(x), - Bool(x) => StaticValue::new_inline(x), - String(x) => StaticValue::new_inline(x), - Path(_) => todo!("path value type"), - PrimOp { - id, - arity, - dispatch_ip, - } => StaticValue::new_primop(id, arity, dispatch_ip), - Null => StaticValue::default(), - }; - self.runtime.add_const(val) - } - - fn current_source_dir(&mut self) -> StringId { - let dir = self - .code - .sources - .last() - .expect("current_source not set") - .get_dir() - .to_string_lossy() - .into_owned(); - self.runtime.intern_string(dir) - } - - fn current_scope_slot(&self) -> Option { - self.code.current_scope_slot - } -} - #[derive(Default)] pub struct Constants { data: Vec, @@ -315,402 +140,6 @@ impl Constants { } } -fn parse_error_span(error: &rnix::ParseError) -> Option { - 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, - source: Source, -) -> Option> { - for err in errors { - if let Some(span) = parse_error_span(err) { - return Some( - Error::parse_error(err.to_string()) - .with_source(source) - .with_span(span), - ); - } - } - None -} - -struct DowngradeCtx<'ctx, 'id, 'ir, R: VmRuntimeCtx> { - bump: &'ir Bump, - token: GhostToken<'id>, - runtime: &'ctx mut R, - source: Source, - scopes: Vec>, - with_stack: Vec>, - arg_count: u32, - thunk_count: &'ctx mut usize, - thunk_scopes: Vec>, -} - -impl<'ctx, 'id, 'ir, R: VmRuntimeCtx> DowngradeCtx<'ctx, 'id, 'ir, R> { - fn new( - bump: &'ir Bump, - token: GhostToken<'id>, - runtime: &'ctx mut R, - global: &'ctx HashMap, - extra_scope: Option>, - thunk_count: &'ctx mut usize, - source: Source, - ) -> Self { - Self { - bump, - token, - runtime, - source, - scopes: std::iter::once(Scope::Global(global)) - .chain(extra_scope) - .collect(), - thunk_count, - arg_count: 0, - with_stack: Vec::new(), - thunk_scopes: vec![ThunkScope::new_in(bump)], - } - } -} - -impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir> - for DowngradeCtx<'ctx, 'id, 'ir, R> -{ - fn new_expr(&self, expr: Ir<'ir, GhostRoRef<'id, 'ir>>) -> GhostRoIrRef<'id, 'ir> { - self.bump.alloc(GhostCell::new(expr).into()) - } - - fn maybe_thunk(&mut self, ir: GhostRoIrRef<'id, 'ir>) -> GhostRoMaybeThunkRef<'id, 'ir> { - use MaybeThunk::*; - let expr = (|| { - let expr = match *ir.borrow(&self.token) { - Ir::Builtin(x) => Builtin(x), - Ir::Int(x) => Int(x), - Ir::Float(x) => Float(x), - Ir::Bool(x) => Bool(x), - Ir::Str(x) => Str(x), - Ir::Arg { layer } => Arg { layer }, - Ir::Builtins => Builtins, - Ir::Null => Null, - Ir::MaybeThunk(thunk) => return Some(thunk), - _ => return None, - }; - Some(self.bump.alloc(GhostCell::new(expr).into())) - })(); - if let Some(thunk) = expr { - return thunk; - } - let id = ThunkId(*self.thunk_count); - *self.thunk_count = self.thunk_count.checked_add(1).expect("thunk id overflow"); - self.thunk_scopes - .last_mut() - .expect("no active cache scope") - .add_binding(id, ir); - self.bump.alloc(GhostCell::new(Thunk(id)).into()) - } - - fn intern_string(&mut self, sym: impl AsRef) -> StringId { - self.runtime.intern_string(sym) - } - - fn resolve_sym(&self, id: StringId) -> Symbol<'_> { - self.runtime.resolve_string(id).into() - } - - fn lookup( - &mut self, - sym: StringId, - span: rnix::TextRange, - ) -> Result> { - for scope in self.scopes.iter().rev() { - match scope { - &Scope::Global(global_scope) => { - if let Some(expr) = global_scope.get(&sym) { - return Ok(expr.into()); - } - } - &Scope::Repl(repl_bindings) => { - if repl_bindings.contains(&sym) { - return Ok(self - .bump - .alloc(GhostCell::new(MaybeThunk::ReplBinding(sym)).into())); - } - } - Scope::ScopedImport { keys, .. } => { - if keys.contains(&sym) { - return Ok(self - .bump - .alloc(GhostCell::new(MaybeThunk::ScopedImportBinding(sym)).into())); - } - } - Scope::Let(let_scope) => { - if let Some(&expr) = let_scope.get(&sym) { - return Ok(expr.into()); - } - } - &Scope::Param { - sym: param_sym, - abs_layer, - } => { - if param_sym == sym { - let layers: u8 = - self.thunk_scopes.len().try_into().expect("scope too deep!"); - let layer = layers - abs_layer; - return Ok(self - .bump - .alloc(GhostCell::new(MaybeThunk::Arg { layer }).into())); - } - } - } - } - - if !self.with_stack.is_empty() { - let id = ThunkId(*self.thunk_count); - *self.thunk_count = self.thunk_count.checked_add(1).expect("thunk id overflow"); - let mut namespaces = - bumpalo::collections::Vec::with_capacity_in(self.with_stack.len(), self.bump); - namespaces.extend(self.with_stack.iter().rev().copied()); - let body = self - .bump - .alloc(GhostCell::new(Ir::WithLookup { sym, namespaces }).into()); - self.thunk_scopes - .last_mut() - .expect("no active thunk scope") - .add_binding(id, body); - Ok(self - .bump - .alloc(GhostCell::new(MaybeThunk::Thunk(id)).into())) - } else { - Err(Error::downgrade_error( - format!("'{}' not found", self.resolve_sym(sym)), - self.get_current_source(), - span, - )) - } - } - - fn get_current_source(&self) -> Source { - self.source.clone() - } - - fn with_let_scope(&mut self, keys: &[StringId], f: F) -> Result - where - F: FnOnce( - &mut Self, - ) -> Result<( - bumpalo::collections::Vec<'ir, GhostRoMaybeThunkRef<'id, 'ir>>, - Ret, - )>, - { - let base = *self.thunk_count; - *self.thunk_count = self - .thunk_count - .checked_add(keys.len()) - .expect("thunk id overflow"); - let handles = (base..base + keys.len()) - .map(|id| { - &*self - .bump - .alloc(GhostCell::new(MaybeThunk::Thunk(ThunkId(id)))) - }) - .collect::>(); - let scope = keys.iter().copied().zip(handles.iter().copied()).collect(); - self.scopes.push(Scope::Let(scope)); - let (vals, ret) = { f(self)? }; - self.scopes.pop(); - assert_eq!(keys.len(), vals.len()); - let scope = self.thunk_scopes.last_mut().expect("no active thunk scope"); - for (i, (val, handle)) in vals.into_iter().zip(handles).enumerate() { - let thunk = *val.borrow(&self.token); - *handle.borrow_mut(&mut self.token) = thunk; - let id = ThunkId(base + i); - let ir_ref = self - .bump - .alloc(GhostCell::new(Ir::MaybeThunk(handle.into())).into()); - scope.add_binding(id, ir_ref); - } - Ok(ret) - } - - fn with_param_scope(&mut self, sym: StringId, f: F) -> Ret - where - F: FnOnce(&mut Self) -> Ret, - { - self.scopes.push(Scope::Param { - sym, - abs_layer: self.thunk_scopes.len().try_into().expect("scope too deep!"), - }); - let mut guard = ScopeGuard { ctx: self }; - f(guard.as_ctx()) - } - - fn with_with_scope(&mut self, namespace: GhostRoMaybeThunkRef<'id, 'ir>, f: F) -> Ret - where - F: FnOnce(&mut Self) -> Ret, - { - self.with_stack.push(namespace); - let ret = f(self); - self.with_stack.pop(); - ret - } - - fn with_thunk_scope( - &mut self, - f: F, - ) -> ( - Ret, - bumpalo::collections::Vec<'ir, (ThunkId, GhostRoIrRef<'id, 'ir>)>, - ) - where - F: FnOnce(&mut Self) -> Ret, - { - if self.thunk_scopes.len() == u8::MAX as usize { - panic!("scope too deep!"); - } - self.thunk_scopes.push(ThunkScope::new_in(self.bump)); - let ret = f(self); - ( - ret, - self.thunk_scopes - .pop() - .expect("no thunk scope left???") - .bindings, - ) - } - - fn bump(&self) -> &'ir bumpalo::Bump { - self.bump - } -} - -impl<'id, 'ir, 'ctx: 'ir, R: VmRuntimeCtx> DowngradeCtx<'ctx, 'id, 'ir, R> { - fn downgrade_toplevel(mut self, root: rnix::ast::Expr) -> Result> { - let body = root.downgrade(&mut self)?; - let thunks = self - .thunk_scopes - .pop() - .expect("no thunk scope left???") - .bindings; - Ok(Ir::freeze( - self.new_expr(Ir::TopLevel { body, thunks }), - self.token, - )) - } -} - -struct ThunkScope<'id, 'ir> { - bindings: bumpalo::collections::Vec<'ir, (ThunkId, GhostRoIrRef<'id, 'ir>)>, -} - -impl<'id, 'ir> ThunkScope<'id, 'ir> { - fn new_in(bump: &'ir Bump) -> Self { - Self { - bindings: bumpalo::collections::Vec::new_in(bump), - } - } - - fn add_binding(&mut self, id: ThunkId, ir: GhostRoIrRef<'id, 'ir>) { - self.bindings.push((id, ir)); - } - - fn extend_bindings( - &mut self, - iter: impl IntoIterator)>, - ) { - self.bindings.extend(iter); - } -} - -enum Scope<'ctx, 'id, 'ir> { - Global(&'ctx HashMap), - Repl(&'ctx HashSet), - ScopedImport { - keys: HashSet, - slot_id: u32, - }, - Let(HashMap>), - Param { - sym: StringId, - abs_layer: u8, - }, -} - -pub enum ExtraScope<'ctx> { - Repl(&'ctx HashSet), - ScopedImport { - keys: HashSet, - slot_id: u32, - }, -} - -impl<'ctx> From> for Scope<'ctx, '_, '_> { - fn from(value: ExtraScope<'ctx>) -> Self { - use ExtraScope::*; - match value { - ScopedImport { keys, slot_id } => Scope::ScopedImport { keys, slot_id }, - Repl(scope) => Scope::Repl(scope), - } - } -} - -struct ScopeGuard<'a, 'ctx, 'id, 'ir, R: VmRuntimeCtx> { - ctx: &'a mut DowngradeCtx<'ctx, 'id, 'ir, R>, -} - -impl<'a, 'ctx, 'id, 'ir, R: VmRuntimeCtx> Drop for ScopeGuard<'a, 'ctx, 'id, 'ir, R> { - fn drop(&mut self) { - self.ctx.scopes.pop(); - } -} - -impl<'a, 'ctx, 'id, 'ir, R: VmRuntimeCtx> ScopeGuard<'a, 'ctx, 'id, 'ir, R> { - fn as_ctx(&mut self) -> &mut DowngradeCtx<'ctx, 'id, 'ir, R> { - self.ctx - } -} - -struct OwnedIr { - _bump: Bump, - ir: RawIrRef<'static>, -} - -impl OwnedIr { - /// # Safety - /// `ir` must be an allocation backed by `bump`. The reference's - /// lifetime is extended to `'static` as a placeholder; the stored IR - /// must only be re-borrowed via [`OwnedIr::as_ref`], which narrows - /// the lifetime back to that of the `&self` borrow. Moving `bump` - /// into the struct keeps the underlying allocation live for the - /// lifetime of the `OwnedIr`. - unsafe fn new(ir: RawIrRef<'_>, bump: Bump) -> Self { - Self { - _bump: bump, - // SAFETY: see function docs - caller guarantees `ir` is in `bump`, - // and the `'static` lifetime is a placeholder narrowed by `as_ref`. - ir: unsafe { std::mem::transmute::, RawIrRef<'static>>(ir) }, - } - } - - fn as_ref<'ir>(&'ir self) -> RawIrRef<'ir> { - // SAFETY: narrows the placeholder `'static` lifetime stored in - // `self.ir` down to `'ir = &'ir self`. Lifetime shortening is - // logically sound for covariant positions; the transmute is only - // needed because `RawRef<'ir>` carries `'ir` through a GAT - // (`Ref::Ref`), which prevents the compiler from inferring - // covariance automatically. The bump arena that backs the IR is - // owned by `self._bump`, so the data is live for at least `'ir`. - unsafe { std::mem::transmute::, RawIrRef<'ir>>(self.ir) } - } -} - impl DisassemblerContext for Evaluator { fn get_code(&self) -> &[u8] { &self.code.bytecode diff --git a/fix/tests/tests/derivation.rs b/fix/tests/tests/derivation.rs index 9f33baf..be37749 100644 --- a/fix/tests/tests/derivation.rs +++ b/fix/tests/tests/derivation.rs @@ -1,4 +1,4 @@ -use fix_common::Value; +use fix_lang::Value; use crate::utils::{eval_deep, eval_deep_result}; diff --git a/fix/tests/tests/io_operations.rs b/fix/tests/tests/io_operations.rs index 1d30545..2ea0002 100644 --- a/fix/tests/tests/io_operations.rs +++ b/fix/tests/tests/io_operations.rs @@ -1,6 +1,6 @@ use fix::Evaluator; -use fix_common::Value; use fix_error::Source; +use fix_lang::Value; use crate::utils::{eval, eval_result}; diff --git a/fix/tests/tests/lang.rs b/fix/tests/tests/lang.rs index a4f8f19..977f4a7 100644 --- a/fix/tests/tests/lang.rs +++ b/fix/tests/tests/lang.rs @@ -3,8 +3,8 @@ use std::path::PathBuf; use fix::Evaluator; -use fix_common::Value; use fix_error::{Source, SourceType}; +use fix_lang::Value; fn get_lang_dir() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/tests/lang") diff --git a/fix/tests/tests/string_context.rs b/fix/tests/tests/string_context.rs index 689ae46..99b6465 100644 --- a/fix/tests/tests/string_context.rs +++ b/fix/tests/tests/string_context.rs @@ -1,5 +1,5 @@ use fix::Evaluator; -use fix_common::Value; +use fix_lang::Value; use crate::utils::eval_result; diff --git a/fix/tests/tests/utils.rs b/fix/tests/tests/utils.rs index 7a74009..acbbda9 100644 --- a/fix/tests/tests/utils.rs +++ b/fix/tests/tests/utils.rs @@ -1,8 +1,8 @@ #![allow(dead_code)] use fix::Evaluator; -use fix_common::Value; use fix_error::{Result, Source}; +use fix_lang::Value; pub fn eval(expr: &str) -> Value { Evaluator::new()