refactor: abstract VM
This commit is contained in:
Generated
+37
-1
@@ -449,6 +449,7 @@ dependencies = [
|
|||||||
"clap",
|
"clap",
|
||||||
"criterion",
|
"criterion",
|
||||||
"ere",
|
"ere",
|
||||||
|
"fix-abstract-vm",
|
||||||
"fix-builtins",
|
"fix-builtins",
|
||||||
"fix-codegen",
|
"fix-codegen",
|
||||||
"fix-common",
|
"fix-common",
|
||||||
@@ -469,6 +470,23 @@ dependencies = [
|
|||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[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]]
|
[[package]]
|
||||||
name = "fix-builtins"
|
name = "fix-builtins"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -526,15 +544,33 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fix-vm"
|
name = "fix-primops"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"fix-abstract-vm",
|
||||||
"fix-builtins",
|
"fix-builtins",
|
||||||
"fix-codegen",
|
"fix-codegen",
|
||||||
"fix-common",
|
"fix-common",
|
||||||
"fix-error",
|
"fix-error",
|
||||||
"gc-arena",
|
"gc-arena",
|
||||||
"hashbrown 0.16.1",
|
"hashbrown 0.16.1",
|
||||||
|
"num_enum",
|
||||||
|
"smallvec",
|
||||||
|
"string-interner",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fix-vm"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"fix-abstract-vm",
|
||||||
|
"fix-builtins",
|
||||||
|
"fix-codegen",
|
||||||
|
"fix-common",
|
||||||
|
"fix-error",
|
||||||
|
"fix-primops",
|
||||||
|
"gc-arena",
|
||||||
|
"hashbrown 0.16.1",
|
||||||
"likely_stable",
|
"likely_stable",
|
||||||
"num_enum",
|
"num_enum",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
|
|||||||
@@ -2,11 +2,13 @@
|
|||||||
resolver = "3"
|
resolver = "3"
|
||||||
members = [
|
members = [
|
||||||
"fix",
|
"fix",
|
||||||
|
"fix-abstract-vm",
|
||||||
"fix-builtins",
|
"fix-builtins",
|
||||||
"fix-codegen",
|
"fix-codegen",
|
||||||
"fix-common",
|
"fix-common",
|
||||||
"fix-error",
|
"fix-error",
|
||||||
"fix-ir",
|
"fix-ir",
|
||||||
|
"fix-primops",
|
||||||
"fix-vm",
|
"fix-vm",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
[package]
|
||||||
|
name = "fix-abstract-vm"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
gc-arena = { workspace = true }
|
||||||
|
hashbrown = { workspace = true }
|
||||||
|
num_enum = { workspace = true }
|
||||||
|
smallvec = { workspace = true }
|
||||||
|
string-interner = { workspace = true }
|
||||||
|
likely_stable = { workspace = true }
|
||||||
|
sptr = "0.3"
|
||||||
|
|
||||||
|
fix-builtins = { path = "../fix-builtins" }
|
||||||
|
fix-codegen = { path = "../fix-codegen" }
|
||||||
|
fix-common = { path = "../fix-common" }
|
||||||
|
fix-error = { path = "../fix-error" }
|
||||||
@@ -24,7 +24,7 @@ impl<T: Default + Copy, const N: usize> ArrayExt<N> for [T; N] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait RawStore: Sized {
|
pub trait RawStore: Sized {
|
||||||
fn to_val(self, value: &mut Value);
|
fn to_val(self, value: &mut Value);
|
||||||
fn from_val(value: &Value) -> Self;
|
fn from_val(value: &Value) -> Self;
|
||||||
}
|
}
|
||||||
@@ -157,24 +157,24 @@ enum TagVal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
pub(crate) struct RawTag(TagVal);
|
pub struct RawTag(TagVal);
|
||||||
|
|
||||||
impl RawTag {
|
impl RawTag {
|
||||||
pub(crate) const P1: RawTag = RawTag(TagVal::_P1);
|
pub const P1: RawTag = RawTag(TagVal::_P1);
|
||||||
pub(crate) const P2: RawTag = RawTag(TagVal::_P2);
|
pub const P2: RawTag = RawTag(TagVal::_P2);
|
||||||
pub(crate) const P3: RawTag = RawTag(TagVal::_P3);
|
pub const P3: RawTag = RawTag(TagVal::_P3);
|
||||||
pub(crate) const P4: RawTag = RawTag(TagVal::_P4);
|
pub const P4: RawTag = RawTag(TagVal::_P4);
|
||||||
pub(crate) const P5: RawTag = RawTag(TagVal::_P5);
|
pub const P5: RawTag = RawTag(TagVal::_P5);
|
||||||
pub(crate) const P6: RawTag = RawTag(TagVal::_P6);
|
pub const P6: RawTag = RawTag(TagVal::_P6);
|
||||||
pub(crate) const P7: RawTag = RawTag(TagVal::_P7);
|
pub const P7: RawTag = RawTag(TagVal::_P7);
|
||||||
|
|
||||||
pub(crate) const N1: RawTag = RawTag(TagVal::_N1);
|
pub const N1: RawTag = RawTag(TagVal::_N1);
|
||||||
pub(crate) const N2: RawTag = RawTag(TagVal::_N2);
|
pub const N2: RawTag = RawTag(TagVal::_N2);
|
||||||
pub(crate) const N3: RawTag = RawTag(TagVal::_N3);
|
pub const N3: RawTag = RawTag(TagVal::_N3);
|
||||||
pub(crate) const N4: RawTag = RawTag(TagVal::_N4);
|
pub const N4: RawTag = RawTag(TagVal::_N4);
|
||||||
pub(crate) const N5: RawTag = RawTag(TagVal::_N5);
|
pub const N5: RawTag = RawTag(TagVal::_N5);
|
||||||
pub(crate) const N6: RawTag = RawTag(TagVal::_N6);
|
pub const N6: RawTag = RawTag(TagVal::_N6);
|
||||||
pub(crate) const N7: RawTag = RawTag(TagVal::_N7);
|
pub const N7: RawTag = RawTag(TagVal::_N7);
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
@@ -260,7 +260,7 @@ impl RawTag {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) const fn neg_val(self) -> (bool, u8) {
|
pub const fn neg_val(self) -> (bool, u8) {
|
||||||
match self.0 {
|
match self.0 {
|
||||||
TagVal::_P1 => (false, 1),
|
TagVal::_P1 => (false, 1),
|
||||||
TagVal::_P2 => (false, 2),
|
TagVal::_P2 => (false, 2),
|
||||||
@@ -323,7 +323,7 @@ impl Header {
|
|||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
#[repr(C, align(8))]
|
#[repr(C, align(8))]
|
||||||
pub(crate) struct Value {
|
pub struct Value {
|
||||||
#[cfg(target_endian = "big")]
|
#[cfg(target_endian = "big")]
|
||||||
header: Header,
|
header: Header,
|
||||||
data: [u8; 6],
|
data: [u8; 6],
|
||||||
@@ -373,7 +373,7 @@ impl Value {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn data(&self) -> &[u8; 6] {
|
pub fn data(&self) -> &[u8; 6] {
|
||||||
&self.data
|
&self.data
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use fix_codegen::OperandType;
|
use fix_codegen::OperandType;
|
||||||
use fix_common::StringId;
|
use fix_common::StringId;
|
||||||
use num_enum::TryFromPrimitive;
|
use num_enum::TryFromPrimitive;
|
||||||
@@ -5,15 +7,14 @@ use string_interner::Symbol as _;
|
|||||||
|
|
||||||
use crate::{OperandData, VmRuntimeCtx};
|
use crate::{OperandData, VmRuntimeCtx};
|
||||||
|
|
||||||
pub(crate) struct BytecodeReader<'a> {
|
pub struct BytecodeReader<'a> {
|
||||||
bytecode: &'a [u8],
|
bytecode: &'a [u8],
|
||||||
pc: usize,
|
pc: usize,
|
||||||
inst_start_pc: usize,
|
inst_start_pc: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> BytecodeReader<'a> {
|
impl<'a> BytecodeReader<'a> {
|
||||||
#[cfg_attr(feature = "tailcall", allow(dead_code))]
|
pub fn new(bytecode: &'a [u8], pc: usize) -> Self {
|
||||||
pub(crate) fn new(bytecode: &'a [u8], pc: usize) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
bytecode,
|
bytecode,
|
||||||
pc,
|
pc,
|
||||||
@@ -22,8 +23,7 @@ impl<'a> BytecodeReader<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[cfg_attr(not(feature = "tailcall"), allow(dead_code))]
|
pub fn from_after_op(bytecode: &'a [u8], inst_start_pc: usize) -> Self {
|
||||||
pub(crate) fn from_after_op(bytecode: &'a [u8], inst_start_pc: usize) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
bytecode,
|
bytecode,
|
||||||
pc: inst_start_pc + 1,
|
pc: inst_start_pc + 1,
|
||||||
@@ -42,8 +42,7 @@ impl<'a> BytecodeReader<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[cfg_attr(feature = "tailcall", allow(dead_code))]
|
pub fn read_op(&mut self) -> fix_codegen::Op {
|
||||||
pub(crate) fn read_op(&mut self) -> fix_codegen::Op {
|
|
||||||
use fix_codegen::Op;
|
use fix_codegen::Op;
|
||||||
self.inst_start_pc = self.pc;
|
self.inst_start_pc = self.pc;
|
||||||
let byte = self.bytecode[self.pc];
|
let byte = self.bytecode[self.pc];
|
||||||
@@ -55,46 +54,46 @@ impl<'a> BytecodeReader<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn read_u8(&mut self) -> u8 {
|
pub fn read_u8(&mut self) -> u8 {
|
||||||
let val = self.bytecode[self.pc];
|
let val = self.bytecode[self.pc];
|
||||||
self.pc += 1;
|
self.pc += 1;
|
||||||
val
|
val
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn read_u16(&mut self) -> u16 {
|
pub fn read_u16(&mut self) -> u16 {
|
||||||
u16::from_le_bytes(self.read_array())
|
u16::from_le_bytes(self.read_array())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn read_u32(&mut self) -> u32 {
|
pub fn read_u32(&mut self) -> u32 {
|
||||||
u32::from_le_bytes(self.read_array())
|
u32::from_le_bytes(self.read_array())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn read_i32(&mut self) -> i32 {
|
pub fn read_i32(&mut self) -> i32 {
|
||||||
i32::from_le_bytes(self.read_array())
|
i32::from_le_bytes(self.read_array())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn read_i64(&mut self) -> i64 {
|
pub fn read_i64(&mut self) -> i64 {
|
||||||
i64::from_le_bytes(self.read_array())
|
i64::from_le_bytes(self.read_array())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn read_f64(&mut self) -> f64 {
|
pub fn read_f64(&mut self) -> f64 {
|
||||||
f64::from_le_bytes(self.read_array())
|
f64::from_le_bytes(self.read_array())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn read_string_id(&mut self) -> StringId {
|
pub fn read_string_id(&mut self) -> StringId {
|
||||||
let raw = self.read_u32();
|
let raw = self.read_u32();
|
||||||
#[allow(clippy::unwrap_used)]
|
#[allow(clippy::unwrap_used)]
|
||||||
StringId(string_interner::symbol::SymbolU32::try_from_usize(raw as usize).unwrap())
|
StringId(string_interner::symbol::SymbolU32::try_from_usize(raw as usize).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn read_operand_data<C: VmRuntimeCtx>(&mut self, ctx: &C) -> OperandData {
|
pub fn read_operand_data<C: VmRuntimeCtx>(&mut self, ctx: &C) -> OperandData {
|
||||||
let tag = self.read_u8();
|
let tag = self.read_u8();
|
||||||
let Ok(ty) = OperandType::try_from_primitive(tag)
|
let Ok(ty) = OperandType::try_from_primitive(tag)
|
||||||
.map_err(|err| panic!("unknown operand tag: {:#04x}", err.number));
|
.map_err(|err| panic!("unknown operand tag: {:#04x}", err.number));
|
||||||
@@ -129,15 +128,15 @@ impl<'a> BytecodeReader<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn pc(&self) -> usize {
|
pub fn pc(&self) -> usize {
|
||||||
self.pc
|
self.pc
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn set_pc(&mut self, pc: usize) {
|
pub fn set_pc(&mut self, pc: usize) {
|
||||||
self.pc = pc;
|
self.pc = pc;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn inst_start_pc(&self) -> usize {
|
pub fn inst_start_pc(&self) -> usize {
|
||||||
self.inst_start_pc
|
self.inst_start_pc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,18 +1,20 @@
|
|||||||
use fix_common::StringId;
|
use fix_common::StringId;
|
||||||
use gc_arena::{Gc, Mutation};
|
use gc_arena::{Gc, Mutation};
|
||||||
|
|
||||||
use crate::value::*;
|
use crate::{
|
||||||
use crate::{Break, BytecodeReader, NixNum, Step, Vm};
|
AttrSet, Break, BytecodeReader, Closure, List, Machine, NixNum, NixString, NixType, Null,
|
||||||
|
PrimOp, PrimOpApp, Step, StrictValue,
|
||||||
|
};
|
||||||
|
|
||||||
pub(crate) trait Forced<'gc>: Sized {
|
pub trait Forced<'gc>: Sized {
|
||||||
const WIDTH: usize;
|
const WIDTH: usize;
|
||||||
|
|
||||||
/// Force and type-check the `WIDTH` slots starting at `base_depth` from
|
/// Force and type-check the `WIDTH` slots starting at `base_depth` from
|
||||||
/// TOS, deepest-first. If a slot holds a thunk, enter it and return
|
/// TOS, deepest-first. If a slot holds a thunk, enter it and return
|
||||||
/// `Break::Force`. If a slot holds a value of the wrong type, call
|
/// `Break::Force`. If a slot holds a value of the wrong type, call
|
||||||
/// `finish_type_err` and return `Break::Done`.
|
/// `finish_type_err` and return `Break::Done`.
|
||||||
fn force_and_check(
|
fn force_and_check<M: Machine<'gc>>(
|
||||||
vm: &mut Vm<'gc>,
|
m: &mut M,
|
||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
base_depth: usize,
|
base_depth: usize,
|
||||||
@@ -22,26 +24,26 @@ pub(crate) trait Forced<'gc>: Sized {
|
|||||||
/// After `force_and_check` returned `Continue`, pop `WIDTH` slots
|
/// After `force_and_check` returned `Continue`, pop `WIDTH` slots
|
||||||
/// (TOS first) and convert. Type assertions are infallible because
|
/// (TOS first) and convert. Type assertions are infallible because
|
||||||
/// `force_and_check` already validated every slot.
|
/// `force_and_check` already validated every slot.
|
||||||
fn pop_converted(vm: &mut Vm<'gc>) -> Self;
|
fn pop_converted<M: Machine<'gc>>(m: &mut M) -> Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'gc> Forced<'gc> for StrictValue<'gc> {
|
impl<'gc> Forced<'gc> for StrictValue<'gc> {
|
||||||
const WIDTH: usize = 1;
|
const WIDTH: usize = 1;
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn force_and_check(
|
fn force_and_check<M: Machine<'gc>>(
|
||||||
vm: &mut Vm<'gc>,
|
m: &mut M,
|
||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
base_depth: usize,
|
base_depth: usize,
|
||||||
resume_pc: usize,
|
resume_pc: usize,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
vm.force_slot_to_pc(base_depth, reader, mc, resume_pc)
|
m.force_slot_to_pc(base_depth, reader, mc, resume_pc)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn pop_converted(vm: &mut Vm<'gc>) -> Self {
|
fn pop_converted<M: Machine<'gc>>(m: &mut M) -> Self {
|
||||||
vm.pop_forced()
|
m.pop_forced()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,25 +54,25 @@ macro_rules! impl_forced_inline {
|
|||||||
const WIDTH: usize = 1;
|
const WIDTH: usize = 1;
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn force_and_check(
|
fn force_and_check<M: Machine<'gc>>(
|
||||||
vm: &mut Vm<'gc>,
|
m: &mut M,
|
||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
base_depth: usize,
|
base_depth: usize,
|
||||||
resume_pc: usize,
|
resume_pc: usize,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
vm.force_slot_to_pc(base_depth, reader, mc, resume_pc)?;
|
m.force_slot_to_pc(base_depth, reader, mc, resume_pc)?;
|
||||||
let v = vm.peek_forced(base_depth);
|
let v = m.peek_forced(base_depth);
|
||||||
if v.as_inline::<$ty>().is_none() {
|
if v.as_inline::<$ty>().is_none() {
|
||||||
let _: Step = vm.finish_type_err($nix_ty, v.ty());
|
let _: Step = m.finish_type_err($nix_ty, v.ty());
|
||||||
return Step::Break(Break::Done);
|
return Step::Break(Break::Done);
|
||||||
}
|
}
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn pop_converted(vm: &mut Vm<'gc>) -> Self {
|
fn pop_converted<M: Machine<'gc>>(m: &mut M) -> Self {
|
||||||
vm.pop_forced()
|
m.pop_forced()
|
||||||
.as_inline::<$ty>()
|
.as_inline::<$ty>()
|
||||||
.expect("type checked in force_and_check")
|
.expect("type checked in force_and_check")
|
||||||
}
|
}
|
||||||
@@ -86,25 +88,25 @@ macro_rules! impl_forced_gc {
|
|||||||
const WIDTH: usize = 1;
|
const WIDTH: usize = 1;
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn force_and_check(
|
fn force_and_check<M: Machine<'gc>>(
|
||||||
vm: &mut Vm<'gc>,
|
m: &mut M,
|
||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
base_depth: usize,
|
base_depth: usize,
|
||||||
resume_pc: usize,
|
resume_pc: usize,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
vm.force_slot_to_pc(base_depth, reader, mc, resume_pc)?;
|
m.force_slot_to_pc(base_depth, reader, mc, resume_pc)?;
|
||||||
let v = vm.peek_forced(base_depth);
|
let v = m.peek_forced(base_depth);
|
||||||
if v.as_gc::<$ty>().is_none() {
|
if v.as_gc::<$ty>().is_none() {
|
||||||
let _: Step = vm.finish_type_err($nix_ty, v.ty());
|
let _: Step = m.finish_type_err($nix_ty, v.ty());
|
||||||
return Step::Break(Break::Done);
|
return Step::Break(Break::Done);
|
||||||
}
|
}
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn pop_converted(vm: &mut Vm<'gc>) -> Self {
|
fn pop_converted<M: Machine<'gc>>(m: &mut M) -> Self {
|
||||||
vm.pop_forced()
|
m.pop_forced()
|
||||||
.as_gc::<$ty>()
|
.as_gc::<$ty>()
|
||||||
.expect("type checked in force_and_check")
|
.expect("type checked in force_and_check")
|
||||||
}
|
}
|
||||||
@@ -134,25 +136,25 @@ impl<'gc> Forced<'gc> for NixNum {
|
|||||||
const WIDTH: usize = 1;
|
const WIDTH: usize = 1;
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn force_and_check(
|
fn force_and_check<M: Machine<'gc>>(
|
||||||
vm: &mut Vm<'gc>,
|
m: &mut M,
|
||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
base_depth: usize,
|
base_depth: usize,
|
||||||
resume_pc: usize,
|
resume_pc: usize,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
vm.force_slot_to_pc(base_depth, reader, mc, resume_pc)?;
|
m.force_slot_to_pc(base_depth, reader, mc, resume_pc)?;
|
||||||
let v = vm.peek_forced(base_depth);
|
let v = m.peek_forced(base_depth);
|
||||||
if v.as_num().is_none() {
|
if v.as_num().is_none() {
|
||||||
let _: Step = vm.finish_type_err(NixType::Int, v.ty());
|
let _: Step = m.finish_type_err(NixType::Int, v.ty());
|
||||||
return Step::Break(Break::Done);
|
return Step::Break(Break::Done);
|
||||||
}
|
}
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn pop_converted(vm: &mut Vm<'gc>) -> Self {
|
fn pop_converted<M: Machine<'gc>>(m: &mut M) -> Self {
|
||||||
vm.pop_forced()
|
m.pop_forced()
|
||||||
.as_num()
|
.as_num()
|
||||||
.expect("type checked in force_and_check")
|
.expect("type checked in force_and_check")
|
||||||
}
|
}
|
||||||
@@ -162,25 +164,25 @@ impl<'gc> Forced<'gc> for f64 {
|
|||||||
const WIDTH: usize = 1;
|
const WIDTH: usize = 1;
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn force_and_check(
|
fn force_and_check<M: Machine<'gc>>(
|
||||||
vm: &mut Vm<'gc>,
|
m: &mut M,
|
||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
base_depth: usize,
|
base_depth: usize,
|
||||||
resume_pc: usize,
|
resume_pc: usize,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
vm.force_slot_to_pc(base_depth, reader, mc, resume_pc)?;
|
m.force_slot_to_pc(base_depth, reader, mc, resume_pc)?;
|
||||||
let v = vm.peek_forced(base_depth);
|
let v = m.peek_forced(base_depth);
|
||||||
if v.as_float().is_none() {
|
if v.as_float().is_none() {
|
||||||
let _: Step = vm.finish_type_err(NixType::Float, v.ty());
|
let _: Step = m.finish_type_err(NixType::Float, v.ty());
|
||||||
return Step::Break(Break::Done);
|
return Step::Break(Break::Done);
|
||||||
}
|
}
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn pop_converted(vm: &mut Vm<'gc>) -> Self {
|
fn pop_converted<M: Machine<'gc>>(m: &mut M) -> Self {
|
||||||
vm.pop_forced()
|
m.pop_forced()
|
||||||
.as_float()
|
.as_float()
|
||||||
.expect("type checked in force_and_check")
|
.expect("type checked in force_and_check")
|
||||||
}
|
}
|
||||||
@@ -190,21 +192,21 @@ impl<'gc, A: Forced<'gc>, B: Forced<'gc>> Forced<'gc> for (A, B) {
|
|||||||
const WIDTH: usize = A::WIDTH + B::WIDTH;
|
const WIDTH: usize = A::WIDTH + B::WIDTH;
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn force_and_check(
|
fn force_and_check<M: Machine<'gc>>(
|
||||||
vm: &mut Vm<'gc>,
|
m: &mut M,
|
||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
base: usize,
|
base: usize,
|
||||||
resume_pc: usize,
|
resume_pc: usize,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
A::force_and_check(vm, reader, mc, base + B::WIDTH, resume_pc)?;
|
A::force_and_check(m, reader, mc, base + B::WIDTH, resume_pc)?;
|
||||||
B::force_and_check(vm, reader, mc, base, resume_pc)
|
B::force_and_check(m, reader, mc, base, resume_pc)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn pop_converted(vm: &mut Vm<'gc>) -> Self {
|
fn pop_converted<M: Machine<'gc>>(m: &mut M) -> Self {
|
||||||
let b = B::pop_converted(vm);
|
let b = B::pop_converted(m);
|
||||||
let a = A::pop_converted(vm);
|
let a = A::pop_converted(m);
|
||||||
(a, b)
|
(a, b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -213,23 +215,23 @@ impl<'gc, A: Forced<'gc>, B: Forced<'gc>, C: Forced<'gc>> Forced<'gc> for (A, B,
|
|||||||
const WIDTH: usize = A::WIDTH + B::WIDTH + C::WIDTH;
|
const WIDTH: usize = A::WIDTH + B::WIDTH + C::WIDTH;
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn force_and_check(
|
fn force_and_check<M: Machine<'gc>>(
|
||||||
vm: &mut Vm<'gc>,
|
m: &mut M,
|
||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
base: usize,
|
base: usize,
|
||||||
resume_pc: usize,
|
resume_pc: usize,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
A::force_and_check(vm, reader, mc, base + B::WIDTH + C::WIDTH, resume_pc)?;
|
A::force_and_check(m, reader, mc, base + B::WIDTH + C::WIDTH, resume_pc)?;
|
||||||
B::force_and_check(vm, reader, mc, base + C::WIDTH, resume_pc)?;
|
B::force_and_check(m, reader, mc, base + C::WIDTH, resume_pc)?;
|
||||||
C::force_and_check(vm, reader, mc, base, resume_pc)
|
C::force_and_check(m, reader, mc, base, resume_pc)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn pop_converted(vm: &mut Vm<'gc>) -> Self {
|
fn pop_converted<M: Machine<'gc>>(m: &mut M) -> Self {
|
||||||
let c = C::pop_converted(vm);
|
let c = C::pop_converted(m);
|
||||||
let b = B::pop_converted(vm);
|
let b = B::pop_converted(m);
|
||||||
let a = A::pop_converted(vm);
|
let a = A::pop_converted(m);
|
||||||
(a, b, c)
|
(a, b, c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -240,31 +242,31 @@ impl<'gc, A: Forced<'gc>, B: Forced<'gc>, C: Forced<'gc>, D: Forced<'gc>> Forced
|
|||||||
const WIDTH: usize = A::WIDTH + B::WIDTH + C::WIDTH + D::WIDTH;
|
const WIDTH: usize = A::WIDTH + B::WIDTH + C::WIDTH + D::WIDTH;
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn force_and_check(
|
fn force_and_check<M: Machine<'gc>>(
|
||||||
vm: &mut Vm<'gc>,
|
m: &mut M,
|
||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
base: usize,
|
base: usize,
|
||||||
resume_pc: usize,
|
resume_pc: usize,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
A::force_and_check(
|
A::force_and_check(
|
||||||
vm,
|
m,
|
||||||
reader,
|
reader,
|
||||||
mc,
|
mc,
|
||||||
base + B::WIDTH + C::WIDTH + D::WIDTH,
|
base + B::WIDTH + C::WIDTH + D::WIDTH,
|
||||||
resume_pc,
|
resume_pc,
|
||||||
)?;
|
)?;
|
||||||
B::force_and_check(vm, reader, mc, base + C::WIDTH + D::WIDTH, resume_pc)?;
|
B::force_and_check(m, reader, mc, base + C::WIDTH + D::WIDTH, resume_pc)?;
|
||||||
C::force_and_check(vm, reader, mc, base + D::WIDTH, resume_pc)?;
|
C::force_and_check(m, reader, mc, base + D::WIDTH, resume_pc)?;
|
||||||
D::force_and_check(vm, reader, mc, base, resume_pc)
|
D::force_and_check(m, reader, mc, base, resume_pc)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn pop_converted(vm: &mut Vm<'gc>) -> Self {
|
fn pop_converted<M: Machine<'gc>>(m: &mut M) -> Self {
|
||||||
let d = D::pop_converted(vm);
|
let d = D::pop_converted(m);
|
||||||
let c = C::pop_converted(vm);
|
let c = C::pop_converted(m);
|
||||||
let b = B::pop_converted(vm);
|
let b = B::pop_converted(m);
|
||||||
let a = A::pop_converted(vm);
|
let a = A::pop_converted(m);
|
||||||
(a, b, c, d)
|
(a, b, c, d)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
use fix_codegen::InstructionPtr;
|
||||||
|
use fix_common::StringId;
|
||||||
|
use fix_error::Source;
|
||||||
|
use hashbrown::HashSet;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
AttrSet, Closure, ExtraScope, List, NixString, NixType, Null, Path, PrimOp, PrimOpApp,
|
||||||
|
StaticValue, StrictValue, Thunk, ThunkState, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub trait VmContext {
|
||||||
|
fn split(&mut self) -> (&mut impl VmCode, &mut impl VmRuntimeCtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait VmRuntimeCtx {
|
||||||
|
fn intern_string(&mut self, s: impl AsRef<str>) -> StringId;
|
||||||
|
fn resolve_string(&self, id: StringId) -> &str;
|
||||||
|
fn get_const(&self, id: u32) -> StaticValue;
|
||||||
|
fn add_const(&mut self, val: StaticValue) -> u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait VmCode {
|
||||||
|
fn bytecode(&self) -> &[u8];
|
||||||
|
fn compile_with_scope(
|
||||||
|
&mut self,
|
||||||
|
source: Source,
|
||||||
|
extra_scope: Option<ExtraScope>,
|
||||||
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
|
) -> fix_error::Result<InstructionPtr>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait VmRuntimeCtxExt: VmRuntimeCtx {
|
||||||
|
fn get_string<'a, 'gc: 'a>(&'a self, val: StrictValue<'gc>) -> Option<&'a str>;
|
||||||
|
fn get_string_or_path<'a, 'gc: 'a>(&'a self, val: StrictValue<'gc>) -> Option<&'a str>;
|
||||||
|
fn get_string_id<'a, 'gc: 'a>(
|
||||||
|
&'a mut self,
|
||||||
|
val: StrictValue<'gc>,
|
||||||
|
) -> std::result::Result<StringId, NixType>;
|
||||||
|
fn convert_value(&self, val: Value) -> fix_common::Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: VmRuntimeCtx> VmRuntimeCtxExt for T {
|
||||||
|
fn get_string<'a, 'gc: 'a>(&'a self, val: StrictValue<'gc>) -> Option<&'a str> {
|
||||||
|
if let Some(sid) = val.as_inline::<StringId>() {
|
||||||
|
Some(self.resolve_string(sid))
|
||||||
|
} else {
|
||||||
|
val.as_gc::<NixString>().map(|ns| ns.as_ref().as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Like `get_string`, but also accepts `Path` values (returning their
|
||||||
|
/// underlying canonical-path string). Use this in places where Nix
|
||||||
|
/// would coerce a path to a string (string interpolation, file IO
|
||||||
|
/// builtins, etc.).
|
||||||
|
fn get_string_or_path<'a, 'gc: 'a>(&'a self, val: StrictValue<'gc>) -> Option<&'a str> {
|
||||||
|
if let Some(p) = val.as_inline::<Path>() {
|
||||||
|
Some(self.resolve_string(p.0))
|
||||||
|
} else {
|
||||||
|
self.get_string(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_string_id<'a, 'gc: 'a>(
|
||||||
|
&'a mut self,
|
||||||
|
val: StrictValue<'gc>,
|
||||||
|
) -> std::result::Result<StringId, NixType> {
|
||||||
|
if let Some(sid) = val.as_inline::<StringId>() {
|
||||||
|
Ok(sid)
|
||||||
|
} else if let Some(s) = val.as_gc::<NixString>().map(|ns| ns.as_ref().as_str()) {
|
||||||
|
Ok(self.intern_string(s))
|
||||||
|
} else {
|
||||||
|
Err(val.ty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_value(&self, val: Value) -> fix_common::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<u64>) -> fix_common::Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: VmRuntimeCtx> ConvertValueWithSeen for T {
|
||||||
|
fn convert_value_with_seen(&self, val: Value, seen: &mut HashSet<u64>) -> fix_common::Value {
|
||||||
|
use fix_common::Value;
|
||||||
|
if let Some(i) = val.as_inline::<i32>() {
|
||||||
|
Value::Int(i as i64)
|
||||||
|
} else if let Some(gc_i) = val.as_gc::<i64>() {
|
||||||
|
Value::Int(*gc_i)
|
||||||
|
} else if let Some(f) = val.as_float() {
|
||||||
|
Value::Float(f)
|
||||||
|
} else if let Some(b) = val.as_inline::<bool>() {
|
||||||
|
Value::Bool(b)
|
||||||
|
} else if val.is::<Null>() {
|
||||||
|
Value::Null
|
||||||
|
} else if let Some(sid) = val.as_inline::<StringId>() {
|
||||||
|
let s = self.resolve_string(sid).to_owned();
|
||||||
|
Value::String(s)
|
||||||
|
} else if let Some(ns) = val.as_gc::<NixString>() {
|
||||||
|
Value::String(ns.as_str().to_owned())
|
||||||
|
} else if let Some(p) = val.as_inline::<Path>() {
|
||||||
|
Value::Path(self.resolve_string(p.0).to_owned())
|
||||||
|
} else if let Some(attrs) = val.as_gc::<AttrSet>() {
|
||||||
|
let bits = val.to_bits();
|
||||||
|
if attrs.entries.is_empty() {
|
||||||
|
return Value::AttrSet(Default::default());
|
||||||
|
}
|
||||||
|
if !seen.insert(bits) {
|
||||||
|
return Value::Repeated;
|
||||||
|
}
|
||||||
|
let mut map = std::collections::BTreeMap::new();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
Value::AttrSet(fix_common::AttrSet::new(map))
|
||||||
|
} else if let Some(list) = val.as_gc::<List>() {
|
||||||
|
let bits = val.to_bits();
|
||||||
|
if list.inner.borrow().is_empty() {
|
||||||
|
return Value::List(Default::default());
|
||||||
|
}
|
||||||
|
if !seen.insert(bits) {
|
||||||
|
return Value::Repeated;
|
||||||
|
}
|
||||||
|
let items: Vec<_> = list
|
||||||
|
.inner
|
||||||
|
.borrow()
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.map(|v| self.convert_value_with_seen(v, seen))
|
||||||
|
.collect();
|
||||||
|
Value::List(fix_common::List::new(items))
|
||||||
|
} else if val.is::<Closure>() {
|
||||||
|
Value::Func
|
||||||
|
} else if let Some(thunk) = val.as_gc::<Thunk>() {
|
||||||
|
if let ThunkState::Evaluated(v) = *thunk.borrow() {
|
||||||
|
self.convert_value_with_seen(v.relax(), seen)
|
||||||
|
} else {
|
||||||
|
Value::Thunk
|
||||||
|
}
|
||||||
|
} else if let Some(primop) = val.as_inline::<PrimOp>() {
|
||||||
|
let name = fix_builtins::BUILTINS[primop.id as usize].0;
|
||||||
|
Value::PrimOp(name.strip_prefix("__").unwrap_or(name))
|
||||||
|
} else if let Some(app) = val.as_gc::<PrimOpApp>() {
|
||||||
|
let name = fix_builtins::BUILTINS[app.primop.id as usize].0;
|
||||||
|
Value::PrimOpApp(name.strip_prefix("__").unwrap_or(name))
|
||||||
|
} else {
|
||||||
|
Value::Null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
mod boxing;
|
||||||
|
mod bytecode_reader;
|
||||||
|
mod forced;
|
||||||
|
mod host;
|
||||||
|
mod machine;
|
||||||
|
mod path_util;
|
||||||
|
mod resolve;
|
||||||
|
mod state;
|
||||||
|
mod value;
|
||||||
|
|
||||||
|
pub use bytecode_reader::*;
|
||||||
|
pub use forced::*;
|
||||||
|
pub use host::*;
|
||||||
|
pub use machine::*;
|
||||||
|
pub use path_util::*;
|
||||||
|
pub use resolve::*;
|
||||||
|
pub use state::*;
|
||||||
|
pub use value::*;
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
use std::ops::ControlFlow;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use fix_common::StringId;
|
||||||
|
use fix_error::Error;
|
||||||
|
use gc_arena::Mutation;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
Break, BytecodeReader, CallFrame, ForceMode, Forced, GcEnv, NixType, PendingLoad, Step,
|
||||||
|
StrictValue, Value, VmError,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Abstract VM-side operations consumed by instruction handlers and primops.
|
||||||
|
///
|
||||||
|
/// Implementors maintain a value stack, a call stack, an environment chain,
|
||||||
|
/// pending result/error state, and a set of GC-allocated globals. Methods
|
||||||
|
/// fall into a few groups:
|
||||||
|
///
|
||||||
|
/// - Stack ops (`push` / `pop` / `peek` / `replace` / `pop_forced` / ...)
|
||||||
|
/// - Forcing primitives (`force_slot` / `force_slot_to_pc`)
|
||||||
|
/// - Calling (`call` / `return_from_primop`)
|
||||||
|
/// - Call-frame management (`push_call_frame` / `pop_call_frame` / call-depth)
|
||||||
|
/// - Environment access (`env` / `set_env` / `local`)
|
||||||
|
/// - Result finalization (`finish_ok` / `finish_err` / ...)
|
||||||
|
/// - Global lookup (`builtins` / `empty_list` / `empty_attrs` / ...)
|
||||||
|
/// - Imports and scope slots (`import_cache_*` / `scope_slot*` / `set_pending_load`)
|
||||||
|
pub trait Machine<'gc> {
|
||||||
|
fn push(&mut self, val: Value<'gc>);
|
||||||
|
fn pop(&mut self) -> Value<'gc>;
|
||||||
|
fn peek(&self, depth: usize) -> Value<'gc>;
|
||||||
|
fn peek_forced(&self, depth: usize) -> StrictValue<'gc>;
|
||||||
|
fn pop_forced(&mut self) -> StrictValue<'gc>;
|
||||||
|
fn replace(&mut self, depth: usize, val: Value<'gc>);
|
||||||
|
fn stack_len(&self) -> usize;
|
||||||
|
|
||||||
|
fn force_slot_to_pc(
|
||||||
|
&mut self,
|
||||||
|
depth: usize,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
resume_pc: usize,
|
||||||
|
) -> Step;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn force_slot(
|
||||||
|
&mut self,
|
||||||
|
depth: usize,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
let pc = reader.inst_start_pc();
|
||||||
|
self.force_slot_to_pc(depth, reader, mc, pc)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(
|
||||||
|
&mut self,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
arg: Value<'gc>,
|
||||||
|
resume_pc: usize,
|
||||||
|
) -> Step;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn return_from_primop(&mut self, val: Value<'gc>, reader: &mut BytecodeReader<'_>) -> Step {
|
||||||
|
self.push(val);
|
||||||
|
let Some(CallFrame {
|
||||||
|
pc: ret_pc,
|
||||||
|
thunk: _,
|
||||||
|
env,
|
||||||
|
}) = self.pop_call_frame()
|
||||||
|
else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
reader.set_pc(ret_pc);
|
||||||
|
self.dec_call_depth();
|
||||||
|
self.set_env(env);
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_call_frame(&mut self, frame: CallFrame<'gc>);
|
||||||
|
fn pop_call_frame(&mut self) -> Option<CallFrame<'gc>>;
|
||||||
|
fn call_depth(&self) -> usize;
|
||||||
|
fn inc_call_depth(&mut self);
|
||||||
|
fn dec_call_depth(&mut self);
|
||||||
|
|
||||||
|
fn env(&self) -> GcEnv<'gc>;
|
||||||
|
fn set_env(&mut self, env: GcEnv<'gc>);
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn local(&self, layer: u8, idx: u32) -> Value<'gc> {
|
||||||
|
let mut cur = self.env();
|
||||||
|
for _ in 0..layer {
|
||||||
|
let prev = cur.borrow().prev.expect("env chain too short");
|
||||||
|
cur = prev;
|
||||||
|
}
|
||||||
|
cur.borrow().locals[idx as usize]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finish_ok(&mut self, val: fix_common::Value) -> Step;
|
||||||
|
fn finish_err(&mut self, err: Box<Error>) -> Step;
|
||||||
|
fn finish_type_err(&mut self, expected: NixType, got: NixType) -> Step;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn finish_vm_err(&mut self, err: VmError) -> Step {
|
||||||
|
self.finish_err(err.into_error())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn builtins(&self) -> Value<'gc>;
|
||||||
|
fn functor_sym(&self) -> StringId;
|
||||||
|
fn empty_list(&self) -> Value<'gc>;
|
||||||
|
fn empty_attrs(&self) -> Value<'gc>;
|
||||||
|
fn force_mode(&self) -> ForceMode;
|
||||||
|
|
||||||
|
fn import_cache_get(&self, path: &Path) -> Option<Value<'gc>>;
|
||||||
|
fn import_cache_insert(&mut self, path: PathBuf, val: Value<'gc>);
|
||||||
|
fn scope_slot(&self, idx: u32) -> Value<'gc>;
|
||||||
|
fn scope_slots_push(&mut self, val: Value<'gc>) -> u32;
|
||||||
|
fn set_pending_load(&mut self, load: PendingLoad);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extension trait with convenience helpers built on top of [`Machine`].
|
||||||
|
///
|
||||||
|
/// Auto-implemented for every `Machine<'gc>` so callers just need to bring
|
||||||
|
/// `MachineExt` (or `Machine`) into scope.
|
||||||
|
pub trait MachineExt<'gc>: Machine<'gc> {
|
||||||
|
/// Force the top `T::WIDTH` stack slots and return them as `T`.
|
||||||
|
///
|
||||||
|
/// If any slot holds a pending thunk, this method pushes a call frame
|
||||||
|
/// whose resume PC is the **start of the current instruction**
|
||||||
|
/// (`reader.inst_start_pc()`), enters the thunk, and returns
|
||||||
|
/// `Break::Force`. When the thunk eventually returns, the VM will
|
||||||
|
/// **re-execute the entire opcode handler from the beginning**.
|
||||||
|
///
|
||||||
|
/// # Invariants
|
||||||
|
///
|
||||||
|
/// * **Do not call this method more than once in a single handler.**
|
||||||
|
/// If you need to force multiple values, use a tuple type such as
|
||||||
|
/// `(StrictValue, StrictValue)` so they are forced and popped in one
|
||||||
|
/// atomic operation.
|
||||||
|
/// * The stack layout at the call site must be **identical** every time
|
||||||
|
/// the handler is re-entered.
|
||||||
|
/// * Propagate the return value with `?` so `Break::Force` correctly
|
||||||
|
/// unwinds to the dispatch loop.
|
||||||
|
#[inline(always)]
|
||||||
|
fn force_and_retry<T: Forced<'gc>>(
|
||||||
|
&mut self,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> ControlFlow<Break, T>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
let pc = reader.inst_start_pc();
|
||||||
|
self.force_and_retry_pc(reader, mc, pc)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Same as [`force_and_retry`](Self::force_and_retry) but allows
|
||||||
|
/// specifying a custom resume PC.
|
||||||
|
#[inline(always)]
|
||||||
|
fn force_and_retry_pc<T: Forced<'gc>>(
|
||||||
|
&mut self,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
resume_pc: usize,
|
||||||
|
) -> ControlFlow<Break, T>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
T::force_and_check(self, reader, mc, 0, resume_pc)?;
|
||||||
|
ControlFlow::Continue(T::pop_converted(self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gc, M: Machine<'gc>> MachineExt<'gc> for M {}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
use std::path::{Component, PathBuf};
|
||||||
|
|
||||||
|
pub fn canon_path_str(path: impl AsRef<std::path::Path>) -> String {
|
||||||
|
let p = path.as_ref();
|
||||||
|
let mut normalized = PathBuf::new();
|
||||||
|
for component in p.components() {
|
||||||
|
match component {
|
||||||
|
Component::Prefix(p) => normalized.push(p.as_os_str()),
|
||||||
|
Component::RootDir => normalized.push("/"),
|
||||||
|
Component::CurDir => {}
|
||||||
|
Component::ParentDir => {
|
||||||
|
normalized.pop();
|
||||||
|
}
|
||||||
|
Component::Normal(c) => normalized.push(c),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
normalized.to_string_lossy().into_owned()
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
use gc_arena::{Gc, Mutation};
|
||||||
|
|
||||||
|
use crate::{AttrSet, Machine, OperandData, Value};
|
||||||
|
|
||||||
|
/// Resolve a decoded operand into a runtime [`Value`].
|
||||||
|
///
|
||||||
|
/// The operand decoder ([`crate::BytecodeReader::read_operand_data`])
|
||||||
|
/// produces a static enum; this function materializes it against the
|
||||||
|
/// running [`Machine`] (env chain, builtins, scope slots, ...).
|
||||||
|
#[inline]
|
||||||
|
pub fn resolve_operand<'gc, M: Machine<'gc>>(
|
||||||
|
op: &OperandData,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
m: &M,
|
||||||
|
) -> Value<'gc> {
|
||||||
|
use OperandData::*;
|
||||||
|
match *op {
|
||||||
|
Const(sv) => sv.into(),
|
||||||
|
BigInt(val) => Value::new_gc(Gc::new(mc, val)),
|
||||||
|
Local { layer, idx } => m.local(layer, idx),
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
BuiltinConst(id) => m.builtins().as_gc::<AttrSet>().unwrap().lookup(id).unwrap(),
|
||||||
|
Builtins => m.builtins(),
|
||||||
|
ReplBinding(_id) => todo!(),
|
||||||
|
ScopedImportBinding { slot_id, name } => {
|
||||||
|
let scope = m.scope_slot(slot_id);
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let attrs = scope.as_gc::<AttrSet>().expect("scope must be attrset");
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
attrs.lookup(name).expect("scoped binding not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
use std::ops::ControlFlow;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use fix_common::StringId;
|
||||||
|
use fix_error::Error;
|
||||||
|
use gc_arena::{Collect, Gc};
|
||||||
|
use hashbrown::HashSet;
|
||||||
|
|
||||||
|
use crate::{GcEnv, StaticValue, Thunk};
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub enum VmError {
|
||||||
|
Catchable(String),
|
||||||
|
Uncatchable(Box<Error>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Box<Error>> for VmError {
|
||||||
|
fn from(e: Box<Error>) -> Self {
|
||||||
|
VmError::Uncatchable(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VmError {
|
||||||
|
pub fn into_error(self) -> Box<Error> {
|
||||||
|
match self {
|
||||||
|
VmError::Catchable(_) => todo!("Check for tryEval catch frames"),
|
||||||
|
VmError::Uncatchable(e) => e,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn vm_err(msg: impl Into<String>) -> VmError {
|
||||||
|
VmError::Uncatchable(Error::eval_error(msg.into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Collect, Clone, Copy, Debug, PartialEq, Eq, Default)]
|
||||||
|
#[collect(require_static)]
|
||||||
|
pub enum ForceMode {
|
||||||
|
#[default]
|
||||||
|
AsIs,
|
||||||
|
Shallow,
|
||||||
|
Deep,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum Break {
|
||||||
|
Force,
|
||||||
|
Done,
|
||||||
|
LoadFile,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Step = ControlFlow<Break>;
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub struct ErrorFrame {
|
||||||
|
pub span_id: u32,
|
||||||
|
pub message: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Collect, Debug)]
|
||||||
|
#[collect(no_drop)]
|
||||||
|
pub struct CallFrame<'gc> {
|
||||||
|
pub pc: usize,
|
||||||
|
pub thunk: Option<Gc<'gc, Thunk<'gc>>>,
|
||||||
|
pub env: GcEnv<'gc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PendingLoad {
|
||||||
|
pub path: PathBuf,
|
||||||
|
pub scope: Option<PendingScope>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PendingScope {
|
||||||
|
pub keys: HashSet<StringId>,
|
||||||
|
pub slot_id: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extra scope passed to a re-entrant compile from inside a running VM.
|
||||||
|
///
|
||||||
|
/// Currently only `ScopedImport` is produced (by the `scopedImport` builtin),
|
||||||
|
/// but the variant is kept open so REPL bindings could later land here too.
|
||||||
|
pub enum ExtraScope {
|
||||||
|
ScopedImport {
|
||||||
|
keys: HashSet<StringId>,
|
||||||
|
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 },
|
||||||
|
}
|
||||||
@@ -16,7 +16,6 @@ use smallvec::SmallVec;
|
|||||||
use string_interner::Symbol;
|
use string_interner::Symbol;
|
||||||
use string_interner::symbol::SymbolU32;
|
use string_interner::symbol::SymbolU32;
|
||||||
|
|
||||||
use crate::NixNum;
|
|
||||||
use crate::boxing::{RawBox, RawStore, RawTag, Value as RawValue};
|
use crate::boxing::{RawBox, RawStore, RawTag, Value as RawValue};
|
||||||
|
|
||||||
mod private {
|
mod private {
|
||||||
@@ -250,7 +249,7 @@ impl<'gc> Value<'gc> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn to_bits(self) -> u64 {
|
pub fn to_bits(self) -> u64 {
|
||||||
self.raw.to_bits()
|
self.raw.to_bits()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,27 +307,27 @@ impl<'gc> Value<'gc> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn expect_inline<T: InlineStorable>(self) -> Result<T, NixType> {
|
pub fn expect_inline<T: InlineStorable>(self) -> Result<T, NixType> {
|
||||||
self.as_inline::<T>().ok_or_else(|| self.ty())
|
self.as_inline::<T>().ok_or_else(|| self.ty())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn expect_gc<T: GcStorable>(self) -> Result<Gc<'gc, T>, NixType> {
|
pub fn expect_gc<T: GcStorable>(self) -> Result<Gc<'gc, T>, NixType> {
|
||||||
self.as_gc::<T>().ok_or_else(|| self.ty())
|
self.as_gc::<T>().ok_or_else(|| self.ty())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn expect_num(self) -> Result<NixNum, NixType> {
|
pub fn expect_num(self) -> Result<NixNum, NixType> {
|
||||||
self.as_num().ok_or_else(|| self.ty())
|
self.as_num().ok_or_else(|| self.ty())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn expect_bool(self) -> Result<bool, NixType> {
|
pub fn expect_bool(self) -> Result<bool, NixType> {
|
||||||
self.as_inline::<bool>().ok_or_else(|| self.ty())
|
self.as_inline::<bool>().ok_or_else(|| self.ty())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn expect_float(self) -> Result<f64, NixType> {
|
pub fn expect_float(self) -> Result<f64, NixType> {
|
||||||
self.as_float().ok_or_else(|| self.ty())
|
self.as_float().ok_or_else(|| self.ty())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -385,7 +384,7 @@ impl StaticValue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub(crate) struct Null;
|
pub struct Null;
|
||||||
impl RawStore for Null {
|
impl RawStore for Null {
|
||||||
fn to_val(self, value: &mut RawValue) {
|
fn to_val(self, value: &mut RawValue) {
|
||||||
value.set_data([0; 6]);
|
value.set_data([0; 6]);
|
||||||
@@ -410,7 +409,7 @@ impl RawStore for StringId {
|
|||||||
/// A canonicalized absolute path. Inline value carrying an interned
|
/// A canonicalized absolute path. Inline value carrying an interned
|
||||||
/// `StringId` whose contents are the path's absolute, dot-resolved form.
|
/// `StringId` whose contents are the path's absolute, dot-resolved form.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub(crate) struct Path(pub StringId);
|
pub struct Path(pub StringId);
|
||||||
|
|
||||||
impl RawStore for Path {
|
impl RawStore for Path {
|
||||||
fn to_val(self, value: &mut RawValue) {
|
fn to_val(self, value: &mut RawValue) {
|
||||||
@@ -428,17 +427,17 @@ impl RawStore for Path {
|
|||||||
/// the outer allocation.
|
/// the outer allocation.
|
||||||
#[derive(Collect)]
|
#[derive(Collect)]
|
||||||
#[collect(require_static)]
|
#[collect(require_static)]
|
||||||
pub(crate) struct NixString {
|
pub struct NixString {
|
||||||
data: Box<str>,
|
data: Box<str>,
|
||||||
// TODO: string context for derivation dependency tracking
|
// TODO: string context for derivation dependency tracking
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NixString {
|
impl NixString {
|
||||||
pub(crate) fn new(s: impl Into<Box<str>>) -> Self {
|
pub fn new(s: impl Into<Box<str>>) -> Self {
|
||||||
Self { data: s.into() }
|
Self { data: s.into() }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn as_str(&self) -> &str {
|
pub fn as_str(&self) -> &str {
|
||||||
&self.data
|
&self.data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -451,28 +450,28 @@ impl fmt::Debug for NixString {
|
|||||||
|
|
||||||
#[derive(Collect, Debug, Default)]
|
#[derive(Collect, Debug, Default)]
|
||||||
#[collect(no_drop)]
|
#[collect(no_drop)]
|
||||||
pub(crate) struct AttrSet<'gc> {
|
pub struct AttrSet<'gc> {
|
||||||
pub(crate) entries: SmallVec<[(StringId, Value<'gc>); 4]>,
|
pub entries: SmallVec<[(StringId, Value<'gc>); 4]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'gc> AttrSet<'gc> {
|
impl<'gc> AttrSet<'gc> {
|
||||||
pub(crate) fn from_sorted_unchecked(entries: SmallVec<[(StringId, Value<'gc>); 4]>) -> Self {
|
pub fn from_sorted_unchecked(entries: SmallVec<[(StringId, Value<'gc>); 4]>) -> Self {
|
||||||
debug_assert!(entries.is_sorted_by_key(|(key, _)| *key));
|
debug_assert!(entries.is_sorted_by_key(|(key, _)| *key));
|
||||||
Self { entries }
|
Self { entries }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn lookup(&self, key: StringId) -> Option<Value<'gc>> {
|
pub fn lookup(&self, key: StringId) -> Option<Value<'gc>> {
|
||||||
self.entries
|
self.entries
|
||||||
.binary_search_by_key(&key, |(k, _)| *k)
|
.binary_search_by_key(&key, |(k, _)| *k)
|
||||||
.ok()
|
.ok()
|
||||||
.map(|i| self.entries[i].1)
|
.map(|i| self.entries[i].1)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn has(&self, key: StringId) -> bool {
|
pub fn has(&self, key: StringId) -> bool {
|
||||||
self.entries.binary_search_by_key(&key, |(k, _)| *k).is_ok()
|
self.entries.binary_search_by_key(&key, |(k, _)| *k).is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn merge(&self, other: &Self, mc: &Mutation<'gc>) -> Gc<'gc, Self> {
|
pub fn merge(&self, other: &Self, mc: &Mutation<'gc>) -> Gc<'gc, Self> {
|
||||||
use std::cmp::Ordering::*;
|
use std::cmp::Ordering::*;
|
||||||
|
|
||||||
debug_assert!(self.entries.is_sorted_by_key(|(key, _)| *key));
|
debug_assert!(self.entries.is_sorted_by_key(|(key, _)| *key));
|
||||||
@@ -510,12 +509,12 @@ impl<'gc> AttrSet<'gc> {
|
|||||||
#[derive(Collect, Debug, Default)]
|
#[derive(Collect, Debug, Default)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
#[collect(no_drop)]
|
#[collect(no_drop)]
|
||||||
pub(crate) struct List<'gc> {
|
pub struct List<'gc> {
|
||||||
pub(crate) inner: RefLock<SmallVec<[Value<'gc>; 4]>>,
|
pub inner: RefLock<SmallVec<[Value<'gc>; 4]>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'gc> List<'gc> {
|
impl<'gc> List<'gc> {
|
||||||
pub(crate) fn new(mc: &Mutation<'gc>, data: SmallVec<[Value<'gc>; 4]>) -> Gc<'gc, Self> {
|
pub fn new(mc: &Mutation<'gc>, data: SmallVec<[Value<'gc>; 4]>) -> Gc<'gc, Self> {
|
||||||
Gc::new(
|
Gc::new(
|
||||||
mc,
|
mc,
|
||||||
Self {
|
Self {
|
||||||
@@ -524,7 +523,7 @@ impl<'gc> List<'gc> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn new_gc(mc: &Mutation<'gc>) -> Gc<'gc, Self> {
|
pub fn new_gc(mc: &Mutation<'gc>) -> Gc<'gc, Self> {
|
||||||
Gc::new(mc, Self::default())
|
Gc::new(mc, Self::default())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -536,11 +535,11 @@ impl<'gc> Unlock for List<'gc> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) type Thunk<'gc> = RefLock<ThunkState<'gc>>;
|
pub type Thunk<'gc> = RefLock<ThunkState<'gc>>;
|
||||||
|
|
||||||
#[derive(Collect, Debug)]
|
#[derive(Collect, Debug)]
|
||||||
#[collect(no_drop)]
|
#[collect(no_drop)]
|
||||||
pub(crate) enum ThunkState<'gc> {
|
pub enum ThunkState<'gc> {
|
||||||
Pending { ip: usize, env: GcEnv<'gc> },
|
Pending { ip: usize, env: GcEnv<'gc> },
|
||||||
Apply { func: Value<'gc>, arg: Value<'gc> },
|
Apply { func: Value<'gc>, arg: Value<'gc> },
|
||||||
Blackhole,
|
Blackhole,
|
||||||
@@ -549,33 +548,29 @@ pub(crate) enum ThunkState<'gc> {
|
|||||||
|
|
||||||
#[derive(Collect, Debug)]
|
#[derive(Collect, Debug)]
|
||||||
#[collect(no_drop)]
|
#[collect(no_drop)]
|
||||||
pub(crate) struct Env<'gc> {
|
pub struct Env<'gc> {
|
||||||
pub(crate) locals: SmallVec<[Value<'gc>; 4]>,
|
pub locals: SmallVec<[Value<'gc>; 4]>,
|
||||||
pub(crate) prev: Option<GcEnv<'gc>>,
|
pub prev: Option<GcEnv<'gc>>,
|
||||||
}
|
}
|
||||||
pub(crate) type GcEnv<'gc> = GcRefLock<'gc, Env<'gc>>;
|
pub type GcEnv<'gc> = GcRefLock<'gc, Env<'gc>>;
|
||||||
|
|
||||||
#[derive(Collect, Debug)]
|
#[derive(Collect, Debug)]
|
||||||
#[collect(no_drop)]
|
#[collect(no_drop)]
|
||||||
pub(crate) struct WithEnv<'gc> {
|
pub struct WithEnv<'gc> {
|
||||||
pub(crate) env: Value<'gc>,
|
pub env: Value<'gc>,
|
||||||
pub(crate) prev: Option<GcWithEnv<'gc>>,
|
pub prev: Option<GcWithEnv<'gc>>,
|
||||||
}
|
}
|
||||||
pub(crate) type GcWithEnv<'gc> = Gc<'gc, WithEnv<'gc>>;
|
pub type GcWithEnv<'gc> = Gc<'gc, WithEnv<'gc>>;
|
||||||
|
|
||||||
impl<'gc> Env<'gc> {
|
impl<'gc> Env<'gc> {
|
||||||
pub(crate) fn empty() -> Self {
|
pub fn empty() -> Self {
|
||||||
Env {
|
Env {
|
||||||
locals: SmallVec::new(),
|
locals: SmallVec::new(),
|
||||||
prev: None,
|
prev: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn with_arg(
|
pub fn with_arg(arg: Value<'gc>, n_locals: u32, prev: Gc<'gc, RefLock<Env<'gc>>>) -> Self {
|
||||||
arg: Value<'gc>,
|
|
||||||
n_locals: u32,
|
|
||||||
prev: Gc<'gc, RefLock<Env<'gc>>>,
|
|
||||||
) -> Self {
|
|
||||||
let mut locals = smallvec::smallvec![Value::default(); 1 + n_locals as usize];
|
let mut locals = smallvec::smallvec![Value::default(); 1 + n_locals as usize];
|
||||||
locals[0] = arg;
|
locals[0] = arg;
|
||||||
Env {
|
Env {
|
||||||
@@ -587,29 +582,29 @@ impl<'gc> Env<'gc> {
|
|||||||
|
|
||||||
#[derive(Collect, Debug)]
|
#[derive(Collect, Debug)]
|
||||||
#[collect(no_drop)]
|
#[collect(no_drop)]
|
||||||
pub(crate) struct Closure<'gc> {
|
pub struct Closure<'gc> {
|
||||||
pub(crate) ip: u32,
|
pub ip: u32,
|
||||||
pub(crate) n_locals: u32,
|
pub n_locals: u32,
|
||||||
pub(crate) env: Gc<'gc, RefLock<Env<'gc>>>,
|
pub env: Gc<'gc, RefLock<Env<'gc>>>,
|
||||||
pub(crate) pattern: Option<Gc<'gc, PatternInfo>>,
|
pub pattern: Option<Gc<'gc, PatternInfo>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Collect, Debug)]
|
#[derive(Collect, Debug)]
|
||||||
#[collect(require_static)]
|
#[collect(require_static)]
|
||||||
pub(crate) struct PatternInfo {
|
pub struct PatternInfo {
|
||||||
pub(crate) required: SmallVec<[StringId; 4]>,
|
pub required: SmallVec<[StringId; 4]>,
|
||||||
pub(crate) optional: SmallVec<[StringId; 4]>,
|
pub optional: SmallVec<[StringId; 4]>,
|
||||||
pub(crate) ellipsis: bool,
|
pub ellipsis: bool,
|
||||||
pub(crate) param_spans: Box<[(StringId, u32)]>,
|
pub param_spans: Box<[(StringId, u32)]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(packed, Rust)]
|
#[repr(packed, Rust)]
|
||||||
#[derive(Clone, Copy, Debug, Collect)]
|
#[derive(Clone, Copy, Debug, Collect)]
|
||||||
#[collect(require_static)]
|
#[collect(require_static)]
|
||||||
pub(crate) struct PrimOp {
|
pub struct PrimOp {
|
||||||
pub(crate) id: BuiltinId,
|
pub id: BuiltinId,
|
||||||
pub(crate) arity: u8,
|
pub arity: u8,
|
||||||
pub(crate) dispatch_ip: u32,
|
pub dispatch_ip: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RawStore for PrimOp {
|
impl RawStore for PrimOp {
|
||||||
@@ -636,20 +631,20 @@ impl RawStore for PrimOp {
|
|||||||
|
|
||||||
#[derive(Collect, Debug)]
|
#[derive(Collect, Debug)]
|
||||||
#[collect(no_drop)]
|
#[collect(no_drop)]
|
||||||
pub(crate) struct PrimOpApp<'gc> {
|
pub struct PrimOpApp<'gc> {
|
||||||
pub(crate) primop: PrimOp,
|
pub primop: PrimOp,
|
||||||
pub(crate) arity: u8,
|
pub arity: u8,
|
||||||
pub(crate) args: [Value<'gc>; 3],
|
pub args: [Value<'gc>; 3],
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Default, Collect)]
|
#[derive(Copy, Clone, Default, Collect)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
#[collect(no_drop)]
|
#[collect(no_drop)]
|
||||||
pub(crate) struct StrictValue<'gc>(Value<'gc>);
|
pub struct StrictValue<'gc>(Value<'gc>);
|
||||||
|
|
||||||
impl<'gc> StrictValue<'gc> {
|
impl<'gc> StrictValue<'gc> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn relax(self) -> Value<'gc> {
|
pub fn relax(self) -> Value<'gc> {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -670,7 +665,7 @@ impl fmt::Debug for StrictValue<'_> {
|
|||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Collect)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Collect)]
|
||||||
#[collect(require_static)]
|
#[collect(require_static)]
|
||||||
pub(crate) enum NixType {
|
pub enum NixType {
|
||||||
Int,
|
Int,
|
||||||
Float,
|
Float,
|
||||||
Bool,
|
Bool,
|
||||||
@@ -710,3 +705,8 @@ impl std::fmt::Display for NixType {
|
|||||||
write!(f, "{}", self.display())
|
write!(f, "{}", self.display())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum NixNum {
|
||||||
|
Int(i64),
|
||||||
|
Float(f64),
|
||||||
|
}
|
||||||
+12
-1
@@ -124,7 +124,7 @@ define_builtins! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, TryFromPrimitive)]
|
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||||
pub enum PrimOpPhase {
|
pub enum PrimOpPhase {
|
||||||
Abort,
|
Abort,
|
||||||
Add,
|
Add,
|
||||||
@@ -257,6 +257,17 @@ pub enum PrimOpPhase {
|
|||||||
Illegal,
|
Illegal,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u8> for PrimOpPhase {
|
||||||
|
type Error = u8;
|
||||||
|
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||||
|
if (0..Self::Illegal as u8).contains(&value) {
|
||||||
|
Ok(unsafe { std::mem::transmute::<u8, Self>(value) })
|
||||||
|
} else {
|
||||||
|
Err(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl BuiltinId {
|
impl BuiltinId {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn entry_phase(self) -> PrimOpPhase {
|
pub fn entry_phase(self) -> PrimOpPhase {
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
[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" }
|
||||||
@@ -0,0 +1,362 @@
|
|||||||
|
use fix_abstract_vm::{
|
||||||
|
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;
|
||||||
|
|
||||||
|
pub fn seq<'gc, M: Machine<'gc>>(
|
||||||
|
m: &mut M,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
// stack: [e1, e2] - force e1, return e2
|
||||||
|
m.force_slot(1, reader, mc)?;
|
||||||
|
let e2 = m.pop();
|
||||||
|
let _ = m.pop();
|
||||||
|
m.return_from_primop(e2, reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn abort<'gc, M: Machine<'gc>>(
|
||||||
|
m: &mut M,
|
||||||
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
// stack: [msg] - force msg, then abort with it
|
||||||
|
m.force_slot(0, reader, mc)?;
|
||||||
|
let msg_val = m.peek_forced(0);
|
||||||
|
let msg = ctx.get_string(msg_val).unwrap_or("<non-string-value>");
|
||||||
|
m.finish_err(Error::eval_error(format!(
|
||||||
|
"evaluation aborted with the following error message: '{msg}'"
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deep_seq_force_top<'gc, M: Machine<'gc>>(
|
||||||
|
m: &mut M,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
// stack: [e1, e2] - force e1, return e2
|
||||||
|
m.force_slot(1, reader, mc)?;
|
||||||
|
|
||||||
|
let e1 = m.peek_forced(1);
|
||||||
|
|
||||||
|
let children: SmallVec<_> = if let Some(attrs) = e1.as_gc::<AttrSet>() {
|
||||||
|
let attrs = &attrs.entries;
|
||||||
|
if attrs.is_empty() {
|
||||||
|
SmallVec::new()
|
||||||
|
} else {
|
||||||
|
attrs.iter().map(|&(_, v)| v).collect()
|
||||||
|
}
|
||||||
|
} else if let Some(list) = e1.as_gc::<List<'gc>>() {
|
||||||
|
let inner = list.inner.borrow();
|
||||||
|
if inner.is_empty() {
|
||||||
|
SmallVec::new()
|
||||||
|
} else {
|
||||||
|
inner.iter().copied().collect()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SmallVec::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
if children.is_empty() {
|
||||||
|
let e2 = m.pop();
|
||||||
|
let _ = m.pop();
|
||||||
|
return m.return_from_primop(e2, reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
let count = children.len() as i32;
|
||||||
|
let seen: Gc<'gc, List<'gc>> = Gc::new(mc, List::default());
|
||||||
|
let worklist: Gc<'gc, List<'gc>> = List::new(mc, children);
|
||||||
|
|
||||||
|
let e2 = m.pop();
|
||||||
|
let _ = m.pop();
|
||||||
|
m.push(e2);
|
||||||
|
m.push(Value::new_gc(seen));
|
||||||
|
m.push(Value::new_gc(worklist));
|
||||||
|
m.push(Value::new_inline(count));
|
||||||
|
reader.set_pc(PrimOpPhase::DeepSeqPush.ip() as usize);
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deep_seq_push<'gc, M: Machine<'gc>>(
|
||||||
|
m: &mut M,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
// stack: [e2, seen, worklist, counter]
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let counter = m.peek(0).as_inline::<i32>().unwrap();
|
||||||
|
if counter == 0 {
|
||||||
|
let _ = m.pop(); // counter
|
||||||
|
let _ = m.pop(); // worklist
|
||||||
|
let _ = m.pop(); // seen
|
||||||
|
let val = m.pop();
|
||||||
|
return m.return_from_primop(val, reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let worklist = m.peek_forced(1).as_gc::<List<'gc>>().unwrap();
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let item = worklist.unlock(mc).borrow_mut().pop().unwrap();
|
||||||
|
m.replace(0, Value::new_inline(counter - 1));
|
||||||
|
m.push(item);
|
||||||
|
|
||||||
|
// force item at TOS, resume at DeepSeqLoop after force
|
||||||
|
m.force_slot_to_pc(0, reader, mc, PrimOpPhase::DeepSeqLoop.ip() as usize)?;
|
||||||
|
reader.set_pc(PrimOpPhase::DeepSeqLoop.ip() as usize);
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deep_seq_loop<'gc, M: Machine<'gc>>(
|
||||||
|
m: &mut M,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
// stack after pop: [e2, seen, worklist, counter]
|
||||||
|
let item = m.pop();
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let counter = m.peek(0).as_inline::<i32>().unwrap();
|
||||||
|
|
||||||
|
let mut added: usize = 0;
|
||||||
|
if let Some(attrs) = item.as_gc::<AttrSet>() {
|
||||||
|
let attrs = &attrs.entries;
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let seen = m.peek_forced(2).as_gc::<List<'gc>>().unwrap();
|
||||||
|
if !is_value_in_seen(seen, item) {
|
||||||
|
add_value_to_seen(seen, mc, item);
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let worklist = m.peek_forced(1).as_gc::<List<'gc>>().unwrap();
|
||||||
|
{
|
||||||
|
let mut wl = worklist.unlock(mc).borrow_mut();
|
||||||
|
for &(_, v) in attrs.iter() {
|
||||||
|
wl.push(v);
|
||||||
|
}
|
||||||
|
added = attrs.len();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let Some(list) = item.as_gc::<List<'gc>>() {
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let seen = m.peek_forced(2).as_gc::<List<'gc>>().unwrap();
|
||||||
|
if !is_value_in_seen(seen, item) {
|
||||||
|
add_value_to_seen(seen, mc, item);
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let worklist = m.peek_forced(1).as_gc::<List<'gc>>().unwrap();
|
||||||
|
{
|
||||||
|
let inner = list.inner.borrow();
|
||||||
|
let mut wl = worklist.unlock(mc).borrow_mut();
|
||||||
|
for &v in inner.iter() {
|
||||||
|
wl.push(v);
|
||||||
|
}
|
||||||
|
added = inner.len();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m.replace(0, Value::new_inline(counter + added as i32));
|
||||||
|
reader.set_pc(PrimOpPhase::DeepSeqPush.ip() as usize);
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn force_result_shallow<'gc, M: Machine<'gc>>(
|
||||||
|
m: &mut M,
|
||||||
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
m.force_slot(0, reader, mc)?;
|
||||||
|
let val = m.peek_forced(0);
|
||||||
|
|
||||||
|
let (count, has_children) = if let Some(attrs) = val.as_gc::<AttrSet>() {
|
||||||
|
let len = attrs.entries.len();
|
||||||
|
(len, len > 0)
|
||||||
|
} else if let Some(list) = val.as_gc::<List<'gc>>() {
|
||||||
|
let len = list.inner.borrow().len();
|
||||||
|
(len, len > 0)
|
||||||
|
} else {
|
||||||
|
(0, false)
|
||||||
|
};
|
||||||
|
|
||||||
|
if !has_children {
|
||||||
|
let val = m.pop();
|
||||||
|
return m.finish_ok(ctx.convert_value(val));
|
||||||
|
}
|
||||||
|
|
||||||
|
m.push(Value::new_inline(0i32));
|
||||||
|
m.push(Value::new_inline(count as i32));
|
||||||
|
reader.set_pc(PrimOpPhase::ForceResultShallowPush.ip() as usize);
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn force_result_shallow_push<'gc, M: Machine<'gc>>(
|
||||||
|
m: &mut M,
|
||||||
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let idx = m.peek(1).as_inline::<i32>().unwrap();
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let len = m.peek(0).as_inline::<i32>().unwrap();
|
||||||
|
|
||||||
|
if idx == len {
|
||||||
|
let _ = m.pop(); // len
|
||||||
|
let _ = m.pop(); // idx
|
||||||
|
let val = m.pop();
|
||||||
|
return m.finish_ok(ctx.convert_value(val));
|
||||||
|
}
|
||||||
|
|
||||||
|
let val = m.peek_forced(2);
|
||||||
|
let child = if let Some(attrs) = val.as_gc::<AttrSet>() {
|
||||||
|
attrs.entries.get(idx as usize).map(|&(_, v)| v)
|
||||||
|
} else if let Some(list) = val.as_gc::<List<'gc>>() {
|
||||||
|
list.inner.borrow().get(idx as usize).copied()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(child) = child {
|
||||||
|
m.replace(1, Value::new_inline(idx + 1));
|
||||||
|
m.push(child);
|
||||||
|
m.force_slot_to_pc(
|
||||||
|
0,
|
||||||
|
reader,
|
||||||
|
mc,
|
||||||
|
PrimOpPhase::ForceResultShallowLoop.ip() as usize,
|
||||||
|
)?;
|
||||||
|
reader.set_pc(PrimOpPhase::ForceResultShallowLoop.ip() as usize);
|
||||||
|
}
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn force_result_shallow_loop<'gc, M: Machine<'gc>>(
|
||||||
|
m: &mut M,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
_mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
let _ = m.pop(); // forced child
|
||||||
|
reader.set_pc(PrimOpPhase::ForceResultShallowPush.ip() as usize);
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn force_result_deep_finish<'gc, M: Machine<'gc>>(
|
||||||
|
m: &mut M,
|
||||||
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
let val = m.force_and_retry::<StrictValue>(reader, mc)?;
|
||||||
|
m.finish_ok(ctx.convert_value(val.relax()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_value_in_seen<'gc>(seen: Gc<'gc, List<'gc>>, val: Value<'gc>) -> bool {
|
||||||
|
if !is_container(val) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let target = val.to_bits();
|
||||||
|
for &v in seen.inner.borrow().iter() {
|
||||||
|
if v.to_bits() == target {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_value_to_seen<'gc>(seen: Gc<'gc, List<'gc>>, mc: &Mutation<'gc>, val: Value<'gc>) {
|
||||||
|
if is_container(val) {
|
||||||
|
seen.unlock(mc).borrow_mut().push(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn call_functor_1<'gc, M: Machine<'gc>>(
|
||||||
|
m: &mut M,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
// Stack invariant on every (re-)entry: [..., orig_arg, self, functor]
|
||||||
|
// where `functor` is TOS. Retries during force land back here safely.
|
||||||
|
let functor = m.force_and_retry::<StrictValue>(reader, mc)?;
|
||||||
|
// Stack now: [..., orig_arg, self]
|
||||||
|
let self_val = m.pop();
|
||||||
|
m.push(functor.relax());
|
||||||
|
// Stack: [..., orig_arg, functor]
|
||||||
|
// Call 1: functor(self). Resume into CallFunctor2 once it returns.
|
||||||
|
m.call(
|
||||||
|
reader,
|
||||||
|
mc,
|
||||||
|
self_val,
|
||||||
|
PrimOpPhase::CallFunctor2.ip() as usize,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn call_functor_2<'gc, M: Machine<'gc>>(
|
||||||
|
m: &mut M,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
// Stack on entry: [..., orig_arg, intermediate]
|
||||||
|
// call_stack top: synthetic frame with caller's resume_pc.
|
||||||
|
let intermediate = m.pop();
|
||||||
|
let orig_arg = m.pop();
|
||||||
|
let saved = m.pop_call_frame().expect("functor outer frame missing");
|
||||||
|
m.set_env(saved.env);
|
||||||
|
m.push(intermediate);
|
||||||
|
// Call 2: intermediate(orig_arg). Resume to caller.
|
||||||
|
m.call(reader, mc, orig_arg, saved.pc)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn call_pattern<'gc, M: Machine<'gc>>(
|
||||||
|
m: &mut M,
|
||||||
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
let (func, attrset) = m.force_and_retry::<(Gc<Closure>, Gc<AttrSet>)>(reader, mc)?;
|
||||||
|
|
||||||
|
let Closure {
|
||||||
|
ip,
|
||||||
|
n_locals,
|
||||||
|
env,
|
||||||
|
pattern,
|
||||||
|
} = *func;
|
||||||
|
let Some(pattern) = pattern else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
// TODO: get function name
|
||||||
|
// TODO: param spans
|
||||||
|
if !pattern.ellipsis {
|
||||||
|
for key in pattern.required.iter().copied() {
|
||||||
|
if attrset.lookup(key).is_none() {
|
||||||
|
let name = ctx.resolve_string(key);
|
||||||
|
return m.finish_err(Error::eval_error(format!(
|
||||||
|
"function 'anonymous lambda' called without required argument '{name}'"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for &(key, _) in attrset.entries.iter() {
|
||||||
|
let is_expected = pattern.required.contains(&key) || pattern.optional.contains(&key);
|
||||||
|
if !is_expected {
|
||||||
|
let name = ctx.resolve_string(key);
|
||||||
|
return m.finish_err(Error::eval_error(format!(
|
||||||
|
"function 'anonymous lambda' called with unexpected argument '{name}'"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_env = Gc::new(
|
||||||
|
mc,
|
||||||
|
RefLock::new(Env::with_arg(Value::new_gc(attrset), n_locals, env)),
|
||||||
|
);
|
||||||
|
reader.set_pc(ip as usize);
|
||||||
|
m.set_env(new_env);
|
||||||
|
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_container(val: Value<'_>) -> bool {
|
||||||
|
val.is::<AttrSet>() || val.is::<List<'_>>()
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
use fix_abstract_vm::{
|
||||||
|
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>>(
|
||||||
|
m: &mut M,
|
||||||
|
_ctx: &mut impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
let val = m.force_and_retry::<StrictValue>(reader, mc)?;
|
||||||
|
if val.is::<StringId>() || val.is::<NixString>() {
|
||||||
|
return m.return_from_primop(val.relax(), reader);
|
||||||
|
}
|
||||||
|
if let Some(p) = val.as_inline::<Path>() {
|
||||||
|
return m.return_from_primop(Value::new_inline(p.0), reader);
|
||||||
|
}
|
||||||
|
// TODO: derivations / `__toString` / `outPath`,
|
||||||
|
// numbers, lists.
|
||||||
|
m.finish_err(Error::eval_error(format!(
|
||||||
|
"cannot coerce {} to a string",
|
||||||
|
val.ty()
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn type_of<'gc, M: Machine<'gc>>(
|
||||||
|
m: &mut M,
|
||||||
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
let val = m.force_and_retry::<StrictValue>(reader, mc)?;
|
||||||
|
let name: &str = match val.ty() {
|
||||||
|
NixType::Int => "int",
|
||||||
|
NixType::Float => "float",
|
||||||
|
NixType::Bool => "bool",
|
||||||
|
NixType::Null => "null",
|
||||||
|
NixType::String => "string",
|
||||||
|
NixType::Path => "path",
|
||||||
|
NixType::AttrSet => "set",
|
||||||
|
NixType::List => "list",
|
||||||
|
NixType::Closure | NixType::PrimOp | NixType::PrimOpApp => "lambda",
|
||||||
|
NixType::Thunk => unreachable!("forced"),
|
||||||
|
};
|
||||||
|
let sid = ctx.intern_string(name);
|
||||||
|
m.return_from_primop(Value::new_inline(sid), reader)
|
||||||
|
}
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use fix_abstract_vm::{
|
||||||
|
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;
|
||||||
|
|
||||||
|
pub fn import<'gc, M: Machine<'gc>>(
|
||||||
|
m: &mut M,
|
||||||
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
// stack: [path]
|
||||||
|
let path_val = m.force_and_retry::<StrictValue>(reader, mc)?;
|
||||||
|
let path_str = match ctx.get_string_or_path(path_val) {
|
||||||
|
Some(s) => s.to_owned(),
|
||||||
|
None => {
|
||||||
|
return m.finish_err(Error::eval_error(format!(
|
||||||
|
"expected a path or string, got {}",
|
||||||
|
path_val.ty()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let abs = match resolve_import_target(&path_str) {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(e) => return m.finish_err(e),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(cached) = m.import_cache_get(&abs) {
|
||||||
|
return m.return_from_primop(cached, reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stash the resolved path on the stack as a string-id so the
|
||||||
|
// finalizer can use it as the cache key. The slot we pop here was
|
||||||
|
// freed by `force_and_retry`, so we simply push.
|
||||||
|
let path_sid = ctx.intern_string(abs.to_string_lossy());
|
||||||
|
m.push(Value::new_inline(path_sid));
|
||||||
|
let env = m.env();
|
||||||
|
m.push_call_frame(CallFrame {
|
||||||
|
pc: PrimOpPhase::ImportFinalize.ip() as usize,
|
||||||
|
thunk: None,
|
||||||
|
env,
|
||||||
|
});
|
||||||
|
|
||||||
|
m.set_pending_load(PendingLoad {
|
||||||
|
path: abs,
|
||||||
|
scope: None,
|
||||||
|
});
|
||||||
|
Step::Break(Break::LoadFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn import_finalize<'gc, M: Machine<'gc>>(
|
||||||
|
m: &mut M,
|
||||||
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
) -> Step {
|
||||||
|
// stack: [path_sid, return_value]
|
||||||
|
let val = m.pop();
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let path_sid = m.pop().as_inline::<StringId>().unwrap();
|
||||||
|
// The cache key is keyed by the absolute path string we interned in
|
||||||
|
// `import`. Resolve it back to the host PathBuf.
|
||||||
|
let path_str = ctx.resolve_string(path_sid).to_owned();
|
||||||
|
m.import_cache_insert(PathBuf::from(path_str), val);
|
||||||
|
m.push(val);
|
||||||
|
let Some(CallFrame {
|
||||||
|
pc: ret_pc,
|
||||||
|
thunk: _,
|
||||||
|
env,
|
||||||
|
}) = m.pop_call_frame()
|
||||||
|
else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
reader.set_pc(ret_pc);
|
||||||
|
// FIXME:
|
||||||
|
// m.dec_call_depth();
|
||||||
|
m.set_env(env);
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scoped_import<'gc, M: Machine<'gc>>(
|
||||||
|
m: &mut M,
|
||||||
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
// stack: [scope, path]
|
||||||
|
let (scope_attrs, path_val) = m.force_and_retry::<(Gc<AttrSet>, StrictValue)>(reader, mc)?;
|
||||||
|
let path_str = match ctx.get_string_or_path(path_val) {
|
||||||
|
Some(s) => s.to_owned(),
|
||||||
|
None => {
|
||||||
|
return m.finish_err(Error::eval_error(format!(
|
||||||
|
"expected a path or string, got {}",
|
||||||
|
path_val.ty()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let abs = match resolve_import_target(&path_str) {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(e) => return m.finish_err(e),
|
||||||
|
};
|
||||||
|
|
||||||
|
let keys: HashSet<StringId> = scope_attrs.entries.iter().map(|&(k, _)| k).collect();
|
||||||
|
let slot_id = m.scope_slots_push(Value::new_gc(scope_attrs));
|
||||||
|
|
||||||
|
let env = m.env();
|
||||||
|
m.push_call_frame(CallFrame {
|
||||||
|
pc: PrimOpPhase::ScopedImportFinalize.ip() as usize,
|
||||||
|
thunk: None,
|
||||||
|
env,
|
||||||
|
});
|
||||||
|
|
||||||
|
m.set_pending_load(PendingLoad {
|
||||||
|
path: abs,
|
||||||
|
scope: Some(PendingScope { keys, slot_id }),
|
||||||
|
});
|
||||||
|
Step::Break(Break::LoadFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scoped_import_finalize<'gc, M: Machine<'gc>>(
|
||||||
|
m: &mut M,
|
||||||
|
_ctx: &mut impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
_mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
// stack: [return_value]
|
||||||
|
// We intentionally do NOT pop the slot from `scope_slots` so that
|
||||||
|
// closures or thunks created inside the imported file can still
|
||||||
|
// resolve their scope after `scopedImport` returns.
|
||||||
|
let val = m.pop();
|
||||||
|
m.return_from_primop(val, reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn path_exists<'gc, M: Machine<'gc>>(
|
||||||
|
m: &mut M,
|
||||||
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
let path_val = m.force_and_retry::<StrictValue>(reader, mc)?;
|
||||||
|
// pathExists requires an absolute path. A `Path` value is
|
||||||
|
// always absolute; a string is accepted only if it starts with `/`.
|
||||||
|
let (path, is_path_value) = if let Some(p) = path_val.as_inline::<Path>() {
|
||||||
|
(ctx.resolve_string(p.0).to_owned(), true)
|
||||||
|
} else if let Some(s) = ctx.get_string(path_val) {
|
||||||
|
(s.to_owned(), false)
|
||||||
|
} else {
|
||||||
|
return m.finish_err(Error::eval_error(format!(
|
||||||
|
"expected a path or string, got {}",
|
||||||
|
path_val.ty()
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
if !is_path_value && !path.starts_with('/') {
|
||||||
|
return m.finish_err(Error::eval_error(format!(
|
||||||
|
"string '{path}' doesn't represent an absolute path"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
// CppNix collapses consecutive slashes and resolves `.` / `..` lexically
|
||||||
|
// before checking. Trailing-slash / trailing-dot mean "must be a directory".
|
||||||
|
let must_be_dir = path.ends_with('/') || path.ends_with("/.");
|
||||||
|
let canon = canon_path_str(&path);
|
||||||
|
let p = std::path::Path::new(&canon);
|
||||||
|
let exists = if must_be_dir {
|
||||||
|
std::fs::metadata(p).map(|m| m.is_dir()).unwrap_or(false)
|
||||||
|
} else {
|
||||||
|
std::fs::symlink_metadata(p).is_ok()
|
||||||
|
};
|
||||||
|
m.return_from_primop(Value::new_inline(exists), reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert the user-supplied path string into an absolute, dotted-segment
|
||||||
|
/// resolved `PathBuf` and append `default.nix` if the target is a directory.
|
||||||
|
fn resolve_import_target(path: &str) -> Result<PathBuf, Box<Error>> {
|
||||||
|
let mut abs = PathBuf::from(path);
|
||||||
|
if !abs.is_absolute() {
|
||||||
|
return Err(Error::eval_error(format!(
|
||||||
|
"import: expected an absolute path, got '{path}'"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
if abs.is_dir() {
|
||||||
|
abs.push("default.nix");
|
||||||
|
}
|
||||||
|
Ok(abs)
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
mod control;
|
||||||
|
mod conv;
|
||||||
|
mod io;
|
||||||
|
mod list;
|
||||||
|
mod path;
|
||||||
|
|
||||||
|
pub use control::*;
|
||||||
|
pub use conv::*;
|
||||||
|
use fix_abstract_vm::{BytecodeReader, Machine, Step, VmRuntimeCtx};
|
||||||
|
use fix_builtins::PrimOpPhase;
|
||||||
|
use fix_error::Error;
|
||||||
|
use gc_arena::Mutation;
|
||||||
|
pub use io::*;
|
||||||
|
pub use list::*;
|
||||||
|
pub use path::*;
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
|
pub fn dispatch_primop<'gc, M: Machine<'gc>>(
|
||||||
|
m: &mut M,
|
||||||
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
use PrimOpPhase::*;
|
||||||
|
let phase_disc = reader.read_u8();
|
||||||
|
let Ok(phase) = PrimOpPhase::try_from(phase_disc) else {
|
||||||
|
return m.finish_err(Error::eval_error("invalid primop phase"));
|
||||||
|
};
|
||||||
|
match phase {
|
||||||
|
Abort => abort(m, ctx, reader, mc),
|
||||||
|
|
||||||
|
DeepSeq => deep_seq_force_top(m, reader, mc),
|
||||||
|
DeepSeqPush => deep_seq_push(m, reader, mc),
|
||||||
|
DeepSeqLoop => deep_seq_loop(m, reader, mc),
|
||||||
|
Seq => seq(m, reader, mc),
|
||||||
|
|
||||||
|
FilterForceList => filter_force_list(m, reader, mc),
|
||||||
|
FilterCallPred => filter_call_pred(m, reader, mc),
|
||||||
|
FilterCheck => filter_check(m, reader, mc),
|
||||||
|
|
||||||
|
FoldlStrict => foldl_strict_entry(m, reader, mc),
|
||||||
|
FoldlStrictEmpty => foldl_strict_empty(m, reader, mc),
|
||||||
|
FoldlStrictCall1 => foldl_strict_call1(m, reader, mc),
|
||||||
|
FoldlStrictCall2 => foldl_strict_call2(m, reader, mc),
|
||||||
|
FoldlStrictUpdate => foldl_strict_update(m, reader, mc),
|
||||||
|
|
||||||
|
ForceResultShallow => force_result_shallow(m, ctx, reader, mc),
|
||||||
|
ForceResultShallowPush => force_result_shallow_push(m, ctx, reader, mc),
|
||||||
|
ForceResultShallowLoop => force_result_shallow_loop(m, reader, mc),
|
||||||
|
ForceResultDeepFinish => force_result_deep_finish(m, ctx, reader, mc),
|
||||||
|
|
||||||
|
CallPattern => call_pattern(m, ctx, reader, mc),
|
||||||
|
CallFunctor1 => call_functor_1(m, reader, mc),
|
||||||
|
CallFunctor2 => call_functor_2(m, reader, mc),
|
||||||
|
|
||||||
|
Import => import(m, ctx, reader, mc),
|
||||||
|
ImportFinalize => import_finalize(m, ctx, reader),
|
||||||
|
ScopedImport => scoped_import(m, ctx, reader, mc),
|
||||||
|
ScopedImportFinalize => scoped_import_finalize(m, ctx, reader, mc),
|
||||||
|
|
||||||
|
PathExists => path_exists(m, ctx, reader, mc),
|
||||||
|
ToPath => to_path(m, ctx, reader, mc),
|
||||||
|
IsPath => is_path(m, reader, mc),
|
||||||
|
ToString => to_string(m, ctx, reader, mc),
|
||||||
|
TypeOf => type_of(m, ctx, reader, mc),
|
||||||
|
|
||||||
|
phase => todo!("primop phase {phase:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
use fix_abstract_vm::{
|
||||||
|
BytecodeReader, List, Machine, MachineExt, NixType, Step, StrictValue, Value,
|
||||||
|
};
|
||||||
|
use fix_builtins::PrimOpPhase;
|
||||||
|
use gc_arena::Mutation;
|
||||||
|
|
||||||
|
pub fn filter_force_list<'gc, M: Machine<'gc>>(
|
||||||
|
m: &mut M,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
m.force_slot(0, reader, mc)?;
|
||||||
|
let list = match m.peek_forced(0).expect_gc::<List>() {
|
||||||
|
Ok(list) => list,
|
||||||
|
Err(got) => return m.finish_type_err(NixType::List, got),
|
||||||
|
};
|
||||||
|
if list.inner.borrow().is_empty() {
|
||||||
|
let val = m.pop();
|
||||||
|
return m.return_from_primop(val, reader);
|
||||||
|
}
|
||||||
|
// prepare stack layout: [ pred list idx acc ]
|
||||||
|
m.push(Value::new_inline(0));
|
||||||
|
m.push(Value::new_gc(List::new_gc(mc)));
|
||||||
|
reader.set_pc(PrimOpPhase::FilterCallPred.ip() as usize);
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn filter_call_pred<'gc, M: Machine<'gc>>(
|
||||||
|
m: &mut M,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
m.force_slot(3, reader, mc)?;
|
||||||
|
let pred = m.peek_forced(3);
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let idx = m.peek(1).as_inline::<i32>().unwrap();
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let elem = m.peek_forced(2).as_gc::<List>().unwrap().inner.borrow()[idx as usize];
|
||||||
|
m.push(pred.relax());
|
||||||
|
m.call(reader, mc, elem, PrimOpPhase::FilterCheck.ip() as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn filter_check<'gc, M: Machine<'gc>>(
|
||||||
|
m: &mut M,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
let ret = m.force_and_retry::<bool>(reader, mc)?;
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let idx = m.peek(1).as_inline::<i32>().unwrap();
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let list = m.peek_forced(2).as_gc::<List>().unwrap();
|
||||||
|
let list = list.inner.borrow();
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let acc = m.peek_forced(0).as_gc::<List>().unwrap();
|
||||||
|
if ret {
|
||||||
|
let mut acc = acc.unlock(mc).borrow_mut();
|
||||||
|
acc.push(list[idx as usize]);
|
||||||
|
}
|
||||||
|
if idx as usize == list.len() - 1 {
|
||||||
|
let acc = m.pop();
|
||||||
|
let _ = m.pop(); // idx
|
||||||
|
let _ = m.pop(); // list
|
||||||
|
let _ = m.pop(); // pred
|
||||||
|
return m.return_from_primop(acc, reader);
|
||||||
|
}
|
||||||
|
m.replace(1, Value::new_inline(idx + 1));
|
||||||
|
reader.set_pc(PrimOpPhase::FilterCallPred.ip() as usize);
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// foldl' op nul list
|
||||||
|
//
|
||||||
|
// Stack layouts across phases:
|
||||||
|
// Entry: [op, nul, list]
|
||||||
|
// Empty: [op, nul]
|
||||||
|
// Call1: [op, list, idx, acc]
|
||||||
|
// Call2: [op, list, idx, acc, intermediate]
|
||||||
|
// Update: [op, list, idx, acc, result]
|
||||||
|
pub fn foldl_strict_entry<'gc, M: Machine<'gc>>(
|
||||||
|
m: &mut M,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
m.force_slot(0, reader, mc)?;
|
||||||
|
let list_val = m.peek_forced(0);
|
||||||
|
let Some(list) = list_val.as_gc::<List>() else {
|
||||||
|
return m.finish_type_err(NixType::List, list_val.ty());
|
||||||
|
};
|
||||||
|
if list.inner.borrow().is_empty() {
|
||||||
|
let _ = m.pop(); // list
|
||||||
|
reader.set_pc(PrimOpPhase::FoldlStrictEmpty.ip() as usize);
|
||||||
|
return Step::Continue(());
|
||||||
|
}
|
||||||
|
let list_val = m.pop();
|
||||||
|
let nul_val = m.pop();
|
||||||
|
m.push(list_val);
|
||||||
|
m.push(Value::new_inline(0i32));
|
||||||
|
m.push(nul_val);
|
||||||
|
reader.set_pc(PrimOpPhase::FoldlStrictCall1.ip() as usize);
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn foldl_strict_empty<'gc, M: Machine<'gc>>(
|
||||||
|
m: &mut M,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
let nul = m.force_and_retry::<StrictValue>(reader, mc)?;
|
||||||
|
let _ = m.pop(); // op
|
||||||
|
m.return_from_primop(nul.relax(), reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn foldl_strict_call1<'gc, M: Machine<'gc>>(
|
||||||
|
m: &mut M,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
m.force_slot(3, reader, mc)?;
|
||||||
|
let op = m.peek_forced(3);
|
||||||
|
let acc = m.peek(0);
|
||||||
|
m.push(op.relax());
|
||||||
|
m.call(reader, mc, acc, PrimOpPhase::FoldlStrictCall2.ip() as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn foldl_strict_call2<'gc, M: Machine<'gc>>(
|
||||||
|
m: &mut M,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let idx = m.peek(2).as_inline::<i32>().unwrap();
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let list = m.peek_forced(3).as_gc::<List>().unwrap();
|
||||||
|
let elem = list.inner.borrow()[idx as usize];
|
||||||
|
m.call(
|
||||||
|
reader,
|
||||||
|
mc,
|
||||||
|
elem,
|
||||||
|
PrimOpPhase::FoldlStrictUpdate.ip() as usize,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn foldl_strict_update<'gc, M: Machine<'gc>>(
|
||||||
|
m: &mut M,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
_mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
let result = m.pop();
|
||||||
|
m.replace(0, result);
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let idx = m.peek(1).as_inline::<i32>().unwrap();
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let list = m.peek_forced(2).as_gc::<List>().unwrap();
|
||||||
|
let len = list.inner.borrow().len();
|
||||||
|
if (idx as usize) + 1 == len {
|
||||||
|
let acc = m.pop();
|
||||||
|
let _ = m.pop(); // idx
|
||||||
|
let _ = m.pop(); // list
|
||||||
|
let _ = m.pop(); // op
|
||||||
|
return m.return_from_primop(acc, reader);
|
||||||
|
}
|
||||||
|
m.replace(1, Value::new_inline(idx + 1));
|
||||||
|
reader.set_pc(PrimOpPhase::FoldlStrictCall1.ip() as usize);
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
use fix_abstract_vm::{
|
||||||
|
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>>(
|
||||||
|
m: &mut M,
|
||||||
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
// coerce to path THEN TO STRING
|
||||||
|
let val = m.force_and_retry::<StrictValue>(reader, mc)?;
|
||||||
|
if let Some(Path(s)) = val.as_inline::<Path>() {
|
||||||
|
return m.return_from_primop(Value::new_inline(s), reader);
|
||||||
|
}
|
||||||
|
let Some(s) = ctx.get_string(val) else {
|
||||||
|
return m.finish_err(Error::eval_error(format!(
|
||||||
|
"cannot coerce {} to a path",
|
||||||
|
val.ty()
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
if !s.starts_with('/') {
|
||||||
|
return m.finish_err(Error::eval_error(format!(
|
||||||
|
"string '{s}' doesn't represent an absolute path"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
let canon = canon_path_str(s);
|
||||||
|
let sid = ctx.intern_string(canon);
|
||||||
|
m.return_from_primop(Value::new_inline(sid), reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_path<'gc, M: Machine<'gc>>(
|
||||||
|
m: &mut M,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
let val = m.force_and_retry::<StrictValue>(reader, mc)?;
|
||||||
|
let is_path = val.is::<Path>();
|
||||||
|
m.return_from_primop(Value::new_inline(is_path), reader)
|
||||||
|
}
|
||||||
@@ -19,3 +19,5 @@ fix-builtins = { path = "../fix-builtins" }
|
|||||||
fix-codegen = { path = "../fix-codegen" }
|
fix-codegen = { path = "../fix-codegen" }
|
||||||
fix-common = { path = "../fix-common" }
|
fix-common = { path = "../fix-common" }
|
||||||
fix-error = { path = "../fix-error" }
|
fix-error = { path = "../fix-error" }
|
||||||
|
fix-abstract-vm = { path = "../fix-abstract-vm" }
|
||||||
|
fix-primops = { path = "../fix-primops" }
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
use fix_error::Error;
|
|
||||||
|
|
||||||
use crate::VmError;
|
|
||||||
|
|
||||||
pub(crate) fn vm_err(msg: impl Into<String>) -> VmError {
|
|
||||||
VmError::Uncatchable(Error::eval_error(msg.into()))
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
use fix_abstract_vm::*;
|
||||||
use gc_arena::{Gc, Mutation, RefLock};
|
use gc_arena::{Gc, Mutation, RefLock};
|
||||||
|
|
||||||
use crate::instructions::misc::canon_path_str;
|
use crate::{BytecodeReader, NixNum, Step, VmError, VmRuntimeCtx};
|
||||||
use crate::value::*;
|
|
||||||
use crate::{BytecodeReader, NixNum, Step, VmError, VmRuntimeCtx, VmRuntimeCtxExt as _};
|
|
||||||
|
|
||||||
impl<'gc> crate::Vm<'gc> {
|
impl<'gc> crate::Vm<'gc> {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@@ -15,7 +14,7 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
|
let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
|
||||||
// CppNix: if the LHS is a path, the result is a path obtained by
|
// if the LHS is a path, the result is a path obtained by
|
||||||
// canonicalizing the concatenated string. RHS may be a path or a
|
// canonicalizing the concatenated string. RHS may be a path or a
|
||||||
// string. (A `string + path` keeps the string-typed result, handled
|
// string. (A `string + path` keeps the string-typed result, handled
|
||||||
// by the next branch.)
|
// by the next branch.)
|
||||||
@@ -30,7 +29,7 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
let combined = format!("{ls}{rs}");
|
let combined = format!("{ls}{rs}");
|
||||||
let canon = canon_path_str(&combined);
|
let canon = canon_path_str(&combined);
|
||||||
let sid = ctx.intern_string(canon);
|
let sid = ctx.intern_string(canon);
|
||||||
self.push(Value::new_inline(crate::value::Path(sid)));
|
self.push(Value::new_inline(fix_abstract_vm::Path(sid)));
|
||||||
return Step::Continue(());
|
return Step::Continue(());
|
||||||
}
|
}
|
||||||
if let (Some(ls), Some(rs)) = (ctx.get_string(lhs), ctx.get_string_or_path(rhs)) {
|
if let (Some(ls), Some(rs)) = (ctx.get_string(lhs), ctx.get_string_or_path(rhs)) {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
|
use fix_abstract_vm::{resolve_operand, *};
|
||||||
use fix_builtins::PrimOpPhase;
|
use fix_builtins::PrimOpPhase;
|
||||||
use fix_error::Error;
|
use fix_error::Error;
|
||||||
use gc_arena::{Gc, Mutation, RefLock};
|
use gc_arena::{Gc, Mutation, RefLock};
|
||||||
|
|
||||||
use crate::value::*;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
BytecodeReader, CallFrame, Closure, Env, ForceMode, Step, ThunkState, VmRuntimeCtx,
|
BytecodeReader, CallFrame, Closure, Env, ForceMode, Step, ThunkState, VmRuntimeCtx,
|
||||||
VmRuntimeCtxExt,
|
VmRuntimeCtxExt,
|
||||||
@@ -120,7 +120,7 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
let arg = reader.read_operand_data(ctx).resolve(mc, self);
|
let arg = resolve_operand(&reader.read_operand_data(ctx), mc, self);
|
||||||
let pc = reader.pc();
|
let pc = reader.pc();
|
||||||
self.call(reader, mc, arg, pc)
|
self.call(reader, mc, arg, pc)
|
||||||
}
|
}
|
||||||
@@ -170,4 +170,14 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
self.env = env;
|
self.env = env;
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_dispatch_primop(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
fix_primops::dispatch_primop(self, ctx, reader, mc)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
|
use fix_abstract_vm::{NixType, resolve_operand};
|
||||||
use fix_common::StringId;
|
use fix_common::StringId;
|
||||||
use fix_error::Error;
|
use fix_error::Error;
|
||||||
use gc_arena::{Gc, RefLock};
|
use gc_arena::{Gc, RefLock};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
use crate::value::NixType;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
AttrSet, BytecodeReader, List, Step, StrictValue, Value, VmRuntimeCtx, VmRuntimeCtxExt,
|
AttrSet, BytecodeReader, List, Step, StrictValue, Value, VmRuntimeCtx, VmRuntimeCtxExt,
|
||||||
};
|
};
|
||||||
@@ -43,13 +43,13 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
|
|
||||||
for _ in 0..static_count {
|
for _ in 0..static_count {
|
||||||
let key = reader.read_string_id();
|
let key = reader.read_string_id();
|
||||||
let val = reader.read_operand_data(ctx).resolve(mc, self);
|
let val = resolve_operand(&reader.read_operand_data(ctx), mc, self);
|
||||||
let _span_id = reader.read_u32();
|
let _span_id = reader.read_u32();
|
||||||
kv.push((key, val));
|
kv.push((key, val));
|
||||||
}
|
}
|
||||||
|
|
||||||
for key in dyn_keys {
|
for key in dyn_keys {
|
||||||
let val = reader.read_operand_data(ctx).resolve(mc, self);
|
let val = resolve_operand(&reader.read_operand_data(ctx), mc, self);
|
||||||
let _span_id = reader.read_u32();
|
let _span_id = reader.read_u32();
|
||||||
if let Some(key) = key {
|
if let Some(key) = key {
|
||||||
kv.push((key, val))
|
kv.push((key, val))
|
||||||
@@ -314,7 +314,7 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
let count = reader.read_u32() as usize;
|
let count = reader.read_u32() as usize;
|
||||||
let mut items: SmallVec<[Value; 4]> = SmallVec::with_capacity(count);
|
let mut items: SmallVec<[Value; 4]> = SmallVec::with_capacity(count);
|
||||||
for _ in 0..count {
|
for _ in 0..count {
|
||||||
items.push(reader.read_operand_data(ctx).resolve(mc, self));
|
items.push(resolve_operand(&reader.read_operand_data(ctx), mc, self));
|
||||||
}
|
}
|
||||||
let list = Gc::new(
|
let list = Gc::new(
|
||||||
mc,
|
mc,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
use fix_abstract_vm::*;
|
||||||
use fix_error::Error;
|
use fix_error::Error;
|
||||||
use gc_arena::Mutation;
|
use gc_arena::Mutation;
|
||||||
|
|
||||||
use crate::value::*;
|
|
||||||
use crate::{BytecodeReader, Step, VmRuntimeCtx};
|
use crate::{BytecodeReader, Step, VmRuntimeCtx};
|
||||||
|
|
||||||
impl<'gc> crate::Vm<'gc> {
|
impl<'gc> crate::Vm<'gc> {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
use std::path::{Component, PathBuf};
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use fix_abstract_vm::{AttrSet, NixString, Path, StrictValue, canon_path_str};
|
||||||
use fix_builtins::BuiltinId;
|
use fix_builtins::BuiltinId;
|
||||||
use fix_common::StringId;
|
use fix_common::StringId;
|
||||||
use fix_error::Error;
|
use fix_error::Error;
|
||||||
use num_enum::TryFromPrimitive;
|
use num_enum::TryFromPrimitive;
|
||||||
|
|
||||||
use crate::value::{AttrSet, NixString, Path, StrictValue};
|
|
||||||
use crate::{BytecodeReader, PrimOp, Step, Value, VmRuntimeCtx, VmRuntimeCtxExt};
|
use crate::{BytecodeReader, PrimOp, Step, Value, VmRuntimeCtx, VmRuntimeCtxExt};
|
||||||
|
|
||||||
impl<'gc> crate::Vm<'gc> {
|
impl<'gc> crate::Vm<'gc> {
|
||||||
@@ -163,20 +163,3 @@ fn resolve_path_str(current_dir: &str, path: &str) -> Result<String, Box<Error>>
|
|||||||
};
|
};
|
||||||
Ok(canon_path_str(&raw))
|
Ok(canon_path_str(&raw))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn canon_path_str(path: impl AsRef<std::path::Path>) -> String {
|
|
||||||
let p = path.as_ref();
|
|
||||||
let mut normalized = PathBuf::new();
|
|
||||||
for component in p.components() {
|
|
||||||
match component {
|
|
||||||
Component::Prefix(p) => normalized.push(p.as_os_str()),
|
|
||||||
Component::RootDir => normalized.push("/"),
|
|
||||||
Component::CurDir => {}
|
|
||||||
Component::ParentDir => {
|
|
||||||
normalized.pop();
|
|
||||||
}
|
|
||||||
Component::Normal(c) => normalized.push(c),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
normalized.to_string_lossy().into_owned()
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
|
use fix_abstract_vm::{resolve_operand, *};
|
||||||
use fix_common::Symbol;
|
use fix_common::Symbol;
|
||||||
use fix_error::Error;
|
use fix_error::Error;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
use crate::value::*;
|
|
||||||
use crate::{Break, BytecodeReader, CallFrame, Step, VmRuntimeCtx};
|
use crate::{Break, BytecodeReader, CallFrame, Step, VmRuntimeCtx};
|
||||||
|
|
||||||
impl<'gc> crate::Vm<'gc> {
|
impl<'gc> crate::Vm<'gc> {
|
||||||
@@ -20,7 +20,7 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
let n = reader.read_u8();
|
let n = reader.read_u8();
|
||||||
let mut namespaces = SmallVec::<[_; 2]>::new();
|
let mut namespaces = SmallVec::<[_; 2]>::new();
|
||||||
for _ in 0..n {
|
for _ in 0..n {
|
||||||
namespaces.push(reader.read_operand_data(ctx).resolve(mc, self));
|
namespaces.push(resolve_operand(&reader.read_operand_data(ctx), mc, self));
|
||||||
}
|
}
|
||||||
|
|
||||||
let resume_pc = reader.inst_start_pc();
|
let resume_pc = reader.inst_start_pc();
|
||||||
|
|||||||
+164
-284
@@ -13,222 +13,17 @@ use fix_common::StringId;
|
|||||||
use fix_error::{Error, Result, Source};
|
use fix_error::{Error, Result, Source};
|
||||||
use gc_arena::arena::CollectionPhase;
|
use gc_arena::arena::CollectionPhase;
|
||||||
use gc_arena::{Arena, Collect, Gc, Mutation, RefLock, Rootable};
|
use gc_arena::{Arena, Collect, Gc, Mutation, RefLock, Rootable};
|
||||||
use hashbrown::{HashMap, HashSet};
|
use hashbrown::HashMap;
|
||||||
use num_enum::TryFromPrimitive;
|
use num_enum::TryFromPrimitive;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
mod boxing;
|
|
||||||
mod bytecode_reader;
|
|
||||||
#[cfg(feature = "tailcall")]
|
#[cfg(feature = "tailcall")]
|
||||||
mod dispatch_tailcall;
|
mod dispatch_tailcall;
|
||||||
mod forced;
|
pub use fix_abstract_vm::*;
|
||||||
mod value;
|
|
||||||
pub use value::StaticValue;
|
|
||||||
use value::*;
|
|
||||||
mod helpers;
|
|
||||||
mod instructions;
|
mod instructions;
|
||||||
use bytecode_reader::BytecodeReader;
|
|
||||||
use forced::Forced;
|
|
||||||
use helpers::*;
|
|
||||||
mod primops;
|
|
||||||
|
|
||||||
type VmResult<T> = std::result::Result<T, VmError>;
|
type VmResult<T> = std::result::Result<T, VmError>;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
enum VmError {
|
|
||||||
Catchable(String),
|
|
||||||
Uncatchable(Box<Error>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Box<Error>> for VmError {
|
|
||||||
fn from(e: Box<Error>) -> Self {
|
|
||||||
VmError::Uncatchable(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VmError {
|
|
||||||
fn into_error(self) -> Box<Error> {
|
|
||||||
match self {
|
|
||||||
VmError::Catchable(_) => todo!("Check for tryEval catch frames"),
|
|
||||||
VmError::Uncatchable(e) => e,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Collect, Clone, Copy, Debug, PartialEq, Eq, Default)]
|
|
||||||
#[collect(require_static)]
|
|
||||||
pub enum ForceMode {
|
|
||||||
#[default]
|
|
||||||
AsIs,
|
|
||||||
Shallow,
|
|
||||||
Deep,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait VmContext {
|
|
||||||
fn split(&mut self) -> (&mut impl VmCode, &mut impl VmRuntimeCtx);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait VmRuntimeCtx {
|
|
||||||
fn intern_string(&mut self, s: impl AsRef<str>) -> StringId;
|
|
||||||
fn resolve_string(&self, id: StringId) -> &str;
|
|
||||||
fn get_const(&self, id: u32) -> StaticValue;
|
|
||||||
fn add_const(&mut self, val: StaticValue) -> u32;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait VmCode {
|
|
||||||
fn bytecode(&self) -> &[u8];
|
|
||||||
fn compile_with_scope(
|
|
||||||
&mut self,
|
|
||||||
source: Source,
|
|
||||||
extra_scope: Option<ExtraScope>,
|
|
||||||
ctx: &mut impl VmRuntimeCtx,
|
|
||||||
) -> fix_error::Result<InstructionPtr>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extra scope passed to a re-entrant compile from inside a running VM.
|
|
||||||
///
|
|
||||||
/// Currently only `ScopedImport` is produced (by the `scopedImport` builtin),
|
|
||||||
/// but the variant is kept open so REPL bindings could later land here too.
|
|
||||||
pub enum ExtraScope {
|
|
||||||
ScopedImport {
|
|
||||||
keys: HashSet<StringId>,
|
|
||||||
slot_id: u32,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
trait VmRuntimeCtxExt: VmRuntimeCtx {
|
|
||||||
fn get_string<'a, 'gc: 'a>(&'a self, val: StrictValue<'gc>) -> Option<&'a str>;
|
|
||||||
fn get_string_or_path<'a, 'gc: 'a>(&'a self, val: StrictValue<'gc>) -> Option<&'a str>;
|
|
||||||
fn get_string_id<'a, 'gc: 'a>(
|
|
||||||
&'a mut self,
|
|
||||||
val: StrictValue<'gc>,
|
|
||||||
) -> std::result::Result<StringId, NixType>;
|
|
||||||
fn convert_value(&self, val: Value) -> fix_common::Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: VmRuntimeCtx> VmRuntimeCtxExt for T {
|
|
||||||
fn get_string<'a, 'gc: 'a>(&'a self, val: StrictValue<'gc>) -> Option<&'a str> {
|
|
||||||
if let Some(sid) = val.as_inline::<StringId>() {
|
|
||||||
Some(self.resolve_string(sid))
|
|
||||||
} else {
|
|
||||||
val.as_gc::<NixString>().map(|ns| ns.as_ref().as_str())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Like `get_string`, but also accepts `Path` values (returning their
|
|
||||||
/// underlying canonical-path string). Use this in places where Nix
|
|
||||||
/// would coerce a path to a string (string interpolation, file IO
|
|
||||||
/// builtins, etc.).
|
|
||||||
fn get_string_or_path<'a, 'gc: 'a>(&'a self, val: StrictValue<'gc>) -> Option<&'a str> {
|
|
||||||
if let Some(p) = val.as_inline::<Path>() {
|
|
||||||
Some(self.resolve_string(p.0))
|
|
||||||
} else {
|
|
||||||
self.get_string(val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_string_id<'a, 'gc: 'a>(
|
|
||||||
&'a mut self,
|
|
||||||
val: StrictValue<'gc>,
|
|
||||||
) -> std::result::Result<StringId, NixType> {
|
|
||||||
if let Some(sid) = val.as_inline::<StringId>() {
|
|
||||||
Ok(sid)
|
|
||||||
} else if let Some(s) = val.as_gc::<NixString>().map(|ns| ns.as_ref().as_str()) {
|
|
||||||
Ok(self.intern_string(s))
|
|
||||||
} else {
|
|
||||||
Err(val.ty())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn convert_value(&self, val: Value) -> fix_common::Value {
|
|
||||||
self.convert_value_with_seen(val, &mut HashSet::new())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
trait ConvertValueWithSeen: VmRuntimeCtx {
|
|
||||||
fn convert_value_with_seen(&self, val: Value, seen: &mut HashSet<u64>) -> fix_common::Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: VmRuntimeCtx> ConvertValueWithSeen for T {
|
|
||||||
fn convert_value_with_seen(&self, val: Value, seen: &mut HashSet<u64>) -> fix_common::Value {
|
|
||||||
use fix_common::Value;
|
|
||||||
if let Some(i) = val.as_inline::<i32>() {
|
|
||||||
Value::Int(i as i64)
|
|
||||||
} else if let Some(gc_i) = val.as_gc::<i64>() {
|
|
||||||
Value::Int(*gc_i)
|
|
||||||
} else if let Some(f) = val.as_float() {
|
|
||||||
Value::Float(f)
|
|
||||||
} else if let Some(b) = val.as_inline::<bool>() {
|
|
||||||
Value::Bool(b)
|
|
||||||
} else if val.is::<Null>() {
|
|
||||||
Value::Null
|
|
||||||
} else if let Some(sid) = val.as_inline::<StringId>() {
|
|
||||||
let s = self.resolve_string(sid).to_owned();
|
|
||||||
Value::String(s)
|
|
||||||
} else if let Some(ns) = val.as_gc::<NixString>() {
|
|
||||||
Value::String(ns.as_str().to_owned())
|
|
||||||
} else if let Some(p) = val.as_inline::<Path>() {
|
|
||||||
Value::Path(self.resolve_string(p.0).to_owned())
|
|
||||||
} else if let Some(attrs) = val.as_gc::<AttrSet>() {
|
|
||||||
let bits = val.to_bits();
|
|
||||||
if attrs.entries.is_empty() {
|
|
||||||
return Value::AttrSet(Default::default());
|
|
||||||
}
|
|
||||||
if !seen.insert(bits) {
|
|
||||||
return Value::Repeated;
|
|
||||||
}
|
|
||||||
let mut map = std::collections::BTreeMap::new();
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
Value::AttrSet(fix_common::AttrSet::new(map))
|
|
||||||
} else if let Some(list) = val.as_gc::<List>() {
|
|
||||||
let bits = val.to_bits();
|
|
||||||
if list.inner.borrow().is_empty() {
|
|
||||||
return Value::List(Default::default());
|
|
||||||
}
|
|
||||||
if !seen.insert(bits) {
|
|
||||||
return Value::Repeated;
|
|
||||||
}
|
|
||||||
let items: Vec<_> = list
|
|
||||||
.inner
|
|
||||||
.borrow()
|
|
||||||
.iter()
|
|
||||||
.copied()
|
|
||||||
.map(|v| self.convert_value_with_seen(v, seen))
|
|
||||||
.collect();
|
|
||||||
Value::List(fix_common::List::new(items))
|
|
||||||
} else if val.is::<Closure>() {
|
|
||||||
Value::Func
|
|
||||||
} else if let Some(thunk) = val.as_gc::<Thunk>() {
|
|
||||||
if let ThunkState::Evaluated(v) = *thunk.borrow() {
|
|
||||||
self.convert_value_with_seen(v.relax(), seen)
|
|
||||||
} else {
|
|
||||||
Value::Thunk
|
|
||||||
}
|
|
||||||
} else if let Some(primop) = val.as_inline::<PrimOp>() {
|
|
||||||
let name = fix_builtins::BUILTINS[primop.id as usize].0;
|
|
||||||
Value::PrimOp(name.strip_prefix("__").unwrap_or(name))
|
|
||||||
} else if let Some(app) = val.as_gc::<PrimOpApp>() {
|
|
||||||
let name = fix_builtins::BUILTINS[app.primop.id as usize].0;
|
|
||||||
Value::PrimOpApp(name.strip_prefix("__").unwrap_or(name))
|
|
||||||
} else {
|
|
||||||
Value::Null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(u8)]
|
|
||||||
enum Break {
|
|
||||||
Force,
|
|
||||||
Done,
|
|
||||||
LoadFile,
|
|
||||||
}
|
|
||||||
|
|
||||||
type Step = std::ops::ControlFlow<Break>;
|
|
||||||
|
|
||||||
#[derive(Collect)]
|
#[derive(Collect)]
|
||||||
#[collect(no_drop)]
|
#[collect(no_drop)]
|
||||||
pub struct Vm<'gc> {
|
pub struct Vm<'gc> {
|
||||||
@@ -259,66 +54,6 @@ pub struct Vm<'gc> {
|
|||||||
functor_sym: StringId,
|
functor_sym: StringId,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) struct PendingLoad {
|
|
||||||
pub path: PathBuf,
|
|
||||||
pub scope: Option<PendingScope>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) struct PendingScope {
|
|
||||||
pub keys: HashSet<StringId>,
|
|
||||||
pub slot_id: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum OperandData {
|
|
||||||
Const(StaticValue),
|
|
||||||
BigInt(i64),
|
|
||||||
Local { layer: u8, idx: u32 },
|
|
||||||
BuiltinConst(StringId),
|
|
||||||
Builtins,
|
|
||||||
ReplBinding(StringId),
|
|
||||||
ScopedImportBinding { slot_id: u32, name: StringId },
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OperandData {
|
|
||||||
fn resolve<'gc>(&self, mc: &Mutation<'gc>, root: &Vm<'gc>) -> Value<'gc> {
|
|
||||||
use OperandData::*;
|
|
||||||
match *self {
|
|
||||||
Const(sv) => sv.into(),
|
|
||||||
BigInt(val) => Value::new_gc(Gc::new(mc, val)),
|
|
||||||
Local { layer, idx } => {
|
|
||||||
let mut cur = root.env;
|
|
||||||
for _ in 0..layer {
|
|
||||||
let prev = cur.borrow().prev.expect("env chain too short");
|
|
||||||
cur = prev;
|
|
||||||
}
|
|
||||||
cur.borrow().locals[idx as usize]
|
|
||||||
}
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
BuiltinConst(id) => root
|
|
||||||
.builtins
|
|
||||||
.as_gc::<AttrSet>()
|
|
||||||
.unwrap()
|
|
||||||
.lookup(id)
|
|
||||||
.unwrap(),
|
|
||||||
Builtins => root.builtins,
|
|
||||||
ReplBinding(_id) => todo!(),
|
|
||||||
ScopedImportBinding { slot_id, name } => {
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let scope = root
|
|
||||||
.scope_slots
|
|
||||||
.get(slot_id as usize)
|
|
||||||
.expect("invalid scope slot");
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let attrs = scope.as_gc::<AttrSet>().expect("scope must be attrset");
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
attrs.lookup(name).expect("scoped binding not found")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init_builtins<'gc>(mc: &Mutation<'gc>, ctx: &mut impl VmRuntimeCtx) -> Value<'gc> {
|
fn init_builtins<'gc>(mc: &Mutation<'gc>, ctx: &mut impl VmRuntimeCtx) -> Value<'gc> {
|
||||||
let mut entries = SmallVec::with_capacity(BUILTINS.len());
|
let mut entries = SmallVec::with_capacity(BUILTINS.len());
|
||||||
|
|
||||||
@@ -441,7 +176,7 @@ impl<'gc> Vm<'gc> {
|
|||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn peek(&mut self, depth: usize) -> Value<'gc> {
|
fn peek(&self, depth: usize) -> Value<'gc> {
|
||||||
*self
|
*self
|
||||||
.stack
|
.stack
|
||||||
.get(self.stack.len() - depth - 1)
|
.get(self.stack.len() - depth - 1)
|
||||||
@@ -450,7 +185,7 @@ impl<'gc> Vm<'gc> {
|
|||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn peek_forced(&mut self, depth: usize) -> StrictValue<'gc> {
|
fn peek_forced(&self, depth: usize) -> StrictValue<'gc> {
|
||||||
self.stack
|
self.stack
|
||||||
.get(self.stack.len() - depth - 1)
|
.get(self.stack.len() - depth - 1)
|
||||||
.expect("stack underflow")
|
.expect("stack underflow")
|
||||||
@@ -580,18 +315,168 @@ impl<'gc> Vm<'gc> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
impl<'gc> Machine<'gc> for Vm<'gc> {
|
||||||
struct ErrorFrame {
|
#[inline(always)]
|
||||||
span_id: u32,
|
fn push(&mut self, val: Value<'gc>) {
|
||||||
message: Option<String>,
|
self.push(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Collect, Debug)]
|
#[inline(always)]
|
||||||
#[collect(no_drop)]
|
fn pop(&mut self) -> Value<'gc> {
|
||||||
struct CallFrame<'gc> {
|
self.pop()
|
||||||
pc: usize,
|
}
|
||||||
thunk: Option<Gc<'gc, Thunk<'gc>>>,
|
|
||||||
env: Gc<'gc, RefLock<Env<'gc>>>,
|
#[inline(always)]
|
||||||
|
fn peek(&self, depth: usize) -> Value<'gc> {
|
||||||
|
Vm::peek(self, depth)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn peek_forced(&self, depth: usize) -> StrictValue<'gc> {
|
||||||
|
Vm::peek_forced(self, depth)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn pop_forced(&mut self) -> StrictValue<'gc> {
|
||||||
|
self.pop_forced()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn replace(&mut self, depth: usize, val: Value<'gc>) {
|
||||||
|
self.replace(depth, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn stack_len(&self) -> usize {
|
||||||
|
self.stack.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn force_slot_to_pc(
|
||||||
|
&mut self,
|
||||||
|
depth: usize,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
resume_pc: usize,
|
||||||
|
) -> Step {
|
||||||
|
self.force_slot_to_pc(depth, reader, mc, resume_pc)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn call(
|
||||||
|
&mut self,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
arg: Value<'gc>,
|
||||||
|
resume_pc: usize,
|
||||||
|
) -> Step {
|
||||||
|
self.call(reader, mc, arg, resume_pc)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn push_call_frame(&mut self, frame: CallFrame<'gc>) {
|
||||||
|
self.call_stack.push(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn pop_call_frame(&mut self) -> Option<CallFrame<'gc>> {
|
||||||
|
self.call_stack.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn call_depth(&self) -> usize {
|
||||||
|
self.call_depth
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn inc_call_depth(&mut self) {
|
||||||
|
self.call_depth += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn dec_call_depth(&mut self) {
|
||||||
|
self.call_depth -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn env(&self) -> GcEnv<'gc> {
|
||||||
|
self.env
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn set_env(&mut self, env: GcEnv<'gc>) {
|
||||||
|
self.env = env;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn finish_ok(&mut self, val: fix_common::Value) -> Step {
|
||||||
|
self.finish_ok(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn finish_err(&mut self, err: Box<Error>) -> Step {
|
||||||
|
self.finish_err(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn finish_type_err(&mut self, expected: NixType, got: NixType) -> Step {
|
||||||
|
self.finish_type_err(expected, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn builtins(&self) -> Value<'gc> {
|
||||||
|
self.builtins
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn functor_sym(&self) -> StringId {
|
||||||
|
self.functor_sym
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn empty_list(&self) -> Value<'gc> {
|
||||||
|
self.empty_list
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn empty_attrs(&self) -> Value<'gc> {
|
||||||
|
self.empty_attrs
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn force_mode(&self) -> ForceMode {
|
||||||
|
self.force_mode
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn import_cache_get(&self, path: &std::path::Path) -> Option<Value<'gc>> {
|
||||||
|
self.import_cache.get(path).copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn import_cache_insert(&mut self, path: PathBuf, val: Value<'gc>) {
|
||||||
|
self.import_cache.insert(path, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn scope_slot(&self, idx: u32) -> Value<'gc> {
|
||||||
|
*self
|
||||||
|
.scope_slots
|
||||||
|
.get(idx as usize)
|
||||||
|
.expect("invalid scope slot")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn scope_slots_push(&mut self, val: Value<'gc>) -> u32 {
|
||||||
|
let idx = self.scope_slots.len() as u32;
|
||||||
|
self.scope_slots.push(val);
|
||||||
|
idx
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn set_pending_load(&mut self, load: PendingLoad) {
|
||||||
|
self.pending_load = Some(load);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Action {
|
enum Action {
|
||||||
@@ -600,11 +485,6 @@ enum Action {
|
|||||||
LoadFile(PendingLoad),
|
LoadFile(PendingLoad),
|
||||||
}
|
}
|
||||||
|
|
||||||
enum NixNum {
|
|
||||||
Int(i64),
|
|
||||||
Float(f64),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Vm<'_> {
|
impl Vm<'_> {
|
||||||
pub fn run<C: VmContext>(
|
pub fn run<C: VmContext>(
|
||||||
ctx: &mut C,
|
ctx: &mut C,
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
@@ -1,364 +0,0 @@
|
|||||||
use fix_builtins::PrimOpPhase;
|
|
||||||
use fix_error::Error;
|
|
||||||
use gc_arena::{Gc, Mutation, RefLock};
|
|
||||||
use smallvec::SmallVec;
|
|
||||||
|
|
||||||
use crate::value::*;
|
|
||||||
use crate::{BytecodeReader, Step, Vm, VmRuntimeCtx, VmRuntimeCtxExt};
|
|
||||||
|
|
||||||
impl<'gc> Vm<'gc> {
|
|
||||||
pub(crate) fn primop_seq(
|
|
||||||
&mut self,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
// stack: [e1, e2] - force e1, return e2
|
|
||||||
self.force_slot(1, reader, mc)?;
|
|
||||||
let e2 = self.pop();
|
|
||||||
let _ = self.pop();
|
|
||||||
self.return_from_primop(e2, reader)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn primop_abort(
|
|
||||||
&mut self,
|
|
||||||
ctx: &mut impl VmRuntimeCtx,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
// stack: [msg] - force msg, then abort with it
|
|
||||||
self.force_slot(0, reader, mc)?;
|
|
||||||
let msg_val = self.peek_forced(0);
|
|
||||||
let msg = ctx.get_string(msg_val).unwrap_or("<non-string-value>");
|
|
||||||
self.finish_err(Error::eval_error(format!(
|
|
||||||
"evaluation aborted with the following error message: '{msg}'"
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn primop_deep_seq_force_top(
|
|
||||||
&mut self,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
// stack: [e1, e2] - force e1, return e2
|
|
||||||
self.force_slot(1, reader, mc)?;
|
|
||||||
|
|
||||||
let e1 = self.peek_forced(1);
|
|
||||||
|
|
||||||
let children: SmallVec<_> = if let Some(attrs) = e1.as_gc::<AttrSet>() {
|
|
||||||
let attrs = &attrs.entries;
|
|
||||||
if attrs.is_empty() {
|
|
||||||
SmallVec::new()
|
|
||||||
} else {
|
|
||||||
attrs.iter().map(|&(_, v)| v).collect()
|
|
||||||
}
|
|
||||||
} else if let Some(list) = e1.as_gc::<List<'gc>>() {
|
|
||||||
let inner = list.inner.borrow();
|
|
||||||
if inner.is_empty() {
|
|
||||||
SmallVec::new()
|
|
||||||
} else {
|
|
||||||
inner.iter().copied().collect()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
SmallVec::new()
|
|
||||||
};
|
|
||||||
|
|
||||||
if children.is_empty() {
|
|
||||||
let e2 = self.pop();
|
|
||||||
let _ = self.pop();
|
|
||||||
return self.return_from_primop(e2, reader);
|
|
||||||
}
|
|
||||||
|
|
||||||
let count = children.len() as i32;
|
|
||||||
let seen: Gc<'gc, List<'gc>> = Gc::new(mc, List::default());
|
|
||||||
let worklist: Gc<'gc, List<'gc>> = List::new(mc, children);
|
|
||||||
|
|
||||||
let e2 = self.pop();
|
|
||||||
let _ = self.pop();
|
|
||||||
self.push(e2);
|
|
||||||
self.push(Value::new_gc(seen));
|
|
||||||
self.push(Value::new_gc(worklist));
|
|
||||||
self.push(Value::new_inline(count));
|
|
||||||
reader.set_pc(PrimOpPhase::DeepSeqPush.ip() as usize);
|
|
||||||
Step::Continue(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn primop_deep_seq_push(
|
|
||||||
&mut self,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
// stack: [e2, seen, worklist, counter]
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let counter = self.peek(0).as_inline::<i32>().unwrap();
|
|
||||||
if counter == 0 {
|
|
||||||
let _ = self.pop(); // counter
|
|
||||||
let _ = self.pop(); // worklist
|
|
||||||
let _ = self.pop(); // seen
|
|
||||||
let val = self.pop();
|
|
||||||
return self.return_from_primop(val, reader);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let worklist = self.peek_forced(1).as_gc::<List<'gc>>().unwrap();
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let item = worklist.unlock(mc).borrow_mut().pop().unwrap();
|
|
||||||
self.replace(0, Value::new_inline(counter - 1));
|
|
||||||
self.push(item);
|
|
||||||
|
|
||||||
// force item at TOS, resume at DeepSeqLoop after force
|
|
||||||
self.force_slot_to_pc(0, reader, mc, PrimOpPhase::DeepSeqLoop.ip() as usize)?;
|
|
||||||
reader.set_pc(PrimOpPhase::DeepSeqLoop.ip() as usize);
|
|
||||||
Step::Continue(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn primop_deep_seq_loop(
|
|
||||||
&mut self,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
// stack after pop: [e2, seen, worklist, counter]
|
|
||||||
let item = self.pop();
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let counter = self.peek(0).as_inline::<i32>().unwrap();
|
|
||||||
|
|
||||||
let mut added: usize = 0;
|
|
||||||
if let Some(attrs) = item.as_gc::<AttrSet>() {
|
|
||||||
let attrs = &attrs.entries;
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let seen = self.peek_forced(2).as_gc::<List<'gc>>().unwrap();
|
|
||||||
if !self.is_value_in_seen(seen, item) {
|
|
||||||
self.add_value_to_seen(seen, mc, item);
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let worklist = self.peek_forced(1).as_gc::<List<'gc>>().unwrap();
|
|
||||||
{
|
|
||||||
let mut wl = worklist.unlock(mc).borrow_mut();
|
|
||||||
for &(_, v) in attrs.iter() {
|
|
||||||
wl.push(v);
|
|
||||||
}
|
|
||||||
added = attrs.len();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if let Some(list) = item.as_gc::<List<'gc>>() {
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let seen = self.peek_forced(2).as_gc::<List<'gc>>().unwrap();
|
|
||||||
if !self.is_value_in_seen(seen, item) {
|
|
||||||
self.add_value_to_seen(seen, mc, item);
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let worklist = self.peek_forced(1).as_gc::<List<'gc>>().unwrap();
|
|
||||||
{
|
|
||||||
let inner = list.inner.borrow();
|
|
||||||
let mut wl = worklist.unlock(mc).borrow_mut();
|
|
||||||
for &v in inner.iter() {
|
|
||||||
wl.push(v);
|
|
||||||
}
|
|
||||||
added = inner.len();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.replace(0, Value::new_inline(counter + added as i32));
|
|
||||||
reader.set_pc(PrimOpPhase::DeepSeqPush.ip() as usize);
|
|
||||||
Step::Continue(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn primop_force_result_shallow(
|
|
||||||
&mut self,
|
|
||||||
ctx: &mut impl VmRuntimeCtx,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
self.force_slot(0, reader, mc)?;
|
|
||||||
let val = self.peek_forced(0);
|
|
||||||
|
|
||||||
let (count, has_children) = if let Some(attrs) = val.as_gc::<AttrSet>() {
|
|
||||||
let len = attrs.entries.len();
|
|
||||||
(len, len > 0)
|
|
||||||
} else if let Some(list) = val.as_gc::<List<'gc>>() {
|
|
||||||
let len = list.inner.borrow().len();
|
|
||||||
(len, len > 0)
|
|
||||||
} else {
|
|
||||||
(0, false)
|
|
||||||
};
|
|
||||||
|
|
||||||
if !has_children {
|
|
||||||
let val = self.pop();
|
|
||||||
return self.finish_ok(ctx.convert_value(val));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.push(Value::new_inline(0i32));
|
|
||||||
self.push(Value::new_inline(count as i32));
|
|
||||||
reader.set_pc(PrimOpPhase::ForceResultShallowPush.ip() as usize);
|
|
||||||
Step::Continue(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn primop_force_result_shallow_push(
|
|
||||||
&mut self,
|
|
||||||
ctx: &mut impl VmRuntimeCtx,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let idx = self.peek(1).as_inline::<i32>().unwrap();
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let len = self.peek(0).as_inline::<i32>().unwrap();
|
|
||||||
|
|
||||||
if idx == len {
|
|
||||||
let _ = self.pop(); // len
|
|
||||||
let _ = self.pop(); // idx
|
|
||||||
let val = self.pop();
|
|
||||||
return self.finish_ok(ctx.convert_value(val));
|
|
||||||
}
|
|
||||||
|
|
||||||
let val = self.peek_forced(2);
|
|
||||||
let child = if let Some(attrs) = val.as_gc::<AttrSet>() {
|
|
||||||
attrs.entries.get(idx as usize).map(|&(_, v)| v)
|
|
||||||
} else if let Some(list) = val.as_gc::<List<'gc>>() {
|
|
||||||
list.inner.borrow().get(idx as usize).copied()
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(child) = child {
|
|
||||||
self.replace(1, Value::new_inline(idx + 1));
|
|
||||||
self.push(child);
|
|
||||||
self.force_slot_to_pc(
|
|
||||||
0,
|
|
||||||
reader,
|
|
||||||
mc,
|
|
||||||
PrimOpPhase::ForceResultShallowLoop.ip() as usize,
|
|
||||||
)?;
|
|
||||||
reader.set_pc(PrimOpPhase::ForceResultShallowLoop.ip() as usize);
|
|
||||||
}
|
|
||||||
Step::Continue(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn primop_force_result_shallow_loop(
|
|
||||||
&mut self,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
_mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
let _ = self.pop(); // forced child
|
|
||||||
reader.set_pc(PrimOpPhase::ForceResultShallowPush.ip() as usize);
|
|
||||||
Step::Continue(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn primop_force_result_deep_finish(
|
|
||||||
&mut self,
|
|
||||||
ctx: &mut impl VmRuntimeCtx,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
let val = self.force_and_retry::<StrictValue>(reader, mc)?;
|
|
||||||
self.finish_ok(ctx.convert_value(val.relax()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_value_in_seen(&self, seen: Gc<'gc, List<'gc>>, val: Value<'gc>) -> bool {
|
|
||||||
if !is_container(val) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
let target = val.to_bits();
|
|
||||||
for &v in seen.inner.borrow().iter() {
|
|
||||||
if v.to_bits() == target {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_value_to_seen(&self, seen: Gc<'gc, List<'gc>>, mc: &Mutation<'gc>, val: Value<'gc>) {
|
|
||||||
if is_container(val) {
|
|
||||||
seen.unlock(mc).borrow_mut().push(val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn primop_call_functor_1(
|
|
||||||
&mut self,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
// Stack invariant on every (re-)entry: [..., orig_arg, self, functor]
|
|
||||||
// where `functor` is TOS. Retries during force land back here safely.
|
|
||||||
let functor = self.force_and_retry::<StrictValue>(reader, mc)?;
|
|
||||||
// Stack now: [..., orig_arg, self]
|
|
||||||
let self_val = self.pop();
|
|
||||||
self.push(functor.relax());
|
|
||||||
// Stack: [..., orig_arg, functor]
|
|
||||||
// Call 1: functor(self). Resume into CallFunctor2 once it returns.
|
|
||||||
self.call(
|
|
||||||
reader,
|
|
||||||
mc,
|
|
||||||
self_val,
|
|
||||||
PrimOpPhase::CallFunctor2.ip() as usize,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn primop_call_functor_2(
|
|
||||||
&mut self,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
// Stack on entry: [..., orig_arg, intermediate]
|
|
||||||
// call_stack top: synthetic frame with caller's resume_pc.
|
|
||||||
let intermediate = self.pop();
|
|
||||||
let orig_arg = self.pop();
|
|
||||||
let saved = self.call_stack.pop().expect("functor outer frame missing");
|
|
||||||
self.env = saved.env;
|
|
||||||
self.push(intermediate);
|
|
||||||
// Call 2: intermediate(orig_arg). Resume to caller.
|
|
||||||
self.call(reader, mc, orig_arg, saved.pc)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn primop_call_pattern(
|
|
||||||
&mut self,
|
|
||||||
ctx: &mut impl VmRuntimeCtx,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
let (func, attrset) = self.force_and_retry::<(Gc<Closure>, Gc<AttrSet>)>(reader, mc)?;
|
|
||||||
|
|
||||||
let Closure {
|
|
||||||
ip,
|
|
||||||
n_locals,
|
|
||||||
env,
|
|
||||||
pattern,
|
|
||||||
} = *func;
|
|
||||||
let Some(pattern) = pattern else {
|
|
||||||
unreachable!()
|
|
||||||
};
|
|
||||||
// TODO: get function name
|
|
||||||
// TODO: param spans
|
|
||||||
if !pattern.ellipsis {
|
|
||||||
for key in pattern.required.iter().copied() {
|
|
||||||
if attrset.lookup(key).is_none() {
|
|
||||||
let name = ctx.resolve_string(key);
|
|
||||||
return self.finish_err(Error::eval_error(format!(
|
|
||||||
"function 'anonymous lambda' called without required argument '{name}'"
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for &(key, _) in attrset.entries.iter() {
|
|
||||||
let is_expected =
|
|
||||||
pattern.required.contains(&key) || pattern.optional.contains(&key);
|
|
||||||
if !is_expected {
|
|
||||||
let name = ctx.resolve_string(key);
|
|
||||||
return self.finish_err(Error::eval_error(format!(
|
|
||||||
"function 'anonymous lambda' called with unexpected argument '{name}'"
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let new_env = Gc::new(
|
|
||||||
mc,
|
|
||||||
RefLock::new(Env::with_arg(Value::new_gc(attrset), n_locals, env)),
|
|
||||||
);
|
|
||||||
reader.set_pc(ip as usize);
|
|
||||||
self.env = new_env;
|
|
||||||
|
|
||||||
Step::Continue(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_container(val: Value<'_>) -> bool {
|
|
||||||
val.is::<AttrSet>() || val.is::<List<'_>>()
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
use fix_common::StringId;
|
|
||||||
use fix_error::Error;
|
|
||||||
use gc_arena::Mutation;
|
|
||||||
|
|
||||||
use crate::bytecode_reader::BytecodeReader;
|
|
||||||
use crate::value::*;
|
|
||||||
use crate::{Step, Vm, VmRuntimeCtx};
|
|
||||||
|
|
||||||
impl<'gc> Vm<'gc> {
|
|
||||||
pub(crate) fn primop_to_string(
|
|
||||||
&mut self,
|
|
||||||
_ctx: &mut impl VmRuntimeCtx,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
let val = self.force_and_retry::<StrictValue>(reader, mc)?;
|
|
||||||
if val.is::<StringId>() || val.is::<NixString>() {
|
|
||||||
return self.return_from_primop(val.relax(), reader);
|
|
||||||
}
|
|
||||||
if let Some(p) = val.as_inline::<Path>() {
|
|
||||||
return self.return_from_primop(Value::new_inline(p.0), reader);
|
|
||||||
}
|
|
||||||
// TODO: derivations / `__toString` / `outPath`,
|
|
||||||
// numbers, lists.
|
|
||||||
self.finish_err(Error::eval_error(format!(
|
|
||||||
"cannot coerce {} to a string",
|
|
||||||
val.ty()
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn primop_type_of(
|
|
||||||
&mut self,
|
|
||||||
ctx: &mut impl VmRuntimeCtx,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
let val = self.force_and_retry::<StrictValue>(reader, mc)?;
|
|
||||||
let name: &str = match val.ty() {
|
|
||||||
NixType::Int => "int",
|
|
||||||
NixType::Float => "float",
|
|
||||||
NixType::Bool => "bool",
|
|
||||||
NixType::Null => "null",
|
|
||||||
NixType::String => "string",
|
|
||||||
NixType::Path => "path",
|
|
||||||
NixType::AttrSet => "set",
|
|
||||||
NixType::List => "list",
|
|
||||||
NixType::Closure | NixType::PrimOp | NixType::PrimOpApp => "lambda",
|
|
||||||
NixType::Thunk => unreachable!("forced"),
|
|
||||||
};
|
|
||||||
let sid = ctx.intern_string(name);
|
|
||||||
self.return_from_primop(Value::new_inline(sid), reader)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,193 +0,0 @@
|
|||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use fix_builtins::PrimOpPhase;
|
|
||||||
use fix_common::StringId;
|
|
||||||
use fix_error::Error;
|
|
||||||
use gc_arena::{Gc, Mutation};
|
|
||||||
use hashbrown::HashSet;
|
|
||||||
|
|
||||||
use crate::bytecode_reader::BytecodeReader;
|
|
||||||
use crate::instructions::misc::canon_path_str;
|
|
||||||
use crate::value::*;
|
|
||||||
use crate::{Break, CallFrame, PendingLoad, PendingScope, Step, Vm, VmRuntimeCtx, VmRuntimeCtxExt};
|
|
||||||
|
|
||||||
impl<'gc> Vm<'gc> {
|
|
||||||
pub(crate) fn primop_import(
|
|
||||||
&mut self,
|
|
||||||
ctx: &mut impl VmRuntimeCtx,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
// stack: [path]
|
|
||||||
let path_val = self.force_and_retry::<StrictValue>(reader, mc)?;
|
|
||||||
let path_str = match ctx.get_string_or_path(path_val) {
|
|
||||||
Some(s) => s.to_owned(),
|
|
||||||
None => {
|
|
||||||
return self.finish_err(Error::eval_error(format!(
|
|
||||||
"expected a path or string, got {}",
|
|
||||||
path_val.ty()
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let abs = match resolve_import_target(&path_str) {
|
|
||||||
Ok(p) => p,
|
|
||||||
Err(e) => return self.finish_err(e),
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(&cached) = self.import_cache.get(&abs) {
|
|
||||||
return self.return_from_primop(cached, reader);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stash the resolved path on the stack as a string-id so the
|
|
||||||
// finalizer can use it as the cache key. The slot we pop here was
|
|
||||||
// freed by `force_and_retry`, so we simply push.
|
|
||||||
let path_sid = ctx.intern_string(abs.to_string_lossy());
|
|
||||||
self.push(Value::new_inline(path_sid));
|
|
||||||
self.call_stack.push(CallFrame {
|
|
||||||
pc: PrimOpPhase::ImportFinalize.ip() as usize,
|
|
||||||
thunk: None,
|
|
||||||
env: self.env,
|
|
||||||
});
|
|
||||||
|
|
||||||
self.pending_load = Some(PendingLoad {
|
|
||||||
path: abs,
|
|
||||||
scope: None,
|
|
||||||
});
|
|
||||||
Step::Break(Break::LoadFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn primop_import_finalize(
|
|
||||||
&mut self,
|
|
||||||
ctx: &mut impl VmRuntimeCtx,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
) -> Step {
|
|
||||||
// stack: [path_sid, return_value]
|
|
||||||
let val = self.pop();
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let path_sid = self.pop().as_inline::<StringId>().unwrap();
|
|
||||||
// The cache key is keyed by the absolute path string we interned in
|
|
||||||
// `primop_import`. Resolve it back to the host PathBuf.
|
|
||||||
let path_str = ctx.resolve_string(path_sid).to_owned();
|
|
||||||
self.import_cache.insert(PathBuf::from(path_str), val);
|
|
||||||
self.push(val);
|
|
||||||
let Some(CallFrame {
|
|
||||||
pc: ret_pc,
|
|
||||||
thunk: _,
|
|
||||||
env,
|
|
||||||
}) = self.call_stack.pop()
|
|
||||||
else {
|
|
||||||
unreachable!()
|
|
||||||
};
|
|
||||||
reader.set_pc(ret_pc);
|
|
||||||
// FIXME:
|
|
||||||
// self.call_depth -= 1;
|
|
||||||
self.env = env;
|
|
||||||
Step::Continue(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn primop_scoped_import(
|
|
||||||
&mut self,
|
|
||||||
ctx: &mut impl VmRuntimeCtx,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
// stack: [scope, path]
|
|
||||||
let (scope_attrs, path_val) =
|
|
||||||
self.force_and_retry::<(Gc<AttrSet>, StrictValue)>(reader, mc)?;
|
|
||||||
let path_str = match ctx.get_string_or_path(path_val) {
|
|
||||||
Some(s) => s.to_owned(),
|
|
||||||
None => {
|
|
||||||
return self.finish_err(Error::eval_error(format!(
|
|
||||||
"expected a path or string, got {}",
|
|
||||||
path_val.ty()
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let abs = match resolve_import_target(&path_str) {
|
|
||||||
Ok(p) => p,
|
|
||||||
Err(e) => return self.finish_err(e),
|
|
||||||
};
|
|
||||||
|
|
||||||
let keys: HashSet<StringId> = scope_attrs.entries.iter().map(|&(k, _)| k).collect();
|
|
||||||
let slot_id = self.scope_slots.len() as u32;
|
|
||||||
self.scope_slots.push(Value::new_gc(scope_attrs));
|
|
||||||
|
|
||||||
self.call_stack.push(CallFrame {
|
|
||||||
pc: PrimOpPhase::ScopedImportFinalize.ip() as usize,
|
|
||||||
thunk: None,
|
|
||||||
env: self.env,
|
|
||||||
});
|
|
||||||
|
|
||||||
self.pending_load = Some(PendingLoad {
|
|
||||||
path: abs,
|
|
||||||
scope: Some(PendingScope { keys, slot_id }),
|
|
||||||
});
|
|
||||||
Step::Break(Break::LoadFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn primop_scoped_import_finalize(
|
|
||||||
&mut self,
|
|
||||||
_ctx: &mut impl VmRuntimeCtx,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
_mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
// stack: [return_value]
|
|
||||||
// We intentionally do NOT pop the slot from `scope_slots` so that
|
|
||||||
// closures or thunks created inside the imported file can still
|
|
||||||
// resolve their scope after `scopedImport` returns.
|
|
||||||
let val = self.pop();
|
|
||||||
self.return_from_primop(val, reader)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn primop_path_exists(
|
|
||||||
&mut self,
|
|
||||||
ctx: &mut impl VmRuntimeCtx,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
let path_val = self.force_and_retry::<StrictValue>(reader, mc)?;
|
|
||||||
// CppNix: pathExists requires an absolute path. A `Path` value is
|
|
||||||
// always absolute; a string is accepted only if it starts with `/`.
|
|
||||||
let (path, is_path_value) = if let Some(p) = path_val.as_inline::<Path>() {
|
|
||||||
(ctx.resolve_string(p.0).to_owned(), true)
|
|
||||||
} else if let Some(s) = ctx.get_string(path_val) {
|
|
||||||
(s.to_owned(), false)
|
|
||||||
} else {
|
|
||||||
return self.finish_err(Error::eval_error(format!(
|
|
||||||
"expected a path or string, got {}",
|
|
||||||
path_val.ty()
|
|
||||||
)));
|
|
||||||
};
|
|
||||||
if !is_path_value && !path.starts_with('/') {
|
|
||||||
return self.finish_err(Error::eval_error(format!(
|
|
||||||
"string '{path}' doesn't represent an absolute path"
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
// CppNix collapses consecutive slashes and resolves `.` / `..` lexically
|
|
||||||
// before checking. Trailing-slash / trailing-dot mean "must be a directory".
|
|
||||||
let must_be_dir = path.ends_with('/') || path.ends_with("/.");
|
|
||||||
let canon = canon_path_str(&path);
|
|
||||||
let p = std::path::Path::new(&canon);
|
|
||||||
let exists = if must_be_dir {
|
|
||||||
std::fs::metadata(p).map(|m| m.is_dir()).unwrap_or(false)
|
|
||||||
} else {
|
|
||||||
std::fs::symlink_metadata(p).is_ok()
|
|
||||||
};
|
|
||||||
self.return_from_primop(Value::new_inline(exists), reader)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert the user-supplied path string into an absolute, dotted-segment
|
|
||||||
/// resolved `PathBuf` and append `default.nix` if the target is a directory.
|
|
||||||
fn resolve_import_target(path: &str) -> Result<PathBuf, Box<Error>> {
|
|
||||||
let mut abs = PathBuf::from(path);
|
|
||||||
if !abs.is_absolute() {
|
|
||||||
return Err(Error::eval_error(format!(
|
|
||||||
"import: expected an absolute path, got '{path}'"
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
if abs.is_dir() {
|
|
||||||
abs.push("default.nix");
|
|
||||||
}
|
|
||||||
Ok(abs)
|
|
||||||
}
|
|
||||||
@@ -1,169 +0,0 @@
|
|||||||
use fix_builtins::PrimOpPhase;
|
|
||||||
use gc_arena::Mutation;
|
|
||||||
|
|
||||||
use crate::bytecode_reader::BytecodeReader;
|
|
||||||
use crate::value::*;
|
|
||||||
use crate::{Step, Vm};
|
|
||||||
|
|
||||||
impl<'gc> Vm<'gc> {
|
|
||||||
pub(crate) fn primop_filter_force_list(
|
|
||||||
&mut self,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
self.force_slot(0, reader, mc)?;
|
|
||||||
let list = match self.peek_forced(0).expect_gc::<List>() {
|
|
||||||
Ok(list) => list,
|
|
||||||
Err(got) => return self.finish_type_err(NixType::List, got),
|
|
||||||
};
|
|
||||||
if list.inner.borrow().is_empty() {
|
|
||||||
let val = self.pop();
|
|
||||||
return self.return_from_primop(val, reader);
|
|
||||||
}
|
|
||||||
// prepare stack layout: [ pred list idx acc ]
|
|
||||||
self.push(Value::new_inline(0));
|
|
||||||
self.push(Value::new_gc(List::new_gc(mc)));
|
|
||||||
reader.set_pc(PrimOpPhase::FilterCallPred.ip() as usize);
|
|
||||||
Step::Continue(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn primop_filter_call_pred(
|
|
||||||
&mut self,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
self.force_slot(3, reader, mc)?;
|
|
||||||
let pred = self.peek_forced(3);
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let idx = self.peek(1).as_inline::<i32>().unwrap();
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let elem = self.peek_forced(2).as_gc::<List>().unwrap().inner.borrow()[idx as usize];
|
|
||||||
self.push(pred.relax());
|
|
||||||
self.call(reader, mc, elem, PrimOpPhase::FilterCheck.ip() as usize)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn primop_filter_check(
|
|
||||||
&mut self,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
let ret = self.force_and_retry::<bool>(reader, mc)?;
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let idx = self.peek(1).as_inline::<i32>().unwrap();
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let list = self.peek_forced(2).as_gc::<List>().unwrap();
|
|
||||||
let list = list.inner.borrow();
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let acc = self.peek_forced(0).as_gc::<List>().unwrap();
|
|
||||||
if ret {
|
|
||||||
let mut acc = acc.unlock(mc).borrow_mut();
|
|
||||||
acc.push(list[idx as usize]);
|
|
||||||
}
|
|
||||||
if idx as usize == list.len() - 1 {
|
|
||||||
let acc = self.pop();
|
|
||||||
let _ = self.pop(); // idx
|
|
||||||
let _ = self.pop(); // list
|
|
||||||
let _ = self.pop(); // pred
|
|
||||||
return self.return_from_primop(acc, reader);
|
|
||||||
}
|
|
||||||
self.replace(1, Value::new_inline(idx + 1));
|
|
||||||
reader.set_pc(PrimOpPhase::FilterCallPred.ip() as usize);
|
|
||||||
Step::Continue(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// foldl' op nul list
|
|
||||||
//
|
|
||||||
// Stack layouts across phases:
|
|
||||||
// Entry: [op, nul, list]
|
|
||||||
// Empty: [op, nul]
|
|
||||||
// Call1: [op, list, idx, acc]
|
|
||||||
// Call2: [op, list, idx, acc, intermediate]
|
|
||||||
// Update: [op, list, idx, acc, result]
|
|
||||||
pub(crate) fn primop_foldl_strict_entry(
|
|
||||||
&mut self,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
self.force_slot(0, reader, mc)?;
|
|
||||||
let list_val = self.peek_forced(0);
|
|
||||||
let Some(list) = list_val.as_gc::<List>() else {
|
|
||||||
return self.finish_type_err(NixType::List, list_val.ty());
|
|
||||||
};
|
|
||||||
if list.inner.borrow().is_empty() {
|
|
||||||
let _ = self.pop(); // list
|
|
||||||
reader.set_pc(PrimOpPhase::FoldlStrictEmpty.ip() as usize);
|
|
||||||
return Step::Continue(());
|
|
||||||
}
|
|
||||||
let list_val = self.pop();
|
|
||||||
let nul_val = self.pop();
|
|
||||||
self.push(list_val);
|
|
||||||
self.push(Value::new_inline(0i32));
|
|
||||||
self.push(nul_val);
|
|
||||||
reader.set_pc(PrimOpPhase::FoldlStrictCall1.ip() as usize);
|
|
||||||
Step::Continue(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn primop_foldl_strict_empty(
|
|
||||||
&mut self,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
let nul = self.force_and_retry::<StrictValue>(reader, mc)?;
|
|
||||||
let _ = self.pop(); // op
|
|
||||||
self.return_from_primop(nul.relax(), reader)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn primop_foldl_strict_call1(
|
|
||||||
&mut self,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
self.force_slot(3, reader, mc)?;
|
|
||||||
let op = self.peek_forced(3);
|
|
||||||
let acc = self.peek(0);
|
|
||||||
self.push(op.relax());
|
|
||||||
self.call(reader, mc, acc, PrimOpPhase::FoldlStrictCall2.ip() as usize)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn primop_foldl_strict_call2(
|
|
||||||
&mut self,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let idx = self.peek(2).as_inline::<i32>().unwrap();
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let list = self.peek_forced(3).as_gc::<List>().unwrap();
|
|
||||||
let elem = list.inner.borrow()[idx as usize];
|
|
||||||
self.call(
|
|
||||||
reader,
|
|
||||||
mc,
|
|
||||||
elem,
|
|
||||||
PrimOpPhase::FoldlStrictUpdate.ip() as usize,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn primop_foldl_strict_update(
|
|
||||||
&mut self,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
_mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
let result = self.pop();
|
|
||||||
self.replace(0, result);
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let idx = self.peek(1).as_inline::<i32>().unwrap();
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let list = self.peek_forced(2).as_gc::<List>().unwrap();
|
|
||||||
let len = list.inner.borrow().len();
|
|
||||||
if (idx as usize) + 1 == len {
|
|
||||||
let acc = self.pop();
|
|
||||||
let _ = self.pop(); // idx
|
|
||||||
let _ = self.pop(); // list
|
|
||||||
let _ = self.pop(); // op
|
|
||||||
return self.return_from_primop(acc, reader);
|
|
||||||
}
|
|
||||||
self.replace(1, Value::new_inline(idx + 1));
|
|
||||||
reader.set_pc(PrimOpPhase::FoldlStrictCall1.ip() as usize);
|
|
||||||
Step::Continue(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
use fix_builtins::PrimOpPhase;
|
|
||||||
use fix_error::Error;
|
|
||||||
use gc_arena::Mutation;
|
|
||||||
use num_enum::TryFromPrimitive;
|
|
||||||
|
|
||||||
use crate::bytecode_reader::BytecodeReader;
|
|
||||||
use crate::value::Value;
|
|
||||||
use crate::{CallFrame, Step, Vm, VmRuntimeCtx};
|
|
||||||
|
|
||||||
mod attrs;
|
|
||||||
mod control;
|
|
||||||
mod conv;
|
|
||||||
mod io;
|
|
||||||
mod list;
|
|
||||||
mod path;
|
|
||||||
mod regex;
|
|
||||||
mod version;
|
|
||||||
|
|
||||||
impl<'gc> Vm<'gc> {
|
|
||||||
#[allow(clippy::too_many_lines)]
|
|
||||||
pub(crate) fn op_dispatch_primop(
|
|
||||||
&mut self,
|
|
||||||
ctx: &mut impl VmRuntimeCtx,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
use PrimOpPhase::*;
|
|
||||||
let phase_disc = reader.read_u8();
|
|
||||||
let Ok(phase) = PrimOpPhase::try_from_primitive(phase_disc) else {
|
|
||||||
return self.finish_err(Error::eval_error("invalid primop phase"));
|
|
||||||
};
|
|
||||||
match phase {
|
|
||||||
Abort => self.primop_abort(ctx, reader, mc),
|
|
||||||
|
|
||||||
DeepSeq => self.primop_deep_seq_force_top(reader, mc),
|
|
||||||
DeepSeqPush => self.primop_deep_seq_push(reader, mc),
|
|
||||||
DeepSeqLoop => self.primop_deep_seq_loop(reader, mc),
|
|
||||||
Seq => self.primop_seq(reader, mc),
|
|
||||||
|
|
||||||
FilterForceList => self.primop_filter_force_list(reader, mc),
|
|
||||||
FilterCallPred => self.primop_filter_call_pred(reader, mc),
|
|
||||||
FilterCheck => self.primop_filter_check(reader, mc),
|
|
||||||
|
|
||||||
FoldlStrict => self.primop_foldl_strict_entry(reader, mc),
|
|
||||||
FoldlStrictEmpty => self.primop_foldl_strict_empty(reader, mc),
|
|
||||||
FoldlStrictCall1 => self.primop_foldl_strict_call1(reader, mc),
|
|
||||||
FoldlStrictCall2 => self.primop_foldl_strict_call2(reader, mc),
|
|
||||||
FoldlStrictUpdate => self.primop_foldl_strict_update(reader, mc),
|
|
||||||
|
|
||||||
ForceResultShallow => self.primop_force_result_shallow(ctx, reader, mc),
|
|
||||||
ForceResultShallowPush => self.primop_force_result_shallow_push(ctx, reader, mc),
|
|
||||||
ForceResultShallowLoop => self.primop_force_result_shallow_loop(reader, mc),
|
|
||||||
ForceResultDeepFinish => self.primop_force_result_deep_finish(ctx, reader, mc),
|
|
||||||
|
|
||||||
CallPattern => self.primop_call_pattern(ctx, reader, mc),
|
|
||||||
CallFunctor1 => self.primop_call_functor_1(reader, mc),
|
|
||||||
CallFunctor2 => self.primop_call_functor_2(reader, mc),
|
|
||||||
|
|
||||||
Import => self.primop_import(ctx, reader, mc),
|
|
||||||
ImportFinalize => self.primop_import_finalize(ctx, reader),
|
|
||||||
ScopedImport => self.primop_scoped_import(ctx, reader, mc),
|
|
||||||
ScopedImportFinalize => self.primop_scoped_import_finalize(ctx, reader, mc),
|
|
||||||
|
|
||||||
PathExists => self.primop_path_exists(ctx, reader, mc),
|
|
||||||
ToPath => self.primop_to_path(ctx, reader, mc),
|
|
||||||
IsPath => self.primop_is_path(reader, mc),
|
|
||||||
ToString => self.primop_to_string(ctx, reader, mc),
|
|
||||||
TypeOf => self.primop_type_of(ctx, reader, mc),
|
|
||||||
|
|
||||||
phase => todo!("primop phase {phase:?}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn return_from_primop(&mut self, val: Value<'gc>, reader: &mut BytecodeReader<'_>) -> Step {
|
|
||||||
self.push(val);
|
|
||||||
let Some(CallFrame {
|
|
||||||
pc: ret_pc,
|
|
||||||
thunk: _,
|
|
||||||
env,
|
|
||||||
}) = self.call_stack.pop()
|
|
||||||
else {
|
|
||||||
unreachable!()
|
|
||||||
};
|
|
||||||
reader.set_pc(ret_pc);
|
|
||||||
self.call_depth -= 1;
|
|
||||||
self.env = env;
|
|
||||||
Step::Continue(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
use fix_error::Error;
|
|
||||||
use gc_arena::Mutation;
|
|
||||||
|
|
||||||
use crate::bytecode_reader::BytecodeReader;
|
|
||||||
use crate::instructions::misc::canon_path_str;
|
|
||||||
use crate::value::*;
|
|
||||||
use crate::{Step, Vm, VmRuntimeCtx, VmRuntimeCtxExt};
|
|
||||||
|
|
||||||
impl<'gc> Vm<'gc> {
|
|
||||||
pub(crate) fn primop_to_path(
|
|
||||||
&mut self,
|
|
||||||
ctx: &mut impl VmRuntimeCtx,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
// coerce to path THEN TO STRING
|
|
||||||
let val = self.force_and_retry::<StrictValue>(reader, mc)?;
|
|
||||||
if let Some(Path(s)) = val.as_inline::<Path>() {
|
|
||||||
return self.return_from_primop(Value::new_inline(s), reader);
|
|
||||||
}
|
|
||||||
let Some(s) = ctx.get_string(val) else {
|
|
||||||
return self.finish_err(Error::eval_error(format!(
|
|
||||||
"cannot coerce {} to a path",
|
|
||||||
val.ty()
|
|
||||||
)));
|
|
||||||
};
|
|
||||||
if !s.starts_with('/') {
|
|
||||||
return self.finish_err(Error::eval_error(format!(
|
|
||||||
"string '{s}' doesn't represent an absolute path"
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
let canon = canon_path_str(s);
|
|
||||||
let sid = ctx.intern_string(canon);
|
|
||||||
self.return_from_primop(Value::new_inline(sid), reader)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn primop_is_path(
|
|
||||||
&mut self,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
let val = self.force_and_retry::<StrictValue>(reader, mc)?;
|
|
||||||
let is_path = val.is::<Path>();
|
|
||||||
self.return_from_primop(Value::new_inline(is_path), reader)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
@@ -33,6 +33,7 @@ rnix = { workspace = true }
|
|||||||
ere = { workspace = true }
|
ere = { workspace = true }
|
||||||
ghost-cell = { workspace = true }
|
ghost-cell = { workspace = true }
|
||||||
|
|
||||||
|
fix-abstract-vm = { path = "../fix-abstract-vm" }
|
||||||
fix-builtins = { path = "../fix-builtins" }
|
fix-builtins = { path = "../fix-builtins" }
|
||||||
fix-common = { path = "../fix-common" }
|
fix-common = { path = "../fix-common" }
|
||||||
fix-codegen = { path = "../fix-codegen" }
|
fix-codegen = { path = "../fix-codegen" }
|
||||||
|
|||||||
+4
-3
@@ -2,6 +2,7 @@
|
|||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
|
use fix_abstract_vm::{ForceMode, StaticValue, VmCode, VmContext, VmRuntimeCtx};
|
||||||
use fix_builtins::PrimOpPhase;
|
use fix_builtins::PrimOpPhase;
|
||||||
use fix_codegen::disassembler::{Disassembler, DisassemblerContext};
|
use fix_codegen::disassembler::{Disassembler, DisassemblerContext};
|
||||||
use fix_codegen::{BytecodeContext, InstructionPtr, Op};
|
use fix_codegen::{BytecodeContext, InstructionPtr, Op};
|
||||||
@@ -12,7 +13,7 @@ use fix_ir::{
|
|||||||
GhostMaybeThunkRef, GhostRoIrRef, GhostRoMaybeThunkRef, GhostRoRef, Ir, MaybeThunk, RawIrRef,
|
GhostMaybeThunkRef, GhostRoIrRef, GhostRoMaybeThunkRef, GhostRoRef, Ir, MaybeThunk, RawIrRef,
|
||||||
ThunkId,
|
ThunkId,
|
||||||
};
|
};
|
||||||
use fix_vm::{ForceMode, StaticValue, Vm, VmCode, VmContext, VmRuntimeCtx};
|
use fix_vm::Vm;
|
||||||
use ghost_cell::{GhostCell, GhostToken};
|
use ghost_cell::{GhostCell, GhostToken};
|
||||||
use hashbrown::{HashMap, HashSet};
|
use hashbrown::{HashMap, HashSet};
|
||||||
use string_interner::{DefaultStringInterner, Symbol as _};
|
use string_interner::{DefaultStringInterner, Symbol as _};
|
||||||
@@ -155,7 +156,7 @@ impl VmCode for CodeState {
|
|||||||
fn compile_with_scope(
|
fn compile_with_scope(
|
||||||
&mut self,
|
&mut self,
|
||||||
source: Source,
|
source: Source,
|
||||||
extra_scope: Option<fix_vm::ExtraScope>,
|
extra_scope: Option<fix_abstract_vm::ExtraScope>,
|
||||||
runtime: &mut impl VmRuntimeCtx,
|
runtime: &mut impl VmRuntimeCtx,
|
||||||
) -> Result<InstructionPtr> {
|
) -> Result<InstructionPtr> {
|
||||||
let mut compiler = CompilerCtx {
|
let mut compiler = CompilerCtx {
|
||||||
@@ -163,7 +164,7 @@ impl VmCode for CodeState {
|
|||||||
runtime,
|
runtime,
|
||||||
};
|
};
|
||||||
let extra = extra_scope.map(|s| match s {
|
let extra = extra_scope.map(|s| match s {
|
||||||
fix_vm::ExtraScope::ScopedImport { keys, slot_id } => {
|
fix_abstract_vm::ExtraScope::ScopedImport { keys, slot_id } => {
|
||||||
ExtraScope::ScopedImport { keys, slot_id }
|
ExtraScope::ScopedImport { keys, slot_id }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user