Compare commits
20 Commits
4aa694aa3a
...
ed86c928d1
| Author | SHA1 | Date | |
|---|---|---|---|
|
ed86c928d1
|
|||
|
9df43f6152
|
|||
|
151dd4835c
|
|||
|
7fa2f44a87
|
|||
|
2e06c3ced5
|
|||
|
8c093a8d42
|
|||
|
6a8ec079ea
|
|||
|
430d1218de
|
|||
|
1fc716c7be
|
|||
|
ec2726ff8d
|
|||
|
2c4c325307
|
|||
|
4419d38eb9
|
|||
|
7a7a9c3735
|
|||
|
0df38f374f
|
|||
|
0ca5e8af92
|
|||
|
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",
|
||||||
|
|||||||
-70
@@ -1,70 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://biomejs.dev/schemas/2.3.14/schema.json",
|
|
||||||
"vcs": {
|
|
||||||
"enabled": true,
|
|
||||||
"clientKind": "git",
|
|
||||||
"useIgnoreFile": true
|
|
||||||
},
|
|
||||||
"files": {
|
|
||||||
"includes": ["**", "!!**/dist"]
|
|
||||||
},
|
|
||||||
"formatter": {
|
|
||||||
"enabled": true,
|
|
||||||
"formatWithErrors": true,
|
|
||||||
"attributePosition": "auto",
|
|
||||||
"indentStyle": "space",
|
|
||||||
"indentWidth": 2,
|
|
||||||
"lineWidth": 110,
|
|
||||||
"lineEnding": "lf"
|
|
||||||
},
|
|
||||||
"linter": {
|
|
||||||
"rules": {
|
|
||||||
"style": {
|
|
||||||
"useNamingConvention": {
|
|
||||||
"level": "warn",
|
|
||||||
"options": {
|
|
||||||
"strictCase": false,
|
|
||||||
"conventions": [
|
|
||||||
{
|
|
||||||
"selector": { "kind": "objectLiteralProperty" },
|
|
||||||
"formats": ["camelCase", "PascalCase", "CONSTANT_CASE"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"selector": { "kind": "typeProperty" },
|
|
||||||
"formats": ["camelCase", "snake_case"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"includes": ["**/global.d.ts"],
|
|
||||||
"linter": {
|
|
||||||
"rules": {
|
|
||||||
"style": {
|
|
||||||
"useNamingConvention": "off"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"javascript": {
|
|
||||||
"formatter": {
|
|
||||||
"arrowParentheses": "always",
|
|
||||||
"bracketSameLine": false,
|
|
||||||
"bracketSpacing": true,
|
|
||||||
"jsxQuoteStyle": "double",
|
|
||||||
"quoteProperties": "asNeeded",
|
|
||||||
"semicolons": "always",
|
|
||||||
"trailingCommas": "all"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"json": {
|
|
||||||
"formatter": {
|
|
||||||
"trailingCommas": "none"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+247
-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,250 @@ 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,
|
||||||
|
CallFunctor1,
|
||||||
|
CallFunctor2,
|
||||||
|
|
||||||
|
ImportFinalize,
|
||||||
|
ScopedImportFinalize,
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
|
use fix_builtins::BuiltinId;
|
||||||
use num_enum::TryFromPrimitive;
|
use num_enum::TryFromPrimitive;
|
||||||
|
|
||||||
use crate::{AttrKeyType, InstructionPtr, Op, OperandType};
|
use crate::{InstructionPtr, Op, OperandType};
|
||||||
|
|
||||||
pub trait DisassemblerContext {
|
pub trait DisassemblerContext {
|
||||||
fn resolve_string(&self, id: u32) -> &str;
|
fn resolve_string(&self, id: u32) -> &str;
|
||||||
@@ -79,19 +80,30 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
|||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn read_operand_data(&mut self) {
|
fn read_operand_data(&mut self) {
|
||||||
|
use OperandType::*;
|
||||||
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");
|
||||||
match ty {
|
match ty {
|
||||||
OperandType::Const => {
|
Const => {
|
||||||
self.read_u32();
|
self.read_u32();
|
||||||
}
|
}
|
||||||
OperandType::Local => {
|
BigInt => {
|
||||||
|
self.read_i64();
|
||||||
|
}
|
||||||
|
Local => {
|
||||||
self.read_u8();
|
self.read_u8();
|
||||||
self.read_u32();
|
self.read_u32();
|
||||||
}
|
}
|
||||||
OperandType::Builtins => {}
|
BuiltinConst => {
|
||||||
OperandType::BigInt => {
|
self.read_u32();
|
||||||
self.read_i64();
|
}
|
||||||
|
Builtins => {}
|
||||||
|
ReplBinding => {
|
||||||
|
self.read_u32();
|
||||||
|
}
|
||||||
|
ScopedImportBinding => {
|
||||||
|
self.read_u32();
|
||||||
|
self.read_u32();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -280,30 +292,35 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
|||||||
("MakePatternClosure", arg_str)
|
("MakePatternClosure", arg_str)
|
||||||
}
|
}
|
||||||
|
|
||||||
Op::Call => ("Call", String::new()),
|
Op::Call => {
|
||||||
|
self.read_operand_data();
|
||||||
|
("Call", "arg=?".into())
|
||||||
|
}
|
||||||
|
Op::DispatchPrimOp => {
|
||||||
|
let id = BuiltinId::try_from_primitive(self.read_u8()).expect("invalid builtin id");
|
||||||
|
("DispatchPrimOp", format!("id={id:?}"))
|
||||||
|
}
|
||||||
|
|
||||||
Op::MakeAttrs => {
|
Op::MakeAttrs => {
|
||||||
let count = self.read_u32();
|
let static_count = self.read_u32();
|
||||||
let mut args = format!("size={}", count);
|
let dynamic_count = self.read_u32();
|
||||||
for _ in 0..count {
|
let mut args = format!("static={} dynamic={}", static_count, dynamic_count);
|
||||||
let key_tag = self.read_u8();
|
|
||||||
let key_ty =
|
for _ in 0..static_count {
|
||||||
AttrKeyType::try_from_primitive(key_tag).expect("invalid attr key type");
|
|
||||||
match key_ty {
|
|
||||||
AttrKeyType::Static => {
|
|
||||||
let key_id = self.read_u32();
|
let key_id = self.read_u32();
|
||||||
let _ =
|
let _ = write!(args, " [{}={}", self.ctx.resolve_string(key_id), key_id);
|
||||||
write!(args, " [{}={}", self.ctx.resolve_string(key_id), key_id);
|
|
||||||
}
|
|
||||||
AttrKeyType::Dynamic => {
|
|
||||||
let _ = write!(args, " [dyn");
|
|
||||||
self.read_operand_data();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.read_operand_data();
|
self.read_operand_data();
|
||||||
let _span_id = self.read_u32();
|
let _span_id = self.read_u32();
|
||||||
args.push(']');
|
args.push(']');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _ in 0..dynamic_count {
|
||||||
|
let _ = write!(args, " [dyn");
|
||||||
|
self.read_operand_data();
|
||||||
|
let _span_id = self.read_u32();
|
||||||
|
args.push(']');
|
||||||
|
}
|
||||||
|
|
||||||
("MakeAttrs", args)
|
("MakeAttrs", args)
|
||||||
}
|
}
|
||||||
Op::MakeEmptyAttrs => ("MakeEmptyAttrs", String::new()),
|
Op::MakeEmptyAttrs => ("MakeEmptyAttrs", String::new()),
|
||||||
@@ -406,19 +423,25 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
|||||||
let force = self.read_u8();
|
let force = self.read_u8();
|
||||||
("ConcatStrings", format!("count={} force={}", count, force))
|
("ConcatStrings", format!("count={} force={}", count, force))
|
||||||
}
|
}
|
||||||
Op::ResolvePath => ("ResolvePath", String::new()),
|
Op::CoerceToString => ("CoerceToString", String::new()),
|
||||||
|
Op::ResolvePath => {
|
||||||
|
let dir_id = self.read_u32();
|
||||||
|
let dir = self.ctx.resolve_string(dir_id);
|
||||||
|
("ResolvePath", format!("dir={:?}", dir))
|
||||||
|
}
|
||||||
Op::Assert => {
|
Op::Assert => {
|
||||||
let raw_idx = self.read_u32();
|
let raw_idx = self.read_u32();
|
||||||
let span_id = self.read_u32();
|
let span_id = self.read_u32();
|
||||||
("Assert", format!("text_id={} span={}", raw_idx, span_id))
|
("Assert", format!("text_id={} span={}", raw_idx, span_id))
|
||||||
}
|
}
|
||||||
Op::PushWith => ("PushWith", String::new()),
|
|
||||||
Op::PopWith => ("PopWith", String::new()),
|
|
||||||
Op::PrepareWith => ("PrepareWith", String::new()),
|
|
||||||
Op::LookupWith => {
|
Op::LookupWith => {
|
||||||
let idx = self.read_u32();
|
let idx = self.read_u32();
|
||||||
let name = self.ctx.resolve_string(idx);
|
let name = self.ctx.resolve_string(idx);
|
||||||
("LookupWith", format!("{:?}", name))
|
let n = self.read_u8();
|
||||||
|
for _ in 0..n {
|
||||||
|
self.read_operand_data();
|
||||||
|
}
|
||||||
|
("LookupWith", format!("sym={:?} n={}", name, n))
|
||||||
}
|
}
|
||||||
|
|
||||||
Op::LoadBuiltins => ("LoadBuiltins", String::new()),
|
Op::LoadBuiltins => ("LoadBuiltins", String::new()),
|
||||||
@@ -426,19 +449,16 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
|||||||
let id = self.read_u8();
|
let id = self.read_u8();
|
||||||
("LoadBuiltin", format!("id={}", id))
|
("LoadBuiltin", format!("id={}", id))
|
||||||
}
|
}
|
||||||
Op::MkPos => {
|
|
||||||
let span_id = self.read_u32();
|
|
||||||
("MkPos", format!("id={}", span_id))
|
|
||||||
}
|
|
||||||
Op::LoadReplBinding => {
|
Op::LoadReplBinding => {
|
||||||
let idx = self.read_u32();
|
let idx = self.read_u32();
|
||||||
let name = self.ctx.resolve_string(idx);
|
let name = self.ctx.resolve_string(idx);
|
||||||
("LoadReplBinding", format!("{:?}", name))
|
("LoadReplBinding", format!("{:?}", name))
|
||||||
}
|
}
|
||||||
Op::LoadScopedBinding => {
|
Op::LoadScopedBinding => {
|
||||||
|
let slot = self.read_u32();
|
||||||
let idx = self.read_u32();
|
let idx = self.read_u32();
|
||||||
let name = self.ctx.resolve_string(idx);
|
let name = self.ctx.resolve_string(idx);
|
||||||
("LoadScopedBinding", format!("{:?}", name))
|
("LoadScopedBinding", format!("slot={} {:?}", slot, name))
|
||||||
}
|
}
|
||||||
Op::Return => ("Return", String::new()),
|
Op::Return => ("Return", String::new()),
|
||||||
Op::Illegal => ("Illegal", String::new()),
|
Op::Illegal => ("Illegal", String::new()),
|
||||||
|
|||||||
+200
-226
@@ -1,6 +1,4 @@
|
|||||||
use std::ops::Deref;
|
use fix_builtins::{BUILTINS, BuiltinId};
|
||||||
|
|
||||||
use fix_builtins::BuiltinId;
|
|
||||||
use fix_common::StringId;
|
use fix_common::StringId;
|
||||||
use fix_ir::{Attr, BinOpKind, Ir, MaybeThunk, Param, RawIrRef, ThunkId, UnOpKind};
|
use fix_ir::{Attr, BinOpKind, Ir, MaybeThunk, Param, RawIrRef, ThunkId, UnOpKind};
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
@@ -18,6 +16,8 @@ pub trait BytecodeContext {
|
|||||||
fn get_code(&self) -> &[u8];
|
fn get_code(&self) -> &[u8];
|
||||||
fn get_code_mut(&mut self) -> &mut Vec<u8>;
|
fn get_code_mut(&mut self) -> &mut Vec<u8>;
|
||||||
fn add_constant(&mut self, val: Const) -> u32;
|
fn add_constant(&mut self, val: Const) -> u32;
|
||||||
|
fn current_source_dir(&mut self) -> StringId;
|
||||||
|
fn current_scope_slot(&self) -> Option<u32>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
@@ -42,6 +42,7 @@ pub enum Op {
|
|||||||
MakePatternClosure,
|
MakePatternClosure,
|
||||||
|
|
||||||
Call,
|
Call,
|
||||||
|
DispatchPrimOp,
|
||||||
|
|
||||||
MakeAttrs,
|
MakeAttrs,
|
||||||
MakeEmptyAttrs,
|
MakeEmptyAttrs,
|
||||||
@@ -78,21 +79,18 @@ pub enum Op {
|
|||||||
JumpIfTrue,
|
JumpIfTrue,
|
||||||
Jump,
|
Jump,
|
||||||
|
|
||||||
|
CoerceToString,
|
||||||
|
|
||||||
ConcatStrings,
|
ConcatStrings,
|
||||||
ResolvePath,
|
ResolvePath,
|
||||||
|
|
||||||
Assert,
|
Assert,
|
||||||
|
|
||||||
PushWith,
|
|
||||||
PopWith,
|
|
||||||
LookupWith,
|
LookupWith,
|
||||||
PrepareWith,
|
|
||||||
|
|
||||||
LoadBuiltins,
|
LoadBuiltins,
|
||||||
LoadBuiltin,
|
LoadBuiltin,
|
||||||
|
|
||||||
MkPos,
|
|
||||||
|
|
||||||
LoadReplBinding,
|
LoadReplBinding,
|
||||||
LoadScopedBinding,
|
LoadScopedBinding,
|
||||||
|
|
||||||
@@ -102,7 +100,7 @@ pub enum Op {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct ScopeInfo {
|
struct ScopeInfo {
|
||||||
depth: u16,
|
depth: u8,
|
||||||
thunk_map: HashMap<ThunkId, u32>,
|
thunk_map: HashMap<ThunkId, u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,9 +113,12 @@ struct BytecodeEmitter<'a, Ctx: BytecodeContext> {
|
|||||||
#[derive(Debug, Clone, Copy, TryFromPrimitive)]
|
#[derive(Debug, Clone, Copy, TryFromPrimitive)]
|
||||||
pub enum OperandType {
|
pub enum OperandType {
|
||||||
Const,
|
Const,
|
||||||
Local,
|
|
||||||
Builtins,
|
|
||||||
BigInt,
|
BigInt,
|
||||||
|
Local,
|
||||||
|
BuiltinConst,
|
||||||
|
Builtins,
|
||||||
|
ReplBinding,
|
||||||
|
ScopedImportBinding,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Const {
|
pub enum Const {
|
||||||
@@ -125,7 +126,12 @@ pub enum Const {
|
|||||||
Float(f64),
|
Float(f64),
|
||||||
Bool(bool),
|
Bool(bool),
|
||||||
String(StringId),
|
String(StringId),
|
||||||
PrimOp { id: BuiltinId, arity: u8 },
|
Path(StringId),
|
||||||
|
PrimOp {
|
||||||
|
id: BuiltinId,
|
||||||
|
arity: u8,
|
||||||
|
dispatch_ip: u32,
|
||||||
|
},
|
||||||
Null,
|
Null,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,9 +144,12 @@ pub enum AttrKeyType {
|
|||||||
|
|
||||||
pub enum InlineOperand {
|
pub enum InlineOperand {
|
||||||
Const(Const),
|
Const(Const),
|
||||||
Local { layer: u16, local: u32 },
|
|
||||||
Builtins,
|
|
||||||
BigInt(i64),
|
BigInt(i64),
|
||||||
|
Local { layer: u8, local: u32 },
|
||||||
|
BuiltinConst(StringId),
|
||||||
|
Builtins,
|
||||||
|
ReplBinding(StringId),
|
||||||
|
ScopedImportBinding(StringId),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn compile_bytecode(ir: RawIrRef<'_>, ctx: &mut impl BytecodeContext) -> InstructionPtr {
|
pub fn compile_bytecode(ir: RawIrRef<'_>, ctx: &mut impl BytecodeContext) -> InstructionPtr {
|
||||||
@@ -159,12 +168,12 @@ 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) => {
|
||||||
if x <= i32::MAX as i64 {
|
if let Ok(x) = x.try_into() {
|
||||||
InlineOperand::Const(Const::Smi(x as i32))
|
InlineOperand::Const(Const::Smi(x))
|
||||||
} else {
|
} else {
|
||||||
InlineOperand::BigInt(x)
|
InlineOperand::BigInt(x)
|
||||||
}
|
}
|
||||||
@@ -173,38 +182,65 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
Bool(b) => InlineOperand::Const(Const::Bool(b)),
|
Bool(b) => InlineOperand::Const(Const::Bool(b)),
|
||||||
Null => InlineOperand::Const(Const::Null),
|
Null => InlineOperand::Const(Const::Null),
|
||||||
Str(id) => InlineOperand::Const(Const::String(id)),
|
Str(id) => InlineOperand::Const(Const::String(id)),
|
||||||
|
Path(id) => InlineOperand::Const(Const::String(id)),
|
||||||
Thunk(id) => {
|
Thunk(id) => {
|
||||||
let (layer, local) = self.resolve_thunk(id);
|
let (layer, local) = self.resolve_thunk(id);
|
||||||
InlineOperand::Local { layer, local }
|
InlineOperand::Local { layer, local }
|
||||||
}
|
}
|
||||||
Arg { layer } => InlineOperand::Local {
|
Arg { layer } => InlineOperand::Local { layer, local: 0 },
|
||||||
layer: layer.try_into().expect("scope too deep!"),
|
Builtin(id) => {
|
||||||
local: 0,
|
let (_, arity) = BUILTINS[id as usize];
|
||||||
},
|
InlineOperand::Const(Const::PrimOp {
|
||||||
_ => todo!(),
|
id,
|
||||||
|
arity,
|
||||||
|
dispatch_ip: id.entry_phase().ip(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
BuiltinConst(id) => InlineOperand::BuiltinConst(id),
|
||||||
|
Builtins => InlineOperand::Builtins,
|
||||||
|
ReplBinding(id) => InlineOperand::ReplBinding(id),
|
||||||
|
ScopedImportBinding(id) => InlineOperand::ScopedImportBinding(id),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emit_maybe_thunk(&mut self, val: MaybeThunk) {
|
fn emit_maybe_thunk(&mut self, val: &MaybeThunk) {
|
||||||
|
use InlineOperand::*;
|
||||||
let operand = self.inline_maybe_thunk(val);
|
let operand = self.inline_maybe_thunk(val);
|
||||||
match operand {
|
match operand {
|
||||||
InlineOperand::Const(val) => {
|
Const(val) => {
|
||||||
let idx = self.ctx.add_constant(val);
|
let idx = self.ctx.add_constant(val);
|
||||||
self.emit_u8(OperandType::Const as u8);
|
self.emit_u8(OperandType::Const as u8);
|
||||||
self.emit_u32(idx);
|
self.emit_u32(idx);
|
||||||
}
|
}
|
||||||
InlineOperand::Local { layer, local } => {
|
BigInt(val) => {
|
||||||
self.emit_u8(OperandType::Local as u8);
|
|
||||||
self.emit_u8(layer as u8);
|
|
||||||
self.emit_u32(local);
|
|
||||||
}
|
|
||||||
InlineOperand::Builtins => {
|
|
||||||
self.emit_u8(OperandType::Builtins as u8);
|
|
||||||
}
|
|
||||||
InlineOperand::BigInt(val) => {
|
|
||||||
self.emit_u8(OperandType::BigInt as u8);
|
self.emit_u8(OperandType::BigInt as u8);
|
||||||
self.emit_i64(val);
|
self.emit_i64(val);
|
||||||
}
|
}
|
||||||
|
Local { layer, local } => {
|
||||||
|
self.emit_u8(OperandType::Local as u8);
|
||||||
|
self.emit_u8(layer);
|
||||||
|
self.emit_u32(local);
|
||||||
|
}
|
||||||
|
BuiltinConst(id) => {
|
||||||
|
self.emit_u8(OperandType::BuiltinConst as u8);
|
||||||
|
self.emit_str_id(id);
|
||||||
|
}
|
||||||
|
Builtins => {
|
||||||
|
self.emit_u8(OperandType::Builtins as u8);
|
||||||
|
}
|
||||||
|
ReplBinding(id) => {
|
||||||
|
self.emit_u8(OperandType::ReplBinding as u8);
|
||||||
|
self.emit_str_id(id);
|
||||||
|
}
|
||||||
|
ScopedImportBinding(id) => {
|
||||||
|
self.emit_u8(OperandType::ScopedImportBinding as u8);
|
||||||
|
let slot = self
|
||||||
|
.ctx
|
||||||
|
.current_scope_slot()
|
||||||
|
.expect("ScopedImportBinding outside scoped compilation");
|
||||||
|
self.emit_u32(slot);
|
||||||
|
self.emit_str_id(id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,6 +249,11 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
self.ctx.get_code_mut().push(op as u8);
|
self.ctx.get_code_mut().push(op as u8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn emit_bool(&mut self, val: bool) {
|
||||||
|
self.emit_u8(u8::from(val));
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn emit_u8(&mut self, val: u8) {
|
fn emit_u8(&mut self, val: u8) {
|
||||||
self.ctx.get_code_mut().push(val);
|
self.ctx.get_code_mut().push(val);
|
||||||
@@ -284,11 +325,11 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
.extend_from_slice(&(id.0.to_usize() as u32).to_le_bytes());
|
.extend_from_slice(&(id.0.to_usize() as u32).to_le_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn current_depth(&self) -> u16 {
|
fn current_depth(&self) -> u8 {
|
||||||
self.scope_stack.last().map_or(0, |s| s.depth)
|
self.scope_stack.last().map_or(0, |s| s.depth)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_thunk(&self, id: ThunkId) -> (u16, u32) {
|
fn resolve_thunk(&self, id: ThunkId) -> (u8, u32) {
|
||||||
for scope in self.scope_stack.iter().rev() {
|
for scope in self.scope_stack.iter().rev() {
|
||||||
if let Some(&local_idx) = scope.thunk_map.get(&id) {
|
if let Some(&local_idx) = scope.thunk_map.get(&id) {
|
||||||
let layer = self.current_depth() - scope.depth;
|
let layer = self.current_depth() - scope.depth;
|
||||||
@@ -298,111 +339,19 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
panic!("ThunkId {:?} not found in any scope", id);
|
panic!("ThunkId {:?} not found in any scope", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emit_load(&mut self, layer: u16, local: u32) {
|
fn emit_load(&mut self, layer: u8, local: u32) {
|
||||||
if layer == 0 {
|
if layer == 0 {
|
||||||
self.emit_op(Op::LoadLocal);
|
self.emit_op(Op::LoadLocal);
|
||||||
self.emit_u32(local);
|
self.emit_u32(local);
|
||||||
} else {
|
} else {
|
||||||
self.emit_op(Op::LoadOuter);
|
self.emit_op(Op::LoadOuter);
|
||||||
self.emit_u8(layer as u8);
|
self.emit_u8(layer);
|
||||||
self.emit_u32(local);
|
self.emit_u32(local);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn count_with_thunks(&self, ir: RawIrRef<'_>) -> usize {
|
|
||||||
match ir.deref() {
|
|
||||||
Ir::With { thunks, body, .. } => thunks.len() + self.count_with_thunks(*body),
|
|
||||||
Ir::TopLevel { thunks, body } => thunks.len() + self.count_with_thunks(*body),
|
|
||||||
Ir::If { cond, consq, alter } => {
|
|
||||||
self.count_with_thunks(*cond)
|
|
||||||
+ self.count_with_thunks(*consq)
|
|
||||||
+ self.count_with_thunks(*alter)
|
|
||||||
}
|
|
||||||
Ir::BinOp { lhs, rhs, .. } => {
|
|
||||||
self.count_with_thunks(*lhs) + self.count_with_thunks(*rhs)
|
|
||||||
}
|
|
||||||
Ir::UnOp { rhs, .. } => self.count_with_thunks(*rhs),
|
|
||||||
Ir::Call { func, .. } => self.count_with_thunks(*func),
|
|
||||||
Ir::Assert {
|
|
||||||
assertion, expr, ..
|
|
||||||
} => self.count_with_thunks(*assertion) + self.count_with_thunks(*expr),
|
|
||||||
Ir::Select { expr, .. } => self.count_with_thunks(*expr),
|
|
||||||
Ir::HasAttr { lhs, .. } => self.count_with_thunks(*lhs),
|
|
||||||
Ir::ConcatStrings { parts, .. } => {
|
|
||||||
parts.iter().map(|p| self.count_with_thunks(*p)).sum()
|
|
||||||
}
|
|
||||||
_ => 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn collect_all_thunks<'ir>(
|
|
||||||
&self,
|
|
||||||
own_thunks: &[(ThunkId, RawIrRef<'ir>)],
|
|
||||||
body: RawIrRef<'ir>,
|
|
||||||
) -> Vec<(ThunkId, RawIrRef<'ir>)> {
|
|
||||||
let mut all = Vec::from(own_thunks);
|
|
||||||
self.collect_with_thunks_recursive(body, &mut all);
|
|
||||||
let mut i = 0;
|
|
||||||
while i < all.len() {
|
|
||||||
let thunk_body = all[i].1;
|
|
||||||
self.collect_with_thunks_recursive(thunk_body, &mut all);
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
all
|
|
||||||
}
|
|
||||||
|
|
||||||
fn collect_with_thunks_recursive<'ir>(
|
|
||||||
&self,
|
|
||||||
ir: RawIrRef<'ir>,
|
|
||||||
out: &mut Vec<(ThunkId, RawIrRef<'ir>)>,
|
|
||||||
) {
|
|
||||||
match ir.deref() {
|
|
||||||
Ir::With { thunks, body, .. } => {
|
|
||||||
for &(id, inner) in thunks.iter() {
|
|
||||||
out.push((id, inner));
|
|
||||||
}
|
|
||||||
self.collect_with_thunks_recursive(*body, out);
|
|
||||||
}
|
|
||||||
Ir::TopLevel { thunks, body } => {
|
|
||||||
for &(id, inner) in thunks.iter() {
|
|
||||||
out.push((id, inner));
|
|
||||||
}
|
|
||||||
self.collect_with_thunks_recursive(*body, out);
|
|
||||||
}
|
|
||||||
Ir::If { cond, consq, alter } => {
|
|
||||||
self.collect_with_thunks_recursive(*cond, out);
|
|
||||||
self.collect_with_thunks_recursive(*consq, out);
|
|
||||||
self.collect_with_thunks_recursive(*alter, out);
|
|
||||||
}
|
|
||||||
Ir::BinOp { lhs, rhs, .. } => {
|
|
||||||
self.collect_with_thunks_recursive(*lhs, out);
|
|
||||||
self.collect_with_thunks_recursive(*rhs, out);
|
|
||||||
}
|
|
||||||
Ir::UnOp { rhs, .. } => self.collect_with_thunks_recursive(*rhs, out),
|
|
||||||
Ir::Call { func, .. } => {
|
|
||||||
self.collect_with_thunks_recursive(*func, out);
|
|
||||||
}
|
|
||||||
Ir::Assert {
|
|
||||||
assertion, expr, ..
|
|
||||||
} => {
|
|
||||||
self.collect_with_thunks_recursive(*assertion, out);
|
|
||||||
self.collect_with_thunks_recursive(*expr, out);
|
|
||||||
}
|
|
||||||
Ir::Select { expr, .. } => {
|
|
||||||
self.collect_with_thunks_recursive(*expr, out);
|
|
||||||
}
|
|
||||||
Ir::HasAttr { lhs, .. } => self.collect_with_thunks_recursive(*lhs, out),
|
|
||||||
Ir::ConcatStrings { parts, .. } => {
|
|
||||||
for p in parts.iter() {
|
|
||||||
self.collect_with_thunks_recursive(*p, out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_scope(&mut self, has_arg: bool, thunk_ids: &[ThunkId]) {
|
fn push_scope(&mut self, has_arg: bool, thunk_ids: &[ThunkId]) {
|
||||||
let depth = self.scope_stack.len() as u16;
|
let depth = self.scope_stack.len().try_into().expect("scope too deep!");
|
||||||
let thunk_base = if has_arg { 1u32 } else { 0u32 };
|
let thunk_base = if has_arg { 1u32 } else { 0u32 };
|
||||||
let thunk_map = thunk_ids
|
let thunk_map = thunk_ids
|
||||||
.iter()
|
.iter()
|
||||||
@@ -417,19 +366,13 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn emit_toplevel(&mut self, ir: RawIrRef<'_>) {
|
fn emit_toplevel(&mut self, ir: RawIrRef<'_>) {
|
||||||
match ir.deref() {
|
match ir {
|
||||||
&Ir::TopLevel { body, ref thunks } => {
|
&Ir::TopLevel { body, ref thunks } => {
|
||||||
let with_thunk_count = self.count_with_thunks(body);
|
let thunk_ids: Vec<ThunkId> = thunks.iter().map(|&(id, _)| id).collect();
|
||||||
let total_slots = thunks.len() + with_thunk_count;
|
|
||||||
|
|
||||||
let all_thunks = self.collect_all_thunks(thunks, body);
|
|
||||||
let thunk_ids: Vec<ThunkId> = all_thunks.iter().map(|&(id, _)| id).collect();
|
|
||||||
|
|
||||||
self.push_scope(false, &thunk_ids);
|
self.push_scope(false, &thunk_ids);
|
||||||
|
if !thunks.is_empty() {
|
||||||
if total_slots > 0 {
|
|
||||||
self.emit_op(Op::AllocLocals);
|
self.emit_op(Op::AllocLocals);
|
||||||
self.emit_u32(total_slots as u32);
|
self.emit_u32(thunks.len().try_into().expect("too many thunks"));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.emit_scope_thunks(thunks);
|
self.emit_scope_thunks(thunks);
|
||||||
@@ -463,11 +406,11 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn emit_expr(&mut self, ir: RawIrRef<'_>) {
|
fn emit_expr(&mut self, ir: RawIrRef<'_>) {
|
||||||
match ir.deref() {
|
match ir {
|
||||||
&Ir::Int(x) => {
|
&Ir::Int(x) => {
|
||||||
if x <= i32::MAX as i64 {
|
if let Ok(x) = x.try_into() {
|
||||||
self.emit_op(Op::PushSmi);
|
self.emit_op(Op::PushSmi);
|
||||||
self.emit_i32(x as i32);
|
self.emit_i32(x);
|
||||||
} else {
|
} else {
|
||||||
self.emit_op(Op::PushBigInt);
|
self.emit_op(Op::PushBigInt);
|
||||||
self.emit_i64(x);
|
self.emit_i64(x);
|
||||||
@@ -487,6 +430,8 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
&Ir::Path(p) => {
|
&Ir::Path(p) => {
|
||||||
self.emit_expr(p);
|
self.emit_expr(p);
|
||||||
self.emit_op(Op::ResolvePath);
|
self.emit_op(Op::ResolvePath);
|
||||||
|
let dir_id = self.ctx.current_source_dir();
|
||||||
|
self.emit_str_id(dir_id);
|
||||||
}
|
}
|
||||||
&Ir::If { cond, consq, alter } => {
|
&Ir::If { cond, consq, alter } => {
|
||||||
self.emit_expr(cond);
|
self.emit_expr(cond);
|
||||||
@@ -549,7 +494,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
self.emit_maybe_thunk(arg);
|
self.emit_maybe_thunk(arg);
|
||||||
}
|
}
|
||||||
&Ir::Arg { layer } => {
|
&Ir::Arg { layer } => {
|
||||||
self.emit_load(layer.try_into().expect("scope too deep!"), 0);
|
self.emit_load(layer, 0);
|
||||||
}
|
}
|
||||||
&Ir::TopLevel { body, ref thunks } => {
|
&Ir::TopLevel { body, ref thunks } => {
|
||||||
self.emit_toplevel_inner(body, thunks);
|
self.emit_toplevel_inner(body, thunks);
|
||||||
@@ -562,10 +507,6 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
} => {
|
} => {
|
||||||
self.emit_select(expr, attrpath, default, span);
|
self.emit_select(expr, attrpath, default, span);
|
||||||
}
|
}
|
||||||
&Ir::Thunk(id) => {
|
|
||||||
let (layer, local) = self.resolve_thunk(id);
|
|
||||||
self.emit_load(layer, local);
|
|
||||||
}
|
|
||||||
Ir::Builtins => {
|
Ir::Builtins => {
|
||||||
self.emit_op(Op::LoadBuiltins);
|
self.emit_op(Op::LoadBuiltins);
|
||||||
}
|
}
|
||||||
@@ -575,24 +516,23 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
}
|
}
|
||||||
&Ir::BuiltinConst(id) => {
|
&Ir::BuiltinConst(id) => {
|
||||||
self.emit_select(
|
self.emit_select(
|
||||||
RawIrRef(&Ir::Builtins),
|
&Ir::Builtins,
|
||||||
&[Attr::Str(id, TextRange::default())],
|
&[Attr::Str(id, TextRange::default())],
|
||||||
None,
|
None,
|
||||||
TextRange::default(),
|
TextRange::default(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
&Ir::ConcatStrings {
|
&Ir::ConcatStrings {
|
||||||
parts: _,
|
ref parts,
|
||||||
force_string: _,
|
force_string,
|
||||||
} => {
|
} => {
|
||||||
todo!("redesign ConcatStrings");
|
for &part in parts.iter() {
|
||||||
// self.emit_op(Op::ConcatStrings);
|
self.emit_expr(part);
|
||||||
// self.emit_u16(parts.len() as u16);
|
self.emit_op(Op::CoerceToString);
|
||||||
// self.emit_u8(if force_string { 1 } else { 0 });
|
}
|
||||||
// for &part in parts.iter() {
|
self.emit_op(Op::ConcatStrings);
|
||||||
// let operand = self.inline_maybe_thunk(part);
|
self.emit_u16(parts.len() as u16);
|
||||||
// self.emit_inline_operand(operand);
|
self.emit_bool(force_string);
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
&Ir::HasAttr { lhs, ref rhs } => {
|
&Ir::HasAttr { lhs, ref rhs } => {
|
||||||
self.emit_has_attr(lhs, rhs);
|
self.emit_has_attr(lhs, rhs);
|
||||||
@@ -606,15 +546,10 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
let raw_idx = self.ctx.intern_string(assertion_raw);
|
let raw_idx = self.ctx.intern_string(assertion_raw);
|
||||||
let span_id = self.ctx.register_span(*span);
|
let span_id = self.ctx.register_span(*span);
|
||||||
self.emit_expr(*assertion);
|
self.emit_expr(*assertion);
|
||||||
self.emit_expr(*expr);
|
|
||||||
self.emit_op(Op::Assert);
|
self.emit_op(Op::Assert);
|
||||||
self.emit_str_id(raw_idx);
|
self.emit_str_id(raw_idx);
|
||||||
self.emit_u32(span_id);
|
self.emit_u32(span_id);
|
||||||
}
|
self.emit_expr(*expr);
|
||||||
&Ir::CurPos(span) => {
|
|
||||||
let span_id = self.ctx.register_span(span);
|
|
||||||
self.emit_op(Op::MkPos);
|
|
||||||
self.emit_u32(span_id);
|
|
||||||
}
|
}
|
||||||
&Ir::ReplBinding(name) => {
|
&Ir::ReplBinding(name) => {
|
||||||
self.emit_op(Op::LoadReplBinding);
|
self.emit_op(Op::LoadReplBinding);
|
||||||
@@ -622,21 +557,89 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
}
|
}
|
||||||
&Ir::ScopedImportBinding(name) => {
|
&Ir::ScopedImportBinding(name) => {
|
||||||
self.emit_op(Op::LoadScopedBinding);
|
self.emit_op(Op::LoadScopedBinding);
|
||||||
|
let slot = self
|
||||||
|
.ctx
|
||||||
|
.current_scope_slot()
|
||||||
|
.expect("ScopedImportBinding outside scoped compilation");
|
||||||
|
self.emit_u32(slot);
|
||||||
self.emit_str_id(name);
|
self.emit_str_id(name);
|
||||||
}
|
}
|
||||||
&Ir::With {
|
Ir::WithLookup { sym, namespaces } => {
|
||||||
namespace,
|
// counter
|
||||||
body,
|
self.emit_expr(&Ir::Int(0));
|
||||||
ref thunks,
|
|
||||||
} => {
|
|
||||||
self.emit_with(namespace, body, thunks);
|
|
||||||
}
|
|
||||||
&Ir::WithLookup(name) => {
|
|
||||||
// TODO: specialize shallow with lookups
|
|
||||||
self.emit_op(Op::PrepareWith);
|
|
||||||
self.emit_op(Op::LookupWith);
|
self.emit_op(Op::LookupWith);
|
||||||
|
self.emit_str_id(*sym);
|
||||||
|
self.emit_u8(
|
||||||
|
namespaces
|
||||||
|
.len()
|
||||||
|
.try_into()
|
||||||
|
.expect("too many `with` namespaces"),
|
||||||
|
);
|
||||||
|
for namespace in namespaces {
|
||||||
|
self.emit_maybe_thunk(namespace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&Ir::MaybeThunk(thunk) => {
|
||||||
|
use MaybeThunk::*;
|
||||||
|
match *thunk {
|
||||||
|
Int(x) => {
|
||||||
|
if let Ok(x) = x.try_into() {
|
||||||
|
self.emit_op(Op::PushSmi);
|
||||||
|
self.emit_i32(x);
|
||||||
|
} else {
|
||||||
|
self.emit_op(Op::PushBigInt);
|
||||||
|
self.emit_i64(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Float(x) => {
|
||||||
|
self.emit_op(Op::PushFloat);
|
||||||
|
self.emit_f64(x);
|
||||||
|
}
|
||||||
|
Bool(true) => self.emit_op(Op::PushTrue),
|
||||||
|
Bool(false) => self.emit_op(Op::PushFalse),
|
||||||
|
Null => self.emit_op(Op::PushNull),
|
||||||
|
Str(id) => {
|
||||||
|
self.emit_op(Op::PushString);
|
||||||
|
self.emit_str_id(id);
|
||||||
|
}
|
||||||
|
Path(id) => {
|
||||||
|
self.emit_op(Op::PushString);
|
||||||
|
self.emit_str_id(id);
|
||||||
|
self.emit_op(Op::ResolvePath);
|
||||||
|
let dir_id = self.ctx.current_source_dir();
|
||||||
|
self.emit_str_id(dir_id);
|
||||||
|
}
|
||||||
|
Thunk(id) => {
|
||||||
|
let (layer, local) = self.resolve_thunk(id);
|
||||||
|
self.emit_load(layer, local);
|
||||||
|
}
|
||||||
|
Arg { layer } => self.emit_load(layer, 0),
|
||||||
|
Builtin(id) => {
|
||||||
|
self.emit_op(Op::LoadBuiltin);
|
||||||
|
self.emit_u8(id as u8);
|
||||||
|
}
|
||||||
|
BuiltinConst(id) => self.emit_select(
|
||||||
|
&Ir::Builtins,
|
||||||
|
&[Attr::Str(id, TextRange::default())],
|
||||||
|
None,
|
||||||
|
TextRange::default(),
|
||||||
|
),
|
||||||
|
Builtins => self.emit_op(Op::LoadBuiltins),
|
||||||
|
ReplBinding(name) => {
|
||||||
|
self.emit_op(Op::LoadReplBinding);
|
||||||
self.emit_str_id(name);
|
self.emit_str_id(name);
|
||||||
}
|
}
|
||||||
|
ScopedImportBinding(name) => {
|
||||||
|
self.emit_op(Op::LoadScopedBinding);
|
||||||
|
let slot = self
|
||||||
|
.ctx
|
||||||
|
.current_scope_slot()
|
||||||
|
.expect("ScopedImportBinding outside scoped compilation");
|
||||||
|
self.emit_u32(slot);
|
||||||
|
self.emit_str_id(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -700,18 +703,6 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
let end_offset = (self.ctx.get_code_mut().len() as i32) - (after_jump as i32);
|
let end_offset = (self.ctx.get_code_mut().len() as i32) - (after_jump as i32);
|
||||||
self.patch_i32(end_placeholder, end_offset);
|
self.patch_i32(end_placeholder, end_offset);
|
||||||
}
|
}
|
||||||
PipeL => {
|
|
||||||
todo!("new call");
|
|
||||||
// self.emit_expr(rhs);
|
|
||||||
// self.emit_expr(lhs);
|
|
||||||
// self.emit_op(Op::Call);
|
|
||||||
}
|
|
||||||
PipeR => {
|
|
||||||
todo!("new call");
|
|
||||||
// self.emit_expr(lhs);
|
|
||||||
// self.emit_expr(rhs);
|
|
||||||
// self.emit_op(Op::Call);
|
|
||||||
}
|
|
||||||
_ => {
|
_ => {
|
||||||
self.emit_expr(lhs);
|
self.emit_expr(lhs);
|
||||||
self.emit_expr(rhs);
|
self.emit_expr(rhs);
|
||||||
@@ -734,17 +725,13 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emit_func(
|
fn emit_func<'ir>(
|
||||||
&mut self,
|
&mut self,
|
||||||
thunks: &[(ThunkId, RawIrRef<'_>)],
|
thunks: &[(ThunkId, RawIrRef<'ir>)],
|
||||||
param: &Option<Param<'_>>,
|
param: &Option<Param<'ir>>,
|
||||||
body: RawIrRef<'_>,
|
body: RawIrRef<'ir>,
|
||||||
) {
|
) {
|
||||||
let with_thunk_count = self.count_with_thunks(body);
|
let thunk_ids: Vec<ThunkId> = thunks.iter().map(|&(id, _)| id).collect();
|
||||||
let total_slots = thunks.len() + with_thunk_count;
|
|
||||||
|
|
||||||
let all_thunks = self.collect_all_thunks(thunks, body);
|
|
||||||
let thunk_ids: Vec<ThunkId> = all_thunks.iter().map(|&(id, _)| id).collect();
|
|
||||||
|
|
||||||
let skip_patch = self.emit_jump_placeholder();
|
let skip_patch = self.emit_jump_placeholder();
|
||||||
let entry_point = self.ctx.get_code().len() as u32;
|
let entry_point = self.ctx.get_code().len() as u32;
|
||||||
@@ -763,10 +750,10 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
{
|
{
|
||||||
self.emit_op(Op::MakePatternClosure);
|
self.emit_op(Op::MakePatternClosure);
|
||||||
self.emit_u32(entry_point);
|
self.emit_u32(entry_point);
|
||||||
self.emit_u32(total_slots as u32);
|
self.emit_u32(thunks.len().try_into().expect("too many thunks"));
|
||||||
self.emit_u16(required.len() as u16);
|
self.emit_u16(required.len() as u16);
|
||||||
self.emit_u16(optional.len() as u16);
|
self.emit_u16(optional.len() as u16);
|
||||||
self.emit_u8(if *ellipsis { 1 } else { 0 });
|
self.emit_bool(*ellipsis);
|
||||||
|
|
||||||
for &(sym, _) in required.iter() {
|
for &(sym, _) in required.iter() {
|
||||||
self.emit_str_id(sym);
|
self.emit_str_id(sym);
|
||||||
@@ -782,39 +769,39 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
} else {
|
} else {
|
||||||
self.emit_op(Op::MakeClosure);
|
self.emit_op(Op::MakeClosure);
|
||||||
self.emit_u32(entry_point);
|
self.emit_u32(entry_point);
|
||||||
self.emit_u32(total_slots as u32);
|
self.emit_u32(thunks.len().try_into().expect("too many thunks"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emit_attrset(
|
fn emit_attrset(
|
||||||
&mut self,
|
&mut self,
|
||||||
stcs: &fix_ir::HashMap<'_, StringId, (MaybeThunk, TextRange)>,
|
stcs: &fix_ir::HashMap<'_, StringId, (&MaybeThunk, TextRange)>,
|
||||||
dyns: &[(RawIrRef<'_>, MaybeThunk, TextRange)],
|
dyns: &[(RawIrRef<'_>, &MaybeThunk, TextRange)],
|
||||||
) {
|
) {
|
||||||
if stcs.is_empty() && dyns.is_empty() {
|
if stcs.is_empty() && dyns.is_empty() {
|
||||||
self.emit_op(Op::MakeEmptyAttrs);
|
self.emit_op(Op::MakeEmptyAttrs);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let total = stcs.len() + dyns.len();
|
for &(key_expr, _val, _span) in dyns.iter() {
|
||||||
|
self.emit_expr(key_expr);
|
||||||
|
}
|
||||||
|
|
||||||
self.emit_op(Op::MakeAttrs);
|
self.emit_op(Op::MakeAttrs);
|
||||||
self.emit_u32(total as u32);
|
self.emit_u32(stcs.len() as u32);
|
||||||
|
self.emit_u32(dyns.len() as u32);
|
||||||
|
|
||||||
for (&sym, &(val, span)) in stcs.iter() {
|
for (&sym, &(val, span)) in stcs.iter() {
|
||||||
self.emit_u8(AttrKeyType::Static as u8);
|
|
||||||
self.emit_str_id(sym);
|
self.emit_str_id(sym);
|
||||||
self.emit_maybe_thunk(val);
|
self.emit_maybe_thunk(val);
|
||||||
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() {
|
|
||||||
todo!("redesign dynamic attr key");
|
for &(_key, val, span) in dyns.iter() {
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -909,19 +896,6 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
self.emit_op(Op::HasAttrResolve);
|
self.emit_op(Op::HasAttrResolve);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emit_with(
|
|
||||||
&mut self,
|
|
||||||
namespace: MaybeThunk,
|
|
||||||
body: RawIrRef<'_>,
|
|
||||||
thunks: &[(ThunkId, RawIrRef<'_>)],
|
|
||||||
) {
|
|
||||||
self.emit_op(Op::PushWith);
|
|
||||||
self.emit_maybe_thunk(namespace);
|
|
||||||
self.emit_scope_thunks(thunks);
|
|
||||||
self.emit_expr(body);
|
|
||||||
self.emit_op(Op::PopWith);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn emit_toplevel_inner(&mut self, body: RawIrRef<'_>, thunks: &[(ThunkId, RawIrRef<'_>)]) {
|
fn emit_toplevel_inner(&mut self, body: RawIrRef<'_>, thunks: &[(ThunkId, RawIrRef<'_>)]) {
|
||||||
self.emit_scope_thunks(thunks);
|
self.emit_scope_thunks(thunks);
|
||||||
self.emit_expr(body);
|
self.emit_expr(body);
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
+160
-68
@@ -39,12 +39,12 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>, T, E: std::fmt::Display>
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait DowngradeContext<'id: 'ir, 'ir> {
|
pub trait DowngradeContext<'id: 'ir, 'ir> {
|
||||||
fn new_expr(&self, expr: Ir<'ir, IrRef<'id, 'ir>>) -> IrRef<'id, 'ir>;
|
fn new_expr(&self, expr: Ir<'ir, GhostRoRef<'id, 'ir>>) -> GhostRoIrRef<'id, 'ir>;
|
||||||
fn maybe_thunk(&mut self, ir: IrRef<'id, 'ir>) -> MaybeThunk;
|
fn maybe_thunk(&mut self, ir: GhostRoIrRef<'id, 'ir>) -> GhostRoMaybeThunkRef<'id, 'ir>;
|
||||||
|
|
||||||
fn intern_string(&mut self, sym: impl AsRef<str>) -> StringId;
|
fn intern_string(&mut self, sym: impl AsRef<str>) -> StringId;
|
||||||
fn resolve_sym(&self, id: StringId) -> Symbol<'_>;
|
fn resolve_sym(&self, id: StringId) -> Symbol<'_>;
|
||||||
fn lookup(&self, sym: StringId, span: TextRange) -> Result<MaybeThunk>;
|
fn lookup(&mut self, sym: StringId, span: TextRange) -> Result<GhostRoMaybeThunkRef<'id, 'ir>>;
|
||||||
|
|
||||||
fn get_current_source(&self) -> Source;
|
fn get_current_source(&self) -> Source;
|
||||||
|
|
||||||
@@ -53,11 +53,11 @@ pub trait DowngradeContext<'id: 'ir, 'ir> {
|
|||||||
F: FnOnce(&mut Self) -> R;
|
F: FnOnce(&mut Self) -> R;
|
||||||
fn with_let_scope<F, R>(&mut self, bindings: &[StringId], f: F) -> Result<R>
|
fn with_let_scope<F, R>(&mut self, bindings: &[StringId], f: F) -> Result<R>
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut Self) -> Result<(Vec<'ir, IrRef<'id, 'ir>>, R)>;
|
F: FnOnce(&mut Self) -> Result<(Vec<'ir, GhostRoMaybeThunkRef<'id, 'ir>>, R)>;
|
||||||
fn with_with_scope<F, R>(&mut self, f: F) -> R
|
fn with_with_scope<F, R>(&mut self, namespace: GhostRoMaybeThunkRef<'id, 'ir>, f: F) -> R
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut Self) -> R;
|
F: FnOnce(&mut Self) -> R;
|
||||||
fn with_thunk_scope<F, R>(&mut self, f: F) -> (R, Vec<'ir, (ThunkId, IrRef<'id, 'ir>)>)
|
fn with_thunk_scope<F, R>(&mut self, f: F) -> (R, Vec<'ir, (ThunkId, GhostRoIrRef<'id, 'ir>)>)
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut Self) -> R;
|
F: FnOnce(&mut Self) -> R;
|
||||||
|
|
||||||
@@ -65,11 +65,11 @@ pub trait DowngradeContext<'id: 'ir, 'ir> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait Downgrade<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> {
|
pub trait Downgrade<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>>;
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for Expr {
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for Expr {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||||
use Expr::*;
|
use Expr::*;
|
||||||
match self {
|
match self {
|
||||||
Apply(apply) => apply.downgrade(ctx),
|
Apply(apply) => apply.downgrade(ctx),
|
||||||
@@ -98,7 +98,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
|||||||
AttrSet(attrs) => attrs.downgrade(ctx),
|
AttrSet(attrs) => attrs.downgrade(ctx),
|
||||||
UnaryOp(op) => op.downgrade(ctx),
|
UnaryOp(op) => op.downgrade(ctx),
|
||||||
Ident(ident) => ident.downgrade(ctx),
|
Ident(ident) => ident.downgrade(ctx),
|
||||||
CurPos(curpos) => Ok(ctx.new_expr(Ir::CurPos(curpos.syntax().text_range()))),
|
CurPos(curpos) => curpos.downgrade(ctx),
|
||||||
With(with) => with.downgrade(ctx),
|
With(with) => with.downgrade(ctx),
|
||||||
HasAttr(has) => has.downgrade(ctx),
|
HasAttr(has) => has.downgrade(ctx),
|
||||||
Paren(paren) => paren
|
Paren(paren) => paren
|
||||||
@@ -114,7 +114,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Assert {
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Assert {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||||
let span = self.syntax().text_range();
|
let span = self.syntax().text_range();
|
||||||
let assertion = self.condition().require(ctx, span)?;
|
let assertion = self.condition().require(ctx, span)?;
|
||||||
let assertion_raw = assertion.to_string();
|
let assertion_raw = assertion.to_string();
|
||||||
@@ -130,7 +130,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::IfElse {
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::IfElse {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||||
let span = self.syntax().text_range();
|
let span = self.syntax().text_range();
|
||||||
let cond = self.condition().require(ctx, span)?.downgrade(ctx)?;
|
let cond = self.condition().require(ctx, span)?.downgrade(ctx)?;
|
||||||
let consq = self.body().require(ctx, span)?.downgrade(ctx)?;
|
let consq = self.body().require(ctx, span)?.downgrade(ctx)?;
|
||||||
@@ -142,7 +142,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
|||||||
macro_rules! path {
|
macro_rules! path {
|
||||||
($ty:ident) => {
|
($ty:ident) => {
|
||||||
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::$ty {
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::$ty {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||||
downgrade_path(self.parts(), ctx)
|
downgrade_path(self.parts(), ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -152,7 +152,7 @@ path!(PathAbs);
|
|||||||
path!(PathRel);
|
path!(PathRel);
|
||||||
path!(PathHome);
|
path!(PathHome);
|
||||||
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::PathSearch {
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::PathSearch {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||||
let span = self.syntax().text_range();
|
let span = self.syntax().text_range();
|
||||||
let path = {
|
let path = {
|
||||||
let temp = self.content().require(ctx, span)?;
|
let temp = self.content().require(ctx, span)?;
|
||||||
@@ -180,7 +180,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Str {
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Str {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||||
let normalized = self.normalized_parts();
|
let normalized = self.normalized_parts();
|
||||||
let is_single_literal = normalized.len() == 1
|
let is_single_literal = normalized.len() == 1
|
||||||
&& matches!(normalized.first(), Some(ast::InterpolPart::Literal(_)));
|
&& matches!(normalized.first(), Some(ast::InterpolPart::Literal(_)));
|
||||||
@@ -210,7 +210,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Literal {
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Literal {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||||
let span = self.syntax().text_range();
|
let span = self.syntax().text_range();
|
||||||
let expr = match self.kind() {
|
let expr = match self.kind() {
|
||||||
ast::LiteralKind::Integer(int) => Ir::Int(int.value().require(ctx, span)?),
|
ast::LiteralKind::Integer(int) => Ir::Int(int.value().require(ctx, span)?),
|
||||||
@@ -225,16 +225,73 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Ident {
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Ident {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||||
let span = self.syntax().text_range();
|
let span = self.syntax().text_range();
|
||||||
let text = self.ident_token().require(ctx, span)?.to_string();
|
let text = self.ident_token().require(ctx, span)?.to_string();
|
||||||
let sym = ctx.intern_string(text);
|
let sym = ctx.intern_string(text);
|
||||||
ctx.lookup(sym, span).map(|thunk| thunk.to_ir(ctx))
|
ctx.lookup(sym, span)
|
||||||
|
.map(|thunk| ctx.new_expr(Ir::MaybeThunk(thunk)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::CurPos {
|
||||||
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||||
|
fn byte_offset_to_line_col(content: &str, offset: usize) -> (u32, u32) {
|
||||||
|
let mut line = 1u32;
|
||||||
|
let mut col = 1u32;
|
||||||
|
|
||||||
|
for (idx, ch) in content.char_indices() {
|
||||||
|
if idx >= offset {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ch == '\n' {
|
||||||
|
line += 1;
|
||||||
|
col = 1;
|
||||||
|
} else {
|
||||||
|
col += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(line, col)
|
||||||
|
}
|
||||||
|
|
||||||
|
let span = self.syntax().text_range();
|
||||||
|
let source = ctx.get_current_source();
|
||||||
|
let (line, column) = byte_offset_to_line_col(&source.src, span.start().into());
|
||||||
|
|
||||||
|
let file_sym = ctx.intern_string("file");
|
||||||
|
let line_sym = ctx.intern_string("line");
|
||||||
|
let column_sym = ctx.intern_string("column");
|
||||||
|
|
||||||
|
let file: GhostRoMaybeThunkRef = ctx
|
||||||
|
.bump()
|
||||||
|
.alloc(GhostCell::new(MaybeThunk::Str(ctx.intern_string(source.get_name()))).into());
|
||||||
|
let line = ctx
|
||||||
|
.bump()
|
||||||
|
.alloc(GhostCell::new(MaybeThunk::Int(i64::from(line))).into());
|
||||||
|
let column = ctx
|
||||||
|
.bump()
|
||||||
|
.alloc(GhostCell::new(MaybeThunk::Int(i64::from(column))).into());
|
||||||
|
|
||||||
|
let map = {
|
||||||
|
let mut map = HashMap::new_in(ctx.bump());
|
||||||
|
|
||||||
|
map.insert(file_sym, (file, TextRange::default()));
|
||||||
|
map.insert(line_sym, (line, TextRange::default()));
|
||||||
|
map.insert(column_sym, (column, TextRange::default()));
|
||||||
|
|
||||||
|
map
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(ctx.new_expr(Ir::AttrSet {
|
||||||
|
stcs: map,
|
||||||
|
dyns: Vec::new_in(ctx.bump()),
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::AttrSet {
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::AttrSet {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||||
let rec = self.rec_token().is_some();
|
let rec = self.rec_token().is_some();
|
||||||
if !rec {
|
if !rec {
|
||||||
let attrs = downgrade_attrs(self, ctx)?;
|
let attrs = downgrade_attrs(self, ctx)?;
|
||||||
@@ -250,7 +307,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::List {
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::List {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||||
let bump = ctx.bump();
|
let bump = ctx.bump();
|
||||||
let items = self
|
let items = self
|
||||||
.items()
|
.items()
|
||||||
@@ -264,17 +321,52 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::BinOp {
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::BinOp {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||||
|
use BinOpKind::*;
|
||||||
|
use ast::BinOpKind as Kind;
|
||||||
|
|
||||||
let span = self.syntax().text_range();
|
let span = self.syntax().text_range();
|
||||||
let lhs = self.lhs().require(ctx, span)?.downgrade(ctx)?;
|
let lhs = self.lhs().require(ctx, span)?.downgrade(ctx)?;
|
||||||
let rhs = self.rhs().require(ctx, span)?.downgrade(ctx)?;
|
let rhs = self.rhs().require(ctx, span)?.downgrade(ctx)?;
|
||||||
let kind = self.operator().require(ctx, span)?.into();
|
let kind = match self.operator().require(ctx, span)? {
|
||||||
|
Kind::Concat => Con,
|
||||||
|
Kind::Update => Upd,
|
||||||
|
Kind::Add => Add,
|
||||||
|
Kind::Sub => Sub,
|
||||||
|
Kind::Mul => Mul,
|
||||||
|
Kind::Div => Div,
|
||||||
|
Kind::And => And,
|
||||||
|
Kind::Equal => Eq,
|
||||||
|
Kind::Implication => Impl,
|
||||||
|
Kind::Less => Lt,
|
||||||
|
Kind::LessOrEq => Leq,
|
||||||
|
Kind::More => Gt,
|
||||||
|
Kind::MoreOrEq => Geq,
|
||||||
|
Kind::NotEqual => Neq,
|
||||||
|
Kind::Or => Or,
|
||||||
|
Kind::PipeLeft => {
|
||||||
|
let arg = ctx.maybe_thunk(rhs);
|
||||||
|
return Ok(ctx.new_expr(Ir::Call {
|
||||||
|
func: lhs,
|
||||||
|
arg,
|
||||||
|
span,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
Kind::PipeRight => {
|
||||||
|
let arg = ctx.maybe_thunk(lhs);
|
||||||
|
return Ok(ctx.new_expr(Ir::Call {
|
||||||
|
func: rhs,
|
||||||
|
arg,
|
||||||
|
span,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
};
|
||||||
Ok(ctx.new_expr(Ir::BinOp { lhs, rhs, kind }))
|
Ok(ctx.new_expr(Ir::BinOp { lhs, rhs, kind }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::HasAttr {
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::HasAttr {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||||
let span = self.syntax().text_range();
|
let span = self.syntax().text_range();
|
||||||
let lhs = self.expr().require(ctx, span)?.downgrade(ctx)?;
|
let lhs = self.expr().require(ctx, span)?.downgrade(ctx)?;
|
||||||
let rhs = downgrade_attrpath(self.attrpath().require(ctx, span)?, ctx)?;
|
let rhs = downgrade_attrpath(self.attrpath().require(ctx, span)?, ctx)?;
|
||||||
@@ -283,7 +375,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::UnaryOp {
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::UnaryOp {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||||
let span = self.syntax().text_range();
|
let span = self.syntax().text_range();
|
||||||
let rhs = self.expr().require(ctx, span)?.downgrade(ctx)?;
|
let rhs = self.expr().require(ctx, span)?.downgrade(ctx)?;
|
||||||
let kind = self.operator().require(ctx, span)?.into();
|
let kind = self.operator().require(ctx, span)?.into();
|
||||||
@@ -292,7 +384,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Select {
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Select {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||||
let span = self.syntax().text_range();
|
let span = self.syntax().text_range();
|
||||||
let expr = self.expr().require(ctx, span)?.downgrade(ctx)?;
|
let expr = self.expr().require(ctx, span)?.downgrade(ctx)?;
|
||||||
let attrpath = downgrade_attrpath(self.attrpath().require(ctx, span)?, ctx)?;
|
let attrpath = downgrade_attrpath(self.attrpath().require(ctx, span)?, ctx)?;
|
||||||
@@ -311,7 +403,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::LegacyLet {
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::LegacyLet {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||||
let span = self.syntax().text_range();
|
let span = self.syntax().text_range();
|
||||||
let entries: Vec<'ir, _> = self.entries().collect_in(ctx.bump());
|
let entries: Vec<'ir, _> = self.entries().collect_in(ctx.bump());
|
||||||
let attrset_expr = downgrade_let_bindings(entries, ctx, |ctx, binding_keys| {
|
let attrset_expr = downgrade_let_bindings(entries, ctx, |ctx, binding_keys| {
|
||||||
@@ -340,7 +432,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::LetIn {
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::LetIn {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||||
let entries: Vec<'ir, _> = self.entries().collect_in(ctx.bump());
|
let entries: Vec<'ir, _> = self.entries().collect_in(ctx.bump());
|
||||||
let span = self.syntax().text_range();
|
let span = self.syntax().text_range();
|
||||||
let body_expr = self.body().require(ctx, span)?;
|
let body_expr = self.body().require(ctx, span)?;
|
||||||
@@ -350,33 +442,25 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::With {
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::With {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||||
let span = self.syntax().text_range();
|
let span = self.syntax().text_range();
|
||||||
let namespace = self.namespace().require(ctx, span)?.downgrade(ctx)?;
|
let namespace = self.namespace().require(ctx, span)?.downgrade(ctx)?;
|
||||||
let namespace = ctx.maybe_thunk(namespace);
|
let namespace = ctx.maybe_thunk(namespace);
|
||||||
|
|
||||||
let body_expr = self.body().require(ctx, span)?;
|
let body_expr = self.body().require(ctx, span)?;
|
||||||
let (body, thunks) =
|
ctx.with_with_scope(namespace, |ctx| body_expr.downgrade(ctx))
|
||||||
ctx.with_thunk_scope(|ctx| ctx.with_with_scope(|ctx| body_expr.downgrade(ctx)));
|
|
||||||
let body = body?;
|
|
||||||
|
|
||||||
Ok(ctx.new_expr(Ir::With {
|
|
||||||
namespace,
|
|
||||||
body,
|
|
||||||
thunks,
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Lambda {
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Lambda {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||||
let span = self.syntax().text_range();
|
let span = self.syntax().text_range();
|
||||||
let raw_param = self.param().require(ctx, span)?;
|
let raw_param = self.param().require(ctx, span)?;
|
||||||
let body_ast = self.body().require(ctx, span)?;
|
let body_ast = self.body().require(ctx, span)?;
|
||||||
|
|
||||||
struct Ret<'id, 'ir> {
|
struct Ret<'id, 'ir> {
|
||||||
param: Option<Param<'ir>>,
|
param: Option<Param<'ir>>,
|
||||||
body: IrRef<'id, 'ir>,
|
body: GhostRoIrRef<'id, 'ir>,
|
||||||
}
|
}
|
||||||
|
|
||||||
let (ret, thunks) = ctx.with_thunk_scope(|ctx| {
|
let (ret, thunks) = ctx.with_thunk_scope(|ctx| {
|
||||||
@@ -433,7 +517,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Apply {
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Apply {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||||
let span = self.syntax().text_range();
|
let span = self.syntax().text_range();
|
||||||
let func = self.lambda().require(ctx, span)?.downgrade(ctx)?;
|
let func = self.lambda().require(ctx, span)?.downgrade(ctx)?;
|
||||||
let arg = self.argument().require(ctx, span)?.downgrade(ctx)?;
|
let arg = self.argument().require(ctx, span)?.downgrade(ctx)?;
|
||||||
@@ -835,8 +919,15 @@ fn make_attrpath_value_entry<'ir>(path: Vec<'ir, ast::Attr>, value: ast::Expr) -
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct FinalizedAttrSet<'id, 'ir> {
|
struct FinalizedAttrSet<'id, 'ir> {
|
||||||
stcs: HashMap<'ir, StringId, (MaybeThunk, TextRange)>,
|
stcs: HashMap<'ir, StringId, (GhostRoMaybeThunkRef<'id, 'ir>, TextRange)>,
|
||||||
dyns: Vec<'ir, (IrRef<'id, 'ir>, MaybeThunk, TextRange)>,
|
dyns: Vec<
|
||||||
|
'ir,
|
||||||
|
(
|
||||||
|
GhostRoIrRef<'id, 'ir>,
|
||||||
|
GhostRoMaybeThunkRef<'id, 'ir>,
|
||||||
|
TextRange,
|
||||||
|
),
|
||||||
|
>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn downgrade_attrs<'id, 'ir>(
|
fn downgrade_attrs<'id, 'ir>(
|
||||||
@@ -851,7 +942,7 @@ fn downgrade_attrs<'id, 'ir>(
|
|||||||
fn downgrade_attr<'id, 'ir>(
|
fn downgrade_attr<'id, 'ir>(
|
||||||
attr: ast::Attr,
|
attr: ast::Attr,
|
||||||
ctx: &mut impl DowngradeContext<'id, 'ir>,
|
ctx: &mut impl DowngradeContext<'id, 'ir>,
|
||||||
) -> Result<Attr<IrRef<'id, 'ir>>> {
|
) -> Result<Attr<GhostRoIrRef<'id, 'ir>>> {
|
||||||
use ast::Attr::*;
|
use ast::Attr::*;
|
||||||
use ast::InterpolPart::*;
|
use ast::InterpolPart::*;
|
||||||
match attr {
|
match attr {
|
||||||
@@ -912,7 +1003,7 @@ fn downgrade_attr<'id, 'ir>(
|
|||||||
fn downgrade_attrpath<'id, 'ir>(
|
fn downgrade_attrpath<'id, 'ir>(
|
||||||
attrpath: ast::Attrpath,
|
attrpath: ast::Attrpath,
|
||||||
ctx: &mut impl DowngradeContext<'id, 'ir>,
|
ctx: &mut impl DowngradeContext<'id, 'ir>,
|
||||||
) -> Result<Vec<'ir, Attr<IrRef<'id, 'ir>>>> {
|
) -> Result<Vec<'ir, Attr<GhostRoIrRef<'id, 'ir>>>> {
|
||||||
let bump = ctx.bump();
|
let bump = ctx.bump();
|
||||||
attrpath
|
attrpath
|
||||||
.attrs()
|
.attrs()
|
||||||
@@ -921,7 +1012,7 @@ fn downgrade_attrpath<'id, 'ir>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct PatternBindings<'id, 'ir> {
|
struct PatternBindings<'id, 'ir> {
|
||||||
body: IrRef<'id, 'ir>,
|
body: GhostRoIrRef<'id, 'ir>,
|
||||||
required: Vec<'ir, (StringId, TextRange)>,
|
required: Vec<'ir, (StringId, TextRange)>,
|
||||||
optional: Vec<'ir, (StringId, TextRange)>,
|
optional: Vec<'ir, (StringId, TextRange)>,
|
||||||
}
|
}
|
||||||
@@ -930,7 +1021,7 @@ fn downgrade_pattern_bindings<'id, 'ir, Ctx>(
|
|||||||
pat_entries: impl Iterator<Item = ast::PatEntry>,
|
pat_entries: impl Iterator<Item = ast::PatEntry>,
|
||||||
alias: Option<StringId>,
|
alias: Option<StringId>,
|
||||||
ctx: &mut Ctx,
|
ctx: &mut Ctx,
|
||||||
body_fn: impl FnOnce(&mut Ctx, &[StringId]) -> Result<IrRef<'id, 'ir>>,
|
body_fn: impl FnOnce(&mut Ctx, &[StringId]) -> Result<GhostRoIrRef<'id, 'ir>>,
|
||||||
) -> Result<PatternBindings<'id, 'ir>>
|
) -> Result<PatternBindings<'id, 'ir>>
|
||||||
where
|
where
|
||||||
Ctx: DowngradeContext<'id, 'ir>,
|
Ctx: DowngradeContext<'id, 'ir>,
|
||||||
@@ -989,6 +1080,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
let arg = ctx.new_expr(Ir::Arg { layer: 0 });
|
let arg = ctx.new_expr(Ir::Arg { layer: 0 });
|
||||||
|
let arg_thunk = ctx.maybe_thunk(arg);
|
||||||
ctx.with_let_scope(&keys, |ctx| {
|
ctx.with_let_scope(&keys, |ctx| {
|
||||||
let vals = params
|
let vals = params
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -1000,21 +1092,16 @@ where
|
|||||||
span,
|
span,
|
||||||
} = param;
|
} = param;
|
||||||
let default = default.map(|default| default.downgrade(ctx)).transpose()?;
|
let default = default.map(|default| default.downgrade(ctx)).transpose()?;
|
||||||
// let default = if let Some(default) = default {
|
|
||||||
// let default = default.clone().downgrade(ctx)?;
|
|
||||||
// Some(ctx.maybe_thunk(default))
|
|
||||||
// } else {
|
|
||||||
// None
|
|
||||||
// };
|
|
||||||
|
|
||||||
Ok(ctx.new_expr(Ir::Select {
|
let expr = ctx.new_expr(Ir::Select {
|
||||||
expr: arg,
|
expr: arg,
|
||||||
attrpath: Vec::from_iter_in([Attr::Str(sym, sym_span)], bump),
|
attrpath: Vec::from_iter_in([Attr::Str(sym, sym_span)], bump),
|
||||||
default,
|
default,
|
||||||
span,
|
span,
|
||||||
}))
|
});
|
||||||
|
Ok(ctx.maybe_thunk(expr))
|
||||||
})
|
})
|
||||||
.chain(alias.into_iter().map(|_| Ok(arg)))
|
.chain(alias.into_iter().map(|_| Ok(arg_thunk)))
|
||||||
.collect_in::<Result<_>>(bump)?;
|
.collect_in::<Result<_>>(bump)?;
|
||||||
|
|
||||||
let body = body_fn(ctx, &keys)?;
|
let body = body_fn(ctx, &keys)?;
|
||||||
@@ -1034,10 +1121,10 @@ fn downgrade_let_bindings<'id, 'ir, Ctx, F>(
|
|||||||
entries: Vec<'ir, ast::Entry>,
|
entries: Vec<'ir, ast::Entry>,
|
||||||
ctx: &mut Ctx,
|
ctx: &mut Ctx,
|
||||||
body_fn: F,
|
body_fn: F,
|
||||||
) -> Result<IrRef<'id, 'ir>>
|
) -> Result<GhostRoIrRef<'id, 'ir>>
|
||||||
where
|
where
|
||||||
Ctx: DowngradeContext<'id, 'ir>,
|
Ctx: DowngradeContext<'id, 'ir>,
|
||||||
F: FnOnce(&mut Ctx, &[StringId]) -> Result<IrRef<'id, 'ir>>,
|
F: FnOnce(&mut Ctx, &[StringId]) -> Result<GhostRoIrRef<'id, 'ir>>,
|
||||||
{
|
{
|
||||||
downgrade_rec_attrs_impl::<_, _, false>(entries, ctx, |ctx, binding_keys, _dyns| {
|
downgrade_rec_attrs_impl::<_, _, false>(entries, ctx, |ctx, binding_keys, _dyns| {
|
||||||
body_fn(ctx, binding_keys)
|
body_fn(ctx, binding_keys)
|
||||||
@@ -1047,7 +1134,7 @@ where
|
|||||||
fn downgrade_rec_bindings<'id, 'ir, Ctx>(
|
fn downgrade_rec_bindings<'id, 'ir, Ctx>(
|
||||||
entries: Vec<'ir, ast::Entry>,
|
entries: Vec<'ir, ast::Entry>,
|
||||||
ctx: &mut Ctx,
|
ctx: &mut Ctx,
|
||||||
) -> Result<IrRef<'id, 'ir>>
|
) -> Result<GhostRoIrRef<'id, 'ir>>
|
||||||
where
|
where
|
||||||
Ctx: DowngradeContext<'id, 'ir>,
|
Ctx: DowngradeContext<'id, 'ir>,
|
||||||
{
|
{
|
||||||
@@ -1068,14 +1155,18 @@ fn downgrade_rec_attrs_impl<'id, 'ir, Ctx, F, const ALLOW_DYN: bool>(
|
|||||||
entries: Vec<'ir, ast::Entry>,
|
entries: Vec<'ir, ast::Entry>,
|
||||||
ctx: &mut Ctx,
|
ctx: &mut Ctx,
|
||||||
body_fn: F,
|
body_fn: F,
|
||||||
) -> Result<IrRef<'id, 'ir>>
|
) -> Result<GhostRoIrRef<'id, 'ir>>
|
||||||
where
|
where
|
||||||
Ctx: DowngradeContext<'id, 'ir>,
|
Ctx: DowngradeContext<'id, 'ir>,
|
||||||
F: FnOnce(
|
F: FnOnce(
|
||||||
&mut Ctx,
|
&mut Ctx,
|
||||||
&[StringId],
|
&[StringId],
|
||||||
&[(IrRef<'id, 'ir>, MaybeThunk, TextRange)],
|
&[(
|
||||||
) -> Result<IrRef<'id, 'ir>>,
|
GhostRoIrRef<'id, 'ir>,
|
||||||
|
GhostRoMaybeThunkRef<'id, 'ir>,
|
||||||
|
TextRange,
|
||||||
|
)],
|
||||||
|
) -> Result<GhostRoIrRef<'id, 'ir>>,
|
||||||
{
|
{
|
||||||
let mut pending = PendingAttrSet::new_in(ctx.bump());
|
let mut pending = PendingAttrSet::new_in(ctx.bump());
|
||||||
pending.collect_entries(entries.iter().cloned(), ctx)?;
|
pending.collect_entries(entries.iter().cloned(), ctx)?;
|
||||||
@@ -1090,7 +1181,7 @@ where
|
|||||||
let vals = {
|
let vals = {
|
||||||
let mut temp = Vec::with_capacity_in(keys.len(), ctx.bump());
|
let mut temp = Vec::with_capacity_in(keys.len(), ctx.bump());
|
||||||
for sym in &keys {
|
for sym in &keys {
|
||||||
temp.push(finalized.stcs.get(sym).expect("WTF").0.to_ir(ctx));
|
temp.push(finalized.stcs.get(sym).expect("WTF").0);
|
||||||
}
|
}
|
||||||
temp
|
temp
|
||||||
};
|
};
|
||||||
@@ -1101,7 +1192,7 @@ where
|
|||||||
fn collect_inherit_lookups<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>>(
|
fn collect_inherit_lookups<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>>(
|
||||||
entries: &[ast::Entry],
|
entries: &[ast::Entry],
|
||||||
ctx: &mut Ctx,
|
ctx: &mut Ctx,
|
||||||
) -> Result<HashMap<'ir, StringId, (MaybeThunk, TextRange)>> {
|
) -> Result<HashMap<'ir, StringId, (GhostRoMaybeThunkRef<'id, 'ir>, TextRange)>> {
|
||||||
let mut inherit_lookups = HashMap::new_in(ctx.bump());
|
let mut inherit_lookups = HashMap::new_in(ctx.bump());
|
||||||
for entry in entries {
|
for entry in entries {
|
||||||
if let ast::Entry::Inherit(inherit) = entry
|
if let ast::Entry::Inherit(inherit) = entry
|
||||||
@@ -1141,7 +1232,7 @@ fn collect_binding_syms<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>, const AL
|
|||||||
|
|
||||||
fn finalize_pending_set<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW_DYN: bool>(
|
fn finalize_pending_set<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW_DYN: bool>(
|
||||||
pending: PendingAttrSet,
|
pending: PendingAttrSet,
|
||||||
inherit_lookups: &HashMap<StringId, (MaybeThunk, TextRange)>,
|
inherit_lookups: &HashMap<StringId, (GhostRoMaybeThunkRef<'id, 'ir>, TextRange)>,
|
||||||
ctx: &mut Ctx,
|
ctx: &mut Ctx,
|
||||||
) -> Result<FinalizedAttrSet<'id, 'ir>> {
|
) -> Result<FinalizedAttrSet<'id, 'ir>> {
|
||||||
let mut stcs = HashMap::new_in(ctx.bump());
|
let mut stcs = HashMap::new_in(ctx.bump());
|
||||||
@@ -1169,9 +1260,9 @@ fn finalize_pending_set<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW_D
|
|||||||
|
|
||||||
fn finalize_pending_value<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW_DYN: bool>(
|
fn finalize_pending_value<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW_DYN: bool>(
|
||||||
value: PendingValue,
|
value: PendingValue,
|
||||||
inherit_lookups: &HashMap<StringId, (MaybeThunk, TextRange)>,
|
inherit_lookups: &HashMap<StringId, (GhostRoMaybeThunkRef<'id, 'ir>, TextRange)>,
|
||||||
ctx: &mut Ctx,
|
ctx: &mut Ctx,
|
||||||
) -> Result<IrRef<'id, 'ir>> {
|
) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||||
match value {
|
match value {
|
||||||
PendingValue::Expr(expr) => expr.downgrade(ctx),
|
PendingValue::Expr(expr) => expr.downgrade(ctx),
|
||||||
PendingValue::InheritFrom(from_expr, sym, span) => {
|
PendingValue::InheritFrom(from_expr, sym, span) => {
|
||||||
@@ -1185,9 +1276,10 @@ fn finalize_pending_value<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW
|
|||||||
}
|
}
|
||||||
PendingValue::InheritScope(sym, span) => {
|
PendingValue::InheritScope(sym, span) => {
|
||||||
if let Some(&(expr, _)) = inherit_lookups.get(&sym) {
|
if let Some(&(expr, _)) = inherit_lookups.get(&sym) {
|
||||||
Ok(expr.to_ir(ctx))
|
Ok(ctx.new_expr(Ir::MaybeThunk(expr)))
|
||||||
} else {
|
} else {
|
||||||
ctx.lookup(sym, span).map(|val| val.to_ir(ctx))
|
ctx.lookup(sym, span)
|
||||||
|
.map(|val| ctx.new_expr(Ir::MaybeThunk(val)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PendingValue::Set(set) => {
|
PendingValue::Set(set) => {
|
||||||
@@ -1210,7 +1302,7 @@ fn finalize_pending_value<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW
|
|||||||
fn downgrade_path<'id, 'ir>(
|
fn downgrade_path<'id, 'ir>(
|
||||||
parts: impl IntoIterator<Item = ast::InterpolPart<ast::PathContent>>,
|
parts: impl IntoIterator<Item = ast::InterpolPart<ast::PathContent>>,
|
||||||
ctx: &mut impl DowngradeContext<'id, 'ir>,
|
ctx: &mut impl DowngradeContext<'id, 'ir>,
|
||||||
) -> Result<IrRef<'id, 'ir>> {
|
) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||||
let bump = ctx.bump();
|
let bump = ctx.bump();
|
||||||
let parts = parts
|
let parts = parts
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|||||||
+124
-134
@@ -1,5 +1,5 @@
|
|||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::ops::Deref;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use bumpalo::collections::Vec;
|
use bumpalo::collections::Vec;
|
||||||
@@ -10,56 +10,70 @@ use num_enum::TryFromPrimitive as _;
|
|||||||
use rnix::{TextRange, ast};
|
use rnix::{TextRange, ast};
|
||||||
use string_interner::DefaultStringInterner;
|
use string_interner::DefaultStringInterner;
|
||||||
|
|
||||||
use crate::downgrade::DowngradeContext;
|
|
||||||
|
|
||||||
pub mod downgrade;
|
pub mod downgrade;
|
||||||
|
|
||||||
pub type HashMap<'ir, K, V> = hashbrown::HashMap<K, V, hashbrown::DefaultHashBuilder, &'ir Bump>;
|
pub type HashMap<'ir, K, V> = hashbrown::HashMap<K, V, hashbrown::DefaultHashBuilder, &'ir Bump>;
|
||||||
|
|
||||||
#[repr(transparent)]
|
pub type GhostIrRef<'id, 'ir> = <GhostRef<'id, 'ir> as RefExt<'ir>>::IrRef;
|
||||||
#[derive(Clone, Copy)]
|
pub type GhostRoIrRef<'id, 'ir> = <GhostRoRef<'id, 'ir> as RefExt<'ir>>::IrRef;
|
||||||
pub struct IrRef<'id, 'ir>(&'ir GhostCell<'id, Ir<'ir, Self>>);
|
pub type RawIrRef<'ir> = <RawRef<'ir> as RefExt<'ir>>::IrRef;
|
||||||
|
pub type GhostMaybeThunkRef<'id, 'ir> = <GhostRef<'id, 'ir> as RefExt<'ir>>::MaybeThunkRef;
|
||||||
impl<'id, 'ir> IrRef<'id, 'ir> {
|
pub type GhostRoMaybeThunkRef<'id, 'ir> = <GhostRoRef<'id, 'ir> as RefExt<'ir>>::MaybeThunkRef;
|
||||||
pub fn new(ir: &'ir GhostCell<'id, Ir<'ir, Self>>) -> Self {
|
|
||||||
Self(ir)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn alloc(bump: &'ir Bump, ir: Ir<'ir, Self>) -> Self {
|
|
||||||
Self(bump.alloc(GhostCell::new(ir)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn borrow<'a>(&'a self, token: &'a GhostToken<'id>) -> &'a Ir<'ir, Self> {
|
|
||||||
self.0.borrow(token)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
impl<'id, 'ir> Ir<'ir, GhostRoRef<'id, 'ir>> {
|
||||||
/// Freeze a mutable IR reference into a read-only one, consuming the
|
/// Freeze a mutable IR reference into a read-only one, consuming the
|
||||||
/// `GhostToken` to prevent any further mutation.
|
/// `GhostToken` to prevent any further mutation.
|
||||||
///
|
pub fn freeze(this: GhostRoIrRef<'id, 'ir>, _: GhostToken<'id>) -> RawIrRef<'ir> {
|
||||||
/// # Safety
|
// SAFETY: The transmute is sound because:
|
||||||
/// The transmute is sound because:
|
// - `GhostCell<'id, T>` is `#[repr(transparent)]` over `T`, so
|
||||||
/// - `GhostCell<'id, T>` is `#[repr(transparent)]` over `T`
|
// `&'ir GhostCell<'id, T>` and `&'ir T` have identical layout.
|
||||||
/// - `IrRef<'id, 'ir>` is `#[repr(transparent)]` over
|
// - `Ir<'ir, R>` is `#[repr(C)]`, and for every field that depends on
|
||||||
/// `&'ir GhostCell<'id, Ir<'ir, Self>>`
|
// `R`, instantiating `R = GhostRef<'id, 'ir>` vs `R = RawRef<'ir>`
|
||||||
/// - `RawIrRef<'ir>` is `#[repr(transparent)]` over `&'ir Ir<'ir, Self>`
|
// produces types of identical layout:
|
||||||
/// - `Ir<'ir, Ref>` is `#[repr(C)]` and both ref types are pointer-sized
|
// - `R::IrRef` becomes `&'ir GhostCell<'id, Ir<...>>` vs `&'ir Ir<...>`
|
||||||
///
|
// - `R::MaybeThunkRef` becomes `&'ir GhostCell<'id, MaybeThunk>`
|
||||||
/// Consuming the `GhostToken` guarantees no `borrow_mut` calls can occur
|
// vs `&'ir MaybeThunk`
|
||||||
/// afterwards, so the shared `&Ir` references from `RawIrRef::Deref` can
|
// - `R::Ref<Ir<'ir, R>>` (used in `ConcatStrings::parts`) reduces
|
||||||
/// never alias with mutable references.
|
// to the same case as `R::IrRef`
|
||||||
pub fn freeze(self, _token: GhostToken<'id>) -> RawIrRef<'ir> {
|
// - Therefore `IrRef<'id, 'ir>` and `RawIrRef<'ir>` are both
|
||||||
unsafe { std::mem::transmute(self) }
|
// pointer-sized references with the same layout.
|
||||||
|
//
|
||||||
|
// Consuming the `GhostToken` guarantees no `borrow_mut` calls can
|
||||||
|
// occur afterwards, so the shared `&Ir` references reachable from a
|
||||||
|
// `RawIrRef<'ir>` can never alias with mutable references.
|
||||||
|
unsafe { std::mem::transmute::<GhostRoIrRef<'id, 'ir>, RawIrRef<'ir>>(this) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
#[derive(Clone, Copy, Debug)]
|
pub struct GhostRoCell<'id, T: ?Sized>(GhostCell<'id, T>);
|
||||||
pub struct RawIrRef<'ir>(pub &'ir Ir<'ir, Self>);
|
|
||||||
|
|
||||||
impl<'ir> Deref for RawIrRef<'ir> {
|
impl<'id, T> From<GhostCell<'id, T>> for GhostRoCell<'id, T> {
|
||||||
type Target = Ir<'ir, RawIrRef<'ir>>;
|
fn from(value: GhostCell<'id, T>) -> Self {
|
||||||
fn deref(&self) -> &Self::Target {
|
Self(value)
|
||||||
self.0
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'id, T: ?Sized> From<&GhostCell<'id, T>> for &GhostRoCell<'id, T> {
|
||||||
|
fn from(value: &GhostCell<'id, T>) -> Self {
|
||||||
|
// SAFETY: `GhostRoCell` is `#[repr(transparent)]` over `GhostCell`
|
||||||
|
// TODO: document mutability
|
||||||
|
unsafe { std::mem::transmute(value) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'id, T: ?Sized> From<&T> for &GhostRoCell<'id, T> {
|
||||||
|
fn from(value: &T) -> Self {
|
||||||
|
// SAFETY: `GhostRoCell` is `#[repr(transparent)]` over `GhostCell`,
|
||||||
|
// which is `#[repr(transparent)]` over `T`
|
||||||
|
// TODO: document mutability
|
||||||
|
unsafe { std::mem::transmute(value) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'id, T: ?Sized> GhostRoCell<'id, T> {
|
||||||
|
pub fn borrow<'a>(&'a self, token: &'a GhostToken<'id>) -> &'a T {
|
||||||
|
self.0.borrow(token)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,110 +87,117 @@ pub enum MaybeThunk {
|
|||||||
Str(StringId),
|
Str(StringId),
|
||||||
Path(StringId),
|
Path(StringId),
|
||||||
Thunk(ThunkId),
|
Thunk(ThunkId),
|
||||||
Arg { layer: usize },
|
Arg { layer: u8 },
|
||||||
Builtin(BuiltinId),
|
Builtin(BuiltinId),
|
||||||
|
BuiltinConst(StringId),
|
||||||
Builtins,
|
Builtins,
|
||||||
ReplBinding(StringId),
|
ReplBinding(StringId),
|
||||||
ScopedImportBinding(StringId),
|
ScopedImportBinding(StringId),
|
||||||
WithLookup(StringId),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MaybeThunk {
|
pub trait Ref<'ir> {
|
||||||
fn to_ir<'id, 'ir>(self, ctx: &mut impl DowngradeContext<'id, 'ir>) -> IrRef<'id, 'ir> {
|
type Ref<T>
|
||||||
use MaybeThunk::*;
|
where
|
||||||
let ir = match self {
|
T: 'ir;
|
||||||
Int(x) => Ir::Int(x),
|
|
||||||
Float(x) => Ir::Float(x),
|
|
||||||
Bool(x) => Ir::Bool(x),
|
|
||||||
Null => Ir::Null,
|
|
||||||
Str(x) => Ir::Str(x),
|
|
||||||
Path(x) => Ir::Path(ctx.new_expr(Ir::Str(x))),
|
|
||||||
Thunk(x) => Ir::Thunk(x),
|
|
||||||
Arg { layer } => Ir::Arg { layer },
|
|
||||||
Builtin(x) => Ir::Builtin(x),
|
|
||||||
Builtins => Ir::Builtins,
|
|
||||||
ReplBinding(x) => Ir::ReplBinding(x),
|
|
||||||
ScopedImportBinding(x) => Ir::ScopedImportBinding(x),
|
|
||||||
WithLookup(x) => Ir::WithLookup(x),
|
|
||||||
};
|
|
||||||
ctx.new_expr(ir)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait RefExt<'ir>: Ref<'ir> {
|
||||||
|
type Ir;
|
||||||
|
type IrRef;
|
||||||
|
type MaybeThunkRef;
|
||||||
|
}
|
||||||
|
impl<'ir, T: Ref<'ir> + 'ir> RefExt<'ir> for T {
|
||||||
|
type Ir = Ir<'ir, Self>;
|
||||||
|
type IrRef = Self::Ref<Self::Ir>;
|
||||||
|
type MaybeThunkRef = Self::Ref<MaybeThunk>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GhostRef<'id, 'ir>(PhantomData<&'ir GhostCell<'id, ()>>);
|
||||||
|
pub struct GhostRoRef<'id, 'ir>(PhantomData<&'ir GhostRoCell<'id, ()>>);
|
||||||
|
pub struct RawRef<'ir>(PhantomData<&'ir ()>);
|
||||||
|
|
||||||
|
impl<'id, 'ir> Ref<'ir> for GhostRef<'id, 'ir> {
|
||||||
|
type Ref<T: 'ir> = &'ir GhostCell<'id, T>;
|
||||||
|
}
|
||||||
|
impl<'id, 'ir> Ref<'ir> for GhostRoRef<'id, 'ir> {
|
||||||
|
type Ref<T: 'ir> = &'ir GhostRoCell<'id, T>;
|
||||||
|
}
|
||||||
|
impl<'ir> Ref<'ir> for RawRef<'ir> {
|
||||||
|
type Ref<T: 'ir> = &'ir T;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Ir<'ir, Ref> {
|
pub enum Ir<'ir, R: RefExt<'ir> + ?Sized + 'ir> {
|
||||||
Int(i64),
|
Int(i64),
|
||||||
Float(f64),
|
Float(f64),
|
||||||
Bool(bool),
|
Bool(bool),
|
||||||
Null,
|
Null,
|
||||||
Str(StringId),
|
Str(StringId),
|
||||||
Path(Ref),
|
Path(R::IrRef),
|
||||||
AttrSet {
|
AttrSet {
|
||||||
stcs: HashMap<'ir, StringId, (MaybeThunk, TextRange)>,
|
stcs: HashMap<'ir, StringId, (R::MaybeThunkRef, TextRange)>,
|
||||||
dyns: Vec<'ir, (Ref, MaybeThunk, TextRange)>,
|
dyns: Vec<'ir, (R::IrRef, R::MaybeThunkRef, TextRange)>,
|
||||||
},
|
},
|
||||||
List {
|
List {
|
||||||
items: Vec<'ir, MaybeThunk>,
|
items: Vec<'ir, R::MaybeThunkRef>,
|
||||||
},
|
},
|
||||||
ConcatStrings {
|
ConcatStrings {
|
||||||
parts: Vec<'ir, Ref>,
|
parts: Vec<'ir, R::Ref<Ir<'ir, R>>>,
|
||||||
force_string: bool,
|
force_string: bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
// OPs
|
// OPs
|
||||||
UnOp {
|
UnOp {
|
||||||
rhs: Ref,
|
rhs: R::IrRef,
|
||||||
kind: UnOpKind,
|
kind: UnOpKind,
|
||||||
},
|
},
|
||||||
BinOp {
|
BinOp {
|
||||||
lhs: Ref,
|
lhs: R::IrRef,
|
||||||
rhs: Ref,
|
rhs: R::IrRef,
|
||||||
kind: BinOpKind,
|
kind: BinOpKind,
|
||||||
},
|
},
|
||||||
HasAttr {
|
HasAttr {
|
||||||
lhs: Ref,
|
lhs: R::IrRef,
|
||||||
rhs: Vec<'ir, Attr<Ref>>,
|
rhs: Vec<'ir, Attr<R::IrRef>>,
|
||||||
},
|
},
|
||||||
Select {
|
Select {
|
||||||
expr: Ref,
|
expr: R::IrRef,
|
||||||
attrpath: Vec<'ir, Attr<Ref>>,
|
attrpath: Vec<'ir, Attr<R::IrRef>>,
|
||||||
default: Option<Ref>,
|
default: Option<R::IrRef>,
|
||||||
span: TextRange,
|
span: TextRange,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Conditionals
|
// Conditionals
|
||||||
If {
|
If {
|
||||||
cond: Ref,
|
cond: R::IrRef,
|
||||||
consq: Ref,
|
consq: R::IrRef,
|
||||||
alter: Ref,
|
alter: R::IrRef,
|
||||||
},
|
},
|
||||||
Assert {
|
Assert {
|
||||||
assertion: Ref,
|
assertion: R::IrRef,
|
||||||
expr: Ref,
|
expr: R::IrRef,
|
||||||
assertion_raw: String,
|
assertion_raw: String,
|
||||||
span: TextRange,
|
span: TextRange,
|
||||||
},
|
},
|
||||||
|
|
||||||
With {
|
WithLookup {
|
||||||
namespace: MaybeThunk,
|
sym: StringId,
|
||||||
body: Ref,
|
namespaces: Vec<'ir, R::MaybeThunkRef>,
|
||||||
thunks: Vec<'ir, (ThunkId, Ref)>,
|
|
||||||
},
|
},
|
||||||
WithLookup(StringId),
|
|
||||||
|
|
||||||
// Function related
|
// Function related
|
||||||
Func {
|
Func {
|
||||||
body: Ref,
|
body: R::IrRef,
|
||||||
param: Option<Param<'ir>>,
|
param: Option<Param<'ir>>,
|
||||||
thunks: Vec<'ir, (ThunkId, Ref)>,
|
thunks: Vec<'ir, (ThunkId, R::IrRef)>,
|
||||||
},
|
},
|
||||||
Arg {
|
Arg {
|
||||||
layer: usize,
|
layer: u8,
|
||||||
},
|
},
|
||||||
Call {
|
Call {
|
||||||
func: Ref,
|
func: R::IrRef,
|
||||||
arg: MaybeThunk,
|
arg: R::MaybeThunkRef,
|
||||||
span: TextRange,
|
span: TextRange,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -187,11 +208,10 @@ pub enum Ir<'ir, Ref> {
|
|||||||
|
|
||||||
// Misc
|
// Misc
|
||||||
TopLevel {
|
TopLevel {
|
||||||
body: Ref,
|
body: R::IrRef,
|
||||||
thunks: Vec<'ir, (ThunkId, Ref)>,
|
thunks: Vec<'ir, (ThunkId, R::IrRef)>,
|
||||||
},
|
},
|
||||||
Thunk(ThunkId),
|
MaybeThunk(R::MaybeThunkRef),
|
||||||
CurPos(TextRange),
|
|
||||||
ReplBinding(StringId),
|
ReplBinding(StringId),
|
||||||
ScopedImportBinding(StringId),
|
ScopedImportBinding(StringId),
|
||||||
}
|
}
|
||||||
@@ -241,36 +261,6 @@ pub enum BinOpKind {
|
|||||||
// Set/String/Path operations
|
// Set/String/Path operations
|
||||||
Con, // List concatenation (`++`)
|
Con, // List concatenation (`++`)
|
||||||
Upd, // AttrSet update (`//`)
|
Upd, // AttrSet update (`//`)
|
||||||
|
|
||||||
// Not standard, but part of rnix AST
|
|
||||||
PipeL,
|
|
||||||
PipeR,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ast::BinOpKind> for BinOpKind {
|
|
||||||
fn from(op: ast::BinOpKind) -> Self {
|
|
||||||
use BinOpKind::*;
|
|
||||||
use ast::BinOpKind as kind;
|
|
||||||
match op {
|
|
||||||
kind::Concat => Con,
|
|
||||||
kind::Update => Upd,
|
|
||||||
kind::Add => Add,
|
|
||||||
kind::Sub => Sub,
|
|
||||||
kind::Mul => Mul,
|
|
||||||
kind::Div => Div,
|
|
||||||
kind::And => And,
|
|
||||||
kind::Equal => Eq,
|
|
||||||
kind::Implication => Impl,
|
|
||||||
kind::Less => Lt,
|
|
||||||
kind::LessOrEq => Leq,
|
|
||||||
kind::More => Gt,
|
|
||||||
kind::MoreOrEq => Geq,
|
|
||||||
kind::NotEqual => Neq,
|
|
||||||
kind::Or => Or,
|
|
||||||
kind::PipeLeft => PipeL,
|
|
||||||
kind::PipeRight => PipeR,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The kinds of unary operations.
|
/// The kinds of unary operations.
|
||||||
@@ -299,38 +289,38 @@ pub struct Param<'ir> {
|
|||||||
|
|
||||||
pub fn new_global_env(
|
pub fn new_global_env(
|
||||||
strings: &mut DefaultStringInterner,
|
strings: &mut DefaultStringInterner,
|
||||||
) -> hashbrown::HashMap<StringId, Ir<'static, RawIrRef<'static>>> {
|
) -> hashbrown::HashMap<StringId, MaybeThunk> {
|
||||||
let mut global_env = hashbrown::HashMap::new();
|
let mut global_env = hashbrown::HashMap::new();
|
||||||
let builtins_sym = StringId(strings.get_or_intern("builtins"));
|
let builtins_sym = StringId(strings.get_or_intern("builtins"));
|
||||||
global_env.insert(builtins_sym, Ir::Builtins);
|
global_env.insert(builtins_sym, MaybeThunk::Builtins);
|
||||||
|
|
||||||
for (idx, &(name, _)) in BUILTINS.iter().enumerate() {
|
for (idx, &(name, _)) in BUILTINS.iter().enumerate() {
|
||||||
let id = BuiltinId::try_from_primitive(idx as u8).expect("infallible");
|
let id = BuiltinId::try_from_primitive(idx as u8).expect("infallible");
|
||||||
let name = StringId(strings.get_or_intern(name));
|
let name = StringId(strings.get_or_intern(name));
|
||||||
global_env.insert(name, Ir::Builtin(id));
|
global_env.insert(name, MaybeThunk::Builtin(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
let consts = [
|
let consts = [
|
||||||
(
|
(
|
||||||
"__currentSystem",
|
"__currentSystem",
|
||||||
Ir::BuiltinConst(StringId(strings.get_or_intern("currentSystem"))),
|
MaybeThunk::BuiltinConst(StringId(strings.get_or_intern("currentSystem"))),
|
||||||
),
|
),
|
||||||
("__langVersion", Ir::Int(6)),
|
("__langVersion", MaybeThunk::Int(6)),
|
||||||
(
|
(
|
||||||
"__nixVersion",
|
"__nixVersion",
|
||||||
Ir::BuiltinConst(StringId(strings.get_or_intern("nixVersion"))),
|
MaybeThunk::BuiltinConst(StringId(strings.get_or_intern("nixVersion"))),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"__storeDir",
|
"__storeDir",
|
||||||
Ir::BuiltinConst(StringId(strings.get_or_intern("storeDir"))),
|
MaybeThunk::BuiltinConst(StringId(strings.get_or_intern("storeDir"))),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"__nixPath",
|
"__nixPath",
|
||||||
Ir::BuiltinConst(StringId(strings.get_or_intern("nixPath"))),
|
MaybeThunk::BuiltinConst(StringId(strings.get_or_intern("nixPath"))),
|
||||||
),
|
),
|
||||||
("null", Ir::Null),
|
("null", MaybeThunk::Null),
|
||||||
("true", Ir::Bool(true)),
|
("true", MaybeThunk::Bool(true)),
|
||||||
("false", Ir::Bool(false)),
|
("false", MaybeThunk::Bool(false)),
|
||||||
];
|
];
|
||||||
|
|
||||||
for (name, ir) in consts {
|
for (name, ir) in consts {
|
||||||
|
|||||||
@@ -103,29 +103,30 @@ impl<'a> BytecodeReader<'a> {
|
|||||||
let id = self.read_u32();
|
let id = self.read_u32();
|
||||||
OperandData::Const(ctx.get_const(id))
|
OperandData::Const(ctx.get_const(id))
|
||||||
}
|
}
|
||||||
|
OperandType::BigInt => {
|
||||||
|
let val = self.read_i64();
|
||||||
|
OperandData::BigInt(val)
|
||||||
|
}
|
||||||
OperandType::Local => {
|
OperandType::Local => {
|
||||||
let layer = self.read_u8();
|
let layer = self.read_u8();
|
||||||
let idx = self.read_u32();
|
let idx = self.read_u32();
|
||||||
OperandData::Local { layer, idx }
|
OperandData::Local { layer, idx }
|
||||||
}
|
}
|
||||||
|
OperandType::BuiltinConst => {
|
||||||
|
let id = self.read_string_id();
|
||||||
|
OperandData::BuiltinConst(id)
|
||||||
|
}
|
||||||
OperandType::Builtins => OperandData::Builtins,
|
OperandType::Builtins => OperandData::Builtins,
|
||||||
OperandType::BigInt => {
|
OperandType::ReplBinding => {
|
||||||
let val = self.read_i64();
|
let id = self.read_string_id();
|
||||||
OperandData::BigInt(val)
|
OperandData::ReplBinding(id)
|
||||||
}
|
}
|
||||||
|
OperandType::ScopedImportBinding => {
|
||||||
|
let slot_id = self.read_u32();
|
||||||
|
let name = self.read_string_id();
|
||||||
|
OperandData::ScopedImportBinding { slot_id, name }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub(crate) fn read_attr_key_data<C: VmRuntimeCtx>(&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 {
|
pub(crate) fn pc(&self) -> usize {
|
||||||
@@ -139,8 +140,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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use crate::{Break, BytecodeReader, Step, Vm, VmRuntimeCtx};
|
|||||||
pub(crate) enum TailResult {
|
pub(crate) enum TailResult {
|
||||||
YieldFuel(u32),
|
YieldFuel(u32),
|
||||||
Done,
|
Done,
|
||||||
|
LoadFile,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) type OpFn<'gc, C> = extern "rust-preserve-none" fn(
|
pub(crate) type OpFn<'gc, C> = extern "rust-preserve-none" fn(
|
||||||
@@ -37,6 +38,7 @@ macro_rules! tail_dispatch_after {
|
|||||||
($result:expr, $new_pc:expr, $vm:ident, $mc:ident, $ctx:ident, $bc:ident, $table:ident, $fuel:ident) => {{
|
($result:expr, $new_pc:expr, $vm:ident, $mc:ident, $ctx:ident, $bc:ident, $table:ident, $fuel:ident) => {{
|
||||||
match $result {
|
match $result {
|
||||||
Step::Continue(()) | Step::Break(Break::Force) => {}
|
Step::Continue(()) | Step::Break(Break::Force) => {}
|
||||||
|
Step::Break(Break::LoadFile) => return TailResult::LoadFile,
|
||||||
Step::Break(Break::Done) => return TailResult::Done,
|
Step::Break(Break::Done) => return TailResult::Done,
|
||||||
}
|
}
|
||||||
let new_pc: u32 = $new_pc;
|
let new_pc: u32 = $new_pc;
|
||||||
@@ -142,6 +144,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,29 +175,27 @@ 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));
|
||||||
tail_fn!(op_jump, (reader));
|
tail_fn!(op_jump, (reader));
|
||||||
|
|
||||||
|
tail_fn!(op_coerce_to_string, (reader, mc));
|
||||||
|
|
||||||
tail_fn!(op_concat_strings, (ctx, reader, mc));
|
tail_fn!(op_concat_strings, (ctx, reader, mc));
|
||||||
tail_fn!(op_resolve_path, (ctx));
|
tail_fn!(op_resolve_path, (ctx, reader, mc));
|
||||||
|
|
||||||
tail_fn!(op_assert, (reader));
|
tail_fn!(op_assert, (ctx, reader, mc));
|
||||||
|
|
||||||
tail_fn!(op_push_with, (ctx, reader, mc));
|
|
||||||
tail_fn!(op_pop_with, ());
|
|
||||||
tail_fn!(op_lookup_with, (ctx, reader, mc));
|
tail_fn!(op_lookup_with, (ctx, reader, mc));
|
||||||
tail_fn!(op_prepare_with, ());
|
|
||||||
|
|
||||||
tail_fn!(op_load_builtins, ());
|
tail_fn!(op_load_builtins, ());
|
||||||
tail_fn!(op_load_builtin, (reader));
|
tail_fn!(op_load_builtin, (reader));
|
||||||
|
|
||||||
tail_fn!(op_mk_pos, (reader));
|
|
||||||
tail_fn!(op_load_repl_binding, (reader));
|
tail_fn!(op_load_repl_binding, (reader));
|
||||||
tail_fn!(op_load_scoped_binding, (reader));
|
tail_fn!(op_load_scoped_binding, (ctx, reader, mc));
|
||||||
|
|
||||||
macro_rules! table {
|
macro_rules! table {
|
||||||
($($variant:ident => $fn:ident),* $(,)?) => {
|
($($variant:ident => $fn:ident),* $(,)?) => {
|
||||||
@@ -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,
|
||||||
@@ -271,20 +273,18 @@ table! {
|
|||||||
JumpIfTrue => op_jump_if_true,
|
JumpIfTrue => op_jump_if_true,
|
||||||
Jump => op_jump,
|
Jump => op_jump,
|
||||||
|
|
||||||
|
CoerceToString => op_coerce_to_string,
|
||||||
|
|
||||||
ConcatStrings => op_concat_strings,
|
ConcatStrings => op_concat_strings,
|
||||||
ResolvePath => op_resolve_path,
|
ResolvePath => op_resolve_path,
|
||||||
|
|
||||||
Assert => op_assert,
|
Assert => op_assert,
|
||||||
|
|
||||||
PushWith => op_push_with,
|
|
||||||
PopWith => op_pop_with,
|
|
||||||
LookupWith => op_lookup_with,
|
LookupWith => op_lookup_with,
|
||||||
PrepareWith => op_prepare_with,
|
|
||||||
|
|
||||||
LoadBuiltins => op_load_builtins,
|
LoadBuiltins => op_load_builtins,
|
||||||
LoadBuiltin => op_load_builtin,
|
LoadBuiltin => op_load_builtin,
|
||||||
|
|
||||||
MkPos => op_mk_pos,
|
|
||||||
LoadReplBinding => op_load_repl_binding,
|
LoadReplBinding => op_load_repl_binding,
|
||||||
LoadScopedBinding => op_load_scoped_binding,
|
LoadScopedBinding => op_load_scoped_binding,
|
||||||
|
|
||||||
|
|||||||
+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 _};
|
||||||
@@ -13,7 +13,7 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
let (lhs, rhs) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?;
|
let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
|
||||||
if let (Some(ls), Some(rs)) = (ctx.get_string(lhs), ctx.get_string(rhs)) {
|
if let (Some(ls), Some(rs)) = (ctx.get_string(lhs), ctx.get_string(rhs)) {
|
||||||
let ns = Gc::new(mc, crate::NixString::new(format!("{ls}{rs}")));
|
let ns = Gc::new(mc, crate::NixString::new(format!("{ls}{rs}")));
|
||||||
self.push(Value::new_gc(ns));
|
self.push(Value::new_gc(ns));
|
||||||
@@ -47,7 +47,7 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
int_op: fn(i64, i64) -> i64,
|
int_op: fn(i64, i64) -> i64,
|
||||||
float_op: fn(f64, f64) -> f64,
|
float_op: fn(f64, f64) -> f64,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
let (lhs, rhs) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?;
|
let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
|
||||||
let res = numeric_binop(lhs, rhs, mc, int_op, float_op);
|
let res = numeric_binop(lhs, rhs, mc, int_op, float_op);
|
||||||
match res {
|
match res {
|
||||||
Ok(val) => {
|
Ok(val) => {
|
||||||
@@ -60,7 +60,7 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn op_div(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> Step {
|
pub(crate) fn op_div(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> Step {
|
||||||
let (lhs, rhs) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?;
|
let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
|
||||||
match (get_num(lhs), get_num(rhs)) {
|
match (get_num(lhs), get_num(rhs)) {
|
||||||
(_, Some(NixNum::Int(0))) | (_, Some(NixNum::Float(0.))) => {
|
(_, Some(NixNum::Int(0))) | (_, Some(NixNum::Float(0.))) => {
|
||||||
return self.finish_vm_err(VmError::Uncatchable(fix_error::Error::eval_error(
|
return self.finish_vm_err(VmError::Uncatchable(fix_error::Error::eval_error(
|
||||||
@@ -86,7 +86,7 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
let (lhs, rhs) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?;
|
let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
|
||||||
let eq = match self.values_equal(ctx, lhs, rhs) {
|
let eq = match self.values_equal(ctx, lhs, rhs) {
|
||||||
Ok(eq) => eq,
|
Ok(eq) => eq,
|
||||||
Err(e) => return self.finish_vm_err(e),
|
Err(e) => return self.finish_vm_err(e),
|
||||||
@@ -102,7 +102,7 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
let (lhs, rhs) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?;
|
let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
|
||||||
let eq = match self.values_equal(ctx, lhs, rhs) {
|
let eq = match self.values_equal(ctx, lhs, rhs) {
|
||||||
Ok(eq) => eq,
|
Ok(eq) => eq,
|
||||||
Err(e) => return self.finish_vm_err(e),
|
Err(e) => return self.finish_vm_err(e),
|
||||||
@@ -158,7 +158,7 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
pred: fn(Ordering) -> bool,
|
pred: fn(Ordering) -> bool,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
let (lhs, rhs) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?;
|
let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
|
||||||
match self.compare_values_inner(ctx, pred, lhs, rhs) {
|
match self.compare_values_inner(ctx, pred, lhs, rhs) {
|
||||||
Ok(()) => Step::Continue(()),
|
Ok(()) => Step::Continue(()),
|
||||||
Err(e) => self.finish_vm_err(e),
|
Err(e) => self.finish_vm_err(e),
|
||||||
@@ -171,11 +171,16 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
let (l, r) = self.try_force::<(Gc<List>, Gc<List>)>(reader, mc)?;
|
let (l, r) = self.force_and_retry::<(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(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,19 +190,26 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
let (l, r) = self.try_force::<(Gc<AttrSet>, Gc<AttrSet>)>(reader, mc)?;
|
let (l, r) = self.force_and_retry::<(Gc<AttrSet>, Gc<AttrSet>)>(reader, mc)?;
|
||||||
self.push(Value::new_gc(l.merge(&r, mc)));
|
self.push(Value::new_gc(l.merge(&r, mc)));
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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.force_and_retry::<NixNum>(reader, mc)?;
|
||||||
|
match rhs {
|
||||||
|
NixNum::Int(int) => self.push(Value::make_int(-int, mc)),
|
||||||
|
NixNum::Float(float) => self.push(Value::new_float(-float)),
|
||||||
|
}
|
||||||
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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.force_and_retry::<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;
|
||||||
|
let b = &b.entries;
|
||||||
if a.len() != b.len() {
|
if a.len() != b.len() {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
@@ -312,6 +326,9 @@ fn numeric_binop<'gc>(
|
|||||||
(Some(NixNum::Float(a)), Some(NixNum::Int(b))) => {
|
(Some(NixNum::Float(a)), Some(NixNum::Int(b))) => {
|
||||||
Ok(Value::new_float(float_op(a, b as f64)))
|
Ok(Value::new_float(float_op(a, b as f64)))
|
||||||
}
|
}
|
||||||
_ => Err(crate::vm_err("cannot perform arithmetic on non-numbers")),
|
_ => Err(crate::vm_err(format!(
|
||||||
|
"cannot perform arithmetic on non-numbers: {:?}",
|
||||||
|
(lhs.ty(), rhs.ty())
|
||||||
|
))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
use fix_builtins::BuiltinId;
|
|
||||||
use num_enum::TryFromPrimitive;
|
|
||||||
|
|
||||||
use crate::{BytecodeReader, PrimOp, Step, Value, VmRuntimeCtx};
|
|
||||||
|
|
||||||
impl<'gc> crate::Vm<'gc> {
|
|
||||||
#[inline(always)]
|
|
||||||
pub(crate) fn op_load_builtins(&mut self) -> Step {
|
|
||||||
self.push(self.builtins);
|
|
||||||
Step::Continue(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub(crate) fn op_load_builtin(&mut self, reader: &mut BytecodeReader<'_>) -> Step {
|
|
||||||
let Ok(id) = BuiltinId::try_from_primitive(reader.read_u8())
|
|
||||||
.map_err(|err| panic!("unknown builtin id: {}", err.number));
|
|
||||||
self.push(Value::new_inline(PrimOp {
|
|
||||||
id,
|
|
||||||
arity: fix_builtins::BUILTINS[id as usize].1,
|
|
||||||
}));
|
|
||||||
Step::Continue(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub(crate) fn op_mk_pos(&mut self, reader: &mut BytecodeReader<'_>) -> Step {
|
|
||||||
let _span_id = reader.read_u32();
|
|
||||||
todo!("MkPos");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub(crate) fn op_load_repl_binding(&mut self, reader: &mut BytecodeReader<'_>) -> Step {
|
|
||||||
let _name = reader.read_string_id();
|
|
||||||
todo!("LoadReplBinding");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub(crate) fn op_load_scoped_binding(&mut self, reader: &mut BytecodeReader<'_>) -> Step {
|
|
||||||
let _name = reader.read_string_id();
|
|
||||||
todo!("LoadScopedBinding");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub(crate) fn op_concat_strings(
|
|
||||||
&mut self,
|
|
||||||
ctx: &mut impl VmRuntimeCtx,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
_mc: &gc_arena::Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
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 VmRuntimeCtx) -> Step {
|
|
||||||
todo!("implement ResolvePath");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +1,128 @@
|
|||||||
|
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.force_and_retry::<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>() {
|
||||||
|
if closure.pattern.is_some() {
|
||||||
|
// FIXME: better DX...
|
||||||
|
self.push(func.relax());
|
||||||
|
self.push(arg);
|
||||||
|
self.call_stack.push(CallFrame {
|
||||||
|
pc: resume_pc,
|
||||||
|
thunk: None,
|
||||||
|
env: self.env,
|
||||||
|
});
|
||||||
|
reader.set_pc(PrimOpPhase::CallPattern.ip() as usize);
|
||||||
|
return Step::Continue(());
|
||||||
|
}
|
||||||
|
|
||||||
let ip = closure.ip;
|
let ip = closure.ip;
|
||||||
let n_locals = closure.n_locals;
|
let n_locals = closure.n_locals;
|
||||||
let env = closure.env;
|
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)));
|
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,
|
|
||||||
thunk: None,
|
thunk: None,
|
||||||
env: self.env,
|
env: self.env,
|
||||||
with_env: self.with_env,
|
|
||||||
});
|
});
|
||||||
reader.set_pc(ip as usize);
|
reader.set_pc(ip as usize);
|
||||||
self.env = new_env;
|
self.env = new_env;
|
||||||
}
|
} else if let Some(primop) = func.as_inline::<PrimOp>() {
|
||||||
|
if primop.arity == 1 {
|
||||||
|
self.push(arg);
|
||||||
|
self.call_stack.push(CallFrame {
|
||||||
|
pc: resume_pc,
|
||||||
|
thunk: None,
|
||||||
|
env: self.env,
|
||||||
|
});
|
||||||
|
reader.set_pc(primop.dispatch_ip as usize)
|
||||||
} else {
|
} else {
|
||||||
todo!("call other types: {func:?}")
|
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,
|
||||||
|
thunk: None,
|
||||||
|
env: self.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 if let Some(attrs) = func.as_gc::<AttrSet>()
|
||||||
|
&& let Some(functor) = attrs.lookup(self.functor_sym)
|
||||||
|
{
|
||||||
|
// f arg => (f.__functor f) arg
|
||||||
|
//
|
||||||
|
// Stage the work for `CallFunctor1` so retries during force are
|
||||||
|
// safe: the stack invariant `[..., orig_arg, self, functor]`
|
||||||
|
// holds every time control re-enters phase 1.
|
||||||
|
self.call_depth -= 1;
|
||||||
|
self.call_stack.push(CallFrame {
|
||||||
|
pc: resume_pc,
|
||||||
|
thunk: None,
|
||||||
|
env: self.env,
|
||||||
|
});
|
||||||
|
self.push(arg);
|
||||||
|
self.push(func.relax());
|
||||||
|
self.push(functor);
|
||||||
|
reader.set_pc(PrimOpPhase::CallFunctor1.ip() as usize);
|
||||||
|
return Step::Continue(());
|
||||||
|
} else {
|
||||||
|
return self.finish_err(Error::eval_error(format!(
|
||||||
|
"attempt to call something which is not a function but {}",
|
||||||
|
func.ty()
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
Step::Continue(())
|
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,87 +130,42 @@ 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.force_and_retry::<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,
|
|
||||||
thunk,
|
thunk,
|
||||||
env,
|
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,
|
||||||
|
thunk: None,
|
||||||
|
env: self.env,
|
||||||
|
});
|
||||||
|
self.call_depth += 1;
|
||||||
|
reader.set_pc(PrimOpPhase::DeepSeq.ip() as usize);
|
||||||
|
return Step::Continue(());
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
reader.set_pc(ret_pc);
|
reader.set_pc(ret_pc);
|
||||||
if let Some(outer_thunk) = thunk {
|
if let Some(outer_thunk) = thunk {
|
||||||
let val = self.pop();
|
|
||||||
match val.restrict() {
|
|
||||||
Ok(val) => {
|
|
||||||
*outer_thunk.borrow_mut(mc) = 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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
RefLock::new(ThunkState::Pending {
|
RefLock::new(ThunkState::Pending {
|
||||||
ip: entry_point as usize,
|
ip: entry_point as usize,
|
||||||
env: self.env,
|
env: self.env,
|
||||||
with_env: self.with_env,
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
self.push(Value::new_gc(thunk));
|
self.push(Value::new_gc(thunk));
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
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;
|
||||||
use crate::{
|
use crate::{
|
||||||
AttrKeyData, AttrSet, BytecodeReader, List, OperandData, Step, StrictValue, Value,
|
AttrSet, BytecodeReader, List, Step, StrictValue, Value, VmRuntimeCtx, VmRuntimeCtxExt,
|
||||||
VmRuntimeCtx, VmRuntimeCtxExt,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
impl<'gc> crate::Vm<'gc> {
|
impl<'gc> crate::Vm<'gc> {
|
||||||
@@ -17,27 +16,46 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
mc: &gc_arena::Mutation<'gc>,
|
mc: &gc_arena::Mutation<'gc>,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
let count = reader.read_u32() as usize;
|
let static_count = reader.read_u32() as usize;
|
||||||
let mut entries: SmallVec<[AttrEntry; 4]> = SmallVec::with_capacity(count);
|
let dynamic_count = reader.read_u32() as usize;
|
||||||
for _ in 0..count {
|
|
||||||
let key = reader.read_attr_key_data(ctx);
|
for i in 0..dynamic_count {
|
||||||
let val = reader.read_operand_data(ctx);
|
let depth = dynamic_count - 1 - i;
|
||||||
let _span_id = reader.read_u32();
|
self.force_slot_to_pc(depth, reader, mc, reader.inst_start_pc())?;
|
||||||
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 mut dyn_keys: SmallVec<[_; 2]> = SmallVec::with_capacity(dynamic_count);
|
||||||
|
for i in 0..dynamic_count {
|
||||||
|
let depth = dynamic_count - 1 - i;
|
||||||
|
let key_val = self.peek_forced(depth);
|
||||||
|
let key_sid = match ctx.get_string_id(key_val) {
|
||||||
|
Ok(id) => Some(id),
|
||||||
|
Err(NixType::Null) => None,
|
||||||
|
Err(got) => return self.finish_type_err(NixType::String, got),
|
||||||
};
|
};
|
||||||
let val = entry.val.resolve(mc, self);
|
dyn_keys.push(key_sid);
|
||||||
kv.push((key_sid, val));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.stack.truncate(self.stack.len() - dynamic_count);
|
||||||
|
|
||||||
|
let mut kv: SmallVec<[(crate::StringId, Value); 4]> =
|
||||||
|
SmallVec::with_capacity(static_count + dynamic_count);
|
||||||
|
|
||||||
|
for _ in 0..static_count {
|
||||||
|
let key = reader.read_string_id();
|
||||||
|
let val = reader.read_operand_data(ctx).resolve(mc, self);
|
||||||
|
let _span_id = reader.read_u32();
|
||||||
|
kv.push((key, val));
|
||||||
|
}
|
||||||
|
|
||||||
|
for key in dyn_keys {
|
||||||
|
let val = reader.read_operand_data(ctx).resolve(mc, self);
|
||||||
|
let _span_id = reader.read_u32();
|
||||||
|
if let Some(key) = key {
|
||||||
|
kv.push((key, val))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
kv.sort_by_key(|(k, _)| *k);
|
kv.sort_by_key(|(k, _)| *k);
|
||||||
let attrs = Gc::new(mc, AttrSet::from_sorted_unchecked(kv));
|
let attrs = Gc::new(mc, AttrSet::from_sorted_unchecked(kv));
|
||||||
self.push(Value::new_gc(attrs));
|
self.push(Value::new_gc(attrs));
|
||||||
@@ -60,7 +78,7 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
let _span_id = reader.read_u32();
|
let _span_id = reader.read_u32();
|
||||||
let key = reader.read_string_id();
|
let key = reader.read_string_id();
|
||||||
|
|
||||||
let attrset = self.try_force::<Gc<AttrSet>>(reader, mc)?;
|
let attrset = self.force_and_retry::<Gc<AttrSet>>(reader, mc)?;
|
||||||
|
|
||||||
match attrset.lookup(key) {
|
match attrset.lookup(key) {
|
||||||
Some(v) => {
|
Some(v) => {
|
||||||
@@ -80,7 +98,7 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
) -> Step {
|
) -> Step {
|
||||||
let _span_id = reader.read_u32();
|
let _span_id = reader.read_u32();
|
||||||
|
|
||||||
let (attrset, key_val) = self.try_force::<(Gc<AttrSet>, StrictValue)>(reader, mc)?;
|
let (attrset, key_val) = self.force_and_retry::<(Gc<AttrSet>, StrictValue)>(reader, mc)?;
|
||||||
|
|
||||||
let key_sid = match ctx.get_string_id(key_val) {
|
let key_sid = match ctx.get_string_id(key_val) {
|
||||||
Ok(id) => id,
|
Ok(id) => id,
|
||||||
@@ -176,7 +194,7 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
let _span_id = reader.read_u32();
|
let _span_id = reader.read_u32();
|
||||||
let key = reader.read_string_id();
|
let key = reader.read_string_id();
|
||||||
|
|
||||||
let current = self.try_force::<StrictValue>(reader, mc)?;
|
let current = self.force_and_retry::<StrictValue>(reader, mc)?;
|
||||||
|
|
||||||
match current
|
match current
|
||||||
.as_gc::<AttrSet>()
|
.as_gc::<AttrSet>()
|
||||||
@@ -199,7 +217,7 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
) -> Step {
|
) -> Step {
|
||||||
let _span_id = reader.read_u32();
|
let _span_id = reader.read_u32();
|
||||||
|
|
||||||
let (current, key_val) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?;
|
let (current, key_val) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
|
||||||
|
|
||||||
let key_sid = match ctx.get_string_id(key_val) {
|
let key_sid = match ctx.get_string_id(key_val) {
|
||||||
Ok(id) => id,
|
Ok(id) => id,
|
||||||
@@ -239,7 +257,7 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
mc: &gc_arena::Mutation<'gc>,
|
mc: &gc_arena::Mutation<'gc>,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
let key = reader.read_string_id();
|
let key = reader.read_string_id();
|
||||||
let current = self.try_force::<StrictValue>(reader, mc)?;
|
let current = self.force_and_retry::<StrictValue>(reader, mc)?;
|
||||||
|
|
||||||
self.push(Value::new_inline(
|
self.push(Value::new_inline(
|
||||||
current
|
current
|
||||||
@@ -260,7 +278,7 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
mc: &gc_arena::Mutation<'gc>,
|
mc: &gc_arena::Mutation<'gc>,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
let (current, dyn_key) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?;
|
let (current, dyn_key) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
|
||||||
|
|
||||||
let key_sid = match ctx.get_string_id(dyn_key) {
|
let key_sid = match ctx.get_string_id(dyn_key) {
|
||||||
Ok(id) => id,
|
Ok(id) => id,
|
||||||
@@ -298,7 +316,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(())
|
||||||
}
|
}
|
||||||
@@ -309,8 +332,3 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct AttrEntry {
|
|
||||||
pub(crate) key: AttrKeyData,
|
|
||||||
pub(crate) val: OperandData,
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
|
use fix_error::Error;
|
||||||
|
use gc_arena::Mutation;
|
||||||
|
|
||||||
use crate::value::*;
|
use crate::value::*;
|
||||||
use crate::{BytecodeReader, Step};
|
use crate::{BytecodeReader, Step, VmRuntimeCtx};
|
||||||
|
|
||||||
impl<'gc> crate::Vm<'gc> {
|
impl<'gc> crate::Vm<'gc> {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@@ -9,7 +12,7 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
mc: &gc_arena::Mutation<'gc>,
|
mc: &gc_arena::Mutation<'gc>,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
let offset = reader.read_i32();
|
let offset = reader.read_i32();
|
||||||
let cond = self.try_force::<StrictValue>(reader, mc)?;
|
let cond = self.force_and_retry::<StrictValue>(reader, mc)?;
|
||||||
if cond.as_inline::<bool>() == Some(false) {
|
if cond.as_inline::<bool>() == Some(false) {
|
||||||
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
|
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
|
||||||
}
|
}
|
||||||
@@ -20,10 +23,10 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
pub(crate) fn op_jump_if_true(
|
pub(crate) fn op_jump_if_true(
|
||||||
&mut self,
|
&mut self,
|
||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
mc: &gc_arena::Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
let offset = reader.read_i32();
|
let offset = reader.read_i32();
|
||||||
let cond = self.try_force::<StrictValue>(reader, mc)?;
|
let cond = self.force_and_retry::<StrictValue>(reader, mc)?;
|
||||||
if cond.as_inline::<bool>() == Some(true) {
|
if cond.as_inline::<bool>() == Some(true) {
|
||||||
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
|
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
|
||||||
}
|
}
|
||||||
@@ -38,9 +41,20 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn op_assert(&mut self, reader: &mut BytecodeReader<'_>) -> Step {
|
pub(crate) fn op_assert(
|
||||||
let _raw_idx = reader.read_u32();
|
&mut self,
|
||||||
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
let raw_id = reader.read_string_id();
|
||||||
|
let raw = ctx.resolve_string(raw_id);
|
||||||
let _span_id = reader.read_u32();
|
let _span_id = reader.read_u32();
|
||||||
todo!("implement Assert (force TOS)");
|
let assertion = self.force_and_retry::<bool>(reader, mc)?;
|
||||||
|
if !assertion {
|
||||||
|
// FIXME: use catchable error
|
||||||
|
return self.finish_err(Error::eval_error(format!("assertion '{raw}' failed")));
|
||||||
|
}
|
||||||
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,177 @@
|
|||||||
|
use std::path::{Component, PathBuf};
|
||||||
|
|
||||||
|
use fix_builtins::BuiltinId;
|
||||||
|
use fix_common::StringId;
|
||||||
|
use fix_error::Error;
|
||||||
|
use num_enum::TryFromPrimitive;
|
||||||
|
|
||||||
|
use crate::value::{AttrSet, NixString, StrictValue};
|
||||||
|
use crate::{BytecodeReader, PrimOp, Step, Value, VmRuntimeCtx, VmRuntimeCtxExt};
|
||||||
|
|
||||||
|
impl<'gc> crate::Vm<'gc> {
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_load_builtins(&mut self) -> Step {
|
||||||
|
self.push(self.builtins);
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_load_builtin(&mut self, reader: &mut BytecodeReader<'_>) -> Step {
|
||||||
|
let Ok(id) = BuiltinId::try_from_primitive(reader.read_u8())
|
||||||
|
.map_err(|err| panic!("unknown builtin id: {}", err.number));
|
||||||
|
self.push(Value::new_inline(PrimOp {
|
||||||
|
id,
|
||||||
|
arity: fix_builtins::BUILTINS[id as usize].1,
|
||||||
|
dispatch_ip: id.entry_phase().ip(),
|
||||||
|
}));
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_load_repl_binding(&mut self, reader: &mut BytecodeReader<'_>) -> Step {
|
||||||
|
let _name = reader.read_string_id();
|
||||||
|
todo!("LoadReplBinding");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_load_scoped_binding(
|
||||||
|
&mut self,
|
||||||
|
ctx: &impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
_mc: &gc_arena::Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
let slot_id = reader.read_u32();
|
||||||
|
let name = reader.read_string_id();
|
||||||
|
let scope = match self.scope_slots.get(slot_id as usize).copied() {
|
||||||
|
Some(s) => s,
|
||||||
|
None => {
|
||||||
|
return self.finish_err(Error::eval_error(format!(
|
||||||
|
"internal: invalid scope slot {slot_id}"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let Some(attrs) = scope.as_gc::<AttrSet>() else {
|
||||||
|
return self.finish_err(Error::eval_error(
|
||||||
|
"internal: scope slot is not an attrset",
|
||||||
|
));
|
||||||
|
};
|
||||||
|
match attrs.lookup(name) {
|
||||||
|
Some(val) => {
|
||||||
|
self.push(val);
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
None => self.finish_err(Error::eval_error(format!(
|
||||||
|
"scoped binding '{}' not found",
|
||||||
|
ctx.resolve_string(name)
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_coerce_to_string(
|
||||||
|
&mut self,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &gc_arena::Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
let val = self.force_and_retry::<StrictValue>(reader, mc)?;
|
||||||
|
if val.is::<StringId>() || val.is::<NixString>() {
|
||||||
|
self.push(val.relax());
|
||||||
|
} else {
|
||||||
|
todo!("coerce other types to string: {:?}", val.ty());
|
||||||
|
}
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_concat_strings(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
_mc: &gc_arena::Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
let count = reader.read_u16() as usize;
|
||||||
|
let _force_string = reader.read_u8() != 0;
|
||||||
|
|
||||||
|
let mut total_len = 0;
|
||||||
|
for i in 0..count {
|
||||||
|
let val = self.peek_forced(count - 1 - i);
|
||||||
|
let s = ctx.get_string(val).expect("coerced");
|
||||||
|
total_len += s.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut result = String::with_capacity(total_len);
|
||||||
|
for i in 0..count {
|
||||||
|
let val = self.peek_forced(count - 1 - i);
|
||||||
|
let s = ctx.get_string(val).expect("coerced");
|
||||||
|
result.push_str(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.stack.truncate(self.stack.len() - count);
|
||||||
|
|
||||||
|
let sid = ctx.intern_string(result);
|
||||||
|
self.push(Value::new_inline(sid));
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_resolve_path(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &gc_arena::Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
let path_val = self.force_and_retry::<StrictValue>(reader, mc)?;
|
||||||
|
let dir_id = reader.read_string_id();
|
||||||
|
let path = match ctx.get_string(path_val) {
|
||||||
|
Some(s) => s.to_owned(),
|
||||||
|
None => {
|
||||||
|
return self.finish_err(Error::eval_error(format!(
|
||||||
|
"expected a string for path, got {}",
|
||||||
|
path_val.ty()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let resolved = match resolve_path_str(ctx.resolve_string(dir_id), &path) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => return self.finish_err(e),
|
||||||
|
};
|
||||||
|
let sid = ctx.intern_string(resolved);
|
||||||
|
self.push(Value::new_inline(sid));
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolve a Nix path literal against `current_dir`.
|
||||||
|
///
|
||||||
|
/// Mirrors nix-js's `op_resolve_path`: absolute paths returned as-is, `~/X`
|
||||||
|
/// expanded against `$HOME`, otherwise joined onto `current_dir`. The result
|
||||||
|
/// is normalized by removing `.` components and resolving `..` lexically
|
||||||
|
/// (no symlink resolution).
|
||||||
|
fn resolve_path_str(current_dir: &str, path: &str) -> Result<String, Box<Error>> {
|
||||||
|
let raw = if path.starts_with('/') {
|
||||||
|
return Ok(path.to_owned());
|
||||||
|
} else if let Some(rest) = path.strip_prefix("~/") {
|
||||||
|
#[allow(deprecated)]
|
||||||
|
let mut dir = std::env::home_dir()
|
||||||
|
.ok_or_else(|| Error::eval_error("home dir not defined"))?;
|
||||||
|
dir.push(rest);
|
||||||
|
dir
|
||||||
|
} else {
|
||||||
|
let mut dir = PathBuf::from(current_dir);
|
||||||
|
dir.push(path);
|
||||||
|
dir
|
||||||
|
};
|
||||||
|
let mut normalized = PathBuf::new();
|
||||||
|
for component in raw.components() {
|
||||||
|
match component {
|
||||||
|
Component::Prefix(p) => normalized.push(p.as_os_str()),
|
||||||
|
Component::RootDir => normalized.push("/"),
|
||||||
|
Component::CurDir => {}
|
||||||
|
Component::ParentDir => {
|
||||||
|
normalized.pop();
|
||||||
|
}
|
||||||
|
Component::Normal(c) => normalized.push(c),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(normalized.to_string_lossy().into_owned())
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
pub(crate) mod arithmetic;
|
pub(crate) mod arithmetic;
|
||||||
pub(crate) mod builtins_misc;
|
|
||||||
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;
|
||||||
|
|||||||
@@ -1,51 +1,11 @@
|
|||||||
use fix_common::Symbol;
|
use fix_common::Symbol;
|
||||||
use fix_error::Error;
|
use fix_error::Error;
|
||||||
use gc_arena::Gc;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
use crate::value::*;
|
use crate::value::*;
|
||||||
use crate::{BytecodeReader, CallFrame, Step, VmRuntimeCtx, WithEnv};
|
use crate::{Break, BytecodeReader, CallFrame, Step, VmRuntimeCtx};
|
||||||
|
|
||||||
impl<'gc> crate::Vm<'gc> {
|
impl<'gc> crate::Vm<'gc> {
|
||||||
#[inline(always)]
|
|
||||||
pub(crate) fn op_push_with(
|
|
||||||
&mut self,
|
|
||||||
ctx: &mut impl VmRuntimeCtx,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &gc_arena::Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
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);
|
|
||||||
Step::Continue(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub(crate) fn op_pop_with(&mut self) -> Step {
|
|
||||||
let Some(scope) = self.with_env else {
|
|
||||||
unreachable!("no with_scope to pop");
|
|
||||||
};
|
|
||||||
self.with_env = scope.prev;
|
|
||||||
Step::Continue(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub(crate) fn op_prepare_with(&mut self) -> Step {
|
|
||||||
self.call_stack.push(CallFrame {
|
|
||||||
pc: usize::MAX,
|
|
||||||
stack_depth: 0,
|
|
||||||
thunk: None,
|
|
||||||
env: self.env,
|
|
||||||
with_env: self.with_env,
|
|
||||||
});
|
|
||||||
Step::Continue(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn op_lookup_with(
|
pub(crate) fn op_lookup_with(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -53,31 +13,66 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
mc: &gc_arena::Mutation<'gc>,
|
mc: &gc_arena::Mutation<'gc>,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
let name = reader.read_string_id();
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let counter = self.peek_forced(0).as_inline::<i32>().unwrap();
|
||||||
|
|
||||||
let Some(&WithEnv { env, prev }) = self.with_env.as_deref() else {
|
let name = reader.read_string_id();
|
||||||
let Some(CallFrame { with_env, .. }) = self.call_stack.pop() else {
|
let n = reader.read_u8();
|
||||||
unreachable!()
|
let mut namespaces = SmallVec::<[_; 2]>::new();
|
||||||
|
for _ in 0..n {
|
||||||
|
namespaces.push(reader.read_operand_data(ctx).resolve(mc, self));
|
||||||
|
}
|
||||||
|
|
||||||
|
let resume_pc = reader.inst_start_pc();
|
||||||
|
let namespace = match namespaces[counter as usize].restrict() {
|
||||||
|
Ok(val) => val,
|
||||||
|
Err(thunk) => {
|
||||||
|
let mut state = thunk.borrow_mut(mc);
|
||||||
|
match *state {
|
||||||
|
ThunkState::Pending { ip, env } => {
|
||||||
|
*state = ThunkState::Blackhole;
|
||||||
|
self.call_stack.push(CallFrame {
|
||||||
|
thunk: Some(thunk),
|
||||||
|
pc: resume_pc,
|
||||||
|
env: self.env,
|
||||||
|
});
|
||||||
|
self.env = env;
|
||||||
|
reader.set_pc(ip);
|
||||||
|
return Step::Break(Break::Force);
|
||||||
|
}
|
||||||
|
ThunkState::Evaluated(v) => v,
|
||||||
|
ThunkState::Apply { func, arg } => {
|
||||||
|
self.call_stack.push(CallFrame {
|
||||||
|
thunk: Some(thunk),
|
||||||
|
pc: resume_pc,
|
||||||
|
env: self.env,
|
||||||
|
});
|
||||||
|
self.push(func);
|
||||||
|
return self.call(reader, mc, arg, resume_pc);
|
||||||
|
}
|
||||||
|
ThunkState::Blackhole => {
|
||||||
|
return self
|
||||||
|
.finish_err(Error::eval_error("infinite recursion encountered"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
self.with_env = with_env;
|
|
||||||
|
if let Some(val) = namespace
|
||||||
|
.as_gc::<AttrSet>()
|
||||||
|
.and_then(|attrs| attrs.lookup(name))
|
||||||
|
{
|
||||||
|
self.replace(0, val);
|
||||||
|
} else if counter + 1 == n as i32 {
|
||||||
return self.finish_err(Error::eval_error(format!(
|
return self.finish_err(Error::eval_error(format!(
|
||||||
"undefined variable '{}'",
|
"undefined variable '{}'",
|
||||||
Symbol::from(ctx.resolve_string(name))
|
Symbol::from(ctx.resolve_string(name))
|
||||||
)));
|
)));
|
||||||
};
|
} else {
|
||||||
self.push(env);
|
self.replace(0, Value::new_inline(counter + 1));
|
||||||
let env = self.try_force::<Gc<AttrSet>>(reader, mc)?;
|
reader.set_pc(resume_pc);
|
||||||
let Some(val) = env.lookup(name) else {
|
}
|
||||||
reader.set_pc(reader.inst_start_pc());
|
|
||||||
self.with_env = prev;
|
|
||||||
return Step::Continue(());
|
|
||||||
};
|
|
||||||
|
|
||||||
self.push(val);
|
|
||||||
let Some(CallFrame { with_env, .. }) = self.call_stack.pop() else {
|
|
||||||
unreachable!()
|
|
||||||
};
|
|
||||||
self.with_env = with_env;
|
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+253
-91
@@ -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;
|
||||||
|
|
||||||
@@ -26,10 +26,11 @@ mod value;
|
|||||||
pub use value::StaticValue;
|
pub use value::StaticValue;
|
||||||
use value::*;
|
use value::*;
|
||||||
mod helpers;
|
mod helpers;
|
||||||
pub(crate) mod instructions;
|
mod instructions;
|
||||||
pub(crate) use bytecode_reader::BytecodeReader;
|
use bytecode_reader::BytecodeReader;
|
||||||
pub(crate) use forced::Forced;
|
use forced::Forced;
|
||||||
use helpers::*;
|
use helpers::*;
|
||||||
|
mod primops;
|
||||||
|
|
||||||
type VmResult<T> = std::result::Result<T, VmError>;
|
type VmResult<T> = std::result::Result<T, VmError>;
|
||||||
|
|
||||||
@@ -76,14 +77,26 @@ pub trait VmRuntimeCtx {
|
|||||||
|
|
||||||
pub trait VmCode {
|
pub trait VmCode {
|
||||||
fn bytecode(&self) -> &[u8];
|
fn bytecode(&self) -> &[u8];
|
||||||
fn compile(
|
fn compile_with_scope(
|
||||||
&mut self,
|
&mut self,
|
||||||
source: Source,
|
source: Source,
|
||||||
|
extra_scope: Option<ExtraScope>,
|
||||||
ctx: &mut impl VmRuntimeCtx,
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
) -> fix_error::Result<InstructionPtr>;
|
) -> fix_error::Result<InstructionPtr>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait VmRuntimeCtxExt: VmRuntimeCtx {
|
/// Extra scope passed to a re-entrant compile from inside a running VM.
|
||||||
|
///
|
||||||
|
/// Currently only `ScopedImport` is produced (by the `scopedImport` builtin),
|
||||||
|
/// but the variant is kept open so REPL bindings could later land here too.
|
||||||
|
pub enum ExtraScope {
|
||||||
|
ScopedImport {
|
||||||
|
keys: HashSet<StringId>,
|
||||||
|
slot_id: u32,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
trait VmRuntimeCtxExt: VmRuntimeCtx {
|
||||||
fn get_string<'a, 'gc: 'a>(&'a self, val: StrictValue<'gc>) -> Option<&'a str>;
|
fn get_string<'a, 'gc: 'a>(&'a self, val: StrictValue<'gc>) -> Option<&'a str>;
|
||||||
fn get_string_id<'a, 'gc: 'a>(
|
fn get_string_id<'a, 'gc: 'a>(
|
||||||
&'a mut self,
|
&'a mut self,
|
||||||
@@ -115,6 +128,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 +155,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.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.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>() {
|
||||||
|
if let ThunkState::Evaluated(v) = *thunk.borrow() {
|
||||||
|
self.convert_value_with_seen(v.relax(), seen)
|
||||||
|
} else {
|
||||||
Value::Thunk
|
Value::Thunk
|
||||||
} else if val.as_inline::<PrimOp>().is_some() {
|
}
|
||||||
Value::PrimOp("primop".into())
|
} else if let Some(primop) = val.as_inline::<PrimOp>() {
|
||||||
} else if val.is::<PrimOpApp>() {
|
let name = fix_builtins::BUILTINS[primop.id as usize].0;
|
||||||
Value::PrimOpApp("primop-app".into())
|
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
|
||||||
}
|
}
|
||||||
@@ -162,50 +206,73 @@ impl<T: VmRuntimeCtx> VmRuntimeCtxExt for T {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub(crate) enum Break {
|
enum Break {
|
||||||
Force,
|
Force,
|
||||||
Done,
|
Done,
|
||||||
|
LoadFile,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) type Step = std::ops::ControlFlow<Break>;
|
type Step = std::ops::ControlFlow<Break>;
|
||||||
|
|
||||||
#[derive(Collect)]
|
#[derive(Collect)]
|
||||||
#[collect(no_drop)]
|
#[collect(no_drop)]
|
||||||
pub struct Vm<'gc> {
|
pub struct Vm<'gc> {
|
||||||
pub(crate) stack: Vec<Value<'gc>>,
|
stack: Vec<Value<'gc>>,
|
||||||
pub(crate) call_stack: Vec<CallFrame<'gc>>,
|
call_stack: Vec<CallFrame<'gc>>,
|
||||||
pub(crate) call_depth: usize,
|
call_depth: usize,
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[collect(require_static)]
|
#[collect(require_static)]
|
||||||
pub(crate) error_context: Vec<ErrorFrame>,
|
error_context: Vec<ErrorFrame>,
|
||||||
|
|
||||||
pub(crate) env: GcEnv<'gc>,
|
env: GcEnv<'gc>,
|
||||||
pub(crate) with_env: Option<GcWithEnv<'gc>>,
|
|
||||||
|
|
||||||
pub(crate) import_cache: HashMap<PathBuf, Value<'gc>>,
|
import_cache: HashMap<PathBuf, Value<'gc>>,
|
||||||
|
scope_slots: Vec<Value<'gc>>,
|
||||||
|
|
||||||
pub(crate) builtins: Value<'gc>,
|
builtins: Value<'gc>,
|
||||||
pub(crate) empty_list: Value<'gc>,
|
empty_list: Value<'gc>,
|
||||||
pub(crate) empty_attrs: Value<'gc>,
|
empty_attrs: Value<'gc>,
|
||||||
|
|
||||||
pub(crate) force_mode: ForceMode,
|
force_mode: ForceMode,
|
||||||
|
|
||||||
#[collect(require_static)]
|
#[collect(require_static)]
|
||||||
pub(crate) result: Option<Result<fix_common::Value>>,
|
result: Option<Result<fix_common::Value>>,
|
||||||
|
|
||||||
|
#[collect(require_static)]
|
||||||
|
pending_load: Option<PendingLoad>,
|
||||||
|
|
||||||
|
functor_sym: StringId,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) enum OperandData {
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct PendingLoad {
|
||||||
|
pub path: PathBuf,
|
||||||
|
pub scope: Option<PendingScope>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct PendingScope {
|
||||||
|
pub keys: HashSet<StringId>,
|
||||||
|
pub slot_id: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OperandData {
|
||||||
Const(StaticValue),
|
Const(StaticValue),
|
||||||
Local { layer: u8, idx: u32 },
|
|
||||||
Builtins,
|
|
||||||
BigInt(i64),
|
BigInt(i64),
|
||||||
|
Local { layer: u8, idx: u32 },
|
||||||
|
BuiltinConst(StringId),
|
||||||
|
Builtins,
|
||||||
|
ReplBinding(StringId),
|
||||||
|
ScopedImportBinding { slot_id: u32, name: StringId },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OperandData {
|
impl OperandData {
|
||||||
pub(crate) fn resolve<'gc>(&self, mc: &Mutation<'gc>, root: &Vm<'gc>) -> Value<'gc> {
|
fn resolve<'gc>(&self, mc: &Mutation<'gc>, root: &Vm<'gc>) -> Value<'gc> {
|
||||||
|
use OperandData::*;
|
||||||
match *self {
|
match *self {
|
||||||
OperandData::Const(sv) => sv.into(),
|
Const(sv) => sv.into(),
|
||||||
OperandData::Local { layer, idx } => {
|
BigInt(val) => Value::new_gc(Gc::new(mc, val)),
|
||||||
|
Local { layer, idx } => {
|
||||||
let mut cur = root.env;
|
let mut cur = root.env;
|
||||||
for _ in 0..layer {
|
for _ in 0..layer {
|
||||||
let prev = cur.borrow().prev.expect("env chain too short");
|
let prev = cur.borrow().prev.expect("env chain too short");
|
||||||
@@ -213,15 +280,28 @@ impl OperandData {
|
|||||||
}
|
}
|
||||||
cur.borrow().locals[idx as usize]
|
cur.borrow().locals[idx as usize]
|
||||||
}
|
}
|
||||||
OperandData::Builtins => root.builtins,
|
#[allow(clippy::unwrap_used)]
|
||||||
OperandData::BigInt(val) => Value::new_gc(Gc::new(mc, val)),
|
BuiltinConst(id) => root
|
||||||
|
.builtins
|
||||||
|
.as_gc::<AttrSet>()
|
||||||
|
.unwrap()
|
||||||
|
.lookup(id)
|
||||||
|
.unwrap(),
|
||||||
|
Builtins => root.builtins,
|
||||||
|
ReplBinding(_id) => todo!(),
|
||||||
|
ScopedImportBinding { slot_id, name } => {
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let scope = root
|
||||||
|
.scope_slots
|
||||||
|
.get(slot_id as usize)
|
||||||
|
.expect("invalid scope slot");
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let attrs = scope.as_gc::<AttrSet>().expect("scope must be attrset");
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
attrs.lookup(name).expect("scoped binding not found")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) enum AttrKeyData {
|
|
||||||
Static(StringId),
|
|
||||||
Dynamic(OperandData),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 +311,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 +336,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 +355,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> {
|
||||||
@@ -288,9 +371,9 @@ impl<'gc> Vm<'gc> {
|
|||||||
error_context: Vec::with_capacity(1024),
|
error_context: Vec::with_capacity(1024),
|
||||||
|
|
||||||
env: Gc::new(mc, RefLock::new(Env::empty())),
|
env: Gc::new(mc, RefLock::new(Env::empty())),
|
||||||
with_env: None,
|
|
||||||
|
|
||||||
import_cache: HashMap::new(),
|
import_cache: HashMap::new(),
|
||||||
|
scope_slots: Vec::new(),
|
||||||
|
|
||||||
builtins,
|
builtins,
|
||||||
empty_list: Value::new_gc(Gc::new(mc, List::default())),
|
empty_list: Value::new_gc(Gc::new(mc, List::default())),
|
||||||
@@ -299,23 +382,26 @@ impl<'gc> Vm<'gc> {
|
|||||||
force_mode,
|
force_mode,
|
||||||
|
|
||||||
result: None,
|
result: None,
|
||||||
|
pending_load: None,
|
||||||
|
|
||||||
|
functor_sym: ctx.intern_string("__functor"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn finish_ok(&mut self, val: fix_common::Value) -> Step {
|
fn finish_ok(&mut self, val: fix_common::Value) -> Step {
|
||||||
self.result = Some(Ok(val));
|
self.result = Some(Ok(val));
|
||||||
Step::Break(Break::Done)
|
Step::Break(Break::Done)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn finish_err(&mut self, err: Box<Error>) -> Step {
|
fn finish_err(&mut self, err: Box<Error>) -> Step {
|
||||||
self.result = Some(Err(err));
|
self.result = Some(Err(err));
|
||||||
Step::Break(Break::Done)
|
Step::Break(Break::Done)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn finish_type_err(&mut self, expected: NixType, got: NixType) -> Step {
|
fn finish_type_err(&mut self, expected: NixType, got: NixType) -> Step {
|
||||||
self.result = Some(Err(Error::eval_error(format!(
|
self.result = Some(Err(Error::eval_error(format!(
|
||||||
"expected {expected}, got {got}"
|
"expected {expected}, got {got}"
|
||||||
))));
|
))));
|
||||||
@@ -323,24 +409,24 @@ impl<'gc> Vm<'gc> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn finish_vm_err(&mut self, err: VmError) -> Step {
|
fn finish_vm_err(&mut self, err: VmError) -> Step {
|
||||||
self.finish_err(err.into_error())
|
self.finish_err(err.into_error())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn push(&mut self, val: Value<'gc>) {
|
fn push(&mut self, val: Value<'gc>) {
|
||||||
self.stack.push(val);
|
self.stack.push(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn pop(&mut self) -> Value<'gc> {
|
fn pop(&mut self) -> Value<'gc> {
|
||||||
self.stack.pop().expect("stack underflow")
|
self.stack.pop().expect("stack underflow")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn peek(&mut self, depth: usize) -> Value<'gc> {
|
fn peek(&mut self, depth: usize) -> Value<'gc> {
|
||||||
*self
|
*self
|
||||||
.stack
|
.stack
|
||||||
.get(self.stack.len() - depth - 1)
|
.get(self.stack.len() - depth - 1)
|
||||||
@@ -349,7 +435,7 @@ impl<'gc> Vm<'gc> {
|
|||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn peek_forced(&mut self, depth: usize) -> StrictValue<'gc> {
|
fn peek_forced(&mut self, depth: usize) -> StrictValue<'gc> {
|
||||||
self.stack
|
self.stack
|
||||||
.get(self.stack.len() - depth - 1)
|
.get(self.stack.len() - depth - 1)
|
||||||
.expect("stack underflow")
|
.expect("stack underflow")
|
||||||
@@ -358,7 +444,7 @@ impl<'gc> Vm<'gc> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn replace(&mut self, depth: usize, val: Value<'gc>) {
|
fn replace(&mut self, depth: usize, val: Value<'gc>) {
|
||||||
let len = self.stack.len();
|
let len = self.stack.len();
|
||||||
*self
|
*self
|
||||||
.stack
|
.stack
|
||||||
@@ -368,7 +454,7 @@ impl<'gc> Vm<'gc> {
|
|||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[cfg_attr(debug_assertions, track_caller)]
|
#[cfg_attr(debug_assertions, track_caller)]
|
||||||
pub(crate) fn pop_forced(&mut self) -> StrictValue<'gc> {
|
fn pop_forced(&mut self) -> StrictValue<'gc> {
|
||||||
self.stack
|
self.stack
|
||||||
.pop()
|
.pop()
|
||||||
.expect("stack underflow")
|
.expect("stack underflow")
|
||||||
@@ -376,51 +462,103 @@ impl<'gc> Vm<'gc> {
|
|||||||
.expect("forced")
|
.expect("forced")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Force the top `T::WIDTH` stack slots and return them as `T`.
|
||||||
|
///
|
||||||
|
/// If any slot holds a pending thunk, this method pushes a call frame
|
||||||
|
/// whose resume PC is the **start of the current instruction**
|
||||||
|
/// (`reader.inst_start_pc()`), enters the thunk, and returns
|
||||||
|
/// `Break::Force`. When the thunk eventually returns, the VM will
|
||||||
|
/// **re-execute the entire opcode handler from the beginning**.
|
||||||
|
///
|
||||||
|
/// # Invariants
|
||||||
|
///
|
||||||
|
/// * **Do not call this method more than once in a single handler.**
|
||||||
|
/// If you need to force multiple values, use a tuple type such as
|
||||||
|
/// `(StrictValue, StrictValue)` so they are forced and popped in one
|
||||||
|
/// atomic operation. Calling `force_and_retry` twice (or more)
|
||||||
|
/// means the handler will be re-run from the top after each retry;
|
||||||
|
/// any stack modifications between the two calls would be duplicated
|
||||||
|
/// and corrupt the stack layout.
|
||||||
|
///
|
||||||
|
/// * The caller must ensure that the stack layout at the point of
|
||||||
|
/// invocation is **identical** every time the handler is re-entered.
|
||||||
|
/// In practice this means no pushes, pops, or local mutations may
|
||||||
|
/// happen before the call, and the call must be the first thing
|
||||||
|
/// that consumes the instruction's operand values.
|
||||||
|
///
|
||||||
|
/// * The return value must be propagated with `?` so that
|
||||||
|
/// `Break::Force` correctly unwinds to the dispatch loop.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn try_force<T: Forced<'gc>>(
|
fn force_and_retry<T: Forced<'gc>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
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.force_and_retry_pc(reader, mc, reader.inst_start_pc())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Same as [`force_and_retry`](Self::force_and_retry) but allows
|
||||||
|
/// specifying a custom resume PC.
|
||||||
|
#[inline(always)]
|
||||||
|
fn force_and_retry_pc<T: Forced<'gc>>(
|
||||||
|
&mut self,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
resume_pc: usize,
|
||||||
|
) -> std::ops::ControlFlow<Break, T> {
|
||||||
|
T::force_and_check(self, reader, mc, 0, resume_pc)?;
|
||||||
std::ops::ControlFlow::Continue(T::pop_converted(self))
|
std::ops::ControlFlow::Continue(T::pop_converted(self))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn force_slot(
|
#[allow(unused)]
|
||||||
|
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)]
|
||||||
|
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(());
|
||||||
};
|
};
|
||||||
let mut state = thunk.borrow_mut(mc);
|
let mut state = thunk.borrow_mut(mc);
|
||||||
match *state {
|
match *state {
|
||||||
ThunkState::Pending { ip, env, with_env } => {
|
ThunkState::Pending { ip, env } => {
|
||||||
*state = ThunkState::Blackhole;
|
*state = ThunkState::Blackhole;
|
||||||
drop(state);
|
|
||||||
self.call_stack.push(CallFrame {
|
self.call_stack.push(CallFrame {
|
||||||
thunk: Some(thunk),
|
thunk: Some(thunk),
|
||||||
stack_depth: depth,
|
pc: resume_pc,
|
||||||
pc: reader.inst_start_pc(),
|
|
||||||
env: self.env,
|
env: self.env,
|
||||||
with_env: self.with_env,
|
|
||||||
});
|
});
|
||||||
self.env = env;
|
self.env = env;
|
||||||
self.with_env = with_env;
|
|
||||||
reader.set_pc(ip);
|
reader.set_pc(ip);
|
||||||
Step::Break(Break::Force)
|
Step::Break(Break::Force)
|
||||||
}
|
}
|
||||||
ThunkState::Evaluated(v) => {
|
ThunkState::Evaluated(v) => {
|
||||||
drop(state);
|
|
||||||
self.replace(depth, v.relax());
|
self.replace(depth, v.relax());
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
ThunkState::Apply { .. } => todo!("force apply"),
|
ThunkState::Apply { func, arg } => {
|
||||||
|
self.call_stack.push(CallFrame {
|
||||||
|
thunk: Some(thunk),
|
||||||
|
pc: resume_pc,
|
||||||
|
env: self.env,
|
||||||
|
});
|
||||||
|
self.push(func);
|
||||||
|
self.call(reader, mc, arg, resume_pc)
|
||||||
|
}
|
||||||
ThunkState::Blackhole => {
|
ThunkState::Blackhole => {
|
||||||
drop(state);
|
|
||||||
self.finish_err(Error::eval_error("infinite recursion encountered"))
|
self.finish_err(Error::eval_error("infinite recursion encountered"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -435,20 +573,19 @@ struct ErrorFrame {
|
|||||||
|
|
||||||
#[derive(Collect, Debug)]
|
#[derive(Collect, Debug)]
|
||||||
#[collect(no_drop)]
|
#[collect(no_drop)]
|
||||||
pub(crate) struct CallFrame<'gc> {
|
struct CallFrame<'gc> {
|
||||||
pub(crate) pc: usize,
|
pc: usize,
|
||||||
pub(crate) stack_depth: usize,
|
thunk: Option<Gc<'gc, Thunk<'gc>>>,
|
||||||
pub(crate) thunk: Option<Gc<'gc, Thunk<'gc>>>,
|
env: Gc<'gc, RefLock<Env<'gc>>>,
|
||||||
pub(crate) env: Gc<'gc, RefLock<Env<'gc>>>,
|
|
||||||
pub(crate) with_env: Option<Gc<'gc, WithEnv<'gc>>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) enum Action {
|
enum Action {
|
||||||
Continue { pc: usize },
|
Continue { pc: usize },
|
||||||
Done(Result<fix_common::Value>),
|
Done(Result<fix_common::Value>),
|
||||||
|
LoadFile(PendingLoad),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) enum NixNum {
|
enum NixNum {
|
||||||
Int(i64),
|
Int(i64),
|
||||||
Float(f64),
|
Float(f64),
|
||||||
}
|
}
|
||||||
@@ -478,6 +615,21 @@ impl Vm<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Action::LoadFile(load) => {
|
||||||
|
let source = match Source::new_file(load.path) {
|
||||||
|
Ok(src) => src,
|
||||||
|
Err(err) => break Err(Error::eval_error(format!("import failed: {err}"))),
|
||||||
|
};
|
||||||
|
let extra_scope = load.scope.map(|s| ExtraScope::ScopedImport {
|
||||||
|
keys: s.keys,
|
||||||
|
slot_id: s.slot_id,
|
||||||
|
});
|
||||||
|
let new_ip = match code.compile_with_scope(source, extra_scope, runtime) {
|
||||||
|
Ok(ip) => ip,
|
||||||
|
Err(err) => break Err(err),
|
||||||
|
};
|
||||||
|
pc = new_ip.0;
|
||||||
|
}
|
||||||
Action::Done(done) => break done,
|
Action::Done(done) => break done,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -509,6 +661,11 @@ impl<'gc> Vm<'gc> {
|
|||||||
TailResult::Done => {
|
TailResult::Done => {
|
||||||
Action::Done(self.result.take().expect("TailResult::Done without result"))
|
Action::Done(self.result.take().expect("TailResult::Done without result"))
|
||||||
}
|
}
|
||||||
|
TailResult::LoadFile => Action::LoadFile(
|
||||||
|
self.pending_load
|
||||||
|
.take()
|
||||||
|
.expect("TailResult::LoadFile without pending_load"),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -554,6 +711,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,29 +742,26 @@ 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),
|
||||||
Jump => self.op_jump(&mut reader),
|
Jump => self.op_jump(&mut reader),
|
||||||
|
|
||||||
ConcatStrings => self.op_concat_strings(ctx, &mut reader, mc),
|
ConcatStrings => self.op_concat_strings(ctx, &mut reader, mc),
|
||||||
ResolvePath => self.op_resolve_path(ctx),
|
CoerceToString => self.op_coerce_to_string(&mut reader, mc),
|
||||||
|
ResolvePath => self.op_resolve_path(ctx, &mut reader, mc),
|
||||||
|
|
||||||
Assert => self.op_assert(&mut reader),
|
Assert => self.op_assert(ctx, &mut reader, mc),
|
||||||
|
|
||||||
PushWith => self.op_push_with(ctx, &mut reader, mc),
|
|
||||||
PopWith => self.op_pop_with(),
|
|
||||||
LookupWith => self.op_lookup_with(ctx, &mut reader, mc),
|
LookupWith => self.op_lookup_with(ctx, &mut reader, mc),
|
||||||
PrepareWith => self.op_prepare_with(),
|
|
||||||
|
|
||||||
LoadBuiltins => self.op_load_builtins(),
|
LoadBuiltins => self.op_load_builtins(),
|
||||||
LoadBuiltin => self.op_load_builtin(&mut reader),
|
LoadBuiltin => self.op_load_builtin(&mut reader),
|
||||||
|
|
||||||
MkPos => self.op_mk_pos(&mut reader),
|
|
||||||
LoadReplBinding => self.op_load_repl_binding(&mut reader),
|
LoadReplBinding => self.op_load_repl_binding(&mut reader),
|
||||||
LoadScopedBinding => self.op_load_scoped_binding(&mut reader),
|
LoadScopedBinding => self.op_load_scoped_binding(ctx, &mut reader, mc),
|
||||||
|
|
||||||
Illegal => unreachable!(),
|
Illegal => unreachable!(),
|
||||||
};
|
};
|
||||||
@@ -616,6 +771,13 @@ impl<'gc> Vm<'gc> {
|
|||||||
Step::Break(Break::Done) => {
|
Step::Break(Break::Done) => {
|
||||||
return Action::Done(self.result.take().expect("Break::Done without result"));
|
return Action::Done(self.result.take().expect("Break::Done without result"));
|
||||||
}
|
}
|
||||||
|
Step::Break(Break::LoadFile) => {
|
||||||
|
return Action::LoadFile(
|
||||||
|
self.pending_load
|
||||||
|
.take()
|
||||||
|
.expect("Break::LoadFile without pending_load"),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -0,0 +1,364 @@
|
|||||||
|
use fix_builtins::PrimOpPhase;
|
||||||
|
use fix_error::Error;
|
||||||
|
use gc_arena::{Gc, Mutation, RefLock};
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
|
use crate::value::*;
|
||||||
|
use crate::{BytecodeReader, Step, Vm, VmRuntimeCtx, VmRuntimeCtxExt};
|
||||||
|
|
||||||
|
impl<'gc> Vm<'gc> {
|
||||||
|
pub(crate) fn primop_seq(
|
||||||
|
&mut self,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
// stack: [e1, e2] - force e1, return e2
|
||||||
|
self.force_slot(1, reader, mc)?;
|
||||||
|
let e2 = self.pop();
|
||||||
|
let _ = self.pop();
|
||||||
|
self.return_from_primop(e2, reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn primop_abort(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
// stack: [msg] - force msg, then abort with it
|
||||||
|
self.force_slot(0, reader, mc)?;
|
||||||
|
let msg_val = self.peek_forced(0);
|
||||||
|
let msg = ctx.get_string(msg_val).unwrap_or("<non-string-value>");
|
||||||
|
self.finish_err(Error::eval_error(format!(
|
||||||
|
"evaluation aborted with the following error message: '{msg}'"
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn primop_deep_seq_force_top(
|
||||||
|
&mut self,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
// stack: [e1, e2] - force e1, return e2
|
||||||
|
self.force_slot(1, reader, mc)?;
|
||||||
|
|
||||||
|
let e1 = self.peek_forced(1);
|
||||||
|
|
||||||
|
let children: SmallVec<_> = if let Some(attrs) = e1.as_gc::<AttrSet>() {
|
||||||
|
let attrs = &attrs.entries;
|
||||||
|
if attrs.is_empty() {
|
||||||
|
SmallVec::new()
|
||||||
|
} else {
|
||||||
|
attrs.iter().map(|&(_, v)| v).collect()
|
||||||
|
}
|
||||||
|
} else if let Some(list) = e1.as_gc::<List<'gc>>() {
|
||||||
|
let inner = list.inner.borrow();
|
||||||
|
if inner.is_empty() {
|
||||||
|
SmallVec::new()
|
||||||
|
} else {
|
||||||
|
inner.iter().copied().collect()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SmallVec::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
if children.is_empty() {
|
||||||
|
let e2 = self.pop();
|
||||||
|
let _ = self.pop();
|
||||||
|
return self.return_from_primop(e2, reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
let count = children.len() as i32;
|
||||||
|
let seen: Gc<'gc, List<'gc>> = Gc::new(mc, List::default());
|
||||||
|
let worklist: Gc<'gc, List<'gc>> = List::new(mc, children);
|
||||||
|
|
||||||
|
let e2 = self.pop();
|
||||||
|
let _ = self.pop();
|
||||||
|
self.push(e2);
|
||||||
|
self.push(Value::new_gc(seen));
|
||||||
|
self.push(Value::new_gc(worklist));
|
||||||
|
self.push(Value::new_inline(count));
|
||||||
|
reader.set_pc(PrimOpPhase::DeepSeqPush.ip() as usize);
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn primop_deep_seq_push(
|
||||||
|
&mut self,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
// stack: [e2, seen, worklist, counter]
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let counter = self.peek(0).as_inline::<i32>().unwrap();
|
||||||
|
if counter == 0 {
|
||||||
|
let _ = self.pop(); // counter
|
||||||
|
let _ = self.pop(); // worklist
|
||||||
|
let _ = self.pop(); // seen
|
||||||
|
let val = self.pop();
|
||||||
|
return self.return_from_primop(val, reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let worklist = self.peek_forced(1).as_gc::<List<'gc>>().unwrap();
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let item = worklist.unlock(mc).borrow_mut().pop().unwrap();
|
||||||
|
self.replace(0, Value::new_inline(counter - 1));
|
||||||
|
self.push(item);
|
||||||
|
|
||||||
|
// force item at TOS, resume at DeepSeqLoop after force
|
||||||
|
self.force_slot_to_pc(0, reader, mc, PrimOpPhase::DeepSeqLoop.ip() as usize)?;
|
||||||
|
reader.set_pc(PrimOpPhase::DeepSeqLoop.ip() as usize);
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn primop_deep_seq_loop(
|
||||||
|
&mut self,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
// stack after pop: [e2, seen, worklist, counter]
|
||||||
|
let item = self.pop();
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let counter = self.peek(0).as_inline::<i32>().unwrap();
|
||||||
|
|
||||||
|
let mut added: usize = 0;
|
||||||
|
if let Some(attrs) = item.as_gc::<AttrSet>() {
|
||||||
|
let attrs = &attrs.entries;
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let seen = self.peek_forced(2).as_gc::<List<'gc>>().unwrap();
|
||||||
|
if !self.is_value_in_seen(seen, item) {
|
||||||
|
self.add_value_to_seen(seen, mc, item);
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let worklist = self.peek_forced(1).as_gc::<List<'gc>>().unwrap();
|
||||||
|
{
|
||||||
|
let mut wl = worklist.unlock(mc).borrow_mut();
|
||||||
|
for &(_, v) in attrs.iter() {
|
||||||
|
wl.push(v);
|
||||||
|
}
|
||||||
|
added = attrs.len();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let Some(list) = item.as_gc::<List<'gc>>() {
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let seen = self.peek_forced(2).as_gc::<List<'gc>>().unwrap();
|
||||||
|
if !self.is_value_in_seen(seen, item) {
|
||||||
|
self.add_value_to_seen(seen, mc, item);
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let worklist = self.peek_forced(1).as_gc::<List<'gc>>().unwrap();
|
||||||
|
{
|
||||||
|
let inner = list.inner.borrow();
|
||||||
|
let mut wl = worklist.unlock(mc).borrow_mut();
|
||||||
|
for &v in inner.iter() {
|
||||||
|
wl.push(v);
|
||||||
|
}
|
||||||
|
added = inner.len();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.replace(0, Value::new_inline(counter + added as i32));
|
||||||
|
reader.set_pc(PrimOpPhase::DeepSeqPush.ip() as usize);
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn primop_force_result_shallow(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
self.force_slot(0, reader, mc)?;
|
||||||
|
let val = self.peek_forced(0);
|
||||||
|
|
||||||
|
let (count, has_children) = if let Some(attrs) = val.as_gc::<AttrSet>() {
|
||||||
|
let len = attrs.entries.len();
|
||||||
|
(len, len > 0)
|
||||||
|
} else if let Some(list) = val.as_gc::<List<'gc>>() {
|
||||||
|
let len = list.inner.borrow().len();
|
||||||
|
(len, len > 0)
|
||||||
|
} else {
|
||||||
|
(0, false)
|
||||||
|
};
|
||||||
|
|
||||||
|
if !has_children {
|
||||||
|
let val = self.pop();
|
||||||
|
return self.finish_ok(ctx.convert_value(val));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.push(Value::new_inline(0i32));
|
||||||
|
self.push(Value::new_inline(count as i32));
|
||||||
|
reader.set_pc(PrimOpPhase::ForceResultShallowPush.ip() as usize);
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn primop_force_result_shallow_push(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let idx = self.peek(1).as_inline::<i32>().unwrap();
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let len = self.peek(0).as_inline::<i32>().unwrap();
|
||||||
|
|
||||||
|
if idx == len {
|
||||||
|
let _ = self.pop(); // len
|
||||||
|
let _ = self.pop(); // idx
|
||||||
|
let val = self.pop();
|
||||||
|
return self.finish_ok(ctx.convert_value(val));
|
||||||
|
}
|
||||||
|
|
||||||
|
let val = self.peek_forced(2);
|
||||||
|
let child = if let Some(attrs) = val.as_gc::<AttrSet>() {
|
||||||
|
attrs.entries.get(idx as usize).map(|&(_, v)| v)
|
||||||
|
} else if let Some(list) = val.as_gc::<List<'gc>>() {
|
||||||
|
list.inner.borrow().get(idx as usize).copied()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(child) = child {
|
||||||
|
self.replace(1, Value::new_inline(idx + 1));
|
||||||
|
self.push(child);
|
||||||
|
self.force_slot_to_pc(
|
||||||
|
0,
|
||||||
|
reader,
|
||||||
|
mc,
|
||||||
|
PrimOpPhase::ForceResultShallowLoop.ip() as usize,
|
||||||
|
)?;
|
||||||
|
reader.set_pc(PrimOpPhase::ForceResultShallowLoop.ip() as usize);
|
||||||
|
}
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn primop_force_result_shallow_loop(
|
||||||
|
&mut self,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
_mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
let _ = self.pop(); // forced child
|
||||||
|
reader.set_pc(PrimOpPhase::ForceResultShallowPush.ip() as usize);
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn primop_force_result_deep_finish(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
let val = self.force_and_retry::<StrictValue>(reader, mc)?;
|
||||||
|
self.finish_ok(ctx.convert_value(val.relax()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_value_in_seen(&self, seen: Gc<'gc, List<'gc>>, val: Value<'gc>) -> bool {
|
||||||
|
if !is_container(val) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let target = val.to_bits();
|
||||||
|
for &v in seen.inner.borrow().iter() {
|
||||||
|
if v.to_bits() == target {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_value_to_seen(&self, seen: Gc<'gc, List<'gc>>, mc: &Mutation<'gc>, val: Value<'gc>) {
|
||||||
|
if is_container(val) {
|
||||||
|
seen.unlock(mc).borrow_mut().push(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn primop_call_functor_1(
|
||||||
|
&mut self,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
// Stack invariant on every (re-)entry: [..., orig_arg, self, functor]
|
||||||
|
// where `functor` is TOS. Retries during force land back here safely.
|
||||||
|
let functor = self.force_and_retry::<StrictValue>(reader, mc)?;
|
||||||
|
// Stack now: [..., orig_arg, self]
|
||||||
|
let self_val = self.pop();
|
||||||
|
self.push(functor.relax());
|
||||||
|
// Stack: [..., orig_arg, functor]
|
||||||
|
// Call 1: functor(self). Resume into CallFunctor2 once it returns.
|
||||||
|
self.call(
|
||||||
|
reader,
|
||||||
|
mc,
|
||||||
|
self_val,
|
||||||
|
PrimOpPhase::CallFunctor2.ip() as usize,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn primop_call_functor_2(
|
||||||
|
&mut self,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
// Stack on entry: [..., orig_arg, intermediate]
|
||||||
|
// call_stack top: synthetic frame with caller's resume_pc.
|
||||||
|
let intermediate = self.pop();
|
||||||
|
let orig_arg = self.pop();
|
||||||
|
let saved = self.call_stack.pop().expect("functor outer frame missing");
|
||||||
|
self.env = saved.env;
|
||||||
|
self.push(intermediate);
|
||||||
|
// Call 2: intermediate(orig_arg). Resume to caller.
|
||||||
|
self.call(reader, mc, orig_arg, saved.pc)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn primop_call_pattern(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
let (func, attrset) = self.force_and_retry::<(Gc<Closure>, Gc<AttrSet>)>(reader, mc)?;
|
||||||
|
|
||||||
|
let Closure {
|
||||||
|
ip,
|
||||||
|
n_locals,
|
||||||
|
env,
|
||||||
|
pattern,
|
||||||
|
} = *func;
|
||||||
|
let Some(pattern) = pattern else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
// TODO: get function name
|
||||||
|
// TODO: param spans
|
||||||
|
if !pattern.ellipsis {
|
||||||
|
for key in pattern.required.iter().copied() {
|
||||||
|
if attrset.lookup(key).is_none() {
|
||||||
|
let name = ctx.resolve_string(key);
|
||||||
|
return self.finish_err(Error::eval_error(format!(
|
||||||
|
"function 'anonymous lambda' called without required argument '{name}'"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for &(key, _) in attrset.entries.iter() {
|
||||||
|
let is_expected =
|
||||||
|
pattern.required.contains(&key) || pattern.optional.contains(&key);
|
||||||
|
if !is_expected {
|
||||||
|
let name = ctx.resolve_string(key);
|
||||||
|
return self.finish_err(Error::eval_error(format!(
|
||||||
|
"function 'anonymous lambda' called with unexpected argument '{name}'"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_env = Gc::new(
|
||||||
|
mc,
|
||||||
|
RefLock::new(Env::with_arg(Value::new_gc(attrset), n_locals, env)),
|
||||||
|
);
|
||||||
|
reader.set_pc(ip as usize);
|
||||||
|
self.env = new_env;
|
||||||
|
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_container(val: Value<'_>) -> bool {
|
||||||
|
val.is::<AttrSet>() || val.is::<List<'_>>()
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use fix_builtins::PrimOpPhase;
|
||||||
|
use fix_common::StringId;
|
||||||
|
use fix_error::Error;
|
||||||
|
use gc_arena::{Gc, Mutation};
|
||||||
|
use hashbrown::HashSet;
|
||||||
|
|
||||||
|
use crate::bytecode_reader::BytecodeReader;
|
||||||
|
use crate::value::*;
|
||||||
|
use crate::{Break, CallFrame, PendingLoad, PendingScope, Step, Vm, VmRuntimeCtx, VmRuntimeCtxExt};
|
||||||
|
|
||||||
|
impl<'gc> Vm<'gc> {
|
||||||
|
pub(crate) fn primop_import(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
// stack: [path]
|
||||||
|
let path_val = self.force_and_retry::<StrictValue>(reader, mc)?;
|
||||||
|
let path_str = match ctx.get_string(path_val) {
|
||||||
|
Some(s) => s.to_owned(),
|
||||||
|
None => {
|
||||||
|
return self.finish_err(Error::eval_error(format!(
|
||||||
|
"expected a path string, got {}",
|
||||||
|
path_val.ty()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let abs = match resolve_import_target(&path_str) {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(e) => return self.finish_err(e),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(&cached) = self.import_cache.get(&abs) {
|
||||||
|
return self.return_from_primop(cached, reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stash the resolved path on the stack as a string-id so the
|
||||||
|
// finalizer can use it as the cache key. The slot we pop here was
|
||||||
|
// freed by `force_and_retry`, so we simply push.
|
||||||
|
let path_sid = ctx.intern_string(abs.to_string_lossy());
|
||||||
|
self.push(Value::new_inline(path_sid));
|
||||||
|
self.call_stack.push(CallFrame {
|
||||||
|
pc: PrimOpPhase::ImportFinalize.ip() as usize,
|
||||||
|
thunk: None,
|
||||||
|
env: self.env,
|
||||||
|
});
|
||||||
|
|
||||||
|
self.pending_load = Some(PendingLoad {
|
||||||
|
path: abs,
|
||||||
|
scope: None,
|
||||||
|
});
|
||||||
|
Step::Break(Break::LoadFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn primop_import_finalize(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
) -> Step {
|
||||||
|
// stack: [path_sid, return_value]
|
||||||
|
let val = self.pop();
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let path_sid = self.pop().as_inline::<StringId>().unwrap();
|
||||||
|
// The cache key is keyed by the absolute path string we interned in
|
||||||
|
// `primop_import`. Resolve it back to the host PathBuf.
|
||||||
|
let path_str = ctx.resolve_string(path_sid).to_owned();
|
||||||
|
self.import_cache.insert(PathBuf::from(path_str), val);
|
||||||
|
self.push(val);
|
||||||
|
let Some(CallFrame {
|
||||||
|
pc: ret_pc,
|
||||||
|
thunk: _,
|
||||||
|
env,
|
||||||
|
}) = self.call_stack.pop()
|
||||||
|
else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
reader.set_pc(ret_pc);
|
||||||
|
// FIXME:
|
||||||
|
// self.call_depth -= 1;
|
||||||
|
self.env = env;
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn primop_scoped_import(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
// stack: [scope, path]
|
||||||
|
let (scope_attrs, path_val) =
|
||||||
|
self.force_and_retry::<(Gc<AttrSet>, StrictValue)>(reader, mc)?;
|
||||||
|
let path_str = match ctx.get_string(path_val) {
|
||||||
|
Some(s) => s.to_owned(),
|
||||||
|
None => {
|
||||||
|
return self.finish_err(Error::eval_error(format!(
|
||||||
|
"expected a path string, got {}",
|
||||||
|
path_val.ty()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let abs = match resolve_import_target(&path_str) {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(e) => return self.finish_err(e),
|
||||||
|
};
|
||||||
|
|
||||||
|
let keys: HashSet<StringId> = scope_attrs.entries.iter().map(|&(k, _)| k).collect();
|
||||||
|
let slot_id = self.scope_slots.len() as u32;
|
||||||
|
self.scope_slots.push(Value::new_gc(scope_attrs));
|
||||||
|
|
||||||
|
self.call_stack.push(CallFrame {
|
||||||
|
pc: PrimOpPhase::ScopedImportFinalize.ip() as usize,
|
||||||
|
thunk: None,
|
||||||
|
env: self.env,
|
||||||
|
});
|
||||||
|
|
||||||
|
self.pending_load = Some(PendingLoad {
|
||||||
|
path: abs,
|
||||||
|
scope: Some(PendingScope { keys, slot_id }),
|
||||||
|
});
|
||||||
|
Step::Break(Break::LoadFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn primop_scoped_import_finalize(
|
||||||
|
&mut self,
|
||||||
|
_ctx: &mut impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
_mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
// stack: [return_value]
|
||||||
|
// We intentionally do NOT pop the slot from `scope_slots` so that
|
||||||
|
// closures or thunks created inside the imported file can still
|
||||||
|
// resolve their scope after `scopedImport` returns.
|
||||||
|
let val = self.pop();
|
||||||
|
self.return_from_primop(val, reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn primop_path_exists(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
let path_val = self.force_and_retry::<StrictValue>(reader, mc)?;
|
||||||
|
let path = match ctx.get_string(path_val) {
|
||||||
|
Some(s) => s.to_owned(),
|
||||||
|
None => {
|
||||||
|
return self.finish_err(Error::eval_error(format!(
|
||||||
|
"expected a path string, got {}",
|
||||||
|
path_val.ty()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let must_be_dir = path.ends_with('/') || path.ends_with("/.");
|
||||||
|
let p = std::path::Path::new(&path);
|
||||||
|
let exists = if must_be_dir {
|
||||||
|
std::fs::metadata(p).map(|m| m.is_dir()).unwrap_or(false)
|
||||||
|
} else {
|
||||||
|
std::fs::symlink_metadata(p).is_ok()
|
||||||
|
};
|
||||||
|
self.return_from_primop(Value::new_inline(exists), reader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert the user-supplied path string into an absolute, dotted-segment
|
||||||
|
/// resolved `PathBuf` and append `default.nix` if the target is a directory.
|
||||||
|
fn resolve_import_target(path: &str) -> Result<PathBuf, Box<Error>> {
|
||||||
|
let mut abs = PathBuf::from(path);
|
||||||
|
if !abs.is_absolute() {
|
||||||
|
return Err(Error::eval_error(format!(
|
||||||
|
"import: expected an absolute path, got '{path}'"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
if abs.is_dir() {
|
||||||
|
abs.push("default.nix");
|
||||||
|
}
|
||||||
|
Ok(abs)
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
use fix_builtins::PrimOpPhase;
|
||||||
|
use gc_arena::Mutation;
|
||||||
|
|
||||||
|
use crate::bytecode_reader::BytecodeReader;
|
||||||
|
use crate::value::*;
|
||||||
|
use crate::{Step, Vm};
|
||||||
|
|
||||||
|
impl<'gc> Vm<'gc> {
|
||||||
|
pub(crate) fn primop_filter_force_list(
|
||||||
|
&mut self,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
self.force_slot(0, reader, mc)?;
|
||||||
|
let list = match self.peek_forced(0).expect_gc::<List>() {
|
||||||
|
Ok(list) => list,
|
||||||
|
Err(got) => return self.finish_type_err(NixType::List, got),
|
||||||
|
};
|
||||||
|
if list.inner.borrow().is_empty() {
|
||||||
|
let val = self.pop();
|
||||||
|
return self.return_from_primop(val, reader);
|
||||||
|
}
|
||||||
|
// prepare stack layout: [ pred list idx acc ]
|
||||||
|
self.push(Value::new_inline(0));
|
||||||
|
self.push(Value::new_gc(List::new_gc(mc)));
|
||||||
|
reader.set_pc(PrimOpPhase::FilterCallPred.ip() as usize);
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn primop_filter_call_pred(
|
||||||
|
&mut self,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
self.force_slot(3, reader, mc)?;
|
||||||
|
let pred = self.peek_forced(3);
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let idx = self.peek(1).as_inline::<i32>().unwrap();
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let elem = self.peek_forced(2).as_gc::<List>().unwrap().inner.borrow()[idx as usize];
|
||||||
|
self.push(pred.relax());
|
||||||
|
self.call(reader, mc, elem, PrimOpPhase::FilterCheck.ip() as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn primop_filter_check(
|
||||||
|
&mut self,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
let ret = self.force_and_retry::<bool>(reader, mc)?;
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let idx = self.peek(1).as_inline::<i32>().unwrap();
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let list = self.peek_forced(2).as_gc::<List>().unwrap();
|
||||||
|
let list = list.inner.borrow();
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let acc = self.peek_forced(0).as_gc::<List>().unwrap();
|
||||||
|
if ret {
|
||||||
|
let mut acc = acc.unlock(mc).borrow_mut();
|
||||||
|
acc.push(list[idx as usize]);
|
||||||
|
}
|
||||||
|
if idx as usize == list.len() - 1 {
|
||||||
|
let acc = self.pop();
|
||||||
|
let _ = self.pop(); // idx
|
||||||
|
let _ = self.pop(); // list
|
||||||
|
let _ = self.pop(); // pred
|
||||||
|
return self.return_from_primop(acc, reader);
|
||||||
|
}
|
||||||
|
self.replace(1, Value::new_inline(idx + 1));
|
||||||
|
reader.set_pc(PrimOpPhase::FilterCallPred.ip() as usize);
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
use fix_builtins::PrimOpPhase;
|
||||||
|
use fix_error::Error;
|
||||||
|
use gc_arena::Mutation;
|
||||||
|
use num_enum::TryFromPrimitive;
|
||||||
|
|
||||||
|
use crate::bytecode_reader::BytecodeReader;
|
||||||
|
use crate::value::Value;
|
||||||
|
use crate::{CallFrame, Step, Vm, VmRuntimeCtx};
|
||||||
|
|
||||||
|
mod attrs;
|
||||||
|
mod control;
|
||||||
|
mod conv;
|
||||||
|
mod io;
|
||||||
|
mod list;
|
||||||
|
mod path;
|
||||||
|
mod regex;
|
||||||
|
mod version;
|
||||||
|
|
||||||
|
impl<'gc> Vm<'gc> {
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
|
pub(crate) fn op_dispatch_primop(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
use PrimOpPhase::*;
|
||||||
|
let phase_disc = reader.read_u8();
|
||||||
|
let Ok(phase) = PrimOpPhase::try_from_primitive(phase_disc) else {
|
||||||
|
return self.finish_err(Error::eval_error("invalid primop phase"));
|
||||||
|
};
|
||||||
|
match phase {
|
||||||
|
Abort => self.primop_abort(ctx, reader, mc),
|
||||||
|
|
||||||
|
DeepSeq => self.primop_deep_seq_force_top(reader, mc),
|
||||||
|
DeepSeqPush => self.primop_deep_seq_push(reader, mc),
|
||||||
|
DeepSeqLoop => self.primop_deep_seq_loop(reader, mc),
|
||||||
|
Seq => self.primop_seq(reader, mc),
|
||||||
|
|
||||||
|
FilterForceList => self.primop_filter_force_list(reader, mc),
|
||||||
|
FilterCallPred => self.primop_filter_call_pred(reader, mc),
|
||||||
|
FilterCheck => self.primop_filter_check(reader, mc),
|
||||||
|
|
||||||
|
ForceResultShallow => self.primop_force_result_shallow(ctx, reader, mc),
|
||||||
|
ForceResultShallowPush => self.primop_force_result_shallow_push(ctx, reader, mc),
|
||||||
|
ForceResultShallowLoop => self.primop_force_result_shallow_loop(reader, mc),
|
||||||
|
ForceResultDeepFinish => self.primop_force_result_deep_finish(ctx, reader, mc),
|
||||||
|
|
||||||
|
CallPattern => self.primop_call_pattern(ctx, reader, mc),
|
||||||
|
CallFunctor1 => self.primop_call_functor_1(reader, mc),
|
||||||
|
CallFunctor2 => self.primop_call_functor_2(reader, mc),
|
||||||
|
|
||||||
|
Import => self.primop_import(ctx, reader, mc),
|
||||||
|
ImportFinalize => self.primop_import_finalize(ctx, reader),
|
||||||
|
ScopedImport => self.primop_scoped_import(ctx, reader, mc),
|
||||||
|
ScopedImportFinalize => self.primop_scoped_import_finalize(ctx, reader, mc),
|
||||||
|
|
||||||
|
PathExists => self.primop_path_exists(ctx, reader, mc),
|
||||||
|
|
||||||
|
phase => todo!("primop phase {phase:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn return_from_primop(&mut self, val: Value<'gc>, reader: &mut BytecodeReader<'_>) -> Step {
|
||||||
|
self.push(val);
|
||||||
|
let Some(CallFrame {
|
||||||
|
pc: ret_pc,
|
||||||
|
thunk: _,
|
||||||
|
env,
|
||||||
|
}) = self.call_stack.pop()
|
||||||
|
else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
reader.set_pc(ret_pc);
|
||||||
|
self.call_depth -= 1;
|
||||||
|
self.env = env;
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
+53
-27
@@ -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,14 +435,7 @@ 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: SmallVec<[(StringId, Value<'gc>); 4]>,
|
||||||
}
|
|
||||||
|
|
||||||
impl<'gc> Deref for AttrSet<'gc> {
|
|
||||||
type Target = [(StringId, Value<'gc>)];
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.entries
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'gc> AttrSet<'gc> {
|
impl<'gc> AttrSet<'gc> {
|
||||||
@@ -487,14 +491,31 @@ impl<'gc> AttrSet<'gc> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[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() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -503,15 +524,8 @@ pub(crate) type Thunk<'gc> = RefLock<ThunkState<'gc>>;
|
|||||||
#[derive(Collect, Debug)]
|
#[derive(Collect, Debug)]
|
||||||
#[collect(no_drop)]
|
#[collect(no_drop)]
|
||||||
pub(crate) enum ThunkState<'gc> {
|
pub(crate) enum ThunkState<'gc> {
|
||||||
Pending {
|
Pending { ip: usize, env: GcEnv<'gc> },
|
||||||
ip: usize,
|
Apply { func: Value<'gc>, arg: Value<'gc> },
|
||||||
env: GcEnv<'gc>,
|
|
||||||
with_env: Option<GcWithEnv<'gc>>,
|
|
||||||
},
|
|
||||||
Apply {
|
|
||||||
func: Value<'gc>,
|
|
||||||
arg: Value<'gc>,
|
|
||||||
},
|
|
||||||
Blackhole,
|
Blackhole,
|
||||||
Evaluated(StrictValue<'gc>),
|
Evaluated(StrictValue<'gc>),
|
||||||
}
|
}
|
||||||
@@ -572,22 +586,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 +621,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" }
|
||||||
|
|||||||
+214
-96
@@ -2,12 +2,16 @@
|
|||||||
#![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};
|
||||||
use fix_ir::{Ir, IrRef, MaybeThunk, RawIrRef, ThunkId};
|
use fix_ir::{
|
||||||
|
GhostMaybeThunkRef, GhostRoIrRef, GhostRoMaybeThunkRef, GhostRoRef, Ir, MaybeThunk, RawIrRef,
|
||||||
|
ThunkId,
|
||||||
|
};
|
||||||
use fix_vm::{ForceMode, StaticValue, Vm, VmCode, VmContext, VmRuntimeCtx};
|
use fix_vm::{ForceMode, StaticValue, Vm, VmCode, VmContext, VmRuntimeCtx};
|
||||||
use ghost_cell::{GhostCell, GhostToken};
|
use ghost_cell::{GhostCell, GhostToken};
|
||||||
use hashbrown::{HashMap, HashSet};
|
use hashbrown::{HashMap, HashSet};
|
||||||
@@ -29,7 +33,10 @@ pub struct CodeState {
|
|||||||
pub sources: Vec<Source>,
|
pub sources: Vec<Source>,
|
||||||
pub spans: Vec<(usize, rnix::TextRange)>,
|
pub spans: Vec<(usize, rnix::TextRange)>,
|
||||||
pub thunk_count: usize,
|
pub thunk_count: usize,
|
||||||
pub global_env: HashMap<StringId, Ir<'static, RawIrRef<'static>>>,
|
pub global_env: HashMap<StringId, MaybeThunk>,
|
||||||
|
/// Set during a compilation pass when the code is being compiled under a
|
||||||
|
/// `scopedImport` scope. Read by [`CompilerCtx::current_scope_slot`].
|
||||||
|
pub current_scope_slot: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Evaluator {
|
pub struct Evaluator {
|
||||||
@@ -47,6 +54,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,8 +68,9 @@ 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,
|
||||||
|
current_scope_slot: None,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -79,13 +92,13 @@ impl Evaluator {
|
|||||||
source: Source,
|
source: Source,
|
||||||
scope: &HashSet<StringId>,
|
scope: &HashSet<StringId>,
|
||||||
) -> Result<fix_common::Value> {
|
) -> Result<fix_common::Value> {
|
||||||
self.do_eval(source, Some(Scope::Repl(scope)), ForceMode::Shallow)
|
self.do_eval(source, Some(ExtraScope::Repl(scope)), ForceMode::Shallow)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn do_eval<'ctx>(
|
fn do_eval<'ctx>(
|
||||||
&'ctx mut self,
|
&'ctx mut self,
|
||||||
source: Source,
|
source: Source,
|
||||||
extra_scope: Option<Scope<'ctx>>,
|
extra_scope: Option<ExtraScope<'ctx>>,
|
||||||
force_mode: ForceMode,
|
force_mode: ForceMode,
|
||||||
) -> Result<fix_common::Value> {
|
) -> Result<fix_common::Value> {
|
||||||
let ip = {
|
let ip = {
|
||||||
@@ -104,7 +117,7 @@ impl Evaluator {
|
|||||||
_expr: &str,
|
_expr: &str,
|
||||||
_scope: &mut HashSet<StringId>,
|
_scope: &mut HashSet<StringId>,
|
||||||
) -> Result<fix_common::Value> {
|
) -> Result<fix_common::Value> {
|
||||||
todo!()
|
todo!("add_binding")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn compile_bytecode(&mut self, source: Source) -> Result<InstructionPtr> {
|
pub fn compile_bytecode(&mut self, source: Source) -> Result<InstructionPtr> {
|
||||||
@@ -141,16 +154,22 @@ impl VmCode for CodeState {
|
|||||||
fn bytecode(&self) -> &[u8] {
|
fn bytecode(&self) -> &[u8] {
|
||||||
&self.bytecode
|
&self.bytecode
|
||||||
}
|
}
|
||||||
fn compile(
|
fn compile_with_scope(
|
||||||
&mut self,
|
&mut self,
|
||||||
source: Source,
|
source: Source,
|
||||||
|
extra_scope: Option<fix_vm::ExtraScope>,
|
||||||
runtime: &mut impl VmRuntimeCtx,
|
runtime: &mut impl VmRuntimeCtx,
|
||||||
) -> Result<InstructionPtr> {
|
) -> Result<InstructionPtr> {
|
||||||
let mut compiler = CompilerCtx {
|
let mut compiler = CompilerCtx {
|
||||||
code: self,
|
code: self,
|
||||||
runtime,
|
runtime,
|
||||||
};
|
};
|
||||||
compiler.compile_bytecode(source, None)
|
let extra = extra_scope.map(|s| match s {
|
||||||
|
fix_vm::ExtraScope::ScopedImport { keys, slot_id } => {
|
||||||
|
ExtraScope::ScopedImport { keys, slot_id }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
compiler.compile_bytecode(source, extra)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,14 +188,23 @@ impl<'a, R: VmRuntimeCtx> CompilerCtx<'a, R> {
|
|||||||
fn compile_bytecode(
|
fn compile_bytecode(
|
||||||
&mut self,
|
&mut self,
|
||||||
source: Source,
|
source: Source,
|
||||||
extra_scope: Option<Scope>,
|
extra_scope: Option<ExtraScope>,
|
||||||
) -> Result<InstructionPtr> {
|
) -> Result<InstructionPtr> {
|
||||||
|
let prev_scope_slot = self.code.current_scope_slot;
|
||||||
|
self.code.current_scope_slot = match &extra_scope {
|
||||||
|
Some(ExtraScope::ScopedImport { slot_id, .. }) => Some(*slot_id),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
let result = (|| -> Result<InstructionPtr> {
|
||||||
let root = self.downgrade(source, extra_scope)?;
|
let root = self.downgrade(source, extra_scope)?;
|
||||||
let ip = fix_codegen::compile_bytecode(root.as_ref(), self);
|
let ip = fix_codegen::compile_bytecode(root.as_ref(), self);
|
||||||
Ok(ip)
|
Ok(ip)
|
||||||
|
})();
|
||||||
|
self.code.current_scope_slot = prev_scope_slot;
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
fn downgrade(&mut self, source: Source, extra_scope: Option<Scope>) -> Result<OwnedIr> {
|
fn downgrade(&mut self, source: Source, extra_scope: Option<ExtraScope>) -> Result<OwnedIr> {
|
||||||
tracing::debug!("Parsing Nix expression");
|
tracing::debug!("Parsing Nix expression");
|
||||||
|
|
||||||
self.code.sources.push(source.clone());
|
self.code.sources.push(source.clone());
|
||||||
@@ -196,7 +224,7 @@ impl<'a, R: VmRuntimeCtx> CompilerCtx<'a, R> {
|
|||||||
token,
|
token,
|
||||||
self.runtime,
|
self.runtime,
|
||||||
&self.code.global_env,
|
&self.code.global_env,
|
||||||
extra_scope,
|
extra_scope.map(Into::into),
|
||||||
&mut self.code.thunk_count,
|
&mut self.code.thunk_count,
|
||||||
source,
|
source,
|
||||||
);
|
);
|
||||||
@@ -239,11 +267,32 @@ 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),
|
Path(_) => todo!("path value type"),
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn current_source_dir(&mut self) -> StringId {
|
||||||
|
let dir = self
|
||||||
|
.code
|
||||||
|
.sources
|
||||||
|
.last()
|
||||||
|
.expect("current_source not set")
|
||||||
|
.get_dir()
|
||||||
|
.to_string_lossy()
|
||||||
|
.into_owned();
|
||||||
|
self.runtime.intern_string(dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_scope_slot(&self) -> Option<u32> {
|
||||||
|
self.code.current_scope_slot
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@@ -300,34 +349,20 @@ struct DowngradeCtx<'ctx, 'id, 'ir, R: VmRuntimeCtx> {
|
|||||||
token: GhostToken<'id>,
|
token: GhostToken<'id>,
|
||||||
runtime: &'ctx mut R,
|
runtime: &'ctx mut R,
|
||||||
source: Source,
|
source: Source,
|
||||||
scopes: Vec<Scope<'ctx>>,
|
scopes: Vec<Scope<'ctx, 'id, 'ir>>,
|
||||||
with_scope_count: u32,
|
with_stack: Vec<GhostRoMaybeThunkRef<'id, 'ir>>,
|
||||||
arg_count: u32,
|
arg_count: u32,
|
||||||
thunk_count: &'ctx mut usize,
|
thunk_count: &'ctx mut usize,
|
||||||
thunk_scopes: Vec<ThunkScope<'id, 'ir>>,
|
thunk_scopes: Vec<ThunkScope<'id, 'ir>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn should_thunk<'id>(ir: IrRef<'id, '_>, token: &GhostToken<'id>) -> bool {
|
|
||||||
!matches!(
|
|
||||||
ir.borrow(token),
|
|
||||||
Ir::Builtin(_)
|
|
||||||
| Ir::Builtins
|
|
||||||
| Ir::Int(_)
|
|
||||||
| Ir::Float(_)
|
|
||||||
| Ir::Bool(_)
|
|
||||||
| Ir::Null
|
|
||||||
| Ir::Str(_)
|
|
||||||
| Ir::Thunk(_)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'ctx, 'id, 'ir, R: VmRuntimeCtx> DowngradeCtx<'ctx, 'id, 'ir, R> {
|
impl<'ctx, 'id, 'ir, R: VmRuntimeCtx> DowngradeCtx<'ctx, 'id, 'ir, R> {
|
||||||
fn new(
|
fn new(
|
||||||
bump: &'ir Bump,
|
bump: &'ir Bump,
|
||||||
token: GhostToken<'id>,
|
token: GhostToken<'id>,
|
||||||
runtime: &'ctx mut R,
|
runtime: &'ctx mut R,
|
||||||
global: &'ctx HashMap<StringId, Ir<'static, RawIrRef<'static>>>,
|
global: &'ctx HashMap<StringId, MaybeThunk>,
|
||||||
extra_scope: Option<Scope<'ctx>>,
|
extra_scope: Option<Scope<'ctx, 'id, 'ir>>,
|
||||||
thunk_count: &'ctx mut usize,
|
thunk_count: &'ctx mut usize,
|
||||||
source: Source,
|
source: Source,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@@ -341,7 +376,7 @@ impl<'ctx, 'id, 'ir, R: VmRuntimeCtx> DowngradeCtx<'ctx, 'id, 'ir, R> {
|
|||||||
.collect(),
|
.collect(),
|
||||||
thunk_count,
|
thunk_count,
|
||||||
arg_count: 0,
|
arg_count: 0,
|
||||||
with_scope_count: 0,
|
with_stack: Vec::new(),
|
||||||
thunk_scopes: vec![ThunkScope::new_in(bump)],
|
thunk_scopes: vec![ThunkScope::new_in(bump)],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -350,31 +385,37 @@ impl<'ctx, 'id, 'ir, R: VmRuntimeCtx> DowngradeCtx<'ctx, 'id, 'ir, R> {
|
|||||||
impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir>
|
impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir>
|
||||||
for DowngradeCtx<'ctx, 'id, 'ir, R>
|
for DowngradeCtx<'ctx, 'id, 'ir, R>
|
||||||
{
|
{
|
||||||
fn new_expr(&self, expr: Ir<'ir, IrRef<'id, 'ir>>) -> IrRef<'id, 'ir> {
|
fn new_expr(&self, expr: Ir<'ir, GhostRoRef<'id, 'ir>>) -> GhostRoIrRef<'id, 'ir> {
|
||||||
IrRef::new(self.bump.alloc(GhostCell::new(expr)))
|
self.bump.alloc(GhostCell::new(expr).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn maybe_thunk(&mut self, ir: IrRef<'id, 'ir>) -> MaybeThunk {
|
fn maybe_thunk(&mut self, ir: GhostRoIrRef<'id, 'ir>) -> GhostRoMaybeThunkRef<'id, 'ir> {
|
||||||
use MaybeThunk::*;
|
use MaybeThunk::*;
|
||||||
match *ir.borrow(&self.token) {
|
let expr = (|| {
|
||||||
Ir::Builtin(x) => return Builtin(x),
|
let expr = match *ir.borrow(&self.token) {
|
||||||
Ir::Int(x) => return Int(x),
|
Ir::Builtin(x) => Builtin(x),
|
||||||
Ir::Float(x) => return Float(x),
|
Ir::Int(x) => Int(x),
|
||||||
Ir::Bool(x) => return Bool(x),
|
Ir::Float(x) => Float(x),
|
||||||
Ir::Str(x) => return Str(x),
|
Ir::Bool(x) => Bool(x),
|
||||||
Ir::Thunk(x) => return Thunk(x),
|
Ir::Str(x) => Str(x),
|
||||||
Ir::Arg { layer } => return Arg { layer },
|
Ir::Arg { layer } => Arg { layer },
|
||||||
Ir::Builtins => return Builtins,
|
Ir::Builtins => Builtins,
|
||||||
Ir::Null => return Null,
|
Ir::Null => Null,
|
||||||
_ => (),
|
Ir::MaybeThunk(thunk) => return Some(thunk),
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
Some(self.bump.alloc(GhostCell::new(expr).into()))
|
||||||
|
})();
|
||||||
|
if let Some(thunk) = expr {
|
||||||
|
return thunk;
|
||||||
}
|
}
|
||||||
let id = ThunkId(*self.thunk_count);
|
let id = ThunkId(*self.thunk_count);
|
||||||
*self.thunk_count = self.thunk_count.checked_add(1).expect("thunk id overflow");
|
*self.thunk_count = self.thunk_count.checked_add(1).expect("thunk id overflow");
|
||||||
self.thunk_scopes
|
self.thunk_scopes
|
||||||
.last_mut()
|
.last_mut()
|
||||||
.expect("no active cache scope")
|
.expect("no active cache scope")
|
||||||
.add_binding(id, ir, &self.token);
|
.add_binding(id, ir);
|
||||||
Thunk(id)
|
self.bump.alloc(GhostCell::new(Thunk(id)).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn intern_string(&mut self, sym: impl AsRef<str>) -> StringId {
|
fn intern_string(&mut self, sym: impl AsRef<str>) -> StringId {
|
||||||
@@ -385,35 +426,35 @@ impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir>
|
|||||||
self.runtime.resolve_string(id).into()
|
self.runtime.resolve_string(id).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lookup(&self, sym: StringId, span: rnix::TextRange) -> Result<MaybeThunk> {
|
fn lookup(
|
||||||
|
&mut self,
|
||||||
|
sym: StringId,
|
||||||
|
span: rnix::TextRange,
|
||||||
|
) -> Result<GhostRoMaybeThunkRef<'id, 'ir>> {
|
||||||
for scope in self.scopes.iter().rev() {
|
for scope in self.scopes.iter().rev() {
|
||||||
match scope {
|
match scope {
|
||||||
&Scope::Global(global_scope) => {
|
&Scope::Global(global_scope) => {
|
||||||
use MaybeThunk::*;
|
|
||||||
if let Some(expr) = global_scope.get(&sym) {
|
if let Some(expr) = global_scope.get(&sym) {
|
||||||
let val = match expr {
|
return Ok(expr.into());
|
||||||
Ir::Builtins => Builtins,
|
|
||||||
Ir::Builtin(s) => Builtin(*s),
|
|
||||||
Ir::Bool(b) => Bool(*b),
|
|
||||||
Ir::Null => Null,
|
|
||||||
_ => unreachable!("globals should only contain leaf IR nodes"),
|
|
||||||
};
|
|
||||||
return Ok(val);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&Scope::Repl(repl_bindings) => {
|
&Scope::Repl(repl_bindings) => {
|
||||||
if repl_bindings.contains(&sym) {
|
if repl_bindings.contains(&sym) {
|
||||||
return Ok(MaybeThunk::ReplBinding(sym));
|
return Ok(self
|
||||||
|
.bump
|
||||||
|
.alloc(GhostCell::new(MaybeThunk::ReplBinding(sym)).into()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Scope::ScopedImport(scoped_bindings) => {
|
Scope::ScopedImport { keys, .. } => {
|
||||||
if scoped_bindings.contains(&sym) {
|
if keys.contains(&sym) {
|
||||||
return Ok(MaybeThunk::ScopedImportBinding(sym));
|
return Ok(self
|
||||||
|
.bump
|
||||||
|
.alloc(GhostCell::new(MaybeThunk::ScopedImportBinding(sym)).into()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Scope::Let(let_scope) => {
|
Scope::Let(let_scope) => {
|
||||||
if let Some(&expr) = let_scope.get(&sym) {
|
if let Some(&expr) = let_scope.get(&sym) {
|
||||||
return Ok(MaybeThunk::Thunk(expr));
|
return Ok(expr.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&Scope::Param {
|
&Scope::Param {
|
||||||
@@ -421,16 +462,33 @@ impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir>
|
|||||||
abs_layer,
|
abs_layer,
|
||||||
} => {
|
} => {
|
||||||
if param_sym == sym {
|
if param_sym == sym {
|
||||||
return Ok(MaybeThunk::Arg {
|
let layers: u8 =
|
||||||
layer: self.thunk_scopes.len() - abs_layer,
|
self.thunk_scopes.len().try_into().expect("scope too deep!");
|
||||||
});
|
let layer = layers - abs_layer;
|
||||||
|
return Ok(self
|
||||||
|
.bump
|
||||||
|
.alloc(GhostCell::new(MaybeThunk::Arg { layer }).into()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.with_scope_count > 0 {
|
if !self.with_stack.is_empty() {
|
||||||
Ok(MaybeThunk::WithLookup(sym))
|
let id = ThunkId(*self.thunk_count);
|
||||||
|
*self.thunk_count = self.thunk_count.checked_add(1).expect("thunk id overflow");
|
||||||
|
let mut namespaces =
|
||||||
|
bumpalo::collections::Vec::with_capacity_in(self.with_stack.len(), self.bump);
|
||||||
|
namespaces.extend(self.with_stack.iter().rev().copied());
|
||||||
|
let body = self
|
||||||
|
.bump
|
||||||
|
.alloc(GhostCell::new(Ir::WithLookup { sym, namespaces }).into());
|
||||||
|
self.thunk_scopes
|
||||||
|
.last_mut()
|
||||||
|
.expect("no active thunk scope")
|
||||||
|
.add_binding(id, body);
|
||||||
|
Ok(self
|
||||||
|
.bump
|
||||||
|
.alloc(GhostCell::new(MaybeThunk::Thunk(id)).into()))
|
||||||
} else {
|
} else {
|
||||||
Err(Error::downgrade_error(
|
Err(Error::downgrade_error(
|
||||||
format!("'{}' not found", self.resolve_sym(sym)),
|
format!("'{}' not found", self.resolve_sym(sym)),
|
||||||
@@ -446,25 +504,40 @@ impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir>
|
|||||||
|
|
||||||
fn with_let_scope<F, Ret>(&mut self, keys: &[StringId], f: F) -> Result<Ret>
|
fn with_let_scope<F, Ret>(&mut self, keys: &[StringId], f: F) -> Result<Ret>
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut Self) -> Result<(bumpalo::collections::Vec<'ir, IrRef<'id, 'ir>>, Ret)>,
|
F: FnOnce(
|
||||||
|
&mut Self,
|
||||||
|
) -> Result<(
|
||||||
|
bumpalo::collections::Vec<'ir, GhostRoMaybeThunkRef<'id, 'ir>>,
|
||||||
|
Ret,
|
||||||
|
)>,
|
||||||
{
|
{
|
||||||
let base = *self.thunk_count;
|
let base = *self.thunk_count;
|
||||||
*self.thunk_count = self
|
*self.thunk_count = self
|
||||||
.thunk_count
|
.thunk_count
|
||||||
.checked_add(keys.len())
|
.checked_add(keys.len())
|
||||||
.expect("thunk id overflow");
|
.expect("thunk id overflow");
|
||||||
let iter = keys
|
let handles = (base..base + keys.len())
|
||||||
.iter()
|
.map(|id| {
|
||||||
.enumerate()
|
&*self
|
||||||
.map(|(offset, &key)| (key, ThunkId(base + offset)));
|
.bump
|
||||||
self.scopes.push(Scope::Let(iter.collect()));
|
.alloc(GhostCell::new(MaybeThunk::Thunk(ThunkId(id))))
|
||||||
let (vals, ret) = {
|
})
|
||||||
let mut guard = ScopeGuard { ctx: self };
|
.collect::<Vec<_>>();
|
||||||
f(guard.as_ctx())?
|
let scope = keys.iter().copied().zip(handles.iter().copied()).collect();
|
||||||
};
|
self.scopes.push(Scope::Let(scope));
|
||||||
|
let (vals, ret) = { f(self)? };
|
||||||
|
self.scopes.pop();
|
||||||
assert_eq!(keys.len(), vals.len());
|
assert_eq!(keys.len(), vals.len());
|
||||||
let scope = self.thunk_scopes.last_mut().expect("no active thunk scope");
|
let scope = self.thunk_scopes.last_mut().expect("no active thunk scope");
|
||||||
scope.extend_bindings((base..base + keys.len()).map(ThunkId).zip(vals));
|
for (i, (val, handle)) in vals.into_iter().zip(handles).enumerate() {
|
||||||
|
let thunk = *val.borrow(&self.token);
|
||||||
|
*handle.borrow_mut(&mut self.token) = thunk;
|
||||||
|
let id = ThunkId(base + i);
|
||||||
|
let ir_ref = self
|
||||||
|
.bump
|
||||||
|
.alloc(GhostCell::new(Ir::MaybeThunk(handle.into())).into());
|
||||||
|
scope.add_binding(id, ir_ref);
|
||||||
|
}
|
||||||
Ok(ret)
|
Ok(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -474,19 +547,19 @@ impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir>
|
|||||||
{
|
{
|
||||||
self.scopes.push(Scope::Param {
|
self.scopes.push(Scope::Param {
|
||||||
sym,
|
sym,
|
||||||
abs_layer: self.thunk_scopes.len(),
|
abs_layer: self.thunk_scopes.len().try_into().expect("scope too deep!"),
|
||||||
});
|
});
|
||||||
let mut guard = ScopeGuard { ctx: self };
|
let mut guard = ScopeGuard { ctx: self };
|
||||||
f(guard.as_ctx())
|
f(guard.as_ctx())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_with_scope<F, Ret>(&mut self, f: F) -> Ret
|
fn with_with_scope<F, Ret>(&mut self, namespace: GhostRoMaybeThunkRef<'id, 'ir>, f: F) -> Ret
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut Self) -> Ret,
|
F: FnOnce(&mut Self) -> Ret,
|
||||||
{
|
{
|
||||||
self.with_scope_count += 1;
|
self.with_stack.push(namespace);
|
||||||
let ret = f(self);
|
let ret = f(self);
|
||||||
self.with_scope_count -= 1;
|
self.with_stack.pop();
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -495,11 +568,14 @@ impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir>
|
|||||||
f: F,
|
f: F,
|
||||||
) -> (
|
) -> (
|
||||||
Ret,
|
Ret,
|
||||||
bumpalo::collections::Vec<'ir, (ThunkId, IrRef<'id, 'ir>)>,
|
bumpalo::collections::Vec<'ir, (ThunkId, GhostRoIrRef<'id, 'ir>)>,
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut Self) -> Ret,
|
F: FnOnce(&mut Self) -> Ret,
|
||||||
{
|
{
|
||||||
|
if self.thunk_scopes.len() == u8::MAX as usize {
|
||||||
|
panic!("scope too deep!");
|
||||||
|
}
|
||||||
self.thunk_scopes.push(ThunkScope::new_in(self.bump));
|
self.thunk_scopes.push(ThunkScope::new_in(self.bump));
|
||||||
let ret = f(self);
|
let ret = f(self);
|
||||||
(
|
(
|
||||||
@@ -524,13 +600,15 @@ impl<'id, 'ir, 'ctx: 'ir, R: VmRuntimeCtx> DowngradeCtx<'ctx, 'id, 'ir, R> {
|
|||||||
.pop()
|
.pop()
|
||||||
.expect("no thunk scope left???")
|
.expect("no thunk scope left???")
|
||||||
.bindings;
|
.bindings;
|
||||||
let ir = IrRef::alloc(self.bump, Ir::TopLevel { body, thunks });
|
Ok(Ir::freeze(
|
||||||
Ok(ir.freeze(self.token))
|
self.new_expr(Ir::TopLevel { body, thunks }),
|
||||||
|
self.token,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ThunkScope<'id, 'ir> {
|
struct ThunkScope<'id, 'ir> {
|
||||||
bindings: bumpalo::collections::Vec<'ir, (ThunkId, IrRef<'id, 'ir>)>,
|
bindings: bumpalo::collections::Vec<'ir, (ThunkId, GhostRoIrRef<'id, 'ir>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'id, 'ir> ThunkScope<'id, 'ir> {
|
impl<'id, 'ir> ThunkScope<'id, 'ir> {
|
||||||
@@ -540,21 +618,45 @@ impl<'id, 'ir> ThunkScope<'id, 'ir> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_binding(&mut self, id: ThunkId, ir: IrRef<'id, 'ir>, _token: &GhostToken<'id>) {
|
fn add_binding(&mut self, id: ThunkId, ir: GhostRoIrRef<'id, 'ir>) {
|
||||||
self.bindings.push((id, ir));
|
self.bindings.push((id, ir));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extend_bindings(&mut self, iter: impl IntoIterator<Item = (ThunkId, IrRef<'id, 'ir>)>) {
|
fn extend_bindings(
|
||||||
|
&mut self,
|
||||||
|
iter: impl IntoIterator<Item = (ThunkId, GhostRoIrRef<'id, 'ir>)>,
|
||||||
|
) {
|
||||||
self.bindings.extend(iter);
|
self.bindings.extend(iter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Scope<'ctx> {
|
enum Scope<'ctx, 'id, 'ir> {
|
||||||
Global(&'ctx HashMap<StringId, Ir<'static, RawIrRef<'static>>>),
|
Global(&'ctx HashMap<StringId, MaybeThunk>),
|
||||||
Repl(&'ctx HashSet<StringId>),
|
Repl(&'ctx HashSet<StringId>),
|
||||||
ScopedImport(HashSet<StringId>),
|
ScopedImport {
|
||||||
Let(HashMap<StringId, ThunkId>),
|
keys: HashSet<StringId>,
|
||||||
Param { sym: StringId, abs_layer: usize },
|
slot_id: u32,
|
||||||
|
},
|
||||||
|
Let(HashMap<StringId, GhostMaybeThunkRef<'id, 'ir>>),
|
||||||
|
Param { sym: StringId, abs_layer: u8 },
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum ExtraScope<'ctx> {
|
||||||
|
Repl(&'ctx HashSet<StringId>),
|
||||||
|
ScopedImport {
|
||||||
|
keys: HashSet<StringId>,
|
||||||
|
slot_id: u32,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'ctx> From<ExtraScope<'ctx>> for Scope<'ctx, '_, '_> {
|
||||||
|
fn from(value: ExtraScope<'ctx>) -> Self {
|
||||||
|
use ExtraScope::*;
|
||||||
|
match value {
|
||||||
|
ScopedImport { keys, slot_id } => Scope::ScopedImport { keys, slot_id },
|
||||||
|
Repl(scope) => Scope::Repl(scope),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ScopeGuard<'a, 'ctx, 'id, 'ir, R: VmRuntimeCtx> {
|
struct ScopeGuard<'a, 'ctx, 'id, 'ir, R: VmRuntimeCtx> {
|
||||||
@@ -579,15 +681,31 @@ struct OwnedIr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl OwnedIr {
|
impl OwnedIr {
|
||||||
|
/// # Safety
|
||||||
|
/// `ir` must be an allocation backed by `bump`. The reference's
|
||||||
|
/// lifetime is extended to `'static` as a placeholder; the stored IR
|
||||||
|
/// must only be re-borrowed via [`OwnedIr::as_ref`], which narrows
|
||||||
|
/// the lifetime back to that of the `&self` borrow. Moving `bump`
|
||||||
|
/// into the struct keeps the underlying allocation live for the
|
||||||
|
/// lifetime of the `OwnedIr`.
|
||||||
unsafe fn new(ir: RawIrRef<'_>, bump: Bump) -> Self {
|
unsafe fn new(ir: RawIrRef<'_>, bump: Bump) -> Self {
|
||||||
Self {
|
Self {
|
||||||
_bump: bump,
|
_bump: bump,
|
||||||
|
// SAFETY: see function docs - caller guarantees `ir` is in `bump`,
|
||||||
|
// and the `'static` lifetime is a placeholder narrowed by `as_ref`.
|
||||||
ir: unsafe { std::mem::transmute::<RawIrRef<'_>, RawIrRef<'static>>(ir) },
|
ir: unsafe { std::mem::transmute::<RawIrRef<'_>, RawIrRef<'static>>(ir) },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_ref(&self) -> RawIrRef<'_> {
|
fn as_ref<'ir>(&'ir self) -> RawIrRef<'ir> {
|
||||||
self.ir
|
// SAFETY: narrows the placeholder `'static` lifetime stored in
|
||||||
|
// `self.ir` down to `'ir = &'ir self`. Lifetime shortening is
|
||||||
|
// logically sound for covariant positions; the transmute is only
|
||||||
|
// needed because `RawRef<'ir>` carries `'ir` through a GAT
|
||||||
|
// (`Ref::Ref<T>`), which prevents the compiler from inferring
|
||||||
|
// covariance automatically. The bump arena that backs the IR is
|
||||||
|
// owned by `self._bump`, so the data is live for at least `'ir`.
|
||||||
|
unsafe { std::mem::transmute::<RawIrRef<'static>, RawIrRef<'ir>>(self.ir) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Generated
+22
-38
@@ -31,7 +31,6 @@
|
|||||||
"llm-agents",
|
"llm-agents",
|
||||||
"flake-parts"
|
"flake-parts"
|
||||||
],
|
],
|
||||||
"import-tree": "import-tree",
|
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"llm-agents",
|
"llm-agents",
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
@@ -46,11 +45,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1776192490,
|
"lastModified": 1777369708,
|
||||||
"narHash": "sha256-5gYQNEs0/vDkHhg63aHS5g0IwG/8HNvU1Vr00cElofk=",
|
"narHash": "sha256-1xW7cRZNsFNPQD+cE0fwnLVStnDth0HSoASEIFeT7uI=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "bun2nix",
|
"repo": "bun2nix",
|
||||||
"rev": "6ef9f144616eedea90b364bb408ef2e1de7b310a",
|
"rev": "e659e1cc4b8e1b21d0aa85f1c481f9db61ecfa98",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -68,11 +67,11 @@
|
|||||||
"rust-analyzer-src": "rust-analyzer-src"
|
"rust-analyzer-src": "rust-analyzer-src"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1777018861,
|
"lastModified": 1777796307,
|
||||||
"narHash": "sha256-l+dfxHtTq1jQM53xgYudV8ciECFmJ72PcRAqRS4ys04=",
|
"narHash": "sha256-L7xLjorTwVf2aLu5b0ZZY2D0RFXwD/a/a/fFFDikB2w=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "fenix",
|
"repo": "fenix",
|
||||||
"rev": "7b33c6466f781cd699fe250c5b69dc4193da67a7",
|
"rev": "0f9881f2344c0b1c100bd9e774555759b7da6fd5",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -84,11 +83,11 @@
|
|||||||
"flake-compat": {
|
"flake-compat": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1751685974,
|
"lastModified": 1777699697,
|
||||||
"narHash": "sha256-NKw96t+BgHIYzHUjkTK95FqYRVKB8DHpVhefWSz/kTw=",
|
"narHash": "sha256-Eg9b/rq/ECYwNwEXs5i9wHyhxNI0JrYx2srdI2uZMaQ=",
|
||||||
"rev": "549f2762aebeff29a2e5ece7a7dc0f955281a1d1",
|
"rev": "382052b74656a369c5408822af3f2501e9b1af81",
|
||||||
"type": "tarball",
|
"type": "tarball",
|
||||||
"url": "https://git.lix.systems/api/v1/repos/lix-project/flake-compat/archive/549f2762aebeff29a2e5ece7a7dc0f955281a1d1.tar.gz"
|
"url": "https://git.lix.systems/api/v1/repos/lix-project/flake-compat/archive/382052b74656a369c5408822af3f2501e9b1af81.tar.gz"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"type": "tarball",
|
"type": "tarball",
|
||||||
@@ -103,11 +102,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1775087534,
|
"lastModified": 1777678872,
|
||||||
"narHash": "sha256-91qqW8lhL7TLwgQWijoGBbiD4t7/q75KTi8NxjVmSmA=",
|
"narHash": "sha256-EPIFsulyon7Z1vLQq5Fk64GR8L7cQsT+IPhcsukVbgk=",
|
||||||
"owner": "hercules-ci",
|
"owner": "hercules-ci",
|
||||||
"repo": "flake-parts",
|
"repo": "flake-parts",
|
||||||
"rev": "3107b77cd68437b9a76194f0f7f9c55f2329ca5b",
|
"rev": "5250617bffd85403b14dbf43c3870e7f255d2c16",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -116,21 +115,6 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"import-tree": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1763762820,
|
|
||||||
"narHash": "sha256-ZvYKbFib3AEwiNMLsejb/CWs/OL/srFQ8AogkebEPF0=",
|
|
||||||
"owner": "vic",
|
|
||||||
"repo": "import-tree",
|
|
||||||
"rev": "3c23749d8013ec6daa1d7255057590e9ca726646",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "vic",
|
|
||||||
"repo": "import-tree",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"llm-agents": {
|
"llm-agents": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"blueprint": "blueprint",
|
"blueprint": "blueprint",
|
||||||
@@ -143,11 +127,11 @@
|
|||||||
"treefmt-nix": "treefmt-nix"
|
"treefmt-nix": "treefmt-nix"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1777019177,
|
"lastModified": 1777786380,
|
||||||
"narHash": "sha256-YjPvucTsKmGO9QVNz07x7sSsK11PB0jtMniRkTolbq4=",
|
"narHash": "sha256-GGKC1WrEoTafJwIXn+fim6cZ/w1ZWVc+DUYdk2lvPvA=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "llm-agents.nix",
|
"repo": "llm-agents.nix",
|
||||||
"rev": "8ff0f2a7fcd176b4547da6879ad549de2bbded41",
|
"rev": "961b1096bc0b2ecc7096e360646cd2f29671c55e",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -158,11 +142,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1776548001,
|
"lastModified": 1777578337,
|
||||||
"narHash": "sha256-ZSK0NL4a1BwVbbTBoSnWgbJy9HeZFXLYQizjb2DPF24=",
|
"narHash": "sha256-Ad49moKWeXtKBJNy2ebiTQUEgdLyvGmTeykAQ9xM+Z4=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "b12141ef619e0a9c1c84dc8c684040326f27cdcc",
|
"rev": "15f4ee454b1dce334612fa6843b3e05cf546efab",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -183,11 +167,11 @@
|
|||||||
"rust-analyzer-src": {
|
"rust-analyzer-src": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1776800521,
|
"lastModified": 1777768857,
|
||||||
"narHash": "sha256-f8YJfwAOsLFpIoqZuX3yF69UvMLrkx7iVzMH1pJU7cM=",
|
"narHash": "sha256-zfekJcaVctfAps1KDHwZpwkvAQn7GObRHh3Gl3xocGI=",
|
||||||
"owner": "rust-lang",
|
"owner": "rust-lang",
|
||||||
"repo": "rust-analyzer",
|
"repo": "rust-analyzer",
|
||||||
"rev": "8954b66d43225e62c92e8bbcc8500191b5cceb1e",
|
"rev": "1102c0b633599564919e36076d4362d7e68dbddc",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
Reference in New Issue
Block a user