Compare commits

...

2 Commits

Author SHA1 Message Date
imxyy1soope1 f0e3f1eeca fix-vm: use Machine trait exclusively 2026-06-19 22:37:29 +08:00
imxyy1soope1 afbc471e40 *.toml: reformat using tombi 2026-06-19 21:17:10 +08:00
22 changed files with 1291 additions and 1373 deletions
+24 -21
View File
@@ -1,40 +1,43 @@
[workspace] [workspace]
resolver = "3" resolver = "3"
members = [ members = [
"fix", "fix",
"fix-bytecode", "fix-bytecode",
"fix-compiler", "fix-compiler",
"fix-error", "fix-error",
"fix-lang", "fix-lang",
"fix-runtime", "fix-runtime",
"fix-vm", "fix-vm",
] ]
[profile.profiling]
inherits = "release"
debug = true
[profile.lto]
inherits = "release"
lto = true
[workspace.dependencies] [workspace.dependencies]
bumpalo = { version = "3.20", features = [ bumpalo = {
version = "3.20",
features = [
"allocator-api2", "allocator-api2",
"boxed", "boxed",
"collections", "collections",
] } ]
}
ere = "0.2"
ghost-cell = "0.2" ghost-cell = "0.2"
hashbrown = "0.16" hashbrown = "0.16"
likely_stable = "0.1"
num_enum = "0.7.5" num_enum = "0.7.5"
smallvec = { version = "1.15", features = ["const_new", "const_generics"] }
ere = "0.2"
string-interner = "0.19"
rnix = "0.14" rnix = "0.14"
rowan = "0.16" rowan = "0.16"
likely_stable = "0.1" smallvec = { version = "1.15", features = ["const_generics", "const_new"] }
string-interner = "0.19"
[workspace.dependencies.gc-arena] [workspace.dependencies.gc-arena]
git = "https://github.com/kyren/gc-arena" git = "https://github.com/kyren/gc-arena"
rev = "75671ae03f53718357b741ed4027560f14e90836" rev = "75671ae03f53718357b741ed4027560f14e90836"
features = ["allocator-api2", "hashbrown", "smallvec"] features = ["allocator-api2", "hashbrown", "smallvec"]
[profile.lto]
inherits = "release"
lto = true
[profile.profiling]
inherits = "release"
debug = true
+1 -1
View File
@@ -5,13 +5,13 @@ edition = "2024"
[dependencies] [dependencies]
bumpalo = { workspace = true } bumpalo = { workspace = true }
colored = "3.1.1"
ghost-cell = { workspace = true } ghost-cell = { workspace = true }
hashbrown = { workspace = true } hashbrown = { workspace = true }
num_enum = { workspace = true } num_enum = { workspace = true }
rnix = { workspace = true } rnix = { workspace = true }
rowan = { workspace = true } rowan = { workspace = true }
string-interner = { workspace = true } string-interner = { workspace = true }
colored = "3.1.1"
fix-bytecode = { path = "../fix-bytecode" } fix-bytecode = { path = "../fix-bytecode" }
fix-error = { path = "../fix-error" } fix-error = { path = "../fix-error" }
+1 -1
View File
@@ -5,5 +5,5 @@ edition = "2024"
[dependencies] [dependencies]
miette = { version = "7.6", features = ["fancy"] } miette = { version = "7.6", features = ["fancy"] }
thiserror = "2.0"
rnix = { workspace = true } rnix = { workspace = true }
thiserror = "2.0"
+1 -1
View File
@@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
ere = { workspace = true }
gc-arena = { workspace = true } gc-arena = { workspace = true }
num_enum = { workspace = true } num_enum = { workspace = true }
string-interner = { workspace = true } string-interner = { workspace = true }
ere = { workspace = true }
+3 -3
View File
@@ -6,12 +6,12 @@ edition = "2024"
[dependencies] [dependencies]
gc-arena = { workspace = true } gc-arena = { workspace = true }
hashbrown = { workspace = true } hashbrown = { workspace = true }
likely_stable = { workspace = true }
num_enum = { workspace = true } num_enum = { workspace = true }
smallvec = { workspace = true } smallvec = { workspace = true }
string-interner = { workspace = true }
likely_stable = { workspace = true }
sptr = "0.3" sptr = "0.3"
string-interner = { workspace = true }
fix-bytecode = { path = "../fix-bytecode" } fix-bytecode = { path = "../fix-bytecode" }
fix-lang = { path = "../fix-lang" }
fix-error = { path = "../fix-error" } fix-error = { path = "../fix-error" }
fix-lang = { path = "../fix-lang" }
+4
View File
@@ -26,11 +26,15 @@ use crate::{
/// - Imports and scope slots (`import_cache_*` / `scope_slot*` / `set_pending_load`) /// - Imports and scope slots (`import_cache_*` / `scope_slot*` / `set_pending_load`)
pub trait Machine<'gc> { pub trait Machine<'gc> {
fn push(&mut self, val: Value<'gc>); fn push(&mut self, val: Value<'gc>);
#[must_use]
fn pop(&mut self) -> Value<'gc>; fn pop(&mut self) -> Value<'gc>;
#[must_use]
fn peek(&self, depth: usize) -> Value<'gc>; fn peek(&self, depth: usize) -> Value<'gc>;
#[must_use]
fn peek_forced(&self, depth: usize) -> StrictValue<'gc>; fn peek_forced(&self, depth: usize) -> StrictValue<'gc>;
fn pop_forced(&mut self) -> StrictValue<'gc>; fn pop_forced(&mut self) -> StrictValue<'gc>;
fn replace(&mut self, depth: usize, val: Value<'gc>); fn replace(&mut self, depth: usize, val: Value<'gc>);
fn drop_n(&mut self, depth: usize);
fn stack_len(&self) -> usize; fn stack_len(&self) -> usize;
fn force_slot_to_pc( fn force_slot_to_pc(
+5 -5
View File
@@ -3,20 +3,20 @@ name = "fix-vm"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
[features]
tailcall = []
[dependencies] [dependencies]
gc-arena = { workspace = true } gc-arena = { workspace = true }
hashbrown = { workspace = true } hashbrown = { workspace = true }
likely_stable = { workspace = true }
num_enum = { workspace = true } num_enum = { workspace = true }
smallvec = { workspace = true } smallvec = { workspace = true }
string-interner = { workspace = true }
likely_stable = { workspace = true }
sptr = "0.3" sptr = "0.3"
string-interner = { workspace = true }
sysinfo = { version = "0.38", default-features = false, features = ["system"] } sysinfo = { version = "0.38", default-features = false, features = ["system"] }
fix-bytecode = { path = "../fix-bytecode" } fix-bytecode = { path = "../fix-bytecode" }
fix-error = { path = "../fix-error" } fix-error = { path = "../fix-error" }
fix-lang = { path = "../fix-lang" } fix-lang = { path = "../fix-lang" }
fix-runtime = { path = "../fix-runtime" } fix-runtime = { path = "../fix-runtime" }
[features]
tailcall = []
+5 -5
View File
@@ -61,7 +61,7 @@ macro_rules! tail_fn {
pc: u32, pc: u32,
fuel: u32, fuel: u32,
) -> TailResult { ) -> TailResult {
let result = vm.$name(); let result = crate::instructions::$name(vm);
tail_dispatch_after!(result, pc + 1, vm, mc, ctx, bc, table, fuel) tail_dispatch_after!(result, pc + 1, vm, mc, ctx, bc, table, fuel)
} }
}; };
@@ -76,7 +76,7 @@ macro_rules! tail_fn {
fuel: u32, fuel: u32,
) -> TailResult { ) -> TailResult {
let mut reader = BytecodeReader::from_after_op(bc, pc as usize); let mut reader = BytecodeReader::from_after_op(bc, pc as usize);
let result = vm.$name(&mut reader); let result = crate::instructions::$name(vm, &mut reader);
tail_dispatch_after!(result, reader.pc() as u32, vm, mc, ctx, bc, table, fuel) tail_dispatch_after!(result, reader.pc() as u32, vm, mc, ctx, bc, table, fuel)
} }
}; };
@@ -91,7 +91,7 @@ macro_rules! tail_fn {
fuel: u32, fuel: u32,
) -> TailResult { ) -> TailResult {
let mut reader = BytecodeReader::from_after_op(bc, pc as usize); let mut reader = BytecodeReader::from_after_op(bc, pc as usize);
let result = vm.$name(&mut reader, mc); let result = crate::instructions::$name(vm, &mut reader, mc);
tail_dispatch_after!(result, reader.pc() as u32, vm, mc, ctx, bc, table, fuel) tail_dispatch_after!(result, reader.pc() as u32, vm, mc, ctx, bc, table, fuel)
} }
}; };
@@ -106,7 +106,7 @@ macro_rules! tail_fn {
fuel: u32, fuel: u32,
) -> TailResult { ) -> TailResult {
let mut reader = BytecodeReader::from_after_op(bc, pc as usize); let mut reader = BytecodeReader::from_after_op(bc, pc as usize);
let result = vm.$name(ctx, &mut reader, mc); let result = crate::instructions::$name(vm, ctx, &mut reader, mc);
tail_dispatch_after!(result, reader.pc() as u32, vm, mc, ctx, bc, table, fuel) tail_dispatch_after!(result, reader.pc() as u32, vm, mc, ctx, bc, table, fuel)
} }
}; };
@@ -120,7 +120,7 @@ macro_rules! tail_fn {
pc: u32, pc: u32,
fuel: u32, fuel: u32,
) -> TailResult { ) -> TailResult {
let result = vm.$name(ctx); let result = crate::instructions::$name(vm, ctx);
tail_dispatch_after!(result, pc + 1, vm, mc, ctx, bc, table, fuel) tail_dispatch_after!(result, pc + 1, vm, mc, ctx, bc, table, fuel)
} }
}; };
+268 -250
View File
@@ -5,267 +5,285 @@ use gc_arena::{Gc, Mutation, RefLock};
use crate::{BytecodeReader, NixNum, Step, VmError, VmRuntimeCtx}; use crate::{BytecodeReader, NixNum, Step, VmError, VmRuntimeCtx};
impl<'gc> crate::Vm<'gc> { #[inline(always)]
#[inline(always)] pub(crate) fn op_add<'gc, M: Machine<'gc>>(
pub(crate) fn op_add( m: &mut M,
&mut self, ctx: &mut impl VmRuntimeCtx,
ctx: &mut impl VmRuntimeCtx, reader: &mut BytecodeReader<'_>,
reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>,
mc: &Mutation<'gc>, ) -> Step {
) -> Step { let (lhs, rhs) = m.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?; // 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.) if lhs.is::<Path>() {
if lhs.is::<Path>() { let (Some(ls), Some(rs)) = (ctx.get_string_or_path(lhs), ctx.get_string_or_path(rhs))
let (Some(ls), Some(rs)) = (ctx.get_string_or_path(lhs), ctx.get_string_or_path(rhs)) else {
else { return m.finish_err(fix_error::Error::eval_error(format!(
return self.finish_err(fix_error::Error::eval_error(format!( "cannot append {} to a path",
"cannot append {} to a path", rhs.ty()
rhs.ty() )));
))); };
}; 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); m.push(Value::new_inline(fix_runtime::Path(sid)));
self.push(Value::new_inline(fix_runtime::Path(sid))); return Step::Continue(());
return Step::Continue(());
}
if let (Some(ls), Some(rs)) = (ctx.get_string(lhs), ctx.get_string_or_path(rhs)) {
let merged = ctx
.get_string_context(lhs)
.merge(ctx.get_string_context(rhs));
let ns = Gc::new(
mc,
crate::NixString::with_context(format!("{ls}{rs}"), merged),
);
self.push(Value::new_gc(ns));
return Step::Continue(());
}
let res = numeric_binop(lhs, rhs, mc, i64::wrapping_add, |a, b| a + b);
match res {
Ok(val) => {
self.push(val);
Step::Continue(())
}
Err(e) => self.finish_vm_err(e),
}
} }
if let (Some(ls), Some(rs)) = (ctx.get_string(lhs), ctx.get_string_or_path(rhs)) {
#[inline(always)] let merged = ctx
pub(crate) fn op_sub(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> Step { .get_string_context(lhs)
self.op_arith(reader, mc, i64::wrapping_sub, |a, b| a - b) .merge(ctx.get_string_context(rhs));
} let ns = Gc::new(
#[inline(always)]
pub(crate) fn op_mul(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> Step {
self.op_arith(reader, mc, i64::wrapping_mul, |a, b| a * b)
}
#[inline(always)]
fn op_arith(
&mut self,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
int_op: fn(i64, i64) -> i64,
float_op: fn(f64, f64) -> f64,
) -> Step {
let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
let res = numeric_binop(lhs, rhs, mc, int_op, float_op);
match res {
Ok(val) => {
self.push(val);
Step::Continue(())
}
Err(e) => self.finish_vm_err(e),
}
}
#[inline(always)]
pub(crate) fn op_div(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> Step {
let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
match (get_num(lhs), get_num(rhs)) {
(_, Some(NixNum::Int(0))) | (_, Some(NixNum::Float(0.))) => {
return self.finish_vm_err(VmError::Uncatchable(fix_error::Error::eval_error(
"division by zero",
)));
}
_ => {}
}
let res = numeric_binop(lhs, rhs, mc, |a, b| a / b, |a, b| a / b);
match res {
Ok(val) => {
self.push(val);
Step::Continue(())
}
Err(e) => self.finish_vm_err(e),
}
}
#[inline(always)]
pub(crate) fn op_eq(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
crate::primops::start_eq(self, ctx, reader, mc, lhs, rhs, false)
}
#[inline(always)]
pub(crate) fn op_neq(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
crate::primops::start_eq(self, ctx, reader, mc, lhs, rhs, true)
}
#[inline(always)]
pub(crate) fn op_lt(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
self.compare_values(ctx, reader, mc, Ordering::is_lt)
}
#[inline(always)]
pub(crate) fn op_gt(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
self.compare_values(ctx, reader, mc, Ordering::is_gt)
}
#[inline(always)]
pub(crate) fn op_leq(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
self.compare_values(ctx, reader, mc, Ordering::is_le)
}
#[inline(always)]
pub(crate) fn op_geq(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
self.compare_values(ctx, reader, mc, Ordering::is_ge)
}
fn compare_values(
&mut self,
ctx: &impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
pred: fn(Ordering) -> bool,
) -> Step {
let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
match self.compare_values_inner(ctx, pred, lhs, rhs) {
Ok(()) => Step::Continue(()),
Err(e) => self.finish_vm_err(e),
}
}
#[inline(always)]
pub(crate) fn op_concat(
&mut self,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let (l, r) = self.force_and_retry::<(Gc<List>, Gc<List>)>(reader, mc)?;
let mut items = smallvec::SmallVec::new();
items.extend_from_slice(&l.inner.borrow());
items.extend_from_slice(&r.inner.borrow());
self.push(Value::new_gc(Gc::new(
mc, mc,
crate::List { crate::NixString::with_context(format!("{ls}{rs}"), merged),
inner: RefLock::new(items), );
}, m.push(Value::new_gc(ns));
))); return Step::Continue(());
Step::Continue(())
} }
let res = numeric_binop(lhs, rhs, mc, i64::wrapping_add, |a, b| a + b);
#[inline(always)] match res {
pub(crate) fn op_update( Ok(val) => {
&mut self, m.push(val);
reader: &mut BytecodeReader<'_>, Step::Continue(())
mc: &Mutation<'gc>,
) -> Step {
let (l, r) = self.force_and_retry::<(Gc<AttrSet>, Gc<AttrSet>)>(reader, mc)?;
self.push(Value::new_gc(l.merge(&r, mc)));
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_neg(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> Step {
let rhs = self.force_and_retry::<NixNum>(reader, mc)?;
match rhs {
NixNum::Int(int) => self.push(Value::make_int(-int, mc)),
NixNum::Float(float) => self.push(Value::new_float(-float)),
} }
Step::Continue(()) Err(e) => m.finish_vm_err(e),
} }
}
#[inline(always)] #[inline(always)]
pub(crate) fn op_not(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> Step { pub(crate) fn op_sub<'gc, M: Machine<'gc>>(
let rhs = self.force_and_retry::<bool>(reader, mc)?; m: &mut M,
self.push(Value::new_inline(!rhs)); reader: &mut BytecodeReader<'_>,
Step::Continue(()) mc: &Mutation<'gc>,
} ) -> Step {
op_arith(m, reader, mc, i64::wrapping_sub, |a, b| a - b)
}
fn compare_values_inner( #[inline(always)]
&mut self, pub(crate) fn op_mul<'gc, M: Machine<'gc>>(
ctx: &impl VmRuntimeCtx, m: &mut M,
pred: fn(Ordering) -> bool, reader: &mut BytecodeReader<'_>,
lhs: StrictValue<'gc>, mc: &Mutation<'gc>,
rhs: StrictValue<'gc>, ) -> Step {
) -> crate::VmResult<()> { op_arith(m, reader, mc, i64::wrapping_mul, |a, b| a * b)
if let (Some(a), Some(b)) = (get_num(lhs), get_num(rhs)) { }
let ord = match (a, b) {
(NixNum::Int(a), NixNum::Int(b)) => a.cmp(&b), #[inline(always)]
(NixNum::Float(a), NixNum::Float(b)) => a.partial_cmp(&b).unwrap_or(Ordering::Less), fn op_arith<'gc, M: Machine<'gc>>(
(NixNum::Int(a), NixNum::Float(b)) => { m: &mut M,
(a as f64).partial_cmp(&b).unwrap_or(Ordering::Less) reader: &mut BytecodeReader<'_>,
} mc: &Mutation<'gc>,
(NixNum::Float(a), NixNum::Int(b)) => { int_op: fn(i64, i64) -> i64,
a.partial_cmp(&(b as f64)).unwrap_or(Ordering::Less) float_op: fn(f64, f64) -> f64,
} ) -> Step {
}; let (lhs, rhs) = m.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
self.push(Value::new_inline(pred(ord))); let res = numeric_binop(lhs, rhs, mc, int_op, float_op);
return Ok(()); match res {
Ok(val) => {
m.push(val);
Step::Continue(())
} }
if let (Some(a), Some(b)) = (ctx.get_string(lhs), ctx.get_string(rhs)) { Err(e) => m.finish_vm_err(e),
self.push(Value::new_inline(pred(a.cmp(b))));
return Ok(());
}
if let (Some(a), Some(b)) = (lhs.as_inline::<Path>(), rhs.as_inline::<Path>()) {
let a = ctx.resolve_string(a.0);
let b = ctx.resolve_string(b.0);
self.push(Value::new_inline(pred(a.cmp(b))));
return Ok(());
}
// TODO: compare other types
Err(crate::vm_err(format!(
"cannot compare {} with {}",
lhs.ty(),
rhs.ty()
)))
} }
} }
#[inline(always)]
pub(crate) fn op_div<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let (lhs, rhs) = m.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
match (get_num(lhs), get_num(rhs)) {
(_, Some(NixNum::Int(0))) | (_, Some(NixNum::Float(0.))) => {
return m.finish_vm_err(VmError::Uncatchable(fix_error::Error::eval_error(
"division by zero",
)));
}
_ => {}
}
let res = numeric_binop(lhs, rhs, mc, |a, b| a / b, |a, b| a / b);
match res {
Ok(val) => {
m.push(val);
Step::Continue(())
}
Err(e) => m.finish_vm_err(e),
}
}
#[inline(always)]
pub(crate) fn op_eq<'gc, M: Machine<'gc>>(
m: &mut M,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let (lhs, rhs) = m.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
crate::primops::start_eq(m, ctx, reader, mc, lhs, rhs, false)
}
#[inline(always)]
pub(crate) fn op_neq<'gc, M: Machine<'gc>>(
m: &mut M,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let (lhs, rhs) = m.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
crate::primops::start_eq(m, ctx, reader, mc, lhs, rhs, true)
}
#[inline(always)]
pub(crate) fn op_lt<'gc, M: Machine<'gc>>(
m: &mut M,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
compare_values(m, ctx, reader, mc, Ordering::is_lt)
}
#[inline(always)]
pub(crate) fn op_gt<'gc, M: Machine<'gc>>(
m: &mut M,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
compare_values(m, ctx, reader, mc, Ordering::is_gt)
}
#[inline(always)]
pub(crate) fn op_leq<'gc, M: Machine<'gc>>(
m: &mut M,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
compare_values(m, ctx, reader, mc, Ordering::is_le)
}
#[inline(always)]
pub(crate) fn op_geq<'gc, M: Machine<'gc>>(
m: &mut M,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
compare_values(m, ctx, reader, mc, Ordering::is_ge)
}
fn compare_values<'gc, M: Machine<'gc>>(
m: &mut M,
ctx: &impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
pred: fn(Ordering) -> bool,
) -> Step {
let (lhs, rhs) = m.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
match compare_values_inner(m, ctx, pred, lhs, rhs) {
Ok(()) => Step::Continue(()),
Err(e) => m.finish_vm_err(e),
}
}
#[inline(always)]
pub(crate) fn op_concat<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let (l, r) = m.force_and_retry::<(Gc<List>, Gc<List>)>(reader, mc)?;
let mut items = smallvec::SmallVec::new();
items.extend_from_slice(&l.inner.borrow());
items.extend_from_slice(&r.inner.borrow());
m.push(Value::new_gc(Gc::new(
mc,
crate::List {
inner: RefLock::new(items),
},
)));
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_update<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let (l, r) = m.force_and_retry::<(Gc<AttrSet>, Gc<AttrSet>)>(reader, mc)?;
m.push(Value::new_gc(l.merge(&r, mc)));
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_neg<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let rhs = m.force_and_retry::<NixNum>(reader, mc)?;
match rhs {
NixNum::Int(int) => m.push(Value::make_int(-int, mc)),
NixNum::Float(float) => m.push(Value::new_float(-float)),
}
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_not<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let rhs = m.force_and_retry::<bool>(reader, mc)?;
m.push(Value::new_inline(!rhs));
Step::Continue(())
}
fn compare_values_inner<'gc, M: Machine<'gc>>(
m: &mut M,
ctx: &impl VmRuntimeCtx,
pred: fn(Ordering) -> bool,
lhs: StrictValue<'gc>,
rhs: StrictValue<'gc>,
) -> crate::VmResult<()> {
if let (Some(a), Some(b)) = (get_num(lhs), get_num(rhs)) {
let ord = match (a, b) {
(NixNum::Int(a), NixNum::Int(b)) => a.cmp(&b),
(NixNum::Float(a), NixNum::Float(b)) => a.partial_cmp(&b).unwrap_or(Ordering::Less),
(NixNum::Int(a), NixNum::Float(b)) => {
(a as f64).partial_cmp(&b).unwrap_or(Ordering::Less)
}
(NixNum::Float(a), NixNum::Int(b)) => {
a.partial_cmp(&(b as f64)).unwrap_or(Ordering::Less)
}
};
m.push(Value::new_inline(pred(ord)));
return Ok(());
}
if let (Some(a), Some(b)) = (ctx.get_string(lhs), ctx.get_string(rhs)) {
m.push(Value::new_inline(pred(a.cmp(b))));
return Ok(());
}
if let (Some(a), Some(b)) = (lhs.as_inline::<Path>(), rhs.as_inline::<Path>()) {
let a = ctx.resolve_string(a.0);
let b = ctx.resolve_string(b.0);
m.push(Value::new_inline(pred(a.cmp(b))));
return Ok(());
}
// TODO: compare other types
Err(crate::vm_err(format!(
"cannot compare {} with {}",
lhs.ty(),
rhs.ty()
)))
}
pub(crate) fn get_num(val: StrictValue<'_>) -> Option<NixNum> { pub(crate) fn get_num(val: StrictValue<'_>) -> Option<NixNum> {
if let Some(i) = val.as_inline::<i32>() { if let Some(i) = val.as_inline::<i32>() {
Some(NixNum::Int(i64::from(i))) Some(NixNum::Int(i64::from(i)))
+162 -164
View File
@@ -8,176 +8,174 @@ use crate::{
VmRuntimeCtxExt, VmRuntimeCtxExt,
}; };
impl<'gc> crate::Vm<'gc> { #[inline(always)]
#[inline(always)] pub(crate) fn call<'gc, M: Machine<'gc>>(
pub(crate) fn call( m: &mut M,
&mut self, reader: &mut BytecodeReader<'_>,
reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>,
mc: &Mutation<'gc>, arg: Value<'gc>,
arg: Value<'gc>, resume_pc: usize,
resume_pc: usize, ) -> Step {
) -> Step { let func = m.force_and_retry::<StrictValue>(reader, mc)?;
let func = self.force_and_retry::<StrictValue>(reader, mc)?; if m.call_depth() > 10000 {
if self.call_depth > 10000 { return m.finish_err(Error::eval_error("stack overflow; max-call-depth exceeded"));
return self.finish_err(Error::eval_error("stack overflow; max-call-depth exceeded")); }
m.inc_call_depth();
if let Some(closure) = func.as_gc::<Closure>() {
if closure.pattern.is_some() {
// FIXME: better DX...
m.push(func.relax());
m.push(arg);
m.push_call_frame(CallFrame {
pc: resume_pc,
thunk: None,
env: m.env(),
});
reader.set_pc(PrimOpPhase::CallPattern.ip() as usize);
return Step::Continue(());
} }
self.call_depth += 1;
if let Some(closure) = func.as_gc::<Closure>() { let ip = closure.ip;
if closure.pattern.is_some() { let n_locals = closure.n_locals;
// FIXME: better DX... let env = closure.env;
self.push(func.relax()); let new_env = Gc::new(mc, RefLock::new(Env::with_arg(arg, n_locals, env)));
self.push(arg); m.push_call_frame(CallFrame {
self.call_stack.push(CallFrame { pc: resume_pc,
pc: resume_pc, thunk: None,
thunk: None, env: m.env(),
env: self.env, });
}); reader.set_pc(ip as usize);
reader.set_pc(PrimOpPhase::CallPattern.ip() as usize); m.set_env(new_env);
} else if let Some(primop) = func.as_inline::<PrimOp>() {
if primop.arity == 1 {
m.push(arg);
m.push_call_frame(CallFrame {
pc: resume_pc,
thunk: None,
env: m.env(),
});
reader.set_pc(primop.dispatch_ip as usize)
} else {
let app = PrimOpApp {
primop,
arity: primop.arity - 1,
args: [arg, Value::default(), Value::default()],
};
m.push(Value::new_gc(Gc::new(mc, app)));
}
} else if let Some(app) = func.as_gc::<PrimOpApp>() {
if app.arity == 1 {
for i in 0..app.primop.arity - 1 {
m.push(app.args[i as usize]);
}
m.push(arg);
m.push_call_frame(CallFrame {
pc: resume_pc,
thunk: None,
env: m.env(),
});
reader.set_pc(app.primop.dispatch_ip as usize)
} else {
let position = (app.primop.arity - app.arity) as usize;
let mut new_app = PrimOpApp {
arity: app.arity - 1,
..*app
};
new_app.args[position] = arg;
m.push(Value::new_gc(Gc::new(mc, new_app)))
}
} else if let Some(attrs) = func.as_gc::<AttrSet>()
&& let Some(functor) = attrs.lookup(m.functor_sym())
{
// f arg => (f.__functor f) arg
//
// Stage the work for `CallFunctor1` so retries during force are
// safe: the stack invariant `[..., orig_arg, self, functor]`
// holds every time control re-enters phase 1.
m.dec_call_depth();
m.push_call_frame(CallFrame {
pc: resume_pc,
thunk: None,
env: m.env(),
});
m.push(arg);
m.push(func.relax());
m.push(functor);
reader.set_pc(PrimOpPhase::CallFunctor1.ip() as usize);
return Step::Continue(());
} else {
return m.finish_err(Error::eval_error(format!(
"attempt to call something which is not a function but {}",
func.ty()
)));
}
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_call<'gc, M: Machine<'gc>>(
m: &mut M,
ctx: &impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let arg = resolve_operand(&reader.read_operand_data(), mc, ctx, m);
let pc = reader.pc();
m.call(reader, mc, arg, pc)
}
#[inline(always)]
pub(crate) fn op_return<'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 Some(CallFrame {
pc: ret_pc,
thunk,
env,
}) = m.pop_call_frame()
else {
match m.force_mode() {
ForceMode::AsIs => return m.finish_ok(ctx.convert_value(val.relax())),
ForceMode::Shallow => {
m.push(val.relax());
reader.set_pc(PrimOpPhase::ForceResultShallow.ip() as usize);
return Step::Continue(()); return Step::Continue(());
} }
ForceMode::Deep => {
let ip = closure.ip; m.push(val.relax());
let n_locals = closure.n_locals; m.push(val.relax());
let env = closure.env; m.push_call_frame(CallFrame {
let new_env = Gc::new(mc, RefLock::new(Env::with_arg(arg, n_locals, env))); pc: PrimOpPhase::ForceResultDeepFinish.ip() as usize,
self.call_stack.push(CallFrame {
pc: resume_pc,
thunk: None,
env: self.env,
});
reader.set_pc(ip as usize);
self.env = new_env;
} else if let Some(primop) = func.as_inline::<PrimOp>() {
if primop.arity == 1 {
self.push(arg);
self.call_stack.push(CallFrame {
pc: resume_pc,
thunk: None, thunk: None,
env: self.env, env: m.env(),
}); });
reader.set_pc(primop.dispatch_ip as usize) m.inc_call_depth();
} else { reader.set_pc(PrimOpPhase::DeepSeq.ip() as usize);
let app = PrimOpApp { return Step::Continue(());
primop,
arity: primop.arity - 1,
args: [arg, Value::default(), Value::default()],
};
self.push(Value::new_gc(Gc::new(mc, app)));
} }
} else if let Some(app) = func.as_gc::<PrimOpApp>() {
if app.arity == 1 {
for i in 0..app.primop.arity - 1 {
self.push(app.args[i as usize]);
}
self.push(arg);
self.call_stack.push(CallFrame {
pc: resume_pc,
thunk: None,
env: self.env,
});
reader.set_pc(app.primop.dispatch_ip as usize)
} else {
let position = (app.primop.arity - app.arity) as usize;
let mut new_app = PrimOpApp {
arity: app.arity - 1,
..*app
};
new_app.args[position] = arg;
self.push(Value::new_gc(Gc::new(mc, new_app)))
}
} else if let Some(attrs) = func.as_gc::<AttrSet>()
&& let Some(functor) = attrs.lookup(self.functor_sym)
{
// f arg => (f.__functor f) arg
//
// Stage the work for `CallFunctor1` so retries during force are
// safe: the stack invariant `[..., orig_arg, self, functor]`
// holds every time control re-enters phase 1.
self.call_depth -= 1;
self.call_stack.push(CallFrame {
pc: resume_pc,
thunk: None,
env: self.env,
});
self.push(arg);
self.push(func.relax());
self.push(functor);
reader.set_pc(PrimOpPhase::CallFunctor1.ip() as usize);
return Step::Continue(());
} else {
return self.finish_err(Error::eval_error(format!(
"attempt to call something which is not a function but {}",
func.ty()
)));
} }
Step::Continue(()) };
} reader.set_pc(ret_pc);
if let Some(outer_thunk) = thunk {
#[inline(always)] *outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val);
pub(crate) fn op_call( } else {
&mut self, m.dec_call_depth();
ctx: &impl VmRuntimeCtx, m.push(val.relax())
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let arg = resolve_operand(&reader.read_operand_data(), mc, ctx, self);
let pc = reader.pc();
self.call(reader, mc, arg, pc)
}
#[inline(always)]
pub(crate) fn op_return(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let val = self.force_and_retry::<StrictValue>(reader, mc)?;
let Some(CallFrame {
pc: ret_pc,
thunk,
env,
}) = self.call_stack.pop()
else {
match self.force_mode {
ForceMode::AsIs => return self.finish_ok(ctx.convert_value(val.relax())),
ForceMode::Shallow => {
self.push(val.relax());
reader.set_pc(PrimOpPhase::ForceResultShallow.ip() as usize);
return Step::Continue(());
}
ForceMode::Deep => {
self.push(val.relax());
self.push(val.relax());
self.call_stack.push(CallFrame {
pc: PrimOpPhase::ForceResultDeepFinish.ip() as usize,
thunk: None,
env: self.env,
});
self.call_depth += 1;
reader.set_pc(PrimOpPhase::DeepSeq.ip() as usize);
return Step::Continue(());
}
}
};
reader.set_pc(ret_pc);
if let Some(outer_thunk) = thunk {
*outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val);
} else {
self.call_depth -= 1;
self.push(val.relax())
}
self.env = env;
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_dispatch_primop(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
crate::primops::dispatch_primop(self, ctx, reader, mc)
} }
m.set_env(env);
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_dispatch_primop<'gc, M: Machine<'gc>>(
m: &mut M,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
crate::primops::dispatch_primop(m, ctx, reader, mc)
} }
+84 -85
View File
@@ -1,94 +1,93 @@
use fix_runtime::Machine;
use gc_arena::{Gc, Mutation, RefLock}; use gc_arena::{Gc, Mutation, RefLock};
use crate::{BytecodeReader, Step, ThunkState, Value}; use crate::{BytecodeReader, Step, ThunkState, Value};
impl<'gc> crate::Vm<'gc> { #[inline(always)]
#[inline(always)] pub(crate) fn op_make_thunk<'gc, M: Machine<'gc>>(
pub(crate) fn op_make_thunk( m: &mut M,
&mut self, reader: &mut BytecodeReader<'_>,
reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>,
mc: &Mutation<'gc>, ) -> Step {
) -> Step { let entry_point = reader.read_u32();
let entry_point = reader.read_u32(); let thunk = Gc::new(
let thunk = Gc::new( mc,
mc, RefLock::new(ThunkState::Pending {
RefLock::new(ThunkState::Pending { ip: entry_point as usize,
ip: entry_point as usize, env: m.env(),
env: self.env, }),
}), );
); m.push(Value::new_gc(thunk));
self.push(Value::new_gc(thunk)); Step::Continue(())
Step::Continue(()) }
}
#[inline(always)] #[inline(always)]
pub(crate) fn op_make_closure( pub(crate) fn op_make_closure<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
let entry_point = reader.read_u32(); let entry_point = reader.read_u32();
let n_locals = reader.read_u32(); let n_locals = reader.read_u32();
let closure = Gc::new( let closure = Gc::new(
mc, mc,
crate::Closure { crate::Closure {
ip: entry_point, ip: entry_point,
n_locals, n_locals,
env: self.env, env: m.env(),
pattern: None, pattern: None,
}, },
); );
self.push(Value::new_gc(closure)); m.push(Value::new_gc(closure));
Step::Continue(()) Step::Continue(())
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_make_pattern_closure( pub(crate) fn op_make_pattern_closure<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
let entry_point = reader.read_u32(); let entry_point = reader.read_u32();
let n_locals = reader.read_u32(); let n_locals = reader.read_u32();
let req_count = reader.read_u16() as usize; let req_count = reader.read_u16() as usize;
let opt_count = reader.read_u16() as usize; let opt_count = reader.read_u16() as usize;
let has_ellipsis = reader.read_u8() != 0; let has_ellipsis = reader.read_u8() != 0;
let mut required = smallvec::SmallVec::new(); let mut required = smallvec::SmallVec::new();
for _ in 0..req_count { for _ in 0..req_count {
required.push(reader.read_string_id()); required.push(reader.read_string_id());
} }
let mut optional = smallvec::SmallVec::new(); let mut optional = smallvec::SmallVec::new();
for _ in 0..opt_count { for _ in 0..opt_count {
optional.push(reader.read_string_id()); optional.push(reader.read_string_id());
} }
let total = req_count + opt_count; let total = req_count + opt_count;
let mut param_spans = Vec::with_capacity(total); let mut param_spans = Vec::with_capacity(total);
for _ in 0..total { for _ in 0..total {
let name = reader.read_string_id(); let name = reader.read_string_id();
let span_id = reader.read_u32(); let span_id = reader.read_u32();
param_spans.push((name, span_id)); param_spans.push((name, span_id));
} }
let pattern = Gc::new( let pattern = Gc::new(
mc, mc,
crate::PatternInfo { crate::PatternInfo {
required, required,
optional, optional,
ellipsis: has_ellipsis, ellipsis: has_ellipsis,
param_spans: param_spans.into_boxed_slice(), param_spans: param_spans.into_boxed_slice(),
}, },
); );
let closure = Gc::new( let closure = Gc::new(
mc, mc,
crate::Closure { crate::Closure {
ip: entry_point, ip: entry_point,
n_locals, n_locals,
env: self.env, env: m.env(),
pattern: Some(pattern), pattern: Some(pattern),
}, },
); );
self.push(Value::new_gc(closure)); m.push(Value::new_gc(closure));
Step::Continue(()) Step::Continue(())
}
} }
+310 -307
View File
@@ -1,6 +1,6 @@
use fix_error::Error; use fix_error::Error;
use fix_lang::StringId; use fix_lang::StringId;
use fix_runtime::{NixType, resolve_operand}; use fix_runtime::{Machine, MachineExt, NixType, resolve_operand};
use gc_arena::{Gc, RefLock}; use gc_arena::{Gc, RefLock};
use smallvec::SmallVec; use smallvec::SmallVec;
@@ -8,327 +8,330 @@ use crate::{
AttrSet, BytecodeReader, List, Step, StrictValue, Value, VmRuntimeCtx, VmRuntimeCtxExt, AttrSet, BytecodeReader, List, Step, StrictValue, Value, VmRuntimeCtx, VmRuntimeCtxExt,
}; };
impl<'gc> crate::Vm<'gc> { #[inline(always)]
#[inline(always)] pub(crate) fn op_make_attrs<'gc, M: Machine<'gc>>(
pub(crate) fn op_make_attrs( m: &mut M,
&mut self, ctx: &mut impl VmRuntimeCtx,
ctx: &mut impl VmRuntimeCtx, reader: &mut BytecodeReader<'_>,
reader: &mut BytecodeReader<'_>, mc: &gc_arena::Mutation<'gc>,
mc: &gc_arena::Mutation<'gc>, ) -> Step {
) -> Step { let static_count = reader.read_u32() as usize;
let static_count = reader.read_u32() as usize; let dynamic_count = reader.read_u32() as usize;
let dynamic_count = reader.read_u32() as usize;
for i in 0..dynamic_count { for i in 0..dynamic_count {
let depth = dynamic_count - 1 - i; let depth = dynamic_count - 1 - i;
self.force_slot_to_pc(depth, reader, mc, reader.inst_start_pc())?; m.force_slot_to_pc(depth, reader, mc, reader.inst_start_pc())?;
}
let mut dyn_keys: SmallVec<[_; 2]> = SmallVec::with_capacity(dynamic_count);
for i in 0..dynamic_count {
let depth = dynamic_count - 1 - i;
let key_val = self.peek_forced(depth);
let key_sid = match ctx.get_string_id(key_val) {
Ok(id) => Some(id),
Err(NixType::Null) => None,
Err(got) => return self.finish_type_err(NixType::String, got),
};
dyn_keys.push(key_sid);
}
self.stack.truncate(self.stack.len() - dynamic_count);
let mut kv: SmallVec<[(crate::StringId, Value); 4]> =
SmallVec::with_capacity(static_count + dynamic_count);
for _ in 0..static_count {
let key = reader.read_string_id();
let val = resolve_operand(&reader.read_operand_data(), mc, ctx, self);
let _span_id = reader.read_u32();
kv.push((key, val));
}
for key in dyn_keys {
let val = resolve_operand(&reader.read_operand_data(), mc, ctx, self);
let _span_id = reader.read_u32();
if let Some(key) = key {
kv.push((key, val))
}
}
kv.sort_by_key(|(k, _)| *k);
let attrs = Gc::new(mc, AttrSet::from_sorted_unchecked(kv));
self.push(Value::new_gc(attrs));
Step::Continue(())
} }
#[inline(always)] let mut dyn_keys: SmallVec<[_; 2]> = SmallVec::with_capacity(dynamic_count);
pub(crate) fn op_make_empty_attrs(&mut self) -> Step { for i in 0..dynamic_count {
self.push(self.empty_attrs); let depth = dynamic_count - 1 - i;
Step::Continue(()) let key_val = m.peek_forced(depth);
}
#[inline(always)]
pub(crate) fn op_select_static(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let _span_id = reader.read_u32();
let key = reader.read_string_id();
let attrset = self.force_and_retry::<Gc<AttrSet>>(reader, mc)?;
match attrset.lookup(key) {
Some(v) => {
self.push(v);
}
None => return self.select_skip(key, ctx, reader),
}
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_select_dynamic(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let _span_id = reader.read_u32();
let (attrset, key_val) = self.force_and_retry::<(Gc<AttrSet>, StrictValue)>(reader, mc)?;
let key_sid = match ctx.get_string_id(key_val) { let key_sid = match ctx.get_string_id(key_val) {
Ok(id) => id, Ok(id) => Some(id),
Err(got) => return self.finish_type_err(NixType::String, got), Err(NixType::Null) => None,
Err(got) => return m.finish_type_err(NixType::String, got),
}; };
dyn_keys.push(key_sid);
match attrset.lookup(key_sid) {
Some(v) => {
self.push(v);
}
None => return self.select_skip(key_sid, ctx, reader),
}
Step::Continue(())
} }
/// Skip the rest of a **Select** attrpath after a missing attribute. m.drop_n(dynamic_count);
/// Only recognises Select opcodes and jumps; encountering any other
/// opcode means we've reached the end of the select sequence and
/// should report the missing-attribute error.
fn select_skip(
&mut self,
key: StringId,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
) -> Step {
use fix_bytecode::Op::*;
loop {
match reader.read_op() {
SelectStatic => {
reader.set_pc(reader.pc() + 4 + 4);
}
SelectDynamic => {
reader.set_pc(reader.pc() + 4);
}
JumpIfSelectSucceeded => {
reader.set_pc(reader.pc() + 4);
break Step::Continue(());
}
JumpIfSelectFailed => {
let offset = reader.read_i32();
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
}
_ => {
let name = ctx.resolve_string(key);
return self
.finish_err(Error::eval_error(format!("attribute '{name}' missing")));
}
}
}
}
/// Skip the rest of a **HasAttr** attrpath after an intermediate let mut kv: SmallVec<[(crate::StringId, Value); 4]> =
/// lookup failed. Only recognises HasAttr opcodes and jumps. SmallVec::with_capacity(static_count + dynamic_count);
fn has_attr_skip(&mut self, reader: &mut BytecodeReader<'_>) -> Step {
use fix_bytecode::Op::*;
loop {
match reader.read_op() {
HasAttrPathStatic => {
reader.set_pc(reader.pc() + 4 + 4);
}
HasAttrPathDynamic => {
reader.set_pc(reader.pc() + 4);
}
HasAttrStatic => {
reader.set_pc(reader.pc() + 4);
break Step::Continue(());
}
JumpIfSelectFailed => {
let offset = reader.read_i32();
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
}
HasAttrDynamic => {
break Step::Continue(());
}
HasAttrResolve => {
reader.set_pc(reader.pc() - 1);
break Step::Continue(());
}
other => {
unreachable!("unexpected opcode {:?} in has_attr_skip", other as u8)
}
}
}
}
#[inline(always)] for _ in 0..static_count {
pub(crate) fn op_has_attr_path_static(
&mut self,
_ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let _span_id = reader.read_u32();
let key = reader.read_string_id(); let key = reader.read_string_id();
let val = resolve_operand(&reader.read_operand_data(), mc, ctx, m);
let current = self.force_and_retry::<StrictValue>(reader, mc)?;
match current
.as_gc::<AttrSet>()
.and_then(|attrs| attrs.lookup(key))
{
Some(v) => {
self.push(v);
}
None => return self.has_attr_skip(reader),
}
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_has_attr_path_dynamic(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let _span_id = reader.read_u32(); let _span_id = reader.read_u32();
kv.push((key, val));
}
let (current, key_val) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?; for key in dyn_keys {
let val = resolve_operand(&reader.read_operand_data(), mc, ctx, m);
let _span_id = reader.read_u32();
if let Some(key) = key {
kv.push((key, val))
}
}
let key_sid = match ctx.get_string_id(key_val) { kv.sort_by_key(|(k, _)| *k);
Ok(id) => id, let attrs = Gc::new(mc, AttrSet::from_sorted_unchecked(kv));
Err(got) => return self.finish_type_err(NixType::String, got), m.push(Value::new_gc(attrs));
}; Step::Continue(())
}
match current #[inline(always)]
.as_gc::<AttrSet>() pub(crate) fn op_make_empty_attrs<'gc, M: Machine<'gc>>(m: &mut M) -> Step {
.and_then(|attrs| attrs.lookup(key_sid)) m.push(m.empty_attrs());
{ Step::Continue(())
Some(v) => { }
self.push(v);
#[inline(always)]
pub(crate) fn op_select_static<'gc, M: Machine<'gc>>(
m: &mut M,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let _span_id = reader.read_u32();
let key = reader.read_string_id();
let attrset = m.force_and_retry::<Gc<AttrSet>>(reader, mc)?;
match attrset.lookup(key) {
Some(v) => {
m.push(v);
}
None => return select_skip(m, key, ctx, reader),
}
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_select_dynamic<'gc, M: Machine<'gc>>(
m: &mut M,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let _span_id = reader.read_u32();
let (attrset, key_val) = m.force_and_retry::<(Gc<AttrSet>, StrictValue)>(reader, mc)?;
let key_sid = match ctx.get_string_id(key_val) {
Ok(id) => id,
Err(got) => return m.finish_type_err(NixType::String, got),
};
match attrset.lookup(key_sid) {
Some(v) => {
m.push(v);
}
None => return select_skip(m, key_sid, ctx, reader),
}
Step::Continue(())
}
/// Skip the rest of a **Select** attrpath after a missing attribute.
/// Only recognises Select opcodes and jumps; encountering any other
/// opcode means we've reached the end of the select sequence and
/// should report the missing-attribute error.
fn select_skip<'gc, M: Machine<'gc>>(
m: &mut M,
key: StringId,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
) -> Step {
use fix_bytecode::Op::*;
loop {
match reader.read_op() {
SelectStatic => {
reader.set_pc(reader.pc() + 4 + 4);
}
SelectDynamic => {
reader.set_pc(reader.pc() + 4);
}
JumpIfSelectSucceeded => {
reader.set_pc(reader.pc() + 4);
break Step::Continue(());
}
JumpIfSelectFailed => {
let offset = reader.read_i32();
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
}
_ => {
let name = ctx.resolve_string(key);
return m.finish_err(Error::eval_error(format!("attribute '{name}' missing")));
} }
None => return self.has_attr_skip(reader),
} }
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_jump_if_select_failed(&mut self, reader: &mut BytecodeReader<'_>) -> Step {
// No-op
let _offset = reader.read_i32();
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_jump_if_select_succeeded(&mut self, reader: &mut BytecodeReader<'_>) -> Step {
let offset = reader.read_i32();
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_has_attr_static(
&mut self,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let key = reader.read_string_id();
let current = self.force_and_retry::<StrictValue>(reader, mc)?;
self.push(Value::new_inline(
current
.as_gc::<AttrSet>()
.and_then(|attrs| attrs.lookup(key))
.is_some(),
));
// Skip HasAttrResolve
reader.set_pc(reader.pc() + 1);
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_has_attr_dynamic(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let (current, dyn_key) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
let key_sid = match ctx.get_string_id(dyn_key) {
Ok(id) => id,
Err(got) => return self.finish_type_err(NixType::String, got),
};
self.push(Value::new_inline(
current
.as_gc::<AttrSet>()
.and_then(|attrs| attrs.lookup(key_sid))
.is_some(),
));
// Skip HasAttrResolve
reader.set_pc(reader.pc() + 1);
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_has_attr_resolve(&mut self) -> Step {
// If we reach here, has_attr check has failed, push false (AttrSet is already popped)
self.push(Value::new_inline(false));
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_make_list(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let count = reader.read_u32() as usize;
let mut items: SmallVec<[Value; 4]> = SmallVec::with_capacity(count);
for _ in 0..count {
items.push(resolve_operand(&reader.read_operand_data(), mc, ctx, self));
}
let list = Gc::new(
mc,
List {
inner: RefLock::new(items),
},
);
self.push(Value::new_gc(list));
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_make_empty_list(&mut self) -> Step {
self.push(self.empty_list);
Step::Continue(())
} }
} }
/// Skip the rest of a **HasAttr** attrpath after an intermediate
/// lookup failed. Only recognises HasAttr opcodes and jumps.
fn has_attr_skip(reader: &mut BytecodeReader<'_>) -> Step {
use fix_bytecode::Op::*;
loop {
match reader.read_op() {
HasAttrPathStatic => {
reader.set_pc(reader.pc() + 4 + 4);
}
HasAttrPathDynamic => {
reader.set_pc(reader.pc() + 4);
}
HasAttrStatic => {
reader.set_pc(reader.pc() + 4);
break Step::Continue(());
}
JumpIfSelectFailed => {
let offset = reader.read_i32();
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
}
HasAttrDynamic => {
break Step::Continue(());
}
HasAttrResolve => {
reader.set_pc(reader.pc() - 1);
break Step::Continue(());
}
other => {
unreachable!("unexpected opcode {:?} in has_attr_skip", other as u8)
}
}
}
}
#[inline(always)]
pub(crate) fn op_has_attr_path_static<'gc, M: Machine<'gc>>(
m: &mut M,
_ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let _span_id = reader.read_u32();
let key = reader.read_string_id();
let current = m.force_and_retry::<StrictValue>(reader, mc)?;
match current
.as_gc::<AttrSet>()
.and_then(|attrs| attrs.lookup(key))
{
Some(v) => {
m.push(v);
}
None => return has_attr_skip(reader),
}
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_has_attr_path_dynamic<'gc, M: Machine<'gc>>(
m: &mut M,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let _span_id = reader.read_u32();
let (current, key_val) = m.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
let key_sid = match ctx.get_string_id(key_val) {
Ok(id) => id,
Err(got) => return m.finish_type_err(NixType::String, got),
};
match current
.as_gc::<AttrSet>()
.and_then(|attrs| attrs.lookup(key_sid))
{
Some(v) => {
m.push(v);
}
None => return has_attr_skip(reader),
}
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_jump_if_select_failed<'gc, M: Machine<'gc>>(
_m: &mut M,
reader: &mut BytecodeReader<'_>,
) -> Step {
// No-op
let _offset = reader.read_i32();
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_jump_if_select_succeeded<'gc, M: Machine<'gc>>(
_m: &mut M,
reader: &mut BytecodeReader<'_>,
) -> Step {
let offset = reader.read_i32();
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_has_attr_static<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let key = reader.read_string_id();
let current = m.force_and_retry::<StrictValue>(reader, mc)?;
m.push(Value::new_inline(
current
.as_gc::<AttrSet>()
.and_then(|attrs| attrs.lookup(key))
.is_some(),
));
// Skip HasAttrResolve
reader.set_pc(reader.pc() + 1);
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_has_attr_dynamic<'gc, M: MachineExt<'gc>>(
m: &mut M,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let (current, dyn_key) = m.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
let key_sid = match ctx.get_string_id(dyn_key) {
Ok(id) => id,
Err(got) => return m.finish_type_err(NixType::String, got),
};
m.push(Value::new_inline(
current
.as_gc::<AttrSet>()
.and_then(|attrs| attrs.lookup(key_sid))
.is_some(),
));
// Skip HasAttrResolve
reader.set_pc(reader.pc() + 1);
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_has_attr_resolve<'gc, M: Machine<'gc>>(m: &mut M) -> Step {
// If we reach here, has_attr check has failed, push false (AttrSet is already popped)
m.push(Value::new_inline(false));
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_make_list<'gc, M: Machine<'gc>>(
m: &mut M,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let count = reader.read_u32() as usize;
let mut items: SmallVec<[Value; 4]> = SmallVec::with_capacity(count);
for _ in 0..count {
items.push(resolve_operand(&reader.read_operand_data(), mc, ctx, m));
}
let list = Gc::new(
mc,
List {
inner: RefLock::new(items),
},
);
m.push(Value::new_gc(list));
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_make_empty_list<'gc, M: Machine<'gc>>(m: &mut M) -> Step {
m.push(m.empty_list());
Step::Continue(())
}
+49 -51
View File
@@ -4,57 +4,55 @@ use gc_arena::Mutation;
use crate::{BytecodeReader, Step, VmRuntimeCtx}; use crate::{BytecodeReader, Step, VmRuntimeCtx};
impl<'gc> crate::Vm<'gc> { #[inline(always)]
#[inline(always)] pub(crate) fn op_jump_if_false<'gc, M: Machine<'gc>>(
pub(crate) fn op_jump_if_false( m: &mut M,
&mut self, reader: &mut BytecodeReader<'_>,
reader: &mut BytecodeReader<'_>, mc: &gc_arena::Mutation<'gc>,
mc: &gc_arena::Mutation<'gc>, ) -> Step {
) -> Step { let offset = reader.read_i32();
let offset = reader.read_i32(); let cond = m.force_and_retry::<StrictValue>(reader, mc)?;
let cond = self.force_and_retry::<StrictValue>(reader, mc)?; if cond.as_inline::<bool>() == Some(false) {
if cond.as_inline::<bool>() == Some(false) {
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
}
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_jump_if_true(
&mut self,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let offset = reader.read_i32();
let cond = self.force_and_retry::<StrictValue>(reader, mc)?;
if cond.as_inline::<bool>() == Some(true) {
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
}
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_jump(&mut self, reader: &mut BytecodeReader<'_>) -> Step {
let offset = reader.read_i32();
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize); reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_assert(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let raw_id = reader.read_string_id();
let raw = ctx.resolve_string(raw_id);
let _span_id = reader.read_u32();
let assertion = self.force_and_retry::<bool>(reader, mc)?;
if !assertion {
// FIXME: use catchable error
return self.finish_err(Error::eval_error(format!("assertion '{raw}' failed")));
}
Step::Continue(())
} }
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_jump_if_true<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let offset = reader.read_i32();
let cond = m.force_and_retry::<StrictValue>(reader, mc)?;
if cond.as_inline::<bool>() == Some(true) {
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
}
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_jump<'gc, M: Machine<'gc>>(_m: &mut M, reader: &mut BytecodeReader<'_>) -> Step {
let offset = reader.read_i32();
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_assert<'gc, M: Machine<'gc>>(
m: &mut M,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let raw_id = reader.read_string_id();
let raw = ctx.resolve_string(raw_id);
let _span_id = reader.read_u32();
let assertion = m.force_and_retry::<bool>(reader, mc)?;
if !assertion {
// FIXME: use catchable error
return m.finish_err(Error::eval_error(format!("assertion '{raw}' failed")));
}
Step::Continue(())
} }
+52 -44
View File
@@ -1,55 +1,63 @@
use fix_runtime::Machine;
use gc_arena::{Gc, Mutation}; use gc_arena::{Gc, Mutation};
use crate::{BytecodeReader, Step, Value}; use crate::{BytecodeReader, Step, Value};
impl<'gc> crate::Vm<'gc> { #[inline(always)]
#[inline(always)] pub(crate) fn op_push_smi<'gc, M: Machine<'gc>>(
pub(crate) fn op_push_smi(&mut self, reader: &mut BytecodeReader<'_>) -> Step { m: &mut M,
let val = reader.read_i32(); reader: &mut BytecodeReader<'_>,
self.push(Value::new_inline(val)); ) -> Step {
Step::Continue(()) let val = reader.read_i32();
} m.push(Value::new_inline(val));
Step::Continue(())
}
#[inline(always)] #[inline(always)]
pub(crate) fn op_push_bigint( pub(crate) fn op_push_bigint<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
let val = reader.read_i64(); let val = reader.read_i64();
self.push(Value::new_gc(Gc::new(mc, val))); m.push(Value::new_gc(Gc::new(mc, val)));
Step::Continue(()) Step::Continue(())
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_push_float(&mut self, reader: &mut BytecodeReader<'_>) -> Step { pub(crate) fn op_push_float<'gc, M: Machine<'gc>>(
let val = reader.read_f64(); m: &mut M,
self.push(Value::new_float(val)); reader: &mut BytecodeReader<'_>,
Step::Continue(()) ) -> Step {
} let val = reader.read_f64();
m.push(Value::new_float(val));
Step::Continue(())
}
#[inline(always)] #[inline(always)]
pub(crate) fn op_push_string(&mut self, reader: &mut BytecodeReader<'_>) -> Step { pub(crate) fn op_push_string<'gc, M: Machine<'gc>>(
let sid = reader.read_string_id(); m: &mut M,
self.push(Value::new_inline(sid)); reader: &mut BytecodeReader<'_>,
Step::Continue(()) ) -> Step {
} let sid = reader.read_string_id();
m.push(Value::new_inline(sid));
Step::Continue(())
}
#[inline(always)] #[inline(always)]
pub(crate) fn op_push_null(&mut self) -> Step { pub(crate) fn op_push_null<'gc, M: Machine<'gc>>(m: &mut M) -> Step {
self.push(Value::new_inline(crate::Null)); m.push(Value::new_inline(crate::Null));
Step::Continue(()) Step::Continue(())
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_push_true(&mut self) -> Step { pub(crate) fn op_push_true<'gc, M: Machine<'gc>>(m: &mut M) -> Step {
self.push(Value::new_inline(true)); m.push(Value::new_inline(true));
Step::Continue(()) Step::Continue(())
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_push_false(&mut self) -> Step { pub(crate) fn op_push_false<'gc, M: Machine<'gc>>(m: &mut M) -> Step {
self.push(Value::new_inline(false)); m.push(Value::new_inline(false));
Step::Continue(()) Step::Continue(())
}
} }
+143 -144
View File
@@ -3,165 +3,164 @@ use std::path::PathBuf;
use fix_bytecode::PrimOpPhase; use fix_bytecode::PrimOpPhase;
use fix_error::Error; use fix_error::Error;
use fix_lang::{BUILTINS, BuiltinId, StringId}; use fix_lang::{BUILTINS, BuiltinId, StringId};
use fix_runtime::{AttrSet, NixString, Path, StrictValue, StringContext, canon_path_str}; use fix_runtime::{
AttrSet, Machine, MachineExt, NixString, Path, StrictValue, StringContext, canon_path_str,
};
use num_enum::TryFromPrimitive; use num_enum::TryFromPrimitive;
use crate::{BytecodeReader, PrimOp, Step, Value, VmRuntimeCtx, VmRuntimeCtxExt}; use crate::{BytecodeReader, PrimOp, Step, Value, VmRuntimeCtx, VmRuntimeCtxExt};
impl<'gc> crate::Vm<'gc> { #[inline(always)]
#[inline(always)] pub(crate) fn op_load_builtins<'gc, M: Machine<'gc>>(m: &mut M) -> Step {
pub(crate) fn op_load_builtins(&mut self) -> Step { m.push(m.builtins());
self.push(self.builtins); Step::Continue(())
Step::Continue(()) }
}
#[inline(always)] #[inline(always)]
pub(crate) fn op_load_builtin(&mut self, reader: &mut BytecodeReader<'_>) -> Step { pub(crate) fn op_load_builtin<'gc, M: Machine<'gc>>(
let Ok(id) = BuiltinId::try_from_primitive(reader.read_u8()) m: &mut M,
.map_err(|err| panic!("unknown builtin id: {}", err.number)); reader: &mut BytecodeReader<'_>,
self.push(Value::new_inline(PrimOp { ) -> Step {
id, let Ok(id) = BuiltinId::try_from_primitive(reader.read_u8())
arity: BUILTINS[id as usize].1, .map_err(|err| panic!("unknown builtin id: {}", err.number));
dispatch_ip: PrimOpPhase::entry_for_builtin(id).ip(), m.push(Value::new_inline(PrimOp {
})); id,
Step::Continue(()) arity: BUILTINS[id as usize].1,
} dispatch_ip: PrimOpPhase::entry_for_builtin(id).ip(),
}));
Step::Continue(())
}
#[inline(always)] #[inline(always)]
pub(crate) fn op_load_repl_binding(&mut self, reader: &mut BytecodeReader<'_>) -> Step { pub(crate) fn op_load_repl_binding<'gc, M: Machine<'gc>>(
let _name = reader.read_string_id(); _m: &mut M,
todo!("LoadReplBinding"); reader: &mut BytecodeReader<'_>,
} ) -> Step {
let _name = reader.read_string_id();
todo!("LoadReplBinding");
}
#[inline(always)] #[inline(always)]
pub(crate) fn op_load_scoped_binding( pub(crate) fn op_load_scoped_binding<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
ctx: &impl VmRuntimeCtx, ctx: &impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
_mc: &gc_arena::Mutation<'gc>, _mc: &gc_arena::Mutation<'gc>,
) -> Step { ) -> Step {
let slot_id = reader.read_u32(); let slot_id = reader.read_u32();
let name = reader.read_string_id(); let name = reader.read_string_id();
let scope = match self.scope_slots.get(slot_id as usize).copied() { let scope = m.scope_slot(slot_id);
Some(s) => s, let Some(attrs) = scope.as_gc::<AttrSet>() else {
None => { return m.finish_err(Error::eval_error("internal: scope slot is not an attrset"));
return self.finish_err(Error::eval_error(format!( };
"internal: invalid scope slot {slot_id}" match attrs.lookup(name) {
))); Some(val) => {
} m.push(val);
}; Step::Continue(())
let Some(attrs) = scope.as_gc::<AttrSet>() else { }
return self.finish_err(Error::eval_error("internal: scope slot is not an attrset")); None => m.finish_err(Error::eval_error(format!(
}; "scoped binding '{}' not found",
match attrs.lookup(name) { ctx.resolve_string(name)
Some(val) => { ))),
self.push(val); }
Step::Continue(()) }
}
None => self.finish_err(Error::eval_error(format!( #[inline(always)]
"scoped binding '{}' not found", pub(crate) fn op_coerce_to_string<'gc, M: Machine<'gc>>(
ctx.resolve_string(name) m: &mut M,
))), reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let val = m.force_and_retry::<StrictValue>(reader, mc)?;
if val.is::<StringId>() || val.is::<NixString>() {
m.push(val.relax());
} else if let Some(p) = val.as_inline::<Path>() {
// Coercing a path to a string yields the canonical path text.
// FIXME: copy to store
m.push(Value::new_inline(p.0));
} else {
todo!("coerce other types to string: {:?}", val.ty());
}
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_concat_strings<'gc, M: Machine<'gc>>(
m: &mut M,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let count = reader.read_u16() as usize;
let _force_string = reader.read_u8() != 0;
let mut total_len = 0;
let mut has_any_context = false;
for i in 0..count {
let val = m.peek_forced(count - 1 - i);
let s = ctx.get_string(val).expect("coerced");
total_len += s.len();
if !ctx.get_string_context(val).is_empty() {
has_any_context = true;
} }
} }
#[inline(always)] let mut result = String::with_capacity(total_len);
pub(crate) fn op_coerce_to_string( let mut merged = StringContext::new();
&mut self, for i in 0..count {
reader: &mut BytecodeReader<'_>, let val = m.peek_forced(count - 1 - i);
mc: &gc_arena::Mutation<'gc>, let s = ctx.get_string(val).expect("coerced");
) -> Step { result.push_str(s);
let val = self.force_and_retry::<StrictValue>(reader, mc)?; if has_any_context {
if val.is::<StringId>() || val.is::<NixString>() { let ctx = ctx.get_string_context(val);
self.push(val.relax()); if !ctx.is_empty() {
} else if let Some(p) = val.as_inline::<Path>() { merged = merged.merge(ctx);
// Coercing a path to a string yields the canonical path text.
// FIXME: copy to store
self.push(Value::new_inline(p.0));
} else {
todo!("coerce other types to string: {:?}", val.ty());
}
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_concat_strings(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let count = reader.read_u16() as usize;
let _force_string = reader.read_u8() != 0;
let mut total_len = 0;
let mut has_any_context = false;
for i in 0..count {
let val = self.peek_forced(count - 1 - i);
let s = ctx.get_string(val).expect("coerced");
total_len += s.len();
if !ctx.get_string_context(val).is_empty() {
has_any_context = true;
} }
} }
let mut result = String::with_capacity(total_len);
let mut merged = StringContext::new();
for i in 0..count {
let val = self.peek_forced(count - 1 - i);
let s = ctx.get_string(val).expect("coerced");
result.push_str(s);
if has_any_context {
let ctx = ctx.get_string_context(val);
if !ctx.is_empty() {
merged = merged.merge(ctx);
}
}
}
self.stack.truncate(self.stack.len() - count);
if merged.is_empty() {
let sid = ctx.intern_string(result);
self.push(Value::new_inline(sid));
} else {
let ns = gc_arena::Gc::new(mc, NixString::with_context(result, merged));
self.push(Value::new_gc(ns));
}
Step::Continue(())
} }
#[inline(always)] m.drop_n(count);
pub(crate) fn op_resolve_path(
&mut self, if merged.is_empty() {
ctx: &mut impl VmRuntimeCtx, let sid = ctx.intern_string(result);
reader: &mut BytecodeReader<'_>, m.push(Value::new_inline(sid));
mc: &gc_arena::Mutation<'gc>, } else {
) -> Step { let ns = gc_arena::Gc::new(mc, NixString::with_context(result, merged));
let path_val = self.force_and_retry::<StrictValue>(reader, mc)?; m.push(Value::new_gc(ns));
let dir_id = reader.read_string_id();
// Already a path: keep as-is. ResolvePath is idempotent on paths.
if let Some(p) = path_val.as_inline::<Path>() {
self.push(Value::new_inline(p));
return Step::Continue(());
}
let path = match ctx.get_string(path_val) {
Some(s) => s.to_owned(),
None => {
return self.finish_err(Error::eval_error(format!(
"expected a string for path, got {}",
path_val.ty()
)));
}
};
let resolved = match resolve_path_str(ctx.resolve_string(dir_id), &path) {
Ok(s) => s,
Err(e) => return self.finish_err(e),
};
let sid = ctx.intern_string(resolved);
self.push(Value::new_inline(Path(sid)));
Step::Continue(())
} }
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_resolve_path<'gc, M: MachineExt<'gc>>(
m: &mut M,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let path_val = m.force_and_retry::<StrictValue>(reader, mc)?;
let dir_id = reader.read_string_id();
// Already a path: keep as-is. ResolvePath is idempotent on paths.
if let Some(p) = path_val.as_inline::<Path>() {
m.push(Value::new_inline(p));
return Step::Continue(());
}
let path = match ctx.get_string(path_val) {
Some(s) => s.to_owned(),
None => {
return m.finish_err(Error::eval_error(format!(
"expected a string for path, got {}",
path_val.ty()
)));
}
};
let resolved = match resolve_path_str(ctx.resolve_string(dir_id), &path) {
Ok(s) => s,
Err(e) => return m.finish_err(e),
};
let sid = ctx.intern_string(resolved);
m.push(Value::new_inline(Path(sid)));
Step::Continue(())
} }
fn resolve_path_str(current_dir: &str, path: &str) -> Result<String, Box<Error>> { fn resolve_path_str(current_dir: &str, path: &str) -> Result<String, Box<Error>> {
+10
View File
@@ -7,3 +7,13 @@ pub(crate) mod literals;
pub(crate) mod misc; pub(crate) mod misc;
pub(crate) mod variables; pub(crate) mod variables;
pub(crate) mod with_scope; pub(crate) mod with_scope;
pub(crate) use arithmetic::*;
pub(crate) use calls::*;
pub(crate) use closures::*;
pub(crate) use collections::*;
pub(crate) use control::*;
pub(crate) use literals::*;
pub(crate) use misc::*;
pub(crate) use variables::*;
pub(crate) use with_scope::*;
+12 -14
View File
@@ -1,50 +1,48 @@
use fix_runtime::Machine;
use crate::{BytecodeReader, Mutation, Step, Value}; use crate::{BytecodeReader, Mutation, Step, Value};
impl<'gc> crate::Vm<'gc> {
#[inline(always)] #[inline(always)]
pub(crate) fn op_load_local(&mut self, reader: &mut BytecodeReader<'_>) -> Step { pub(crate) fn op_load_local<'gc, M: Machine<'gc>>(m: &mut M, reader: &mut BytecodeReader<'_>) -> Step {
let idx = reader.read_u32() as usize; let idx = reader.read_u32() as usize;
self.push(self.env.borrow().locals[idx]); m.push(m.env().borrow().locals[idx]);
Step::Continue(()) Step::Continue(())
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_load_outer(&mut self, reader: &mut BytecodeReader<'_>) -> Step { pub(crate) fn op_load_outer<'gc, M: Machine<'gc>>(m: &mut M, reader: &mut BytecodeReader<'_>) -> Step {
let layer = reader.read_u8(); let layer = reader.read_u8();
let idx = reader.read_u32() as usize; let idx = reader.read_u32() as usize;
let mut cur = self.env; let mut cur = m.env();
for _ in 0..layer { for _ in 0..layer {
let prev = cur.borrow().prev.expect("LoadOuter: env chain too short"); let prev = cur.borrow().prev.expect("LoadOuter: env chain too short");
cur = prev; cur = prev;
} }
let val = cur.borrow().locals[idx]; let val = cur.borrow().locals[idx];
self.push(val); m.push(val);
Step::Continue(()) Step::Continue(())
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_store_local( pub(crate) fn op_store_local<'gc, M: Machine<'gc>>(m: &mut M,
&mut self,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
let idx = reader.read_u32() as usize; let idx = reader.read_u32() as usize;
let val = self.pop(); let val = m.pop();
self.env.borrow_mut(mc).locals[idx] = val; m.env().borrow_mut(mc).locals[idx] = val;
Step::Continue(()) Step::Continue(())
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_alloc_locals( pub(crate) fn op_alloc_locals<'gc, M: Machine<'gc>>(m: &mut M,
&mut self,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
let count = reader.read_u32() as usize; let count = reader.read_u32() as usize;
self.env m.env()
.borrow_mut(mc) .borrow_mut(mc)
.locals .locals
.extend(std::iter::repeat_n(Value::default(), count)); .extend(std::iter::repeat_n(Value::default(), count));
Step::Continue(()) Step::Continue(())
} }
}
+60 -63
View File
@@ -5,74 +5,71 @@ use smallvec::SmallVec;
use crate::{Break, BytecodeReader, CallFrame, Step, VmRuntimeCtx}; use crate::{Break, BytecodeReader, CallFrame, Step, VmRuntimeCtx};
impl<'gc> crate::Vm<'gc> { #[inline(always)]
#[inline(always)] pub(crate) fn op_lookup_with<'gc, M: Machine<'gc>>(
pub(crate) fn op_lookup_with( m: &mut M,
&mut self, ctx: &mut impl VmRuntimeCtx,
ctx: &mut impl VmRuntimeCtx, reader: &mut BytecodeReader<'_>,
reader: &mut BytecodeReader<'_>, mc: &gc_arena::Mutation<'gc>,
mc: &gc_arena::Mutation<'gc>, ) -> Step {
) -> Step { #[allow(clippy::unwrap_used)]
#[allow(clippy::unwrap_used)] let counter = m.peek_forced(0).as_inline::<i32>().unwrap();
let counter = self.peek_forced(0).as_inline::<i32>().unwrap();
let name = reader.read_string_id(); let name = reader.read_string_id();
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(resolve_operand(&reader.read_operand_data(), mc, ctx, self)); namespaces.push(resolve_operand(&reader.read_operand_data(), mc, ctx, m));
} }
let resume_pc = reader.inst_start_pc(); let resume_pc = reader.inst_start_pc();
let namespace = match namespaces[counter as usize].restrict() { let namespace = match namespaces[counter as usize].restrict() {
Ok(val) => val, Ok(val) => val,
Err(thunk) => { Err(thunk) => {
let mut state = thunk.borrow_mut(mc); let mut state = thunk.borrow_mut(mc);
match *state { match *state {
ThunkState::Pending { ip, env } => { ThunkState::Pending { ip, env } => {
*state = ThunkState::Blackhole; *state = ThunkState::Blackhole;
self.call_stack.push(CallFrame { m.push_call_frame(CallFrame {
thunk: Some(thunk), thunk: Some(thunk),
pc: resume_pc, pc: resume_pc,
env: self.env, env: m.env(),
}); });
self.env = env; m.set_env(env);
reader.set_pc(ip); reader.set_pc(ip);
return Step::Break(Break::Force); return Step::Break(Break::Force);
} }
ThunkState::Evaluated(v) => v, ThunkState::Evaluated(v) => v,
ThunkState::Apply { func, arg } => { ThunkState::Apply { func, arg } => {
self.call_stack.push(CallFrame { m.push_call_frame(CallFrame {
thunk: Some(thunk), thunk: Some(thunk),
pc: resume_pc, pc: resume_pc,
env: self.env, env: m.env(),
}); });
self.push(func); m.push(func);
return self.call(reader, mc, arg, resume_pc); return m.call(reader, mc, arg, resume_pc);
} }
ThunkState::Blackhole => { ThunkState::Blackhole => {
return self return m.finish_err(Error::eval_error("infinite recursion encountered"));
.finish_err(Error::eval_error("infinite recursion encountered"));
}
} }
} }
};
if let Some(val) = namespace
.as_gc::<AttrSet>()
.and_then(|attrs| attrs.lookup(name))
{
self.replace(0, val);
} else if counter + 1 == n as i32 {
return self.finish_err(Error::eval_error(format!(
"undefined variable '{}'",
Symbol::from(ctx.resolve_string(name))
)));
} else {
self.replace(0, Value::new_inline(counter + 1));
reader.set_pc(resume_pc);
} }
};
Step::Continue(()) if let Some(val) = namespace
.as_gc::<AttrSet>()
.and_then(|attrs| attrs.lookup(name))
{
m.replace(0, val);
} else if counter + 1 == n as i32 {
return m.finish_err(Error::eval_error(format!(
"undefined variable '{}'",
Symbol::from(ctx.resolve_string(name))
)));
} else {
m.replace(0, Value::new_inline(counter + 1));
reader.set_pc(resume_pc);
} }
Step::Continue(())
} }
+81 -199
View File
@@ -137,45 +137,20 @@ impl<'gc> Vm<'gc> {
functor_sym: ctx.intern_string("__functor"), functor_sym: ctx.intern_string("__functor"),
} }
} }
}
#[inline(always)] impl<'gc> Machine<'gc> for Vm<'gc> {
fn finish_ok(&mut self, val: fix_lang::Value) -> Step {
self.result = Some(Ok(val));
Step::Break(Break::Done)
}
#[inline(always)]
fn finish_err(&mut self, err: Box<Error>) -> Step {
self.result = Some(Err(err));
Step::Break(Break::Done)
}
#[inline(always)]
fn finish_type_err(&mut self, expected: NixType, got: NixType) -> Step {
self.result = Some(Err(Error::eval_error(format!(
"expected {expected}, got {got}"
))));
Step::Break(Break::Done)
}
#[inline(always)]
fn finish_vm_err(&mut self, err: VmError) -> Step {
self.finish_err(err.into_error())
}
#[inline(always)] #[inline(always)]
fn push(&mut self, val: Value<'gc>) { fn push(&mut self, val: Value<'gc>) {
self.stack.push(val); self.stack.push(val);
} }
#[inline(always)] #[inline(always)]
#[must_use]
fn pop(&mut self) -> Value<'gc> { fn pop(&mut self) -> Value<'gc> {
self.stack.pop().expect("stack underflow") self.stack.pop().expect("stack underflow")
} }
#[inline(always)] #[inline(always)]
#[must_use]
fn peek(&self, depth: usize) -> Value<'gc> { fn peek(&self, depth: usize) -> Value<'gc> {
*self *self
.stack .stack
@@ -184,7 +159,6 @@ impl<'gc> Vm<'gc> {
} }
#[inline(always)] #[inline(always)]
#[must_use]
fn peek_forced(&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)
@@ -193,6 +167,15 @@ impl<'gc> Vm<'gc> {
.expect("forced") .expect("forced")
} }
#[inline(always)]
fn pop_forced(&mut self) -> StrictValue<'gc> {
self.stack
.pop()
.expect("stack underflow")
.restrict()
.expect("forced")
}
#[inline(always)] #[inline(always)]
fn replace(&mut self, depth: usize, val: Value<'gc>) { fn replace(&mut self, depth: usize, val: Value<'gc>) {
let len = self.stack.len(); let len = self.stack.len();
@@ -203,72 +186,13 @@ impl<'gc> Vm<'gc> {
} }
#[inline(always)] #[inline(always)]
#[cfg_attr(debug_assertions, track_caller)] fn drop_n(&mut self, depth: usize) {
fn pop_forced(&mut self) -> StrictValue<'gc> { self.stack.truncate(self.stack.len() - depth);
self.stack
.pop()
.expect("stack underflow")
.restrict()
.expect("forced")
}
/// 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. Calling `force_and_retry` twice (or more)
/// means the handler will be re-run from the top after each retry;
/// any stack modifications between the two calls would be duplicated
/// and corrupt the stack layout.
///
/// * The caller must ensure that the stack layout at the point of
/// invocation is **identical** every time the handler is re-entered.
/// In practice this means no pushes, pops, or local mutations may
/// happen before the call, and the call must be the first thing
/// that consumes the instruction's operand values.
///
/// * The return value must be propagated with `?` so that
/// `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>,
) -> std::ops::ControlFlow<Break, T> {
self.force_and_retry_pc(reader, mc, reader.inst_start_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,
) -> std::ops::ControlFlow<Break, T> {
T::force_and_check(self, reader, mc, 0, resume_pc)?;
std::ops::ControlFlow::Continue(T::pop_converted(self))
} }
#[inline(always)] #[inline(always)]
#[allow(unused)] fn stack_len(&self) -> usize {
fn force_slot( self.stack.len()
&mut self,
depth: usize,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
self.force_slot_to_pc(depth, reader, mc, reader.inst_start_pc())
} }
#[inline(always)] #[inline(always)]
@@ -313,54 +237,6 @@ impl<'gc> Vm<'gc> {
} }
} }
} }
}
impl<'gc> Machine<'gc> for Vm<'gc> {
#[inline(always)]
fn push(&mut self, val: Value<'gc>) {
self.push(val);
}
#[inline(always)]
fn pop(&mut self) -> Value<'gc> {
self.pop()
}
#[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)] #[inline(always)]
fn call( fn call(
@@ -370,7 +246,7 @@ impl<'gc> Machine<'gc> for Vm<'gc> {
arg: Value<'gc>, arg: Value<'gc>,
resume_pc: usize, resume_pc: usize,
) -> Step { ) -> Step {
self.call(reader, mc, arg, resume_pc) instructions::call(self, reader, mc, arg, resume_pc)
} }
#[inline(always)] #[inline(always)]
@@ -410,17 +286,22 @@ impl<'gc> Machine<'gc> for Vm<'gc> {
#[inline(always)] #[inline(always)]
fn finish_ok(&mut self, val: fix_lang::Value) -> Step { fn finish_ok(&mut self, val: fix_lang::Value) -> Step {
self.finish_ok(val) self.result = Some(Ok(val));
Step::Break(Break::Done)
} }
#[inline(always)] #[inline(always)]
fn finish_err(&mut self, err: Box<Error>) -> Step { fn finish_err(&mut self, err: Box<Error>) -> Step {
self.finish_err(err) self.result = Some(Err(err));
Step::Break(Break::Done)
} }
#[inline(always)] #[inline(always)]
fn finish_type_err(&mut self, expected: NixType, got: NixType) -> Step { fn finish_type_err(&mut self, expected: NixType, got: NixType) -> Step {
self.finish_type_err(expected, got) self.result = Some(Err(Error::eval_error(format!(
"expected {expected}, got {got}"
))));
Step::Break(Break::Done)
} }
#[inline(always)] #[inline(always)]
@@ -590,6 +471,7 @@ impl<'gc> Vm<'gc> {
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Action { ) -> Action {
use fix_bytecode::Op::*; use fix_bytecode::Op::*;
use instructions::*;
let mut reader = BytecodeReader::new(bytecode, pc); let mut reader = BytecodeReader::new(bytecode, pc);
let mut fuel = Self::DEFAULT_FUEL_AMOUNT; let mut fuel = Self::DEFAULT_FUEL_AMOUNT;
@@ -603,75 +485,75 @@ impl<'gc> Vm<'gc> {
let op = reader.read_op(); let op = reader.read_op();
let result = match op { let result = match op {
PushSmi => self.op_push_smi(&mut reader), PushSmi => op_push_smi(self, &mut reader),
PushBigInt => self.op_push_bigint(&mut reader, mc), PushBigInt => op_push_bigint(self, &mut reader, mc),
PushFloat => self.op_push_float(&mut reader), PushFloat => op_push_float(self, &mut reader),
PushString => self.op_push_string(&mut reader), PushString => op_push_string(self, &mut reader),
PushNull => self.op_push_null(), PushNull => op_push_null(self),
PushTrue => self.op_push_true(), PushTrue => op_push_true(self),
PushFalse => self.op_push_false(), PushFalse => op_push_false(self),
LoadLocal => self.op_load_local(&mut reader), LoadLocal => op_load_local(self, &mut reader),
LoadOuter => self.op_load_outer(&mut reader), LoadOuter => op_load_outer(self, &mut reader),
StoreLocal => self.op_store_local(&mut reader, mc), StoreLocal => op_store_local(self, &mut reader, mc),
AllocLocals => self.op_alloc_locals(&mut reader, mc), AllocLocals => op_alloc_locals(self, &mut reader, mc),
MakeThunk => self.op_make_thunk(&mut reader, mc), MakeThunk => op_make_thunk(self, &mut reader, mc),
MakeClosure => self.op_make_closure(&mut reader, mc), MakeClosure => op_make_closure(self, &mut reader, mc),
MakePatternClosure => self.op_make_pattern_closure(&mut reader, mc), MakePatternClosure => op_make_pattern_closure(self, &mut reader, mc),
Call => self.op_call(ctx, &mut reader, mc), Call => op_call(self, ctx, &mut reader, mc),
DispatchPrimOp => self.op_dispatch_primop(ctx, &mut reader, mc), DispatchPrimOp => op_dispatch_primop(self, ctx, &mut reader, mc),
Return => self.op_return(ctx, &mut reader, mc), Return => op_return(self, ctx, &mut reader, mc),
MakeAttrs => self.op_make_attrs(ctx, &mut reader, mc), MakeAttrs => op_make_attrs(self, ctx, &mut reader, mc),
MakeEmptyAttrs => self.op_make_empty_attrs(), MakeEmptyAttrs => op_make_empty_attrs(self),
SelectStatic => self.op_select_static(ctx, &mut reader, mc), SelectStatic => op_select_static(self, ctx, &mut reader, mc),
SelectDynamic => self.op_select_dynamic(ctx, &mut reader, mc), SelectDynamic => op_select_dynamic(self, ctx, &mut reader, mc),
HasAttrPathStatic => self.op_has_attr_path_static(ctx, &mut reader, mc), HasAttrPathStatic => op_has_attr_path_static(self, ctx, &mut reader, mc),
HasAttrPathDynamic => self.op_has_attr_path_dynamic(ctx, &mut reader, mc), HasAttrPathDynamic => op_has_attr_path_dynamic(self, ctx, &mut reader, mc),
HasAttrStatic => self.op_has_attr_static(&mut reader, mc), HasAttrStatic => op_has_attr_static(self, &mut reader, mc),
HasAttrDynamic => self.op_has_attr_dynamic(ctx, &mut reader, mc), HasAttrDynamic => op_has_attr_dynamic(self, ctx, &mut reader, mc),
HasAttrResolve => self.op_has_attr_resolve(), HasAttrResolve => op_has_attr_resolve(self),
JumpIfSelectFailed => self.op_jump_if_select_failed(&mut reader), JumpIfSelectFailed => op_jump_if_select_failed(self, &mut reader),
JumpIfSelectSucceeded => self.op_jump_if_select_succeeded(&mut reader), JumpIfSelectSucceeded => op_jump_if_select_succeeded(self, &mut reader),
MakeList => self.op_make_list(ctx, &mut reader, mc), MakeList => op_make_list(self, ctx, &mut reader, mc),
MakeEmptyList => self.op_make_empty_list(), MakeEmptyList => op_make_empty_list(self),
OpAdd => self.op_add(ctx, &mut reader, mc), OpAdd => op_add(self, ctx, &mut reader, mc),
OpSub => self.op_sub(&mut reader, mc), OpSub => op_sub(self, &mut reader, mc),
OpMul => self.op_mul(&mut reader, mc), OpMul => op_mul(self, &mut reader, mc),
OpDiv => self.op_div(&mut reader, mc), OpDiv => op_div(self, &mut reader, mc),
OpEq => self.op_eq(ctx, &mut reader, mc), OpEq => op_eq(self, ctx, &mut reader, mc),
OpNeq => self.op_neq(ctx, &mut reader, mc), OpNeq => op_neq(self, ctx, &mut reader, mc),
OpLt => self.op_lt(ctx, &mut reader, mc), OpLt => op_lt(self, ctx, &mut reader, mc),
OpGt => self.op_gt(ctx, &mut reader, mc), OpGt => op_gt(self, ctx, &mut reader, mc),
OpLeq => self.op_leq(ctx, &mut reader, mc), OpLeq => op_leq(self, ctx, &mut reader, mc),
OpGeq => self.op_geq(ctx, &mut reader, mc), OpGeq => op_geq(self, ctx, &mut reader, mc),
OpConcat => self.op_concat(&mut reader, mc), OpConcat => op_concat(self, &mut reader, mc),
OpUpdate => self.op_update(&mut reader, mc), OpUpdate => op_update(self, &mut reader, mc),
OpNeg => self.op_neg(&mut reader, mc), OpNeg => op_neg(self, &mut reader, mc),
OpNot => self.op_not(&mut reader, mc), OpNot => op_not(self, &mut reader, mc),
JumpIfFalse => self.op_jump_if_false(&mut reader, mc), JumpIfFalse => op_jump_if_false(self, &mut reader, mc),
JumpIfTrue => self.op_jump_if_true(&mut reader, mc), JumpIfTrue => op_jump_if_true(self, &mut reader, mc),
Jump => self.op_jump(&mut reader), Jump => op_jump(self, &mut reader),
ConcatStrings => self.op_concat_strings(ctx, &mut reader, mc), ConcatStrings => op_concat_strings(self, ctx, &mut reader, mc),
CoerceToString => self.op_coerce_to_string(&mut reader, mc), CoerceToString => op_coerce_to_string(self, &mut reader, mc),
ResolvePath => self.op_resolve_path(ctx, &mut reader, mc), ResolvePath => op_resolve_path(self, ctx, &mut reader, mc),
Assert => self.op_assert(ctx, &mut reader, mc), Assert => op_assert(self, ctx, &mut reader, mc),
LookupWith => self.op_lookup_with(ctx, &mut reader, mc), LookupWith => op_lookup_with(self, ctx, &mut reader, mc),
LoadBuiltins => self.op_load_builtins(), LoadBuiltins => op_load_builtins(self),
LoadBuiltin => self.op_load_builtin(&mut reader), LoadBuiltin => op_load_builtin(self, &mut reader),
LoadReplBinding => self.op_load_repl_binding(&mut reader), LoadReplBinding => op_load_repl_binding(self, &mut reader),
LoadScopedBinding => self.op_load_scoped_binding(ctx, &mut reader, mc), LoadScopedBinding => op_load_scoped_binding(self, ctx, &mut reader, mc),
Illegal => unreachable!(), Illegal => unreachable!(),
}; };
+13 -13
View File
@@ -3,6 +3,18 @@ name = "fix"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
[[bench]]
harness = false
name = "basic_ops"
[[bench]]
harness = false
name = "builtins"
[[bench]]
harness = false
name = "thunk_scope"
[dependencies] [dependencies]
mimalloc = "0.1" mimalloc = "0.1"
@@ -17,9 +29,9 @@ clap = { version = "4", features = ["derive"] }
tracing = "0.1" tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-subscriber = { version = "0.3", features = ["env-filter"] }
miette = { version = "7.4", features = ["fancy"] }
# Error Reporting # Error Reporting
thiserror = "2" thiserror = "2"
miette = { version = "7.4", features = ["fancy"] }
# Data Structure # Data Structure
hashbrown = { workspace = true } hashbrown = { workspace = true }
@@ -38,15 +50,3 @@ fix-vm = { path = "../fix-vm" }
criterion = { version = "0.8", features = ["html_reports"] } criterion = { version = "0.8", features = ["html_reports"] }
tempfile = "3.24" tempfile = "3.24"
test-log = { version = "0.2", features = ["trace"] } test-log = { version = "0.2", features = ["trace"] }
[[bench]]
name = "basic_ops"
harness = false
[[bench]]
name = "builtins"
harness = false
[[bench]]
name = "thunk_scope"
harness = false
+1
View File
@@ -47,6 +47,7 @@
just just
samply samply
tokei tokei
tombi
# llm-agents.codex # llm-agents.codex
llm-agents.claude-code llm-agents.claude-code
+2 -2
View File
@@ -1,7 +1,7 @@
[files] [files]
extend-exclude = [ extend-exclude = [
"nix-js/tests/tests/regex.rs", "nix-js/tests/tests/regex.rs",
"nix-js/tests/tests/lang", "nix-js/tests/tests/lang",
] ]
[default.extend-words] [default.extend-words]