implement __seq and __deepSeq
This commit is contained in:
@@ -146,7 +146,11 @@ pub enum PrimOpPhase {
|
||||
ConcatMap,
|
||||
ConcatStringsSep,
|
||||
ConvertHash,
|
||||
|
||||
DeepSeq,
|
||||
DeepSeqPush,
|
||||
DeepSeqLoop,
|
||||
|
||||
Derivation,
|
||||
DerivationStrict,
|
||||
DirOf,
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
use fix_builtins::PrimOpPhase;
|
||||
use fix_error::Error;
|
||||
use gc_arena::Mutation;
|
||||
use gc_arena::{Gc, Mutation};
|
||||
use num_enum::TryFromPrimitive as _;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::value::*;
|
||||
use crate::{BytecodeReader, CallFrame, Step, Vm, VmRuntimeCtx};
|
||||
use crate::{
|
||||
BytecodeReader, CallFrame, Step, Vm, VmRuntimeCtx, VmRuntimeCtxExt,
|
||||
};
|
||||
|
||||
impl<'gc> Vm<'gc> {
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub(crate) fn op_dispatch_primop(
|
||||
&mut self,
|
||||
_ctx: &mut impl VmRuntimeCtx,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
@@ -19,9 +23,17 @@ impl<'gc> Vm<'gc> {
|
||||
return self.finish_err(Error::eval_error("invalid primop phase"));
|
||||
};
|
||||
match phase {
|
||||
Abort => self.primop_abort(ctx, reader, mc),
|
||||
|
||||
DeepSeq => self.primop_deep_seq_force_top(reader, mc),
|
||||
DeepSeqPush => self.primop_deep_seq_push(reader, mc),
|
||||
DeepSeqLoop => self.primop_deep_seq_loop(reader, mc),
|
||||
Seq => self.primop_seq(reader, mc),
|
||||
|
||||
FilterForceList => self.primop_filter_force_list(reader, mc),
|
||||
FilterCallPred => self.primop_filter_call_pred(reader, mc),
|
||||
FilterCheck => self.primop_filter_check(reader, mc),
|
||||
|
||||
_ => todo!("dispatch builtin phase"),
|
||||
}
|
||||
}
|
||||
@@ -108,4 +120,196 @@ impl<'gc> Vm<'gc> {
|
||||
reader.set_pc(PrimOpPhase::FilterCallPred.ip() as usize);
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
pub(crate) fn primop_seq(
|
||||
&mut self,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
// stack: [e1, e2] - force e1, return e2
|
||||
self.force_slot(1, reader, mc)?;
|
||||
let e2 = self.pop();
|
||||
let _ = self.pop();
|
||||
self.push(e2);
|
||||
self.return_from_primop(reader)
|
||||
}
|
||||
|
||||
pub(crate) fn primop_abort(
|
||||
&mut self,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
// stack: [msg] - force msg, then abort with it
|
||||
self.force_slot(0, reader, mc)?;
|
||||
let msg_val = self.peek_forced(0);
|
||||
let msg = ctx
|
||||
.get_string(msg_val)
|
||||
.unwrap_or("<non-string-value>");
|
||||
self.finish_err(Error::eval_error(format!(
|
||||
"evaluation aborted with the following error message: '{msg}'"
|
||||
)))
|
||||
}
|
||||
|
||||
pub(crate) fn primop_deep_seq_force_top(
|
||||
&mut self,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
// stack: [e1, e2] - force e1, return e2
|
||||
self.force_slot(1, reader, mc)?;
|
||||
|
||||
let e1 = self.peek_forced(1);
|
||||
|
||||
let children: SmallVec<_> = if let Some(attrs) = e1.as_gc::<AttrSet>() {
|
||||
if attrs.is_empty() {
|
||||
SmallVec::new()
|
||||
} else {
|
||||
attrs.iter().map(|&(_, v)| v).collect()
|
||||
}
|
||||
} else if let Some(list) = e1.as_gc::<List<'gc>>() {
|
||||
let inner = list.inner.borrow();
|
||||
if inner.is_empty() {
|
||||
SmallVec::new()
|
||||
} else {
|
||||
inner.iter().copied().collect()
|
||||
}
|
||||
} else {
|
||||
SmallVec::new()
|
||||
};
|
||||
|
||||
if children.is_empty() {
|
||||
let e2 = self.pop();
|
||||
let _ = self.pop();
|
||||
self.push(e2);
|
||||
return self.return_from_primop(reader);
|
||||
}
|
||||
|
||||
let count = children.len() as i32;
|
||||
let seen: Gc<'gc, List<'gc>> = Gc::new(mc, List::default());
|
||||
let worklist: Gc<'gc, List<'gc>> = List::new(mc, children);
|
||||
|
||||
let e2 = self.pop();
|
||||
let _ = self.pop();
|
||||
self.push(e2);
|
||||
self.push(Value::new_gc(seen));
|
||||
self.push(Value::new_gc(worklist));
|
||||
self.push(Value::new_inline(count));
|
||||
reader.set_pc(PrimOpPhase::DeepSeqPush.ip() as usize);
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
pub(crate) fn primop_deep_seq_push(
|
||||
&mut self,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
// stack: [e2, seen, worklist, counter]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let counter = self.peek(0).as_inline::<i32>().unwrap();
|
||||
if counter == 0 {
|
||||
let _ = self.pop(); // counter
|
||||
let _ = self.pop(); // worklist
|
||||
let _ = self.pop(); // seen
|
||||
return self.return_from_primop(reader);
|
||||
}
|
||||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let worklist = self.peek_forced(1).as_gc::<List<'gc>>().unwrap();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let item = worklist.unlock(mc).borrow_mut().pop().unwrap();
|
||||
self.replace(0, Value::new_inline(counter - 1));
|
||||
self.push(item);
|
||||
|
||||
// force item at TOS, resume at DeepSeqLoop after force
|
||||
self.force_slot_to_pc(
|
||||
0,
|
||||
reader,
|
||||
mc,
|
||||
PrimOpPhase::DeepSeqLoop.ip() as usize,
|
||||
)?;
|
||||
reader.set_pc(PrimOpPhase::DeepSeqLoop.ip() as usize);
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
pub(crate) fn primop_deep_seq_loop(
|
||||
&mut self,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
// stack after pop: [e2, seen, worklist, counter]
|
||||
let item = self.pop();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let counter = self.peek(0).as_inline::<i32>().unwrap();
|
||||
|
||||
let mut added: usize = 0;
|
||||
if let Some(attrs) = item.as_gc::<AttrSet>() {
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let seen = self.peek_forced(2).as_gc::<List<'gc>>().unwrap();
|
||||
if !self.is_value_in_seen(seen, item) {
|
||||
self.add_value_to_seen(seen, mc, item);
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let worklist = self.peek_forced(1).as_gc::<List<'gc>>().unwrap();
|
||||
{
|
||||
let mut wl = worklist.unlock(mc).borrow_mut();
|
||||
for &(_, v) in attrs.iter() {
|
||||
wl.push(v);
|
||||
}
|
||||
added = attrs.len();
|
||||
}
|
||||
}
|
||||
} else if let Some(list) = item.as_gc::<List<'gc>>() {
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let seen = self.peek_forced(2).as_gc::<List<'gc>>().unwrap();
|
||||
if !self.is_value_in_seen(seen, item) {
|
||||
self.add_value_to_seen(seen, mc, item);
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let worklist = self.peek_forced(1).as_gc::<List<'gc>>().unwrap();
|
||||
{
|
||||
let inner = list.inner.borrow();
|
||||
let mut wl = worklist.unlock(mc).borrow_mut();
|
||||
for &v in inner.iter() {
|
||||
wl.push(v);
|
||||
}
|
||||
added = inner.len();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.replace(0, Value::new_inline(counter + added as i32));
|
||||
reader.set_pc(PrimOpPhase::DeepSeqPush.ip() as usize);
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
fn is_value_in_seen(
|
||||
&self,
|
||||
seen: Gc<'gc, List<'gc>>,
|
||||
val: Value<'gc>,
|
||||
) -> bool {
|
||||
if !is_container(val) {
|
||||
return false;
|
||||
}
|
||||
let target = val.to_bits();
|
||||
for &v in seen.inner.borrow().iter() {
|
||||
if v.to_bits() == target {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn add_value_to_seen(
|
||||
&self,
|
||||
seen: Gc<'gc, List<'gc>>,
|
||||
mc: &Mutation<'gc>,
|
||||
val: Value<'gc>,
|
||||
) {
|
||||
if is_container(val) {
|
||||
seen.unlock(mc).borrow_mut().push(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_container(val: Value<'_>) -> bool {
|
||||
val.is::<AttrSet>() || val.is::<List<'_>>()
|
||||
}
|
||||
|
||||
@@ -248,6 +248,11 @@ impl<'gc> Value<'gc> {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn to_bits(self) -> u64 {
|
||||
self.raw.to_bits()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_num(self) -> Option<NixNum> {
|
||||
if let Some(i) = self.as_inline::<i32>() {
|
||||
@@ -500,6 +505,12 @@ pub(crate) struct List<'gc> {
|
||||
}
|
||||
|
||||
impl<'gc> List<'gc> {
|
||||
pub(crate) fn new(mc: &Mutation<'gc>, data: SmallVec<[Value<'gc>; 4]>) -> Gc<'gc, Self> {
|
||||
Gc::new(mc, Self {
|
||||
inner: RefLock::new(data)
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn new_gc(mc: &Mutation<'gc>) -> Gc<'gc, Self> {
|
||||
Gc::new(mc, Self::default())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user