Compare commits
6 Commits
new
...
66d1f30b5a
| Author | SHA1 | Date | |
|---|---|---|---|
|
66d1f30b5a
|
|||
|
260bea9ff1
|
|||
|
c85423ae79
|
|||
|
bc16596dd3
|
|||
|
ac76d4a9e4
|
|||
|
d77dcc8929
|
Generated
+1
@@ -449,6 +449,7 @@ dependencies = [
|
|||||||
"clap",
|
"clap",
|
||||||
"criterion",
|
"criterion",
|
||||||
"ere",
|
"ere",
|
||||||
|
"fix-builtins",
|
||||||
"fix-codegen",
|
"fix-codegen",
|
||||||
"fix-common",
|
"fix-common",
|
||||||
"fix-error",
|
"fix-error",
|
||||||
|
|||||||
+242
-1
@@ -88,7 +88,6 @@ define_builtins! {
|
|||||||
("__mapAttrs", MapAttrs, 2),
|
("__mapAttrs", MapAttrs, 2),
|
||||||
("__match", Match, 2),
|
("__match", Match, 2),
|
||||||
("__mul", Mul, 2),
|
("__mul", Mul, 2),
|
||||||
("null", Null, 0), // constant, not a function
|
|
||||||
("__parseDrvName", ParseDrvName, 1),
|
("__parseDrvName", ParseDrvName, 1),
|
||||||
("__partition", Partition, 2),
|
("__partition", Partition, 2),
|
||||||
("__path", Path, 1),
|
("__path", Path, 1),
|
||||||
@@ -123,3 +122,245 @@ define_builtins! {
|
|||||||
("__warn", Warn, 2),
|
("__warn", Warn, 2),
|
||||||
("__zipAttrsWith", ZipAttrsWith, 2),
|
("__zipAttrsWith", ZipAttrsWith, 2),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, TryFromPrimitive)]
|
||||||
|
pub enum PrimOpPhase {
|
||||||
|
Abort,
|
||||||
|
Add,
|
||||||
|
AddErrorContext,
|
||||||
|
All,
|
||||||
|
Any,
|
||||||
|
AppendContext,
|
||||||
|
AttrNames,
|
||||||
|
AttrValues,
|
||||||
|
BaseNameOf,
|
||||||
|
BitAnd,
|
||||||
|
BitOr,
|
||||||
|
BitXor,
|
||||||
|
Break,
|
||||||
|
CatAttrs,
|
||||||
|
Ceil,
|
||||||
|
CompareVersions,
|
||||||
|
ConcatLists,
|
||||||
|
ConcatMap,
|
||||||
|
ConcatStringsSep,
|
||||||
|
ConvertHash,
|
||||||
|
|
||||||
|
DeepSeq,
|
||||||
|
DeepSeqPush,
|
||||||
|
DeepSeqLoop,
|
||||||
|
|
||||||
|
Derivation,
|
||||||
|
DerivationStrict,
|
||||||
|
DirOf,
|
||||||
|
Div,
|
||||||
|
Elem,
|
||||||
|
ElemAt,
|
||||||
|
FetchGit,
|
||||||
|
FetchMercurial,
|
||||||
|
FetchTarball,
|
||||||
|
FetchTree,
|
||||||
|
FetchUrl,
|
||||||
|
|
||||||
|
FilterForceList,
|
||||||
|
FilterCallPred,
|
||||||
|
FilterCheck,
|
||||||
|
|
||||||
|
FilterSource,
|
||||||
|
FindFile,
|
||||||
|
Floor,
|
||||||
|
FoldlStrict,
|
||||||
|
FromJSON,
|
||||||
|
FromTOML,
|
||||||
|
FunctionArgs,
|
||||||
|
GenList,
|
||||||
|
GenericClosure,
|
||||||
|
GetAttr,
|
||||||
|
GetContext,
|
||||||
|
GetEnv,
|
||||||
|
GroupBy,
|
||||||
|
HasAttr,
|
||||||
|
HasContext,
|
||||||
|
HashFile,
|
||||||
|
HashString,
|
||||||
|
Head,
|
||||||
|
Import,
|
||||||
|
IntersectAttrs,
|
||||||
|
IsAttrs,
|
||||||
|
IsBool,
|
||||||
|
IsFloat,
|
||||||
|
IsFunction,
|
||||||
|
IsInt,
|
||||||
|
IsList,
|
||||||
|
IsNull,
|
||||||
|
IsPath,
|
||||||
|
IsString,
|
||||||
|
Length,
|
||||||
|
LessThan,
|
||||||
|
ListToAttrs,
|
||||||
|
Map,
|
||||||
|
MapAttrs,
|
||||||
|
Match,
|
||||||
|
Mul,
|
||||||
|
ParseDrvName,
|
||||||
|
Partition,
|
||||||
|
Path,
|
||||||
|
PathExists,
|
||||||
|
Placeholder,
|
||||||
|
ReadDir,
|
||||||
|
ReadFile,
|
||||||
|
ReadFileType,
|
||||||
|
RemoveAttrs,
|
||||||
|
ReplaceStrings,
|
||||||
|
ScopedImport,
|
||||||
|
Seq,
|
||||||
|
Sort,
|
||||||
|
Split,
|
||||||
|
SplitVersion,
|
||||||
|
StorePath,
|
||||||
|
StringLength,
|
||||||
|
Sub,
|
||||||
|
Substring,
|
||||||
|
Tail,
|
||||||
|
Throw,
|
||||||
|
ToFile,
|
||||||
|
ToJSON,
|
||||||
|
ToPath,
|
||||||
|
ToString,
|
||||||
|
ToXML,
|
||||||
|
Trace,
|
||||||
|
TryEval,
|
||||||
|
TypeOf,
|
||||||
|
UnsafeDiscardStringContext,
|
||||||
|
UnsafeGetAttrPos,
|
||||||
|
Warn,
|
||||||
|
ZipAttrsWith,
|
||||||
|
|
||||||
|
ForceResultShallow,
|
||||||
|
ForceResultShallowPush,
|
||||||
|
ForceResultShallowLoop,
|
||||||
|
ForceResultDeepFinish,
|
||||||
|
|
||||||
|
// TODO: split into separate enums
|
||||||
|
CallPattern,
|
||||||
|
|
||||||
|
Illegal,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BuiltinId {
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn entry_phase(self) -> PrimOpPhase {
|
||||||
|
use BuiltinId::*;
|
||||||
|
match self {
|
||||||
|
Abort => PrimOpPhase::Abort,
|
||||||
|
Add => PrimOpPhase::Add,
|
||||||
|
AddErrorContext => PrimOpPhase::AddErrorContext,
|
||||||
|
All => PrimOpPhase::All,
|
||||||
|
Any => PrimOpPhase::Any,
|
||||||
|
AppendContext => PrimOpPhase::AppendContext,
|
||||||
|
AttrNames => PrimOpPhase::AttrNames,
|
||||||
|
AttrValues => PrimOpPhase::AttrValues,
|
||||||
|
BaseNameOf => PrimOpPhase::BaseNameOf,
|
||||||
|
BitAnd => PrimOpPhase::BitAnd,
|
||||||
|
BitOr => PrimOpPhase::BitOr,
|
||||||
|
BitXor => PrimOpPhase::BitXor,
|
||||||
|
Break => PrimOpPhase::Break,
|
||||||
|
CatAttrs => PrimOpPhase::CatAttrs,
|
||||||
|
Ceil => PrimOpPhase::Ceil,
|
||||||
|
CompareVersions => PrimOpPhase::CompareVersions,
|
||||||
|
ConcatLists => PrimOpPhase::ConcatLists,
|
||||||
|
ConcatMap => PrimOpPhase::ConcatMap,
|
||||||
|
ConcatStringsSep => PrimOpPhase::ConcatStringsSep,
|
||||||
|
ConvertHash => PrimOpPhase::ConvertHash,
|
||||||
|
DeepSeq => PrimOpPhase::DeepSeq,
|
||||||
|
Derivation => PrimOpPhase::Derivation,
|
||||||
|
DerivationStrict => PrimOpPhase::DerivationStrict,
|
||||||
|
DirOf => PrimOpPhase::DirOf,
|
||||||
|
Div => PrimOpPhase::Div,
|
||||||
|
Elem => PrimOpPhase::Elem,
|
||||||
|
ElemAt => PrimOpPhase::ElemAt,
|
||||||
|
FetchGit => PrimOpPhase::FetchGit,
|
||||||
|
FetchMercurial => PrimOpPhase::FetchMercurial,
|
||||||
|
FetchTarball => PrimOpPhase::FetchTarball,
|
||||||
|
FetchTree => PrimOpPhase::FetchTree,
|
||||||
|
FetchUrl => PrimOpPhase::FetchUrl,
|
||||||
|
Filter => PrimOpPhase::FilterForceList,
|
||||||
|
FilterSource => PrimOpPhase::FilterSource,
|
||||||
|
FindFile => PrimOpPhase::FindFile,
|
||||||
|
Floor => PrimOpPhase::Floor,
|
||||||
|
FoldlStrict => PrimOpPhase::FoldlStrict,
|
||||||
|
FromJSON => PrimOpPhase::FromJSON,
|
||||||
|
FromTOML => PrimOpPhase::FromTOML,
|
||||||
|
FunctionArgs => PrimOpPhase::FunctionArgs,
|
||||||
|
GenList => PrimOpPhase::GenList,
|
||||||
|
GenericClosure => PrimOpPhase::GenericClosure,
|
||||||
|
GetAttr => PrimOpPhase::GetAttr,
|
||||||
|
GetContext => PrimOpPhase::GetContext,
|
||||||
|
GetEnv => PrimOpPhase::GetEnv,
|
||||||
|
GroupBy => PrimOpPhase::GroupBy,
|
||||||
|
HasAttr => PrimOpPhase::HasAttr,
|
||||||
|
HasContext => PrimOpPhase::HasContext,
|
||||||
|
HashFile => PrimOpPhase::HashFile,
|
||||||
|
HashString => PrimOpPhase::HashString,
|
||||||
|
Head => PrimOpPhase::Head,
|
||||||
|
Import => PrimOpPhase::Import,
|
||||||
|
IntersectAttrs => PrimOpPhase::IntersectAttrs,
|
||||||
|
IsAttrs => PrimOpPhase::IsAttrs,
|
||||||
|
IsBool => PrimOpPhase::IsBool,
|
||||||
|
IsFloat => PrimOpPhase::IsFloat,
|
||||||
|
IsFunction => PrimOpPhase::IsFunction,
|
||||||
|
IsInt => PrimOpPhase::IsInt,
|
||||||
|
IsList => PrimOpPhase::IsList,
|
||||||
|
IsNull => PrimOpPhase::IsNull,
|
||||||
|
IsPath => PrimOpPhase::IsPath,
|
||||||
|
IsString => PrimOpPhase::IsString,
|
||||||
|
Length => PrimOpPhase::Length,
|
||||||
|
LessThan => PrimOpPhase::LessThan,
|
||||||
|
ListToAttrs => PrimOpPhase::ListToAttrs,
|
||||||
|
Map => PrimOpPhase::Map,
|
||||||
|
MapAttrs => PrimOpPhase::MapAttrs,
|
||||||
|
Match => PrimOpPhase::Match,
|
||||||
|
Mul => PrimOpPhase::Mul,
|
||||||
|
ParseDrvName => PrimOpPhase::ParseDrvName,
|
||||||
|
Partition => PrimOpPhase::Partition,
|
||||||
|
Path => PrimOpPhase::Path,
|
||||||
|
PathExists => PrimOpPhase::PathExists,
|
||||||
|
Placeholder => PrimOpPhase::Placeholder,
|
||||||
|
ReadDir => PrimOpPhase::ReadDir,
|
||||||
|
ReadFile => PrimOpPhase::ReadFile,
|
||||||
|
ReadFileType => PrimOpPhase::ReadFileType,
|
||||||
|
RemoveAttrs => PrimOpPhase::RemoveAttrs,
|
||||||
|
ReplaceStrings => PrimOpPhase::ReplaceStrings,
|
||||||
|
ScopedImport => PrimOpPhase::ScopedImport,
|
||||||
|
Seq => PrimOpPhase::Seq,
|
||||||
|
Sort => PrimOpPhase::Sort,
|
||||||
|
Split => PrimOpPhase::Split,
|
||||||
|
SplitVersion => PrimOpPhase::SplitVersion,
|
||||||
|
StorePath => PrimOpPhase::StorePath,
|
||||||
|
StringLength => PrimOpPhase::StringLength,
|
||||||
|
Sub => PrimOpPhase::Sub,
|
||||||
|
Substring => PrimOpPhase::Substring,
|
||||||
|
Tail => PrimOpPhase::Tail,
|
||||||
|
Throw => PrimOpPhase::Throw,
|
||||||
|
ToFile => PrimOpPhase::ToFile,
|
||||||
|
ToJSON => PrimOpPhase::ToJSON,
|
||||||
|
ToPath => PrimOpPhase::ToPath,
|
||||||
|
ToString => PrimOpPhase::ToString,
|
||||||
|
ToXML => PrimOpPhase::ToXML,
|
||||||
|
Trace => PrimOpPhase::Trace,
|
||||||
|
TryEval => PrimOpPhase::TryEval,
|
||||||
|
TypeOf => PrimOpPhase::TypeOf,
|
||||||
|
UnsafeDiscardStringContext => PrimOpPhase::UnsafeDiscardStringContext,
|
||||||
|
UnsafeGetAttrPos => PrimOpPhase::UnsafeGetAttrPos,
|
||||||
|
Warn => PrimOpPhase::Warn,
|
||||||
|
ZipAttrsWith => PrimOpPhase::ZipAttrsWith,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrimOpPhase {
|
||||||
|
pub fn ip(self) -> u32 {
|
||||||
|
self as u32 * 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -281,6 +281,9 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Op::Call => ("Call", String::new()),
|
Op::Call => ("Call", String::new()),
|
||||||
|
Op::DispatchPrimOp => {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
|
||||||
Op::MakeAttrs => {
|
Op::MakeAttrs => {
|
||||||
let count = self.read_u32();
|
let count = self.read_u32();
|
||||||
|
|||||||
+12
-10
@@ -42,6 +42,7 @@ pub enum Op {
|
|||||||
MakePatternClosure,
|
MakePatternClosure,
|
||||||
|
|
||||||
Call,
|
Call,
|
||||||
|
DispatchPrimOp,
|
||||||
|
|
||||||
MakeAttrs,
|
MakeAttrs,
|
||||||
MakeEmptyAttrs,
|
MakeEmptyAttrs,
|
||||||
@@ -125,7 +126,11 @@ pub enum Const {
|
|||||||
Float(f64),
|
Float(f64),
|
||||||
Bool(bool),
|
Bool(bool),
|
||||||
String(StringId),
|
String(StringId),
|
||||||
PrimOp { id: BuiltinId, arity: u8 },
|
PrimOp {
|
||||||
|
id: BuiltinId,
|
||||||
|
arity: u8,
|
||||||
|
dispatch_ip: u32,
|
||||||
|
},
|
||||||
Null,
|
Null,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +164,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn inline_maybe_thunk(&mut self, val: MaybeThunk) -> InlineOperand {
|
fn inline_maybe_thunk(&self, val: MaybeThunk) -> InlineOperand {
|
||||||
use MaybeThunk::*;
|
use MaybeThunk::*;
|
||||||
match val {
|
match val {
|
||||||
Int(x) => {
|
Int(x) => {
|
||||||
@@ -807,14 +812,11 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
let span_id = self.ctx.register_span(span);
|
let span_id = self.ctx.register_span(span);
|
||||||
self.emit_u32(span_id);
|
self.emit_u32(span_id);
|
||||||
}
|
}
|
||||||
for &(_key, _val, _span) in dyns.iter() {
|
for &(_key, val, span) in dyns.iter() {
|
||||||
todo!("redesign dynamic attr key");
|
self.emit_u8(AttrKeyType::Dynamic as u8);
|
||||||
// self.emit_u8(AttrKeyType::Dynamic as u8);
|
self.emit_maybe_thunk(val);
|
||||||
// self.emit_maybe_thunk(key);
|
let span_id = self.ctx.register_span(span);
|
||||||
// let val_operand = self.inline_maybe_thunk(val);
|
self.emit_u32(span_id);
|
||||||
// self.emit_maybe_thunk(val);
|
|
||||||
// let span_id = self.ctx.register_span(span);
|
|
||||||
// self.emit_u32(span_id);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -272,9 +272,9 @@ pub enum Value {
|
|||||||
/// A function (lambda).
|
/// A function (lambda).
|
||||||
Func,
|
Func,
|
||||||
/// A primitive (built-in) operation.
|
/// A primitive (built-in) operation.
|
||||||
PrimOp(String),
|
PrimOp(&'static str),
|
||||||
/// A partially applied primitive operation.
|
/// A partially applied primitive operation.
|
||||||
PrimOpApp(String),
|
PrimOpApp(&'static str),
|
||||||
/// A marker for a value that has been seen before during serialization, to break cycles.
|
/// A marker for a value that has been seen before during serialization, to break cycles.
|
||||||
/// This is used to prevent infinite recursion when printing or serializing cyclic data structures.
|
/// This is used to prevent infinite recursion when printing or serializing cyclic data structures.
|
||||||
Repeated,
|
Repeated,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use fix_common::StringId;
|
|||||||
use num_enum::TryFromPrimitive;
|
use num_enum::TryFromPrimitive;
|
||||||
use string_interner::Symbol as _;
|
use string_interner::Symbol as _;
|
||||||
|
|
||||||
use crate::{OperandData, VmRuntimeCtx};
|
use crate::{AttrKeyData, OperandData, VmRuntimeCtx};
|
||||||
|
|
||||||
pub(crate) struct BytecodeReader<'a> {
|
pub(crate) struct BytecodeReader<'a> {
|
||||||
bytecode: &'a [u8],
|
bytecode: &'a [u8],
|
||||||
@@ -117,14 +117,14 @@ impl<'a> BytecodeReader<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn read_attr_key_data<C: VmRuntimeCtx>(&mut self, ctx: &C) -> crate::AttrKeyData {
|
pub(crate) fn read_attr_key_data(&mut self) -> crate::AttrKeyData {
|
||||||
use fix_codegen::AttrKeyType;
|
use fix_codegen::AttrKeyType;
|
||||||
let tag = self.read_u8();
|
let tag = self.read_u8();
|
||||||
let ty = AttrKeyType::try_from_primitive(tag)
|
let ty = AttrKeyType::try_from_primitive(tag)
|
||||||
.unwrap_or_else(|err| panic!("unknown key tag: {:#04x}", err.number));
|
.unwrap_or_else(|err| panic!("unknown key tag: {:#04x}", err.number));
|
||||||
match ty {
|
match ty {
|
||||||
AttrKeyType::Static => crate::AttrKeyData::Static(self.read_string_id()),
|
AttrKeyType::Static => AttrKeyData::Static(self.read_string_id()),
|
||||||
AttrKeyType::Dynamic => crate::AttrKeyData::Dynamic(self.read_operand_data(ctx)),
|
AttrKeyType::Dynamic => AttrKeyData::Dynamic,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,8 +139,4 @@ impl<'a> BytecodeReader<'a> {
|
|||||||
pub(crate) fn inst_start_pc(&self) -> usize {
|
pub(crate) fn inst_start_pc(&self) -> usize {
|
||||||
self.inst_start_pc
|
self.inst_start_pc
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn bytecode(&self) -> &'a [u8] {
|
|
||||||
self.bytecode
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,6 +142,7 @@ tail_fn!(op_make_closure, (reader, mc));
|
|||||||
tail_fn!(op_make_pattern_closure, (reader, mc));
|
tail_fn!(op_make_pattern_closure, (reader, mc));
|
||||||
|
|
||||||
tail_fn!(op_call, (ctx, reader, mc));
|
tail_fn!(op_call, (ctx, reader, mc));
|
||||||
|
tail_fn!(op_dispatch_primop, (ctx, reader, mc));
|
||||||
tail_fn!(op_return, (ctx, reader, mc));
|
tail_fn!(op_return, (ctx, reader, mc));
|
||||||
|
|
||||||
tail_fn!(op_make_attrs, (ctx, reader, mc));
|
tail_fn!(op_make_attrs, (ctx, reader, mc));
|
||||||
@@ -172,8 +173,8 @@ tail_fn!(op_geq, (ctx, reader, mc));
|
|||||||
tail_fn!(op_concat, (reader, mc));
|
tail_fn!(op_concat, (reader, mc));
|
||||||
tail_fn!(op_update, (reader, mc));
|
tail_fn!(op_update, (reader, mc));
|
||||||
|
|
||||||
tail_fn!(op_neg, ());
|
tail_fn!(op_neg, (reader, mc));
|
||||||
tail_fn!(op_not, ());
|
tail_fn!(op_not, (reader, mc));
|
||||||
|
|
||||||
tail_fn!(op_jump_if_false, (reader, mc));
|
tail_fn!(op_jump_if_false, (reader, mc));
|
||||||
tail_fn!(op_jump_if_true, (reader, mc));
|
tail_fn!(op_jump_if_true, (reader, mc));
|
||||||
@@ -234,6 +235,7 @@ table! {
|
|||||||
MakePatternClosure => op_make_pattern_closure,
|
MakePatternClosure => op_make_pattern_closure,
|
||||||
|
|
||||||
Call => op_call,
|
Call => op_call,
|
||||||
|
DispatchPrimOp => op_dispatch_primop,
|
||||||
Return => op_return,
|
Return => op_return,
|
||||||
|
|
||||||
MakeAttrs => op_make_attrs,
|
MakeAttrs => op_make_attrs,
|
||||||
|
|||||||
+29
-14
@@ -16,6 +16,7 @@ pub(crate) trait Forced<'gc>: Sized {
|
|||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
base_depth: usize,
|
base_depth: usize,
|
||||||
|
resume_pc: usize,
|
||||||
) -> Step;
|
) -> Step;
|
||||||
|
|
||||||
/// After `force_and_check` returned `Continue`, pop `WIDTH` slots
|
/// After `force_and_check` returned `Continue`, pop `WIDTH` slots
|
||||||
@@ -33,8 +34,9 @@ impl<'gc> Forced<'gc> for StrictValue<'gc> {
|
|||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
base_depth: usize,
|
base_depth: usize,
|
||||||
|
resume_pc: usize,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
vm.force_slot(base_depth, reader, mc)
|
vm.force_slot_to_pc(base_depth, reader, mc, resume_pc)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@@ -55,8 +57,9 @@ macro_rules! impl_forced_inline {
|
|||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
base_depth: usize,
|
base_depth: usize,
|
||||||
|
resume_pc: usize,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
vm.force_slot(base_depth, reader, mc)?;
|
vm.force_slot_to_pc(base_depth, reader, mc, resume_pc)?;
|
||||||
let v = vm.peek_forced(base_depth);
|
let v = vm.peek_forced(base_depth);
|
||||||
if v.as_inline::<$ty>().is_none() {
|
if v.as_inline::<$ty>().is_none() {
|
||||||
let _: Step = vm.finish_type_err($nix_ty, v.ty());
|
let _: Step = vm.finish_type_err($nix_ty, v.ty());
|
||||||
@@ -88,8 +91,9 @@ macro_rules! impl_forced_gc {
|
|||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
base_depth: usize,
|
base_depth: usize,
|
||||||
|
resume_pc: usize,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
vm.force_slot(base_depth, reader, mc)?;
|
vm.force_slot_to_pc(base_depth, reader, mc, resume_pc)?;
|
||||||
let v = vm.peek_forced(base_depth);
|
let v = vm.peek_forced(base_depth);
|
||||||
if v.as_gc::<$ty>().is_none() {
|
if v.as_gc::<$ty>().is_none() {
|
||||||
let _: Step = vm.finish_type_err($nix_ty, v.ty());
|
let _: Step = vm.finish_type_err($nix_ty, v.ty());
|
||||||
@@ -135,8 +139,9 @@ impl<'gc> Forced<'gc> for NixNum {
|
|||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
base_depth: usize,
|
base_depth: usize,
|
||||||
|
resume_pc: usize,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
vm.force_slot(base_depth, reader, mc)?;
|
vm.force_slot_to_pc(base_depth, reader, mc, resume_pc)?;
|
||||||
let v = vm.peek_forced(base_depth);
|
let v = vm.peek_forced(base_depth);
|
||||||
if v.as_num().is_none() {
|
if v.as_num().is_none() {
|
||||||
let _: Step = vm.finish_type_err(NixType::Int, v.ty());
|
let _: Step = vm.finish_type_err(NixType::Int, v.ty());
|
||||||
@@ -162,8 +167,9 @@ impl<'gc> Forced<'gc> for f64 {
|
|||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
base_depth: usize,
|
base_depth: usize,
|
||||||
|
resume_pc: usize,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
vm.force_slot(base_depth, reader, mc)?;
|
vm.force_slot_to_pc(base_depth, reader, mc, resume_pc)?;
|
||||||
let v = vm.peek_forced(base_depth);
|
let v = vm.peek_forced(base_depth);
|
||||||
if v.as_float().is_none() {
|
if v.as_float().is_none() {
|
||||||
let _: Step = vm.finish_type_err(NixType::Float, v.ty());
|
let _: Step = vm.finish_type_err(NixType::Float, v.ty());
|
||||||
@@ -189,9 +195,10 @@ impl<'gc, A: Forced<'gc>, B: Forced<'gc>> Forced<'gc> for (A, B) {
|
|||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
base: usize,
|
base: usize,
|
||||||
|
resume_pc: usize,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
A::force_and_check(vm, reader, mc, base + B::WIDTH)?;
|
A::force_and_check(vm, reader, mc, base + B::WIDTH, resume_pc)?;
|
||||||
B::force_and_check(vm, reader, mc, base)
|
B::force_and_check(vm, reader, mc, base, resume_pc)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@@ -211,10 +218,11 @@ impl<'gc, A: Forced<'gc>, B: Forced<'gc>, C: Forced<'gc>> Forced<'gc> for (A, B,
|
|||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
base: usize,
|
base: usize,
|
||||||
|
resume_pc: usize,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
A::force_and_check(vm, reader, mc, base + B::WIDTH + C::WIDTH)?;
|
A::force_and_check(vm, reader, mc, base + B::WIDTH + C::WIDTH, resume_pc)?;
|
||||||
B::force_and_check(vm, reader, mc, base + C::WIDTH)?;
|
B::force_and_check(vm, reader, mc, base + C::WIDTH, resume_pc)?;
|
||||||
C::force_and_check(vm, reader, mc, base)
|
C::force_and_check(vm, reader, mc, base, resume_pc)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@@ -237,11 +245,18 @@ impl<'gc, A: Forced<'gc>, B: Forced<'gc>, C: Forced<'gc>, D: Forced<'gc>> Forced
|
|||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
base: usize,
|
base: usize,
|
||||||
|
resume_pc: usize,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
A::force_and_check(vm, reader, mc, base + B::WIDTH + C::WIDTH + D::WIDTH)?;
|
A::force_and_check(
|
||||||
B::force_and_check(vm, reader, mc, base + C::WIDTH + D::WIDTH)?;
|
vm,
|
||||||
C::force_and_check(vm, reader, mc, base + D::WIDTH)?;
|
reader,
|
||||||
D::force_and_check(vm, reader, mc, base)
|
mc,
|
||||||
|
base + B::WIDTH + C::WIDTH + D::WIDTH,
|
||||||
|
resume_pc,
|
||||||
|
)?;
|
||||||
|
B::force_and_check(vm, reader, mc, base + C::WIDTH + D::WIDTH, resume_pc)?;
|
||||||
|
C::force_and_check(vm, reader, mc, base + D::WIDTH, resume_pc)?;
|
||||||
|
D::force_and_check(vm, reader, mc, base, resume_pc)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
use gc_arena::{Gc, Mutation};
|
use gc_arena::{Gc, Mutation, RefLock};
|
||||||
|
|
||||||
use crate::value::*;
|
use crate::value::*;
|
||||||
use crate::{BytecodeReader, NixNum, Step, VmError, VmRuntimeCtx, VmRuntimeCtxExt as _};
|
use crate::{BytecodeReader, NixNum, Step, VmError, VmRuntimeCtx, VmRuntimeCtxExt as _};
|
||||||
@@ -173,9 +173,14 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
) -> Step {
|
) -> Step {
|
||||||
let (l, r) = self.try_force::<(Gc<List>, Gc<List>)>(reader, mc)?;
|
let (l, r) = self.try_force::<(Gc<List>, Gc<List>)>(reader, mc)?;
|
||||||
let mut items = smallvec::SmallVec::new();
|
let mut items = smallvec::SmallVec::new();
|
||||||
items.extend_from_slice(&l);
|
items.extend_from_slice(&l.inner.borrow());
|
||||||
items.extend_from_slice(&r);
|
items.extend_from_slice(&r.inner.borrow());
|
||||||
self.push(Value::new_gc(Gc::new(mc, crate::List { inner: items })));
|
self.push(Value::new_gc(Gc::new(
|
||||||
|
mc,
|
||||||
|
crate::List {
|
||||||
|
inner: RefLock::new(items),
|
||||||
|
},
|
||||||
|
)));
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,13 +196,20 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn op_neg(&mut self) -> Step {
|
pub(crate) fn op_neg(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> Step {
|
||||||
todo!("implement unary operation");
|
let rhs = self.try_force::<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(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn op_not(&mut self) -> Step {
|
pub(crate) fn op_not(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> Step {
|
||||||
todo!("implement unary operation");
|
let rhs = self.try_force::<bool>(reader, mc)?;
|
||||||
|
self.push(Value::new_inline(!rhs));
|
||||||
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn values_equal(
|
pub(crate) fn values_equal(
|
||||||
@@ -224,10 +236,10 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
return Ok(a == b);
|
return Ok(a == b);
|
||||||
}
|
}
|
||||||
if let (Some(a), Some(b)) = (lhs.as_gc::<crate::List>(), rhs.as_gc::<crate::List>()) {
|
if let (Some(a), Some(b)) = (lhs.as_gc::<crate::List>(), rhs.as_gc::<crate::List>()) {
|
||||||
if a.inner.len() != b.inner.len() {
|
if a.inner.borrow().len() != b.inner.borrow().len() {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
for (x, y) in a.inner.iter().zip(b.inner.iter()) {
|
for (x, y) in a.inner.borrow().iter().zip(b.inner.borrow().iter()) {
|
||||||
let lx = x.restrict().expect("forced");
|
let lx = x.restrict().expect("forced");
|
||||||
let ly = y.restrict().expect("forced");
|
let ly = y.restrict().expect("forced");
|
||||||
if !self.values_equal(ctx, lx, ly)? {
|
if !self.values_equal(ctx, lx, ly)? {
|
||||||
@@ -237,6 +249,8 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
if let (Some(a), Some(b)) = (lhs.as_gc::<crate::AttrSet>(), rhs.as_gc::<crate::AttrSet>()) {
|
if let (Some(a), Some(b)) = (lhs.as_gc::<crate::AttrSet>(), rhs.as_gc::<crate::AttrSet>()) {
|
||||||
|
let a = a.entries.borrow();
|
||||||
|
let b = b.entries.borrow();
|
||||||
if a.len() != b.len() {
|
if a.len() != b.len() {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,447 @@
|
|||||||
|
use fix_builtins::PrimOpPhase;
|
||||||
|
use fix_error::Error;
|
||||||
|
use gc_arena::{Gc, Mutation, RefLock};
|
||||||
|
use num_enum::TryFromPrimitive as _;
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
|
use crate::value::*;
|
||||||
|
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,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
use PrimOpPhase::*;
|
||||||
|
let phase_disc = reader.read_u8();
|
||||||
|
let Ok(phase) = PrimOpPhase::try_from_primitive(phase_disc) else {
|
||||||
|
return self.finish_err(Error::eval_error("invalid primop phase"));
|
||||||
|
};
|
||||||
|
match phase {
|
||||||
|
Abort => self.primop_abort(ctx, reader, mc),
|
||||||
|
|
||||||
|
DeepSeq => self.primop_deep_seq_force_top(reader, mc),
|
||||||
|
DeepSeqPush => self.primop_deep_seq_push(reader, mc),
|
||||||
|
DeepSeqLoop => self.primop_deep_seq_loop(reader, mc),
|
||||||
|
Seq => self.primop_seq(reader, mc),
|
||||||
|
|
||||||
|
FilterForceList => self.primop_filter_force_list(reader, mc),
|
||||||
|
FilterCallPred => self.primop_filter_call_pred(reader, mc),
|
||||||
|
FilterCheck => self.primop_filter_check(reader, mc),
|
||||||
|
|
||||||
|
ForceResultShallow => self.primop_force_result_shallow(ctx, reader, mc),
|
||||||
|
ForceResultShallowPush => self.primop_force_result_shallow_push(ctx, reader, mc),
|
||||||
|
ForceResultShallowLoop => self.primop_force_result_shallow_loop(reader, mc),
|
||||||
|
ForceResultDeepFinish => self.primop_force_result_deep_finish(ctx, reader, mc),
|
||||||
|
|
||||||
|
CallPattern => self.primop_call_pattern(ctx, reader, mc),
|
||||||
|
|
||||||
|
phase => todo!("primop phase {phase:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn return_from_primop(&mut self, reader: &mut BytecodeReader<'_>) -> Step {
|
||||||
|
let Some(CallFrame {
|
||||||
|
pc: ret_pc,
|
||||||
|
stack_depth: _,
|
||||||
|
thunk: _,
|
||||||
|
env,
|
||||||
|
with_env,
|
||||||
|
}) = self.call_stack.pop()
|
||||||
|
else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
reader.set_pc(ret_pc);
|
||||||
|
self.call_depth -= 1;
|
||||||
|
self.env = env;
|
||||||
|
self.with_env = with_env;
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn primop_filter_force_list(
|
||||||
|
&mut self,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
self.force_slot(0, reader, mc)?;
|
||||||
|
let list = match self.peek_forced(0).expect_gc::<List>() {
|
||||||
|
Ok(list) => list,
|
||||||
|
Err(got) => return self.finish_type_err(NixType::List, got),
|
||||||
|
};
|
||||||
|
if list.inner.borrow().is_empty() {
|
||||||
|
return self.return_from_primop(reader);
|
||||||
|
}
|
||||||
|
// prepare stack layout: [ pred list idx acc ]
|
||||||
|
self.push(Value::new_inline(0));
|
||||||
|
self.push(Value::new_gc(List::new_gc(mc)));
|
||||||
|
reader.set_pc(PrimOpPhase::FilterCallPred.ip() as usize);
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn primop_filter_call_pred(
|
||||||
|
&mut self,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
self.force_slot(3, reader, mc)?;
|
||||||
|
let pred = self.peek_forced(3);
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let idx = self.peek(1).as_inline::<i32>().unwrap();
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let elem = self.peek_forced(2).as_gc::<List>().unwrap().inner.borrow()[idx as usize];
|
||||||
|
self.push(pred.relax());
|
||||||
|
self.call(reader, mc, elem, PrimOpPhase::FilterCheck.ip() as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn primop_filter_check(
|
||||||
|
&mut self,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
let ret = self.try_force::<bool>(reader, mc)?;
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let idx = self.peek(1).as_inline::<i32>().unwrap();
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let list = self.peek_forced(2).as_gc::<List>().unwrap();
|
||||||
|
let list = list.inner.borrow();
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let acc = self.peek_forced(0).as_gc::<List>().unwrap();
|
||||||
|
if ret {
|
||||||
|
let mut acc = acc.unlock(mc).borrow_mut();
|
||||||
|
acc.push(list[idx as usize]);
|
||||||
|
}
|
||||||
|
if idx as usize == list.len() - 1 {
|
||||||
|
let acc = self.pop();
|
||||||
|
let _ = self.pop(); // idx
|
||||||
|
let _ = self.pop(); // list
|
||||||
|
let _ = self.pop(); // pred
|
||||||
|
self.push(acc);
|
||||||
|
return self.return_from_primop(reader);
|
||||||
|
}
|
||||||
|
self.replace(1, Value::new_inline(idx + 1));
|
||||||
|
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>() {
|
||||||
|
let attrs = attrs.entries.borrow();
|
||||||
|
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>() {
|
||||||
|
let attrs = attrs.entries.borrow();
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let seen = self.peek_forced(2).as_gc::<List<'gc>>().unwrap();
|
||||||
|
if !self.is_value_in_seen(seen, item) {
|
||||||
|
self.add_value_to_seen(seen, mc, item);
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let worklist = self.peek_forced(1).as_gc::<List<'gc>>().unwrap();
|
||||||
|
{
|
||||||
|
let mut wl = worklist.unlock(mc).borrow_mut();
|
||||||
|
for &(_, v) in attrs.iter() {
|
||||||
|
wl.push(v);
|
||||||
|
}
|
||||||
|
added = attrs.len();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let Some(list) = item.as_gc::<List<'gc>>() {
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let seen = self.peek_forced(2).as_gc::<List<'gc>>().unwrap();
|
||||||
|
if !self.is_value_in_seen(seen, item) {
|
||||||
|
self.add_value_to_seen(seen, mc, item);
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let worklist = self.peek_forced(1).as_gc::<List<'gc>>().unwrap();
|
||||||
|
{
|
||||||
|
let inner = list.inner.borrow();
|
||||||
|
let mut wl = worklist.unlock(mc).borrow_mut();
|
||||||
|
for &v in inner.iter() {
|
||||||
|
wl.push(v);
|
||||||
|
}
|
||||||
|
added = inner.len();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.replace(0, Value::new_inline(counter + added as i32));
|
||||||
|
reader.set_pc(PrimOpPhase::DeepSeqPush.ip() as usize);
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn primop_force_result_shallow(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
self.force_slot(0, reader, mc)?;
|
||||||
|
let val = self.peek_forced(0);
|
||||||
|
|
||||||
|
let (count, has_children) = if let Some(attrs) = val.as_gc::<AttrSet>() {
|
||||||
|
let len = attrs.entries.borrow().len();
|
||||||
|
(len, len > 0)
|
||||||
|
} else if let Some(list) = val.as_gc::<List<'gc>>() {
|
||||||
|
let len = list.inner.borrow().len();
|
||||||
|
(len, len > 0)
|
||||||
|
} else {
|
||||||
|
(0, false)
|
||||||
|
};
|
||||||
|
|
||||||
|
if !has_children {
|
||||||
|
let val = self.pop();
|
||||||
|
return self.finish_ok(ctx.convert_value(val));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.push(Value::new_inline(0i32));
|
||||||
|
self.push(Value::new_inline(count as i32));
|
||||||
|
reader.set_pc(PrimOpPhase::ForceResultShallowPush.ip() as usize);
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn primop_force_result_shallow_push(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let idx = self.peek(1).as_inline::<i32>().unwrap();
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let len = self.peek(0).as_inline::<i32>().unwrap();
|
||||||
|
|
||||||
|
if idx == len {
|
||||||
|
let _ = self.pop(); // len
|
||||||
|
let _ = self.pop(); // idx
|
||||||
|
let val = self.pop();
|
||||||
|
return self.finish_ok(ctx.convert_value(val));
|
||||||
|
}
|
||||||
|
|
||||||
|
let val = self.peek_forced(2);
|
||||||
|
let child = if let Some(attrs) = val.as_gc::<AttrSet>() {
|
||||||
|
attrs.entries.borrow().get(idx as usize).map(|&(_, v)| v)
|
||||||
|
} else if let Some(list) = val.as_gc::<List<'gc>>() {
|
||||||
|
list.inner.borrow().get(idx as usize).copied()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(child) = child {
|
||||||
|
self.replace(1, Value::new_inline(idx + 1));
|
||||||
|
self.push(child);
|
||||||
|
self.force_slot_to_pc(
|
||||||
|
0,
|
||||||
|
reader,
|
||||||
|
mc,
|
||||||
|
PrimOpPhase::ForceResultShallowLoop.ip() as usize,
|
||||||
|
)?;
|
||||||
|
reader.set_pc(PrimOpPhase::ForceResultShallowLoop.ip() as usize);
|
||||||
|
}
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn primop_force_result_shallow_loop(
|
||||||
|
&mut self,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
_mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
let _ = self.pop(); // forced child
|
||||||
|
reader.set_pc(PrimOpPhase::ForceResultShallowPush.ip() as usize);
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn primop_force_result_deep_finish(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
let val = self.try_force::<StrictValue>(reader, mc)?;
|
||||||
|
self.finish_ok(ctx.convert_value(val.relax()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn primop_call_pattern(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
let (func, attrset) = self.try_force::<(Gc<Closure>, Gc<AttrSet>)>(reader, mc)?;
|
||||||
|
|
||||||
|
let Closure {
|
||||||
|
ip,
|
||||||
|
n_locals,
|
||||||
|
env,
|
||||||
|
pattern,
|
||||||
|
} = *func;
|
||||||
|
let Some(pattern) = pattern else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
// TODO: get function name
|
||||||
|
// TODO: param spans
|
||||||
|
if !pattern.ellipsis {
|
||||||
|
for key in pattern.required.iter().copied() {
|
||||||
|
if attrset.lookup(key).is_none() {
|
||||||
|
let name = ctx.resolve_string(key);
|
||||||
|
return self.finish_err(Error::eval_error(format!(
|
||||||
|
"function 'anonymous lambda' called without required argument '{name}'"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for &(key, _) in attrset.entries.borrow().iter() {
|
||||||
|
let is_expected =
|
||||||
|
pattern.required.contains(&key) || pattern.optional.contains(&key);
|
||||||
|
if !is_expected {
|
||||||
|
let name = ctx.resolve_string(key);
|
||||||
|
return self.finish_err(Error::eval_error(format!(
|
||||||
|
"function 'anonymous lambda' called with unexpected argument '{name}'"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_env = Gc::new(
|
||||||
|
mc,
|
||||||
|
RefLock::new(Env::with_arg(Value::new_gc(attrset), n_locals, env)),
|
||||||
|
);
|
||||||
|
reader.set_pc(ip as usize);
|
||||||
|
self.env = new_env;
|
||||||
|
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_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<'_>>()
|
||||||
|
}
|
||||||
@@ -1,42 +1,95 @@
|
|||||||
|
use fix_builtins::PrimOpPhase;
|
||||||
use fix_error::Error;
|
use fix_error::Error;
|
||||||
use gc_arena::{Gc, Mutation, RefLock};
|
use gc_arena::{Gc, Mutation, RefLock};
|
||||||
|
|
||||||
use crate::value::*;
|
use crate::value::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
BytecodeReader, CallFrame, Closure, Env, Step, ThunkState, VmRuntimeCtx, VmRuntimeCtxExt,
|
BytecodeReader, CallFrame, Closure, Env, ForceMode, Step, ThunkState, VmRuntimeCtx,
|
||||||
|
VmRuntimeCtxExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl<'gc> crate::Vm<'gc> {
|
impl<'gc> crate::Vm<'gc> {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn op_call(
|
pub(crate) fn call(
|
||||||
&mut self,
|
&mut self,
|
||||||
ctx: &mut impl VmRuntimeCtx,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
|
arg: Value<'gc>,
|
||||||
|
resume_pc: usize,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
let func = self.try_force::<StrictValue>(reader, mc)?;
|
let func = self.try_force::<StrictValue>(reader, mc)?;
|
||||||
if self.call_depth > 10000 {
|
if self.call_depth > 10000 {
|
||||||
return self.finish_err(Error::eval_error("stack overflow; max-call-depth exceeded"));
|
return self.finish_err(Error::eval_error("stack overflow; max-call-depth exceeded"));
|
||||||
}
|
}
|
||||||
self.call_depth += 1;
|
self.call_depth += 1;
|
||||||
let arg = reader.read_operand_data(ctx).resolve(mc, self);
|
|
||||||
if let Some(closure) = func.as_gc::<Closure>() {
|
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());
|
||||||
if let Some(ref _pattern) = closure.pattern {
|
self.push(arg);
|
||||||
todo!("pattern call")
|
|
||||||
} else {
|
|
||||||
let new_env = Gc::new(mc, RefLock::new(Env::with_arg(arg, n_locals, env)));
|
|
||||||
self.call_stack.push(CallFrame {
|
self.call_stack.push(CallFrame {
|
||||||
pc: reader.pc(),
|
pc: resume_pc,
|
||||||
stack_depth: 0,
|
stack_depth: 0,
|
||||||
thunk: None,
|
thunk: None,
|
||||||
env: self.env,
|
env: self.env,
|
||||||
with_env: self.with_env,
|
with_env: self.with_env,
|
||||||
});
|
});
|
||||||
reader.set_pc(ip as usize);
|
reader.set_pc(PrimOpPhase::CallPattern.ip() as usize);
|
||||||
self.env = new_env;
|
return Step::Continue(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let ip = closure.ip;
|
||||||
|
let n_locals = closure.n_locals;
|
||||||
|
let env = closure.env;
|
||||||
|
let new_env = Gc::new(mc, RefLock::new(Env::with_arg(arg, n_locals, env)));
|
||||||
|
self.call_stack.push(CallFrame {
|
||||||
|
pc: resume_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 if let Some(primop) = func.as_inline::<PrimOp>() {
|
||||||
|
if primop.arity == 1 {
|
||||||
|
self.push(arg);
|
||||||
|
self.call_stack.push(CallFrame {
|
||||||
|
pc: resume_pc,
|
||||||
|
stack_depth: 0,
|
||||||
|
thunk: None,
|
||||||
|
env: self.env,
|
||||||
|
with_env: self.with_env,
|
||||||
|
});
|
||||||
|
reader.set_pc(primop.dispatch_ip as usize)
|
||||||
|
} else {
|
||||||
|
let app = PrimOpApp {
|
||||||
|
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,
|
||||||
|
stack_depth: 0,
|
||||||
|
thunk: None,
|
||||||
|
env: self.env,
|
||||||
|
with_env: self.with_env,
|
||||||
|
});
|
||||||
|
reader.set_pc(app.primop.dispatch_ip as usize)
|
||||||
|
} else {
|
||||||
|
let new_app = PrimOpApp {
|
||||||
|
arity: app.arity - 1,
|
||||||
|
..*app
|
||||||
|
};
|
||||||
|
self.push(Value::new_gc(Gc::new(mc, new_app)))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
todo!("call other types: {func:?}")
|
todo!("call other types: {func:?}")
|
||||||
@@ -44,6 +97,18 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_call(
|
||||||
|
&mut self,
|
||||||
|
ctx: &impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
let arg = reader.read_operand_data(ctx).resolve(mc, self);
|
||||||
|
let pc = reader.pc();
|
||||||
|
self.call(reader, mc, arg, pc)
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn op_return(
|
pub(crate) fn op_return(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -51,16 +116,7 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
self.handle_return(reader, ctx, mc)
|
let val = self.try_force::<StrictValue>(reader, mc)?;
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn handle_return<C: VmRuntimeCtx>(
|
|
||||||
&mut self,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
ctx: &C,
|
|
||||||
mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
let ret_inst_pc = reader.pc() - 1;
|
|
||||||
let Some(CallFrame {
|
let Some(CallFrame {
|
||||||
pc: ret_pc,
|
pc: ret_pc,
|
||||||
stack_depth,
|
stack_depth,
|
||||||
@@ -69,66 +125,36 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
with_env,
|
with_env,
|
||||||
}) = self.call_stack.pop()
|
}) = self.call_stack.pop()
|
||||||
else {
|
else {
|
||||||
let val = self.pop();
|
match self.force_mode {
|
||||||
return self.finish_ok(ctx.convert_value(val));
|
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,
|
||||||
|
stack_depth: 0,
|
||||||
|
thunk: None,
|
||||||
|
env: self.env,
|
||||||
|
with_env: self.with_env,
|
||||||
|
});
|
||||||
|
self.call_depth += 1;
|
||||||
|
reader.set_pc(PrimOpPhase::DeepSeq.ip() as usize);
|
||||||
|
return Step::Continue(());
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
reader.set_pc(ret_pc);
|
reader.set_pc(ret_pc);
|
||||||
if let Some(outer_thunk) = thunk {
|
if let Some(outer_thunk) = thunk {
|
||||||
let val = self.pop();
|
*outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val);
|
||||||
match val.restrict() {
|
self.replace(stack_depth, val.relax());
|
||||||
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(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 Step::Continue(());
|
|
||||||
}
|
|
||||||
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(val.relax());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ThunkState::Apply { func: _, arg: _ } => todo!("force Apply thunk"),
|
|
||||||
ThunkState::Blackhole => {
|
|
||||||
return self
|
|
||||||
.finish_err(Error::eval_error("infinite recursion encountered"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
self.call_depth -= 1;
|
self.call_depth -= 1;
|
||||||
|
self.push(val.relax())
|
||||||
}
|
}
|
||||||
self.env = env;
|
self.env = env;
|
||||||
self.with_env = with_env;
|
self.with_env = with_env;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use fix_common::StringId;
|
use fix_common::StringId;
|
||||||
use fix_error::Error;
|
use fix_error::Error;
|
||||||
use gc_arena::Gc;
|
use gc_arena::{Gc, RefLock};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
use crate::value::NixType;
|
use crate::value::NixType;
|
||||||
@@ -20,7 +20,7 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
let count = reader.read_u32() as usize;
|
let count = reader.read_u32() as usize;
|
||||||
let mut entries: SmallVec<[AttrEntry; 4]> = SmallVec::with_capacity(count);
|
let mut entries: SmallVec<[AttrEntry; 4]> = SmallVec::with_capacity(count);
|
||||||
for _ in 0..count {
|
for _ in 0..count {
|
||||||
let key = reader.read_attr_key_data(ctx);
|
let key = reader.read_attr_key_data();
|
||||||
let val = reader.read_operand_data(ctx);
|
let val = reader.read_operand_data(ctx);
|
||||||
let _span_id = reader.read_u32();
|
let _span_id = reader.read_u32();
|
||||||
entries.push(AttrEntry { key, val });
|
entries.push(AttrEntry { key, val });
|
||||||
@@ -29,10 +29,8 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
for entry in &entries {
|
for entry in &entries {
|
||||||
let key_sid = match &entry.key {
|
let key_sid = match &entry.key {
|
||||||
AttrKeyData::Static(sid) => *sid,
|
AttrKeyData::Static(sid) => *sid,
|
||||||
AttrKeyData::Dynamic(op) => {
|
AttrKeyData::Dynamic => {
|
||||||
let v = op.resolve(mc, self);
|
todo!()
|
||||||
v.as_inline::<crate::StringId>()
|
|
||||||
.expect("dynamic attr key must be a string")
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let val = entry.val.resolve(mc, self);
|
let val = entry.val.resolve(mc, self);
|
||||||
@@ -298,7 +296,12 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
for _ in 0..count {
|
for _ in 0..count {
|
||||||
items.push(reader.read_operand_data(ctx).resolve(mc, self));
|
items.push(reader.read_operand_data(ctx).resolve(mc, self));
|
||||||
}
|
}
|
||||||
let list = Gc::new(mc, List { inner: items });
|
let list = Gc::new(
|
||||||
|
mc,
|
||||||
|
List {
|
||||||
|
inner: RefLock::new(items),
|
||||||
|
},
|
||||||
|
);
|
||||||
self.push(Value::new_gc(list));
|
self.push(Value::new_gc(list));
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
self.push(Value::new_inline(PrimOp {
|
self.push(Value::new_inline(PrimOp {
|
||||||
id,
|
id,
|
||||||
arity: fix_builtins::BUILTINS[id as usize].1,
|
arity: fix_builtins::BUILTINS[id as usize].1,
|
||||||
|
dispatch_ip: id.entry_phase().ip(),
|
||||||
}));
|
}));
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
pub(crate) mod arithmetic;
|
pub(crate) mod arithmetic;
|
||||||
pub(crate) mod builtins_misc;
|
pub(crate) mod builtins;
|
||||||
pub(crate) mod calls;
|
pub(crate) mod calls;
|
||||||
pub(crate) mod closures;
|
pub(crate) mod closures;
|
||||||
pub(crate) mod collections;
|
pub(crate) mod collections;
|
||||||
pub(crate) mod control;
|
pub(crate) mod control;
|
||||||
pub(crate) mod literals;
|
pub(crate) mod literals;
|
||||||
|
pub(crate) mod misc;
|
||||||
pub(crate) mod variables;
|
pub(crate) mod variables;
|
||||||
pub(crate) mod with_scope;
|
pub(crate) mod with_scope;
|
||||||
|
|||||||
+83
-26
@@ -13,7 +13,7 @@ use fix_common::StringId;
|
|||||||
use fix_error::{Error, Result, Source};
|
use fix_error::{Error, Result, Source};
|
||||||
use gc_arena::arena::CollectionPhase;
|
use gc_arena::arena::CollectionPhase;
|
||||||
use gc_arena::{Arena, Collect, Gc, Mutation, RefLock, Rootable};
|
use gc_arena::{Arena, Collect, Gc, Mutation, RefLock, Rootable};
|
||||||
use hashbrown::HashMap;
|
use hashbrown::{HashMap, HashSet};
|
||||||
use num_enum::TryFromPrimitive;
|
use num_enum::TryFromPrimitive;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
@@ -115,6 +115,16 @@ impl<T: VmRuntimeCtx> VmRuntimeCtxExt for T {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn convert_value(&self, val: Value) -> fix_common::Value {
|
fn convert_value(&self, val: Value) -> fix_common::Value {
|
||||||
|
self.convert_value_with_seen(val, &mut HashSet::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait ConvertValueWithSeen: VmRuntimeCtx {
|
||||||
|
fn convert_value_with_seen(&self, val: Value, seen: &mut HashSet<u64>) -> fix_common::Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: VmRuntimeCtx> ConvertValueWithSeen for T {
|
||||||
|
fn convert_value_with_seen(&self, val: Value, seen: &mut HashSet<u64>) -> fix_common::Value {
|
||||||
use fix_common::Value;
|
use fix_common::Value;
|
||||||
if let Some(i) = val.as_inline::<i32>() {
|
if let Some(i) = val.as_inline::<i32>() {
|
||||||
Value::Int(i as i64)
|
Value::Int(i as i64)
|
||||||
@@ -132,29 +142,50 @@ impl<T: VmRuntimeCtx> VmRuntimeCtxExt for T {
|
|||||||
} else if let Some(ns) = val.as_gc::<NixString>() {
|
} else if let Some(ns) = val.as_gc::<NixString>() {
|
||||||
Value::String(ns.as_str().to_owned())
|
Value::String(ns.as_str().to_owned())
|
||||||
} else if let Some(attrs) = val.as_gc::<AttrSet>() {
|
} else if let Some(attrs) = val.as_gc::<AttrSet>() {
|
||||||
|
let bits = val.to_bits();
|
||||||
|
if attrs.entries.borrow().is_empty() {
|
||||||
|
return Value::AttrSet(Default::default());
|
||||||
|
}
|
||||||
|
if !seen.insert(bits) {
|
||||||
|
return Value::Repeated;
|
||||||
|
}
|
||||||
let mut map = std::collections::BTreeMap::new();
|
let mut map = std::collections::BTreeMap::new();
|
||||||
for &(key, val) in attrs.iter() {
|
for &(key, val) in attrs.entries.borrow().iter() {
|
||||||
let key = self.resolve_string(key).to_owned();
|
let key = self.resolve_string(key).to_owned();
|
||||||
let converted = self.convert_value(val);
|
let converted = self.convert_value_with_seen(val, seen);
|
||||||
map.insert(fix_common::Symbol::from(key), converted);
|
map.insert(fix_common::Symbol::from(key), converted);
|
||||||
}
|
}
|
||||||
Value::AttrSet(fix_common::AttrSet::new(map))
|
Value::AttrSet(fix_common::AttrSet::new(map))
|
||||||
} else if let Some(list) = val.as_gc::<List>() {
|
} else if let Some(list) = val.as_gc::<List>() {
|
||||||
|
let bits = val.to_bits();
|
||||||
|
if list.inner.borrow().is_empty() {
|
||||||
|
return Value::List(Default::default());
|
||||||
|
}
|
||||||
|
if !seen.insert(bits) {
|
||||||
|
return Value::Repeated;
|
||||||
|
}
|
||||||
let items: Vec<_> = list
|
let items: Vec<_> = list
|
||||||
.inner
|
.inner
|
||||||
|
.borrow()
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.copied()
|
||||||
.map(|v| self.convert_value(v))
|
.map(|v| self.convert_value_with_seen(v, seen))
|
||||||
.collect();
|
.collect();
|
||||||
Value::List(fix_common::List::new(items))
|
Value::List(fix_common::List::new(items))
|
||||||
} else if val.is::<Closure>() {
|
} else if val.is::<Closure>() {
|
||||||
Value::Func
|
Value::Func
|
||||||
} else if val.is::<Thunk>() {
|
} else if let Some(thunk) = val.as_gc::<Thunk>() {
|
||||||
Value::Thunk
|
if let ThunkState::Evaluated(v) = *thunk.borrow() {
|
||||||
} else if val.as_inline::<PrimOp>().is_some() {
|
self.convert_value_with_seen(v.relax(), seen)
|
||||||
Value::PrimOp("primop".into())
|
} else {
|
||||||
} else if val.is::<PrimOpApp>() {
|
Value::Thunk
|
||||||
Value::PrimOpApp("primop-app".into())
|
}
|
||||||
|
} else if let Some(primop) = val.as_inline::<PrimOp>() {
|
||||||
|
let name = fix_builtins::BUILTINS[primop.id as usize].0;
|
||||||
|
Value::PrimOp(name.strip_prefix("__").unwrap_or(name))
|
||||||
|
} else if let Some(app) = val.as_gc::<PrimOpApp>() {
|
||||||
|
let name = fix_builtins::BUILTINS[app.primop.id as usize].0;
|
||||||
|
Value::PrimOpApp(name.strip_prefix("__").unwrap_or(name))
|
||||||
} else {
|
} else {
|
||||||
Value::Null
|
Value::Null
|
||||||
}
|
}
|
||||||
@@ -221,7 +252,7 @@ impl OperandData {
|
|||||||
|
|
||||||
pub(crate) enum AttrKeyData {
|
pub(crate) enum AttrKeyData {
|
||||||
Static(StringId),
|
Static(StringId),
|
||||||
Dynamic(OperandData),
|
Dynamic,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_builtins<'gc>(mc: &Mutation<'gc>, ctx: &mut impl VmRuntimeCtx) -> Value<'gc> {
|
fn init_builtins<'gc>(mc: &Mutation<'gc>, ctx: &mut impl VmRuntimeCtx) -> Value<'gc> {
|
||||||
@@ -231,7 +262,15 @@ fn init_builtins<'gc>(mc: &Mutation<'gc>, ctx: &mut impl VmRuntimeCtx) -> Value<
|
|||||||
let id = BuiltinId::try_from_primitive(idx as u8).expect("infallible");
|
let id = BuiltinId::try_from_primitive(idx as u8).expect("infallible");
|
||||||
let name = name.strip_prefix("__").unwrap_or(name);
|
let name = name.strip_prefix("__").unwrap_or(name);
|
||||||
let name = ctx.intern_string(name);
|
let name = ctx.intern_string(name);
|
||||||
entries.push((name, Value::new_inline(PrimOp { id, arity })));
|
let dispatch_ip = id.entry_phase().ip();
|
||||||
|
entries.push((
|
||||||
|
name,
|
||||||
|
Value::new_inline(PrimOp {
|
||||||
|
id,
|
||||||
|
arity,
|
||||||
|
dispatch_ip,
|
||||||
|
}),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let consts = [
|
let consts = [
|
||||||
@@ -248,15 +287,7 @@ fn init_builtins<'gc>(mc: &Mutation<'gc>, ctx: &mut impl VmRuntimeCtx) -> Value<
|
|||||||
"__storeDir",
|
"__storeDir",
|
||||||
Value::new_inline(ctx.intern_string("/nix/store")),
|
Value::new_inline(ctx.intern_string("/nix/store")),
|
||||||
),
|
),
|
||||||
(
|
("__nixPath", Value::new_gc(Gc::new(mc, List::default()))),
|
||||||
"__nixPath",
|
|
||||||
Value::new_gc(Gc::new(
|
|
||||||
mc,
|
|
||||||
List {
|
|
||||||
inner: SmallVec::new(),
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
("null", Value::new_inline(Null)),
|
("null", Value::new_inline(Null)),
|
||||||
("true", Value::new_inline(true)),
|
("true", Value::new_inline(true)),
|
||||||
("false", Value::new_inline(false)),
|
("false", Value::new_inline(false)),
|
||||||
@@ -275,7 +306,10 @@ fn init_builtins<'gc>(mc: &Mutation<'gc>, ctx: &mut impl VmRuntimeCtx) -> Value<
|
|||||||
entries.sort_by_key(|(k, _)| *k);
|
entries.sort_by_key(|(k, _)| *k);
|
||||||
|
|
||||||
let builtins_set = Gc::new(mc, AttrSet::from_sorted_unchecked(entries));
|
let builtins_set = Gc::new(mc, AttrSet::from_sorted_unchecked(entries));
|
||||||
Value::new_gc(builtins_set)
|
let builtins_value = Value::new_gc(builtins_set);
|
||||||
|
*self_ref_thunk.borrow_mut(mc) =
|
||||||
|
ThunkState::Evaluated(builtins_value.restrict().expect("builtins is not a thunk"));
|
||||||
|
builtins_value
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'gc> Vm<'gc> {
|
impl<'gc> Vm<'gc> {
|
||||||
@@ -382,16 +416,38 @@ impl<'gc> Vm<'gc> {
|
|||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
) -> std::ops::ControlFlow<Break, T> {
|
) -> std::ops::ControlFlow<Break, T> {
|
||||||
T::force_and_check(self, reader, mc, 0)?;
|
self.try_force_to_pc(reader, mc, reader.inst_start_pc())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn try_force_to_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))
|
std::ops::ControlFlow::Continue(T::pop_converted(self))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
#[allow(unused)]
|
||||||
pub(crate) fn force_slot(
|
pub(crate) fn force_slot(
|
||||||
&mut self,
|
&mut self,
|
||||||
depth: usize,
|
depth: usize,
|
||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
self.force_slot_to_pc(depth, reader, mc, reader.inst_start_pc())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn force_slot_to_pc(
|
||||||
|
&mut self,
|
||||||
|
depth: usize,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
resume_pc: usize,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
let Some(thunk) = self.peek(depth).as_gc::<Thunk>() else {
|
let Some(thunk) = self.peek(depth).as_gc::<Thunk>() else {
|
||||||
return Step::Continue(());
|
return Step::Continue(());
|
||||||
@@ -404,7 +460,7 @@ impl<'gc> Vm<'gc> {
|
|||||||
self.call_stack.push(CallFrame {
|
self.call_stack.push(CallFrame {
|
||||||
thunk: Some(thunk),
|
thunk: Some(thunk),
|
||||||
stack_depth: depth,
|
stack_depth: depth,
|
||||||
pc: reader.inst_start_pc(),
|
pc: resume_pc,
|
||||||
env: self.env,
|
env: self.env,
|
||||||
with_env: self.with_env,
|
with_env: self.with_env,
|
||||||
});
|
});
|
||||||
@@ -554,6 +610,7 @@ impl<'gc> Vm<'gc> {
|
|||||||
MakePatternClosure => self.op_make_pattern_closure(&mut reader, mc),
|
MakePatternClosure => self.op_make_pattern_closure(&mut reader, mc),
|
||||||
|
|
||||||
Call => self.op_call(ctx, &mut reader, mc),
|
Call => self.op_call(ctx, &mut reader, mc),
|
||||||
|
DispatchPrimOp => self.op_dispatch_primop(ctx, &mut reader, mc),
|
||||||
Return => self.op_return(ctx, &mut reader, mc),
|
Return => self.op_return(ctx, &mut reader, mc),
|
||||||
|
|
||||||
MakeAttrs => self.op_make_attrs(ctx, &mut reader, mc),
|
MakeAttrs => self.op_make_attrs(ctx, &mut reader, mc),
|
||||||
@@ -584,8 +641,8 @@ impl<'gc> Vm<'gc> {
|
|||||||
OpConcat => self.op_concat(&mut reader, mc),
|
OpConcat => self.op_concat(&mut reader, mc),
|
||||||
OpUpdate => self.op_update(&mut reader, mc),
|
OpUpdate => self.op_update(&mut reader, mc),
|
||||||
|
|
||||||
OpNeg => self.op_neg(),
|
OpNeg => self.op_neg(&mut reader, mc),
|
||||||
OpNot => self.op_not(),
|
OpNot => self.op_not(&mut reader, mc),
|
||||||
|
|
||||||
JumpIfFalse => self.op_jump_if_false(&mut reader, mc),
|
JumpIfFalse => self.op_jump_if_false(&mut reader, mc),
|
||||||
JumpIfTrue => self.op_jump_if_true(&mut reader, mc),
|
JumpIfTrue => self.op_jump_if_true(&mut reader, mc),
|
||||||
|
|||||||
+82
-29
@@ -1,5 +1,6 @@
|
|||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
use std::cell::RefCell;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
@@ -7,6 +8,7 @@ use std::ops::Deref;
|
|||||||
|
|
||||||
use fix_builtins::BuiltinId;
|
use fix_builtins::BuiltinId;
|
||||||
use fix_common::*;
|
use fix_common::*;
|
||||||
|
use gc_arena::barrier::Unlock;
|
||||||
use gc_arena::collect::Trace;
|
use gc_arena::collect::Trace;
|
||||||
use gc_arena::{Collect, Gc, GcRefLock, Mutation, RefLock};
|
use gc_arena::{Collect, Gc, GcRefLock, Mutation, RefLock};
|
||||||
use num_enum::TryFromPrimitive;
|
use num_enum::TryFromPrimitive;
|
||||||
@@ -246,6 +248,11 @@ impl<'gc> Value<'gc> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn to_bits(self) -> u64 {
|
||||||
|
self.raw.to_bits()
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn as_num(self) -> Option<NixNum> {
|
pub fn as_num(self) -> Option<NixNum> {
|
||||||
if let Some(i) = self.as_inline::<i32>() {
|
if let Some(i) = self.as_inline::<i32>() {
|
||||||
@@ -345,8 +352,12 @@ impl StaticValue {
|
|||||||
Self(Value::new_inline(val))
|
Self(Value::new_inline(val))
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new_primop(id: BuiltinId, arity: u8) -> Self {
|
pub fn new_primop(id: BuiltinId, arity: u8, dispatch_ip: u32) -> Self {
|
||||||
Self(Value::new_inline(PrimOp { id, arity }))
|
Self(Value::new_inline(PrimOp {
|
||||||
|
id,
|
||||||
|
arity,
|
||||||
|
dispatch_ip,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_float(self) -> bool {
|
pub fn is_float(self) -> bool {
|
||||||
@@ -424,77 +435,107 @@ impl fmt::Debug for NixString {
|
|||||||
#[derive(Collect, Debug, Default)]
|
#[derive(Collect, Debug, Default)]
|
||||||
#[collect(no_drop)]
|
#[collect(no_drop)]
|
||||||
pub(crate) struct AttrSet<'gc> {
|
pub(crate) struct AttrSet<'gc> {
|
||||||
entries: SmallVec<[(StringId, Value<'gc>); 4]>,
|
pub(crate) entries: RefLock<SmallVec<[(StringId, Value<'gc>); 4]>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'gc> Deref for AttrSet<'gc> {
|
impl<'gc> Unlock for AttrSet<'gc> {
|
||||||
type Target = [(StringId, Value<'gc>)];
|
type Unlocked = RefCell<SmallVec<[(StringId, Value<'gc>); 4]>>;
|
||||||
fn deref(&self) -> &Self::Target {
|
unsafe fn unlock_unchecked(&self) -> &Self::Unlocked {
|
||||||
&self.entries
|
unsafe { self.entries.unlock_unchecked() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'gc> AttrSet<'gc> {
|
impl<'gc> AttrSet<'gc> {
|
||||||
pub(crate) fn from_sorted_unchecked(entries: SmallVec<[(StringId, Value<'gc>); 4]>) -> Self {
|
pub(crate) fn from_sorted_unchecked(entries: SmallVec<[(StringId, Value<'gc>); 4]>) -> Self {
|
||||||
debug_assert!(entries.is_sorted_by_key(|(key, _)| *key));
|
debug_assert!(entries.is_sorted_by_key(|(key, _)| *key));
|
||||||
Self { entries }
|
Self {
|
||||||
|
entries: RefLock::new(entries),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn lookup(&self, key: StringId) -> Option<Value<'gc>> {
|
pub(crate) fn lookup(&self, key: StringId) -> Option<Value<'gc>> {
|
||||||
self.entries
|
let entries = self.entries.borrow();
|
||||||
|
entries
|
||||||
.binary_search_by_key(&key, |(k, _)| *k)
|
.binary_search_by_key(&key, |(k, _)| *k)
|
||||||
.ok()
|
.ok()
|
||||||
.map(|i| self.entries[i].1)
|
.map(|i| entries[i].1)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn has(&self, key: StringId) -> bool {
|
pub(crate) fn has(&self, key: StringId) -> bool {
|
||||||
self.entries.binary_search_by_key(&key, |(k, _)| *k).is_ok()
|
self.entries
|
||||||
|
.borrow()
|
||||||
|
.binary_search_by_key(&key, |(k, _)| *k)
|
||||||
|
.is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn merge(&self, other: &Self, mc: &Mutation<'gc>) -> Gc<'gc, Self> {
|
pub(crate) fn merge(&self, other: &Self, mc: &Mutation<'gc>) -> Gc<'gc, Self> {
|
||||||
use std::cmp::Ordering::*;
|
use std::cmp::Ordering::*;
|
||||||
|
|
||||||
debug_assert!(self.entries.is_sorted_by_key(|(key, _)| *key));
|
let self_entries = self.entries.borrow();
|
||||||
debug_assert!(other.entries.is_sorted_by_key(|(key, _)| *key));
|
let other_entries = other.entries.borrow();
|
||||||
|
debug_assert!(self_entries.is_sorted_by_key(|(key, _)| *key));
|
||||||
|
debug_assert!(other_entries.is_sorted_by_key(|(key, _)| *key));
|
||||||
|
|
||||||
let mut entries = SmallVec::new();
|
let mut entries = SmallVec::new();
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
let mut j = 0;
|
let mut j = 0;
|
||||||
while i < self.entries.len() && j < other.entries.len() {
|
while i < self_entries.len() && j < other_entries.len() {
|
||||||
match self.entries[i].0.cmp(&other.entries[j].0) {
|
match self_entries[i].0.cmp(&other_entries[j].0) {
|
||||||
Less => {
|
Less => {
|
||||||
entries.push(self.entries[i]);
|
entries.push(self_entries[i]);
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
Greater => {
|
Greater => {
|
||||||
entries.push(other.entries[j]);
|
entries.push(other_entries[j]);
|
||||||
j += 1;
|
j += 1;
|
||||||
}
|
}
|
||||||
Equal => {
|
Equal => {
|
||||||
entries.push(other.entries[j]);
|
entries.push(other_entries[j]);
|
||||||
i += 1;
|
i += 1;
|
||||||
j += 1;
|
j += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
entries.extend(other.entries[j..].iter().cloned());
|
entries.extend(other_entries[j..].iter().cloned());
|
||||||
entries.extend(self.entries[i..].iter().cloned());
|
entries.extend(self_entries[i..].iter().cloned());
|
||||||
|
|
||||||
debug_assert!(entries.is_sorted_by_key(|(key, _)| *key));
|
debug_assert!(entries.is_sorted_by_key(|(key, _)| *key));
|
||||||
|
|
||||||
Gc::new(mc, AttrSet { entries })
|
Gc::new(
|
||||||
|
mc,
|
||||||
|
AttrSet {
|
||||||
|
entries: RefLock::new(entries),
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Collect, Debug, Default)]
|
#[derive(Collect, Debug, Default)]
|
||||||
|
#[repr(transparent)]
|
||||||
#[collect(no_drop)]
|
#[collect(no_drop)]
|
||||||
pub(crate) struct List<'gc> {
|
pub(crate) struct List<'gc> {
|
||||||
pub(crate) inner: SmallVec<[Value<'gc>; 4]>,
|
pub(crate) inner: RefLock<SmallVec<[Value<'gc>; 4]>>,
|
||||||
}
|
}
|
||||||
impl<'gc> Deref for List<'gc> {
|
|
||||||
type Target = SmallVec<[Value<'gc>; 4]>;
|
impl<'gc> List<'gc> {
|
||||||
fn deref(&self) -> &Self::Target {
|
pub(crate) fn new(mc: &Mutation<'gc>, data: SmallVec<[Value<'gc>; 4]>) -> Gc<'gc, Self> {
|
||||||
&self.inner
|
Gc::new(
|
||||||
|
mc,
|
||||||
|
Self {
|
||||||
|
inner: RefLock::new(data),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn new_gc(mc: &Mutation<'gc>) -> Gc<'gc, Self> {
|
||||||
|
Gc::new(mc, Self::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gc> Unlock for List<'gc> {
|
||||||
|
type Unlocked = RefCell<SmallVec<[Value<'gc>; 4]>>;
|
||||||
|
unsafe fn unlock_unchecked(&self) -> &Self::Unlocked {
|
||||||
|
unsafe { self.inner.unlock_unchecked() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -572,22 +613,33 @@ pub(crate) struct PatternInfo {
|
|||||||
pub(crate) param_spans: Box<[(StringId, u32)]>,
|
pub(crate) param_spans: Box<[(StringId, u32)]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(packed, Rust)]
|
||||||
#[derive(Clone, Copy, Debug, Collect)]
|
#[derive(Clone, Copy, Debug, Collect)]
|
||||||
#[collect(require_static)]
|
#[collect(require_static)]
|
||||||
pub(crate) struct PrimOp {
|
pub(crate) struct PrimOp {
|
||||||
pub(crate) id: BuiltinId,
|
pub(crate) id: BuiltinId,
|
||||||
pub(crate) arity: u8,
|
pub(crate) arity: u8,
|
||||||
|
pub(crate) dispatch_ip: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RawStore for PrimOp {
|
impl RawStore for PrimOp {
|
||||||
fn to_val(self, value: &mut RawValue) {
|
fn to_val(self, value: &mut RawValue) {
|
||||||
value.set_data([0, 0, 0, 0, self.id as u8, self.arity]);
|
let bytes = self.dispatch_ip.to_le_bytes();
|
||||||
|
value.set_data([
|
||||||
|
self.id as u8,
|
||||||
|
self.arity,
|
||||||
|
bytes[0],
|
||||||
|
bytes[1],
|
||||||
|
bytes[2],
|
||||||
|
bytes[3],
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
fn from_val(value: &RawValue) -> Self {
|
fn from_val(value: &RawValue) -> Self {
|
||||||
let [.., id, arity] = *value.data();
|
let [id, arity, bytes @ ..] = *value.data();
|
||||||
Self {
|
Self {
|
||||||
id: BuiltinId::try_from_primitive(id).expect("invalid BuiltinId"),
|
id: BuiltinId::try_from_primitive(id).expect("invalid BuiltinId"),
|
||||||
arity,
|
arity,
|
||||||
|
dispatch_ip: u32::from_le_bytes(bytes),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -596,7 +648,8 @@ impl RawStore for PrimOp {
|
|||||||
#[collect(no_drop)]
|
#[collect(no_drop)]
|
||||||
pub(crate) struct PrimOpApp<'gc> {
|
pub(crate) struct PrimOpApp<'gc> {
|
||||||
pub(crate) primop: PrimOp,
|
pub(crate) primop: PrimOp,
|
||||||
pub(crate) args: SmallVec<[Value<'gc>; 2]>,
|
pub(crate) arity: u8,
|
||||||
|
pub(crate) args: [Value<'gc>; 3],
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Default, Collect)]
|
#[derive(Copy, Clone, Default, Collect)]
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ rnix = { workspace = true }
|
|||||||
ere = { workspace = true }
|
ere = { workspace = true }
|
||||||
ghost-cell = { workspace = true }
|
ghost-cell = { workspace = true }
|
||||||
|
|
||||||
|
fix-builtins = { path = "../fix-builtins" }
|
||||||
fix-common = { path = "../fix-common" }
|
fix-common = { path = "../fix-common" }
|
||||||
fix-codegen = { path = "../fix-codegen" }
|
fix-codegen = { path = "../fix-codegen" }
|
||||||
fix-error = { path = "../fix-error" }
|
fix-error = { path = "../fix-error" }
|
||||||
|
|||||||
+13
-3
@@ -2,8 +2,9 @@
|
|||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
|
use fix_builtins::PrimOpPhase;
|
||||||
use fix_codegen::disassembler::{Disassembler, DisassemblerContext};
|
use fix_codegen::disassembler::{Disassembler, DisassemblerContext};
|
||||||
use fix_codegen::{BytecodeContext, InstructionPtr};
|
use fix_codegen::{BytecodeContext, InstructionPtr, Op};
|
||||||
use fix_common::{StringId, Symbol};
|
use fix_common::{StringId, Symbol};
|
||||||
use fix_error::{Error, Result, Source};
|
use fix_error::{Error, Result, Source};
|
||||||
use fix_ir::downgrade::{Downgrade as _, DowngradeContext};
|
use fix_ir::downgrade::{Downgrade as _, DowngradeContext};
|
||||||
@@ -47,6 +48,11 @@ impl Evaluator {
|
|||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let mut strings = DefaultStringInterner::new();
|
let mut strings = DefaultStringInterner::new();
|
||||||
let global_env = fix_ir::new_global_env(&mut strings);
|
let global_env = fix_ir::new_global_env(&mut strings);
|
||||||
|
let mut bytecode = Vec::with_capacity(PrimOpPhase::Illegal as usize * 2);
|
||||||
|
for phase in 0..=PrimOpPhase::Illegal as u8 {
|
||||||
|
bytecode.push(Op::DispatchPrimOp as u8);
|
||||||
|
bytecode.push(phase);
|
||||||
|
}
|
||||||
Self {
|
Self {
|
||||||
runtime: RuntimeState {
|
runtime: RuntimeState {
|
||||||
strings,
|
strings,
|
||||||
@@ -56,7 +62,7 @@ impl Evaluator {
|
|||||||
sources: Vec::new(),
|
sources: Vec::new(),
|
||||||
spans: Vec::new(),
|
spans: Vec::new(),
|
||||||
thunk_count: 0,
|
thunk_count: 0,
|
||||||
bytecode: Vec::new(),
|
bytecode,
|
||||||
global_env,
|
global_env,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -239,7 +245,11 @@ impl<'a, R: VmRuntimeCtx> BytecodeContext for CompilerCtx<'a, R> {
|
|||||||
Float(x) => StaticValue::new_float(x),
|
Float(x) => StaticValue::new_float(x),
|
||||||
Bool(x) => StaticValue::new_inline(x),
|
Bool(x) => StaticValue::new_inline(x),
|
||||||
String(x) => StaticValue::new_inline(x),
|
String(x) => StaticValue::new_inline(x),
|
||||||
PrimOp { id, arity } => StaticValue::new_primop(id, arity),
|
PrimOp {
|
||||||
|
id,
|
||||||
|
arity,
|
||||||
|
dispatch_ip,
|
||||||
|
} => StaticValue::new_primop(id, arity, dispatch_ip),
|
||||||
Null => StaticValue::default(),
|
Null => StaticValue::default(),
|
||||||
};
|
};
|
||||||
self.runtime.add_const(val)
|
self.runtime.add_const(val)
|
||||||
|
|||||||
Reference in New Issue
Block a user