refactor vm

This commit is contained in:
2026-04-19 17:23:51 +08:00
parent ca7f7a5ec8
commit e527d31450
14 changed files with 1504 additions and 906 deletions
+7
View File
@@ -25,12 +25,14 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
} }
} }
#[inline(always)]
fn read_u8(&mut self) -> u8 { fn read_u8(&mut self) -> u8 {
let b = self.code[self.pc]; let b = self.code[self.pc];
self.pc += 1; self.pc += 1;
b b
} }
#[inline(always)]
fn read_u16(&mut self) -> u16 { fn read_u16(&mut self) -> u16 {
let bytes = self.code[self.pc..self.pc + 2] let bytes = self.code[self.pc..self.pc + 2]
.try_into() .try_into()
@@ -39,6 +41,7 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
u16::from_le_bytes(bytes) u16::from_le_bytes(bytes)
} }
#[inline(always)]
fn read_u32(&mut self) -> u32 { fn read_u32(&mut self) -> u32 {
let bytes = self.code[self.pc..self.pc + 4] let bytes = self.code[self.pc..self.pc + 4]
.try_into() .try_into()
@@ -47,6 +50,7 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
u32::from_le_bytes(bytes) u32::from_le_bytes(bytes)
} }
#[inline(always)]
fn read_i32(&mut self) -> i32 { fn read_i32(&mut self) -> i32 {
let bytes = self.code[self.pc..self.pc + 4] let bytes = self.code[self.pc..self.pc + 4]
.try_into() .try_into()
@@ -55,6 +59,7 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
i32::from_le_bytes(bytes) i32::from_le_bytes(bytes)
} }
#[inline(always)]
fn read_i64(&mut self) -> i64 { fn read_i64(&mut self) -> i64 {
let bytes = self.code[self.pc..self.pc + 8] let bytes = self.code[self.pc..self.pc + 8]
.try_into() .try_into()
@@ -63,6 +68,7 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
i64::from_le_bytes(bytes) i64::from_le_bytes(bytes)
} }
#[inline(always)]
fn read_f64(&mut self) -> f64 { fn read_f64(&mut self) -> f64 {
let bytes = self.code[self.pc..self.pc + 8] let bytes = self.code[self.pc..self.pc + 8]
.try_into() .try_into()
@@ -71,6 +77,7 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
f64::from_le_bytes(bytes) f64::from_le_bytes(bytes)
} }
#[inline(always)]
fn read_operand_data(&mut self) { fn read_operand_data(&mut self) {
let tag = self.read_u8(); let tag = self.read_u8();
let ty = OperandType::try_from_primitive(tag).expect("invalid operand type"); let ty = OperandType::try_from_primitive(tag).expect("invalid operand type");
+135
View File
@@ -0,0 +1,135 @@
use fix_codegen::OperandType;
use fix_common::StringId;
use num_enum::TryFromPrimitive;
use string_interner::Symbol as _;
use crate::OperandData;
pub(crate) struct BytecodeReader<'a> {
bytecode: &'a [u8],
pc: usize,
inst_start_pc: usize,
}
impl<'a> BytecodeReader<'a> {
pub(crate) fn new(bytecode: &'a [u8], pc: usize) -> Self {
Self {
bytecode,
pc,
inst_start_pc: pc,
}
}
#[inline(always)]
#[cfg_attr(debug_assertions, track_caller)]
fn read_array<const N: usize>(&mut self) -> [u8; N] {
let ret = self.bytecode[self.pc..self.pc + N]
.try_into()
.expect("read_array failed");
self.pc += N;
ret
}
#[inline(always)]
pub(crate) fn read_op(&mut self) -> fix_codegen::Op {
use fix_codegen::Op;
self.inst_start_pc = self.pc;
let byte = self.bytecode[self.pc];
if !likely_stable::likely((0..Op::Illegal as u8).contains(&byte)) {
panic!("unknown opcode: {byte:#04x}")
}
self.pc += 1;
unsafe { std::mem::transmute::<u8, Op>(byte) }
}
#[inline(always)]
pub(crate) fn read_u8(&mut self) -> u8 {
let val = self.bytecode[self.pc];
self.pc += 1;
val
}
#[inline(always)]
pub(crate) fn read_u16(&mut self) -> u16 {
u16::from_le_bytes(self.read_array())
}
#[inline(always)]
pub(crate) fn read_u32(&mut self) -> u32 {
u32::from_le_bytes(self.read_array())
}
#[inline(always)]
pub(crate) fn read_i32(&mut self) -> i32 {
i32::from_le_bytes(self.read_array())
}
#[inline(always)]
pub(crate) fn read_i64(&mut self) -> i64 {
i64::from_le_bytes(self.read_array())
}
#[inline(always)]
pub(crate) fn read_f64(&mut self) -> f64 {
f64::from_le_bytes(self.read_array())
}
#[inline(always)]
pub(crate) fn read_string_id(&mut self) -> StringId {
let raw = self.read_u32();
StringId(string_interner::symbol::SymbolU32::try_from_usize(raw as usize).unwrap())
}
#[inline(always)]
pub(crate) fn read_operand_data<C: crate::VmContext>(&mut self, ctx: &C) -> OperandData {
let tag = self.read_u8();
let Ok(ty) = OperandType::try_from_primitive(tag)
.map_err(|err| panic!("unknown operand tag: {:#04x}", err.number));
match ty {
OperandType::Const => {
let id = self.read_u32();
OperandData::Const(ctx.get_const(id))
}
OperandType::Local => {
let layer = self.read_u8();
let idx = self.read_u32();
OperandData::Local { layer, idx }
}
OperandType::Builtins => OperandData::Builtins,
OperandType::BigInt => {
let val = self.read_i64();
OperandData::BigInt(val)
}
}
}
#[inline(always)]
pub(crate) fn read_attr_key_data<C: crate::VmContext>(&mut self, ctx: &C) -> crate::AttrKeyData {
use fix_codegen::AttrKeyType;
let tag = self.read_u8();
let ty = AttrKeyType::try_from_primitive(tag)
.unwrap_or_else(|err| panic!("unknown key tag: {:#04x}", err.number));
match ty {
AttrKeyType::Static => crate::AttrKeyData::Static(self.read_string_id()),
AttrKeyType::Dynamic => {
crate::AttrKeyData::Dynamic(self.read_operand_data(ctx))
}
}
}
pub(crate) fn pc(&self) -> usize {
self.pc
}
pub(crate) fn set_pc(&mut self, pc: usize) {
self.pc = pc;
}
pub(crate) fn inst_start_pc(&self) -> usize {
self.inst_start_pc
}
pub(crate) fn bytecode(&self) -> &'a [u8] {
self.bytecode
}
}
+2 -13
View File
@@ -1,18 +1,7 @@
use fix_error::Error; use fix_error::Error;
use crate::value::StrictValue; use crate::VmError;
use crate::{NixNum, VmError};
pub(super) fn vm_err(msg: impl Into<String>) -> VmError { pub(crate) fn vm_err(msg: impl Into<String>) -> VmError {
VmError::Uncatchable(Error::eval_error(msg.into())) VmError::Uncatchable(Error::eval_error(msg.into()))
} }
pub(super) fn get_num(val: StrictValue<'_>) -> Option<NixNum> {
if let Some(i) = val.as_inline::<i32>() {
Some(NixNum::Int(i as i64))
} else if let Some(gc_i) = val.as_gc::<i64>() {
Some(NixNum::Int(*gc_i))
} else {
val.as_float().map(NixNum::Float)
}
}
+416
View File
@@ -0,0 +1,416 @@
use std::cmp::Ordering;
use gc_arena::{Gc, Mutation};
use crate::{BytecodeReader, NixNum, StepResult, StrictValue, VmError, Value};
use crate::VmContextExt;
impl<'gc> crate::Vm<'gc> {
#[inline(always)]
pub(crate) fn op_add(
&mut self,
ctx: &mut impl crate::VmContext,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> StepResult<'gc> {
if let Some(step) = self.try_force_resolved(1, reader.inst_start_pc(), mc) {
return step;
}
if let Some(step) = self.try_force_resolved(0, reader.inst_start_pc(), mc) {
return step;
}
let rhs = self.pop_stack_forced();
let lhs = self.pop_stack_forced();
if let (Some(ls), Some(rs)) = (VmContextExt::get_string(ctx, lhs), VmContextExt::get_string(ctx, rhs)) {
let ns = Gc::new(mc, crate::NixString::new(format!("{ls}{rs}")));
self.push_stack(Value::new_gc(ns));
return StepResult::Continue;
}
let res = numeric_binop(lhs, rhs, mc, i64::wrapping_add, |a, b| a + b);
match res {
Ok(val) => {
self.push_stack(val);
StepResult::Continue
}
Err(e) => e.into_step_result(),
}
}
#[inline(always)]
pub(crate) fn op_sub(
&mut self,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> StepResult<'gc> {
self.op_arith(mc, i64::wrapping_sub, |a, b| a - b, reader.inst_start_pc())
}
#[inline(always)]
pub(crate) fn op_mul(
&mut self,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> StepResult<'gc> {
self.op_arith(mc, i64::wrapping_mul, |a, b| a * b, reader.inst_start_pc())
}
#[inline(always)]
fn op_arith(
&mut self,
mc: &Mutation<'gc>,
int_op: fn(i64, i64) -> i64,
float_op: fn(f64, f64) -> f64,
inst_start_pc: usize,
) -> StepResult<'gc> {
if let Some(step) = self.try_force_resolved(1, inst_start_pc, mc) {
return step;
}
if let Some(step) = self.try_force_resolved(0, inst_start_pc, mc) {
return step;
}
let rhs = self.pop_stack_forced();
let lhs = self.pop_stack_forced();
let res = numeric_binop(lhs, rhs, mc, int_op, float_op);
match res {
Ok(val) => {
self.push_stack(val);
StepResult::Continue
}
Err(e) => e.into_step_result(),
}
}
#[inline(always)]
pub(crate) fn op_div(
&mut self,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> StepResult<'gc> {
if let Some(step) = self.try_force_resolved(1, reader.inst_start_pc(), mc) {
return step;
}
if let Some(step) = self.try_force_resolved(0, reader.inst_start_pc(), mc) {
return step;
}
let rhs = self.pop_stack_forced();
let lhs = self.pop_stack_forced();
match (get_num(rhs), get_num(lhs)) {
(_, Some(NixNum::Int(0))) | (_, Some(NixNum::Float(0.))) => {
return VmError::Uncatchable(fix_error::Error::eval_error(
"division by zero",
))
.into_step_result();
}
_ => {}
}
let res = numeric_binop(lhs, rhs, mc, |a, b| a / b, |a, b| a / b);
match res {
Ok(val) => {
self.push_stack(val);
StepResult::Continue
}
Err(e) => e.into_step_result(),
}
}
#[inline(always)]
pub(crate) fn op_eq(
&mut self,
ctx: &mut impl crate::VmContext,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> StepResult<'gc> {
if let Some(step) = self.try_force_resolved(1, reader.inst_start_pc(), mc) {
return step;
}
if let Some(step) = self.try_force_resolved(0, reader.inst_start_pc(), mc) {
return step;
}
let eq = match self.values_equal(ctx) {
Ok(eq) => eq,
Err(e) => return e.into_step_result(),
};
self.push_stack(Value::new_inline(eq));
StepResult::Continue
}
#[inline(always)]
pub(crate) fn op_neq(
&mut self,
ctx: &mut impl crate::VmContext,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> StepResult<'gc> {
if let Some(step) = self.try_force_resolved(1, reader.inst_start_pc(), mc) {
return step;
}
if let Some(step) = self.try_force_resolved(0, reader.inst_start_pc(), mc) {
return step;
}
let eq = match self.values_equal(ctx) {
Ok(eq) => eq,
Err(e) => return e.into_step_result(),
};
self.push_stack(Value::new_inline(!eq));
StepResult::Continue
}
#[inline(always)]
pub(crate) fn op_lt(
&mut self,
ctx: &mut impl crate::VmContext,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> StepResult<'gc> {
self.compare_values(ctx, reader, mc, Ordering::is_lt)
}
#[inline(always)]
pub(crate) fn op_gt(
&mut self,
ctx: &mut impl crate::VmContext,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> StepResult<'gc> {
self.compare_values(ctx, reader, mc, Ordering::is_gt)
}
#[inline(always)]
pub(crate) fn op_leq(
&mut self,
ctx: &mut impl crate::VmContext,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> StepResult<'gc> {
self.compare_values(ctx, reader, mc, Ordering::is_le)
}
#[inline(always)]
pub(crate) fn op_geq(
&mut self,
ctx: &mut impl crate::VmContext,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> StepResult<'gc> {
self.compare_values(ctx, reader, mc, Ordering::is_ge)
}
fn compare_values(
&mut self,
ctx: &impl crate::VmContext,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
pred: fn(Ordering) -> bool,
) -> StepResult<'gc> {
if let Some(step) = self.try_force_resolved(1, reader.inst_start_pc(), mc) {
return step;
}
if let Some(step) = self.try_force_resolved(0, reader.inst_start_pc(), mc) {
return step;
}
match self.compare_values_inner(ctx, pred) {
Ok(()) => StepResult::Continue,
Err(e) => e.into_step_result(),
}
}
#[inline(always)]
pub(crate) fn op_concat(
&mut self,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> StepResult<'gc> {
if let Some(step) = self.try_force_resolved(1, reader.inst_start_pc(), mc) {
return step;
}
if let Some(step) = self.try_force_resolved(0, reader.inst_start_pc(), mc) {
return step;
}
let rhs = self.pop_stack_forced();
let lhs = self.pop_stack_forced();
let Some(l) = lhs.as_gc::<crate::List>() else {
return VmError::Uncatchable(fix_error::Error::eval_error(
"cannot concatenate: left operand is not a list",
))
.into_step_result();
};
let Some(r) = rhs.as_gc::<crate::List>() else {
return VmError::Uncatchable(fix_error::Error::eval_error(
"cannot concatenate: right operand is not a list",
))
.into_step_result();
};
let mut items = smallvec::SmallVec::new();
items.extend_from_slice(&l);
items.extend_from_slice(&r);
self.push_stack(Value::new_gc(Gc::new(mc, crate::List { inner: items })));
StepResult::Continue
}
#[inline(always)]
pub(crate) fn op_update(
&mut self,
mc: &Mutation<'gc>,
inst_start_pc: usize,
) -> StepResult<'gc> {
if let Some(step) = self.try_force_resolved(1, inst_start_pc, mc) {
return step;
}
if let Some(step) = self.try_force_resolved(0, inst_start_pc, mc) {
return step;
}
let rhs = self.pop_stack_forced();
let lhs = self.pop_stack_forced();
let Some(l) = lhs.as_gc::<crate::AttrSet>() else {
return VmError::Uncatchable(fix_error::Error::eval_error(
"cannot update: left operand is not a set",
))
.into_step_result();
};
let Some(r) = rhs.as_gc::<crate::AttrSet>() else {
return VmError::Uncatchable(fix_error::Error::eval_error(
"cannot update: right operand is not a set",
))
.into_step_result();
};
self.push_stack(Value::new_gc(l.merge(&r, mc)));
StepResult::Continue
}
#[inline(always)]
pub(crate) fn op_neg(&mut self) -> StepResult<'gc> {
todo!("implement unary operation");
}
#[inline(always)]
pub(crate) fn op_not(&mut self) -> StepResult<'gc> {
todo!("implement unary operation");
}
pub(crate) fn values_equal(&mut self, ctx: &impl crate::VmContext) -> crate::VmResult<bool> {
let rhs = self.pop_stack_forced();
let lhs = self.pop_stack_forced();
if let (Some(a), Some(b)) = (get_num(lhs), get_num(rhs)) {
return Ok(match (a, b) {
(NixNum::Int(a), NixNum::Int(b)) => a == b,
(NixNum::Float(a), NixNum::Float(b)) => a == b,
(NixNum::Int(a), NixNum::Float(b)) => a as f64 == b,
(NixNum::Float(a), NixNum::Int(b)) => a == b as f64,
});
}
if let (Some(a), Some(b)) = (lhs.as_inline::<bool>(), rhs.as_inline::<bool>()) {
return Ok(a == b);
}
if lhs.is::<crate::Null>() && rhs.is::<crate::Null>() {
return Ok(true);
}
if let (Some(a), Some(b)) = (VmContextExt::get_string(ctx, lhs), VmContextExt::get_string(ctx, rhs)) {
return Ok(a == b);
}
if let (Some(a), Some(b)) = (lhs.as_gc::<crate::List>(), rhs.as_gc::<crate::List>()) {
if a.inner.len() != b.inner.len() {
return Ok(false);
}
let len = a.inner.len();
for (x, y) in a.inner.iter().zip(b.inner.iter()).rev() {
self.push_stack(*x);
self.push_stack(*y);
}
for i in 0..len {
let eq = self.values_equal(ctx)?;
if !eq {
let rem = len - 1 - i;
self.stack.truncate(self.stack.len() - rem * 2);
return Ok(false);
}
}
return Ok(true);
}
if let (Some(a), Some(b)) = (lhs.as_gc::<crate::AttrSet>(), rhs.as_gc::<crate::AttrSet>()) {
if a.len() != b.len() {
return Ok(false);
}
let len = a.len();
for ((k1, v1), (k2, v2)) in a.iter().zip(b.iter()).rev() {
if k1 != k2 {
return Ok(false);
}
self.push_stack(*v1);
self.push_stack(*v2);
}
for i in 0..len {
let eq = self.values_equal(ctx)?;
if !eq {
let rem = len - 1 - i;
self.stack.truncate(self.stack.len() - rem * 2);
return Ok(false);
}
}
return Ok(true);
}
Ok(false)
}
fn compare_values_inner(
&mut self,
ctx: &impl crate::VmContext,
pred: fn(Ordering) -> bool,
) -> crate::VmResult<()> {
let rhs = self.pop_stack_forced();
let lhs = self.pop_stack_forced();
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),
};
self.push_stack(Value::new_inline(pred(ord)));
return Ok(());
}
if let (Some(a), Some(b)) = (VmContextExt::get_string(ctx, lhs), VmContextExt::get_string(ctx, rhs)) {
self.push_stack(Value::new_inline(pred(a.cmp(b))));
return Ok(());
}
Err(crate::vm_err("cannot compare these types"))
}
}
pub(crate) fn get_num(val: StrictValue<'_>) -> Option<NixNum> {
if let Some(i) = val.as_inline::<i32>() {
Some(NixNum::Int(i as i64))
} else if let Some(gc_i) = val.as_gc::<i64>() {
Some(NixNum::Int(*gc_i))
} else {
val.as_float().map(NixNum::Float)
}
}
#[inline]
fn numeric_binop<'gc>(
lhs: StrictValue<'gc>,
rhs: StrictValue<'gc>,
mc: &Mutation<'gc>,
int_op: fn(i64, i64) -> i64,
float_op: fn(f64, f64) -> f64,
) -> crate::VmResult<Value<'gc>> {
match (get_num(lhs), get_num(rhs)) {
(Some(NixNum::Int(a)), Some(NixNum::Int(b))) => Ok(Value::make_int(int_op(a, b), mc)),
(Some(NixNum::Float(a)), Some(NixNum::Float(b))) => Ok(Value::new_float(float_op(a, b))),
(Some(NixNum::Int(a)), Some(NixNum::Float(b))) => {
Ok(Value::new_float(float_op(a as f64, b)))
}
(Some(NixNum::Float(a)), Some(NixNum::Int(b))) => {
Ok(Value::new_float(float_op(a, b as f64)))
}
_ => Err(crate::vm_err("cannot perform arithmetic on non-numbers")),
}
}
+62
View File
@@ -0,0 +1,62 @@
use crate::{BytecodeReader, PrimOp, StepResult, Value};
use fix_builtins::BuiltinId;
use num_enum::TryFromPrimitive;
impl<'gc> crate::Vm<'gc> {
#[inline(always)]
pub(crate) fn op_load_builtins(&mut self) -> StepResult<'gc> {
self.push_stack(self.builtins);
StepResult::Continue
}
#[inline(always)]
pub(crate) fn op_load_builtin(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> {
let Ok(id) = BuiltinId::try_from_primitive(reader.read_u8())
.map_err(|err| panic!("unknown builtin id: {}", err.number));
self.push_stack(Value::new_inline(PrimOp {
id,
arity: fix_builtins::BUILTINS[id as usize].1,
}));
StepResult::Continue
}
#[inline(always)]
pub(crate) fn op_mk_pos(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> {
let _span_id = reader.read_u32();
todo!("MkPos");
}
#[inline(always)]
pub(crate) fn op_load_repl_binding(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> {
let _name = reader.read_string_id();
todo!("LoadReplBinding");
}
#[inline(always)]
pub(crate) fn op_load_scoped_binding(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> {
let _name = reader.read_string_id();
todo!("LoadScopedBinding");
}
#[inline(always)]
pub(crate) fn op_concat_strings(
&mut self,
ctx: &mut impl crate::VmContext,
reader: &mut BytecodeReader<'_>,
_mc: &gc_arena::Mutation<'gc>,
) -> StepResult<'gc> {
let _parts_count = reader.read_u16() as usize;
let _force_string = reader.read_u8() != 0;
let mut _operands: smallvec::SmallVec<[crate::OperandData; 4]> =
smallvec::SmallVec::with_capacity(_parts_count);
for _ in 0.._parts_count {
_operands.push(reader.read_operand_data(ctx));
}
todo!("implement ConcatStrings (force parts, coerce to string, concatenate)");
}
#[inline(always)]
pub(crate) fn op_resolve_path(&mut self, _ctx: &mut impl crate::VmContext) -> StepResult<'gc> {
todo!("implement ResolvePath");
}
}
+153
View File
@@ -0,0 +1,153 @@
use fix_error::Error;
use crate::{
BytecodeReader, CallFrame, Closure, Env, StepResult, ThunkState,
};
use crate::VmContextExt;
use gc_arena::{Gc, Mutation, RefLock};
impl<'gc> crate::Vm<'gc> {
#[inline(always)]
pub(crate) fn op_call(
&mut self,
ctx: &mut impl crate::VmContext,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> StepResult<'gc> {
if let Some(step) = self.try_force_resolved(0, reader.inst_start_pc(), mc) {
return step;
}
if self.call_depth > 10000 {
return StepResult::Done(Err(Error::eval_error(
"stack overflow; max-call-depth exceeded",
)));
}
self.call_depth += 1;
let func = self.pop_stack();
let arg = reader.read_operand_data(ctx).resolve(mc, self);
if let Some(closure) = func.as_gc::<Closure>() {
let ip = closure.ip;
let n_locals = closure.n_locals;
let env = closure.env;
if let Some(ref _pattern) = closure.pattern {
todo!("pattern call")
} else {
let new_env = Gc::new(mc, RefLock::new(Env::with_arg(arg, n_locals, env)));
self.call_stack.push(CallFrame {
pc: reader.pc(),
stack_depth: 0,
thunk: None,
env: self.env,
with_env: self.with_env,
});
reader.set_pc(ip as usize);
self.env = new_env;
}
} else {
todo!("call other types: {func:?}")
}
StepResult::Continue
}
#[inline(always)]
pub(crate) fn op_return(
&mut self,
ctx: &mut impl crate::VmContext,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> StepResult<'gc> {
match self.handle_return(reader, ctx, mc) {
Some(result) => StepResult::Done(result),
None => StepResult::Continue,
}
}
pub(crate) fn handle_return<C: crate::VmContext>(
&mut self,
reader: &mut BytecodeReader<'_>,
ctx: &C,
mc: &Mutation<'gc>,
) -> Option<std::result::Result<fix_common::Value, Box<Error>>> {
let ret_inst_pc = reader.pc() - 1;
let Some(CallFrame {
pc: ret_pc,
stack_depth,
thunk,
env,
with_env,
}) = self.call_stack.pop()
else {
let val = self.pop_stack();
return Some(Ok(ctx.convert_value(val)));
};
reader.set_pc(ret_pc);
if let Some(outer_thunk) = thunk {
let val = self.pop_stack();
match val.restrict() {
Ok(val) => {
*outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val);
if reader
.bytecode()
.get(ret_pc)
.copied()
== Some(fix_codegen::Op::Return as u8)
{
self.push_stack(val.relax());
}
}
Err(inner_thunk) => {
let mut state = inner_thunk.borrow_mut(mc);
match *state {
ThunkState::Pending {
ip: inner_ip,
env: inner_env,
with_env: inner_with_env,
} => {
self.call_stack.push(CallFrame {
pc: ret_pc,
stack_depth,
thunk: Some(outer_thunk),
env,
with_env,
});
self.call_stack.push(CallFrame {
pc: ret_inst_pc,
stack_depth: 0,
thunk: Some(inner_thunk),
env: inner_env,
with_env: inner_with_env,
});
*state = ThunkState::Blackhole;
reader.set_pc(inner_ip);
self.env = inner_env;
self.with_env = inner_with_env;
return None;
}
ThunkState::Evaluated(val) => {
*outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val);
if reader
.bytecode()
.get(ret_pc)
.copied()
== Some(fix_codegen::Op::Return as u8)
{
self.push_stack(val.relax());
}
}
ThunkState::Apply { func: _, arg: _ } => todo!("force Apply thunk"),
ThunkState::Blackhole => {
return Some(Err(Error::eval_error(
"infinite recursion encountered",
)));
}
}
}
}
} else {
self.call_depth -= 1;
}
self.env = env;
self.with_env = with_env;
None
}
}
+83
View File
@@ -0,0 +1,83 @@
use gc_arena::{Gc, Mutation, RefLock};
use crate::{BytecodeReader, StepResult, ThunkState, Value};
impl<'gc> crate::Vm<'gc> {
#[inline(always)]
pub(crate) fn op_make_thunk(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> StepResult<'gc> {
let entry_point = reader.read_u32();
let thunk = Gc::new(
mc,
RefLock::new(ThunkState::Pending {
ip: entry_point as usize,
env: self.env,
with_env: self.with_env,
}),
);
self.push_stack(Value::new_gc(thunk));
StepResult::Continue
}
#[inline(always)]
pub(crate) fn op_make_closure(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> StepResult<'gc> {
let entry_point = reader.read_u32();
let n_locals = reader.read_u32();
let closure = Gc::new(
mc,
crate::Closure {
ip: entry_point,
n_locals,
env: self.env,
pattern: None,
},
);
self.push_stack(Value::new_gc(closure));
StepResult::Continue
}
#[inline(always)]
pub(crate) fn op_make_pattern_closure(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> StepResult<'gc> {
let entry_point = reader.read_u32();
let n_locals = reader.read_u32();
let req_count = reader.read_u16() as usize;
let opt_count = reader.read_u16() as usize;
let has_ellipsis = reader.read_u8() != 0;
let mut required = smallvec::SmallVec::new();
for _ in 0..req_count {
required.push(reader.read_string_id());
}
let mut optional = smallvec::SmallVec::new();
for _ in 0..opt_count {
optional.push(reader.read_string_id());
}
let total = req_count + opt_count;
let mut param_spans = Vec::with_capacity(total);
for _ in 0..total {
let name = reader.read_string_id();
let span_id = reader.read_u32();
param_spans.push((name, span_id));
}
let pattern = Gc::new(
mc,
crate::PatternInfo {
required,
optional,
ellipsis: has_ellipsis,
param_spans: param_spans.into_boxed_slice(),
},
);
let closure = Gc::new(
mc,
crate::Closure {
ip: entry_point,
n_locals,
env: self.env,
pattern: Some(pattern),
},
);
self.push_stack(Value::new_gc(closure));
StepResult::Continue
}
}
+189
View File
@@ -0,0 +1,189 @@
use fix_error::Error;
use gc_arena::Gc;
use smallvec::SmallVec;
use crate::{
AttrKeyData, AttrSet, BytecodeReader, List, NixString, OperandData, StepResult, Value,
};
impl<'gc> crate::Vm<'gc> {
#[inline(always)]
pub(crate) fn op_make_attrs(
&mut self,
ctx: &mut impl crate::VmContext,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> StepResult<'gc> {
let count = reader.read_u32() as usize;
let mut entries: SmallVec<[AttrEntry; 4]> = SmallVec::with_capacity(count);
for _ in 0..count {
let key = reader.read_attr_key_data(ctx);
let val = reader.read_operand_data(ctx);
let _span_id = reader.read_u32();
entries.push(AttrEntry { key, val });
}
let mut kv: SmallVec<[(crate::StringId, Value); 4]> = SmallVec::with_capacity(count);
for entry in &entries {
let key_sid = match &entry.key {
AttrKeyData::Static(sid) => *sid,
AttrKeyData::Dynamic(op) => {
let v = op.resolve(mc, self);
v.as_inline::<crate::StringId>()
.expect("dynamic attr key must be a string")
}
};
let val = entry.val.resolve(mc, self);
kv.push((key_sid, val));
}
kv.sort_by_key(|(k, _)| *k);
let attrs = Gc::new(mc, AttrSet::from_sorted_unchecked(kv));
self.push_stack(Value::new_gc(attrs));
StepResult::Continue
}
#[inline(always)]
pub(crate) fn op_make_empty_attrs(&mut self) -> StepResult<'gc> {
self.push_stack(self.empty_attrs);
StepResult::Continue
}
#[inline(always)]
pub(crate) fn op_select_static(
&mut self,
ctx: &mut impl crate::VmContext,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> StepResult<'gc> {
let _span_id = reader.read_u32();
let key = reader.read_string_id();
if let Some(step) = self.try_force_resolved(0, reader.inst_start_pc(), mc) {
return step;
}
let attrs = self.peek_stack(0).restrict().expect("forced");
let Some(attrset) = attrs.as_gc::<AttrSet>() else {
return StepResult::Done(Err(Error::eval_error(
"value is not a set while a set was expected",
)));
};
match attrset.lookup(key) {
Some(v) => {
self.replace_stack(0, v);
}
None => {
loop {
let byte = reader.bytecode()[reader.pc()];
if byte == fix_codegen::Op::SelectStatic as u8 {
reader.set_pc(reader.pc() + 1 + 4 + 4);
} else if byte == fix_codegen::Op::SelectDynamic as u8 {
reader.set_pc(reader.pc() + 1 + 4);
} else if byte == fix_codegen::Op::JumpIfSelectSucceeded as u8 {
reader.set_pc(reader.pc() + 1 + 4);
let _ = self.pop_stack();
break;
} else {
let name = ctx.resolve_string(key);
return StepResult::Done(Err(Error::eval_error(format!(
"attribute '{name}' missing"
))));
}
}
}
}
StepResult::Continue
}
#[inline(always)]
pub(crate) fn op_select_dynamic(
&mut self,
ctx: &mut impl crate::VmContext,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> StepResult<'gc> {
let _span_id = reader.read_u32();
if let Some(step) = self.try_force_resolved(0, reader.inst_start_pc(), mc) {
return step;
}
if let Some(step) = self.try_force_resolved(1, reader.inst_start_pc(), mc) {
return step;
}
let key_val = self.stack[self.stack.len() - 1]
.restrict()
.expect("dynamic key must be forced");
let key_sid = if let Some(sid) = key_val.as_inline::<crate::StringId>() {
sid
} else if let Some(ns) = key_val.as_gc::<NixString>() {
ctx.intern_string(ns.as_str())
} else {
return StepResult::Done(Err(Error::eval_error(
"dynamic select key must be a string",
)));
};
let attrset_val = self.stack[self.stack.len() - 2].restrict().expect("forced");
let Some(attrset) = attrset_val.as_gc::<AttrSet>() else {
return StepResult::Done(Err(Error::eval_error(
"value is not a set while a set was expected",
)));
};
match attrset.lookup(key_sid) {
Some(v) => {
self.stack.truncate(self.stack.len() - 2);
self.push_stack(v);
}
None => {
let name = ctx.resolve_string(key_sid);
return StepResult::Done(Err(Error::eval_error(format!(
"attribute '{name}' missing"
))));
}
}
StepResult::Continue
}
#[inline(always)]
pub(crate) fn op_jump_if_select_succeeded(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> {
let offset = reader.read_i32();
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
StepResult::Continue
}
#[inline(always)]
pub(crate) fn op_has_attr(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> {
let _n = reader.read_u16() as usize;
todo!("HasAttr");
}
#[inline(always)]
pub(crate) fn op_make_list(
&mut self,
ctx: &mut impl crate::VmContext,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> StepResult<'gc> {
let count = reader.read_u32() as usize;
let mut items: SmallVec<[Value; 4]> = SmallVec::with_capacity(count);
for _ in 0..count {
items.push(reader.read_operand_data(ctx).resolve(mc, self));
}
let list = Gc::new(mc, List { inner: items });
self.push_stack(Value::new_gc(list));
StepResult::Continue
}
#[inline(always)]
pub(crate) fn op_make_empty_list(&mut self) -> StepResult<'gc> {
self.push_stack(self.empty_list);
StepResult::Continue
}
}
pub(crate) struct AttrEntry {
pub(crate) key: AttrKeyData,
pub(crate) val: OperandData,
}
+51
View File
@@ -0,0 +1,51 @@
use crate::{BytecodeReader, StepResult};
impl<'gc> crate::Vm<'gc> {
#[inline(always)]
pub(crate) fn op_jump_if_false(
&mut self,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> StepResult<'gc> {
let offset = reader.read_i32();
if let Some(step) = self.try_force_resolved(0, reader.inst_start_pc(), mc) {
return step;
}
let cond = self.pop_stack();
if cond.as_inline::<bool>() == Some(false) {
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
}
StepResult::Continue
}
#[inline(always)]
pub(crate) fn op_jump_if_true(
&mut self,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> StepResult<'gc> {
let offset = reader.read_i32();
if let Some(step) = self.try_force_resolved(0, reader.inst_start_pc(), mc) {
return step;
}
let cond = self.pop_stack();
if cond.as_inline::<bool>() == Some(true) {
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
}
StepResult::Continue
}
#[inline(always)]
pub(crate) fn op_jump(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> {
let offset = reader.read_i32();
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
StepResult::Continue
}
#[inline(always)]
pub(crate) fn op_assert(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> {
let _raw_idx = reader.read_u32();
let _span_id = reader.read_u32();
todo!("implement Assert (force TOS)");
}
}
+51
View File
@@ -0,0 +1,51 @@
use gc_arena::{Gc, Mutation};
use crate::{BytecodeReader, StepResult, Value};
impl<'gc> crate::Vm<'gc> {
#[inline(always)]
pub(crate) fn op_push_smi(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> {
let val = reader.read_i32();
self.push_stack(Value::new_inline(val));
StepResult::Continue
}
#[inline(always)]
pub(crate) fn op_push_bigint(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> StepResult<'gc> {
let val = reader.read_i64();
self.push_stack(Value::new_gc(Gc::new(mc, val)));
StepResult::Continue
}
#[inline(always)]
pub(crate) fn op_push_float(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> {
let val = reader.read_f64();
self.push_stack(Value::new_float(val));
StepResult::Continue
}
#[inline(always)]
pub(crate) fn op_push_string(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> {
let sid = reader.read_string_id();
self.push_stack(Value::new_inline(sid));
StepResult::Continue
}
#[inline(always)]
pub(crate) fn op_push_null(&mut self) -> StepResult<'gc> {
self.push_stack(Value::new_inline(crate::Null));
StepResult::Continue
}
#[inline(always)]
pub(crate) fn op_push_true(&mut self) -> StepResult<'gc> {
self.push_stack(Value::new_inline(true));
StepResult::Continue
}
#[inline(always)]
pub(crate) fn op_push_false(&mut self) -> StepResult<'gc> {
self.push_stack(Value::new_inline(false));
StepResult::Continue
}
}
+9
View File
@@ -0,0 +1,9 @@
pub(crate) mod literals;
pub(crate) mod variables;
pub(crate) mod closures;
pub(crate) mod calls;
pub(crate) mod collections;
pub(crate) mod arithmetic;
pub(crate) mod control;
pub(crate) mod with_scope;
pub(crate) mod builtins_misc;
+42
View File
@@ -0,0 +1,42 @@
use crate::{BytecodeReader, Mutation, StepResult, Value};
impl<'gc> crate::Vm<'gc> {
#[inline(always)]
pub(crate) fn op_load_local(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> {
let idx = reader.read_u32() as usize;
self.push_stack(self.env.borrow().locals[idx]);
StepResult::Continue
}
#[inline(always)]
pub(crate) fn op_load_outer(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> {
let layer = reader.read_u8();
let idx = reader.read_u32() as usize;
let mut cur = self.env;
for _ in 0..layer {
let prev = cur.borrow().prev.expect("LoadOuter: env chain too short");
cur = prev;
}
let val = cur.borrow().locals[idx];
self.push_stack(val);
StepResult::Continue
}
#[inline(always)]
pub(crate) fn op_store_local(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> StepResult<'gc> {
let idx = reader.read_u32() as usize;
let val = self.pop_stack();
self.env.borrow_mut(mc).locals[idx] = val;
StepResult::Continue
}
#[inline(always)]
pub(crate) fn op_alloc_locals(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> StepResult<'gc> {
let count = reader.read_u32() as usize;
self.env
.borrow_mut(mc)
.locals
.extend(std::iter::repeat_n(Value::default(), count));
StepResult::Continue
}
}
+86
View File
@@ -0,0 +1,86 @@
use fix_error::Error;
use fix_common::Symbol;
use crate::{BytecodeReader, CallFrame, StepResult, WithEnv};
use gc_arena::Gc;
impl<'gc> crate::Vm<'gc> {
#[inline(always)]
pub(crate) fn op_push_with(
&mut self,
ctx: &mut impl crate::VmContext,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> StepResult<'gc> {
let env = reader.read_operand_data(ctx).resolve(mc, self);
let scope = Gc::new(
mc,
WithEnv {
env,
prev: self.with_env,
},
);
self.with_env = Some(scope);
StepResult::Continue
}
#[inline(always)]
pub(crate) fn op_pop_with(&mut self) -> StepResult<'gc> {
let Some(scope) = self.with_env else {
unreachable!("no with_scope to pop");
};
self.with_env = scope.prev;
StepResult::Continue
}
#[inline(always)]
pub(crate) fn op_prepare_with(&mut self) -> StepResult<'gc> {
self.call_stack.push(CallFrame {
pc: usize::MAX,
stack_depth: 0,
thunk: None,
env: self.env,
with_env: self.with_env,
});
StepResult::Continue
}
#[inline(always)]
pub(crate) fn op_lookup_with(
&mut self,
ctx: &mut impl crate::VmContext,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> StepResult<'gc> {
let name = reader.read_string_id();
let Some(&WithEnv { env, prev }) = self.with_env.as_deref() else {
let Some(CallFrame { with_env, .. }) = self.call_stack.pop() else {
unreachable!()
};
self.with_env = with_env;
return StepResult::Done(Err(Error::eval_error(format!(
"undefined variable '{}'",
Symbol::from(ctx.resolve_string(name))
))));
};
self.push_stack(env);
if let Some(step) = self.try_force_resolved(0, reader.inst_start_pc(), mc) {
return step;
}
let env = self.pop_stack().as_gc::<crate::AttrSet>().unwrap();
let Some(val) = env.lookup(name) else {
reader.set_pc(reader.inst_start_pc());
self.with_env = prev;
return StepResult::Continue;
};
self.push_stack(val);
let Some(CallFrame { with_env, .. }) = self.call_stack.pop() else {
unreachable!()
};
self.with_env = with_env;
StepResult::Continue
}
}
+217 -892
View File
File diff suppressed because it is too large Load Diff