Compare commits
11
Commits
dde3052e2d
..
new
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac7574565d
|
||
|
|
89f5d84009
|
||
|
|
c0f2d1a20a
|
||
|
|
5a06fb4c1e
|
||
|
|
0c0e1849ae
|
||
|
|
cb38031f85
|
||
|
|
5f494983b8
|
||
|
|
ed312f7919
|
||
|
|
11622bff22
|
||
|
|
09ee923903
|
||
|
|
7220b42024
|
Generated
+313
-349
File diff suppressed because it is too large
Load Diff
+68
-7
@@ -6,6 +6,7 @@ members = [
|
|||||||
"fix-compiler",
|
"fix-compiler",
|
||||||
"fix-error",
|
"fix-error",
|
||||||
"fix-lang",
|
"fix-lang",
|
||||||
|
"fix-macros",
|
||||||
"fix-runtime",
|
"fix-runtime",
|
||||||
"fix-vm",
|
"fix-vm",
|
||||||
]
|
]
|
||||||
@@ -21,17 +22,77 @@ bumpalo = {
|
|||||||
}
|
}
|
||||||
ere = "0.2"
|
ere = "0.2"
|
||||||
ghost-cell = "0.2"
|
ghost-cell = "0.2"
|
||||||
hashbrown = "0.16"
|
hashbrown = "0.17"
|
||||||
num_enum = "0.7.5"
|
num_enum = "0.7"
|
||||||
rnix = "0.14"
|
rnix = "0.14"
|
||||||
rowan = "0.16"
|
rowan = "0.16"
|
||||||
smallvec = { version = "1.15", features = ["const_generics", "const_new"] }
|
smallvec = { version = "1.15", features = ["const_generics", "const_new"] }
|
||||||
string-interner = "0.19"
|
string-interner = "0.20"
|
||||||
|
gc-arena = { version = "0.7", features = ["hashbrown", "smallvec"] }
|
||||||
|
|
||||||
[workspace.dependencies.gc-arena]
|
# https://emschwartz.me/your-clippy-config-should-be-stricter/
|
||||||
git = "https://github.com/kyren/gc-arena"
|
# tombi: format.rules.table-keys-order.disabled = true
|
||||||
rev = "75671ae03f53718357b741ed4027560f14e90836"
|
[workspace.lints.clippy]
|
||||||
features = ["allocator-api2", "hashbrown", "smallvec"]
|
# Don't Panic - prevent panics from unwraps and unsafe slicing or indexing
|
||||||
|
string_slice = "warn"
|
||||||
|
indexing_slicing = "warn"
|
||||||
|
unwrap_used = "warn"
|
||||||
|
panic = "warn"
|
||||||
|
# WIP evaluator: several features are deliberately stubbed with todo!/unimplemented!
|
||||||
|
todo = "allow"
|
||||||
|
unimplemented = "allow"
|
||||||
|
unreachable = "warn"
|
||||||
|
get_unwrap = "warn"
|
||||||
|
unwrap_in_result = "warn"
|
||||||
|
unchecked_time_subtraction = "warn"
|
||||||
|
panic_in_result_fn = "warn"
|
||||||
|
# Optional - see post for caveats
|
||||||
|
# expect_used = "warn"
|
||||||
|
# arithmetic_side_effects = "warn"
|
||||||
|
|
||||||
|
# Don't Fail Silently - prevent dropped futures and swallowed errors
|
||||||
|
let_underscore_future = "warn"
|
||||||
|
let_underscore_must_use = "warn"
|
||||||
|
unused_result_ok = "warn"
|
||||||
|
map_err_ignore = "warn"
|
||||||
|
assertions_on_result_states = "warn"
|
||||||
|
|
||||||
|
# Don't Do Bad Async Stuff - prevent deadlocks and concurrency bugs
|
||||||
|
await_holding_lock = "warn"
|
||||||
|
await_holding_refcell_ref = "warn"
|
||||||
|
if_let_mutex = "warn" # only relevant on editions before 2024
|
||||||
|
large_futures = "warn"
|
||||||
|
|
||||||
|
# Don't Do Unsafe Things with Memory
|
||||||
|
mem_forget = "warn"
|
||||||
|
undocumented_unsafe_blocks = "warn"
|
||||||
|
multiple_unsafe_ops_per_block = "warn"
|
||||||
|
unnecessary_safety_doc = "warn"
|
||||||
|
unnecessary_safety_comment = "warn"
|
||||||
|
|
||||||
|
# Don't Do Potentially Incorrect Things with Numbers
|
||||||
|
float_cmp = "warn"
|
||||||
|
float_cmp_const = "warn"
|
||||||
|
lossy_float_literal = "warn"
|
||||||
|
cast_sign_loss = "warn"
|
||||||
|
invalid_upcast_comparisons = "warn"
|
||||||
|
# Optional - these effectively force you to document numeric invariants
|
||||||
|
# cast_possible_wrap = "warn"
|
||||||
|
# cast_precision_loss = "warn"
|
||||||
|
# cast_possible_truncation = "warn"
|
||||||
|
|
||||||
|
# Don't Do Bad Things That are Easy to Avoid
|
||||||
|
rc_mutex = "warn"
|
||||||
|
debug_assert_with_mut_call = "warn"
|
||||||
|
iter_not_returning_iterator = "warn"
|
||||||
|
expl_impl_clone_on_copy = "warn"
|
||||||
|
infallible_try_from = "warn"
|
||||||
|
dbg_macro = "warn"
|
||||||
|
|
||||||
|
# Don't `allow` Your Way Around These Lints - every suppression must be
|
||||||
|
# a deliberate #[expect(..., reason = "…")] rather than a silent #[allow]
|
||||||
|
allow_attributes = "warn"
|
||||||
|
allow_attributes_without_reason = "warn"
|
||||||
|
|
||||||
[profile.lto]
|
[profile.lto]
|
||||||
inherits = "release"
|
inherits = "release"
|
||||||
|
|||||||
@@ -14,22 +14,6 @@
|
|||||||
@evalr expr:
|
@evalr expr:
|
||||||
cargo run --release -- eval --expr '{{expr}}'
|
cargo run --release -- eval --expr '{{expr}}'
|
||||||
|
|
||||||
[no-exit-message]
|
|
||||||
@repli:
|
|
||||||
cargo run --release --features inspector -- --inspect-brk 127.0.0.1:9229 repl
|
|
||||||
|
|
||||||
[no-exit-message]
|
|
||||||
@evali expr:
|
|
||||||
cargo run --release --features inspector -- --inspect-brk 127.0.0.1:9229 eval --expr '{{expr}}'
|
|
||||||
|
|
||||||
[no-exit-message]
|
|
||||||
@replp:
|
|
||||||
cargo run --release --features prof -- repl
|
|
||||||
|
|
||||||
[no-exit-message]
|
|
||||||
@evalp expr:
|
|
||||||
cargo run --release --features prof -- eval --expr '{{expr}}'
|
|
||||||
|
|
||||||
[no-exit-message]
|
[no-exit-message]
|
||||||
[positional-arguments]
|
[positional-arguments]
|
||||||
@cg *args='':
|
@cg *args='':
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
allow-indexing-slicing-in-tests = true
|
||||||
|
allow-panic-in-tests = true
|
||||||
|
allow-unwrap-in-tests = true
|
||||||
|
allow-expect-in-tests = true
|
||||||
|
allow-dbg-in-tests = true
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
{ pkgs }:
|
||||||
|
pkgs.mkShell {
|
||||||
|
packages = with pkgs; [
|
||||||
|
(fenix.latest.withComponents [
|
||||||
|
"cargo"
|
||||||
|
"clippy"
|
||||||
|
"rust-src"
|
||||||
|
"rustc"
|
||||||
|
"rustfmt"
|
||||||
|
"rust-analyzer"
|
||||||
|
])
|
||||||
|
cargo-machete
|
||||||
|
cargo-bloat
|
||||||
|
cargo-expand
|
||||||
|
lldb
|
||||||
|
valgrind
|
||||||
|
kdePackages.kcachegrind
|
||||||
|
hyperfine
|
||||||
|
just
|
||||||
|
samply
|
||||||
|
tokei
|
||||||
|
tombi
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -9,3 +9,6 @@ num_enum = { workspace = true }
|
|||||||
string-interner = { workspace = true }
|
string-interner = { workspace = true }
|
||||||
|
|
||||||
fix-lang = { path = "../fix-lang" }
|
fix-lang = { path = "../fix-lang" }
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use std::fmt::Write;
|
|||||||
|
|
||||||
use colored::Colorize as _;
|
use colored::Colorize as _;
|
||||||
|
|
||||||
use crate::{InstructionPtr, Op, OperandType, PrimOpPhase};
|
use crate::{BytecodeReader, Continuation, InstructionPtr, Op};
|
||||||
|
|
||||||
pub trait DisassemblerContext {
|
pub trait DisassemblerContext {
|
||||||
fn resolve_string(&self, id: u32) -> &str;
|
fn resolve_string(&self, id: u32) -> &str;
|
||||||
@@ -10,99 +10,15 @@ pub trait DisassemblerContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct Disassembler<'a, Ctx> {
|
pub struct Disassembler<'a, Ctx> {
|
||||||
code: &'a [u8],
|
reader: BytecodeReader<'a>,
|
||||||
ctx: &'a Ctx,
|
ctx: &'a Ctx,
|
||||||
pc: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
||||||
pub fn new(ip: InstructionPtr, ctx: &'a Ctx) -> Self {
|
pub fn new(ip: InstructionPtr, ctx: &'a Ctx) -> Self {
|
||||||
Self {
|
Self {
|
||||||
code: ctx.get_code(),
|
reader: BytecodeReader::new(ctx.get_code(), ip.0),
|
||||||
ctx,
|
ctx,
|
||||||
pc: ip.0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn read_u8(&mut self) -> u8 {
|
|
||||||
let b = self.code[self.pc];
|
|
||||||
self.pc += 1;
|
|
||||||
b
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn read_u16(&mut self) -> u16 {
|
|
||||||
let bytes = self.code[self.pc..self.pc + 2]
|
|
||||||
.try_into()
|
|
||||||
.expect("no enough bytes");
|
|
||||||
self.pc += 2;
|
|
||||||
u16::from_le_bytes(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn read_u32(&mut self) -> u32 {
|
|
||||||
let bytes = self.code[self.pc..self.pc + 4]
|
|
||||||
.try_into()
|
|
||||||
.expect("no enough bytes");
|
|
||||||
self.pc += 4;
|
|
||||||
u32::from_le_bytes(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn read_i32(&mut self) -> i32 {
|
|
||||||
let bytes = self.code[self.pc..self.pc + 4]
|
|
||||||
.try_into()
|
|
||||||
.expect("no enough bytes");
|
|
||||||
self.pc += 4;
|
|
||||||
i32::from_le_bytes(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn read_i64(&mut self) -> i64 {
|
|
||||||
let bytes = self.code[self.pc..self.pc + 8]
|
|
||||||
.try_into()
|
|
||||||
.expect("no enough bytes");
|
|
||||||
self.pc += 8;
|
|
||||||
i64::from_le_bytes(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn read_f64(&mut self) -> f64 {
|
|
||||||
let bytes = self.code[self.pc..self.pc + 8]
|
|
||||||
.try_into()
|
|
||||||
.expect("no enough bytes");
|
|
||||||
self.pc += 8;
|
|
||||||
f64::from_le_bytes(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn read_operand_data(&mut self) {
|
|
||||||
use OperandType::*;
|
|
||||||
let tag = self.read_u8();
|
|
||||||
let ty = OperandType::try_from(tag).expect("invalid operand type");
|
|
||||||
match ty {
|
|
||||||
Const => {
|
|
||||||
self.read_u32();
|
|
||||||
}
|
|
||||||
BigInt => {
|
|
||||||
self.read_i64();
|
|
||||||
}
|
|
||||||
Local => {
|
|
||||||
self.read_u8();
|
|
||||||
self.read_u32();
|
|
||||||
}
|
|
||||||
BuiltinConst => {
|
|
||||||
self.read_u32();
|
|
||||||
}
|
|
||||||
Builtins => {}
|
|
||||||
ReplBinding => {
|
|
||||||
self.read_u32();
|
|
||||||
}
|
|
||||||
ScopedImportBinding => {
|
|
||||||
self.read_u32();
|
|
||||||
self.read_u32();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,6 +30,11 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
|||||||
self.disassemble_impl(true)
|
self.disassemble_impl(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(
|
||||||
|
clippy::let_underscore_must_use,
|
||||||
|
clippy::indexing_slicing,
|
||||||
|
reason = "disassembler operates on well-formed bytecode; writes target an in-memory String via fmt::Write, which is infallible"
|
||||||
|
)]
|
||||||
fn disassemble_impl(&mut self, color: bool) -> String {
|
fn disassemble_impl(&mut self, color: bool) -> String {
|
||||||
let mut out = String::new();
|
let mut out = String::new();
|
||||||
if color {
|
if color {
|
||||||
@@ -122,24 +43,24 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
|||||||
out,
|
out,
|
||||||
"{} {}",
|
"{} {}",
|
||||||
"Length:".white(),
|
"Length:".white(),
|
||||||
format!("{} bytes", self.code.len()).cyan()
|
format!("{} bytes", self.reader.len()).cyan()
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
let _ = writeln!(out, "=== Bytecode Disassembly ===");
|
let _ = writeln!(out, "=== Bytecode Disassembly ===");
|
||||||
let _ = writeln!(out, "Length: {} bytes", self.code.len());
|
let _ = writeln!(out, "Length: {} bytes", self.reader.len());
|
||||||
}
|
}
|
||||||
|
|
||||||
while self.pc < self.code.len() {
|
while self.reader.pc() < self.reader.len() {
|
||||||
let start_pos = self.pc;
|
let start_pos = self.reader.pc();
|
||||||
let op_byte = self.read_u8();
|
let op_byte = self.reader.read_u8();
|
||||||
let (mnemonic, args) = self.decode_instruction(op_byte, start_pos);
|
let (mnemonic, args) = self.decode_instruction(op_byte, start_pos);
|
||||||
|
|
||||||
let bytes_slice = &self.code[start_pos + 1..self.pc];
|
let bytes_slice = &self.reader[start_pos + 1..self.reader.pc()];
|
||||||
let mut chunks = bytes_slice.chunks(4);
|
let mut chunks = bytes_slice.chunks(4);
|
||||||
|
|
||||||
let first_chunk = chunks.next().unwrap_or(&[]);
|
let first_chunk = chunks.next().unwrap_or(&[]);
|
||||||
let bytes_str = {
|
let bytes_str = {
|
||||||
let mut temp = format!("{:02x}", self.code[start_pos]);
|
let mut temp = format!("{:02x}", self.reader[start_pos]);
|
||||||
for b in first_chunk {
|
for b in first_chunk {
|
||||||
let _ = write!(&mut temp, " {:02x}", b);
|
let _ = write!(&mut temp, " {:02x}", b);
|
||||||
}
|
}
|
||||||
@@ -199,31 +120,36 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
|||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(
|
||||||
|
clippy::let_underscore_must_use,
|
||||||
|
clippy::cast_sign_loss,
|
||||||
|
reason = "disassembler operates on well-formed bytecode; jump targets are valid non-negative PCs and writes to String are infallible"
|
||||||
|
)]
|
||||||
fn decode_instruction(&mut self, op_byte: u8, current_pc: usize) -> (&'static str, String) {
|
fn decode_instruction(&mut self, op_byte: u8, current_pc: usize) -> (&'static str, String) {
|
||||||
let op = Op::try_from(op_byte).expect("invalid op code");
|
let op = Op::try_from(op_byte).expect("invalid op code");
|
||||||
|
|
||||||
match op {
|
match op {
|
||||||
Op::PushSmi => {
|
Op::PushSmi => {
|
||||||
let val = self.read_i32();
|
let val = self.reader.read_i32();
|
||||||
("PushSmi", format!("{}", val))
|
("PushSmi", format!("{}", val))
|
||||||
}
|
}
|
||||||
Op::PushBigInt => {
|
Op::PushBigInt => {
|
||||||
let val = self.read_i64();
|
let val = self.reader.read_i64();
|
||||||
("PushBigInt", format!("{}", val))
|
("PushBigInt", format!("{}", val))
|
||||||
}
|
}
|
||||||
Op::PushFloat => {
|
Op::PushFloat => {
|
||||||
let val = self.read_f64();
|
let val = self.reader.read_f64();
|
||||||
("PushFloat", format!("{}", val))
|
("PushFloat", format!("{}", val))
|
||||||
}
|
}
|
||||||
Op::PushString => {
|
Op::PushString => {
|
||||||
let idx = self.read_u32();
|
let idx = self.reader.read_u32();
|
||||||
let s = self.ctx.resolve_string(idx);
|
let s = self.ctx.resolve_string(idx);
|
||||||
let len = s.len();
|
let len = s.len();
|
||||||
let mut s_fmt = format!("{:?}", s);
|
let mut s_fmt = format!("{:?}", s);
|
||||||
if s_fmt.len() > 60 {
|
if s_fmt.len() > 60 {
|
||||||
s_fmt.truncate(57);
|
s_fmt.truncate(57);
|
||||||
#[allow(clippy::unwrap_used)]
|
write!(s_fmt, "...\" (total {len} bytes)")
|
||||||
write!(s_fmt, "...\" (total {len} bytes)").unwrap();
|
.expect("writing to String is infallible");
|
||||||
}
|
}
|
||||||
("PushString", format!("@{} {}", idx, s_fmt))
|
("PushString", format!("@{} {}", idx, s_fmt))
|
||||||
}
|
}
|
||||||
@@ -232,38 +158,38 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
|||||||
Op::PushFalse => ("PushFalse", String::new()),
|
Op::PushFalse => ("PushFalse", String::new()),
|
||||||
|
|
||||||
Op::LoadLocal => {
|
Op::LoadLocal => {
|
||||||
let idx = self.read_u32();
|
let idx = self.reader.read_u32();
|
||||||
("LoadLocal", format!("[{}]", idx))
|
("LoadLocal", format!("[{}]", idx))
|
||||||
}
|
}
|
||||||
Op::LoadOuter => {
|
Op::LoadOuter => {
|
||||||
let depth = self.read_u8();
|
let depth = self.reader.read_u8();
|
||||||
let idx = self.read_u32();
|
let idx = self.reader.read_u32();
|
||||||
("LoadOuter", format!("depth={} [{}]", depth, idx))
|
("LoadOuter", format!("depth={} [{}]", depth, idx))
|
||||||
}
|
}
|
||||||
Op::StoreLocal => {
|
Op::StoreLocal => {
|
||||||
let idx = self.read_u32();
|
let idx = self.reader.read_u32();
|
||||||
("StoreLocal", format!("[{}]", idx))
|
("StoreLocal", format!("[{}]", idx))
|
||||||
}
|
}
|
||||||
Op::AllocLocals => {
|
Op::AllocLocals => {
|
||||||
let count = self.read_u32();
|
let count = self.reader.read_u32();
|
||||||
("AllocLocals", format!("count={}", count))
|
("AllocLocals", format!("count={}", count))
|
||||||
}
|
}
|
||||||
|
|
||||||
Op::MakeThunk => {
|
Op::MakeThunk => {
|
||||||
let offset = self.read_u32();
|
let offset = self.reader.read_u32();
|
||||||
("MakeThunk", format!("-> {:04x}", offset))
|
("MakeThunk", format!("-> {:04x}", offset))
|
||||||
}
|
}
|
||||||
Op::MakeClosure => {
|
Op::MakeClosure => {
|
||||||
let offset = self.read_u32();
|
let offset = self.reader.read_u32();
|
||||||
let slots = self.read_u32();
|
let slots = self.reader.read_u32();
|
||||||
("MakeClosure", format!("-> {:04x} slots={}", offset, slots))
|
("MakeClosure", format!("-> {:04x} slots={}", offset, slots))
|
||||||
}
|
}
|
||||||
Op::MakePatternClosure => {
|
Op::MakePatternClosure => {
|
||||||
let offset = self.read_u32();
|
let offset = self.reader.read_u32();
|
||||||
let slots = self.read_u32();
|
let slots = self.reader.read_u32();
|
||||||
let req_count = self.read_u16();
|
let req_count = self.reader.read_u16();
|
||||||
let opt_count = self.read_u16();
|
let opt_count = self.reader.read_u16();
|
||||||
let ellipsis = self.read_u8() != 0;
|
let ellipsis = self.reader.read_u8() != 0;
|
||||||
|
|
||||||
let mut arg_str = format!(
|
let mut arg_str = format!(
|
||||||
"-> {:04x} slots={} req={} opt={} ...={})",
|
"-> {:04x} slots={} req={} opt={} ...={})",
|
||||||
@@ -272,18 +198,18 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
|||||||
|
|
||||||
arg_str.push_str(" Args=[");
|
arg_str.push_str(" Args=[");
|
||||||
for _ in 0..req_count {
|
for _ in 0..req_count {
|
||||||
let idx = self.read_u32();
|
let idx = self.reader.read_u32();
|
||||||
arg_str.push_str(&format!("Req({}) ", self.ctx.resolve_string(idx)));
|
arg_str.push_str(&format!("Req({}) ", self.ctx.resolve_string(idx)));
|
||||||
}
|
}
|
||||||
for _ in 0..opt_count {
|
for _ in 0..opt_count {
|
||||||
let idx = self.read_u32();
|
let idx = self.reader.read_u32();
|
||||||
arg_str.push_str(&format!("Opt({}) ", self.ctx.resolve_string(idx)));
|
arg_str.push_str(&format!("Opt({}) ", self.ctx.resolve_string(idx)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let total_args = req_count + opt_count;
|
let total_args = req_count + opt_count;
|
||||||
for _ in 0..total_args {
|
for _ in 0..total_args {
|
||||||
let _name_idx = self.read_u32();
|
let _name_idx = self.reader.read_u32();
|
||||||
let _span_id = self.read_u32();
|
let _span_id = self.reader.read_u32();
|
||||||
}
|
}
|
||||||
arg_str.push(']');
|
arg_str.push(']');
|
||||||
|
|
||||||
@@ -291,31 +217,32 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Op::Call => {
|
Op::Call => {
|
||||||
self.read_operand_data();
|
let _ = self.reader.read_operand_data();
|
||||||
("Call", "arg=?".into())
|
("Call", "arg=?".into())
|
||||||
}
|
}
|
||||||
Op::DispatchPrimOp => {
|
Op::DispatchCont => {
|
||||||
let phase = PrimOpPhase::try_from(self.read_u8()).expect("invalid primop phase");
|
let phase =
|
||||||
|
Continuation::try_from(self.reader.read_u8()).expect("invalid primop phase");
|
||||||
("DispatchPrimOp", format!("phase={phase:?}"))
|
("DispatchPrimOp", format!("phase={phase:?}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
Op::MakeAttrs => {
|
Op::MakeAttrs => {
|
||||||
let static_count = self.read_u32();
|
let static_count = self.reader.read_u32();
|
||||||
let dynamic_count = self.read_u32();
|
let dynamic_count = self.reader.read_u32();
|
||||||
let mut args = format!("static={} dynamic={}", static_count, dynamic_count);
|
let mut args = format!("static={} dynamic={}", static_count, dynamic_count);
|
||||||
|
|
||||||
for _ in 0..static_count {
|
for _ in 0..static_count {
|
||||||
let key_id = self.read_u32();
|
let key_id = self.reader.read_u32();
|
||||||
let _ = write!(args, " [{}={}", self.ctx.resolve_string(key_id), key_id);
|
let _ = write!(args, " [{}={}", self.ctx.resolve_string(key_id), key_id);
|
||||||
self.read_operand_data();
|
let _ = self.reader.read_operand_data();
|
||||||
let _span_id = self.read_u32();
|
let _span_id = self.reader.read_u32();
|
||||||
args.push(']');
|
args.push(']');
|
||||||
}
|
}
|
||||||
|
|
||||||
for _ in 0..dynamic_count {
|
for _ in 0..dynamic_count {
|
||||||
let _ = write!(args, " [dyn");
|
let _ = write!(args, " [dyn");
|
||||||
self.read_operand_data();
|
let _ = self.reader.read_operand_data();
|
||||||
let _span_id = self.read_u32();
|
let _span_id = self.reader.read_u32();
|
||||||
args.push(']');
|
args.push(']');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,31 +251,31 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
|||||||
Op::MakeEmptyAttrs => ("MakeEmptyAttrs", String::new()),
|
Op::MakeEmptyAttrs => ("MakeEmptyAttrs", String::new()),
|
||||||
|
|
||||||
Op::SelectStatic => {
|
Op::SelectStatic => {
|
||||||
let span_id = self.read_u32();
|
let span_id = self.reader.read_u32();
|
||||||
let key_id = self.read_u32();
|
let key_id = self.reader.read_u32();
|
||||||
(
|
(
|
||||||
"SelectStatic",
|
"SelectStatic",
|
||||||
format!("key={} span={}", self.ctx.resolve_string(key_id), span_id),
|
format!("key={} span={}", self.ctx.resolve_string(key_id), span_id),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Op::SelectDynamic => {
|
Op::SelectDynamic => {
|
||||||
let span_id = self.read_u32();
|
let span_id = self.reader.read_u32();
|
||||||
("SelectDynamic", format!("span={}", span_id))
|
("SelectDynamic", format!("span={}", span_id))
|
||||||
}
|
}
|
||||||
Op::HasAttrPathStatic => {
|
Op::HasAttrPathStatic => {
|
||||||
let span_id = self.read_u32();
|
let span_id = self.reader.read_u32();
|
||||||
let key_id = self.read_u32();
|
let key_id = self.reader.read_u32();
|
||||||
(
|
(
|
||||||
"HasAttrPathStatic",
|
"HasAttrPathStatic",
|
||||||
format!("key={} span={}", self.ctx.resolve_string(key_id), span_id),
|
format!("key={} span={}", self.ctx.resolve_string(key_id), span_id),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Op::HasAttrPathDynamic => {
|
Op::HasAttrPathDynamic => {
|
||||||
let span_id = self.read_u32();
|
let span_id = self.reader.read_u32();
|
||||||
("HasAttrPathDynamic", format!("span={}", span_id))
|
("HasAttrPathDynamic", format!("span={}", span_id))
|
||||||
}
|
}
|
||||||
Op::HasAttrStatic => {
|
Op::HasAttrStatic => {
|
||||||
let key_id = self.read_u32();
|
let key_id = self.reader.read_u32();
|
||||||
(
|
(
|
||||||
"HasAttrStatic",
|
"HasAttrStatic",
|
||||||
format!("key={}", self.ctx.resolve_string(key_id)),
|
format!("key={}", self.ctx.resolve_string(key_id)),
|
||||||
@@ -357,7 +284,7 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
|||||||
Op::HasAttrDynamic => ("HasAttrDynamic", String::new()),
|
Op::HasAttrDynamic => ("HasAttrDynamic", String::new()),
|
||||||
Op::HasAttrResolve => ("HasAttrResolve", String::new()),
|
Op::HasAttrResolve => ("HasAttrResolve", String::new()),
|
||||||
Op::JumpIfSelectSucceeded => {
|
Op::JumpIfSelectSucceeded => {
|
||||||
let offset = self.read_i32();
|
let offset = self.reader.read_i32();
|
||||||
let target = (current_pc as isize + 1 + 4 + offset as isize) as usize;
|
let target = (current_pc as isize + 1 + 4 + offset as isize) as usize;
|
||||||
(
|
(
|
||||||
"JumpIfSelectSucceeded",
|
"JumpIfSelectSucceeded",
|
||||||
@@ -365,7 +292,7 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
Op::JumpIfSelectFailed => {
|
Op::JumpIfSelectFailed => {
|
||||||
let offset = self.read_i32();
|
let offset = self.reader.read_i32();
|
||||||
let target = (current_pc as isize + 1 + 4 + offset as isize) as usize;
|
let target = (current_pc as isize + 1 + 4 + offset as isize) as usize;
|
||||||
(
|
(
|
||||||
"JumpIfSelectFailed",
|
"JumpIfSelectFailed",
|
||||||
@@ -374,9 +301,9 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Op::MakeList => {
|
Op::MakeList => {
|
||||||
let count = self.read_u32();
|
let count = self.reader.read_u32();
|
||||||
for _ in 0..count {
|
for _ in 0..count {
|
||||||
self.read_operand_data();
|
let _ = self.reader.read_operand_data();
|
||||||
}
|
}
|
||||||
("MakeList", format!("size={}", count))
|
("MakeList", format!("size={}", count))
|
||||||
}
|
}
|
||||||
@@ -398,7 +325,7 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
|||||||
Op::OpNot => ("OpNot", String::new()),
|
Op::OpNot => ("OpNot", String::new()),
|
||||||
|
|
||||||
Op::JumpIfFalse => {
|
Op::JumpIfFalse => {
|
||||||
let offset = self.read_i32();
|
let offset = self.reader.read_i32();
|
||||||
let target = (current_pc as isize + 1 + 4 + offset as isize) as usize;
|
let target = (current_pc as isize + 1 + 4 + offset as isize) as usize;
|
||||||
(
|
(
|
||||||
"JumpIfFalse",
|
"JumpIfFalse",
|
||||||
@@ -406,55 +333,55 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
Op::JumpIfTrue => {
|
Op::JumpIfTrue => {
|
||||||
let offset = self.read_i32();
|
let offset = self.reader.read_i32();
|
||||||
let target = (current_pc as isize + 1 + 4 + offset as isize) as usize;
|
let target = (current_pc as isize + 1 + 4 + offset as isize) as usize;
|
||||||
("JumpIfTrue", format!("-> {:04x} offset={}", target, offset))
|
("JumpIfTrue", format!("-> {:04x} offset={}", target, offset))
|
||||||
}
|
}
|
||||||
Op::Jump => {
|
Op::Jump => {
|
||||||
let offset = self.read_i32();
|
let offset = self.reader.read_i32();
|
||||||
let target = (current_pc as isize + 1 + 4 + offset as isize) as usize;
|
let target = (current_pc as isize + 1 + 4 + offset as isize) as usize;
|
||||||
("Jump", format!("-> {:04x} offset={}", target, offset))
|
("Jump", format!("-> {:04x} offset={}", target, offset))
|
||||||
}
|
}
|
||||||
|
|
||||||
Op::ConcatStrings => {
|
Op::ConcatStrings => {
|
||||||
let count = self.read_u16();
|
let count = self.reader.read_u16();
|
||||||
let force = self.read_u8();
|
let force = self.reader.read_u8();
|
||||||
("ConcatStrings", format!("count={} force={}", count, force))
|
("ConcatStrings", format!("count={} force={}", count, force))
|
||||||
}
|
}
|
||||||
Op::CoerceToString => ("CoerceToString", String::new()),
|
Op::CoerceToString => ("CoerceToString", String::new()),
|
||||||
Op::ResolvePath => {
|
Op::ResolvePath => {
|
||||||
let dir_id = self.read_u32();
|
let dir_id = self.reader.read_u32();
|
||||||
let dir = self.ctx.resolve_string(dir_id);
|
let dir = self.ctx.resolve_string(dir_id);
|
||||||
("ResolvePath", format!("dir={:?}", dir))
|
("ResolvePath", format!("dir={:?}", dir))
|
||||||
}
|
}
|
||||||
Op::Assert => {
|
Op::Assert => {
|
||||||
let raw_idx = self.read_u32();
|
let raw_idx = self.reader.read_u32();
|
||||||
let span_id = self.read_u32();
|
let span_id = self.reader.read_u32();
|
||||||
("Assert", format!("text_id={} span={}", raw_idx, span_id))
|
("Assert", format!("text_id={} span={}", raw_idx, span_id))
|
||||||
}
|
}
|
||||||
Op::LookupWith => {
|
Op::LookupWith => {
|
||||||
let idx = self.read_u32();
|
let idx = self.reader.read_u32();
|
||||||
let name = self.ctx.resolve_string(idx);
|
let name = self.ctx.resolve_string(idx);
|
||||||
let n = self.read_u8();
|
let n = self.reader.read_u8();
|
||||||
for _ in 0..n {
|
for _ in 0..n {
|
||||||
self.read_operand_data();
|
let _ = self.reader.read_operand_data();
|
||||||
}
|
}
|
||||||
("LookupWith", format!("sym={:?} n={}", name, n))
|
("LookupWith", format!("sym={:?} n={}", name, n))
|
||||||
}
|
}
|
||||||
|
|
||||||
Op::LoadBuiltins => ("LoadBuiltins", String::new()),
|
Op::LoadBuiltins => ("LoadBuiltins", String::new()),
|
||||||
Op::LoadBuiltin => {
|
Op::LoadBuiltin => {
|
||||||
let id = self.read_u8();
|
let id = self.reader.read_u8();
|
||||||
("LoadBuiltin", format!("id={}", id))
|
("LoadBuiltin", format!("id={}", id))
|
||||||
}
|
}
|
||||||
Op::LoadReplBinding => {
|
Op::LoadReplBinding => {
|
||||||
let idx = self.read_u32();
|
let idx = self.reader.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 slot = self.reader.read_u32();
|
||||||
let idx = self.read_u32();
|
let idx = self.reader.read_u32();
|
||||||
let name = self.ctx.resolve_string(idx);
|
let name = self.ctx.resolve_string(idx);
|
||||||
("LoadScopedBinding", format!("slot={} {:?}", slot, name))
|
("LoadScopedBinding", format!("slot={} {:?}", slot, name))
|
||||||
}
|
}
|
||||||
|
|||||||
+404
-322
@@ -1,4 +1,7 @@
|
|||||||
#![allow(dead_code)]
|
#![allow(
|
||||||
|
dead_code,
|
||||||
|
reason = "crate is under active development; some opcodes and helpers are not yet wired up"
|
||||||
|
)]
|
||||||
|
|
||||||
use fix_lang::{BuiltinId, StringId};
|
use fix_lang::{BuiltinId, StringId};
|
||||||
use num_enum::TryFromPrimitive;
|
use num_enum::TryFromPrimitive;
|
||||||
@@ -10,8 +13,7 @@ pub mod disassembler;
|
|||||||
pub struct InstructionPtr(pub usize);
|
pub struct InstructionPtr(pub usize);
|
||||||
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[derive(Debug, Clone, Copy, TryFromPrimitive)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
#[allow(clippy::enum_variant_names)]
|
|
||||||
pub enum Op {
|
pub enum Op {
|
||||||
PushSmi,
|
PushSmi,
|
||||||
PushBigInt,
|
PushBigInt,
|
||||||
@@ -31,7 +33,7 @@ pub enum Op {
|
|||||||
MakePatternClosure,
|
MakePatternClosure,
|
||||||
|
|
||||||
Call,
|
Call,
|
||||||
DispatchPrimOp,
|
DispatchCont,
|
||||||
|
|
||||||
MakeAttrs,
|
MakeAttrs,
|
||||||
MakeEmptyAttrs,
|
MakeEmptyAttrs,
|
||||||
@@ -88,6 +90,21 @@ pub enum Op {
|
|||||||
Illegal,
|
Illegal,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u8> for Op {
|
||||||
|
type Error = u8;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||||
|
if (0..Self::Illegal as u8).contains(&value) {
|
||||||
|
// SAFETY: `value` is in `0..Illegal`, which are exactly the valid
|
||||||
|
// discriminants of this `#[repr(u8)]` enum, so the transmute is sound.
|
||||||
|
Ok(unsafe { std::mem::transmute::<u8, Self>(value) })
|
||||||
|
} else {
|
||||||
|
Err(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[derive(Debug, Clone, Copy, TryFromPrimitive)]
|
#[derive(Debug, Clone, Copy, TryFromPrimitive)]
|
||||||
pub enum OperandType {
|
pub enum OperandType {
|
||||||
@@ -106,11 +123,7 @@ pub enum Const {
|
|||||||
Bool(bool),
|
Bool(bool),
|
||||||
String(StringId),
|
String(StringId),
|
||||||
Path(StringId),
|
Path(StringId),
|
||||||
PrimOp {
|
PrimOp(BuiltinId),
|
||||||
id: BuiltinId,
|
|
||||||
arity: u8,
|
|
||||||
dispatch_ip: u32,
|
|
||||||
},
|
|
||||||
Null,
|
Null,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,128 +146,149 @@ pub enum OperandData {
|
|||||||
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||||
pub enum PrimOpPhase {
|
pub enum Continuation {
|
||||||
Abort,
|
// primops
|
||||||
Add,
|
PAbort,
|
||||||
AddErrorContext,
|
PAdd,
|
||||||
|
PAddErrorContext,
|
||||||
|
|
||||||
All,
|
PAll0,
|
||||||
AllCallPred,
|
PAll1,
|
||||||
AllCheck,
|
PAll2,
|
||||||
|
PAll3,
|
||||||
|
|
||||||
Any,
|
PAny0,
|
||||||
AnyCallPred,
|
PAny1,
|
||||||
AnyCheck,
|
PAny2,
|
||||||
|
PAny3,
|
||||||
|
|
||||||
AppendContext,
|
PAppendContext,
|
||||||
AttrNames,
|
PAppendContextLoop,
|
||||||
AttrValues,
|
PAppendContextEntryForced,
|
||||||
BaseNameOf,
|
PAppendContextOutputsForced,
|
||||||
BitAnd,
|
PAppendContextOutputElementLoop,
|
||||||
BitOr,
|
PAppendContextOutputElementForced,
|
||||||
BitXor,
|
|
||||||
Break,
|
|
||||||
CatAttrs,
|
|
||||||
Ceil,
|
|
||||||
CompareVersions,
|
|
||||||
ConcatLists,
|
|
||||||
ConcatMap,
|
|
||||||
ConcatStringsSep,
|
|
||||||
ConvertHash,
|
|
||||||
|
|
||||||
DeepSeq,
|
PAttrNames,
|
||||||
DeepSeqPush,
|
PAttrValues,
|
||||||
DeepSeqLoop,
|
PBaseNameOf,
|
||||||
|
PBitAnd,
|
||||||
|
PBitOr,
|
||||||
|
PBitXor,
|
||||||
|
PBreak,
|
||||||
|
PCatAttrs,
|
||||||
|
PCeil,
|
||||||
|
PCompareVersions,
|
||||||
|
PConcatLists,
|
||||||
|
PConcatMap,
|
||||||
|
PConcatStringsSep,
|
||||||
|
PConvertHash,
|
||||||
|
|
||||||
Derivation,
|
PDeepSeq0,
|
||||||
DerivationStrict,
|
PDeepSeq1,
|
||||||
DirOf,
|
PDeepSeq2,
|
||||||
Div,
|
PDeepSeq3,
|
||||||
Elem,
|
PDeepSeq4,
|
||||||
ElemAt,
|
PDeepSeq5,
|
||||||
FetchGit,
|
|
||||||
FetchMercurial,
|
|
||||||
FetchTarball,
|
|
||||||
FetchTree,
|
|
||||||
FetchUrl,
|
|
||||||
|
|
||||||
FilterForceList,
|
PDerivation,
|
||||||
FilterCallPred,
|
PDerivationStrict,
|
||||||
FilterCheck,
|
PDirOf,
|
||||||
|
PDiv,
|
||||||
|
PElem,
|
||||||
|
PElemAt,
|
||||||
|
PFetchGit,
|
||||||
|
PFetchMercurial,
|
||||||
|
PFetchTarball,
|
||||||
|
PFetchTree,
|
||||||
|
PFetchUrl,
|
||||||
|
|
||||||
FilterSource,
|
PFilter0,
|
||||||
FindFile,
|
PFilter1,
|
||||||
Floor,
|
PFilter2,
|
||||||
FoldlStrict,
|
PFilter3,
|
||||||
FoldlStrictEmpty,
|
PFilter4,
|
||||||
FoldlStrictCall1,
|
|
||||||
FoldlStrictCall2,
|
PFilterSource,
|
||||||
FoldlStrictUpdate,
|
PFindFile,
|
||||||
FromJSON,
|
PFloor,
|
||||||
FromTOML,
|
PFoldlStrict0,
|
||||||
FunctionArgs,
|
PFoldlStrict1,
|
||||||
GenList,
|
PFoldlStrict2,
|
||||||
GenericClosure,
|
PFoldlStrict3,
|
||||||
GetAttr,
|
PFoldlStrict4,
|
||||||
GetContext,
|
PFoldlStrict5,
|
||||||
GetEnv,
|
PFromJSON,
|
||||||
GroupBy,
|
PFromTOML,
|
||||||
HasAttr,
|
PFunctionArgs,
|
||||||
HasContext,
|
PGenList,
|
||||||
HashFile,
|
PGenericClosure,
|
||||||
HashString,
|
PGetAttr,
|
||||||
Head,
|
PGetContext,
|
||||||
Import,
|
PGetEnv,
|
||||||
IntersectAttrs,
|
PGroupBy,
|
||||||
IsAttrs,
|
PHasAttr,
|
||||||
IsBool,
|
PHasContext,
|
||||||
IsFloat,
|
PHashFile,
|
||||||
IsFunction,
|
PHashString,
|
||||||
IsInt,
|
PHead,
|
||||||
IsList,
|
|
||||||
IsNull,
|
PImport,
|
||||||
IsPath,
|
PImportFinalize,
|
||||||
IsString,
|
PScopedImport,
|
||||||
Length,
|
PScopedImportFinalize,
|
||||||
LessThan,
|
|
||||||
ListToAttrs,
|
PIntersectAttrs,
|
||||||
Map,
|
PIsAttrs,
|
||||||
MapAttrs,
|
PIsBool,
|
||||||
Match,
|
PIsFloat,
|
||||||
Mul,
|
PIsFunction,
|
||||||
ParseDrvName,
|
PIsInt,
|
||||||
Partition,
|
PIsList,
|
||||||
Path,
|
PIsNull,
|
||||||
PathExists,
|
PIsPath,
|
||||||
Placeholder,
|
PIsString,
|
||||||
ReadDir,
|
PLength,
|
||||||
ReadFile,
|
PLessThan,
|
||||||
ReadFileType,
|
PListToAttrs,
|
||||||
RemoveAttrs,
|
PMap,
|
||||||
ReplaceStrings,
|
PMapAttrs,
|
||||||
ScopedImport,
|
PMatch,
|
||||||
Seq,
|
PMul,
|
||||||
Sort,
|
PParseDrvName,
|
||||||
Split,
|
PPartition,
|
||||||
SplitVersion,
|
PPath,
|
||||||
StorePath,
|
PPathExists,
|
||||||
StringLength,
|
PPlaceholder,
|
||||||
Sub,
|
PReadDir,
|
||||||
Substring,
|
PReadFile,
|
||||||
Tail,
|
PReadFileType,
|
||||||
Throw,
|
PRemoveAttrs,
|
||||||
ToFile,
|
PReplaceStrings,
|
||||||
ToJSON,
|
PSeq0,
|
||||||
ToPath,
|
PSeq1,
|
||||||
ToString,
|
PSort,
|
||||||
ToXML,
|
PSplit,
|
||||||
Trace,
|
PSplitVersion,
|
||||||
TryEval,
|
PStorePath,
|
||||||
TypeOf,
|
PStringLength,
|
||||||
UnsafeDiscardStringContext,
|
PSub,
|
||||||
UnsafeGetAttrPos,
|
PSubstring,
|
||||||
Warn,
|
PTail,
|
||||||
ZipAttrsWith,
|
PThrow,
|
||||||
|
PToFile,
|
||||||
|
PToJSON,
|
||||||
|
PToPath,
|
||||||
|
PToString,
|
||||||
|
PToXML,
|
||||||
|
PTrace,
|
||||||
|
PTryEval,
|
||||||
|
PTypeOf,
|
||||||
|
PUnsafeDiscardStringContext,
|
||||||
|
PUnsafeGetAttrPos,
|
||||||
|
PWarn,
|
||||||
|
PZipAttrsWith,
|
||||||
|
PUnsafeDiscardOutputDependency,
|
||||||
|
|
||||||
ForceResultShallow,
|
ForceResultShallow,
|
||||||
ForceResultShallowPush,
|
ForceResultShallowPush,
|
||||||
@@ -268,25 +302,17 @@ pub enum PrimOpPhase {
|
|||||||
CallFunctor1,
|
CallFunctor1,
|
||||||
CallFunctor2,
|
CallFunctor2,
|
||||||
|
|
||||||
ImportFinalize,
|
|
||||||
ScopedImportFinalize,
|
|
||||||
|
|
||||||
AppendContextLoop,
|
|
||||||
AppendContextEntryForced,
|
|
||||||
AppendContextOutputsForced,
|
|
||||||
AppendContextOutputElementLoop,
|
|
||||||
AppendContextOutputElementForced,
|
|
||||||
|
|
||||||
UnsafeDiscardOutputDependency,
|
|
||||||
|
|
||||||
Illegal,
|
Illegal,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<u8> for PrimOpPhase {
|
impl TryFrom<u8> for Continuation {
|
||||||
type Error = u8;
|
type Error = u8;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||||
if (0..Self::Illegal as u8).contains(&value) {
|
if (0..Self::Illegal as u8).contains(&value) {
|
||||||
|
// SAFETY: `value` is in `0..Illegal`, which are exactly the valid
|
||||||
|
// discriminants of this `#[repr(u8)]` enum, so the transmute is sound.
|
||||||
Ok(unsafe { std::mem::transmute::<u8, Self>(value) })
|
Ok(unsafe { std::mem::transmute::<u8, Self>(value) })
|
||||||
} else {
|
} else {
|
||||||
Err(value)
|
Err(value)
|
||||||
@@ -294,117 +320,117 @@ impl TryFrom<u8> for PrimOpPhase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PrimOpPhase {
|
impl Continuation {
|
||||||
pub fn entry_for_builtin(id: BuiltinId) -> Self {
|
pub const fn entry_for_builtin(id: BuiltinId) -> Self {
|
||||||
use BuiltinId::*;
|
use BuiltinId::*;
|
||||||
match id {
|
match id {
|
||||||
Abort => Self::Abort,
|
Abort => Self::PAbort,
|
||||||
Add => Self::Add,
|
Add => Self::PAdd,
|
||||||
AddErrorContext => Self::AddErrorContext,
|
AddErrorContext => Self::PAddErrorContext,
|
||||||
All => Self::All,
|
All => Self::PAll0,
|
||||||
Any => Self::Any,
|
Any => Self::PAny0,
|
||||||
AppendContext => Self::AppendContext,
|
AppendContext => Self::PAppendContext,
|
||||||
AttrNames => Self::AttrNames,
|
AttrNames => Self::PAttrNames,
|
||||||
AttrValues => Self::AttrValues,
|
AttrValues => Self::PAttrValues,
|
||||||
BaseNameOf => Self::BaseNameOf,
|
BaseNameOf => Self::PBaseNameOf,
|
||||||
BitAnd => Self::BitAnd,
|
BitAnd => Self::PBitAnd,
|
||||||
BitOr => Self::BitOr,
|
BitOr => Self::PBitOr,
|
||||||
BitXor => Self::BitXor,
|
BitXor => Self::PBitXor,
|
||||||
Break => Self::Break,
|
Break => Self::PBreak,
|
||||||
CatAttrs => Self::CatAttrs,
|
CatAttrs => Self::PCatAttrs,
|
||||||
Ceil => Self::Ceil,
|
Ceil => Self::PCeil,
|
||||||
CompareVersions => Self::CompareVersions,
|
CompareVersions => Self::PCompareVersions,
|
||||||
ConcatLists => Self::ConcatLists,
|
ConcatLists => Self::PConcatLists,
|
||||||
ConcatMap => Self::ConcatMap,
|
ConcatMap => Self::PConcatMap,
|
||||||
ConcatStringsSep => Self::ConcatStringsSep,
|
ConcatStringsSep => Self::PConcatStringsSep,
|
||||||
ConvertHash => Self::ConvertHash,
|
ConvertHash => Self::PConvertHash,
|
||||||
DeepSeq => Self::DeepSeq,
|
DeepSeq => Self::PDeepSeq0,
|
||||||
Derivation => Self::Derivation,
|
Derivation => Self::PDerivation,
|
||||||
DerivationStrict => Self::DerivationStrict,
|
DerivationStrict => Self::PDerivationStrict,
|
||||||
DirOf => Self::DirOf,
|
DirOf => Self::PDirOf,
|
||||||
Div => Self::Div,
|
Div => Self::PDiv,
|
||||||
Elem => Self::Elem,
|
Elem => Self::PElem,
|
||||||
ElemAt => Self::ElemAt,
|
ElemAt => Self::PElemAt,
|
||||||
FetchGit => Self::FetchGit,
|
FetchGit => Self::PFetchGit,
|
||||||
FetchMercurial => Self::FetchMercurial,
|
FetchMercurial => Self::PFetchMercurial,
|
||||||
FetchTarball => Self::FetchTarball,
|
FetchTarball => Self::PFetchTarball,
|
||||||
FetchTree => Self::FetchTree,
|
FetchTree => Self::PFetchTree,
|
||||||
FetchUrl => Self::FetchUrl,
|
FetchUrl => Self::PFetchUrl,
|
||||||
Filter => Self::FilterForceList,
|
Filter => Self::PFilter0,
|
||||||
FilterSource => Self::FilterSource,
|
FilterSource => Self::PFilterSource,
|
||||||
FindFile => Self::FindFile,
|
FindFile => Self::PFindFile,
|
||||||
Floor => Self::Floor,
|
Floor => Self::PFloor,
|
||||||
FoldlStrict => Self::FoldlStrict,
|
FoldlStrict => Self::PFoldlStrict0,
|
||||||
FromJSON => Self::FromJSON,
|
FromJSON => Self::PFromJSON,
|
||||||
FromTOML => Self::FromTOML,
|
FromTOML => Self::PFromTOML,
|
||||||
FunctionArgs => Self::FunctionArgs,
|
FunctionArgs => Self::PFunctionArgs,
|
||||||
GenList => Self::GenList,
|
GenList => Self::PGenList,
|
||||||
GenericClosure => Self::GenericClosure,
|
GenericClosure => Self::PGenericClosure,
|
||||||
GetAttr => Self::GetAttr,
|
GetAttr => Self::PGetAttr,
|
||||||
GetContext => Self::GetContext,
|
GetContext => Self::PGetContext,
|
||||||
GetEnv => Self::GetEnv,
|
GetEnv => Self::PGetEnv,
|
||||||
GroupBy => Self::GroupBy,
|
GroupBy => Self::PGroupBy,
|
||||||
HasAttr => Self::HasAttr,
|
HasAttr => Self::PHasAttr,
|
||||||
HasContext => Self::HasContext,
|
HasContext => Self::PHasContext,
|
||||||
HashFile => Self::HashFile,
|
HashFile => Self::PHashFile,
|
||||||
HashString => Self::HashString,
|
HashString => Self::PHashString,
|
||||||
Head => Self::Head,
|
Head => Self::PHead,
|
||||||
Import => Self::Import,
|
Import => Self::PImport,
|
||||||
IntersectAttrs => Self::IntersectAttrs,
|
IntersectAttrs => Self::PIntersectAttrs,
|
||||||
IsAttrs => Self::IsAttrs,
|
IsAttrs => Self::PIsAttrs,
|
||||||
IsBool => Self::IsBool,
|
IsBool => Self::PIsBool,
|
||||||
IsFloat => Self::IsFloat,
|
IsFloat => Self::PIsFloat,
|
||||||
IsFunction => Self::IsFunction,
|
IsFunction => Self::PIsFunction,
|
||||||
IsInt => Self::IsInt,
|
IsInt => Self::PIsInt,
|
||||||
IsList => Self::IsList,
|
IsList => Self::PIsList,
|
||||||
IsNull => Self::IsNull,
|
IsNull => Self::PIsNull,
|
||||||
IsPath => Self::IsPath,
|
IsPath => Self::PIsPath,
|
||||||
IsString => Self::IsString,
|
IsString => Self::PIsString,
|
||||||
Length => Self::Length,
|
Length => Self::PLength,
|
||||||
LessThan => Self::LessThan,
|
LessThan => Self::PLessThan,
|
||||||
ListToAttrs => Self::ListToAttrs,
|
ListToAttrs => Self::PListToAttrs,
|
||||||
Map => Self::Map,
|
Map => Self::PMap,
|
||||||
MapAttrs => Self::MapAttrs,
|
MapAttrs => Self::PMapAttrs,
|
||||||
Match => Self::Match,
|
Match => Self::PMatch,
|
||||||
Mul => Self::Mul,
|
Mul => Self::PMul,
|
||||||
ParseDrvName => Self::ParseDrvName,
|
ParseDrvName => Self::PParseDrvName,
|
||||||
Partition => Self::Partition,
|
Partition => Self::PPartition,
|
||||||
Path => Self::Path,
|
Path => Self::PPath,
|
||||||
PathExists => Self::PathExists,
|
PathExists => Self::PPathExists,
|
||||||
Placeholder => Self::Placeholder,
|
Placeholder => Self::PPlaceholder,
|
||||||
ReadDir => Self::ReadDir,
|
ReadDir => Self::PReadDir,
|
||||||
ReadFile => Self::ReadFile,
|
ReadFile => Self::PReadFile,
|
||||||
ReadFileType => Self::ReadFileType,
|
ReadFileType => Self::PReadFileType,
|
||||||
RemoveAttrs => Self::RemoveAttrs,
|
RemoveAttrs => Self::PRemoveAttrs,
|
||||||
ReplaceStrings => Self::ReplaceStrings,
|
ReplaceStrings => Self::PReplaceStrings,
|
||||||
ScopedImport => Self::ScopedImport,
|
ScopedImport => Self::PScopedImport,
|
||||||
Seq => Self::Seq,
|
Seq => Self::PSeq0,
|
||||||
Sort => Self::Sort,
|
Sort => Self::PSort,
|
||||||
Split => Self::Split,
|
Split => Self::PSplit,
|
||||||
SplitVersion => Self::SplitVersion,
|
SplitVersion => Self::PSplitVersion,
|
||||||
StorePath => Self::StorePath,
|
StorePath => Self::PStorePath,
|
||||||
StringLength => Self::StringLength,
|
StringLength => Self::PStringLength,
|
||||||
Sub => Self::Sub,
|
Sub => Self::PSub,
|
||||||
Substring => Self::Substring,
|
Substring => Self::PSubstring,
|
||||||
Tail => Self::Tail,
|
Tail => Self::PTail,
|
||||||
Throw => Self::Throw,
|
Throw => Self::PThrow,
|
||||||
ToFile => Self::ToFile,
|
ToFile => Self::PToFile,
|
||||||
ToJSON => Self::ToJSON,
|
ToJSON => Self::PToJSON,
|
||||||
ToPath => Self::ToPath,
|
ToPath => Self::PToPath,
|
||||||
ToString => Self::ToString,
|
ToString => Self::PToString,
|
||||||
ToXML => Self::ToXML,
|
ToXML => Self::PToXML,
|
||||||
Trace => Self::Trace,
|
Trace => Self::PTrace,
|
||||||
TryEval => Self::TryEval,
|
TryEval => Self::PTryEval,
|
||||||
TypeOf => Self::TypeOf,
|
TypeOf => Self::PTypeOf,
|
||||||
UnsafeDiscardStringContext => Self::UnsafeDiscardStringContext,
|
UnsafeDiscardStringContext => Self::PUnsafeDiscardStringContext,
|
||||||
UnsafeDiscardOutputDependency => Self::UnsafeDiscardOutputDependency,
|
UnsafeDiscardOutputDependency => Self::PUnsafeDiscardOutputDependency,
|
||||||
UnsafeGetAttrPos => Self::UnsafeGetAttrPos,
|
UnsafeGetAttrPos => Self::PUnsafeGetAttrPos,
|
||||||
Warn => Self::Warn,
|
Warn => Self::PWarn,
|
||||||
ZipAttrsWith => Self::ZipAttrsWith,
|
ZipAttrsWith => Self::PZipAttrsWith,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ip(self) -> u32 {
|
pub const fn ip(self) -> u32 {
|
||||||
self as u32 * 2
|
self as u32 * 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -415,7 +441,129 @@ pub struct BytecodeReader<'a> {
|
|||||||
inst_start_pc: usize,
|
inst_start_pc: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::ops::Deref for BytecodeReader<'_> {
|
||||||
|
type Target = [u8];
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
self.bytecode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait FromBytecode {
|
||||||
|
#[must_use]
|
||||||
|
fn read(reader: &mut BytecodeReader<'_>) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> FromBytecode for [u8; N] {
|
||||||
|
#[inline(always)]
|
||||||
|
#[expect(
|
||||||
|
clippy::indexing_slicing,
|
||||||
|
reason = "reads well-formed bytecode; an out-of-range PC indicates a codegen bug and should panic loudly"
|
||||||
|
)]
|
||||||
|
fn read(reader: &mut BytecodeReader<'_>) -> Self {
|
||||||
|
let ret = reader.bytecode[reader.pc..reader.pc + N]
|
||||||
|
.try_into()
|
||||||
|
.expect("read_array failed");
|
||||||
|
reader.pc += N;
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromBytecode for u8 {
|
||||||
|
#[inline(always)]
|
||||||
|
fn read(reader: &mut BytecodeReader<'_>) -> Self {
|
||||||
|
let [byte] = reader.read();
|
||||||
|
byte
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! numbers_impl {
|
||||||
|
( $($ty:ty),* ) => {
|
||||||
|
$(
|
||||||
|
impl FromBytecode for $ty {
|
||||||
|
#[inline(always)]
|
||||||
|
fn read(reader: &mut BytecodeReader<'_>) -> Self {
|
||||||
|
<$ty>::from_le_bytes(FromBytecode::read(reader))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
numbers_impl!(u16, u32, i32, i64, f64);
|
||||||
|
|
||||||
|
impl FromBytecode for Op {
|
||||||
|
#[inline(always)]
|
||||||
|
#[expect(
|
||||||
|
clippy::panic,
|
||||||
|
reason = "reads well-formed bytecode; an unknown opcode indicates a codegen bug and should panic loudly"
|
||||||
|
)]
|
||||||
|
fn read(reader: &mut BytecodeReader<'_>) -> Self {
|
||||||
|
reader.inst_start_pc = reader.pc;
|
||||||
|
let byte: u8 = reader.read();
|
||||||
|
byte.try_into().unwrap_or_else(|byte| {
|
||||||
|
std::hint::cold_path();
|
||||||
|
panic!("unknown opcode: {byte:#04x}")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromBytecode for StringId {
|
||||||
|
#[inline(always)]
|
||||||
|
fn read(reader: &mut BytecodeReader<'_>) -> Self {
|
||||||
|
let raw: u32 = reader.read();
|
||||||
|
StringId(
|
||||||
|
string_interner::symbol::SymbolU32::try_from_usize(raw as usize)
|
||||||
|
.expect("raw is a valid interner symbol index"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromBytecode for OperandData {
|
||||||
|
#[inline(always)]
|
||||||
|
#[expect(
|
||||||
|
clippy::panic,
|
||||||
|
reason = "an unknown operand tag in well-formed bytecode indicates a codegen bug and should panic loudly"
|
||||||
|
)]
|
||||||
|
fn read(reader: &mut BytecodeReader<'_>) -> Self {
|
||||||
|
let tag = reader.read();
|
||||||
|
let ty = OperandType::try_from_primitive(tag).unwrap_or_else(|err| {
|
||||||
|
std::hint::cold_path();
|
||||||
|
panic!("unknown operand tag: {:#04x}", err.number)
|
||||||
|
});
|
||||||
|
match ty {
|
||||||
|
OperandType::Const => OperandData::Const(reader.read()),
|
||||||
|
OperandType::BigInt => OperandData::BigInt(reader.read()),
|
||||||
|
OperandType::Local => {
|
||||||
|
let layer = reader.read();
|
||||||
|
let idx = reader.read();
|
||||||
|
OperandData::Local { layer, idx }
|
||||||
|
}
|
||||||
|
OperandType::BuiltinConst => OperandData::BuiltinConst(reader.read()),
|
||||||
|
OperandType::Builtins => OperandData::Builtins,
|
||||||
|
OperandType::ReplBinding => OperandData::ReplBinding(reader.read()),
|
||||||
|
OperandType::ScopedImportBinding => {
|
||||||
|
let slot_id = reader.read();
|
||||||
|
let name = reader.read();
|
||||||
|
OperandData::ScopedImportBinding { slot_id, name }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! read_aliases {
|
||||||
|
{ $($alias:ident -> $ty:ty),*$(,)? } => {
|
||||||
|
$(
|
||||||
|
#[inline(always)]
|
||||||
|
#[must_use]
|
||||||
|
pub fn $alias(&mut self) -> $ty {
|
||||||
|
<$ty>::read(self)
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> BytecodeReader<'a> {
|
impl<'a> BytecodeReader<'a> {
|
||||||
|
#[must_use]
|
||||||
pub fn new(bytecode: &'a [u8], pc: usize) -> Self {
|
pub fn new(bytecode: &'a [u8], pc: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
bytecode,
|
bytecode,
|
||||||
@@ -425,6 +573,7 @@ impl<'a> BytecodeReader<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
#[must_use]
|
||||||
pub fn from_after_op(bytecode: &'a [u8], inst_start_pc: usize) -> Self {
|
pub fn from_after_op(bytecode: &'a [u8], inst_start_pc: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
bytecode,
|
bytecode,
|
||||||
@@ -434,88 +583,21 @@ impl<'a> BytecodeReader<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[cfg_attr(debug_assertions, track_caller)]
|
#[must_use]
|
||||||
fn read_array<const N: usize>(&mut self) -> [u8; N] {
|
pub fn read<T: FromBytecode>(&mut self) -> T {
|
||||||
let ret = self.bytecode[self.pc..self.pc + N]
|
T::read(self)
|
||||||
.try_into()
|
|
||||||
.expect("read_array failed");
|
|
||||||
self.pc += N;
|
|
||||||
ret
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
read_aliases! {
|
||||||
pub fn read_op(&mut self) -> Op {
|
read_op -> Op,
|
||||||
self.inst_start_pc = self.pc;
|
read_u8 -> u8,
|
||||||
let byte = self.bytecode[self.pc];
|
read_u16 -> u16,
|
||||||
if !(0..Op::Illegal as u8).contains(&byte) {
|
read_u32 -> u32,
|
||||||
std::hint::cold_path();
|
read_i32 -> i32,
|
||||||
panic!("unknown opcode: {byte:#04x}")
|
read_i64 -> i64,
|
||||||
}
|
read_f64 -> f64,
|
||||||
self.pc += 1;
|
read_string_id -> StringId,
|
||||||
unsafe { std::mem::transmute::<u8, Op>(byte) }
|
read_operand_data -> OperandData,
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn read_u8(&mut self) -> u8 {
|
|
||||||
let val = self.bytecode[self.pc];
|
|
||||||
self.pc += 1;
|
|
||||||
val
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn read_u16(&mut self) -> u16 {
|
|
||||||
u16::from_le_bytes(self.read_array())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn read_u32(&mut self) -> u32 {
|
|
||||||
u32::from_le_bytes(self.read_array())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn read_i32(&mut self) -> i32 {
|
|
||||||
i32::from_le_bytes(self.read_array())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn read_i64(&mut self) -> i64 {
|
|
||||||
i64::from_le_bytes(self.read_array())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn read_f64(&mut self) -> f64 {
|
|
||||||
f64::from_le_bytes(self.read_array())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn read_string_id(&mut self) -> StringId {
|
|
||||||
let raw = self.read_u32();
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
StringId(string_interner::symbol::SymbolU32::try_from_usize(raw as usize).unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn read_operand_data(&mut self) -> OperandData {
|
|
||||||
let tag = self.read_u8();
|
|
||||||
let Ok(ty) = OperandType::try_from_primitive(tag)
|
|
||||||
.map_err(|err| panic!("unknown operand tag: {:#04x}", err.number));
|
|
||||||
match ty {
|
|
||||||
OperandType::Const => OperandData::Const(self.read_u32()),
|
|
||||||
OperandType::BigInt => OperandData::BigInt(self.read_i64()),
|
|
||||||
OperandType::Local => {
|
|
||||||
let layer = self.read_u8();
|
|
||||||
let idx = self.read_u32();
|
|
||||||
OperandData::Local { layer, idx }
|
|
||||||
}
|
|
||||||
OperandType::BuiltinConst => OperandData::BuiltinConst(self.read_string_id()),
|
|
||||||
OperandType::Builtins => OperandData::Builtins,
|
|
||||||
OperandType::ReplBinding => OperandData::ReplBinding(self.read_string_id()),
|
|
||||||
OperandType::ScopedImportBinding => {
|
|
||||||
let slot_id = self.read_u32();
|
|
||||||
let name = self.read_string_id();
|
|
||||||
OperandData::ScopedImportBinding { slot_id, name }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pc(&self) -> usize {
|
pub fn pc(&self) -> usize {
|
||||||
|
|||||||
@@ -17,3 +17,6 @@ fix-error = { path = "../fix-error" }
|
|||||||
fix-lang = { path = "../fix-lang" }
|
fix-lang = { path = "../fix-lang" }
|
||||||
fix-runtime = { path = "../fix-runtime" }
|
fix-runtime = { path = "../fix-runtime" }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|||||||
+46
-18
@@ -1,5 +1,5 @@
|
|||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use fix_bytecode::{Const, InstructionPtr, Op, PrimOpPhase};
|
use fix_bytecode::{Const, Continuation, InstructionPtr, Op};
|
||||||
use fix_error::{Error, Result, Source};
|
use fix_error::{Error, Result, Source};
|
||||||
use fix_lang::{StringId, Symbol};
|
use fix_lang::{StringId, Symbol};
|
||||||
use fix_runtime::{StaticValue, VmCode, VmRuntimeCtx};
|
use fix_runtime::{StaticValue, VmCode, VmRuntimeCtx};
|
||||||
@@ -25,9 +25,9 @@ pub struct CodeState {
|
|||||||
impl CodeState {
|
impl CodeState {
|
||||||
pub fn new(strings: &mut DefaultStringInterner) -> Self {
|
pub fn new(strings: &mut DefaultStringInterner) -> Self {
|
||||||
let global_env = crate::ir::new_global_env(strings);
|
let global_env = crate::ir::new_global_env(strings);
|
||||||
let mut bytecode = Vec::with_capacity(PrimOpPhase::Illegal as usize * 2);
|
let mut bytecode = Vec::with_capacity(Continuation::Illegal as usize * 2);
|
||||||
for phase in 0..=PrimOpPhase::Illegal as u8 {
|
for phase in 0..=Continuation::Illegal as u8 {
|
||||||
bytecode.push(Op::DispatchPrimOp as u8);
|
bytecode.push(Op::DispatchCont as u8);
|
||||||
bytecode.push(phase);
|
bytecode.push(phase);
|
||||||
}
|
}
|
||||||
Self {
|
Self {
|
||||||
@@ -114,8 +114,13 @@ impl<'a, R: VmRuntimeCtx> CompilerCtx<'a, R> {
|
|||||||
source,
|
source,
|
||||||
);
|
);
|
||||||
let ir = downgrade_ctx.downgrade_toplevel(expr)?;
|
let ir = downgrade_ctx.downgrade_toplevel(expr)?;
|
||||||
|
// SAFETY: `ir` borrows from `bump`, which is moved into the returned
|
||||||
|
// `OwnedIr` alongside the reference. The `'static` lifetime is a
|
||||||
|
// storage token that is re-narrowed to the `OwnedIr`'s own borrow in
|
||||||
|
// `as_ref`, so no reference ever outlives the arena backing it.
|
||||||
let ir = unsafe { std::mem::transmute::<RawIrRef<'_>, RawIrRef<'static>>(ir) };
|
let ir = unsafe { std::mem::transmute::<RawIrRef<'_>, RawIrRef<'static>>(ir) };
|
||||||
Ok(OwnedIr { _bump: bump, ir })
|
// SAFETY: `ir` borrows from `bump`
|
||||||
|
Ok(unsafe { OwnedIr::new(bump, ir) })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -148,16 +153,12 @@ impl<'a, R: VmRuntimeCtx> BytecodeContext for CompilerCtx<'a, R> {
|
|||||||
fn add_constant(&mut self, val: Const) -> u32 {
|
fn add_constant(&mut self, val: Const) -> u32 {
|
||||||
use Const::*;
|
use Const::*;
|
||||||
let val = match val {
|
let val = match val {
|
||||||
Smi(x) => StaticValue::new_inline(x),
|
Smi(x) => StaticValue::new(x),
|
||||||
Float(x) => StaticValue::new_float(x),
|
Float(x) => StaticValue::new(x),
|
||||||
Bool(x) => StaticValue::new_inline(x),
|
Bool(x) => StaticValue::new(x),
|
||||||
String(x) => StaticValue::new_inline(x),
|
String(x) => StaticValue::new(x),
|
||||||
Path(x) => StaticValue::new_inline(fix_runtime::Path(x)),
|
Path(x) => StaticValue::new(fix_runtime::Path(x)),
|
||||||
PrimOp {
|
PrimOp(id) => StaticValue::new(fix_runtime::PrimOp::from(id)),
|
||||||
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)
|
||||||
@@ -284,6 +285,10 @@ impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir>
|
|||||||
self.runtime.resolve_string(id).into()
|
self.runtime.resolve_string(id).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(
|
||||||
|
clippy::unwrap_in_result,
|
||||||
|
reason = "exceeding u8::MAX thunk scope layers is a compiler invariant violation, not a user-facing error"
|
||||||
|
)]
|
||||||
fn lookup(
|
fn lookup(
|
||||||
&mut self,
|
&mut self,
|
||||||
sym: StringId,
|
sym: StringId,
|
||||||
@@ -360,6 +365,10 @@ impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir>
|
|||||||
self.source.clone()
|
self.source.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(
|
||||||
|
clippy::panic_in_result_fn,
|
||||||
|
reason = "assert_eq guards an internal invariant (bindings and values have equal length); a mismatch is a compiler bug, not a user error"
|
||||||
|
)]
|
||||||
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(
|
F: FnOnce(
|
||||||
@@ -421,6 +430,10 @@ impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir>
|
|||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(
|
||||||
|
clippy::panic,
|
||||||
|
reason = "exceeding u8::MAX thunk scope layers is a compiler invariant violation, not a user-facing error"
|
||||||
|
)]
|
||||||
fn with_thunk_scope<F, Ret>(
|
fn with_thunk_scope<F, Ret>(
|
||||||
&mut self,
|
&mut self,
|
||||||
f: F,
|
f: F,
|
||||||
@@ -486,7 +499,6 @@ enum Scope<'ctx, 'id, 'ir> {
|
|||||||
Repl(&'ctx HashSet<StringId>),
|
Repl(&'ctx HashSet<StringId>),
|
||||||
ScopedImport {
|
ScopedImport {
|
||||||
keys: HashSet<StringId>,
|
keys: HashSet<StringId>,
|
||||||
#[allow(dead_code)]
|
|
||||||
slot_id: u32,
|
slot_id: u32,
|
||||||
},
|
},
|
||||||
Let(HashMap<StringId, GhostMaybeThunkRef<'id, 'ir>>),
|
Let(HashMap<StringId, GhostMaybeThunkRef<'id, 'ir>>),
|
||||||
@@ -530,13 +542,29 @@ impl<'a, 'ctx, 'id, 'ir, R: VmRuntimeCtx> ScopeGuard<'a, 'ctx, 'id, 'ir, R> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct OwnedIr {
|
mod sealed {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub struct OwnedIr {
|
||||||
_bump: Bump,
|
_bump: Bump,
|
||||||
ir: RawIrRef<'static>,
|
ir: RawIrRef<'static>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OwnedIr {
|
impl OwnedIr {
|
||||||
fn as_ref<'ir>(&'ir self) -> RawIrRef<'ir> {
|
/// # Safety
|
||||||
|
/// `ir` must borrows from `bump`
|
||||||
|
pub unsafe fn new(bump: Bump, ir: RawIrRef<'static>) -> Self {
|
||||||
|
Self { _bump: bump, ir }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_ref<'ir>(&'ir self) -> RawIrRef<'ir> {
|
||||||
|
// SAFETY: `self.ir`'s `'static` lifetime is a storage token; the IR is
|
||||||
|
// backed by `self._bump`, which lives as long as `self`. Narrowing to
|
||||||
|
// `'ir` (tied to `&self`) hands out a reference that cannot outlive the
|
||||||
|
// arena.
|
||||||
unsafe { std::mem::transmute::<RawIrRef<'static>, RawIrRef<'ir>>(self.ir) }
|
unsafe { std::mem::transmute::<RawIrRef<'static>, RawIrRef<'ir>>(self.ir) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub use sealed::OwnedIr;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::marker::PhantomData;
|
|||||||
|
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use bumpalo::collections::Vec;
|
use bumpalo::collections::Vec;
|
||||||
use fix_lang::{BUILTINS, BuiltinId, StringId};
|
use fix_lang::{BuiltinId, StringId};
|
||||||
use ghost_cell::{GhostCell, GhostToken};
|
use ghost_cell::{GhostCell, GhostToken};
|
||||||
use rnix::{TextRange, ast};
|
use rnix::{TextRange, ast};
|
||||||
use string_interner::DefaultStringInterner;
|
use string_interner::DefaultStringInterner;
|
||||||
@@ -222,7 +222,6 @@ pub enum Ir<'ir, R: RefExt<'ir> + ?Sized + 'ir> {
|
|||||||
pub struct ThunkId(pub usize);
|
pub struct ThunkId(pub usize);
|
||||||
|
|
||||||
/// Represents a key in an attribute path.
|
/// Represents a key in an attribute path.
|
||||||
#[allow(unused)]
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Attr<Ref> {
|
pub enum Attr<Ref> {
|
||||||
/// A dynamic attribute key, which is an expression that must evaluate to a string.
|
/// A dynamic attribute key, which is an expression that must evaluate to a string.
|
||||||
@@ -291,9 +290,8 @@ pub fn new_global_env(
|
|||||||
let builtins_sym = StringId(strings.get_or_intern("builtins"));
|
let builtins_sym = StringId(strings.get_or_intern("builtins"));
|
||||||
global_env.insert(builtins_sym, MaybeThunk::Builtins);
|
global_env.insert(builtins_sym, MaybeThunk::Builtins);
|
||||||
|
|
||||||
for (idx, &(name, _)) in BUILTINS.iter().enumerate() {
|
for id in BuiltinId::ALL {
|
||||||
let id = BuiltinId::try_from(idx as u8).expect("infallible");
|
let name = StringId(strings.get_or_intern(id.info().global_name));
|
||||||
let name = StringId(strings.get_or_intern(name));
|
|
||||||
global_env.insert(name, MaybeThunk::Builtin(id));
|
global_env.insert(name, MaybeThunk::Builtin(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -156,6 +156,10 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
|||||||
let path = {
|
let path = {
|
||||||
let temp = self.content().require(ctx, span)?;
|
let temp = self.content().require(ctx, span)?;
|
||||||
let text = temp.text();
|
let text = temp.text();
|
||||||
|
#[expect(
|
||||||
|
clippy::string_slice,
|
||||||
|
reason = "PathSearch text is <...> wrapped in ASCII angle brackets, so byte indices 1 and len-1 are char boundaries"
|
||||||
|
)]
|
||||||
let id = ctx.intern_string(&text[1..text.len() - 1]);
|
let id = ctx.intern_string(&text[1..text.len() - 1]);
|
||||||
let expr = ctx.new_expr(Ir::Str(id));
|
let expr = ctx.new_expr(Ir::Str(id));
|
||||||
ctx.maybe_thunk(expr)
|
ctx.maybe_thunk(expr)
|
||||||
@@ -462,16 +466,14 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
|||||||
body: GhostRoIrRef<'id, 'ir>,
|
body: GhostRoIrRef<'id, 'ir>,
|
||||||
}
|
}
|
||||||
|
|
||||||
let (ret, thunks) = ctx.with_thunk_scope(|ctx| {
|
let (ret, thunks) = ctx.with_thunk_scope(|ctx| -> Result<Ret> {
|
||||||
let param;
|
let (param, body) = match raw_param {
|
||||||
let body;
|
|
||||||
|
|
||||||
match raw_param {
|
|
||||||
ast::Param::IdentParam(id) => {
|
ast::Param::IdentParam(id) => {
|
||||||
let param_sym = ctx.intern_string(id.to_string());
|
let param_sym = ctx.intern_string(id.to_string());
|
||||||
param = None;
|
(
|
||||||
|
None,
|
||||||
body = ctx.with_param_scope(param_sym, |ctx| body_ast.downgrade(ctx))?;
|
ctx.with_param_scope(param_sym, |ctx| body_ast.downgrade(ctx))?,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
ast::Param::Pattern(pattern) => {
|
ast::Param::Pattern(pattern) => {
|
||||||
let alias = pattern
|
let alias = pattern
|
||||||
@@ -493,17 +495,18 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
|||||||
body_ast.clone().downgrade(ctx)
|
body_ast.clone().downgrade(ctx)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
param = Some(Param {
|
(
|
||||||
|
Some(Param {
|
||||||
required,
|
required,
|
||||||
optional,
|
optional,
|
||||||
ellipsis,
|
ellipsis,
|
||||||
});
|
}),
|
||||||
|
inner_body,
|
||||||
body = inner_body;
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Result::Ok(Ret { param, body })
|
Ok(Ret { param, body })
|
||||||
});
|
});
|
||||||
let Ret { param, body } = ret?;
|
let Ret { param, body } = ret?;
|
||||||
|
|
||||||
@@ -550,6 +553,10 @@ impl<'id: 'ir, 'ir> PendingAttrSet<'ir> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(
|
||||||
|
clippy::indexing_slicing,
|
||||||
|
reason = "path is non-empty here: path.first() was just unwrapped above, so path[1..] is in bounds"
|
||||||
|
)]
|
||||||
fn insert(
|
fn insert(
|
||||||
&mut self,
|
&mut self,
|
||||||
path: &[ast::Attr],
|
path: &[ast::Attr],
|
||||||
@@ -651,6 +658,10 @@ impl<'id: 'ir, 'ir> PendingAttrSet<'ir> {
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if !path.is_empty() {
|
if !path.is_empty() {
|
||||||
let mut nested = PendingAttrSet::new_in(ctx.bump());
|
let mut nested = PendingAttrSet::new_in(ctx.bump());
|
||||||
|
#[expect(
|
||||||
|
clippy::indexing_slicing,
|
||||||
|
reason = "path is non-empty in this branch, so path[0] and path[1..] are in bounds"
|
||||||
|
)]
|
||||||
nested.insert_dynamic(
|
nested.insert_dynamic(
|
||||||
path[0].clone(),
|
path[0].clone(),
|
||||||
path[0].syntax().text_range(),
|
path[0].syntax().text_range(),
|
||||||
@@ -665,6 +676,10 @@ impl<'id: 'ir, 'ir> PendingAttrSet<'ir> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(
|
||||||
|
clippy::unreachable,
|
||||||
|
reason = "value was just reassigned to PendingValue::Set on the line above, so the re-match always hits the Set arm"
|
||||||
|
)]
|
||||||
fn ensure_pending_set<'a>(
|
fn ensure_pending_set<'a>(
|
||||||
value: &'a mut PendingValue<'ir>,
|
value: &'a mut PendingValue<'ir>,
|
||||||
ctx: &mut impl DowngradeContext<'id, 'ir>,
|
ctx: &mut impl DowngradeContext<'id, 'ir>,
|
||||||
|
|||||||
+32
-13
@@ -1,5 +1,5 @@
|
|||||||
use fix_bytecode::{Const, InstructionPtr, Op, OperandType, PrimOpPhase};
|
use fix_bytecode::{Const, InstructionPtr, Op, OperandType};
|
||||||
use fix_lang::{BUILTINS, StringId};
|
use fix_lang::StringId;
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use rnix::TextRange;
|
use rnix::TextRange;
|
||||||
use string_interner::Symbol as _;
|
use string_interner::Symbol as _;
|
||||||
@@ -54,7 +54,6 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
fn inline_maybe_thunk(&self, val: &MaybeThunk) -> InlineOperand {
|
fn inline_maybe_thunk(&self, val: &MaybeThunk) -> InlineOperand {
|
||||||
use MaybeThunk::*;
|
use MaybeThunk::*;
|
||||||
match *val {
|
match *val {
|
||||||
@@ -75,14 +74,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
InlineOperand::Local { layer, local }
|
InlineOperand::Local { layer, local }
|
||||||
}
|
}
|
||||||
Arg { layer } => InlineOperand::Local { layer, local: 0 },
|
Arg { layer } => InlineOperand::Local { layer, local: 0 },
|
||||||
Builtin(id) => {
|
Builtin(id) => InlineOperand::Const(Const::PrimOp(id)),
|
||||||
let (_, arity) = BUILTINS[id as usize];
|
|
||||||
InlineOperand::Const(Const::PrimOp {
|
|
||||||
id,
|
|
||||||
arity,
|
|
||||||
dispatch_ip: PrimOpPhase::entry_for_builtin(id).ip(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
BuiltinConst(id) => InlineOperand::BuiltinConst(id),
|
BuiltinConst(id) => InlineOperand::BuiltinConst(id),
|
||||||
Builtins => InlineOperand::Builtins,
|
Builtins => InlineOperand::Builtins,
|
||||||
ReplBinding(id) => InlineOperand::ReplBinding(id),
|
ReplBinding(id) => InlineOperand::ReplBinding(id),
|
||||||
@@ -186,6 +178,10 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
offset
|
offset
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
|
#[expect(
|
||||||
|
clippy::indexing_slicing,
|
||||||
|
reason = "offset addresses a 4-byte i32 placeholder this compiler previously emitted, so it is in bounds"
|
||||||
|
)]
|
||||||
fn patch_i32(&mut self, offset: usize, val: i32) {
|
fn patch_i32(&mut self, offset: usize, val: i32) {
|
||||||
self.ctx.get_code_mut()[offset..offset + 4].copy_from_slice(&val.to_le_bytes());
|
self.ctx.get_code_mut()[offset..offset + 4].copy_from_slice(&val.to_le_bytes());
|
||||||
}
|
}
|
||||||
@@ -214,6 +210,10 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
self.scope_stack.last().map_or(0, |s| s.depth)
|
self.scope_stack.last().map_or(0, |s| s.depth)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(
|
||||||
|
clippy::panic,
|
||||||
|
reason = "a ThunkId must resolve in some enclosing scope; failure indicates a compiler bug"
|
||||||
|
)]
|
||||||
fn resolve_thunk(&self, id: ThunkId) -> (u8, 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) {
|
||||||
@@ -520,6 +520,10 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(
|
||||||
|
clippy::unreachable,
|
||||||
|
reason = "the outer match only reaches this arm for the binary operator kinds enumerated above"
|
||||||
|
)]
|
||||||
fn emit_binop(&mut self, lhs: RawIrRef<'_>, rhs: RawIrRef<'_>, kind: BinOpKind) {
|
fn emit_binop(&mut self, lhs: RawIrRef<'_>, rhs: RawIrRef<'_>, kind: BinOpKind) {
|
||||||
use BinOpKind::*;
|
use BinOpKind::*;
|
||||||
match kind {
|
match kind {
|
||||||
@@ -712,14 +716,25 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(default) = default {
|
if let Some(default) = default {
|
||||||
let before: i32 = self.ctx.get_code().len().try_into().unwrap();
|
// FIXME: i32???
|
||||||
|
let before: i32 = self
|
||||||
|
.ctx
|
||||||
|
.get_code()
|
||||||
|
.len()
|
||||||
|
.try_into()
|
||||||
|
.expect("emitted code length fits in i32");
|
||||||
for patch in dynamic_patches {
|
for patch in dynamic_patches {
|
||||||
self.patch_jump_target(patch);
|
self.patch_jump_target(patch);
|
||||||
}
|
}
|
||||||
self.emit_op(Op::JumpIfSelectSucceeded);
|
self.emit_op(Op::JumpIfSelectSucceeded);
|
||||||
let placeholder = self.emit_i32_placeholder();
|
let placeholder = self.emit_i32_placeholder();
|
||||||
self.emit_expr(default);
|
self.emit_expr(default);
|
||||||
let after: i32 = self.ctx.get_code().len().try_into().unwrap();
|
let after: i32 = self
|
||||||
|
.ctx
|
||||||
|
.get_code()
|
||||||
|
.len()
|
||||||
|
.try_into()
|
||||||
|
.expect("emitted code length fits in i32");
|
||||||
// Offset is relative to after the placeholder, so subtract the
|
// Offset is relative to after the placeholder, so subtract the
|
||||||
// size of JumpIfSelectSucceeded (1) + placeholder (4).
|
// size of JumpIfSelectSucceeded (1) + placeholder (4).
|
||||||
self.patch_i32(placeholder, after - before - 5);
|
self.patch_i32(placeholder, after - before - 5);
|
||||||
@@ -730,6 +745,10 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(
|
||||||
|
clippy::panic,
|
||||||
|
reason = "a hasAttr attrpath always has at least one attr by construction of the AST"
|
||||||
|
)]
|
||||||
fn emit_has_attr(&mut self, lhs: RawIrRef<'_>, rhs: &[Attr<RawIrRef<'_>>]) {
|
fn emit_has_attr(&mut self, lhs: RawIrRef<'_>, rhs: &[Attr<RawIrRef<'_>>]) {
|
||||||
self.emit_expr(lhs);
|
self.emit_expr(lhs);
|
||||||
|
|
||||||
|
|||||||
@@ -7,3 +7,6 @@ edition = "2024"
|
|||||||
miette = { version = "7.6", features = ["fancy"] }
|
miette = { version = "7.6", features = ["fancy"] }
|
||||||
rnix = { workspace = true }
|
rnix = { workspace = true }
|
||||||
thiserror = "2.0"
|
thiserror = "2.0"
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use std::sync::Arc;
|
|||||||
use miette::{Diagnostic, NamedSource, SourceSpan};
|
use miette::{Diagnostic, NamedSource, SourceSpan};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
pub type Result<T> = core::result::Result<T, Box<Error>>;
|
pub type Result<T, E = Box<Error>> = core::result::Result<T, E>;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum SourceType {
|
pub enum SourceType {
|
||||||
|
|||||||
@@ -8,3 +8,6 @@ ere = { workspace = true }
|
|||||||
gc-arena = { workspace = true }
|
gc-arena = { workspace = true }
|
||||||
num_enum = { workspace = true }
|
num_enum = { workspace = true }
|
||||||
string-interner = { workspace = true }
|
string-interner = { workspace = true }
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|||||||
+42
-6
@@ -8,7 +8,7 @@ use num_enum::TryFromPrimitive;
|
|||||||
|
|
||||||
macro_rules! define_builtins {
|
macro_rules! define_builtins {
|
||||||
($(($name:literal, $variant:ident, $arity:expr)),* $(,)?) => {
|
($(($name:literal, $variant:ident, $arity:expr)),* $(,)?) => {
|
||||||
pub const BUILTINS: &[(&str, u8)] = &[
|
const BUILTINS: &[(&str, u8)] = &[
|
||||||
$(($name, $arity),)*
|
$(($name, $arity),)*
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -18,6 +18,10 @@ macro_rules! define_builtins {
|
|||||||
pub enum BuiltinId {
|
pub enum BuiltinId {
|
||||||
$($variant,)*
|
$($variant,)*
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl BuiltinId {
|
||||||
|
pub const ALL: [Self; BUILTINS.len()] = [$(Self::$variant,)*];
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,6 +131,36 @@ define_builtins! {
|
|||||||
("__zipAttrsWith", ZipAttrsWith, 2),
|
("__zipAttrsWith", ZipAttrsWith, 2),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct BuiltinInfo {
|
||||||
|
pub name: &'static str,
|
||||||
|
pub global_name: &'static str,
|
||||||
|
pub global: bool,
|
||||||
|
pub arity: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BuiltinId {
|
||||||
|
pub const TOTAL: usize = BUILTINS.len();
|
||||||
|
|
||||||
|
#[expect(
|
||||||
|
clippy::indexing_slicing,
|
||||||
|
reason = "a `BuiltinId` discriminant is always a valid index into the `BUILTINS` table"
|
||||||
|
)]
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn info(self) -> BuiltinInfo {
|
||||||
|
let (global_name, arity) = BUILTINS[self as usize];
|
||||||
|
let (name, global) = global_name
|
||||||
|
.strip_prefix("__")
|
||||||
|
.map_or((global_name, true), |name| (name, false));
|
||||||
|
BuiltinInfo {
|
||||||
|
name,
|
||||||
|
global_name,
|
||||||
|
global,
|
||||||
|
arity,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Collect)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Collect)]
|
||||||
#[collect(require_static)]
|
#[collect(require_static)]
|
||||||
@@ -427,6 +461,10 @@ fn fmt_nix_float(f: &mut Formatter<'_>, x: f64) -> FmtResult {
|
|||||||
let precision: i32 = 6;
|
let precision: i32 = 6;
|
||||||
let exp = x.abs().log10().floor() as i32;
|
let exp = x.abs().log10().floor() as i32;
|
||||||
|
|
||||||
|
#[expect(
|
||||||
|
clippy::cast_sign_loss,
|
||||||
|
reason = "this branch runs only when exp < precision, so precision-1-exp and precision-1 are non-negative"
|
||||||
|
)]
|
||||||
let formatted = if exp >= -4 && exp < precision {
|
let formatted = if exp >= -4 && exp < precision {
|
||||||
let decimal_places = (precision - 1 - exp) as usize;
|
let decimal_places = (precision - 1 - exp) as usize;
|
||||||
format!("{x:.decimal_places$}")
|
format!("{x:.decimal_places$}")
|
||||||
@@ -451,11 +489,9 @@ fn fmt_nix_float(f: &mut Formatter<'_>, x: f64) -> FmtResult {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if formatted.contains('.') {
|
if formatted.contains('.') {
|
||||||
if let Some(e_pos) = formatted.find('e') {
|
if let Some((head, tail)) = formatted.split_once('e') {
|
||||||
let trimmed = formatted[..e_pos]
|
let trimmed = head.trim_end_matches('0').trim_end_matches('.');
|
||||||
.trim_end_matches('0')
|
write!(f, "{trimmed}e{tail}")
|
||||||
.trim_end_matches('.');
|
|
||||||
write!(f, "{}{}", trimmed, &formatted[e_pos..])
|
|
||||||
} else {
|
} else {
|
||||||
let trimmed = formatted.trim_end_matches('0').trim_end_matches('.');
|
let trimmed = formatted.trim_end_matches('0').trim_end_matches('.');
|
||||||
write!(f, "{trimmed}")
|
write!(f, "{trimmed}")
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "fix-macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
proc-macro2 = "1.0"
|
||||||
|
quote = "1.0"
|
||||||
|
syn = { version = "3.0", features = ["full", "visit", "visit-mut"] }
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,49 @@
|
|||||||
|
extern crate proc_macro;
|
||||||
|
|
||||||
|
mod handler;
|
||||||
|
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn handler(
|
||||||
|
attr: proc_macro::TokenStream,
|
||||||
|
item: proc_macro::TokenStream,
|
||||||
|
) -> proc_macro::TokenStream {
|
||||||
|
handler::handler(attr.into(), item.into()).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adapted from `__unelide_lifetimes` in `gc-arena-derive`.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
// See: https://github.com/kyren/gc-arena
|
||||||
|
#[proc_macro]
|
||||||
|
pub fn unelide_lifetimes(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
|
use quote::ToTokens;
|
||||||
|
use syn::parse::{Parse, ParseStream};
|
||||||
|
use syn::visit_mut::VisitMut;
|
||||||
|
|
||||||
|
struct Input {
|
||||||
|
lt: syn::Lifetime,
|
||||||
|
ty: syn::Type,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Input {
|
||||||
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||||
|
let lt: syn::Lifetime = input.parse()?;
|
||||||
|
let _: syn::Token!(;) = input.parse()?;
|
||||||
|
let ty: syn::Type = input.parse()?;
|
||||||
|
Ok(Self { lt, ty })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UnelideLifetimes(syn::Lifetime);
|
||||||
|
|
||||||
|
impl VisitMut for UnelideLifetimes {
|
||||||
|
fn visit_lifetime_mut(&mut self, i: &mut syn::Lifetime) {
|
||||||
|
if i.ident == "_" {
|
||||||
|
*i = self.0.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut input = syn::parse_macro_input!(input as Input);
|
||||||
|
UnelideLifetimes(input.lt).visit_type_mut(&mut input.ty);
|
||||||
|
input.ty.to_token_stream().into()
|
||||||
|
}
|
||||||
@@ -13,3 +13,7 @@ string-interner = { workspace = true }
|
|||||||
fix-bytecode = { path = "../fix-bytecode" }
|
fix-bytecode = { path = "../fix-bytecode" }
|
||||||
fix-error = { path = "../fix-error" }
|
fix-error = { path = "../fix-error" }
|
||||||
fix-lang = { path = "../fix-lang" }
|
fix-lang = { path = "../fix-lang" }
|
||||||
|
fix-macros = { path = "../fix-macros" }
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|||||||
+62
-16
@@ -1,4 +1,7 @@
|
|||||||
#![allow(dead_code)]
|
#![allow(
|
||||||
|
dead_code,
|
||||||
|
reason = "boxing layer exposes a full API surface; some helpers are used only on specific targets or not yet wired up"
|
||||||
|
)]
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::num::NonZeroU8;
|
use std::num::NonZeroU8;
|
||||||
@@ -79,41 +82,48 @@ int_store!(i16);
|
|||||||
int_store!(i32);
|
int_store!(i32);
|
||||||
|
|
||||||
fn store_ptr<P: Strict + Copy>(value: &mut Value, ptr: P) {
|
fn store_ptr<P: Strict + Copy>(value: &mut Value, ptr: P) {
|
||||||
#[cfg(target_pointer_width = "64")]
|
cfg_select! {
|
||||||
{
|
target_pointer_width = "64" => {
|
||||||
assert!(
|
assert!(
|
||||||
ptr.addr() <= 0x0000_FFFF_FFFF_FFFF,
|
ptr.addr() <= 0x0000_FFFF_FFFF_FFFF,
|
||||||
"Pointer too large to store in NaN box"
|
"Pointer too large to store in NaN box"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// SAFETY: `Value` is `#[repr(C, align(8))]` and exactly 8 bytes, so it is
|
||||||
|
// sound to reinterpret its storage as a `[u8; 8]` that we then treat as `P`
|
||||||
|
// (a pointer-sized value).
|
||||||
let val = (unsafe { value.whole_mut() } as *mut [u8; 8]).cast::<P>();
|
let val = (unsafe { value.whole_mut() } as *mut [u8; 8]).cast::<P>();
|
||||||
|
|
||||||
let ptr = Strict::map_addr(ptr, |addr| {
|
let ptr = Strict::map_addr(ptr, |addr| {
|
||||||
addr | (usize::from(value.header().into_raw()) << 48)
|
addr | (usize::from(value.header().into_raw()) << 48)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// SAFETY: `val` points to the `Value`'s 8-byte storage, which is valid and
|
||||||
|
// suitably aligned to hold `P`.
|
||||||
unsafe { val.write(ptr) };
|
unsafe { val.write(ptr) };
|
||||||
}
|
}
|
||||||
|
_ => {
|
||||||
#[cfg(target_pointer_width = "32")]
|
compile_error!("unsupported pointer width");
|
||||||
{
|
}
|
||||||
let _ = (value, ptr);
|
|
||||||
unimplemented!("32-bit pointer storage not supported");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_ptr<P: Strict>(value: &Value) -> P {
|
fn load_ptr<P: Strict>(value: &Value) -> P {
|
||||||
#[cfg(target_pointer_width = "64")]
|
cfg_select! {
|
||||||
{
|
target_pointer_width = "64" => {
|
||||||
|
// SAFETY: `Value` is `#[repr(C, align(8))]` and exactly 8 bytes, so it
|
||||||
|
// is sound to reinterpret its storage as a `[u8; 8]` cast to `P`; the
|
||||||
|
// pointer was originally written through this same `P` layout by
|
||||||
|
// `store_ptr`.
|
||||||
let val = (unsafe { value.whole() } as *const [u8; 8]).cast::<P>();
|
let val = (unsafe { value.whole() } as *const [u8; 8]).cast::<P>();
|
||||||
|
// SAFETY: `val` points to the `Value`'s 8-byte storage, which is valid
|
||||||
|
// and suitably aligned to hold `P`.
|
||||||
let ptr = unsafe { val.read() };
|
let ptr = unsafe { val.read() };
|
||||||
Strict::map_addr(ptr, |addr| addr & 0x0000_FFFF_FFFF_FFFF)
|
Strict::map_addr(ptr, |addr| addr & 0x0000_FFFF_FFFF_FFFF)
|
||||||
}
|
}
|
||||||
|
_ => {
|
||||||
#[cfg(target_pointer_width = "32")]
|
compile_error!("unsupported pointer width");
|
||||||
{
|
}
|
||||||
let _ = value;
|
|
||||||
unimplemented!("32-bit pointer storage not supported");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,6 +189,10 @@ impl RawTag {
|
|||||||
#[inline]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn new(neg: bool, val: NonZeroU8) -> RawTag {
|
pub(crate) fn new(neg: bool, val: NonZeroU8) -> RawTag {
|
||||||
|
// SAFETY: masking a `NonZeroU8` with `0x07` yields a value in `0..8`;
|
||||||
|
// callers only construct tags from valid tag discriminants in `1..8`,
|
||||||
|
// so the low three bits are in the `1..8` range required by
|
||||||
|
// `new_unchecked`.
|
||||||
unsafe { Self::new_unchecked(neg, val.get() & 0x07) }
|
unsafe { Self::new_unchecked(neg, val.get() & 0x07) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,6 +243,9 @@ impl RawTag {
|
|||||||
(true, 6) => TagVal::_N6,
|
(true, 6) => TagVal::_N6,
|
||||||
(true, 7) => TagVal::_N7,
|
(true, 7) => TagVal::_N7,
|
||||||
|
|
||||||
|
// SAFETY: the caller guarantees `val` is in `1..8`; every
|
||||||
|
// `(neg, val)` combination in that range is matched above, so this
|
||||||
|
// arm cannot be reached.
|
||||||
_ => unsafe { core::hint::unreachable_unchecked() },
|
_ => unsafe { core::hint::unreachable_unchecked() },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -302,6 +319,10 @@ impl Header {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
const fn tag(self) -> RawTag {
|
const fn tag(self) -> RawTag {
|
||||||
|
// SAFETY: a `Header` is only ever constructed by `Header::new` from a
|
||||||
|
// `RawTag` whose value is in `1..8`, stored in the low three bits;
|
||||||
|
// `get_tag` recovers exactly those bits, so the argument passed to
|
||||||
|
// `new_unchecked` is in the required `1..8` range.
|
||||||
unsafe { RawTag::new_unchecked(self.get_sign(), self.get_tag()) }
|
unsafe { RawTag::new_unchecked(self.get_sign(), self.get_tag()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,7 +344,7 @@ impl Header {
|
|||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
#[repr(C, align(8))]
|
#[repr(C, align(8))]
|
||||||
pub struct Value {
|
pub(crate) struct Value {
|
||||||
#[cfg(target_endian = "big")]
|
#[cfg(target_endian = "big")]
|
||||||
header: Header,
|
header: Header,
|
||||||
data: [u8; 6],
|
data: [u8; 6],
|
||||||
@@ -387,6 +408,9 @@ impl Value {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
unsafe fn whole(&self) -> &[u8; 8] {
|
unsafe fn whole(&self) -> &[u8; 8] {
|
||||||
let ptr = (self as *const Value).cast::<[u8; 8]>();
|
let ptr = (self as *const Value).cast::<[u8; 8]>();
|
||||||
|
// SAFETY: `Value` is `#[repr(C, align(8))]` and exactly 8 bytes with no
|
||||||
|
// padding, so it shares its layout with `[u8; 8]`; `ptr` is derived
|
||||||
|
// from a valid `&Value`, so the reference is valid for reads.
|
||||||
unsafe { &*ptr }
|
unsafe { &*ptr }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,6 +418,10 @@ impl Value {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
unsafe fn whole_mut(&mut self) -> &mut [u8; 8] {
|
unsafe fn whole_mut(&mut self) -> &mut [u8; 8] {
|
||||||
let ptr = (self as *mut Value).cast::<[u8; 8]>();
|
let ptr = (self as *mut Value).cast::<[u8; 8]>();
|
||||||
|
// SAFETY: `Value` is `#[repr(C, align(8))]` and exactly 8 bytes with no
|
||||||
|
// padding, so it shares its layout with `[u8; 8]`; `ptr` is derived
|
||||||
|
// from a unique `&mut Value`, so the reference is valid for reads and
|
||||||
|
// writes.
|
||||||
unsafe { &mut *ptr }
|
unsafe { &mut *ptr }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -435,6 +463,9 @@ impl RawBox {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) const fn tag(&self) -> Option<RawTag> {
|
pub(crate) const fn tag(&self) -> Option<RawTag> {
|
||||||
if self.is_value() {
|
if self.is_value() {
|
||||||
|
// SAFETY: `is_value()` returned true, so the union holds a `Value`
|
||||||
|
// (a tagged-NaN bit pattern) rather than a float, making the read
|
||||||
|
// of the `value` field sound.
|
||||||
Some(unsafe { self.value.tag() })
|
Some(unsafe { self.value.tag() })
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -444,12 +475,18 @@ impl RawBox {
|
|||||||
#[inline]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn is_float(&self) -> bool {
|
pub(crate) fn is_float(&self) -> bool {
|
||||||
|
// SAFETY: every 8-byte pattern is simultaneously a valid `f64` and a
|
||||||
|
// valid `u64`, so reading the `float` and `bits` union fields is
|
||||||
|
// always sound.
|
||||||
(unsafe { !self.float.is_nan() } || unsafe { self.bits & SIGN_MASK == QUIET_NAN })
|
(unsafe { !self.float.is_nan() } || unsafe { self.bits & SIGN_MASK == QUIET_NAN })
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) const fn is_value(&self) -> bool {
|
pub(crate) const fn is_value(&self) -> bool {
|
||||||
|
// SAFETY: every 8-byte pattern is simultaneously a valid `f64` and a
|
||||||
|
// valid `u64`, so reading the `float` and `bits` union fields is
|
||||||
|
// always sound.
|
||||||
(unsafe { self.float.is_nan() } && unsafe { self.bits & SIGN_MASK != QUIET_NAN })
|
(unsafe { self.float.is_nan() } && unsafe { self.bits & SIGN_MASK != QUIET_NAN })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -457,6 +494,8 @@ impl RawBox {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn float(&self) -> Option<&f64> {
|
pub(crate) fn float(&self) -> Option<&f64> {
|
||||||
if self.is_float() {
|
if self.is_float() {
|
||||||
|
// SAFETY: reading the `float` field is sound because any 8-byte
|
||||||
|
// pattern is a valid `f64`.
|
||||||
Some(unsafe { &self.float })
|
Some(unsafe { &self.float })
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -467,6 +506,9 @@ impl RawBox {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn value(&self) -> Option<&Value> {
|
pub(crate) fn value(&self) -> Option<&Value> {
|
||||||
if self.is_value() {
|
if self.is_value() {
|
||||||
|
// SAFETY: `is_value()` returned true, so the union holds a `Value`;
|
||||||
|
// `Value` has no invalid bit patterns, so reading the `value` field
|
||||||
|
// is sound.
|
||||||
Some(unsafe { &self.value })
|
Some(unsafe { &self.value })
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -475,12 +517,16 @@ impl RawBox {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn into_float_unchecked(self) -> f64 {
|
pub(crate) fn into_float_unchecked(self) -> f64 {
|
||||||
|
// SAFETY: reading the `float` field is sound because any 8-byte pattern
|
||||||
|
// is a valid `f64`.
|
||||||
unsafe { self.float }
|
unsafe { self.float }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn to_bits(self) -> u64 {
|
pub(crate) fn to_bits(self) -> u64 {
|
||||||
|
// SAFETY: reading the `bits` field is sound because any 8-byte pattern
|
||||||
|
// is a valid `u64`.
|
||||||
unsafe { self.bits }
|
unsafe { self.bits }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-70
@@ -1,9 +1,9 @@
|
|||||||
use fix_lang::StringId;
|
use fix_lang::StringId;
|
||||||
use gc_arena::{Gc, Mutation};
|
use gc_arena::Mutation;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
AttrSet, Break, BytecodeReader, Closure, List, Machine, NixNum, NixString, NixType, Null,
|
AttrSet, BytecodeReader, Closure, List, Machine, NixNum, NixString, NixType, Null, PrimOp,
|
||||||
PrimOp, PrimOpApp, Step, StrictValue,
|
PrimOpApp, Step, StrictValue, ValueVariant,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub trait Forced<'gc>: Sized {
|
pub trait Forced<'gc>: Sized {
|
||||||
@@ -47,10 +47,10 @@ impl<'gc> Forced<'gc> for StrictValue<'gc> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! impl_forced_inline {
|
macro_rules! impl_forced {
|
||||||
($($ty:ty => $nix_ty:expr),* $(,)?) => {
|
($($ty:ty),* $(,)?) => {
|
||||||
$(
|
$(
|
||||||
impl<'gc> Forced<'gc> for $ty {
|
impl<'gc> Forced<'gc> for <$ty as ValueVariant<'gc>>::Ty {
|
||||||
const WIDTH: usize = 1;
|
const WIDTH: usize = 1;
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@@ -63,17 +63,17 @@ macro_rules! impl_forced_inline {
|
|||||||
) -> Step {
|
) -> Step {
|
||||||
m.force_slot_to_pc(base_depth, reader, mc, resume_pc)?;
|
m.force_slot_to_pc(base_depth, reader, mc, resume_pc)?;
|
||||||
let v = m.peek_forced(base_depth);
|
let v = m.peek_forced(base_depth);
|
||||||
if v.as_inline::<$ty>().is_none() {
|
if !v.is::<$ty>() {
|
||||||
let _: Step = m.finish_type_err($nix_ty, v.ty());
|
m.finish_type_err(<$ty as ValueVariant>::TYPE, v.ty())
|
||||||
return Step::Break(Break::Done);
|
} else {
|
||||||
}
|
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn pop_converted<M: Machine<'gc>>(m: &mut M) -> Self {
|
fn pop_converted<M: Machine<'gc>>(m: &mut M) -> Self {
|
||||||
m.pop_forced()
|
m.pop_forced()
|
||||||
.as_inline::<$ty>()
|
.downcast::<$ty>()
|
||||||
.expect("type checked in force_and_check")
|
.expect("type checked in force_and_check")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,55 +81,18 @@ macro_rules! impl_forced_inline {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! impl_forced_gc {
|
impl_forced! {
|
||||||
($($ty:ty => $nix_ty:expr),* $(,)?) => {
|
i32,
|
||||||
$(
|
bool,
|
||||||
impl<'gc> Forced<'gc> for Gc<'gc, $ty> {
|
Null,
|
||||||
const WIDTH: usize = 1;
|
StringId,
|
||||||
|
PrimOp,
|
||||||
#[inline(always)]
|
i64,
|
||||||
fn force_and_check<M: Machine<'gc>>(
|
NixString,
|
||||||
m: &mut M,
|
AttrSet<'gc>,
|
||||||
reader: &mut BytecodeReader<'_>,
|
List<'gc>,
|
||||||
mc: &Mutation<'gc>,
|
Closure<'gc>,
|
||||||
base_depth: usize,
|
PrimOpApp<'gc>,
|
||||||
resume_pc: usize,
|
|
||||||
) -> Step {
|
|
||||||
m.force_slot_to_pc(base_depth, reader, mc, resume_pc)?;
|
|
||||||
let v = m.peek_forced(base_depth);
|
|
||||||
if v.as_gc::<$ty>().is_none() {
|
|
||||||
let _: Step = m.finish_type_err($nix_ty, v.ty());
|
|
||||||
return Step::Break(Break::Done);
|
|
||||||
}
|
|
||||||
Step::Continue(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn pop_converted<M: Machine<'gc>>(m: &mut M) -> Self {
|
|
||||||
m.pop_forced()
|
|
||||||
.as_gc::<$ty>()
|
|
||||||
.expect("type checked in force_and_check")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)*
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_forced_inline! {
|
|
||||||
i32 => NixType::Int,
|
|
||||||
bool => NixType::Bool,
|
|
||||||
Null => NixType::Null,
|
|
||||||
StringId => NixType::String,
|
|
||||||
PrimOp => NixType::PrimOp,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_forced_gc! {
|
|
||||||
i64 => NixType::Int,
|
|
||||||
NixString => NixType::String,
|
|
||||||
AttrSet<'gc> => NixType::AttrSet,
|
|
||||||
List<'gc> => NixType::List,
|
|
||||||
Closure<'gc> => NixType::Closure,
|
|
||||||
PrimOpApp<'gc> => NixType::PrimOpApp,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'gc> Forced<'gc> for NixNum {
|
impl<'gc> Forced<'gc> for NixNum {
|
||||||
@@ -145,17 +108,17 @@ impl<'gc> Forced<'gc> for NixNum {
|
|||||||
) -> Step {
|
) -> Step {
|
||||||
m.force_slot_to_pc(base_depth, reader, mc, resume_pc)?;
|
m.force_slot_to_pc(base_depth, reader, mc, resume_pc)?;
|
||||||
let v = m.peek_forced(base_depth);
|
let v = m.peek_forced(base_depth);
|
||||||
if v.as_num().is_none() {
|
if v.downcast_num().is_none() {
|
||||||
let _: Step = m.finish_type_err(NixType::Int, v.ty());
|
m.finish_type_err(NixType::Int, v.ty())
|
||||||
return Step::Break(Break::Done);
|
} else {
|
||||||
}
|
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn pop_converted<M: Machine<'gc>>(m: &mut M) -> Self {
|
fn pop_converted<M: Machine<'gc>>(m: &mut M) -> Self {
|
||||||
m.pop_forced()
|
m.pop_forced()
|
||||||
.as_num()
|
.downcast_num()
|
||||||
.expect("type checked in force_and_check")
|
.expect("type checked in force_and_check")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -173,17 +136,17 @@ impl<'gc> Forced<'gc> for f64 {
|
|||||||
) -> Step {
|
) -> Step {
|
||||||
m.force_slot_to_pc(base_depth, reader, mc, resume_pc)?;
|
m.force_slot_to_pc(base_depth, reader, mc, resume_pc)?;
|
||||||
let v = m.peek_forced(base_depth);
|
let v = m.peek_forced(base_depth);
|
||||||
if v.as_float().is_none() {
|
if !v.is::<f64>() {
|
||||||
let _: Step = m.finish_type_err(NixType::Float, v.ty());
|
m.finish_type_err(NixType::Float, v.ty())
|
||||||
return Step::Break(Break::Done);
|
} else {
|
||||||
}
|
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn pop_converted<M: Machine<'gc>>(m: &mut M) -> Self {
|
fn pop_converted<M: Machine<'gc>>(m: &mut M) -> Self {
|
||||||
m.pop_forced()
|
m.pop_forced()
|
||||||
.as_float()
|
.downcast::<f64>()
|
||||||
.expect("type checked in force_and_check")
|
.expect("type checked in force_and_check")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-23
@@ -1,6 +1,6 @@
|
|||||||
use fix_bytecode::InstructionPtr;
|
use fix_bytecode::InstructionPtr;
|
||||||
use fix_error::Source;
|
use fix_error::Source;
|
||||||
use fix_lang::{self, BUILTINS, StringId};
|
use fix_lang::{self, StringId};
|
||||||
use hashbrown::HashSet;
|
use hashbrown::HashSet;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -44,10 +44,10 @@ pub trait VmRuntimeCtxExt: VmRuntimeCtx {
|
|||||||
|
|
||||||
impl<T: VmRuntimeCtx> VmRuntimeCtxExt for T {
|
impl<T: VmRuntimeCtx> VmRuntimeCtxExt for T {
|
||||||
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> {
|
||||||
if let Some(sid) = val.as_inline::<StringId>() {
|
if let Some(sid) = val.downcast::<StringId>() {
|
||||||
Some(self.resolve_string(sid))
|
Some(self.resolve_string(sid))
|
||||||
} else {
|
} else {
|
||||||
val.as_gc::<NixString>().map(|ns| ns.as_ref().as_str())
|
val.downcast::<NixString>().map(|ns| ns.as_ref().as_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ impl<T: VmRuntimeCtx> VmRuntimeCtxExt for T {
|
|||||||
/// would coerce a path to a string (string interpolation, file IO
|
/// would coerce a path to a string (string interpolation, file IO
|
||||||
/// builtins, etc.).
|
/// builtins, etc.).
|
||||||
fn get_string_or_path<'a, 'gc: 'a>(&'a self, val: StrictValue<'gc>) -> Option<&'a str> {
|
fn get_string_or_path<'a, 'gc: 'a>(&'a self, val: StrictValue<'gc>) -> Option<&'a str> {
|
||||||
if let Some(p) = val.as_inline::<Path>() {
|
if let Some(p) = val.downcast::<Path>() {
|
||||||
Some(self.resolve_string(p.0))
|
Some(self.resolve_string(p.0))
|
||||||
} else {
|
} else {
|
||||||
self.get_string(val)
|
self.get_string(val)
|
||||||
@@ -67,9 +67,9 @@ impl<T: VmRuntimeCtx> VmRuntimeCtxExt for T {
|
|||||||
&'a mut self,
|
&'a mut self,
|
||||||
val: StrictValue<'gc>,
|
val: StrictValue<'gc>,
|
||||||
) -> std::result::Result<StringId, NixType> {
|
) -> std::result::Result<StringId, NixType> {
|
||||||
if let Some(sid) = val.as_inline::<StringId>() {
|
if let Some(sid) = val.downcast::<StringId>() {
|
||||||
Ok(sid)
|
Ok(sid)
|
||||||
} else if let Some(s) = val.as_gc::<NixString>().map(|ns| ns.as_ref().as_str()) {
|
} else if let Some(s) = val.downcast::<NixString>().map(|ns| ns.as_ref().as_str()) {
|
||||||
Ok(self.intern_string(s))
|
Ok(self.intern_string(s))
|
||||||
} else {
|
} else {
|
||||||
Err(val.ty())
|
Err(val.ty())
|
||||||
@@ -77,7 +77,7 @@ impl<T: VmRuntimeCtx> VmRuntimeCtxExt for T {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_string_context<'gc>(&self, val: StrictValue<'gc>) -> &'gc StringContext {
|
fn get_string_context<'gc>(&self, val: StrictValue<'gc>) -> &'gc StringContext {
|
||||||
if let Some(ns) = val.as_gc::<NixString>() {
|
if let Some(ns) = val.downcast::<NixString>() {
|
||||||
ns.as_ref().context()
|
ns.as_ref().context()
|
||||||
} else {
|
} else {
|
||||||
StringContext::empty()
|
StringContext::empty()
|
||||||
@@ -96,24 +96,24 @@ pub(crate) trait ConvertValueWithSeen: VmRuntimeCtx {
|
|||||||
impl<T: VmRuntimeCtx> ConvertValueWithSeen for T {
|
impl<T: VmRuntimeCtx> ConvertValueWithSeen for T {
|
||||||
fn convert_value_with_seen(&self, val: Value, seen: &mut HashSet<u64>) -> fix_lang::Value {
|
fn convert_value_with_seen(&self, val: Value, seen: &mut HashSet<u64>) -> fix_lang::Value {
|
||||||
use fix_lang::Value;
|
use fix_lang::Value;
|
||||||
if let Some(i) = val.as_inline::<i32>() {
|
if let Some(i) = val.downcast::<i32>() {
|
||||||
Value::Int(i as i64)
|
Value::Int(i as i64)
|
||||||
} else if let Some(gc_i) = val.as_gc::<i64>() {
|
} else if let Some(gc_i) = val.downcast::<i64>() {
|
||||||
Value::Int(*gc_i)
|
Value::Int(*gc_i)
|
||||||
} else if let Some(f) = val.as_float() {
|
} else if let Some(f) = val.downcast::<f64>() {
|
||||||
Value::Float(f)
|
Value::Float(f)
|
||||||
} else if let Some(b) = val.as_inline::<bool>() {
|
} else if let Some(b) = val.downcast::<bool>() {
|
||||||
Value::Bool(b)
|
Value::Bool(b)
|
||||||
} else if val.is::<Null>() {
|
} else if val.is::<Null>() {
|
||||||
Value::Null
|
Value::Null
|
||||||
} else if let Some(sid) = val.as_inline::<StringId>() {
|
} else if let Some(sid) = val.downcast::<StringId>() {
|
||||||
let s = self.resolve_string(sid).to_owned();
|
let s = self.resolve_string(sid).to_owned();
|
||||||
Value::String(s)
|
Value::String(s)
|
||||||
} else if let Some(ns) = val.as_gc::<NixString>() {
|
} else if let Some(ns) = val.downcast::<NixString>() {
|
||||||
Value::String(ns.as_str().to_owned())
|
Value::String(ns.as_str().to_owned())
|
||||||
} else if let Some(p) = val.as_inline::<Path>() {
|
} else if let Some(p) = val.downcast::<Path>() {
|
||||||
Value::Path(self.resolve_string(p.0).to_owned())
|
Value::Path(self.resolve_string(p.0).to_owned())
|
||||||
} else if let Some(attrs) = val.as_gc::<AttrSet>() {
|
} else if let Some(attrs) = val.downcast::<AttrSet>() {
|
||||||
let bits = val.to_bits();
|
let bits = val.to_bits();
|
||||||
if attrs.entries.is_empty() {
|
if attrs.entries.is_empty() {
|
||||||
return Value::AttrSet(Default::default());
|
return Value::AttrSet(Default::default());
|
||||||
@@ -128,7 +128,7 @@ impl<T: VmRuntimeCtx> ConvertValueWithSeen for T {
|
|||||||
map.insert(fix_lang::Symbol::from(key), converted);
|
map.insert(fix_lang::Symbol::from(key), converted);
|
||||||
}
|
}
|
||||||
Value::AttrSet(fix_lang::AttrSet::new(map))
|
Value::AttrSet(fix_lang::AttrSet::new(map))
|
||||||
} else if let Some(list) = val.as_gc::<List>() {
|
} else if let Some(list) = val.downcast::<List>() {
|
||||||
let bits = val.to_bits();
|
let bits = val.to_bits();
|
||||||
if list.inner.borrow().is_empty() {
|
if list.inner.borrow().is_empty() {
|
||||||
return Value::List(Default::default());
|
return Value::List(Default::default());
|
||||||
@@ -146,18 +146,16 @@ impl<T: VmRuntimeCtx> ConvertValueWithSeen for T {
|
|||||||
Value::List(fix_lang::List::new(items))
|
Value::List(fix_lang::List::new(items))
|
||||||
} else if val.is::<Closure>() {
|
} else if val.is::<Closure>() {
|
||||||
Value::Func
|
Value::Func
|
||||||
} else if let Some(thunk) = val.as_gc::<Thunk>() {
|
} else if let Some(thunk) = val.downcast::<Thunk>() {
|
||||||
if let ThunkState::Evaluated(v) = *thunk.borrow() {
|
if let ThunkState::Evaluated(v) = *thunk.borrow() {
|
||||||
self.convert_value_with_seen(v.relax(), seen)
|
self.convert_value_with_seen(v.relax(), seen)
|
||||||
} else {
|
} else {
|
||||||
Value::Thunk
|
Value::Thunk
|
||||||
}
|
}
|
||||||
} else if let Some(primop) = val.as_inline::<PrimOp>() {
|
} else if let Some(primop) = val.downcast::<PrimOp>() {
|
||||||
let name = BUILTINS[primop.id as usize].0;
|
Value::PrimOp(primop.id.info().name)
|
||||||
Value::PrimOp(name.strip_prefix("__").unwrap_or(name))
|
} else if let Some(app) = val.downcast::<PrimOpApp>() {
|
||||||
} else if let Some(app) = val.as_gc::<PrimOpApp>() {
|
Value::PrimOpApp(app.primop.id.info().name)
|
||||||
let name = BUILTINS[app.primop.id as usize].0;
|
|
||||||
Value::PrimOpApp(name.strip_prefix("__").unwrap_or(name))
|
|
||||||
} else {
|
} else {
|
||||||
Value::Null
|
Value::Null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ mod boxing;
|
|||||||
mod forced;
|
mod forced;
|
||||||
mod host;
|
mod host;
|
||||||
mod machine;
|
mod machine;
|
||||||
|
mod macro_support;
|
||||||
mod path_util;
|
mod path_util;
|
||||||
mod resolve;
|
mod resolve;
|
||||||
|
mod slot;
|
||||||
mod state;
|
mod state;
|
||||||
mod string_context;
|
mod string_context;
|
||||||
mod value;
|
mod value;
|
||||||
@@ -12,8 +14,10 @@ pub use fix_bytecode::{BytecodeReader, OperandData};
|
|||||||
pub use forced::*;
|
pub use forced::*;
|
||||||
pub use host::*;
|
pub use host::*;
|
||||||
pub use machine::*;
|
pub use machine::*;
|
||||||
|
pub use macro_support::*;
|
||||||
pub use path_util::*;
|
pub use path_util::*;
|
||||||
pub use resolve::*;
|
pub use resolve::*;
|
||||||
|
pub use slot::*;
|
||||||
pub use state::*;
|
pub use state::*;
|
||||||
pub use string_context::*;
|
pub use string_context::*;
|
||||||
pub use value::*;
|
pub use value::*;
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ use std::path::{Path, PathBuf};
|
|||||||
|
|
||||||
use fix_error::Error;
|
use fix_error::Error;
|
||||||
use fix_lang::{self, StringId};
|
use fix_lang::{self, StringId};
|
||||||
use gc_arena::Mutation;
|
use gc_arena::{Gc, Mutation};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Break, BytecodeReader, CallFrame, ForceMode, Forced, GcEnv, NixType, PendingLoad, Step,
|
AttrSet, Break, BytecodeReader, CallFrame, ForceMode, Forced, GcEnv, NixType, PendingLoad,
|
||||||
StrictValue, Value, VmError,
|
Step, StrictValue, Value, VmError,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Abstract VM-side operations consumed by instruction handlers and primops.
|
/// Abstract VM-side operations consumed by instruction handlers and primops.
|
||||||
@@ -65,12 +65,17 @@ pub trait Machine<'gc> {
|
|||||||
) -> Step;
|
) -> Step;
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
#[expect(
|
||||||
|
clippy::unreachable,
|
||||||
|
reason = "a primop only returns via `return_from_primop` while its call frame is still on the stack, so `pop_call_frame` is always `Some`"
|
||||||
|
)]
|
||||||
fn return_from_primop(&mut self, val: Value<'gc>, reader: &mut BytecodeReader<'_>) -> Step {
|
fn return_from_primop(&mut self, val: Value<'gc>, reader: &mut BytecodeReader<'_>) -> Step {
|
||||||
self.push(val);
|
self.push(val);
|
||||||
let Some(CallFrame {
|
let Some(CallFrame {
|
||||||
pc: ret_pc,
|
pc: ret_pc,
|
||||||
thunk: _,
|
thunk: _,
|
||||||
env,
|
env,
|
||||||
|
depth: None,
|
||||||
}) = self.pop_call_frame()
|
}) = self.pop_call_frame()
|
||||||
else {
|
else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
@@ -91,6 +96,10 @@ pub trait Machine<'gc> {
|
|||||||
fn set_env(&mut self, env: GcEnv<'gc>);
|
fn set_env(&mut self, env: GcEnv<'gc>);
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
#[expect(
|
||||||
|
clippy::indexing_slicing,
|
||||||
|
reason = "codegen guarantees the local index is within the resolved frame's `locals`"
|
||||||
|
)]
|
||||||
fn local(&self, layer: u8, idx: u32) -> Value<'gc> {
|
fn local(&self, layer: u8, idx: u32) -> Value<'gc> {
|
||||||
let mut cur = self.env();
|
let mut cur = self.env();
|
||||||
for _ in 0..layer {
|
for _ in 0..layer {
|
||||||
@@ -109,7 +118,7 @@ pub trait Machine<'gc> {
|
|||||||
self.finish_err(err.into_error())
|
self.finish_err(err.into_error())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn builtins(&self) -> Value<'gc>;
|
fn builtins(&self) -> Gc<'gc, AttrSet<'gc>>;
|
||||||
fn functor_sym(&self) -> StringId;
|
fn functor_sym(&self) -> StringId;
|
||||||
fn empty_list(&self) -> Value<'gc>;
|
fn empty_list(&self) -> Value<'gc>;
|
||||||
fn empty_attrs(&self) -> Value<'gc>;
|
fn empty_attrs(&self) -> Value<'gc>;
|
||||||
|
|||||||
@@ -0,0 +1,165 @@
|
|||||||
|
use std::ops::ControlFlow;
|
||||||
|
|
||||||
|
use gc_arena::Mutation;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
Break, BytecodeReader, Forced, Machine, MachineExt, Slot, SlotContent, Step, StrictValue,
|
||||||
|
Value, ValueVariant,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub trait UnwrapSlot {
|
||||||
|
type Unwrapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> UnwrapSlot for Slot<T> {
|
||||||
|
type Unwrapped = T;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Force-target protocol behind the `#[primop]` `force` form: `Value` only
|
||||||
|
/// needs WHNF, `StrictValue` *is* WHNF, and content types are checked through
|
||||||
|
/// [`Forced`] on their stored representation.
|
||||||
|
pub trait ForceTarget<'gc>: SlotContent<'gc> {
|
||||||
|
/// Type-check the stack slot refined by the previous `force(slot)`; the
|
||||||
|
/// slot is already WHNF when this runs.
|
||||||
|
fn refine_check<M: Machine<'gc>>(
|
||||||
|
m: &mut M,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
depth: usize,
|
||||||
|
) -> Step;
|
||||||
|
|
||||||
|
/// Pop and convert the previous suspension's result into the annotated
|
||||||
|
/// binding's stored representation.
|
||||||
|
fn conv_force<M: Machine<'gc>>(
|
||||||
|
m: &mut M,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> ControlFlow<Break, <Self as SlotContent<'gc>>::Ty>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gc> ForceTarget<'gc> for Value<'gc> {
|
||||||
|
#[inline(always)]
|
||||||
|
fn refine_check<M: Machine<'gc>>(
|
||||||
|
_m: &mut M,
|
||||||
|
_reader: &mut BytecodeReader<'_>,
|
||||||
|
_mc: &Mutation<'gc>,
|
||||||
|
_depth: usize,
|
||||||
|
) -> Step {
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn conv_force<M: Machine<'gc>>(
|
||||||
|
m: &mut M,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> ControlFlow<Break, Value<'gc>> {
|
||||||
|
match m.force_and_retry::<StrictValue<'gc>>(reader, mc) {
|
||||||
|
ControlFlow::Continue(v) => ControlFlow::Continue(v.relax()),
|
||||||
|
ControlFlow::Break(b) => ControlFlow::Break(b),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gc> ForceTarget<'gc> for StrictValue<'gc> {
|
||||||
|
#[inline(always)]
|
||||||
|
fn refine_check<M: Machine<'gc>>(
|
||||||
|
_m: &mut M,
|
||||||
|
_reader: &mut BytecodeReader<'_>,
|
||||||
|
_mc: &Mutation<'gc>,
|
||||||
|
_depth: usize,
|
||||||
|
) -> Step {
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn conv_force<M: Machine<'gc>>(
|
||||||
|
m: &mut M,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> ControlFlow<Break, StrictValue<'gc>> {
|
||||||
|
m.force_and_retry::<StrictValue<'gc>>(reader, mc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gc, T> ForceTarget<'gc> for T
|
||||||
|
where
|
||||||
|
T: ValueVariant<'gc>,
|
||||||
|
<T as ValueVariant<'gc>>::Ty: Forced<'gc>,
|
||||||
|
{
|
||||||
|
#[inline(always)]
|
||||||
|
fn refine_check<M: Machine<'gc>>(
|
||||||
|
m: &mut M,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
depth: usize,
|
||||||
|
) -> Step {
|
||||||
|
<<T as ValueVariant<'gc>>::Ty as Forced<'gc>>::force_and_check(
|
||||||
|
m,
|
||||||
|
reader,
|
||||||
|
mc,
|
||||||
|
depth,
|
||||||
|
reader.inst_start_pc(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn conv_force<M: Machine<'gc>>(
|
||||||
|
m: &mut M,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> ControlFlow<Break, <T as SlotContent<'gc>>::Ty> {
|
||||||
|
m.force_and_retry::<<T as ValueVariant<'gc>>::Ty>(reader, mc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Callee protocol behind `call(&slot, ..)`: an unforced `Slot<Value>` is
|
||||||
|
/// WHNF'd before the call; refined slots already hold strict values.
|
||||||
|
pub trait CalleeReady<'gc> {
|
||||||
|
fn callee_ready<M: Machine<'gc>>(
|
||||||
|
&self,
|
||||||
|
m: &mut M,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> ControlFlow<Break>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gc> CalleeReady<'gc> for Slot<Value<'gc>> {
|
||||||
|
#[inline(always)]
|
||||||
|
fn callee_ready<M: Machine<'gc>>(
|
||||||
|
&self,
|
||||||
|
m: &mut M,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> ControlFlow<Break> {
|
||||||
|
self.force::<StrictValue<'gc>, M>(m, reader, mc)?;
|
||||||
|
ControlFlow::Continue(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gc> CalleeReady<'gc> for Slot<StrictValue<'gc>> {
|
||||||
|
#[inline(always)]
|
||||||
|
fn callee_ready<M: Machine<'gc>>(
|
||||||
|
&self,
|
||||||
|
_m: &mut M,
|
||||||
|
_reader: &mut BytecodeReader<'_>,
|
||||||
|
_mc: &Mutation<'gc>,
|
||||||
|
) -> ControlFlow<Break> {
|
||||||
|
ControlFlow::Continue(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gc, T> CalleeReady<'gc> for Slot<T>
|
||||||
|
where
|
||||||
|
T: ValueVariant<'gc>,
|
||||||
|
{
|
||||||
|
#[inline(always)]
|
||||||
|
fn callee_ready<M: Machine<'gc>>(
|
||||||
|
&self,
|
||||||
|
_m: &mut M,
|
||||||
|
_reader: &mut BytecodeReader<'_>,
|
||||||
|
_mc: &Mutation<'gc>,
|
||||||
|
) -> ControlFlow<Break> {
|
||||||
|
ControlFlow::Continue(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,17 +18,14 @@ pub fn resolve_operand<'gc, M: Machine<'gc>>(
|
|||||||
use OperandData::*;
|
use OperandData::*;
|
||||||
match *op {
|
match *op {
|
||||||
Const(id) => ctx.get_const(id).into(),
|
Const(id) => ctx.get_const(id).into(),
|
||||||
BigInt(val) => Value::new_gc(Gc::new(mc, val)),
|
BigInt(val) => Value::new(Gc::new(mc, val)),
|
||||||
Local { layer, idx } => m.local(layer, idx),
|
Local { layer, idx } => m.local(layer, idx),
|
||||||
#[allow(clippy::unwrap_used)]
|
BuiltinConst(id) => m.builtins().lookup(id).expect("builtin const must exist"),
|
||||||
BuiltinConst(id) => m.builtins().as_gc::<AttrSet>().unwrap().lookup(id).unwrap(),
|
Builtins => m.builtins().into(),
|
||||||
Builtins => m.builtins(),
|
|
||||||
ReplBinding(_id) => todo!(),
|
ReplBinding(_id) => todo!(),
|
||||||
ScopedImportBinding { slot_id, name } => {
|
ScopedImportBinding { slot_id, name } => {
|
||||||
let scope = m.scope_slot(slot_id);
|
let scope = m.scope_slot(slot_id);
|
||||||
#[allow(clippy::unwrap_used)]
|
let attrs = scope.downcast::<AttrSet>().expect("scope must be attrset");
|
||||||
let attrs = scope.as_gc::<AttrSet>().expect("scope must be attrset");
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
attrs.lookup(name).expect("scoped binding not found")
|
attrs.lookup(name).expect("scoped binding not found")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::ops::ControlFlow;
|
||||||
|
|
||||||
|
use fix_bytecode::BytecodeReader;
|
||||||
|
use gc_arena::Mutation;
|
||||||
|
|
||||||
|
use crate::{Break, Forced, Machine, NixType, Step, StrictValue, Value, ValueVariant};
|
||||||
|
|
||||||
|
pub struct TypeError {
|
||||||
|
pub expected: NixType,
|
||||||
|
pub got: NixType,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait SlotContent<'gc> {
|
||||||
|
type Ty: Into<Value<'gc>> + TryFrom<Value<'gc>> + 'gc;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gc> SlotContent<'gc> for Value<'gc> {
|
||||||
|
type Ty = Value<'gc>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gc> SlotContent<'gc> for StrictValue<'gc> {
|
||||||
|
type Ty = StrictValue<'gc>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gc, T> SlotContent<'gc> for T
|
||||||
|
where
|
||||||
|
T: ValueVariant<'gc>,
|
||||||
|
{
|
||||||
|
type Ty = T::Ty;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Slot<T> {
|
||||||
|
depth: u8,
|
||||||
|
_marker: PhantomData<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Slot<T> {
|
||||||
|
#[inline(always)]
|
||||||
|
pub const fn new(depth: u8) -> Self {
|
||||||
|
Self {
|
||||||
|
depth,
|
||||||
|
_marker: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gc, T> Slot<T>
|
||||||
|
where
|
||||||
|
T: SlotContent<'gc>,
|
||||||
|
<T::Ty as TryFrom<Value<'gc>>>::Error: std::fmt::Debug,
|
||||||
|
{
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn read<M: Machine<'gc>>(&self, m: &M) -> T::Ty {
|
||||||
|
T::Ty::try_from(m.peek(self.depth as usize)).expect("slot held a value of the wrong type")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn write<M: Machine<'gc>>(&self, m: &mut M, val: T::Ty) {
|
||||||
|
m.replace(self.depth as usize, T::Ty::into(val));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gc> Slot<Value<'gc>> {
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn force<T: Forced<'gc>, M: Machine<'gc>>(
|
||||||
|
&self,
|
||||||
|
m: &mut M,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> ControlFlow<Break, Slot<T>> {
|
||||||
|
T::force_and_check(m, reader, mc, self.depth as usize, reader.inst_start_pc())?;
|
||||||
|
ControlFlow::Continue(Slot::new(self.depth))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn force_to_pc<M: Machine<'gc>>(
|
||||||
|
&self,
|
||||||
|
m: &mut M,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
pc: usize,
|
||||||
|
) -> Step {
|
||||||
|
m.force_slot_to_pc(self.depth as usize, reader, mc, pc)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,6 @@ use hashbrown::HashSet;
|
|||||||
|
|
||||||
use crate::{GcEnv, Thunk};
|
use crate::{GcEnv, Thunk};
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub enum VmError {
|
pub enum VmError {
|
||||||
Catchable(String),
|
Catchable(String),
|
||||||
Uncatchable(Box<Error>),
|
Uncatchable(Box<Error>),
|
||||||
@@ -51,7 +50,6 @@ pub enum Break {
|
|||||||
|
|
||||||
pub type Step = ControlFlow<Break>;
|
pub type Step = ControlFlow<Break>;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub struct ErrorFrame {
|
pub struct ErrorFrame {
|
||||||
pub span_id: u32,
|
pub span_id: u32,
|
||||||
pub message: Option<String>,
|
pub message: Option<String>,
|
||||||
@@ -61,8 +59,9 @@ pub struct ErrorFrame {
|
|||||||
#[collect(no_drop)]
|
#[collect(no_drop)]
|
||||||
pub struct CallFrame<'gc> {
|
pub struct CallFrame<'gc> {
|
||||||
pub pc: usize,
|
pub pc: usize,
|
||||||
pub thunk: Option<Gc<'gc, Thunk<'gc>>>,
|
|
||||||
pub env: GcEnv<'gc>,
|
pub env: GcEnv<'gc>,
|
||||||
|
pub thunk: Option<Gc<'gc, Thunk<'gc>>>,
|
||||||
|
pub depth: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|||||||
@@ -31,10 +31,10 @@ impl StringContextElem {
|
|||||||
drv_path: drv_path.into(),
|
drv_path: drv_path.into(),
|
||||||
}
|
}
|
||||||
} else if let Some(rest) = encoded.strip_prefix('!') {
|
} else if let Some(rest) = encoded.strip_prefix('!') {
|
||||||
if let Some(second_bang) = rest.find('!') {
|
if let Some((output, drv_path)) = rest.split_once('!') {
|
||||||
Self::Built {
|
Self::Built {
|
||||||
output: rest[..second_bang].into(),
|
output: output.into(),
|
||||||
drv_path: rest[second_bang + 1..].into(),
|
drv_path: drv_path.into(),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Self::Opaque {
|
Self::Opaque {
|
||||||
@@ -117,6 +117,10 @@ impl StringContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(
|
||||||
|
clippy::indexing_slicing,
|
||||||
|
reason = "`i`/`j` stay strictly below their lengths inside the loop, and the trailing slices use those in-bounds cursors as start indices"
|
||||||
|
)]
|
||||||
pub fn merge(&self, other: &Self) -> Self {
|
pub fn merge(&self, other: &Self) -> Self {
|
||||||
if self.data.is_empty() {
|
if self.data.is_empty() {
|
||||||
return other.clone();
|
return other.clone();
|
||||||
|
|||||||
+230
-177
@@ -4,7 +4,9 @@ use std::marker::PhantomData;
|
|||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
use fix_bytecode::Continuation;
|
||||||
use fix_lang::*;
|
use fix_lang::*;
|
||||||
|
use fix_macros::unelide_lifetimes;
|
||||||
use gc_arena::barrier::Unlock;
|
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};
|
||||||
@@ -19,33 +21,99 @@ mod private {
|
|||||||
pub trait Cealed {}
|
pub trait Cealed {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait ValueVariant<'gc>: private::Cealed {
|
||||||
|
#[expect(
|
||||||
|
private_bounds,
|
||||||
|
reason = "Storable is a sealed implementation detail of the value system"
|
||||||
|
)]
|
||||||
|
type Ty: Storable<'gc>;
|
||||||
|
const TYPE: NixType;
|
||||||
|
}
|
||||||
|
|
||||||
|
trait Storable<'gc>: TryFrom<Value<'gc>> + Into<Value<'gc>> + private::Cealed + 'gc {
|
||||||
|
fn is(raw: &RawBox) -> bool;
|
||||||
|
|
||||||
|
fn to_raw_box(self) -> RawBox;
|
||||||
/// # Safety
|
/// # Safety
|
||||||
///
|
///
|
||||||
/// [`Self::TAG`] must be unique among all implementors.
|
/// `raw` must represent a valid `Self`.
|
||||||
unsafe trait Storable: private::Cealed {
|
unsafe fn from_raw_box(raw: RawBox) -> Self;
|
||||||
const TAG: RawTag;
|
|
||||||
}
|
}
|
||||||
trait InlineStorable: Storable + RawStore {}
|
|
||||||
trait GcStorable: Storable {}
|
|
||||||
|
|
||||||
macro_rules! define_value_types {
|
macro_rules! define_value_types {
|
||||||
(
|
(
|
||||||
inline { $($itype:ty => $itag:expr, $iname:literal;)* }
|
inline { $($itype:ty => $itag:path, $ity:path, $iname:literal;)* }
|
||||||
gc { $($gtype:ty => $gtag:expr, $gname:literal;)* }
|
gc { $($gtype:ty => $gtag:path, $gty:path, $gname:literal;)* }
|
||||||
) => {
|
) => {
|
||||||
$(
|
$(
|
||||||
unsafe impl Storable for $itype {
|
impl Storable<'_> for $itype {
|
||||||
const TAG: RawTag = $itag;
|
#[inline(always)]
|
||||||
|
fn is(value: &RawBox) -> bool {
|
||||||
|
value.tag() == Some($itag)
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
fn to_raw_box(self) -> RawBox {
|
||||||
|
RawBox::from_value(RawValue::store($itag, self))
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
unsafe fn from_raw_box(raw: RawBox) -> Self {
|
||||||
|
// SAFETY: the caller guarantees `raw` represents a valid
|
||||||
|
// `Self` of this inline type, so it holds a `Value` (making
|
||||||
|
// `value()` `Some`) whose payload decodes to this type.
|
||||||
|
unsafe { <Self as RawStore>::from_val(raw.value().unwrap_unchecked()) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl InlineStorable for $itype {}
|
|
||||||
impl private::Cealed for $itype {}
|
impl private::Cealed for $itype {}
|
||||||
|
impl<'gc> ValueVariant<'gc> for $itype {
|
||||||
|
type Ty = $itype;
|
||||||
|
const TYPE: NixType = $ity;
|
||||||
|
}
|
||||||
|
impl<'gc> TryFrom<Value<'gc>> for $itype {
|
||||||
|
type Error = ();
|
||||||
|
fn try_from(val: Value<'gc>) -> Result<Self, Self::Error> {
|
||||||
|
<Self as Storable>::is(&val.raw)
|
||||||
|
// SAFETY: `is` returned true, so `val.raw` represents a
|
||||||
|
// valid `Self` of this GC type.
|
||||||
|
.then(|| unsafe { <Self as Storable>::from_raw_box(val.raw) })
|
||||||
|
.ok_or(())
|
||||||
|
}
|
||||||
|
}
|
||||||
)*
|
)*
|
||||||
$(
|
$(
|
||||||
unsafe impl Storable for $gtype {
|
impl<'gc> Storable<'gc> for Gc<'gc, unelide_lifetimes!('gc; $gtype)> {
|
||||||
const TAG: RawTag = $gtag;
|
#[inline(always)]
|
||||||
|
fn is(value: &RawBox) -> bool {
|
||||||
|
value.tag() == Some($gtag)
|
||||||
}
|
}
|
||||||
impl GcStorable for $gtype {}
|
#[inline(always)]
|
||||||
|
fn to_raw_box(self) -> RawBox {
|
||||||
|
RawBox::from_value(RawValue::store($gtag, Gc::as_ptr(self)))
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
unsafe fn from_raw_box(raw: RawBox) -> Self {
|
||||||
|
// SAFETY: the caller guarantees `raw` represents a valid
|
||||||
|
// `Self` of this GC type, so it holds a `Value` (making
|
||||||
|
// `value()` `Some`) whose payload is the pointer originally
|
||||||
|
// produced by `Gc::as_ptr` in `to_raw_box`.
|
||||||
|
unsafe { Gc::from_ptr(<*mut $gtype as RawStore>::from_val(raw.value().unwrap_unchecked())) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'gc> TryFrom<Value<'gc>> for Gc<'gc, unelide_lifetimes!('gc; $gtype)> {
|
||||||
|
type Error = ();
|
||||||
|
fn try_from(val: Value<'gc>) -> Result<Self, Self::Error> {
|
||||||
|
<Self as Storable>::is(&val.raw)
|
||||||
|
// SAFETY: `is` returned true, so `val.raw` represents a
|
||||||
|
// valid `Self` of this GC type.
|
||||||
|
.then(|| unsafe { <Self as Storable>::from_raw_box(val.raw) })
|
||||||
|
.ok_or(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl private::Cealed for Gc<'_, $gtype> {}
|
||||||
impl private::Cealed for $gtype {}
|
impl private::Cealed for $gtype {}
|
||||||
|
impl<'gc> ValueVariant<'gc> for unelide_lifetimes!('gc; $gtype) {
|
||||||
|
type Ty = Gc<'gc, unelide_lifetimes!('gc; $gtype)>;
|
||||||
|
const TYPE: NixType = $gty;
|
||||||
|
}
|
||||||
)*
|
)*
|
||||||
|
|
||||||
const _: () = assert!(size_of::<Value<'static>>() == 8);
|
const _: () = assert!(size_of::<Value<'static>>() == 8);
|
||||||
@@ -57,6 +125,7 @@ macro_rules! define_value_types {
|
|||||||
let mut mask_true: u8 = 0;
|
let mut mask_true: u8 = 0;
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
while i < tags.len() {
|
while i < tags.len() {
|
||||||
|
#[expect(clippy::indexing_slicing, reason = "loop condition guarantees `i < tags.len()`")]
|
||||||
let (neg, val) = tags[i];
|
let (neg, val) = tags[i];
|
||||||
let bit = 1 << val;
|
let bit = 1 << val;
|
||||||
if neg {
|
if neg {
|
||||||
@@ -70,15 +139,20 @@ macro_rules! define_value_types {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// SAFETY: `trace` visits every reachable `Gc` pointer: for each GC
|
||||||
|
// tag it downcasts to the concrete `Gc` type and forwards `trace`,
|
||||||
|
// while inline tags hold no GC pointers and need no tracing.
|
||||||
unsafe impl<'gc> Collect<'gc> for Value<'gc> {
|
unsafe impl<'gc> Collect<'gc> for Value<'gc> {
|
||||||
const NEEDS_TRACE: bool = true;
|
const NEEDS_TRACE: bool = true;
|
||||||
fn trace<T: Trace<'gc>>(&self, cc: &mut T) {
|
fn trace<T: Trace<'gc>>(&self, cc: &mut T) {
|
||||||
let Some(tag) = self.raw.tag() else { return };
|
let Some(tag) = self.raw.tag() else { return };
|
||||||
match tag {
|
match tag {
|
||||||
$(<$gtype as Storable>::TAG => unsafe {
|
$($gtag => unsafe {
|
||||||
self.load_gc::<$gtype>().trace(cc)
|
// SAFETY: `tag` matched `$gtag`, so `downcast` to the
|
||||||
|
// corresponding GC type is guaranteed to be `Some`.
|
||||||
|
self.downcast::<$gtype>().unwrap_unchecked().trace(cc)
|
||||||
},)*
|
},)*
|
||||||
$(<$itype as Storable>::TAG => (),)*
|
$($itag => (),)*
|
||||||
_ => unreachable!("invalid value tag"),
|
_ => unreachable!("invalid value tag"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -87,13 +161,17 @@ macro_rules! define_value_types {
|
|||||||
impl fmt::Debug for Value<'_> {
|
impl fmt::Debug for Value<'_> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self.tag() {
|
match self.tag() {
|
||||||
|
// SAFETY: `tag()` is `None`, meaning the `RawBox` holds a
|
||||||
|
// float, so `float()` is guaranteed to be `Some`.
|
||||||
None => write!(f, "Float({:?})", unsafe {
|
None => write!(f, "Float({:?})", unsafe {
|
||||||
self.raw.float().unwrap_unchecked()
|
self.raw.float().unwrap_unchecked()
|
||||||
}),
|
}),
|
||||||
$(Some(<$itype as Storable>::TAG) => write!(f, "{}({:?})", $iname, unsafe {
|
// SAFETY: `tag()` matched `$itag`, so `downcast` to the
|
||||||
self.as_inline::<$itype>().unwrap_unchecked()
|
// corresponding inline type is guaranteed to be `Some`.
|
||||||
|
$(Some($itag) => write!(f, "{}({:?})", $iname, unsafe {
|
||||||
|
self.downcast::<$itype>().unwrap_unchecked()
|
||||||
}),)*
|
}),)*
|
||||||
$(Some(<$gtype as Storable>::TAG) =>
|
$(Some($gtag) =>
|
||||||
write!(f, "{}(..)", $gname),)*
|
write!(f, "{}(..)", $gname),)*
|
||||||
_ => unreachable!("invalid value tag"),
|
_ => unreachable!("invalid value tag"),
|
||||||
}
|
}
|
||||||
@@ -104,21 +182,58 @@ macro_rules! define_value_types {
|
|||||||
|
|
||||||
define_value_types! {
|
define_value_types! {
|
||||||
inline {
|
inline {
|
||||||
i32 => RawTag::P1, "SmallInt";
|
i32 => RawTag::P1, NixType::Int, "SmallInt";
|
||||||
bool => RawTag::P2, "Bool";
|
bool => RawTag::P2, NixType::Bool, "Bool";
|
||||||
Null => RawTag::P3, "Null";
|
Null => RawTag::P3, NixType::Null, "Null";
|
||||||
StringId => RawTag::P4, "SmallString";
|
StringId => RawTag::P4, NixType::String, "SmallString";
|
||||||
PrimOp => RawTag::P5, "PrimOp";
|
PrimOp => RawTag::P5, NixType::PrimOp, "PrimOp";
|
||||||
Path => RawTag::P6, "Path";
|
Path => RawTag::P6, NixType::Path, "Path";
|
||||||
}
|
}
|
||||||
gc {
|
gc {
|
||||||
i64 => RawTag::P7, "BigInt";
|
i64 => RawTag::P7, NixType::Int, "BigInt";
|
||||||
NixString => RawTag::N1, "String";
|
NixString => RawTag::N1, NixType::String, "String";
|
||||||
AttrSet<'_> => RawTag::N2, "AttrSet";
|
AttrSet<'_> => RawTag::N2, NixType::AttrSet, "AttrSet";
|
||||||
List<'_> => RawTag::N3, "List";
|
List<'_> => RawTag::N3, NixType::List, "List";
|
||||||
Thunk<'_> => RawTag::N4, "Thunk";
|
Thunk<'_> => RawTag::N4, NixType::Thunk, "Thunk";
|
||||||
Closure<'_> => RawTag::N5, "Closure";
|
Closure<'_> => RawTag::N5, NixType::Closure, "Closure";
|
||||||
PrimOpApp<'_> => RawTag::N6, "PrimOpApp";
|
PrimOpApp<'_> => RawTag::N6, NixType::PrimOpApp, "PrimOpApp";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl private::Cealed for f64 {}
|
||||||
|
|
||||||
|
impl Storable<'_> for f64 {
|
||||||
|
#[inline(always)]
|
||||||
|
fn is(value: &RawBox) -> bool {
|
||||||
|
value.is_float()
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
fn to_raw_box(self) -> RawBox {
|
||||||
|
RawBox::from_float(self)
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
unsafe fn from_raw_box(raw: RawBox) -> Self {
|
||||||
|
// SAFETY: the caller guarantees `raw` represents a valid `f64`, so
|
||||||
|
// `float()` is guaranteed to be `Some`.
|
||||||
|
unsafe { raw.float().copied().unwrap_unchecked() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gc> ValueVariant<'gc> for f64 {
|
||||||
|
type Ty = f64;
|
||||||
|
const TYPE: NixType = NixType::Float;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gc> TryFrom<Value<'gc>> for f64 {
|
||||||
|
type Error = ();
|
||||||
|
fn try_from(value: Value<'gc>) -> Result<Self, Self::Error> {
|
||||||
|
value.downcast::<f64>().ok_or(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gc, T: Storable<'gc>> From<T> for Value<'gc> {
|
||||||
|
fn from(value: T) -> Self {
|
||||||
|
Value::new(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,33 +250,11 @@ pub struct Value<'gc> {
|
|||||||
impl Default for Value<'_> {
|
impl Default for Value<'_> {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new_inline(Null)
|
Self::new(Null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'gc> Value<'gc> {
|
impl<'gc> Value<'gc> {
|
||||||
#[inline(always)]
|
|
||||||
fn from_raw_value(rv: RawValue) -> Self {
|
|
||||||
Self {
|
|
||||||
raw: RawBox::from_value(rv),
|
|
||||||
_marker: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Load a GC pointer from a value with a negative tag.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// The value must actually store a `Gc<'gc, T>` with the matching type.
|
|
||||||
#[inline(always)]
|
|
||||||
unsafe fn load_gc<T: GcStorable>(self) -> Gc<'gc, T> {
|
|
||||||
unsafe {
|
|
||||||
let rv = self.raw.value().unwrap_unchecked();
|
|
||||||
let ptr: *const T = <*const T as RawStore>::from_val(rv);
|
|
||||||
Gc::from_ptr(ptr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
const fn tag(self) -> Option<RawTag> {
|
const fn tag(self) -> Option<RawTag> {
|
||||||
self.raw.tag()
|
self.raw.tag()
|
||||||
@@ -170,80 +263,37 @@ impl<'gc> Value<'gc> {
|
|||||||
|
|
||||||
impl<'gc> Value<'gc> {
|
impl<'gc> Value<'gc> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new_float(val: f64) -> Self {
|
#[expect(
|
||||||
|
private_bounds,
|
||||||
|
reason = "Storable is a sealed implementation detail of the value system"
|
||||||
|
)]
|
||||||
|
pub fn new<T: Storable<'gc>>(val: T) -> Self {
|
||||||
Self {
|
Self {
|
||||||
raw: RawBox::from_float(val),
|
raw: val.to_raw_box(),
|
||||||
_marker: PhantomData,
|
_marker: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[allow(private_bounds)]
|
|
||||||
pub fn new_inline<T: InlineStorable>(val: T) -> Self {
|
|
||||||
Self::from_raw_value(RawValue::store(T::TAG, val))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[allow(private_bounds)]
|
|
||||||
pub fn new_gc<T: GcStorable>(gc: Gc<'gc, T>) -> Self {
|
|
||||||
let ptr = Gc::as_ptr(gc);
|
|
||||||
Self::from_raw_value(RawValue::store(T::TAG, ptr))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn make_int(val: i64, mc: &Mutation<'gc>) -> Self {
|
pub fn make_int(val: i64, mc: &Mutation<'gc>) -> Self {
|
||||||
if val >= i32::MIN as i64 && val <= i32::MAX as i64 {
|
if val >= i32::MIN as i64 && val <= i32::MAX as i64 {
|
||||||
Value::new_inline(val as i32)
|
Value::new(val as i32)
|
||||||
} else {
|
} else {
|
||||||
Value::new_gc(Gc::new(mc, val))
|
Value::new(Gc::new(mc, val))
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'gc> Value<'gc> {
|
|
||||||
#[inline]
|
|
||||||
pub fn is_float(self) -> bool {
|
|
||||||
self.raw.is_float()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[allow(private_bounds)]
|
|
||||||
pub fn is<T: Storable>(self) -> bool {
|
|
||||||
self.tag() == Some(T::TAG)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'gc> Value<'gc> {
|
|
||||||
#[inline]
|
|
||||||
pub fn as_float(self) -> Option<f64> {
|
|
||||||
self.raw.float().copied()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[allow(private_bounds)]
|
|
||||||
pub fn as_inline<T: InlineStorable>(self) -> Option<T> {
|
|
||||||
if self.is::<T>() {
|
|
||||||
Some(unsafe {
|
|
||||||
let rv = self.raw.value().unwrap_unchecked();
|
|
||||||
T::from_val(rv)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
#[allow(private_bounds)]
|
pub fn is<T: ValueVariant<'gc>>(self) -> bool {
|
||||||
pub fn as_gc<T: GcStorable>(self) -> Option<Gc<'gc, T>> {
|
T::Ty::is(&self.raw)
|
||||||
if self.is::<T>() {
|
|
||||||
Some(unsafe {
|
|
||||||
let rv = self.raw.value().unwrap_unchecked();
|
|
||||||
let ptr: *const T = <*const T as RawStore>::from_val(rv);
|
|
||||||
Gc::from_ptr(ptr)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn downcast<T: ValueVariant<'gc>>(self) -> Option<T::Ty> {
|
||||||
|
self.is::<T>()
|
||||||
|
// SAFETY: `is::<T>()` returned true, so `self.raw` represents a
|
||||||
|
// valid `T::Ty`.
|
||||||
|
.then(|| unsafe { T::Ty::from_raw_box(self.raw) })
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@@ -252,19 +302,19 @@ impl<'gc> Value<'gc> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn as_num(self) -> Option<NixNum> {
|
pub fn downcast_num(self) -> Option<NixNum> {
|
||||||
if let Some(i) = self.as_inline::<i32>() {
|
if let Some(i) = self.downcast::<i32>() {
|
||||||
Some(NixNum::Int(i as i64))
|
Some(NixNum::Int(i as i64))
|
||||||
} else if let Some(gc_i) = self.as_gc::<i64>() {
|
} else if let Some(gc_i) = self.downcast::<i64>() {
|
||||||
Some(NixNum::Int(*gc_i))
|
Some(NixNum::Int(*gc_i))
|
||||||
} else {
|
} else {
|
||||||
self.as_float().map(NixNum::Float)
|
self.downcast::<f64>().map(NixNum::Float)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn restrict(self) -> Result<StrictValue<'gc>, Gc<'gc, Thunk<'gc>>> {
|
pub fn restrict(self) -> Result<StrictValue<'gc>, Gc<'gc, Thunk<'gc>>> {
|
||||||
if let Some(thunk) = self.as_gc::<Thunk<'gc>>() {
|
if let Some(thunk) = self.downcast::<Thunk>() {
|
||||||
Err(thunk)
|
Err(thunk)
|
||||||
} else {
|
} else {
|
||||||
Ok(StrictValue(self))
|
Ok(StrictValue(self))
|
||||||
@@ -272,8 +322,12 @@ impl<'gc> Value<'gc> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
#[expect(
|
||||||
|
clippy::unreachable,
|
||||||
|
reason = "the preceding `if`/`else if` chain exhausts every registered value tag"
|
||||||
|
)]
|
||||||
pub fn ty(self) -> NixType {
|
pub fn ty(self) -> NixType {
|
||||||
if self.is_float() {
|
if self.is::<f64>() {
|
||||||
NixType::Float
|
NixType::Float
|
||||||
} else if self.is::<i32>() || self.is::<i64>() {
|
} else if self.is::<i32>() || self.is::<i64>() {
|
||||||
NixType::Int
|
NixType::Int
|
||||||
@@ -305,30 +359,13 @@ impl<'gc> Value<'gc> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
#[allow(private_bounds)]
|
pub fn expect<T: ValueVariant<'gc>>(self) -> Result<T::Ty, NixType> {
|
||||||
pub fn expect_inline<T: InlineStorable>(self) -> Result<T, NixType> {
|
self.downcast::<T>().ok_or_else(|| self.ty())
|
||||||
self.as_inline::<T>().ok_or_else(|| self.ty())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[allow(private_bounds)]
|
|
||||||
pub fn expect_gc<T: GcStorable>(self) -> Result<Gc<'gc, T>, NixType> {
|
|
||||||
self.as_gc::<T>().ok_or_else(|| self.ty())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn expect_num(self) -> Result<NixNum, NixType> {
|
pub fn expect_num(self) -> Result<NixNum, NixType> {
|
||||||
self.as_num().ok_or_else(|| self.ty())
|
self.downcast_num().ok_or_else(|| self.ty())
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn expect_bool(self) -> Result<bool, NixType> {
|
|
||||||
self.as_inline::<bool>().ok_or_else(|| self.ty())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn expect_float(self) -> Result<f64, NixType> {
|
|
||||||
self.as_float().ok_or_else(|| self.ty())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,40 +383,24 @@ impl<'gc> From<StaticValue> for Value<'gc> {
|
|||||||
|
|
||||||
impl StaticValue {
|
impl StaticValue {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new_float(val: f64) -> Self {
|
#[expect(
|
||||||
Self(Value::new_float(val))
|
private_bounds,
|
||||||
|
reason = "Storable is a sealed implementation detail of the value system"
|
||||||
|
)]
|
||||||
|
pub fn new<T: Storable<'static>>(val: T) -> Self {
|
||||||
|
Self(Value::new(val))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
#[allow(private_bounds)]
|
pub fn is<T: ValueVariant<'static>>(self) -> bool {
|
||||||
pub fn new_inline<T: InlineStorable>(val: T) -> Self {
|
|
||||||
Self(Value::new_inline(val))
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
pub fn new_primop(id: BuiltinId, arity: u8, dispatch_ip: u32) -> Self {
|
|
||||||
Self(Value::new_inline(PrimOp {
|
|
||||||
id,
|
|
||||||
arity,
|
|
||||||
dispatch_ip,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
pub fn is_float(self) -> bool {
|
|
||||||
self.0.is_float()
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
#[allow(private_bounds)]
|
|
||||||
pub fn is<T: InlineStorable>(self) -> bool {
|
|
||||||
self.0.is::<T>()
|
self.0.is::<T>()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn as_float(self) -> Option<f64> {
|
pub fn downcast<T: ValueVariant<'static>>(self) -> Option<T::Ty> {
|
||||||
self.0.as_float()
|
self.0.downcast::<T>()
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
#[allow(private_bounds)]
|
|
||||||
pub fn as_inline<T: InlineStorable>(self) -> Option<T> {
|
|
||||||
self.0.as_inline::<T>()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn to_bits(self) -> u64 {
|
pub fn to_bits(self) -> u64 {
|
||||||
self.0.raw.to_bits()
|
self.0.raw.to_bits()
|
||||||
@@ -478,6 +499,10 @@ impl<'gc> AttrSet<'gc> {
|
|||||||
Self { entries }
|
Self { entries }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(
|
||||||
|
clippy::indexing_slicing,
|
||||||
|
reason = "index comes from a successful `binary_search_by_key`, so it is a valid entry index"
|
||||||
|
)]
|
||||||
pub fn lookup(&self, key: StringId) -> Option<Value<'gc>> {
|
pub fn lookup(&self, key: StringId) -> Option<Value<'gc>> {
|
||||||
self.entries
|
self.entries
|
||||||
.binary_search_by_key(&key, |(k, _)| *k)
|
.binary_search_by_key(&key, |(k, _)| *k)
|
||||||
@@ -489,6 +514,10 @@ impl<'gc> AttrSet<'gc> {
|
|||||||
self.entries.binary_search_by_key(&key, |(k, _)| *k).is_ok()
|
self.entries.binary_search_by_key(&key, |(k, _)| *k).is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(
|
||||||
|
clippy::indexing_slicing,
|
||||||
|
reason = "`i`/`j` stay strictly below their lengths inside the loop, and the trailing slices use those in-bounds cursors as start indices"
|
||||||
|
)]
|
||||||
pub fn merge(&self, other: &Self, mc: &Mutation<'gc>) -> Gc<'gc, Self> {
|
pub fn merge(&self, other: &Self, mc: &Mutation<'gc>) -> Gc<'gc, Self> {
|
||||||
use std::cmp::Ordering::*;
|
use std::cmp::Ordering::*;
|
||||||
|
|
||||||
@@ -549,6 +578,9 @@ impl<'gc> List<'gc> {
|
|||||||
impl<'gc> Unlock for List<'gc> {
|
impl<'gc> Unlock for List<'gc> {
|
||||||
type Unlocked = RefCell<SmallVec<[Value<'gc>; 4]>>;
|
type Unlocked = RefCell<SmallVec<[Value<'gc>; 4]>>;
|
||||||
unsafe fn unlock_unchecked(&self) -> &Self::Unlocked {
|
unsafe fn unlock_unchecked(&self) -> &Self::Unlocked {
|
||||||
|
// SAFETY: the caller upholds the `Unlock` contract (mutation happens
|
||||||
|
// behind a write barrier); we forward that obligation to the inner
|
||||||
|
// `RefLock`'s `unlock_unchecked`.
|
||||||
unsafe { self.inner.unlock_unchecked() }
|
unsafe { self.inner.unlock_unchecked() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -572,14 +604,6 @@ pub struct Env<'gc> {
|
|||||||
}
|
}
|
||||||
pub type GcEnv<'gc> = GcRefLock<'gc, Env<'gc>>;
|
pub type GcEnv<'gc> = GcRefLock<'gc, Env<'gc>>;
|
||||||
|
|
||||||
#[derive(Collect, Debug)]
|
|
||||||
#[collect(no_drop)]
|
|
||||||
pub struct WithEnv<'gc> {
|
|
||||||
pub env: Value<'gc>,
|
|
||||||
pub prev: Option<GcWithEnv<'gc>>,
|
|
||||||
}
|
|
||||||
pub type GcWithEnv<'gc> = Gc<'gc, WithEnv<'gc>>;
|
|
||||||
|
|
||||||
impl<'gc> Env<'gc> {
|
impl<'gc> Env<'gc> {
|
||||||
pub fn empty() -> Self {
|
pub fn empty() -> Self {
|
||||||
Env {
|
Env {
|
||||||
@@ -588,6 +612,10 @@ impl<'gc> Env<'gc> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(
|
||||||
|
clippy::indexing_slicing,
|
||||||
|
reason = "`locals` was just created with `1 + n_locals` elements, so index 0 is always valid"
|
||||||
|
)]
|
||||||
pub fn with_arg(arg: Value<'gc>, n_locals: u32, prev: Gc<'gc, RefLock<Env<'gc>>>) -> Self {
|
pub fn with_arg(arg: Value<'gc>, n_locals: u32, prev: Gc<'gc, RefLock<Env<'gc>>>) -> Self {
|
||||||
let mut locals = smallvec::smallvec![Value::default(); 1 + n_locals as usize];
|
let mut locals = smallvec::smallvec![Value::default(); 1 + n_locals as usize];
|
||||||
locals[0] = arg;
|
locals[0] = arg;
|
||||||
@@ -625,6 +653,18 @@ pub struct PrimOp {
|
|||||||
pub dispatch_ip: u32,
|
pub dispatch_ip: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<BuiltinId> for PrimOp {
|
||||||
|
fn from(id: BuiltinId) -> Self {
|
||||||
|
let BuiltinInfo { arity, .. } = id.info();
|
||||||
|
let dispatch_ip = Continuation::entry_for_builtin(id).ip();
|
||||||
|
Self {
|
||||||
|
id,
|
||||||
|
arity,
|
||||||
|
dispatch_ip,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl RawStore for PrimOp {
|
impl RawStore for PrimOp {
|
||||||
fn to_val(self, value: &mut RawValue) {
|
fn to_val(self, value: &mut RawValue) {
|
||||||
let bytes = self.dispatch_ip.to_le_bytes();
|
let bytes = self.dispatch_ip.to_le_bytes();
|
||||||
@@ -675,6 +715,19 @@ impl<'gc> Deref for StrictValue<'gc> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'gc> From<StrictValue<'gc>> for Value<'gc> {
|
||||||
|
fn from(value: StrictValue<'gc>) -> Self {
|
||||||
|
value.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gc> TryFrom<Value<'gc>> for StrictValue<'gc> {
|
||||||
|
type Error = Gc<'gc, Thunk<'gc>>;
|
||||||
|
fn try_from(value: Value<'gc>) -> Result<Self, Self::Error> {
|
||||||
|
value.restrict()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl fmt::Debug for StrictValue<'_> {
|
impl fmt::Debug for StrictValue<'_> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
fmt::Debug::fmt(&self.0, f)
|
fmt::Debug::fmt(&self.0, f)
|
||||||
|
|||||||
+5
-1
@@ -7,12 +7,16 @@ edition = "2024"
|
|||||||
gc-arena = { workspace = true }
|
gc-arena = { workspace = true }
|
||||||
hashbrown = { workspace = true }
|
hashbrown = { workspace = true }
|
||||||
smallvec = { workspace = true }
|
smallvec = { workspace = true }
|
||||||
sysinfo = { version = "0.38", default-features = false, features = ["system"] }
|
sysinfo = { version = "0.39", default-features = false, features = ["system"] }
|
||||||
|
|
||||||
fix-bytecode = { path = "../fix-bytecode" }
|
fix-bytecode = { path = "../fix-bytecode" }
|
||||||
fix-error = { path = "../fix-error" }
|
fix-error = { path = "../fix-error" }
|
||||||
fix-lang = { path = "../fix-lang" }
|
fix-lang = { path = "../fix-lang" }
|
||||||
|
fix-macros = { path = "../fix-macros" }
|
||||||
fix-runtime = { path = "../fix-runtime" }
|
fix-runtime = { path = "../fix-runtime" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
tailcall = []
|
tailcall = []
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ pub(crate) type OpFn<'gc, C> = extern "rust-preserve-none" fn(
|
|||||||
|
|
||||||
pub(crate) struct DispatchTable<'gc, C: VmRuntimeCtx>(pub(crate) [OpFn<'gc, C>; 256]);
|
pub(crate) struct DispatchTable<'gc, C: VmRuntimeCtx>(pub(crate) [OpFn<'gc, C>; 256]);
|
||||||
|
|
||||||
|
#[expect(clippy::panic, reason = "illegal opcode should panic")]
|
||||||
extern "rust-preserve-none" fn op_illegal<'gc, C: VmRuntimeCtx>(
|
extern "rust-preserve-none" fn op_illegal<'gc, C: VmRuntimeCtx>(
|
||||||
_vm: &mut Vm<'gc>,
|
_vm: &mut Vm<'gc>,
|
||||||
_mc: &Mutation<'gc>,
|
_mc: &Mutation<'gc>,
|
||||||
@@ -144,7 +145,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_dispatch_cont, (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));
|
||||||
@@ -200,6 +201,7 @@ tail_fn!(op_load_scoped_binding, (ctx, reader, mc));
|
|||||||
macro_rules! table {
|
macro_rules! table {
|
||||||
($($variant:ident => $fn:ident),* $(,)?) => {
|
($($variant:ident => $fn:ident),* $(,)?) => {
|
||||||
impl<'gc, C: VmRuntimeCtx> DispatchTable<'gc, C> {
|
impl<'gc, C: VmRuntimeCtx> DispatchTable<'gc, C> {
|
||||||
|
#[expect(clippy::indexing_slicing, reason = "Op is repr(u8)")]
|
||||||
pub(crate) const NEW: Self = {
|
pub(crate) const NEW: Self = {
|
||||||
let mut arr: [OpFn<'gc, C>; 256] = [op_illegal; 256];
|
let mut arr: [OpFn<'gc, C>; 256] = [op_illegal; 256];
|
||||||
$( arr[fix_bytecode::Op::$variant as usize] = $fn; )*
|
$( arr[fix_bytecode::Op::$variant as usize] = $fn; )*
|
||||||
@@ -209,7 +211,6 @@ macro_rules! table {
|
|||||||
|
|
||||||
// Exhaustiveness check: fails to compile if `fix_bytecode::Op` gains,
|
// Exhaustiveness check: fails to compile if `fix_bytecode::Op` gains,
|
||||||
// loses, or renames a variant that isn't wired up above.
|
// loses, or renames a variant that isn't wired up above.
|
||||||
#[allow(dead_code)]
|
|
||||||
const _: fn(fix_bytecode::Op) = |op| match op {
|
const _: fn(fix_bytecode::Op) = |op| match op {
|
||||||
$( fix_bytecode::Op::$variant => (), )*
|
$( fix_bytecode::Op::$variant => (), )*
|
||||||
};
|
};
|
||||||
@@ -235,7 +236,7 @@ table! {
|
|||||||
MakePatternClosure => op_make_pattern_closure,
|
MakePatternClosure => op_make_pattern_closure,
|
||||||
|
|
||||||
Call => op_call,
|
Call => op_call,
|
||||||
DispatchPrimOp => op_dispatch_primop,
|
DispatchCont => op_dispatch_cont,
|
||||||
Return => op_return,
|
Return => op_return,
|
||||||
|
|
||||||
MakeAttrs => op_make_attrs,
|
MakeAttrs => op_make_attrs,
|
||||||
@@ -291,6 +292,10 @@ table! {
|
|||||||
Illegal => op_illegal,
|
Illegal => op_illegal,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(
|
||||||
|
clippy::indexing_slicing,
|
||||||
|
reason = "assume well-formed bytecode; Op is repr(u8)"
|
||||||
|
)]
|
||||||
pub(crate) fn run_tailcall<'gc, C: VmRuntimeCtx>(
|
pub(crate) fn run_tailcall<'gc, C: VmRuntimeCtx>(
|
||||||
vm: &mut Vm<'gc>,
|
vm: &mut Vm<'gc>,
|
||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ pub(crate) fn op_add<'gc, M: Machine<'gc>>(
|
|||||||
let combined = format!("{ls}{rs}");
|
let combined = format!("{ls}{rs}");
|
||||||
let canon = canon_path_str(&combined);
|
let canon = canon_path_str(&combined);
|
||||||
let sid = ctx.intern_string(canon);
|
let sid = ctx.intern_string(canon);
|
||||||
m.push(Value::new_inline(fix_runtime::Path(sid)));
|
m.push(Value::new(fix_runtime::Path(sid)));
|
||||||
return Step::Continue(());
|
return Step::Continue(());
|
||||||
}
|
}
|
||||||
if let (Some(ls), Some(rs)) = (ctx.get_string(lhs), ctx.get_string_or_path(rhs)) {
|
if let (Some(ls), Some(rs)) = (ctx.get_string(lhs), ctx.get_string_or_path(rhs)) {
|
||||||
@@ -39,7 +39,7 @@ pub(crate) fn op_add<'gc, M: Machine<'gc>>(
|
|||||||
mc,
|
mc,
|
||||||
crate::NixString::with_context(format!("{ls}{rs}"), merged),
|
crate::NixString::with_context(format!("{ls}{rs}"), merged),
|
||||||
);
|
);
|
||||||
m.push(Value::new_gc(ns));
|
m.push(Value::new(ns));
|
||||||
return Step::Continue(());
|
return Step::Continue(());
|
||||||
}
|
}
|
||||||
let res = numeric_binop(lhs, rhs, mc, i64::wrapping_add, |a, b| a + b);
|
let res = numeric_binop(lhs, rhs, mc, i64::wrapping_add, |a, b| a + b);
|
||||||
@@ -200,7 +200,7 @@ pub(crate) fn op_concat<'gc, M: Machine<'gc>>(
|
|||||||
let mut items = smallvec::SmallVec::new();
|
let mut items = smallvec::SmallVec::new();
|
||||||
items.extend_from_slice(&l.inner.borrow());
|
items.extend_from_slice(&l.inner.borrow());
|
||||||
items.extend_from_slice(&r.inner.borrow());
|
items.extend_from_slice(&r.inner.borrow());
|
||||||
m.push(Value::new_gc(Gc::new(
|
m.push(Value::new(Gc::new(
|
||||||
mc,
|
mc,
|
||||||
crate::List {
|
crate::List {
|
||||||
inner: RefLock::new(items),
|
inner: RefLock::new(items),
|
||||||
@@ -216,7 +216,7 @@ pub(crate) fn op_update<'gc, M: Machine<'gc>>(
|
|||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
let (l, r) = m.force_and_retry::<(Gc<AttrSet>, Gc<AttrSet>)>(reader, mc)?;
|
let (l, r) = m.force_and_retry::<(Gc<AttrSet>, Gc<AttrSet>)>(reader, mc)?;
|
||||||
m.push(Value::new_gc(l.merge(&r, mc)));
|
m.push(Value::new(l.merge(&r, mc)));
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,7 +229,7 @@ pub(crate) fn op_neg<'gc, M: Machine<'gc>>(
|
|||||||
let rhs = m.force_and_retry::<NixNum>(reader, mc)?;
|
let rhs = m.force_and_retry::<NixNum>(reader, mc)?;
|
||||||
match rhs {
|
match rhs {
|
||||||
NixNum::Int(int) => m.push(Value::make_int(-int, mc)),
|
NixNum::Int(int) => m.push(Value::make_int(-int, mc)),
|
||||||
NixNum::Float(float) => m.push(Value::new_float(-float)),
|
NixNum::Float(float) => m.push(Value::new(-float)),
|
||||||
}
|
}
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
@@ -241,7 +241,7 @@ pub(crate) fn op_not<'gc, M: Machine<'gc>>(
|
|||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
let rhs = m.force_and_retry::<bool>(reader, mc)?;
|
let rhs = m.force_and_retry::<bool>(reader, mc)?;
|
||||||
m.push(Value::new_inline(!rhs));
|
m.push(Value::new(!rhs));
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,17 +263,17 @@ fn compare_values_inner<'gc, M: Machine<'gc>>(
|
|||||||
a.partial_cmp(&(b as f64)).unwrap_or(Ordering::Less)
|
a.partial_cmp(&(b as f64)).unwrap_or(Ordering::Less)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
m.push(Value::new_inline(pred(ord)));
|
m.push(Value::new(pred(ord)));
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
if let (Some(a), Some(b)) = (ctx.get_string(lhs), ctx.get_string(rhs)) {
|
if let (Some(a), Some(b)) = (ctx.get_string(lhs), ctx.get_string(rhs)) {
|
||||||
m.push(Value::new_inline(pred(a.cmp(b))));
|
m.push(Value::new(pred(a.cmp(b))));
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
if let (Some(a), Some(b)) = (lhs.as_inline::<Path>(), rhs.as_inline::<Path>()) {
|
if let (Some(a), Some(b)) = (lhs.downcast::<Path>(), rhs.downcast::<Path>()) {
|
||||||
let a = ctx.resolve_string(a.0);
|
let a = ctx.resolve_string(a.0);
|
||||||
let b = ctx.resolve_string(b.0);
|
let b = ctx.resolve_string(b.0);
|
||||||
m.push(Value::new_inline(pred(a.cmp(b))));
|
m.push(Value::new(pred(a.cmp(b))));
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
// TODO: compare other types
|
// TODO: compare other types
|
||||||
@@ -285,12 +285,12 @@ fn compare_values_inner<'gc, M: Machine<'gc>>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_num(val: StrictValue<'_>) -> Option<NixNum> {
|
pub(crate) fn get_num(val: StrictValue<'_>) -> Option<NixNum> {
|
||||||
if let Some(i) = val.as_inline::<i32>() {
|
if let Some(i) = val.downcast::<i32>() {
|
||||||
Some(NixNum::Int(i64::from(i)))
|
Some(NixNum::Int(i64::from(i)))
|
||||||
} else if let Some(gc_i) = val.as_gc::<i64>() {
|
} else if let Some(gc_i) = val.downcast::<i64>() {
|
||||||
Some(NixNum::Int(*gc_i))
|
Some(NixNum::Int(*gc_i))
|
||||||
} else {
|
} else {
|
||||||
val.as_float().map(NixNum::Float)
|
val.downcast::<f64>().map(NixNum::Float)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,13 +304,9 @@ fn numeric_binop<'gc>(
|
|||||||
) -> crate::VmResult<Value<'gc>> {
|
) -> crate::VmResult<Value<'gc>> {
|
||||||
match (get_num(lhs), get_num(rhs)) {
|
match (get_num(lhs), get_num(rhs)) {
|
||||||
(Some(NixNum::Int(a)), Some(NixNum::Int(b))) => Ok(Value::make_int(int_op(a, b), mc)),
|
(Some(NixNum::Int(a)), Some(NixNum::Int(b))) => Ok(Value::make_int(int_op(a, b), mc)),
|
||||||
(Some(NixNum::Float(a)), Some(NixNum::Float(b))) => Ok(Value::new_float(float_op(a, b))),
|
(Some(NixNum::Float(a)), Some(NixNum::Float(b))) => Ok(Value::new(float_op(a, b))),
|
||||||
(Some(NixNum::Int(a)), Some(NixNum::Float(b))) => {
|
(Some(NixNum::Int(a)), Some(NixNum::Float(b))) => Ok(Value::new(float_op(a as f64, b))),
|
||||||
Ok(Value::new_float(float_op(a as f64, b)))
|
(Some(NixNum::Float(a)), Some(NixNum::Int(b))) => Ok(Value::new(float_op(a, b as f64))),
|
||||||
}
|
|
||||||
(Some(NixNum::Float(a)), Some(NixNum::Int(b))) => {
|
|
||||||
Ok(Value::new_float(float_op(a, b as f64)))
|
|
||||||
}
|
|
||||||
_ => Err(crate::vm_err(format!(
|
_ => Err(crate::vm_err(format!(
|
||||||
"cannot perform arithmetic on non-numbers: {:?}",
|
"cannot perform arithmetic on non-numbers: {:?}",
|
||||||
(lhs.ty(), rhs.ty())
|
(lhs.ty(), rhs.ty())
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use fix_bytecode::PrimOpPhase;
|
use fix_bytecode::Continuation;
|
||||||
use fix_error::Error;
|
use fix_error::Error;
|
||||||
use fix_runtime::{resolve_operand, *};
|
use fix_runtime::{resolve_operand, *};
|
||||||
use gc_arena::{Gc, Mutation, RefLock};
|
use gc_arena::{Gc, Mutation, RefLock};
|
||||||
@@ -9,6 +9,10 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
#[expect(
|
||||||
|
clippy::indexing_slicing,
|
||||||
|
reason = "app.args is a fixed [Value; 3]; indices are bounded by primop arity (<= 3) validated at PrimOpApp construction"
|
||||||
|
)]
|
||||||
pub(crate) fn call<'gc, M: Machine<'gc>>(
|
pub(crate) fn call<'gc, M: Machine<'gc>>(
|
||||||
m: &mut M,
|
m: &mut M,
|
||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
@@ -21,7 +25,7 @@ pub(crate) fn call<'gc, M: Machine<'gc>>(
|
|||||||
return m.finish_err(Error::eval_error("stack overflow; max-call-depth exceeded"));
|
return m.finish_err(Error::eval_error("stack overflow; max-call-depth exceeded"));
|
||||||
}
|
}
|
||||||
m.inc_call_depth();
|
m.inc_call_depth();
|
||||||
if let Some(closure) = func.as_gc::<Closure>() {
|
if let Some(closure) = func.downcast::<Closure>() {
|
||||||
if closure.pattern.is_some() {
|
if closure.pattern.is_some() {
|
||||||
// FIXME: better DX...
|
// FIXME: better DX...
|
||||||
m.push(func.relax());
|
m.push(func.relax());
|
||||||
@@ -30,8 +34,9 @@ pub(crate) fn call<'gc, M: Machine<'gc>>(
|
|||||||
pc: resume_pc,
|
pc: resume_pc,
|
||||||
thunk: None,
|
thunk: None,
|
||||||
env: m.env(),
|
env: m.env(),
|
||||||
|
depth: None,
|
||||||
});
|
});
|
||||||
reader.set_pc(PrimOpPhase::CallPattern.ip() as usize);
|
reader.set_pc(Continuation::CallPattern.ip() as usize);
|
||||||
return Step::Continue(());
|
return Step::Continue(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,16 +48,18 @@ pub(crate) fn call<'gc, M: Machine<'gc>>(
|
|||||||
pc: resume_pc,
|
pc: resume_pc,
|
||||||
thunk: None,
|
thunk: None,
|
||||||
env: m.env(),
|
env: m.env(),
|
||||||
|
depth: None,
|
||||||
});
|
});
|
||||||
reader.set_pc(ip as usize);
|
reader.set_pc(ip as usize);
|
||||||
m.set_env(new_env);
|
m.set_env(new_env);
|
||||||
} else if let Some(primop) = func.as_inline::<PrimOp>() {
|
} else if let Some(primop) = func.downcast::<PrimOp>() {
|
||||||
if primop.arity == 1 {
|
if primop.arity == 1 {
|
||||||
m.push(arg);
|
m.push(arg);
|
||||||
m.push_call_frame(CallFrame {
|
m.push_call_frame(CallFrame {
|
||||||
pc: resume_pc,
|
pc: resume_pc,
|
||||||
thunk: None,
|
thunk: None,
|
||||||
env: m.env(),
|
env: m.env(),
|
||||||
|
depth: None,
|
||||||
});
|
});
|
||||||
reader.set_pc(primop.dispatch_ip as usize)
|
reader.set_pc(primop.dispatch_ip as usize)
|
||||||
} else {
|
} else {
|
||||||
@@ -61,9 +68,9 @@ pub(crate) fn call<'gc, M: Machine<'gc>>(
|
|||||||
arity: primop.arity - 1,
|
arity: primop.arity - 1,
|
||||||
args: [arg, Value::default(), Value::default()],
|
args: [arg, Value::default(), Value::default()],
|
||||||
};
|
};
|
||||||
m.push(Value::new_gc(Gc::new(mc, app)));
|
m.push(Value::new(Gc::new(mc, app)));
|
||||||
}
|
}
|
||||||
} else if let Some(app) = func.as_gc::<PrimOpApp>() {
|
} else if let Some(app) = func.downcast::<PrimOpApp>() {
|
||||||
if app.arity == 1 {
|
if app.arity == 1 {
|
||||||
for i in 0..app.primop.arity - 1 {
|
for i in 0..app.primop.arity - 1 {
|
||||||
m.push(app.args[i as usize]);
|
m.push(app.args[i as usize]);
|
||||||
@@ -73,6 +80,7 @@ pub(crate) fn call<'gc, M: Machine<'gc>>(
|
|||||||
pc: resume_pc,
|
pc: resume_pc,
|
||||||
thunk: None,
|
thunk: None,
|
||||||
env: m.env(),
|
env: m.env(),
|
||||||
|
depth: None,
|
||||||
});
|
});
|
||||||
reader.set_pc(app.primop.dispatch_ip as usize)
|
reader.set_pc(app.primop.dispatch_ip as usize)
|
||||||
} else {
|
} else {
|
||||||
@@ -82,9 +90,9 @@ pub(crate) fn call<'gc, M: Machine<'gc>>(
|
|||||||
..*app
|
..*app
|
||||||
};
|
};
|
||||||
new_app.args[position] = arg;
|
new_app.args[position] = arg;
|
||||||
m.push(Value::new_gc(Gc::new(mc, new_app)))
|
m.push(Value::new(Gc::new(mc, new_app)))
|
||||||
}
|
}
|
||||||
} else if let Some(attrs) = func.as_gc::<AttrSet>()
|
} else if let Some(attrs) = func.downcast::<AttrSet>()
|
||||||
&& let Some(functor) = attrs.lookup(m.functor_sym())
|
&& let Some(functor) = attrs.lookup(m.functor_sym())
|
||||||
{
|
{
|
||||||
// f arg => (f.__functor f) arg
|
// f arg => (f.__functor f) arg
|
||||||
@@ -97,11 +105,12 @@ pub(crate) fn call<'gc, M: Machine<'gc>>(
|
|||||||
pc: resume_pc,
|
pc: resume_pc,
|
||||||
thunk: None,
|
thunk: None,
|
||||||
env: m.env(),
|
env: m.env(),
|
||||||
|
depth: None,
|
||||||
});
|
});
|
||||||
m.push(arg);
|
m.push(arg);
|
||||||
m.push(func.relax());
|
m.push(func.relax());
|
||||||
m.push(functor);
|
m.push(functor);
|
||||||
reader.set_pc(PrimOpPhase::CallFunctor1.ip() as usize);
|
reader.set_pc(Continuation::CallFunctor1.ip() as usize);
|
||||||
return Step::Continue(());
|
return Step::Continue(());
|
||||||
} else {
|
} else {
|
||||||
return m.finish_err(Error::eval_error(format!(
|
return m.finish_err(Error::eval_error(format!(
|
||||||
@@ -136,25 +145,27 @@ pub(crate) fn op_return<'gc, M: Machine<'gc>>(
|
|||||||
pc: ret_pc,
|
pc: ret_pc,
|
||||||
thunk,
|
thunk,
|
||||||
env,
|
env,
|
||||||
|
depth,
|
||||||
}) = m.pop_call_frame()
|
}) = m.pop_call_frame()
|
||||||
else {
|
else {
|
||||||
match m.force_mode() {
|
match m.force_mode() {
|
||||||
ForceMode::AsIs => return m.finish_ok(ctx.convert_value(val.relax())),
|
ForceMode::AsIs => return m.finish_ok(ctx.convert_value(val.relax())),
|
||||||
ForceMode::Shallow => {
|
ForceMode::Shallow => {
|
||||||
m.push(val.relax());
|
m.push(val.relax());
|
||||||
reader.set_pc(PrimOpPhase::ForceResultShallow.ip() as usize);
|
reader.set_pc(Continuation::ForceResultShallow.ip() as usize);
|
||||||
return Step::Continue(());
|
return Step::Continue(());
|
||||||
}
|
}
|
||||||
ForceMode::Deep => {
|
ForceMode::Deep => {
|
||||||
m.push(val.relax());
|
m.push(val.relax());
|
||||||
m.push(val.relax());
|
m.push(val.relax());
|
||||||
m.push_call_frame(CallFrame {
|
m.push_call_frame(CallFrame {
|
||||||
pc: PrimOpPhase::ForceResultDeepFinish.ip() as usize,
|
pc: Continuation::ForceResultDeepFinish.ip() as usize,
|
||||||
thunk: None,
|
thunk: None,
|
||||||
env: m.env(),
|
env: m.env(),
|
||||||
|
depth: None,
|
||||||
});
|
});
|
||||||
m.inc_call_depth();
|
m.inc_call_depth();
|
||||||
reader.set_pc(PrimOpPhase::DeepSeq.ip() as usize);
|
reader.set_pc(Continuation::PDeepSeq0.ip() as usize);
|
||||||
return Step::Continue(());
|
return Step::Continue(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -162,6 +173,9 @@ pub(crate) fn op_return<'gc, M: Machine<'gc>>(
|
|||||||
reader.set_pc(ret_pc);
|
reader.set_pc(ret_pc);
|
||||||
if let Some(outer_thunk) = thunk {
|
if let Some(outer_thunk) = thunk {
|
||||||
*outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val);
|
*outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val);
|
||||||
|
if let Some(depth) = depth {
|
||||||
|
m.replace(depth, val.relax());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
m.dec_call_depth();
|
m.dec_call_depth();
|
||||||
m.push(val.relax())
|
m.push(val.relax())
|
||||||
@@ -171,11 +185,11 @@ pub(crate) fn op_return<'gc, M: Machine<'gc>>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn op_dispatch_primop<'gc, M: Machine<'gc>>(
|
pub(crate) fn op_dispatch_cont<'gc, M: Machine<'gc>>(
|
||||||
m: &mut M,
|
m: &mut M,
|
||||||
ctx: &mut impl VmRuntimeCtx,
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
crate::primops::dispatch_primop(m, ctx, reader, mc)
|
crate::primops::dispatch_cont(m, ctx, reader, mc)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ pub(crate) fn op_make_thunk<'gc, M: Machine<'gc>>(
|
|||||||
env: m.env(),
|
env: m.env(),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
m.push(Value::new_gc(thunk));
|
m.push(Value::new(thunk));
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ pub(crate) fn op_make_closure<'gc, M: Machine<'gc>>(
|
|||||||
pattern: None,
|
pattern: None,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
m.push(Value::new_gc(closure));
|
m.push(Value::new(closure));
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,6 +88,6 @@ pub(crate) fn op_make_pattern_closure<'gc, M: Machine<'gc>>(
|
|||||||
pattern: Some(pattern),
|
pattern: Some(pattern),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
m.push(Value::new_gc(closure));
|
m.push(Value::new(closure));
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ pub(crate) fn op_make_attrs<'gc, M: Machine<'gc>>(
|
|||||||
|
|
||||||
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));
|
||||||
m.push(Value::new_gc(attrs));
|
m.push(Value::new(attrs));
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,6 +117,10 @@ pub(crate) fn op_select_dynamic<'gc, M: Machine<'gc>>(
|
|||||||
/// Only recognises Select opcodes and jumps; encountering any other
|
/// Only recognises Select opcodes and jumps; encountering any other
|
||||||
/// opcode means we've reached the end of the select sequence and
|
/// opcode means we've reached the end of the select sequence and
|
||||||
/// should report the missing-attribute error.
|
/// should report the missing-attribute error.
|
||||||
|
#[expect(
|
||||||
|
clippy::cast_sign_loss,
|
||||||
|
reason = "jump target = pc + offset is a non-negative bytecode address by codegen invariant"
|
||||||
|
)]
|
||||||
fn select_skip<'gc, M: Machine<'gc>>(
|
fn select_skip<'gc, M: Machine<'gc>>(
|
||||||
m: &mut M,
|
m: &mut M,
|
||||||
key: StringId,
|
key: StringId,
|
||||||
@@ -126,6 +130,7 @@ fn select_skip<'gc, M: Machine<'gc>>(
|
|||||||
use fix_bytecode::Op::*;
|
use fix_bytecode::Op::*;
|
||||||
loop {
|
loop {
|
||||||
match reader.read_op() {
|
match reader.read_op() {
|
||||||
|
// Skip rest of the attrpath
|
||||||
SelectStatic => {
|
SelectStatic => {
|
||||||
reader.set_pc(reader.pc() + 4 + 4);
|
reader.set_pc(reader.pc() + 4 + 4);
|
||||||
}
|
}
|
||||||
@@ -136,10 +141,14 @@ fn select_skip<'gc, M: Machine<'gc>>(
|
|||||||
reader.set_pc(reader.pc() + 4);
|
reader.set_pc(reader.pc() + 4);
|
||||||
break Step::Continue(());
|
break Step::Continue(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default (`a.b or c`)
|
||||||
JumpIfSelectFailed => {
|
JumpIfSelectFailed => {
|
||||||
let offset = reader.read_i32();
|
let offset = reader.read_i32();
|
||||||
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
|
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Report error
|
||||||
_ => {
|
_ => {
|
||||||
let name = ctx.resolve_string(key);
|
let name = ctx.resolve_string(key);
|
||||||
return m.finish_err(Error::eval_error(format!("attribute '{name}' missing")));
|
return m.finish_err(Error::eval_error(format!("attribute '{name}' missing")));
|
||||||
@@ -150,6 +159,14 @@ fn select_skip<'gc, M: Machine<'gc>>(
|
|||||||
|
|
||||||
/// Skip the rest of a **HasAttr** attrpath after an intermediate
|
/// Skip the rest of a **HasAttr** attrpath after an intermediate
|
||||||
/// lookup failed. Only recognises HasAttr opcodes and jumps.
|
/// lookup failed. Only recognises HasAttr opcodes and jumps.
|
||||||
|
#[expect(
|
||||||
|
clippy::cast_sign_loss,
|
||||||
|
reason = "jump target = pc + offset is a non-negative bytecode address by codegen invariant"
|
||||||
|
)]
|
||||||
|
#[expect(
|
||||||
|
clippy::unreachable,
|
||||||
|
reason = "has_attr_skip only runs over a codegen-produced HasAttr attrpath sequence; any other opcode is a codegen bug"
|
||||||
|
)]
|
||||||
fn has_attr_skip(reader: &mut BytecodeReader<'_>) -> Step {
|
fn has_attr_skip(reader: &mut BytecodeReader<'_>) -> Step {
|
||||||
use fix_bytecode::Op::*;
|
use fix_bytecode::Op::*;
|
||||||
loop {
|
loop {
|
||||||
@@ -195,7 +212,7 @@ pub(crate) fn op_has_attr_path_static<'gc, M: Machine<'gc>>(
|
|||||||
let current = m.force_and_retry::<StrictValue>(reader, mc)?;
|
let current = m.force_and_retry::<StrictValue>(reader, mc)?;
|
||||||
|
|
||||||
match current
|
match current
|
||||||
.as_gc::<AttrSet>()
|
.downcast::<AttrSet>()
|
||||||
.and_then(|attrs| attrs.lookup(key))
|
.and_then(|attrs| attrs.lookup(key))
|
||||||
{
|
{
|
||||||
Some(v) => {
|
Some(v) => {
|
||||||
@@ -223,7 +240,7 @@ pub(crate) fn op_has_attr_path_dynamic<'gc, M: Machine<'gc>>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
match current
|
match current
|
||||||
.as_gc::<AttrSet>()
|
.downcast::<AttrSet>()
|
||||||
.and_then(|attrs| attrs.lookup(key_sid))
|
.and_then(|attrs| attrs.lookup(key_sid))
|
||||||
{
|
{
|
||||||
Some(v) => {
|
Some(v) => {
|
||||||
@@ -245,6 +262,10 @@ pub(crate) fn op_jump_if_select_failed<'gc, M: Machine<'gc>>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
#[expect(
|
||||||
|
clippy::cast_sign_loss,
|
||||||
|
reason = "jump target = pc + offset is a non-negative bytecode address by codegen invariant"
|
||||||
|
)]
|
||||||
pub(crate) fn op_jump_if_select_succeeded<'gc, M: Machine<'gc>>(
|
pub(crate) fn op_jump_if_select_succeeded<'gc, M: Machine<'gc>>(
|
||||||
_m: &mut M,
|
_m: &mut M,
|
||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
@@ -263,9 +284,9 @@ pub(crate) fn op_has_attr_static<'gc, M: Machine<'gc>>(
|
|||||||
let key = reader.read_string_id();
|
let key = reader.read_string_id();
|
||||||
let current = m.force_and_retry::<StrictValue>(reader, mc)?;
|
let current = m.force_and_retry::<StrictValue>(reader, mc)?;
|
||||||
|
|
||||||
m.push(Value::new_inline(
|
m.push(Value::new(
|
||||||
current
|
current
|
||||||
.as_gc::<AttrSet>()
|
.downcast::<AttrSet>()
|
||||||
.and_then(|attrs| attrs.lookup(key))
|
.and_then(|attrs| attrs.lookup(key))
|
||||||
.is_some(),
|
.is_some(),
|
||||||
));
|
));
|
||||||
@@ -289,9 +310,9 @@ pub(crate) fn op_has_attr_dynamic<'gc, M: MachineExt<'gc>>(
|
|||||||
Err(got) => return m.finish_type_err(NixType::String, got),
|
Err(got) => return m.finish_type_err(NixType::String, got),
|
||||||
};
|
};
|
||||||
|
|
||||||
m.push(Value::new_inline(
|
m.push(Value::new(
|
||||||
current
|
current
|
||||||
.as_gc::<AttrSet>()
|
.downcast::<AttrSet>()
|
||||||
.and_then(|attrs| attrs.lookup(key_sid))
|
.and_then(|attrs| attrs.lookup(key_sid))
|
||||||
.is_some(),
|
.is_some(),
|
||||||
));
|
));
|
||||||
@@ -304,7 +325,7 @@ pub(crate) fn op_has_attr_dynamic<'gc, M: MachineExt<'gc>>(
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn op_has_attr_resolve<'gc, M: Machine<'gc>>(m: &mut M) -> Step {
|
pub(crate) fn op_has_attr_resolve<'gc, M: Machine<'gc>>(m: &mut M) -> Step {
|
||||||
// If we reach here, has_attr check has failed, push false (AttrSet is already popped)
|
// If we reach here, has_attr check has failed, push false (AttrSet is already popped)
|
||||||
m.push(Value::new_inline(false));
|
m.push(Value::new(false));
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,7 +347,7 @@ pub(crate) fn op_make_list<'gc, M: Machine<'gc>>(
|
|||||||
inner: RefLock::new(items),
|
inner: RefLock::new(items),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
m.push(Value::new_gc(list));
|
m.push(Value::new(list));
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ use gc_arena::Mutation;
|
|||||||
use crate::{BytecodeReader, Step, VmRuntimeCtx};
|
use crate::{BytecodeReader, Step, VmRuntimeCtx};
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
#[expect(
|
||||||
|
clippy::cast_sign_loss,
|
||||||
|
reason = "jump target = pc + offset is a non-negative bytecode address by codegen invariant"
|
||||||
|
)]
|
||||||
pub(crate) fn op_jump_if_false<'gc, M: Machine<'gc>>(
|
pub(crate) fn op_jump_if_false<'gc, M: Machine<'gc>>(
|
||||||
m: &mut M,
|
m: &mut M,
|
||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
@@ -12,13 +16,17 @@ pub(crate) fn op_jump_if_false<'gc, M: Machine<'gc>>(
|
|||||||
) -> Step {
|
) -> Step {
|
||||||
let offset = reader.read_i32();
|
let offset = reader.read_i32();
|
||||||
let cond = m.force_and_retry::<StrictValue>(reader, mc)?;
|
let cond = m.force_and_retry::<StrictValue>(reader, mc)?;
|
||||||
if cond.as_inline::<bool>() == Some(false) {
|
if cond.downcast::<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);
|
||||||
}
|
}
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
#[expect(
|
||||||
|
clippy::cast_sign_loss,
|
||||||
|
reason = "jump target = pc + offset is a non-negative bytecode address by codegen invariant"
|
||||||
|
)]
|
||||||
pub(crate) fn op_jump_if_true<'gc, M: Machine<'gc>>(
|
pub(crate) fn op_jump_if_true<'gc, M: Machine<'gc>>(
|
||||||
m: &mut M,
|
m: &mut M,
|
||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
@@ -26,13 +34,17 @@ pub(crate) fn op_jump_if_true<'gc, M: Machine<'gc>>(
|
|||||||
) -> Step {
|
) -> Step {
|
||||||
let offset = reader.read_i32();
|
let offset = reader.read_i32();
|
||||||
let cond = m.force_and_retry::<StrictValue>(reader, mc)?;
|
let cond = m.force_and_retry::<StrictValue>(reader, mc)?;
|
||||||
if cond.as_inline::<bool>() == Some(true) {
|
if cond.downcast::<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);
|
||||||
}
|
}
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
#[expect(
|
||||||
|
clippy::cast_sign_loss,
|
||||||
|
reason = "jump target = pc + offset is a non-negative bytecode address by codegen invariant"
|
||||||
|
)]
|
||||||
pub(crate) fn op_jump<'gc, M: Machine<'gc>>(_m: &mut M, reader: &mut BytecodeReader<'_>) -> Step {
|
pub(crate) fn op_jump<'gc, M: Machine<'gc>>(_m: &mut M, reader: &mut BytecodeReader<'_>) -> Step {
|
||||||
let offset = reader.read_i32();
|
let offset = reader.read_i32();
|
||||||
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
|
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ pub(crate) fn op_push_smi<'gc, M: Machine<'gc>>(
|
|||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
let val = reader.read_i32();
|
let val = reader.read_i32();
|
||||||
m.push(Value::new_inline(val));
|
m.push(Value::new(val));
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ pub(crate) fn op_push_bigint<'gc, M: Machine<'gc>>(
|
|||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
let val = reader.read_i64();
|
let val = reader.read_i64();
|
||||||
m.push(Value::new_gc(Gc::new(mc, val)));
|
m.push(Value::new(Gc::new(mc, val)));
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ pub(crate) fn op_push_float<'gc, M: Machine<'gc>>(
|
|||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
let val = reader.read_f64();
|
let val = reader.read_f64();
|
||||||
m.push(Value::new_float(val));
|
m.push(Value::new(val));
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,24 +40,24 @@ pub(crate) fn op_push_string<'gc, M: Machine<'gc>>(
|
|||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
let sid = reader.read_string_id();
|
let sid = reader.read_string_id();
|
||||||
m.push(Value::new_inline(sid));
|
m.push(Value::new(sid));
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn op_push_null<'gc, M: Machine<'gc>>(m: &mut M) -> Step {
|
pub(crate) fn op_push_null<'gc, M: Machine<'gc>>(m: &mut M) -> Step {
|
||||||
m.push(Value::new_inline(crate::Null));
|
m.push(Value::new(crate::Null));
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn op_push_true<'gc, M: Machine<'gc>>(m: &mut M) -> Step {
|
pub(crate) fn op_push_true<'gc, M: Machine<'gc>>(m: &mut M) -> Step {
|
||||||
m.push(Value::new_inline(true));
|
m.push(Value::new(true));
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn op_push_false<'gc, M: Machine<'gc>>(m: &mut M) -> Step {
|
pub(crate) fn op_push_false<'gc, M: Machine<'gc>>(m: &mut M) -> Step {
|
||||||
m.push(Value::new_inline(false));
|
m.push(Value::new(false));
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use fix_bytecode::PrimOpPhase;
|
use fix_bytecode::Continuation;
|
||||||
use fix_error::Error;
|
use fix_error::Error;
|
||||||
use fix_lang::{BUILTINS, BuiltinId, StringId};
|
use fix_lang::{BuiltinId, StringId};
|
||||||
use fix_runtime::{
|
use fix_runtime::{
|
||||||
AttrSet, Machine, MachineExt, NixString, Path, StrictValue, StringContext, canon_path_str,
|
AttrSet, Machine, MachineExt, NixString, Path, StrictValue, StringContext, canon_path_str,
|
||||||
};
|
};
|
||||||
@@ -11,21 +11,25 @@ use crate::{BytecodeReader, PrimOp, Step, Value, VmRuntimeCtx, VmRuntimeCtxExt};
|
|||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn op_load_builtins<'gc, M: Machine<'gc>>(m: &mut M) -> Step {
|
pub(crate) fn op_load_builtins<'gc, M: Machine<'gc>>(m: &mut M) -> Step {
|
||||||
m.push(m.builtins());
|
m.push(m.builtins().into());
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
#[expect(
|
||||||
|
clippy::panic,
|
||||||
|
reason = "codegen only emits LoadBuiltin with valid BuiltinId bytes; an unknown id is a codegen/bytecode-corruption bug"
|
||||||
|
)]
|
||||||
pub(crate) fn op_load_builtin<'gc, M: Machine<'gc>>(
|
pub(crate) fn op_load_builtin<'gc, M: Machine<'gc>>(
|
||||||
m: &mut M,
|
m: &mut M,
|
||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
let Ok(id) = BuiltinId::try_from(reader.read_u8())
|
let Ok(id) = BuiltinId::try_from(reader.read_u8())
|
||||||
.map_err(|err| panic!("unknown builtin id: {}", err.number));
|
.map_err(|err| panic!("unknown builtin id: {}", err.number));
|
||||||
m.push(Value::new_inline(PrimOp {
|
m.push(Value::new(PrimOp {
|
||||||
id,
|
id,
|
||||||
arity: BUILTINS[id as usize].1,
|
arity: id.info().arity,
|
||||||
dispatch_ip: PrimOpPhase::entry_for_builtin(id).ip(),
|
dispatch_ip: Continuation::entry_for_builtin(id).ip(),
|
||||||
}));
|
}));
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
@@ -49,7 +53,7 @@ pub(crate) fn op_load_scoped_binding<'gc, M: Machine<'gc>>(
|
|||||||
let slot_id = reader.read_u32();
|
let slot_id = reader.read_u32();
|
||||||
let name = reader.read_string_id();
|
let name = reader.read_string_id();
|
||||||
let scope = m.scope_slot(slot_id);
|
let scope = m.scope_slot(slot_id);
|
||||||
let Some(attrs) = scope.as_gc::<AttrSet>() else {
|
let Some(attrs) = scope.downcast::<AttrSet>() else {
|
||||||
return m.finish_err(Error::eval_error("internal: scope slot is not an attrset"));
|
return m.finish_err(Error::eval_error("internal: scope slot is not an attrset"));
|
||||||
};
|
};
|
||||||
match attrs.lookup(name) {
|
match attrs.lookup(name) {
|
||||||
@@ -73,10 +77,10 @@ pub(crate) fn op_coerce_to_string<'gc, M: Machine<'gc>>(
|
|||||||
let val = m.force_and_retry::<StrictValue>(reader, mc)?;
|
let val = m.force_and_retry::<StrictValue>(reader, mc)?;
|
||||||
if val.is::<StringId>() || val.is::<NixString>() {
|
if val.is::<StringId>() || val.is::<NixString>() {
|
||||||
m.push(val.relax());
|
m.push(val.relax());
|
||||||
} else if let Some(p) = val.as_inline::<Path>() {
|
} else if let Some(p) = val.downcast::<Path>() {
|
||||||
// Coercing a path to a string yields the canonical path text.
|
// Coercing a path to a string yields the canonical path text.
|
||||||
// FIXME: copy to store
|
// FIXME: copy to store
|
||||||
m.push(Value::new_inline(p.0));
|
m.push(Value::new(p.0));
|
||||||
} else {
|
} else {
|
||||||
todo!("coerce other types to string: {:?}", val.ty());
|
todo!("coerce other types to string: {:?}", val.ty());
|
||||||
}
|
}
|
||||||
@@ -122,10 +126,10 @@ pub(crate) fn op_concat_strings<'gc, M: Machine<'gc>>(
|
|||||||
|
|
||||||
if merged.is_empty() {
|
if merged.is_empty() {
|
||||||
let sid = ctx.intern_string(result);
|
let sid = ctx.intern_string(result);
|
||||||
m.push(Value::new_inline(sid));
|
m.push(Value::new(sid));
|
||||||
} else {
|
} else {
|
||||||
let ns = gc_arena::Gc::new(mc, NixString::with_context(result, merged));
|
let ns = gc_arena::Gc::new(mc, NixString::with_context(result, merged));
|
||||||
m.push(Value::new_gc(ns));
|
m.push(Value::new(ns));
|
||||||
}
|
}
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
@@ -140,8 +144,8 @@ pub(crate) fn op_resolve_path<'gc, M: MachineExt<'gc>>(
|
|||||||
let path_val = m.force_and_retry::<StrictValue>(reader, mc)?;
|
let path_val = m.force_and_retry::<StrictValue>(reader, mc)?;
|
||||||
let dir_id = reader.read_string_id();
|
let dir_id = reader.read_string_id();
|
||||||
// Already a path: keep as-is. ResolvePath is idempotent on paths.
|
// Already a path: keep as-is. ResolvePath is idempotent on paths.
|
||||||
if let Some(p) = path_val.as_inline::<Path>() {
|
if let Some(p) = path_val.downcast::<Path>() {
|
||||||
m.push(Value::new_inline(p));
|
m.push(Value::new(p));
|
||||||
return Step::Continue(());
|
return Step::Continue(());
|
||||||
}
|
}
|
||||||
let path = match ctx.get_string(path_val) {
|
let path = match ctx.get_string(path_val) {
|
||||||
@@ -158,7 +162,7 @@ pub(crate) fn op_resolve_path<'gc, M: MachineExt<'gc>>(
|
|||||||
Err(e) => return m.finish_err(e),
|
Err(e) => return m.finish_err(e),
|
||||||
};
|
};
|
||||||
let sid = ctx.intern_string(resolved);
|
let sid = ctx.intern_string(resolved);
|
||||||
m.push(Value::new_inline(Path(sid)));
|
m.push(Value::new(Path(sid)));
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ use fix_runtime::Machine;
|
|||||||
use crate::{BytecodeReader, Mutation, Step, Value};
|
use crate::{BytecodeReader, Mutation, Step, Value};
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
#[expect(
|
||||||
|
clippy::indexing_slicing,
|
||||||
|
reason = "local slot index is produced by codegen and bounded by the frame's AllocLocals count"
|
||||||
|
)]
|
||||||
pub(crate) fn op_load_local<'gc, M: Machine<'gc>>(
|
pub(crate) fn op_load_local<'gc, M: Machine<'gc>>(
|
||||||
m: &mut M,
|
m: &mut M,
|
||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
@@ -13,6 +17,10 @@ pub(crate) fn op_load_local<'gc, M: Machine<'gc>>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
#[expect(
|
||||||
|
clippy::indexing_slicing,
|
||||||
|
reason = "local slot index is produced by codegen and bounded by the target frame's AllocLocals count"
|
||||||
|
)]
|
||||||
pub(crate) fn op_load_outer<'gc, M: Machine<'gc>>(
|
pub(crate) fn op_load_outer<'gc, M: Machine<'gc>>(
|
||||||
m: &mut M,
|
m: &mut M,
|
||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
@@ -30,6 +38,10 @@ pub(crate) fn op_load_outer<'gc, M: Machine<'gc>>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
#[expect(
|
||||||
|
clippy::indexing_slicing,
|
||||||
|
reason = "local slot index is produced by codegen and bounded by the frame's AllocLocals count"
|
||||||
|
)]
|
||||||
pub(crate) fn op_store_local<'gc, M: Machine<'gc>>(
|
pub(crate) fn op_store_local<'gc, M: Machine<'gc>>(
|
||||||
m: &mut M,
|
m: &mut M,
|
||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
|||||||
@@ -6,14 +6,21 @@ use smallvec::SmallVec;
|
|||||||
use crate::{Break, BytecodeReader, CallFrame, Step, VmRuntimeCtx};
|
use crate::{Break, BytecodeReader, CallFrame, Step, VmRuntimeCtx};
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
#[expect(
|
||||||
|
clippy::indexing_slicing,
|
||||||
|
clippy::cast_sign_loss,
|
||||||
|
reason = "counter is a non-negative with-scope index in 0..n, staying within the namespaces vec of length n"
|
||||||
|
)]
|
||||||
pub(crate) fn op_lookup_with<'gc, M: Machine<'gc>>(
|
pub(crate) fn op_lookup_with<'gc, M: Machine<'gc>>(
|
||||||
m: &mut M,
|
m: &mut M,
|
||||||
ctx: &mut impl VmRuntimeCtx,
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
mc: &gc_arena::Mutation<'gc>,
|
mc: &gc_arena::Mutation<'gc>,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
#[allow(clippy::unwrap_used)]
|
let counter = m
|
||||||
let counter = m.peek_forced(0).as_inline::<i32>().unwrap();
|
.peek_forced(0)
|
||||||
|
.downcast::<i32>()
|
||||||
|
.expect("stack slot must be an integer");
|
||||||
|
|
||||||
let name = reader.read_string_id();
|
let name = reader.read_string_id();
|
||||||
let n = reader.read_u8();
|
let n = reader.read_u8();
|
||||||
@@ -34,6 +41,7 @@ pub(crate) fn op_lookup_with<'gc, M: Machine<'gc>>(
|
|||||||
thunk: Some(thunk),
|
thunk: Some(thunk),
|
||||||
pc: resume_pc,
|
pc: resume_pc,
|
||||||
env: m.env(),
|
env: m.env(),
|
||||||
|
depth: None,
|
||||||
});
|
});
|
||||||
m.set_env(env);
|
m.set_env(env);
|
||||||
reader.set_pc(ip);
|
reader.set_pc(ip);
|
||||||
@@ -45,6 +53,7 @@ pub(crate) fn op_lookup_with<'gc, M: Machine<'gc>>(
|
|||||||
thunk: Some(thunk),
|
thunk: Some(thunk),
|
||||||
pc: resume_pc,
|
pc: resume_pc,
|
||||||
env: m.env(),
|
env: m.env(),
|
||||||
|
depth: None,
|
||||||
});
|
});
|
||||||
m.push(func);
|
m.push(func);
|
||||||
return m.call(reader, mc, arg, resume_pc);
|
return m.call(reader, mc, arg, resume_pc);
|
||||||
@@ -57,7 +66,7 @@ pub(crate) fn op_lookup_with<'gc, M: Machine<'gc>>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Some(val) = namespace
|
if let Some(val) = namespace
|
||||||
.as_gc::<AttrSet>()
|
.downcast::<AttrSet>()
|
||||||
.and_then(|attrs| attrs.lookup(name))
|
.and_then(|attrs| attrs.lookup(name))
|
||||||
{
|
{
|
||||||
m.replace(0, val);
|
m.replace(0, val);
|
||||||
@@ -67,7 +76,7 @@ pub(crate) fn op_lookup_with<'gc, M: Machine<'gc>>(
|
|||||||
Symbol::from(ctx.resolve_string(name))
|
Symbol::from(ctx.resolve_string(name))
|
||||||
)));
|
)));
|
||||||
} else {
|
} else {
|
||||||
m.replace(0, Value::new_inline(counter + 1));
|
m.replace(0, Value::new(counter + 1));
|
||||||
reader.set_pc(resume_pc);
|
reader.set_pc(resume_pc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+46
-36
@@ -1,5 +1,7 @@
|
|||||||
#![warn(clippy::unwrap_used)]
|
#![cfg_attr(
|
||||||
#![cfg_attr(feature = "tailcall", expect(incomplete_features))]
|
feature = "tailcall",
|
||||||
|
expect(incomplete_features, reason = "for testing purpose only")
|
||||||
|
)]
|
||||||
#![cfg_attr(
|
#![cfg_attr(
|
||||||
feature = "tailcall",
|
feature = "tailcall",
|
||||||
feature(explicit_tail_calls, rust_preserve_none_cc)
|
feature(explicit_tail_calls, rust_preserve_none_cc)
|
||||||
@@ -7,9 +9,9 @@
|
|||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use fix_bytecode::{InstructionPtr, PrimOpPhase};
|
use fix_bytecode::{Continuation, InstructionPtr};
|
||||||
use fix_error::{Error, Result, Source};
|
use fix_error::{Error, Result, Source};
|
||||||
use fix_lang::{BUILTINS, BuiltinId, StringId};
|
use fix_lang::{BuiltinId, StringId};
|
||||||
use gc_arena::metrics::Pacing;
|
use gc_arena::metrics::Pacing;
|
||||||
use gc_arena::{Arena, Collect, Gc, Mutation, RefLock, Rootable};
|
use gc_arena::{Arena, Collect, Gc, Mutation, RefLock, Rootable};
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
@@ -18,8 +20,12 @@ use smallvec::SmallVec;
|
|||||||
#[cfg(feature = "tailcall")]
|
#[cfg(feature = "tailcall")]
|
||||||
mod dispatch_tailcall;
|
mod dispatch_tailcall;
|
||||||
pub use fix_runtime::*;
|
pub use fix_runtime::*;
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[path = "macro_support.rs"]
|
||||||
|
pub mod __macro_support;
|
||||||
mod instructions;
|
mod instructions;
|
||||||
mod primops;
|
mod primops;
|
||||||
|
extern crate self as fix_vm;
|
||||||
|
|
||||||
type VmResult<T> = std::result::Result<T, VmError>;
|
type VmResult<T> = std::result::Result<T, VmError>;
|
||||||
|
|
||||||
@@ -29,7 +35,10 @@ pub struct Vm<'gc> {
|
|||||||
stack: Vec<Value<'gc>>,
|
stack: Vec<Value<'gc>>,
|
||||||
call_stack: Vec<CallFrame<'gc>>,
|
call_stack: Vec<CallFrame<'gc>>,
|
||||||
call_depth: usize,
|
call_depth: usize,
|
||||||
#[allow(dead_code)]
|
#[expect(
|
||||||
|
dead_code,
|
||||||
|
reason = "error_context is reserved for tryEval catch-frame tracking, not yet wired up"
|
||||||
|
)]
|
||||||
#[collect(require_static)]
|
#[collect(require_static)]
|
||||||
error_context: Vec<ErrorFrame>,
|
error_context: Vec<ErrorFrame>,
|
||||||
|
|
||||||
@@ -38,7 +47,7 @@ pub struct Vm<'gc> {
|
|||||||
import_cache: HashMap<PathBuf, Value<'gc>>,
|
import_cache: HashMap<PathBuf, Value<'gc>>,
|
||||||
scope_slots: Vec<Value<'gc>>,
|
scope_slots: Vec<Value<'gc>>,
|
||||||
|
|
||||||
builtins: Value<'gc>,
|
builtins: Gc<'gc, AttrSet<'gc>>,
|
||||||
empty_list: Value<'gc>,
|
empty_list: Value<'gc>,
|
||||||
empty_attrs: Value<'gc>,
|
empty_attrs: Value<'gc>,
|
||||||
|
|
||||||
@@ -53,17 +62,16 @@ pub struct Vm<'gc> {
|
|||||||
functor_sym: StringId,
|
functor_sym: StringId,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_builtins<'gc>(mc: &Mutation<'gc>, ctx: &mut impl VmRuntimeCtx) -> Value<'gc> {
|
fn init_builtins<'gc>(mc: &Mutation<'gc>, ctx: &mut impl VmRuntimeCtx) -> Gc<'gc, AttrSet<'gc>> {
|
||||||
let mut entries = SmallVec::with_capacity(BUILTINS.len());
|
let mut entries = SmallVec::with_capacity(BuiltinId::TOTAL);
|
||||||
|
|
||||||
for (idx, &(name, arity)) in BUILTINS.iter().enumerate() {
|
for id in BuiltinId::ALL {
|
||||||
let id = BuiltinId::try_from(idx as u8).expect("infallible");
|
let arity = id.info().arity;
|
||||||
let name = name.strip_prefix("__").unwrap_or(name);
|
let name = ctx.intern_string(id.info().name);
|
||||||
let name = ctx.intern_string(name);
|
let dispatch_ip = Continuation::entry_for_builtin(id).ip();
|
||||||
let dispatch_ip = PrimOpPhase::entry_for_builtin(id).ip();
|
|
||||||
entries.push((
|
entries.push((
|
||||||
name,
|
name,
|
||||||
Value::new_inline(PrimOp {
|
Value::new(PrimOp {
|
||||||
id,
|
id,
|
||||||
arity,
|
arity,
|
||||||
dispatch_ip,
|
dispatch_ip,
|
||||||
@@ -74,21 +82,15 @@ fn init_builtins<'gc>(mc: &Mutation<'gc>, ctx: &mut impl VmRuntimeCtx) -> Value<
|
|||||||
let consts = [
|
let consts = [
|
||||||
(
|
(
|
||||||
"__currentSystem",
|
"__currentSystem",
|
||||||
Value::new_inline(ctx.intern_string("x86_64-linux")),
|
Value::new(ctx.intern_string("x86_64-linux")),
|
||||||
),
|
),
|
||||||
("__langVersion", Value::new_inline(6i32)),
|
("__langVersion", Value::new(6i32)),
|
||||||
(
|
("__nixVersion", Value::new(ctx.intern_string("2.24.0"))),
|
||||||
"__nixVersion",
|
("__storeDir", Value::new(ctx.intern_string("/nix/store"))),
|
||||||
Value::new_inline(ctx.intern_string("2.24.0")),
|
("__nixPath", Value::new(Gc::new(mc, List::default()))),
|
||||||
),
|
("null", Value::new(Null)),
|
||||||
(
|
("true", Value::new(true)),
|
||||||
"__storeDir",
|
("false", Value::new(false)),
|
||||||
Value::new_inline(ctx.intern_string("/nix/store")),
|
|
||||||
),
|
|
||||||
("__nixPath", Value::new_gc(Gc::new(mc, List::default()))),
|
|
||||||
("null", Value::new_inline(Null)),
|
|
||||||
("true", Value::new_inline(true)),
|
|
||||||
("false", Value::new_inline(false)),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
for (name, val) in consts {
|
for (name, val) in consts {
|
||||||
@@ -99,15 +101,15 @@ fn init_builtins<'gc>(mc: &Mutation<'gc>, ctx: &mut impl VmRuntimeCtx) -> Value<
|
|||||||
|
|
||||||
let self_ref_thunk = Gc::new(mc, RefLock::new(ThunkState::Blackhole));
|
let self_ref_thunk = Gc::new(mc, RefLock::new(ThunkState::Blackhole));
|
||||||
let sym = ctx.intern_string("builtins");
|
let sym = ctx.intern_string("builtins");
|
||||||
entries.push((sym, Value::new_gc(self_ref_thunk)));
|
entries.push((sym, Value::new(self_ref_thunk)));
|
||||||
|
|
||||||
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));
|
||||||
let builtins_value = Value::new_gc(builtins_set);
|
let builtins_value = Value::new(builtins_set);
|
||||||
*self_ref_thunk.borrow_mut(mc) =
|
*self_ref_thunk.borrow_mut(mc) =
|
||||||
ThunkState::Evaluated(builtins_value.restrict().expect("builtins is not a thunk"));
|
ThunkState::Evaluated(builtins_value.restrict().expect("builtins is not a thunk"));
|
||||||
builtins_value
|
builtins_set
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'gc> Vm<'gc> {
|
impl<'gc> Vm<'gc> {
|
||||||
@@ -125,8 +127,8 @@ impl<'gc> Vm<'gc> {
|
|||||||
scope_slots: Vec::new(),
|
scope_slots: Vec::new(),
|
||||||
|
|
||||||
builtins,
|
builtins,
|
||||||
empty_list: Value::new_gc(Gc::new(mc, List::default())),
|
empty_list: Value::new(Gc::new(mc, List::default())),
|
||||||
empty_attrs: Value::new_gc(Gc::new(mc, AttrSet::default())),
|
empty_attrs: Value::new(Gc::new(mc, AttrSet::default())),
|
||||||
|
|
||||||
force_mode,
|
force_mode,
|
||||||
|
|
||||||
@@ -202,7 +204,7 @@ impl<'gc> Machine<'gc> for Vm<'gc> {
|
|||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
resume_pc: usize,
|
resume_pc: usize,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
let Some(thunk) = self.peek(depth).as_gc::<Thunk>() else {
|
let Some(thunk) = self.peek(depth).downcast::<Thunk>() else {
|
||||||
return Step::Continue(());
|
return Step::Continue(());
|
||||||
};
|
};
|
||||||
let mut state = thunk.borrow_mut(mc);
|
let mut state = thunk.borrow_mut(mc);
|
||||||
@@ -213,6 +215,7 @@ impl<'gc> Machine<'gc> for Vm<'gc> {
|
|||||||
thunk: Some(thunk),
|
thunk: Some(thunk),
|
||||||
pc: resume_pc,
|
pc: resume_pc,
|
||||||
env: self.env,
|
env: self.env,
|
||||||
|
depth: Some(depth),
|
||||||
});
|
});
|
||||||
self.env = env;
|
self.env = env;
|
||||||
reader.set_pc(ip);
|
reader.set_pc(ip);
|
||||||
@@ -227,6 +230,7 @@ impl<'gc> Machine<'gc> for Vm<'gc> {
|
|||||||
thunk: Some(thunk),
|
thunk: Some(thunk),
|
||||||
pc: resume_pc,
|
pc: resume_pc,
|
||||||
env: self.env,
|
env: self.env,
|
||||||
|
depth: Some(depth),
|
||||||
});
|
});
|
||||||
self.push(func);
|
self.push(func);
|
||||||
self.call(reader, mc, arg, resume_pc)
|
self.call(reader, mc, arg, resume_pc)
|
||||||
@@ -304,7 +308,7 @@ impl<'gc> Machine<'gc> for Vm<'gc> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn builtins(&self) -> Value<'gc> {
|
fn builtins(&self) -> Gc<'gc, AttrSet<'gc>> {
|
||||||
self.builtins
|
self.builtins
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -462,6 +466,10 @@ impl<'gc> Vm<'gc> {
|
|||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[cfg(not(feature = "tailcall"))]
|
#[cfg(not(feature = "tailcall"))]
|
||||||
|
#[expect(
|
||||||
|
clippy::unreachable,
|
||||||
|
reason = "Op::Illegal is a sentinel never emitted by codegen; reaching it is a codegen bug"
|
||||||
|
)]
|
||||||
fn execute_batch(
|
fn execute_batch(
|
||||||
&mut self,
|
&mut self,
|
||||||
bytecode: &[u8],
|
bytecode: &[u8],
|
||||||
@@ -476,6 +484,8 @@ impl<'gc> Vm<'gc> {
|
|||||||
let mut fuel = Self::DEFAULT_FUEL_AMOUNT;
|
let mut fuel = Self::DEFAULT_FUEL_AMOUNT;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
use crate::instructions::op_dispatch_cont;
|
||||||
|
|
||||||
if fuel == 0 {
|
if fuel == 0 {
|
||||||
return Action::Continue { pc: reader.pc() };
|
return Action::Continue { pc: reader.pc() };
|
||||||
}
|
}
|
||||||
@@ -502,7 +512,7 @@ impl<'gc> Vm<'gc> {
|
|||||||
MakePatternClosure => op_make_pattern_closure(self, &mut reader, mc),
|
MakePatternClosure => op_make_pattern_closure(self, &mut reader, mc),
|
||||||
|
|
||||||
Call => op_call(self, ctx, &mut reader, mc),
|
Call => op_call(self, ctx, &mut reader, mc),
|
||||||
DispatchPrimOp => op_dispatch_primop(self, ctx, &mut reader, mc),
|
DispatchCont => op_dispatch_cont(self, ctx, &mut reader, mc),
|
||||||
Return => op_return(self, ctx, &mut reader, mc),
|
Return => op_return(self, ctx, &mut reader, mc),
|
||||||
|
|
||||||
MakeAttrs => op_make_attrs(self, ctx, &mut reader, mc),
|
MakeAttrs => op_make_attrs(self, ctx, &mut reader, mc),
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
//! Single macro-facing path for the `#[primop]` support surface: the traits
|
||||||
|
//! generated code type-checks through (defined in `fix_runtime`, where the
|
||||||
|
//! impl sets are coherent) and the stub trio that keeps un-expanded primop
|
||||||
|
//! sources resolving in tooling.
|
||||||
|
|
||||||
|
use std::future::Future;
|
||||||
|
|
||||||
|
pub use fix_runtime::{CalleeReady, ForceTarget, UnwrapSlot};
|
||||||
|
use fix_runtime::{Slot, Value};
|
||||||
|
|
||||||
|
macro_rules! type_hint {
|
||||||
|
() => {
|
||||||
|
if false {
|
||||||
|
return async {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::panic, reason = "deliberately panic when the stub is called")]
|
||||||
|
pub fn force<T>(_: Slot<Value<'_>>) -> impl Future<Output = Slot<T>> {
|
||||||
|
type_hint!();
|
||||||
|
panic!("stub `force` called at runtime")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::panic, reason = "deliberately panic when the stub is called")]
|
||||||
|
pub fn call<T, A, R>(_: &T, _: A) -> impl Future<Output = R> {
|
||||||
|
type_hint!();
|
||||||
|
panic!("stub `call` called at runtime")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::panic, reason = "deliberately panic when the stub is called")]
|
||||||
|
pub fn spill<T>(_: T) -> Slot<T> {
|
||||||
|
panic!("stub `spill` called at runtime")
|
||||||
|
}
|
||||||
+136
-84
@@ -5,7 +5,7 @@
|
|||||||
//! See `fix-runtime/src/string_context.rs` for the
|
//! See `fix-runtime/src/string_context.rs` for the
|
||||||
//! `StringContextElem` type.
|
//! `StringContextElem` type.
|
||||||
|
|
||||||
use fix_bytecode::PrimOpPhase;
|
use fix_bytecode::Continuation;
|
||||||
use fix_error::Error;
|
use fix_error::Error;
|
||||||
use fix_lang::StringId;
|
use fix_lang::StringId;
|
||||||
use fix_runtime::{
|
use fix_runtime::{
|
||||||
@@ -22,11 +22,11 @@ pub fn has_context<'gc, M: Machine<'gc>>(
|
|||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
let val = m.force_and_retry::<StrictValue>(reader, mc)?;
|
let val = m.force_and_retry::<StrictValue>(reader, mc)?;
|
||||||
if !val.is::<StringId>() && val.as_gc::<NixString>().is_none() {
|
if !val.is::<StringId>() && val.downcast::<NixString>().is_none() {
|
||||||
return m.finish_type_err(NixType::String, val.ty());
|
return m.finish_type_err(NixType::String, val.ty());
|
||||||
}
|
}
|
||||||
let has_ctx = !ctx.get_string_context(val).is_empty();
|
let has_ctx = !ctx.get_string_context(val).is_empty();
|
||||||
m.return_from_primop(Value::new_inline(has_ctx), reader)
|
m.return_from_primop(Value::new(has_ctx), reader)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unsafe_discard_string_context<'gc, M: Machine<'gc>>(
|
pub fn unsafe_discard_string_context<'gc, M: Machine<'gc>>(
|
||||||
@@ -36,14 +36,14 @@ pub fn unsafe_discard_string_context<'gc, M: Machine<'gc>>(
|
|||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
let val = m.force_and_retry::<StrictValue>(reader, mc)?;
|
let val = m.force_and_retry::<StrictValue>(reader, mc)?;
|
||||||
if let Some(sid) = val.as_inline::<StringId>() {
|
if let Some(sid) = val.downcast::<StringId>() {
|
||||||
return m.return_from_primop(Value::new_inline(sid), reader);
|
return m.return_from_primop(Value::new(sid), reader);
|
||||||
}
|
}
|
||||||
let Some(ns) = val.as_gc::<NixString>() else {
|
let Some(ns) = val.downcast::<NixString>() else {
|
||||||
return m.finish_type_err(NixType::String, val.ty());
|
return m.finish_type_err(NixType::String, val.ty());
|
||||||
};
|
};
|
||||||
let sid = ctx.intern_string(ns.as_str());
|
let sid = ctx.intern_string(ns.as_str());
|
||||||
m.return_from_primop(Value::new_inline(sid), reader)
|
m.return_from_primop(Value::new(sid), reader)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unsafe_discard_output_dependency<'gc, M: Machine<'gc>>(
|
pub fn unsafe_discard_output_dependency<'gc, M: Machine<'gc>>(
|
||||||
@@ -53,15 +53,15 @@ pub fn unsafe_discard_output_dependency<'gc, M: Machine<'gc>>(
|
|||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
let val = m.force_and_retry::<StrictValue>(reader, mc)?;
|
let val = m.force_and_retry::<StrictValue>(reader, mc)?;
|
||||||
if let Some(sid) = val.as_inline::<StringId>() {
|
if let Some(sid) = val.downcast::<StringId>() {
|
||||||
return m.return_from_primop(Value::new_inline(sid), reader);
|
return m.return_from_primop(Value::new(sid), reader);
|
||||||
}
|
}
|
||||||
let Some(ns) = val.as_gc::<NixString>() else {
|
let Some(ns) = val.downcast::<NixString>() else {
|
||||||
return m.finish_type_err(NixType::String, val.ty());
|
return m.finish_type_err(NixType::String, val.ty());
|
||||||
};
|
};
|
||||||
if ns.context().is_empty() {
|
if ns.context().is_empty() {
|
||||||
let sid = ctx.intern_string(ns.as_str());
|
let sid = ctx.intern_string(ns.as_str());
|
||||||
return m.return_from_primop(Value::new_inline(sid), reader);
|
return m.return_from_primop(Value::new(sid), reader);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut new_ctx = StringContext::new();
|
let mut new_ctx = StringContext::new();
|
||||||
@@ -77,7 +77,7 @@ pub fn unsafe_discard_output_dependency<'gc, M: Machine<'gc>>(
|
|||||||
|
|
||||||
let s: Box<str> = ns.as_str().into();
|
let s: Box<str> = ns.as_str().into();
|
||||||
let new_ns = Gc::new(mc, NixString::with_context(s, new_ctx));
|
let new_ns = Gc::new(mc, NixString::with_context(s, new_ctx));
|
||||||
m.return_from_primop(Value::new_gc(new_ns), reader)
|
m.return_from_primop(Value::new(new_ns), reader)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_context<'gc, M: Machine<'gc>>(
|
pub fn get_context<'gc, M: Machine<'gc>>(
|
||||||
@@ -87,7 +87,7 @@ pub fn get_context<'gc, M: Machine<'gc>>(
|
|||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
let val = m.force_and_retry::<StrictValue>(reader, mc)?;
|
let val = m.force_and_retry::<StrictValue>(reader, mc)?;
|
||||||
if !val.is::<StringId>() && val.as_gc::<NixString>().is_none() {
|
if !val.is::<StringId>() && val.downcast::<NixString>().is_none() {
|
||||||
return m.finish_type_err(NixType::String, val.ty());
|
return m.finish_type_err(NixType::String, val.ty());
|
||||||
}
|
}
|
||||||
let elems = ctx.get_string_context(val);
|
let elems = ctx.get_string_context(val);
|
||||||
@@ -135,29 +135,29 @@ pub fn get_context<'gc, M: Machine<'gc>>(
|
|||||||
|
|
||||||
let mut sub: SmallVec<[(StringId, Value<'gc>); 4]> = SmallVec::new();
|
let mut sub: SmallVec<[(StringId, Value<'gc>); 4]> = SmallVec::new();
|
||||||
if info.all_outputs {
|
if info.all_outputs {
|
||||||
sub.push((ctx.intern_string("allOutputs"), Value::new_inline(true)));
|
sub.push((ctx.intern_string("allOutputs"), Value::new(true)));
|
||||||
}
|
}
|
||||||
if !info.outputs.is_empty() {
|
if !info.outputs.is_empty() {
|
||||||
let items: smallvec::SmallVec<[Value<'gc>; 4]> = info
|
let items: smallvec::SmallVec<[Value<'gc>; 4]> = info
|
||||||
.outputs
|
.outputs
|
||||||
.iter()
|
.iter()
|
||||||
.map(|o| Value::new_inline(ctx.intern_string(o)))
|
.map(|o| Value::new(ctx.intern_string(o)))
|
||||||
.collect();
|
.collect();
|
||||||
let list = VmList::new(mc, items);
|
let list = VmList::new(mc, items);
|
||||||
sub.push((ctx.intern_string("outputs"), Value::new_gc(list)));
|
sub.push((ctx.intern_string("outputs"), Value::new(list)));
|
||||||
}
|
}
|
||||||
if info.path {
|
if info.path {
|
||||||
sub.push((ctx.intern_string("path"), Value::new_inline(true)));
|
sub.push((ctx.intern_string("path"), Value::new(true)));
|
||||||
}
|
}
|
||||||
sub.sort_by_key(|(k, _)| *k);
|
sub.sort_by_key(|(k, _)| *k);
|
||||||
let sub_attrs = Gc::new(mc, AttrSet::from_sorted_unchecked(sub));
|
let sub_attrs = Gc::new(mc, AttrSet::from_sorted_unchecked(sub));
|
||||||
|
|
||||||
outer_entries.push((ctx.intern_string(&path), Value::new_gc(sub_attrs)));
|
outer_entries.push((ctx.intern_string(&path), Value::new(sub_attrs)));
|
||||||
}
|
}
|
||||||
outer_entries.sort_by_key(|(k, _)| *k);
|
outer_entries.sort_by_key(|(k, _)| *k);
|
||||||
|
|
||||||
let outer = Gc::new(mc, AttrSet::from_sorted_unchecked(outer_entries));
|
let outer = Gc::new(mc, AttrSet::from_sorted_unchecked(outer_entries));
|
||||||
m.return_from_primop(Value::new_gc(outer), reader)
|
m.return_from_primop(Value::new(outer), reader)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// appendContext :: String -> AttrSet -> String
|
/// appendContext :: String -> AttrSet -> String
|
||||||
@@ -192,24 +192,33 @@ pub fn append_context<'gc, M: Machine<'gc>>(
|
|||||||
let acc = Gc::new(mc, NixString::with_context("", initial_ctx));
|
let acc = Gc::new(mc, NixString::with_context("", initial_ctx));
|
||||||
|
|
||||||
m.push(str_val.relax());
|
m.push(str_val.relax());
|
||||||
m.push(Value::new_gc(attrs));
|
m.push(Value::new(attrs));
|
||||||
m.push(Value::new_inline(0i32));
|
m.push(Value::new(0i32));
|
||||||
m.push(Value::new_gc(acc));
|
m.push(Value::new(acc));
|
||||||
|
|
||||||
reader.set_pc(PrimOpPhase::AppendContextLoop.ip() as usize);
|
reader.set_pc(Continuation::PAppendContextLoop.ip() as usize);
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(
|
||||||
|
clippy::indexing_slicing,
|
||||||
|
clippy::cast_sign_loss,
|
||||||
|
reason = "idx is a non-negative loop counter guarded by `idx as usize >= attrs.entries.len()`, so it indexes attrs.entries in bounds"
|
||||||
|
)]
|
||||||
pub fn append_context_loop<'gc, M: Machine<'gc>>(
|
pub fn append_context_loop<'gc, M: Machine<'gc>>(
|
||||||
m: &mut M,
|
m: &mut M,
|
||||||
ctx: &mut impl VmRuntimeCtx,
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
#[allow(clippy::unwrap_used)]
|
let idx = m
|
||||||
let idx = m.peek(1).as_inline::<i32>().unwrap();
|
.peek(1)
|
||||||
#[allow(clippy::unwrap_used)]
|
.downcast::<i32>()
|
||||||
let attrs = m.peek_forced(2).as_gc::<AttrSet>().unwrap();
|
.expect("stack slot must be an integer");
|
||||||
|
let attrs = m
|
||||||
|
.peek_forced(2)
|
||||||
|
.downcast::<AttrSet>()
|
||||||
|
.expect("stack slot must be an attrset");
|
||||||
|
|
||||||
if idx as usize >= attrs.entries.len() {
|
if idx as usize >= attrs.entries.len() {
|
||||||
return append_context_finalize(m, ctx, reader, mc);
|
return append_context_finalize(m, ctx, reader, mc);
|
||||||
@@ -221,12 +230,17 @@ pub fn append_context_loop<'gc, M: Machine<'gc>>(
|
|||||||
0,
|
0,
|
||||||
reader,
|
reader,
|
||||||
mc,
|
mc,
|
||||||
PrimOpPhase::AppendContextEntryForced.ip() as usize,
|
Continuation::PAppendContextEntryForced.ip() as usize,
|
||||||
)?;
|
)?;
|
||||||
reader.set_pc(PrimOpPhase::AppendContextEntryForced.ip() as usize);
|
reader.set_pc(Continuation::PAppendContextEntryForced.ip() as usize);
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(
|
||||||
|
clippy::indexing_slicing,
|
||||||
|
clippy::cast_sign_loss,
|
||||||
|
reason = "idx is the same non-negative outer-loop counter, in range for outer.entries validated by append_context_loop"
|
||||||
|
)]
|
||||||
pub fn append_context_entry_forced<'gc, M: Machine<'gc>>(
|
pub fn append_context_entry_forced<'gc, M: Machine<'gc>>(
|
||||||
m: &mut M,
|
m: &mut M,
|
||||||
ctx: &mut impl VmRuntimeCtx,
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
@@ -238,14 +252,18 @@ pub fn append_context_entry_forced<'gc, M: Machine<'gc>>(
|
|||||||
// Evaluated value into the slot.
|
// Evaluated value into the slot.
|
||||||
m.force_slot(0, reader, mc)?;
|
m.force_slot(0, reader, mc)?;
|
||||||
let entry_val = m.peek_forced(0);
|
let entry_val = m.peek_forced(0);
|
||||||
let Some(entry_attrs) = entry_val.as_gc::<AttrSet>() else {
|
let Some(entry_attrs) = entry_val.downcast::<AttrSet>() else {
|
||||||
return m.finish_type_err(NixType::AttrSet, entry_val.ty());
|
return m.finish_type_err(NixType::AttrSet, entry_val.ty());
|
||||||
};
|
};
|
||||||
|
|
||||||
#[allow(clippy::unwrap_used)]
|
let idx = m
|
||||||
let idx = m.peek(2).as_inline::<i32>().unwrap();
|
.peek(2)
|
||||||
#[allow(clippy::unwrap_used)]
|
.downcast::<i32>()
|
||||||
let outer = m.peek_forced(3).as_gc::<AttrSet>().unwrap();
|
.expect("stack slot must be an integer");
|
||||||
|
let outer = m
|
||||||
|
.peek_forced(3)
|
||||||
|
.downcast::<AttrSet>()
|
||||||
|
.expect("stack slot must be an attrset");
|
||||||
let path_key = outer.entries[idx as usize].0;
|
let path_key = outer.entries[idx as usize].0;
|
||||||
let path_str_owned: Box<str> = ctx.resolve_string(path_key).into();
|
let path_str_owned: Box<str> = ctx.resolve_string(path_key).into();
|
||||||
if !path_str_owned.starts_with("/nix/store/") {
|
if !path_str_owned.starts_with("/nix/store/") {
|
||||||
@@ -262,12 +280,14 @@ pub fn append_context_entry_forced<'gc, M: Machine<'gc>>(
|
|||||||
let all_outputs_id = ctx.intern_string("allOutputs");
|
let all_outputs_id = ctx.intern_string("allOutputs");
|
||||||
let outputs_id = ctx.intern_string("outputs");
|
let outputs_id = ctx.intern_string("outputs");
|
||||||
|
|
||||||
#[allow(clippy::unwrap_used)]
|
let acc_gc = m
|
||||||
let acc_gc = m.peek(1).as_gc::<NixString>().unwrap();
|
.peek(1)
|
||||||
|
.downcast::<NixString>()
|
||||||
|
.expect("stack slot must be a string");
|
||||||
let mut new_acc: StringContext = acc_gc.context().iter().cloned().collect();
|
let mut new_acc: StringContext = acc_gc.context().iter().cloned().collect();
|
||||||
|
|
||||||
if let Some(v) = entry_attrs.lookup(path_id)
|
if let Some(v) = entry_attrs.lookup(path_id)
|
||||||
&& v.as_inline::<bool>() == Some(true)
|
&& v.downcast::<bool>() == Some(true)
|
||||||
{
|
{
|
||||||
new_acc.insert(StringContextElem::Opaque {
|
new_acc.insert(StringContextElem::Opaque {
|
||||||
path: path_str_owned.clone(),
|
path: path_str_owned.clone(),
|
||||||
@@ -275,7 +295,7 @@ pub fn append_context_entry_forced<'gc, M: Machine<'gc>>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(v) = entry_attrs.lookup(all_outputs_id)
|
if let Some(v) = entry_attrs.lookup(all_outputs_id)
|
||||||
&& v.as_inline::<bool>() == Some(true)
|
&& v.downcast::<bool>() == Some(true)
|
||||||
{
|
{
|
||||||
if !path_str_owned.ends_with(".drv") {
|
if !path_str_owned.ends_with(".drv") {
|
||||||
return m.finish_err(Error::eval_error(format!(
|
return m.finish_err(Error::eval_error(format!(
|
||||||
@@ -288,7 +308,7 @@ pub fn append_context_entry_forced<'gc, M: Machine<'gc>>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let new_acc_gc = Gc::new(mc, NixString::with_context("", new_acc));
|
let new_acc_gc = Gc::new(mc, NixString::with_context("", new_acc));
|
||||||
m.replace(1, Value::new_gc(new_acc_gc));
|
m.replace(1, Value::new(new_acc_gc));
|
||||||
|
|
||||||
if let Some(outputs_val) = entry_attrs.lookup(outputs_id) {
|
if let Some(outputs_val) = entry_attrs.lookup(outputs_id) {
|
||||||
m.replace(0, outputs_val);
|
m.replace(0, outputs_val);
|
||||||
@@ -296,17 +316,19 @@ pub fn append_context_entry_forced<'gc, M: Machine<'gc>>(
|
|||||||
0,
|
0,
|
||||||
reader,
|
reader,
|
||||||
mc,
|
mc,
|
||||||
PrimOpPhase::AppendContextOutputsForced.ip() as usize,
|
Continuation::PAppendContextOutputsForced.ip() as usize,
|
||||||
)?;
|
)?;
|
||||||
reader.set_pc(PrimOpPhase::AppendContextOutputsForced.ip() as usize);
|
reader.set_pc(Continuation::PAppendContextOutputsForced.ip() as usize);
|
||||||
return Step::Continue(());
|
return Step::Continue(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = m.pop();
|
m.drop_n(1);
|
||||||
#[allow(clippy::unwrap_used)]
|
let idx_back = m
|
||||||
let idx_back = m.peek(1).as_inline::<i32>().unwrap();
|
.peek(1)
|
||||||
m.replace(1, Value::new_inline(idx_back + 1));
|
.downcast::<i32>()
|
||||||
reader.set_pc(PrimOpPhase::AppendContextLoop.ip() as usize);
|
.expect("stack slot must be an integer");
|
||||||
|
m.replace(1, Value::new(idx_back + 1));
|
||||||
|
reader.set_pc(Continuation::PAppendContextLoop.ip() as usize);
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,44 +340,56 @@ pub fn append_context_outputs_forced<'gc, M: Machine<'gc>>(
|
|||||||
) -> Step {
|
) -> Step {
|
||||||
m.force_slot(0, reader, mc)?;
|
m.force_slot(0, reader, mc)?;
|
||||||
let list_val = m.peek_forced(0);
|
let list_val = m.peek_forced(0);
|
||||||
let Some(list) = list_val.as_gc::<VmList>() else {
|
let Some(list) = list_val.downcast::<VmList>() else {
|
||||||
return m.finish_type_err(NixType::List, list_val.ty());
|
return m.finish_type_err(NixType::List, list_val.ty());
|
||||||
};
|
};
|
||||||
if list.inner.borrow().is_empty() {
|
if list.inner.borrow().is_empty() {
|
||||||
// Stack: [strVal, attrs, idx, acc, list] -> drop list, bump idx.
|
// Stack: [strVal, attrs, idx, acc, list] -> drop list, bump idx.
|
||||||
let _ = m.pop();
|
m.drop_n(1);
|
||||||
#[allow(clippy::unwrap_used)]
|
let idx_back = m
|
||||||
let idx_back = m.peek(1).as_inline::<i32>().unwrap();
|
.peek(1)
|
||||||
m.replace(1, Value::new_inline(idx_back + 1));
|
.downcast::<i32>()
|
||||||
reader.set_pc(PrimOpPhase::AppendContextLoop.ip() as usize);
|
.expect("stack slot must be an integer");
|
||||||
|
m.replace(1, Value::new(idx_back + 1));
|
||||||
|
reader.set_pc(Continuation::PAppendContextLoop.ip() as usize);
|
||||||
return Step::Continue(());
|
return Step::Continue(());
|
||||||
}
|
}
|
||||||
|
|
||||||
m.push(Value::new_inline(0i32));
|
m.push(Value::new(0i32));
|
||||||
reader.set_pc(PrimOpPhase::AppendContextOutputElementLoop.ip() as usize);
|
reader.set_pc(Continuation::PAppendContextOutputElementLoop.ip() as usize);
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(
|
||||||
|
clippy::indexing_slicing,
|
||||||
|
clippy::cast_sign_loss,
|
||||||
|
reason = "oidx is a non-negative loop counter guarded by `oidx as usize >= len`, so it indexes the list in bounds"
|
||||||
|
)]
|
||||||
pub fn append_context_output_element_loop<'gc, M: Machine<'gc>>(
|
pub fn append_context_output_element_loop<'gc, M: Machine<'gc>>(
|
||||||
m: &mut M,
|
m: &mut M,
|
||||||
_ctx: &mut impl VmRuntimeCtx,
|
_ctx: &mut impl VmRuntimeCtx,
|
||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
#[allow(clippy::unwrap_used)]
|
let oidx = m
|
||||||
let oidx = m.peek(0).as_inline::<i32>().unwrap();
|
.peek(0)
|
||||||
#[allow(clippy::unwrap_used)]
|
.downcast::<i32>()
|
||||||
let list = m.peek_forced(1).as_gc::<VmList>().unwrap();
|
.expect("stack slot must be an integer");
|
||||||
|
let list = m
|
||||||
|
.peek_forced(1)
|
||||||
|
.downcast::<VmList>()
|
||||||
|
.expect("stack slot must be a list");
|
||||||
let len = list.inner.borrow().len();
|
let len = list.inner.borrow().len();
|
||||||
if oidx as usize >= len {
|
if oidx as usize >= len {
|
||||||
// Stack: [strVal, attrs, idx, acc, list, oidx] -> drop oidx & list,
|
// Stack: [strVal, attrs, idx, acc, list, oidx] -> drop oidx & list,
|
||||||
// bump idx in place.
|
// bump idx in place.
|
||||||
let _ = m.pop();
|
m.drop_n(2);
|
||||||
let _ = m.pop();
|
let idx_back = m
|
||||||
#[allow(clippy::unwrap_used)]
|
.peek(1)
|
||||||
let idx_back = m.peek(1).as_inline::<i32>().unwrap();
|
.downcast::<i32>()
|
||||||
m.replace(1, Value::new_inline(idx_back + 1));
|
.expect("stack slot must be an integer");
|
||||||
reader.set_pc(PrimOpPhase::AppendContextLoop.ip() as usize);
|
m.replace(1, Value::new(idx_back + 1));
|
||||||
|
reader.set_pc(Continuation::PAppendContextLoop.ip() as usize);
|
||||||
return Step::Continue(());
|
return Step::Continue(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,12 +399,17 @@ pub fn append_context_output_element_loop<'gc, M: Machine<'gc>>(
|
|||||||
0,
|
0,
|
||||||
reader,
|
reader,
|
||||||
mc,
|
mc,
|
||||||
PrimOpPhase::AppendContextOutputElementForced.ip() as usize,
|
Continuation::PAppendContextOutputElementForced.ip() as usize,
|
||||||
)?;
|
)?;
|
||||||
reader.set_pc(PrimOpPhase::AppendContextOutputElementForced.ip() as usize);
|
reader.set_pc(Continuation::PAppendContextOutputElementForced.ip() as usize);
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(
|
||||||
|
clippy::indexing_slicing,
|
||||||
|
clippy::cast_sign_loss,
|
||||||
|
reason = "idx is the non-negative outer-loop counter, in range for outer.entries validated by append_context_loop"
|
||||||
|
)]
|
||||||
pub fn append_context_output_element_forced<'gc, M: Machine<'gc>>(
|
pub fn append_context_output_element_forced<'gc, M: Machine<'gc>>(
|
||||||
m: &mut M,
|
m: &mut M,
|
||||||
ctx: &mut impl VmRuntimeCtx,
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
@@ -384,10 +423,14 @@ pub fn append_context_output_element_forced<'gc, M: Machine<'gc>>(
|
|||||||
};
|
};
|
||||||
let output_name: Box<str> = output_name.into();
|
let output_name: Box<str> = output_name.into();
|
||||||
|
|
||||||
#[allow(clippy::unwrap_used)]
|
let idx = m
|
||||||
let idx = m.peek(4).as_inline::<i32>().unwrap();
|
.peek(4)
|
||||||
#[allow(clippy::unwrap_used)]
|
.downcast::<i32>()
|
||||||
let outer = m.peek_forced(5).as_gc::<AttrSet>().unwrap();
|
.expect("stack slot must be an integer");
|
||||||
|
let outer = m
|
||||||
|
.peek_forced(5)
|
||||||
|
.downcast::<AttrSet>()
|
||||||
|
.expect("stack slot must be an attrset");
|
||||||
let path_key = outer.entries[idx as usize].0;
|
let path_key = outer.entries[idx as usize].0;
|
||||||
let path_str: Box<str> = ctx.resolve_string(path_key).into();
|
let path_str: Box<str> = ctx.resolve_string(path_key).into();
|
||||||
if !path_str.ends_with(".drv") {
|
if !path_str.ends_with(".drv") {
|
||||||
@@ -396,26 +439,34 @@ pub fn append_context_output_element_forced<'gc, M: Machine<'gc>>(
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::unwrap_used)]
|
let acc_gc = m
|
||||||
let acc_gc = m.peek(3).as_gc::<NixString>().unwrap();
|
.peek(3)
|
||||||
|
.downcast::<NixString>()
|
||||||
|
.expect("stack slot must be a string");
|
||||||
let mut new_acc: StringContext = acc_gc.context().iter().cloned().collect();
|
let mut new_acc: StringContext = acc_gc.context().iter().cloned().collect();
|
||||||
new_acc.insert(StringContextElem::Built {
|
new_acc.insert(StringContextElem::Built {
|
||||||
drv_path: path_str,
|
drv_path: path_str,
|
||||||
output: output_name,
|
output: output_name,
|
||||||
});
|
});
|
||||||
let new_acc_gc = Gc::new(mc, NixString::with_context("", new_acc));
|
let new_acc_gc = Gc::new(mc, NixString::with_context("", new_acc));
|
||||||
m.replace(3, Value::new_gc(new_acc_gc));
|
m.replace(3, Value::new(new_acc_gc));
|
||||||
|
|
||||||
// Stack: [strVal, attrs, idx, acc, list, oidx, outElem] -> drop outElem,
|
// Stack: [strVal, attrs, idx, acc, list, oidx, outElem] -> drop outElem,
|
||||||
// bump oidx in place.
|
// bump oidx in place.
|
||||||
let _ = m.pop();
|
m.drop_n(1);
|
||||||
#[allow(clippy::unwrap_used)]
|
let oidx = m
|
||||||
let oidx = m.peek(0).as_inline::<i32>().unwrap();
|
.peek(0)
|
||||||
m.replace(0, Value::new_inline(oidx + 1));
|
.downcast::<i32>()
|
||||||
reader.set_pc(PrimOpPhase::AppendContextOutputElementLoop.ip() as usize);
|
.expect("stack slot must be an integer");
|
||||||
|
m.replace(0, Value::new(oidx + 1));
|
||||||
|
reader.set_pc(Continuation::PAppendContextOutputElementLoop.ip() as usize);
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(
|
||||||
|
clippy::panic,
|
||||||
|
reason = "strVal was forced to WHNF at appendContext entry, so restrict() can never observe a thunk here"
|
||||||
|
)]
|
||||||
fn append_context_finalize<'gc, M: Machine<'gc>>(
|
fn append_context_finalize<'gc, M: Machine<'gc>>(
|
||||||
m: &mut M,
|
m: &mut M,
|
||||||
ctx: &mut impl VmRuntimeCtx,
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
@@ -423,10 +474,11 @@ fn append_context_finalize<'gc, M: Machine<'gc>>(
|
|||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
// Stack: [strVal, attrs, idx, acc]
|
// Stack: [strVal, attrs, idx, acc]
|
||||||
#[allow(clippy::unwrap_used)]
|
let acc_gc = m
|
||||||
let acc_gc = m.pop().as_gc::<NixString>().unwrap();
|
.pop()
|
||||||
let _ = m.pop(); // idx
|
.downcast::<NixString>()
|
||||||
let _ = m.pop(); // attrs
|
.expect("stack slot must be a string");
|
||||||
|
m.drop_n(2);
|
||||||
let str_val_raw = m.pop();
|
let str_val_raw = m.pop();
|
||||||
|
|
||||||
// The strVal was already forced at entry; restrict() is infallible here.
|
// The strVal was already forced at entry; restrict() is infallible here.
|
||||||
@@ -438,10 +490,10 @@ fn append_context_finalize<'gc, M: Machine<'gc>>(
|
|||||||
let context: StringContext = acc_gc.context().iter().cloned().collect();
|
let context: StringContext = acc_gc.context().iter().cloned().collect();
|
||||||
let result = if context.is_empty() {
|
let result = if context.is_empty() {
|
||||||
let sid = ctx.intern_string(s_str);
|
let sid = ctx.intern_string(s_str);
|
||||||
Value::new_inline(sid)
|
Value::new(sid)
|
||||||
} else {
|
} else {
|
||||||
let ns = Gc::new(mc, NixString::with_context(s_str, context));
|
let ns = Gc::new(mc, NixString::with_context(s_str, context));
|
||||||
Value::new_gc(ns)
|
Value::new(ns)
|
||||||
};
|
};
|
||||||
m.return_from_primop(result, reader)
|
m.return_from_primop(result, reader)
|
||||||
}
|
}
|
||||||
|
|||||||
+111
-160
@@ -1,22 +1,89 @@
|
|||||||
use fix_bytecode::PrimOpPhase;
|
use fix_bytecode::Continuation;
|
||||||
use fix_error::Error;
|
use fix_error::{Error, Result};
|
||||||
|
use fix_macros::handler;
|
||||||
use fix_runtime::{
|
use fix_runtime::{
|
||||||
AttrSet, BytecodeReader, Closure, Env, List, Machine, MachineExt, Step, StrictValue, Value,
|
AttrSet, BytecodeReader, Closure, Env, List, Machine, MachineExt, Slot, Step, StrictValue,
|
||||||
VmRuntimeCtx, VmRuntimeCtxExt,
|
Value, VmRuntimeCtx, VmRuntimeCtxExt,
|
||||||
};
|
};
|
||||||
use gc_arena::{Gc, Mutation, RefLock};
|
use gc_arena::{Gc, Mutation, RefLock};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
pub fn seq<'gc, M: Machine<'gc>>(
|
use crate::primops::stubs::*;
|
||||||
m: &mut M,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
#[handler(name = PSeq)]
|
||||||
|
fn seq<'gc>(mc: &Mutation<'gc>, e1: Slot<Value<'gc>>, e2: Slot<Value<'gc>>) -> Result<Value<'gc>> {
|
||||||
|
let _e1: Slot<StrictValue<'gc>> = force(e1).await?;
|
||||||
|
Ok(e2.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[handler(name = PDeepSeq)]
|
||||||
|
fn deep_seq<'gc>(
|
||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
) -> Step {
|
e1: Slot<Value<'gc>>,
|
||||||
// stack: [e1, e2] - force e1, return e2
|
e2: Slot<Value<'gc>>,
|
||||||
m.force_slot(1, reader, mc)?;
|
) -> Result<Value<'gc>> {
|
||||||
let e2 = m.pop();
|
let e1: Slot<StrictValue<'gc>> = force(e1).await?;
|
||||||
let _ = m.pop();
|
if collect_children(e1.get()).is_empty() {
|
||||||
m.return_from_primop(e2, reader)
|
return Ok(e2.get());
|
||||||
|
}
|
||||||
|
let seen: Slot<List<'gc>> = spill(Gc::new(mc, List::default()));
|
||||||
|
let worklist: Slot<List<'gc>> = spill(List::new(mc, collect_children(e1.get())));
|
||||||
|
let count: Slot<i32> = spill(worklist.get().inner.borrow().len() as i32);
|
||||||
|
loop {
|
||||||
|
if count.get() == 0 {
|
||||||
|
return Ok(e2.get());
|
||||||
|
}
|
||||||
|
let item = worklist
|
||||||
|
.get()
|
||||||
|
.unlock(mc)
|
||||||
|
.borrow_mut()
|
||||||
|
.pop()
|
||||||
|
.expect("worklist is non-empty while count > 0");
|
||||||
|
count.set(count.get() - 1);
|
||||||
|
let item: Slot<StrictValue<'gc>> = force(item).await?;
|
||||||
|
let added = push_children(mc, seen.get(), worklist.get(), item);
|
||||||
|
count.set(count.get() + added);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_children<'gc>(val: StrictValue<'gc>) -> SmallVec<[Value<'gc>; 4]> {
|
||||||
|
if let Some(attrs) = val.downcast::<AttrSet<'gc>>() {
|
||||||
|
attrs.entries.iter().map(|&(_, v)| v).collect()
|
||||||
|
} else if let Some(list) = val.downcast::<List<'gc>>() {
|
||||||
|
list.inner.borrow().iter().copied().collect()
|
||||||
|
} else {
|
||||||
|
SmallVec::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_children<'gc>(
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
seen: Gc<'gc, List<'gc>>,
|
||||||
|
worklist: Gc<'gc, List<'gc>>,
|
||||||
|
item: StrictValue<'gc>,
|
||||||
|
) -> i32 {
|
||||||
|
let mut added = 0i32;
|
||||||
|
if let Some(attrs) = item.downcast::<AttrSet<'gc>>()
|
||||||
|
&& !is_value_in_seen(seen, item.relax())
|
||||||
|
{
|
||||||
|
add_value_to_seen(seen, mc, item.relax());
|
||||||
|
let mut wl = worklist.unlock(mc).borrow_mut();
|
||||||
|
for &(_, v) in attrs.entries.iter() {
|
||||||
|
wl.push(v);
|
||||||
|
}
|
||||||
|
added = attrs.entries.len() as i32;
|
||||||
|
} else if let Some(list) = item.downcast::<List<'gc>>()
|
||||||
|
&& !is_value_in_seen(seen, item.relax())
|
||||||
|
{
|
||||||
|
add_value_to_seen(seen, mc, item.relax());
|
||||||
|
let inner = list.inner.borrow();
|
||||||
|
let mut wl = worklist.unlock(mc).borrow_mut();
|
||||||
|
for &v in inner.iter() {
|
||||||
|
wl.push(v);
|
||||||
|
}
|
||||||
|
added = inner.len() as i32;
|
||||||
|
}
|
||||||
|
added
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn abort<'gc, M: Machine<'gc>>(
|
pub fn abort<'gc, M: Machine<'gc>>(
|
||||||
@@ -34,133 +101,6 @@ pub fn abort<'gc, M: Machine<'gc>>(
|
|||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deep_seq_force_top<'gc, M: Machine<'gc>>(
|
|
||||||
m: &mut M,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
// stack: [e1, e2] - force e1, return e2
|
|
||||||
m.force_slot(1, reader, mc)?;
|
|
||||||
|
|
||||||
let e1 = m.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 = m.pop();
|
|
||||||
let _ = m.pop();
|
|
||||||
return m.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 = m.pop();
|
|
||||||
let _ = m.pop();
|
|
||||||
m.push(e2);
|
|
||||||
m.push(Value::new_gc(seen));
|
|
||||||
m.push(Value::new_gc(worklist));
|
|
||||||
m.push(Value::new_inline(count));
|
|
||||||
reader.set_pc(PrimOpPhase::DeepSeqPush.ip() as usize);
|
|
||||||
Step::Continue(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deep_seq_push<'gc, M: Machine<'gc>>(
|
|
||||||
m: &mut M,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
// stack: [e2, seen, worklist, counter]
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let counter = m.peek(0).as_inline::<i32>().unwrap();
|
|
||||||
if counter == 0 {
|
|
||||||
let _ = m.pop(); // counter
|
|
||||||
let _ = m.pop(); // worklist
|
|
||||||
let _ = m.pop(); // seen
|
|
||||||
let val = m.pop();
|
|
||||||
return m.return_from_primop(val, reader);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let worklist = m.peek_forced(1).as_gc::<List<'gc>>().unwrap();
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let item = worklist.unlock(mc).borrow_mut().pop().unwrap();
|
|
||||||
m.replace(0, Value::new_inline(counter - 1));
|
|
||||||
m.push(item);
|
|
||||||
|
|
||||||
// force item at TOS, resume at DeepSeqLoop after force
|
|
||||||
m.force_slot_to_pc(0, reader, mc, PrimOpPhase::DeepSeqLoop.ip() as usize)?;
|
|
||||||
reader.set_pc(PrimOpPhase::DeepSeqLoop.ip() as usize);
|
|
||||||
Step::Continue(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deep_seq_loop<'gc, M: Machine<'gc>>(
|
|
||||||
m: &mut M,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
// stack after pop: [e2, seen, worklist, counter]
|
|
||||||
let item = m.pop();
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let counter = m.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 = m.peek_forced(2).as_gc::<List<'gc>>().unwrap();
|
|
||||||
if !is_value_in_seen(seen, item) {
|
|
||||||
add_value_to_seen(seen, mc, item);
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let worklist = m.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 = m.peek_forced(2).as_gc::<List<'gc>>().unwrap();
|
|
||||||
if !is_value_in_seen(seen, item) {
|
|
||||||
add_value_to_seen(seen, mc, item);
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let worklist = m.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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m.replace(0, Value::new_inline(counter + added as i32));
|
|
||||||
reader.set_pc(PrimOpPhase::DeepSeqPush.ip() as usize);
|
|
||||||
Step::Continue(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn force_result_shallow<'gc, M: Machine<'gc>>(
|
pub fn force_result_shallow<'gc, M: Machine<'gc>>(
|
||||||
m: &mut M,
|
m: &mut M,
|
||||||
ctx: &mut impl VmRuntimeCtx,
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
@@ -170,10 +110,10 @@ pub fn force_result_shallow<'gc, M: Machine<'gc>>(
|
|||||||
m.force_slot(0, reader, mc)?;
|
m.force_slot(0, reader, mc)?;
|
||||||
let val = m.peek_forced(0);
|
let val = m.peek_forced(0);
|
||||||
|
|
||||||
let (count, has_children) = if let Some(attrs) = val.as_gc::<AttrSet>() {
|
let (count, has_children) = if let Some(attrs) = val.downcast::<AttrSet>() {
|
||||||
let len = attrs.entries.len();
|
let len = attrs.entries.len();
|
||||||
(len, len > 0)
|
(len, len > 0)
|
||||||
} else if let Some(list) = val.as_gc::<List<'gc>>() {
|
} else if let Some(list) = val.downcast::<List>() {
|
||||||
let len = list.inner.borrow().len();
|
let len = list.inner.borrow().len();
|
||||||
(len, len > 0)
|
(len, len > 0)
|
||||||
} else {
|
} else {
|
||||||
@@ -185,49 +125,56 @@ pub fn force_result_shallow<'gc, M: Machine<'gc>>(
|
|||||||
return m.finish_ok(ctx.convert_value(val));
|
return m.finish_ok(ctx.convert_value(val));
|
||||||
}
|
}
|
||||||
|
|
||||||
m.push(Value::new_inline(0i32));
|
m.push(Value::new(0i32));
|
||||||
m.push(Value::new_inline(count as i32));
|
m.push(Value::new(count as i32));
|
||||||
reader.set_pc(PrimOpPhase::ForceResultShallowPush.ip() as usize);
|
reader.set_pc(Continuation::ForceResultShallowPush.ip() as usize);
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(
|
||||||
|
clippy::cast_sign_loss,
|
||||||
|
reason = "idx is a non-negative loop counter (0..=len); used only with .get(), so the cast is always a valid index or safely out of range"
|
||||||
|
)]
|
||||||
pub fn force_result_shallow_push<'gc, M: Machine<'gc>>(
|
pub fn force_result_shallow_push<'gc, M: Machine<'gc>>(
|
||||||
m: &mut M,
|
m: &mut M,
|
||||||
ctx: &mut impl VmRuntimeCtx,
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
#[allow(clippy::unwrap_used)]
|
let idx = m
|
||||||
let idx = m.peek(1).as_inline::<i32>().unwrap();
|
.peek(1)
|
||||||
#[allow(clippy::unwrap_used)]
|
.downcast::<i32>()
|
||||||
let len = m.peek(0).as_inline::<i32>().unwrap();
|
.expect("stack slot must be an integer");
|
||||||
|
let len = m
|
||||||
|
.peek(0)
|
||||||
|
.downcast::<i32>()
|
||||||
|
.expect("stack slot must be an integer");
|
||||||
|
|
||||||
if idx == len {
|
if idx == len {
|
||||||
let _ = m.pop(); // len
|
m.drop_n(2);
|
||||||
let _ = m.pop(); // idx
|
|
||||||
let val = m.pop();
|
let val = m.pop();
|
||||||
return m.finish_ok(ctx.convert_value(val));
|
return m.finish_ok(ctx.convert_value(val));
|
||||||
}
|
}
|
||||||
|
|
||||||
let val = m.peek_forced(2);
|
let val = m.peek_forced(2);
|
||||||
let child = if let Some(attrs) = val.as_gc::<AttrSet>() {
|
let child = if let Some(attrs) = val.downcast::<AttrSet>() {
|
||||||
attrs.entries.get(idx as usize).map(|&(_, v)| v)
|
attrs.entries.get(idx as usize).map(|&(_, v)| v)
|
||||||
} else if let Some(list) = val.as_gc::<List<'gc>>() {
|
} else if let Some(list) = val.downcast::<List>() {
|
||||||
list.inner.borrow().get(idx as usize).copied()
|
list.inner.borrow().get(idx as usize).copied()
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(child) = child {
|
if let Some(child) = child {
|
||||||
m.replace(1, Value::new_inline(idx + 1));
|
m.replace(1, Value::new(idx + 1));
|
||||||
m.push(child);
|
m.push(child);
|
||||||
m.force_slot_to_pc(
|
m.force_slot_to_pc(
|
||||||
0,
|
0,
|
||||||
reader,
|
reader,
|
||||||
mc,
|
mc,
|
||||||
PrimOpPhase::ForceResultShallowLoop.ip() as usize,
|
Continuation::ForceResultShallowLoop.ip() as usize,
|
||||||
)?;
|
)?;
|
||||||
reader.set_pc(PrimOpPhase::ForceResultShallowLoop.ip() as usize);
|
reader.set_pc(Continuation::ForceResultShallowLoop.ip() as usize);
|
||||||
}
|
}
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
@@ -237,8 +184,8 @@ pub fn force_result_shallow_loop<'gc, M: Machine<'gc>>(
|
|||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
_mc: &Mutation<'gc>,
|
_mc: &Mutation<'gc>,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
let _ = m.pop(); // forced child
|
m.drop_n(1);
|
||||||
reader.set_pc(PrimOpPhase::ForceResultShallowPush.ip() as usize);
|
reader.set_pc(Continuation::ForceResultShallowPush.ip() as usize);
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,7 +235,7 @@ pub fn call_functor_1<'gc, M: Machine<'gc>>(
|
|||||||
reader,
|
reader,
|
||||||
mc,
|
mc,
|
||||||
self_val,
|
self_val,
|
||||||
PrimOpPhase::CallFunctor2.ip() as usize,
|
Continuation::CallFunctor2.ip() as usize,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,6 +255,10 @@ pub fn call_functor_2<'gc, M: Machine<'gc>>(
|
|||||||
m.call(reader, mc, orig_arg, saved.pc)
|
m.call(reader, mc, orig_arg, saved.pc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(
|
||||||
|
clippy::unreachable,
|
||||||
|
reason = "CallPattern is only dispatched for closures whose pattern is Some, established when the Call opcode routed here"
|
||||||
|
)]
|
||||||
pub fn call_pattern<'gc, M: Machine<'gc>>(
|
pub fn call_pattern<'gc, M: Machine<'gc>>(
|
||||||
m: &mut M,
|
m: &mut M,
|
||||||
ctx: &mut impl VmRuntimeCtx,
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
@@ -349,7 +300,7 @@ pub fn call_pattern<'gc, M: Machine<'gc>>(
|
|||||||
|
|
||||||
let new_env = Gc::new(
|
let new_env = Gc::new(
|
||||||
mc,
|
mc,
|
||||||
RefLock::new(Env::with_arg(Value::new_gc(attrset), n_locals, env)),
|
RefLock::new(Env::with_arg(Value::new(attrset), n_locals, env)),
|
||||||
);
|
);
|
||||||
reader.set_pc(ip as usize);
|
reader.set_pc(ip as usize);
|
||||||
m.set_env(new_env);
|
m.set_env(new_env);
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ pub fn to_string<'gc, M: Machine<'gc>>(
|
|||||||
if val.is::<StringId>() || val.is::<NixString>() {
|
if val.is::<StringId>() || val.is::<NixString>() {
|
||||||
return m.return_from_primop(val.relax(), reader);
|
return m.return_from_primop(val.relax(), reader);
|
||||||
}
|
}
|
||||||
if let Some(p) = val.as_inline::<Path>() {
|
if let Some(p) = val.downcast::<Path>() {
|
||||||
return m.return_from_primop(Value::new_inline(p.0), reader);
|
return m.return_from_primop(Value::new(p.0), reader);
|
||||||
}
|
}
|
||||||
// TODO: derivations / `__toString` / `outPath`,
|
// TODO: derivations / `__toString` / `outPath`,
|
||||||
// numbers, lists.
|
// numbers, lists.
|
||||||
@@ -27,6 +27,10 @@ pub fn to_string<'gc, M: Machine<'gc>>(
|
|||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(
|
||||||
|
clippy::unreachable,
|
||||||
|
reason = "val was forced to WHNF by force_and_retry, so its type is never Thunk here"
|
||||||
|
)]
|
||||||
pub fn type_of<'gc, M: Machine<'gc>>(
|
pub fn type_of<'gc, M: Machine<'gc>>(
|
||||||
m: &mut M,
|
m: &mut M,
|
||||||
ctx: &mut impl VmRuntimeCtx,
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
@@ -47,5 +51,5 @@ pub fn type_of<'gc, M: Machine<'gc>>(
|
|||||||
NixType::Thunk => unreachable!("forced"),
|
NixType::Thunk => unreachable!("forced"),
|
||||||
};
|
};
|
||||||
let sid = ctx.intern_string(name);
|
let sid = ctx.intern_string(name);
|
||||||
m.return_from_primop(Value::new_inline(sid), reader)
|
m.return_from_primop(Value::new(sid), reader)
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-26
@@ -1,4 +1,4 @@
|
|||||||
use fix_bytecode::PrimOpPhase;
|
use fix_bytecode::Continuation;
|
||||||
use fix_runtime::{
|
use fix_runtime::{
|
||||||
AttrSet, BytecodeReader, CallFrame, List, Machine, MachineExt, NixNum, Null, Path, Step,
|
AttrSet, BytecodeReader, CallFrame, List, Machine, MachineExt, NixNum, Null, Path, Step,
|
||||||
StrictValue, Value, VmRuntimeCtx, VmRuntimeCtxExt,
|
StrictValue, Value, VmRuntimeCtx, VmRuntimeCtxExt,
|
||||||
@@ -17,11 +17,11 @@ pub fn start_eq<'gc, M: Machine<'gc>>(
|
|||||||
) -> Step {
|
) -> Step {
|
||||||
match shallow_eq(ctx, lhs, rhs) {
|
match shallow_eq(ctx, lhs, rhs) {
|
||||||
ShallowEq::True => {
|
ShallowEq::True => {
|
||||||
m.push(Value::new_inline(!negate));
|
m.push(Value::new(!negate));
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
ShallowEq::False => {
|
ShallowEq::False => {
|
||||||
m.push(Value::new_inline(negate));
|
m.push(Value::new(negate));
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
ShallowEq::RecurseList(la, lb) => {
|
ShallowEq::RecurseList(la, lb) => {
|
||||||
@@ -44,15 +44,15 @@ pub fn eq_step<'gc, M: Machine<'gc>>(
|
|||||||
) -> Step {
|
) -> Step {
|
||||||
let rhs_q = m
|
let rhs_q = m
|
||||||
.peek(0)
|
.peek(0)
|
||||||
.as_gc::<List<'gc>>()
|
.downcast::<List>()
|
||||||
.expect("eq state corrupted: rhs_queue");
|
.expect("eq state corrupted: rhs_queue");
|
||||||
let lhs_q = m
|
let lhs_q = m
|
||||||
.peek(1)
|
.peek(1)
|
||||||
.as_gc::<List<'gc>>()
|
.downcast::<List>()
|
||||||
.expect("eq state corrupted: lhs_queue");
|
.expect("eq state corrupted: lhs_queue");
|
||||||
let result = m
|
let result = m
|
||||||
.peek(2)
|
.peek(2)
|
||||||
.as_inline::<bool>()
|
.downcast::<bool>()
|
||||||
.expect("eq state corrupted: result");
|
.expect("eq state corrupted: result");
|
||||||
|
|
||||||
if !result || lhs_q.inner.borrow().is_empty() {
|
if !result || lhs_q.inner.borrow().is_empty() {
|
||||||
@@ -71,7 +71,7 @@ pub fn eq_step<'gc, M: Machine<'gc>>(
|
|||||||
.expect("non-empty rhs queue");
|
.expect("non-empty rhs queue");
|
||||||
m.push(lhs);
|
m.push(lhs);
|
||||||
m.push(rhs);
|
m.push(rhs);
|
||||||
reader.set_pc(PrimOpPhase::EqForce.ip() as usize);
|
reader.set_pc(Continuation::EqForce.ip() as usize);
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,22 +83,21 @@ pub fn eq_force<'gc, M: Machine<'gc>>(
|
|||||||
) -> Step {
|
) -> Step {
|
||||||
let (lhs, rhs) = m.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
|
let (lhs, rhs) = m.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
|
||||||
apply_pair(m, ctx, mc, lhs, rhs);
|
apply_pair(m, ctx, mc, lhs, rhs);
|
||||||
reader.set_pc(PrimOpPhase::EqStep.ip() as usize);
|
reader.set_pc(Continuation::EqStep.ip() as usize);
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finalize<'gc, M: Machine<'gc>>(m: &mut M, reader: &mut BytecodeReader<'_>) -> Step {
|
fn finalize<'gc, M: Machine<'gc>>(m: &mut M, reader: &mut BytecodeReader<'_>) -> Step {
|
||||||
let _ = m.pop();
|
m.drop_n(2);
|
||||||
let _ = m.pop();
|
|
||||||
let result = m
|
let result = m
|
||||||
.pop()
|
.pop()
|
||||||
.as_inline::<bool>()
|
.downcast::<bool>()
|
||||||
.expect("eq state corrupted: result");
|
.expect("eq state corrupted: result");
|
||||||
let negate = m
|
let negate = m
|
||||||
.pop()
|
.pop()
|
||||||
.as_inline::<bool>()
|
.downcast::<bool>()
|
||||||
.expect("eq state corrupted: negate");
|
.expect("eq state corrupted: negate");
|
||||||
m.return_from_primop(Value::new_inline(result ^ negate), reader)
|
m.return_from_primop(Value::new(result ^ negate), reader)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_pair<'gc, M: Machine<'gc>>(
|
fn apply_pair<'gc, M: Machine<'gc>>(
|
||||||
@@ -111,7 +110,7 @@ fn apply_pair<'gc, M: Machine<'gc>>(
|
|||||||
match shallow_eq(ctx, lhs, rhs) {
|
match shallow_eq(ctx, lhs, rhs) {
|
||||||
ShallowEq::True => {}
|
ShallowEq::True => {}
|
||||||
ShallowEq::False => {
|
ShallowEq::False => {
|
||||||
m.replace(2, Value::new_inline(false));
|
m.replace(2, Value::new(false));
|
||||||
}
|
}
|
||||||
ShallowEq::RecurseList(la, lb) => {
|
ShallowEq::RecurseList(la, lb) => {
|
||||||
extend_queues(
|
extend_queues(
|
||||||
@@ -140,11 +139,11 @@ where
|
|||||||
{
|
{
|
||||||
let rhs_q = m
|
let rhs_q = m
|
||||||
.peek(0)
|
.peek(0)
|
||||||
.as_gc::<List<'gc>>()
|
.downcast::<List>()
|
||||||
.expect("eq state corrupted: rhs_queue");
|
.expect("eq state corrupted: rhs_queue");
|
||||||
let lhs_q = m
|
let lhs_q = m
|
||||||
.peek(1)
|
.peek(1)
|
||||||
.as_gc::<List<'gc>>()
|
.downcast::<List>()
|
||||||
.expect("eq state corrupted: lhs_queue");
|
.expect("eq state corrupted: lhs_queue");
|
||||||
let mut lq = lhs_q.unlock(mc).borrow_mut();
|
let mut lq = lhs_q.unlock(mc).borrow_mut();
|
||||||
let mut rq = rhs_q.unlock(mc).borrow_mut();
|
let mut rq = rhs_q.unlock(mc).borrow_mut();
|
||||||
@@ -167,13 +166,14 @@ fn enter_eq_machine<'gc, M: Machine<'gc>>(
|
|||||||
pc: resume_pc,
|
pc: resume_pc,
|
||||||
thunk: None,
|
thunk: None,
|
||||||
env: m.env(),
|
env: m.env(),
|
||||||
|
depth: None,
|
||||||
});
|
});
|
||||||
m.inc_call_depth();
|
m.inc_call_depth();
|
||||||
m.push(Value::new_inline(negate));
|
m.push(Value::new(negate));
|
||||||
m.push(Value::new_inline(true));
|
m.push(Value::new(true));
|
||||||
m.push(Value::new_gc(List::new(mc, lhs_init)));
|
m.push(Value::new(List::new(mc, lhs_init)));
|
||||||
m.push(Value::new_gc(List::new(mc, rhs_init)));
|
m.push(Value::new(List::new(mc, rhs_init)));
|
||||||
reader.set_pc(PrimOpPhase::EqStep.ip() as usize);
|
reader.set_pc(Continuation::EqStep.ip() as usize);
|
||||||
Step::Continue(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,7 +189,7 @@ fn shallow_eq<'gc>(
|
|||||||
lhs: StrictValue<'gc>,
|
lhs: StrictValue<'gc>,
|
||||||
rhs: StrictValue<'gc>,
|
rhs: StrictValue<'gc>,
|
||||||
) -> ShallowEq<'gc> {
|
) -> ShallowEq<'gc> {
|
||||||
if let (Some(a), Some(b)) = (lhs.as_num(), rhs.as_num()) {
|
if let (Some(a), Some(b)) = (lhs.downcast_num(), rhs.downcast_num()) {
|
||||||
let eq = match (a, b) {
|
let eq = match (a, b) {
|
||||||
(NixNum::Int(a), NixNum::Int(b)) => a == b,
|
(NixNum::Int(a), NixNum::Int(b)) => a == b,
|
||||||
(NixNum::Float(a), NixNum::Float(b)) => a == b,
|
(NixNum::Float(a), NixNum::Float(b)) => a == b,
|
||||||
@@ -198,25 +198,28 @@ fn shallow_eq<'gc>(
|
|||||||
};
|
};
|
||||||
return bool_outcome(eq);
|
return bool_outcome(eq);
|
||||||
}
|
}
|
||||||
if let (Some(a), Some(b)) = (lhs.as_inline::<bool>(), rhs.as_inline::<bool>()) {
|
if let (Some(a), Some(b)) = (lhs.downcast::<bool>(), rhs.downcast::<bool>()) {
|
||||||
return bool_outcome(a == b);
|
return bool_outcome(a == b);
|
||||||
}
|
}
|
||||||
if lhs.is::<Null>() && rhs.is::<Null>() {
|
if lhs.is::<Null>() && rhs.is::<Null>() {
|
||||||
return ShallowEq::True;
|
return ShallowEq::True;
|
||||||
}
|
}
|
||||||
if let (Some(a), Some(b)) = (lhs.as_inline::<Path>(), rhs.as_inline::<Path>()) {
|
if let (Some(a), Some(b)) = (lhs.downcast::<Path>(), rhs.downcast::<Path>()) {
|
||||||
return bool_outcome(a.0 == b.0);
|
return bool_outcome(a.0 == b.0);
|
||||||
}
|
}
|
||||||
if let (Some(a), Some(b)) = (ctx.get_string(lhs), ctx.get_string(rhs)) {
|
if let (Some(a), Some(b)) = (ctx.get_string(lhs), ctx.get_string(rhs)) {
|
||||||
return bool_outcome(a == b);
|
return bool_outcome(a == b);
|
||||||
}
|
}
|
||||||
if let (Some(a), Some(b)) = (lhs.as_gc::<List<'gc>>(), rhs.as_gc::<List<'gc>>()) {
|
if let (Some(a), Some(b)) = (lhs.downcast::<List>(), rhs.downcast::<List>()) {
|
||||||
if a.inner.borrow().len() != b.inner.borrow().len() {
|
if a.inner.borrow().len() != b.inner.borrow().len() {
|
||||||
return ShallowEq::False;
|
return ShallowEq::False;
|
||||||
}
|
}
|
||||||
return ShallowEq::RecurseList(a, b);
|
return ShallowEq::RecurseList(a, b);
|
||||||
}
|
}
|
||||||
if let (Some(a), Some(b)) = (lhs.as_gc::<AttrSet<'gc>>(), rhs.as_gc::<AttrSet<'gc>>()) {
|
if let (Some(a), Some(b)) = (
|
||||||
|
lhs.downcast::<AttrSet<'gc>>(),
|
||||||
|
rhs.downcast::<AttrSet<'gc>>(),
|
||||||
|
) {
|
||||||
let ae = &a.entries;
|
let ae = &a.entries;
|
||||||
let be = &b.entries;
|
let be = &b.entries;
|
||||||
if ae.len() != be.len() {
|
if ae.len() != be.len() {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use fix_bytecode::PrimOpPhase;
|
use fix_bytecode::Continuation;
|
||||||
use fix_error::Error;
|
use fix_error::Error;
|
||||||
use fix_lang::StringId;
|
use fix_lang::StringId;
|
||||||
use fix_runtime::{
|
use fix_runtime::{
|
||||||
@@ -40,12 +40,13 @@ pub fn import<'gc, M: Machine<'gc>>(
|
|||||||
// finalizer can use it as the cache key. The slot we pop here was
|
// finalizer can use it as the cache key. The slot we pop here was
|
||||||
// freed by `force_and_retry`, so we simply push.
|
// freed by `force_and_retry`, so we simply push.
|
||||||
let path_sid = ctx.intern_string(abs.to_string_lossy());
|
let path_sid = ctx.intern_string(abs.to_string_lossy());
|
||||||
m.push(Value::new_inline(path_sid));
|
m.push(Value::new(path_sid));
|
||||||
let env = m.env();
|
let env = m.env();
|
||||||
m.push_call_frame(CallFrame {
|
m.push_call_frame(CallFrame {
|
||||||
pc: PrimOpPhase::ImportFinalize.ip() as usize,
|
pc: Continuation::PImportFinalize.ip() as usize,
|
||||||
thunk: None,
|
thunk: None,
|
||||||
env,
|
env,
|
||||||
|
depth: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
m.set_pending_load(PendingLoad {
|
m.set_pending_load(PendingLoad {
|
||||||
@@ -55,6 +56,10 @@ pub fn import<'gc, M: Machine<'gc>>(
|
|||||||
Step::Break(Break::LoadFile)
|
Step::Break(Break::LoadFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(
|
||||||
|
clippy::unreachable,
|
||||||
|
reason = "import always pushes the PImportFinalize call frame before this runs, so pop_call_frame is always Some"
|
||||||
|
)]
|
||||||
pub fn import_finalize<'gc, M: Machine<'gc>>(
|
pub fn import_finalize<'gc, M: Machine<'gc>>(
|
||||||
m: &mut M,
|
m: &mut M,
|
||||||
ctx: &mut impl VmRuntimeCtx,
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
@@ -62,8 +67,10 @@ pub fn import_finalize<'gc, M: Machine<'gc>>(
|
|||||||
) -> Step {
|
) -> Step {
|
||||||
// stack: [path_sid, return_value]
|
// stack: [path_sid, return_value]
|
||||||
let val = m.pop();
|
let val = m.pop();
|
||||||
#[allow(clippy::unwrap_used)]
|
let path_sid = m
|
||||||
let path_sid = m.pop().as_inline::<StringId>().unwrap();
|
.pop()
|
||||||
|
.downcast::<StringId>()
|
||||||
|
.expect("stack slot must be a string");
|
||||||
// The cache key is keyed by the absolute path string we interned in
|
// The cache key is keyed by the absolute path string we interned in
|
||||||
// `import`. Resolve it back to the host PathBuf.
|
// `import`. Resolve it back to the host PathBuf.
|
||||||
let path_str = ctx.resolve_string(path_sid).to_owned();
|
let path_str = ctx.resolve_string(path_sid).to_owned();
|
||||||
@@ -73,6 +80,7 @@ pub fn import_finalize<'gc, M: Machine<'gc>>(
|
|||||||
pc: ret_pc,
|
pc: ret_pc,
|
||||||
thunk: _,
|
thunk: _,
|
||||||
env,
|
env,
|
||||||
|
depth: None,
|
||||||
}) = m.pop_call_frame()
|
}) = m.pop_call_frame()
|
||||||
else {
|
else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
@@ -107,13 +115,14 @@ pub fn scoped_import<'gc, M: Machine<'gc>>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let keys: HashSet<StringId> = scope_attrs.entries.iter().map(|&(k, _)| k).collect();
|
let keys: HashSet<StringId> = scope_attrs.entries.iter().map(|&(k, _)| k).collect();
|
||||||
let slot_id = m.scope_slots_push(Value::new_gc(scope_attrs));
|
let slot_id = m.scope_slots_push(Value::new(scope_attrs));
|
||||||
|
|
||||||
let env = m.env();
|
let env = m.env();
|
||||||
m.push_call_frame(CallFrame {
|
m.push_call_frame(CallFrame {
|
||||||
pc: PrimOpPhase::ScopedImportFinalize.ip() as usize,
|
pc: Continuation::PScopedImportFinalize.ip() as usize,
|
||||||
thunk: None,
|
thunk: None,
|
||||||
env,
|
env,
|
||||||
|
depth: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
m.set_pending_load(PendingLoad {
|
m.set_pending_load(PendingLoad {
|
||||||
@@ -146,7 +155,7 @@ pub fn path_exists<'gc, M: Machine<'gc>>(
|
|||||||
let path_val = m.force_and_retry::<StrictValue>(reader, mc)?;
|
let path_val = m.force_and_retry::<StrictValue>(reader, mc)?;
|
||||||
// pathExists requires an absolute path. A `Path` value is
|
// pathExists requires an absolute path. A `Path` value is
|
||||||
// always absolute; a string is accepted only if it starts with `/`.
|
// always absolute; a string is accepted only if it starts with `/`.
|
||||||
let (path, is_path_value) = if let Some(p) = path_val.as_inline::<Path>() {
|
let (path, is_path_value) = if let Some(p) = path_val.downcast::<Path>() {
|
||||||
(ctx.resolve_string(p.0).to_owned(), true)
|
(ctx.resolve_string(p.0).to_owned(), true)
|
||||||
} else if let Some(s) = ctx.get_string(path_val) {
|
} else if let Some(s) = ctx.get_string(path_val) {
|
||||||
(s.to_owned(), false)
|
(s.to_owned(), false)
|
||||||
@@ -171,7 +180,7 @@ pub fn path_exists<'gc, M: Machine<'gc>>(
|
|||||||
} else {
|
} else {
|
||||||
std::fs::symlink_metadata(p).is_ok()
|
std::fs::symlink_metadata(p).is_ok()
|
||||||
};
|
};
|
||||||
m.return_from_primop(Value::new_inline(exists), reader)
|
m.return_from_primop(Value::new(exists), reader)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert the user-supplied path string into an absolute, dotted-segment
|
/// Convert the user-supplied path string into an absolute, dotted-segment
|
||||||
|
|||||||
+97
-264
@@ -1,283 +1,116 @@
|
|||||||
use fix_bytecode::PrimOpPhase;
|
use fix_error::Result;
|
||||||
use fix_runtime::{BytecodeReader, List, Machine, MachineExt, NixType, Step, StrictValue, Value};
|
use fix_macros::handler;
|
||||||
|
use fix_runtime::{List, Slot, StrictValue, Value};
|
||||||
use gc_arena::Mutation;
|
use gc_arena::Mutation;
|
||||||
|
|
||||||
pub fn filter_force_list<'gc, M: Machine<'gc>>(
|
use crate::primops::stubs::*;
|
||||||
m: &mut M,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
#[handler(name = PFilter)]
|
||||||
|
fn filter<'gc>(
|
||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
) -> Step {
|
pred: Slot<Value<'gc>>,
|
||||||
m.force_slot(0, reader, mc)?;
|
list: Slot<Value<'gc>>,
|
||||||
let list = match m.peek_forced(0).expect_gc::<List>() {
|
) -> Result<Value<'gc>> {
|
||||||
Ok(list) => list,
|
let list: Slot<List<'gc>> = force(list).await?;
|
||||||
Err(got) => return m.finish_type_err(NixType::List, got),
|
if list.get().inner.borrow().is_empty() {
|
||||||
};
|
return Ok(Value::new(list.get()));
|
||||||
if list.inner.borrow().is_empty() {
|
}
|
||||||
let val = m.pop();
|
let idx: Slot<i32> = spill(0i32);
|
||||||
let _pred = m.pop();
|
let acc: Slot<List<'gc>> = spill(List::new_gc(mc));
|
||||||
return m.return_from_primop(val, reader);
|
loop {
|
||||||
|
#[expect(
|
||||||
|
clippy::indexing_slicing,
|
||||||
|
reason = "idx is a non-negative loop counter in 0..list.len(), so it indexes the list in bounds"
|
||||||
|
)]
|
||||||
|
let keep: bool = call(&pred, list.get().inner.borrow()[idx.get() as usize]).await?;
|
||||||
|
if keep {
|
||||||
|
#[expect(
|
||||||
|
clippy::indexing_slicing,
|
||||||
|
reason = "idx is a non-negative loop counter in 0..list.len(), so it indexes the list in bounds"
|
||||||
|
)]
|
||||||
|
let elem = list.get().inner.borrow()[idx.get() as usize];
|
||||||
|
acc.get().unlock(mc).borrow_mut().push(elem);
|
||||||
|
}
|
||||||
|
if idx.get() as usize == list.get().inner.borrow().len() - 1 {
|
||||||
|
return Ok(Value::new(acc.get()));
|
||||||
|
}
|
||||||
|
idx.set(idx.get() + 1);
|
||||||
}
|
}
|
||||||
// prepare stack layout: [ pred list idx acc ]
|
|
||||||
m.push(Value::new_inline(0));
|
|
||||||
m.push(Value::new_gc(List::new_gc(mc)));
|
|
||||||
reader.set_pc(PrimOpPhase::FilterCallPred.ip() as usize);
|
|
||||||
Step::Continue(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn filter_call_pred<'gc, M: Machine<'gc>>(
|
#[handler(name = PAll)]
|
||||||
m: &mut M,
|
fn all<'gc>(
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
) -> Step {
|
pred: Slot<Value<'gc>>,
|
||||||
m.force_slot(3, reader, mc)?;
|
list: Slot<Value<'gc>>,
|
||||||
let pred = m.peek_forced(3);
|
) -> Result<Value<'gc>> {
|
||||||
#[allow(clippy::unwrap_used)]
|
let list: Slot<List<'gc>> = force(list).await?;
|
||||||
let idx = m.peek(1).as_inline::<i32>().unwrap();
|
if list.get().inner.borrow().is_empty() {
|
||||||
#[allow(clippy::unwrap_used)]
|
return Ok(Value::new(true));
|
||||||
let elem = m.peek_forced(2).as_gc::<List>().unwrap().inner.borrow()[idx as usize];
|
}
|
||||||
m.push(pred.relax());
|
let idx: Slot<i32> = spill(0i32);
|
||||||
m.call(reader, mc, elem, PrimOpPhase::FilterCheck.ip() as usize)
|
loop {
|
||||||
|
#[expect(
|
||||||
|
clippy::indexing_slicing,
|
||||||
|
reason = "idx is a non-negative loop counter in 0..list.len(), so it indexes the list in bounds"
|
||||||
|
)]
|
||||||
|
let keep: bool = call(&pred, list.get().inner.borrow()[idx.get() as usize]).await?;
|
||||||
|
if !keep || idx.get() as usize + 1 == list.get().inner.borrow().len() {
|
||||||
|
return Ok(Value::new(keep));
|
||||||
|
}
|
||||||
|
idx.set(idx.get() + 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn filter_check<'gc, M: Machine<'gc>>(
|
#[handler(name = PAny)]
|
||||||
m: &mut M,
|
fn any<'gc>(
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
) -> Step {
|
pred: Slot<Value<'gc>>,
|
||||||
let ret = m.force_and_retry::<bool>(reader, mc)?;
|
list: Slot<Value<'gc>>,
|
||||||
#[allow(clippy::unwrap_used)]
|
) -> Result<Value<'gc>> {
|
||||||
let idx = m.peek(1).as_inline::<i32>().unwrap();
|
let list: Slot<List<'gc>> = force(list).await?;
|
||||||
#[allow(clippy::unwrap_used)]
|
if list.get().inner.borrow().is_empty() {
|
||||||
let list = m.peek_forced(2).as_gc::<List>().unwrap();
|
return Ok(Value::new(false));
|
||||||
let list = list.inner.borrow();
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let acc = m.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 idx: Slot<i32> = spill(0i32);
|
||||||
let acc = m.pop();
|
loop {
|
||||||
let _ = m.pop(); // idx
|
#[expect(
|
||||||
let _ = m.pop(); // list
|
clippy::indexing_slicing,
|
||||||
let _ = m.pop(); // pred
|
reason = "idx is a non-negative loop counter in 0..list.len(), so it indexes the list in bounds"
|
||||||
return m.return_from_primop(acc, reader);
|
)]
|
||||||
|
let keep: bool = call(&pred, list.get().inner.borrow()[idx.get() as usize]).await?;
|
||||||
|
if keep || idx.get() as usize + 1 == list.get().inner.borrow().len() {
|
||||||
|
return Ok(Value::new(keep));
|
||||||
|
}
|
||||||
|
idx.set(idx.get() + 1);
|
||||||
}
|
}
|
||||||
m.replace(1, Value::new_inline(idx + 1));
|
|
||||||
reader.set_pc(PrimOpPhase::FilterCallPred.ip() as usize);
|
|
||||||
Step::Continue(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// foldl' op nul list
|
#[handler(name = PFoldlStrict)]
|
||||||
//
|
fn foldl_strict<'gc>(
|
||||||
// Stack layouts across phases:
|
|
||||||
// Entry: [op, nul, list]
|
|
||||||
// Empty: [op, nul]
|
|
||||||
// Call1: [op, list, idx, acc]
|
|
||||||
// Call2: [op, list, idx, acc, intermediate]
|
|
||||||
// Update: [op, list, idx, acc, result]
|
|
||||||
pub fn foldl_strict_entry<'gc, M: Machine<'gc>>(
|
|
||||||
m: &mut M,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
) -> Step {
|
op: Slot<Value<'gc>>,
|
||||||
m.force_slot(0, reader, mc)?;
|
nul: Slot<Value<'gc>>,
|
||||||
let list_val = m.peek_forced(0);
|
list: Slot<Value<'gc>>,
|
||||||
let Some(list) = list_val.as_gc::<List>() else {
|
) -> Result<Value<'gc>> {
|
||||||
return m.finish_type_err(NixType::List, list_val.ty());
|
let list: Slot<List<'gc>> = force(list).await?;
|
||||||
};
|
if list.get().inner.borrow().is_empty() {
|
||||||
if list.inner.borrow().is_empty() {
|
return Ok(nul.get());
|
||||||
let _ = m.pop(); // list
|
|
||||||
reader.set_pc(PrimOpPhase::FoldlStrictEmpty.ip() as usize);
|
|
||||||
return Step::Continue(());
|
|
||||||
}
|
}
|
||||||
let list_val = m.pop();
|
let idx: Slot<i32> = spill(0i32);
|
||||||
let nul_val = m.pop();
|
let acc = spill(nul.get());
|
||||||
m.push(list_val);
|
loop {
|
||||||
m.push(Value::new_inline(0i32));
|
let f = call(&op, acc.get()).await?;
|
||||||
m.push(nul_val);
|
#[expect(
|
||||||
reader.set_pc(PrimOpPhase::FoldlStrictCall1.ip() as usize);
|
clippy::indexing_slicing,
|
||||||
Step::Continue(())
|
reason = "idx is a non-negative loop counter in 0..list.len(), so it indexes the list in bounds"
|
||||||
|
)]
|
||||||
|
let new_acc: StrictValue<'gc> =
|
||||||
|
call(&f, list.get().inner.borrow()[idx.get() as usize]).await?;
|
||||||
|
acc.set(new_acc.relax());
|
||||||
|
if idx.get() as usize + 1 == list.get().inner.borrow().len() {
|
||||||
|
return Ok(acc.get());
|
||||||
}
|
}
|
||||||
|
idx.set(idx.get() + 1);
|
||||||
pub fn foldl_strict_empty<'gc, M: Machine<'gc>>(
|
|
||||||
m: &mut M,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
let nul = m.force_and_retry::<StrictValue>(reader, mc)?;
|
|
||||||
let _ = m.pop(); // op
|
|
||||||
m.return_from_primop(nul.relax(), reader)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn foldl_strict_call1<'gc, M: Machine<'gc>>(
|
|
||||||
m: &mut M,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
m.force_slot(3, reader, mc)?;
|
|
||||||
let op = m.peek_forced(3);
|
|
||||||
let acc = m.peek(0);
|
|
||||||
m.push(op.relax());
|
|
||||||
m.call(reader, mc, acc, PrimOpPhase::FoldlStrictCall2.ip() as usize)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn foldl_strict_call2<'gc, M: Machine<'gc>>(
|
|
||||||
m: &mut M,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let idx = m.peek(2).as_inline::<i32>().unwrap();
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let list = m.peek_forced(3).as_gc::<List>().unwrap();
|
|
||||||
let elem = list.inner.borrow()[idx as usize];
|
|
||||||
m.call(
|
|
||||||
reader,
|
|
||||||
mc,
|
|
||||||
elem,
|
|
||||||
PrimOpPhase::FoldlStrictUpdate.ip() as usize,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn foldl_strict_update<'gc, M: Machine<'gc>>(
|
|
||||||
m: &mut M,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
_mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
let result = m.pop();
|
|
||||||
m.replace(0, result);
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let idx = m.peek(1).as_inline::<i32>().unwrap();
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let list = m.peek_forced(2).as_gc::<List>().unwrap();
|
|
||||||
let len = list.inner.borrow().len();
|
|
||||||
if (idx as usize) + 1 == len {
|
|
||||||
let acc = m.pop();
|
|
||||||
let _ = m.pop(); // idx
|
|
||||||
let _ = m.pop(); // list
|
|
||||||
let _ = m.pop(); // op
|
|
||||||
return m.return_from_primop(acc, reader);
|
|
||||||
}
|
|
||||||
m.replace(1, Value::new_inline(idx + 1));
|
|
||||||
reader.set_pc(PrimOpPhase::FoldlStrictCall1.ip() as usize);
|
|
||||||
Step::Continue(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn all_entry<'gc, M: Machine<'gc>>(
|
|
||||||
m: &mut M,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
m.force_slot(0, reader, mc)?;
|
|
||||||
let list = match m.peek_forced(0).expect_gc::<List>() {
|
|
||||||
Ok(list) => list,
|
|
||||||
Err(got) => return m.finish_type_err(NixType::List, got),
|
|
||||||
};
|
|
||||||
// FIXME: force callable
|
|
||||||
m.force_slot(1, reader, mc)?;
|
|
||||||
if list.inner.borrow().is_empty() {
|
|
||||||
let _list = m.pop();
|
|
||||||
let _pred = m.pop();
|
|
||||||
return m.return_from_primop(Value::new_inline(true), reader);
|
|
||||||
}
|
|
||||||
// prepare stack layout: [ pred list idx ]
|
|
||||||
m.push(Value::new_inline(0));
|
|
||||||
reader.set_pc(PrimOpPhase::AllCallPred.ip() as usize);
|
|
||||||
Step::Continue(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn all_call_pred<'gc, M: Machine<'gc>>(
|
|
||||||
m: &mut M,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
let pred = m.peek_forced(2);
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let idx = m.peek(0).as_inline::<i32>().unwrap();
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let elem = m.peek_forced(1).as_gc::<List>().unwrap().inner.borrow()[idx as usize];
|
|
||||||
m.push(pred.relax());
|
|
||||||
m.call(reader, mc, elem, PrimOpPhase::AllCheck.ip() as usize)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn all_check<'gc, M: Machine<'gc>>(
|
|
||||||
m: &mut M,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
let ret = m.force_and_retry::<bool>(reader, mc)?;
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let idx = m.peek(0).as_inline::<i32>().unwrap();
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let list = m.peek_forced(1).as_gc::<List>().unwrap();
|
|
||||||
let list = list.inner.borrow();
|
|
||||||
if idx as usize == list.len() - 1 || !ret {
|
|
||||||
let _ = m.pop(); // idx
|
|
||||||
let _ = m.pop(); // list
|
|
||||||
let _ = m.pop(); // pred
|
|
||||||
return m.return_from_primop(Value::new_inline(ret), reader);
|
|
||||||
}
|
|
||||||
m.replace(0, Value::new_inline(idx + 1));
|
|
||||||
reader.set_pc(PrimOpPhase::AllCallPred.ip() as usize);
|
|
||||||
Step::Continue(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn any_entry<'gc, M: Machine<'gc>>(
|
|
||||||
m: &mut M,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
m.force_slot(0, reader, mc)?;
|
|
||||||
let list = match m.peek_forced(0).expect_gc::<List>() {
|
|
||||||
Ok(list) => list,
|
|
||||||
Err(got) => return m.finish_type_err(NixType::List, got),
|
|
||||||
};
|
|
||||||
// FIXME: force callable
|
|
||||||
m.force_slot(1, reader, mc)?;
|
|
||||||
if list.inner.borrow().is_empty() {
|
|
||||||
let _list = m.pop();
|
|
||||||
let _pred = m.pop();
|
|
||||||
return m.return_from_primop(Value::new_inline(false), reader);
|
|
||||||
}
|
|
||||||
// prepare stack layout: [ pred list idx ]
|
|
||||||
m.push(Value::new_inline(0));
|
|
||||||
reader.set_pc(PrimOpPhase::AnyCallPred.ip() as usize);
|
|
||||||
Step::Continue(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn any_call_pred<'gc, M: Machine<'gc>>(
|
|
||||||
m: &mut M,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
let pred = m.peek_forced(2);
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let idx = m.peek(0).as_inline::<i32>().unwrap();
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let elem = m.peek_forced(1).as_gc::<List>().unwrap().inner.borrow()[idx as usize];
|
|
||||||
m.push(pred.relax());
|
|
||||||
m.call(reader, mc, elem, PrimOpPhase::AnyCheck.ip() as usize)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn any_check<'gc, M: Machine<'gc>>(
|
|
||||||
m: &mut M,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
|
||||||
) -> Step {
|
|
||||||
let ret = m.force_and_retry::<bool>(reader, mc)?;
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let idx = m.peek(0).as_inline::<i32>().unwrap();
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let list = m.peek_forced(1).as_gc::<List>().unwrap();
|
|
||||||
let list = list.inner.borrow();
|
|
||||||
if idx as usize == list.len() - 1 || ret {
|
|
||||||
let _ = m.pop(); // idx
|
|
||||||
let _ = m.pop(); // list
|
|
||||||
let _ = m.pop(); // pred
|
|
||||||
return m.return_from_primop(Value::new_inline(ret), reader);
|
|
||||||
}
|
|
||||||
m.replace(0, Value::new_inline(idx + 1));
|
|
||||||
reader.set_pc(PrimOpPhase::AnyCallPred.ip() as usize);
|
|
||||||
Step::Continue(())
|
|
||||||
}
|
}
|
||||||
|
|||||||
+38
-47
@@ -5,12 +5,13 @@ mod eq;
|
|||||||
mod io;
|
mod io;
|
||||||
mod list;
|
mod list;
|
||||||
mod path;
|
mod path;
|
||||||
|
mod stubs;
|
||||||
|
|
||||||
pub use context::*;
|
pub use context::*;
|
||||||
pub use control::*;
|
pub use control::*;
|
||||||
pub use conv::*;
|
pub use conv::*;
|
||||||
pub use eq::*;
|
pub use eq::*;
|
||||||
use fix_bytecode::PrimOpPhase;
|
use fix_bytecode::Continuation;
|
||||||
use fix_error::Error;
|
use fix_error::Error;
|
||||||
use fix_runtime::{BytecodeReader, Machine, Step, VmRuntimeCtx};
|
use fix_runtime::{BytecodeReader, Machine, Step, VmRuntimeCtx};
|
||||||
use gc_arena::Mutation;
|
use gc_arena::Mutation;
|
||||||
@@ -18,43 +19,32 @@ pub use io::*;
|
|||||||
pub use list::*;
|
pub use list::*;
|
||||||
pub use path::*;
|
pub use path::*;
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)]
|
pub fn dispatch_cont<'gc, M: Machine<'gc>>(
|
||||||
pub fn dispatch_primop<'gc, M: Machine<'gc>>(
|
|
||||||
m: &mut M,
|
m: &mut M,
|
||||||
ctx: &mut impl VmRuntimeCtx,
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
use PrimOpPhase::*;
|
use Continuation::*;
|
||||||
let phase_disc = reader.read_u8();
|
let cont = reader.read_u8();
|
||||||
let Ok(phase) = PrimOpPhase::try_from(phase_disc) else {
|
let Ok(cont) = Continuation::try_from(cont) else {
|
||||||
return m.finish_err(Error::eval_error("invalid primop phase"));
|
return m.finish_err(Error::eval_error("invalid primop phase"));
|
||||||
};
|
};
|
||||||
match phase {
|
match cont {
|
||||||
Abort => abort(m, ctx, reader, mc),
|
PAbort => abort(m, ctx, reader, mc),
|
||||||
|
|
||||||
All => all_entry(m, reader, mc),
|
PAll0 | PAll1 | PAll2 | PAll3 => all(m, reader, mc, cont),
|
||||||
AllCallPred => all_call_pred(m, reader, mc),
|
PAny0 | PAny1 | PAny2 | PAny3 => any(m, reader, mc, cont),
|
||||||
AllCheck => all_check(m, reader, mc),
|
PDeepSeq0 | PDeepSeq1 | PDeepSeq2 | PDeepSeq3 | PDeepSeq4 | PDeepSeq5 => {
|
||||||
|
deep_seq(m, reader, mc, cont)
|
||||||
|
}
|
||||||
|
PSeq0 | PSeq1 => seq(m, reader, mc, cont),
|
||||||
|
|
||||||
Any => any_entry(m, reader, mc),
|
PFilter0 | PFilter1 | PFilter2 | PFilter3 | PFilter4 => filter(m, reader, mc, cont),
|
||||||
AnyCallPred => any_call_pred(m, reader, mc),
|
|
||||||
AnyCheck => any_check(m, reader, mc),
|
|
||||||
|
|
||||||
DeepSeq => deep_seq_force_top(m, reader, mc),
|
PFoldlStrict0 | PFoldlStrict1 | PFoldlStrict2 | PFoldlStrict3 | PFoldlStrict4 | PFoldlStrict5 => {
|
||||||
DeepSeqPush => deep_seq_push(m, reader, mc),
|
foldl_strict(m, reader, mc, cont)
|
||||||
DeepSeqLoop => deep_seq_loop(m, reader, mc),
|
}
|
||||||
Seq => seq(m, reader, mc),
|
|
||||||
|
|
||||||
FilterForceList => filter_force_list(m, reader, mc),
|
|
||||||
FilterCallPred => filter_call_pred(m, reader, mc),
|
|
||||||
FilterCheck => filter_check(m, reader, mc),
|
|
||||||
|
|
||||||
FoldlStrict => foldl_strict_entry(m, reader, mc),
|
|
||||||
FoldlStrictEmpty => foldl_strict_empty(m, reader, mc),
|
|
||||||
FoldlStrictCall1 => foldl_strict_call1(m, reader, mc),
|
|
||||||
FoldlStrictCall2 => foldl_strict_call2(m, reader, mc),
|
|
||||||
FoldlStrictUpdate => foldl_strict_update(m, reader, mc),
|
|
||||||
|
|
||||||
ForceResultShallow => force_result_shallow(m, ctx, reader, mc),
|
ForceResultShallow => force_result_shallow(m, ctx, reader, mc),
|
||||||
ForceResultShallowPush => force_result_shallow_push(m, ctx, reader, mc),
|
ForceResultShallowPush => force_result_shallow_push(m, ctx, reader, mc),
|
||||||
@@ -68,29 +58,30 @@ pub fn dispatch_primop<'gc, M: Machine<'gc>>(
|
|||||||
CallFunctor1 => call_functor_1(m, reader, mc),
|
CallFunctor1 => call_functor_1(m, reader, mc),
|
||||||
CallFunctor2 => call_functor_2(m, reader, mc),
|
CallFunctor2 => call_functor_2(m, reader, mc),
|
||||||
|
|
||||||
Import => import(m, ctx, reader, mc),
|
PImport => import(m, ctx, reader, mc),
|
||||||
ImportFinalize => import_finalize(m, ctx, reader),
|
PImportFinalize => import_finalize(m, ctx, reader),
|
||||||
ScopedImport => scoped_import(m, ctx, reader, mc),
|
PScopedImport => scoped_import(m, ctx, reader, mc),
|
||||||
ScopedImportFinalize => scoped_import_finalize(m, ctx, reader, mc),
|
PScopedImportFinalize => scoped_import_finalize(m, ctx, reader, mc),
|
||||||
|
|
||||||
PathExists => path_exists(m, ctx, reader, mc),
|
PPathExists => path_exists(m, ctx, reader, mc),
|
||||||
ToPath => to_path(m, ctx, reader, mc),
|
PToPath => to_path(m, ctx, reader, mc),
|
||||||
IsPath => is_path(m, reader, mc),
|
PIsPath => is_path(m, reader, mc),
|
||||||
ToString => to_string(m, ctx, reader, mc),
|
PToString => to_string(m, ctx, reader, mc),
|
||||||
TypeOf => type_of(m, ctx, reader, mc),
|
PTypeOf => type_of(m, ctx, reader, mc),
|
||||||
|
|
||||||
HasContext => has_context(m, ctx, reader, mc),
|
PHasContext => has_context(m, ctx, reader, mc),
|
||||||
GetContext => get_context(m, ctx, reader, mc),
|
PGetContext => get_context(m, ctx, reader, mc),
|
||||||
AppendContext => append_context(m, ctx, reader, mc),
|
PAppendContext => append_context(m, ctx, reader, mc),
|
||||||
AppendContextLoop => append_context_loop(m, ctx, reader, mc),
|
PAppendContextLoop => append_context_loop(m, ctx, reader, mc),
|
||||||
AppendContextEntryForced => append_context_entry_forced(m, ctx, reader, mc),
|
PAppendContextEntryForced => append_context_entry_forced(m, ctx, reader, mc),
|
||||||
AppendContextOutputsForced => append_context_outputs_forced(m, ctx, reader, mc),
|
PAppendContextOutputsForced => append_context_outputs_forced(m, ctx, reader, mc),
|
||||||
AppendContextOutputElementLoop => append_context_output_element_loop(m, ctx, reader, mc),
|
PAppendContextOutputElementLoop => append_context_output_element_loop(m, ctx, reader, mc),
|
||||||
AppendContextOutputElementForced => {
|
PAppendContextOutputElementForced => {
|
||||||
append_context_output_element_forced(m, ctx, reader, mc)
|
append_context_output_element_forced(m, ctx, reader, mc)
|
||||||
}
|
}
|
||||||
UnsafeDiscardStringContext => unsafe_discard_string_context(m, ctx, reader, mc),
|
|
||||||
UnsafeDiscardOutputDependency => unsafe_discard_output_dependency(m, ctx, reader, mc),
|
PUnsafeDiscardStringContext => unsafe_discard_string_context(m, ctx, reader, mc),
|
||||||
|
PUnsafeDiscardOutputDependency => unsafe_discard_output_dependency(m, ctx, reader, mc),
|
||||||
|
|
||||||
phase => todo!("primop phase {phase:?}"),
|
phase => todo!("primop phase {phase:?}"),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ pub fn to_path<'gc, M: Machine<'gc>>(
|
|||||||
) -> Step {
|
) -> Step {
|
||||||
// coerce to path THEN TO STRING
|
// coerce to path THEN TO STRING
|
||||||
let val = m.force_and_retry::<StrictValue>(reader, mc)?;
|
let val = m.force_and_retry::<StrictValue>(reader, mc)?;
|
||||||
if let Some(Path(s)) = val.as_inline::<Path>() {
|
if let Some(Path(s)) = val.downcast::<Path>() {
|
||||||
return m.return_from_primop(Value::new_inline(s), reader);
|
return m.return_from_primop(Value::new(s), reader);
|
||||||
}
|
}
|
||||||
let Some(s) = ctx.get_string(val) else {
|
let Some(s) = ctx.get_string(val) else {
|
||||||
return m.finish_err(Error::eval_error(format!(
|
return m.finish_err(Error::eval_error(format!(
|
||||||
@@ -29,7 +29,7 @@ pub fn to_path<'gc, M: Machine<'gc>>(
|
|||||||
}
|
}
|
||||||
let canon = canon_path_str(s);
|
let canon = canon_path_str(s);
|
||||||
let sid = ctx.intern_string(canon);
|
let sid = ctx.intern_string(canon);
|
||||||
m.return_from_primop(Value::new_inline(sid), reader)
|
m.return_from_primop(Value::new(sid), reader)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_path<'gc, M: Machine<'gc>>(
|
pub fn is_path<'gc, M: Machine<'gc>>(
|
||||||
@@ -39,5 +39,5 @@ pub fn is_path<'gc, M: Machine<'gc>>(
|
|||||||
) -> Step {
|
) -> Step {
|
||||||
let val = m.force_and_retry::<StrictValue>(reader, mc)?;
|
let val = m.force_and_retry::<StrictValue>(reader, mc)?;
|
||||||
let is_path = val.is::<Path>();
|
let is_path = val.is::<Path>();
|
||||||
m.return_from_primop(Value::new_inline(is_path), reader)
|
m.return_from_primop(Value::new(is_path), reader)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
//! Fallback definitions for the `#[primop]` surface syntax.
|
||||||
|
//!
|
||||||
|
//! `force`, `call`, and `spill` are recognized and rewritten by the
|
||||||
|
//! `#[primop]` proc macro, so compiled code never calls them. They exist only
|
||||||
|
//! so the un-expanded source name-resolves in tooling that does not run the
|
||||||
|
//! macro (e.g. rust-analyzer while the macro crate is being rebuilt). Calling
|
||||||
|
//! one for real is impossible: they never return.
|
||||||
|
|
||||||
|
pub use crate::__macro_support::{call, force, spill};
|
||||||
@@ -48,5 +48,9 @@ fix-vm = { path = "../fix-vm" }
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = { version = "0.8", features = ["html_reports"] }
|
criterion = { version = "0.8", features = ["html_reports"] }
|
||||||
|
serial_test = "4.0"
|
||||||
tempfile = "3.24"
|
tempfile = "3.24"
|
||||||
test-log = { version = "0.2", features = ["trace"] }
|
test-log = { version = "0.2", features = ["trace"] }
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#![allow(dead_code)]
|
#![allow(clippy::allow_attributes_without_reason)]
|
||||||
|
#![allow(dead_code, clippy::unwrap_used, clippy::unwrap_in_result)]
|
||||||
|
|
||||||
use fix::Evaluator;
|
use fix::Evaluator;
|
||||||
use fix_error::{Result, Source};
|
use fix_error::{Result, Source};
|
||||||
|
|||||||
+7
-11
@@ -1,6 +1,3 @@
|
|||||||
#![warn(clippy::unwrap_used)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
use fix_bytecode::InstructionPtr;
|
use fix_bytecode::InstructionPtr;
|
||||||
use fix_bytecode::disassembler::{Disassembler, DisassemblerContext};
|
use fix_bytecode::disassembler::{Disassembler, DisassemblerContext};
|
||||||
use fix_compiler::{CodeState, ExtraScope};
|
use fix_compiler::{CodeState, ExtraScope};
|
||||||
@@ -11,7 +8,6 @@ use fix_vm::Vm;
|
|||||||
use hashbrown::{HashMap, HashSet};
|
use hashbrown::{HashMap, HashSet};
|
||||||
use string_interner::{DefaultStringInterner, Symbol as _};
|
use string_interner::{DefaultStringInterner, Symbol as _};
|
||||||
|
|
||||||
mod derivation;
|
|
||||||
pub mod logging;
|
pub mod logging;
|
||||||
|
|
||||||
#[global_allocator]
|
#[global_allocator]
|
||||||
@@ -101,12 +97,12 @@ impl VmRuntimeCtx for RuntimeState {
|
|||||||
StringId(self.strings.get_or_intern(s))
|
StringId(self.strings.get_or_intern(s))
|
||||||
}
|
}
|
||||||
fn resolve_string(&self, id: StringId) -> &str {
|
fn resolve_string(&self, id: StringId) -> &str {
|
||||||
#[allow(clippy::unwrap_used)]
|
self.strings
|
||||||
self.strings.resolve(id.0).unwrap()
|
.resolve(id.0)
|
||||||
|
.expect("interned string id must resolve")
|
||||||
}
|
}
|
||||||
fn get_const(&self, id: u32) -> StaticValue {
|
fn get_const(&self, id: u32) -> StaticValue {
|
||||||
#[allow(clippy::unwrap_used)]
|
self.constants.get(id).expect("const id must be valid")
|
||||||
self.constants.get(id).unwrap()
|
|
||||||
}
|
}
|
||||||
fn add_const(&mut self, val: StaticValue) -> u32 {
|
fn add_const(&mut self, val: StaticValue) -> u32 {
|
||||||
self.constants.insert(val)
|
self.constants.insert(val)
|
||||||
@@ -145,9 +141,9 @@ impl DisassemblerContext for Evaluator {
|
|||||||
&self.code.bytecode
|
&self.code.bytecode
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
fn resolve_string(&self, id: u32) -> &str {
|
fn resolve_string(&self, id: u32) -> &str {
|
||||||
let id = string_interner::symbol::SymbolU32::try_from_usize(id as usize).unwrap();
|
let id = string_interner::symbol::SymbolU32::try_from_usize(id as usize)
|
||||||
self.runtime.strings.resolve(id).unwrap()
|
.expect("invalid string id");
|
||||||
|
self.runtime.strings.resolve(id).expect("invalid string id")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-4
@@ -5,7 +5,7 @@ use tracing_subscriber::layer::SubscriberExt;
|
|||||||
use tracing_subscriber::util::SubscriberInitExt;
|
use tracing_subscriber::util::SubscriberInitExt;
|
||||||
use tracing_subscriber::{EnvFilter, Layer, fmt};
|
use tracing_subscriber::{EnvFilter, Layer, fmt};
|
||||||
|
|
||||||
pub fn init_logging() {
|
pub fn init_logging() -> Result<(), miette::InstallError> {
|
||||||
let is_terminal = std::io::stderr().is_terminal();
|
let is_terminal = std::io::stderr().is_terminal();
|
||||||
let show_time = env::var("NIX_JS_LOG_TIME")
|
let show_time = env::var("NIX_JS_LOG_TIME")
|
||||||
.map(|v| v == "1" || v.to_lowercase() == "true")
|
.map(|v| v == "1" || v.to_lowercase() == "true")
|
||||||
@@ -32,10 +32,10 @@ pub fn init_logging() {
|
|||||||
.with(fmt_layer)
|
.with(fmt_layer)
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
init_miette_handler();
|
init_miette_handler()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_miette_handler() {
|
fn init_miette_handler() -> Result<(), miette::InstallError> {
|
||||||
let is_terminal = std::io::stderr().is_terminal();
|
let is_terminal = std::io::stderr().is_terminal();
|
||||||
miette::set_hook(Box::new(move |_| {
|
miette::set_hook(Box::new(move |_| {
|
||||||
Box::new(
|
Box::new(
|
||||||
@@ -46,5 +46,4 @@ fn init_miette_handler() {
|
|||||||
.build(),
|
.build(),
|
||||||
)
|
)
|
||||||
}))
|
}))
|
||||||
.ok();
|
|
||||||
}
|
}
|
||||||
|
|||||||
+34
-19
@@ -20,33 +20,49 @@ struct Cli {
|
|||||||
enum Command {
|
enum Command {
|
||||||
Compile {
|
Compile {
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
source: ExprSource,
|
source: ExprSourceArgs,
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
silent: bool,
|
silent: bool,
|
||||||
},
|
},
|
||||||
Eval {
|
Eval {
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
source: ExprSource,
|
source: ExprSourceArgs,
|
||||||
},
|
},
|
||||||
Repl,
|
Repl,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
#[group(required = true, multiple = false)]
|
#[group(required = true, multiple = false)]
|
||||||
struct ExprSource {
|
struct ExprSourceArgs {
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
expr: Option<String>,
|
expr: Option<String>,
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
file: Option<PathBuf>,
|
file: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ExprSource {
|
||||||
|
Expr(String),
|
||||||
|
File(PathBuf),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[expect(
|
||||||
|
clippy::unreachable,
|
||||||
|
reason = "clap's arg group guarantees exactly one of --expr/--file is set"
|
||||||
|
)]
|
||||||
|
impl From<ExprSourceArgs> for ExprSource {
|
||||||
|
fn from(args: ExprSourceArgs) -> Self {
|
||||||
|
match (args.expr, args.file) {
|
||||||
|
(Some(expr), None) => ExprSource::Expr(expr),
|
||||||
|
(None, Some(file)) => ExprSource::File(file),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn run_compile(eval: &mut Evaluator, src: ExprSource, silent: bool) -> Result<()> {
|
fn run_compile(eval: &mut Evaluator, src: ExprSource, silent: bool) -> Result<()> {
|
||||||
let src = if let Some(expr) = src.expr {
|
let src = match src {
|
||||||
Source::new_eval(expr)?
|
ExprSource::Expr(expr) => Source::new_eval(expr)?,
|
||||||
} else if let Some(file) = src.file {
|
ExprSource::File(file) => Source::new_file(file)?,
|
||||||
Source::new_file(file)?
|
|
||||||
} else {
|
|
||||||
unreachable!()
|
|
||||||
};
|
};
|
||||||
match eval.compile_bytecode(src) {
|
match eval.compile_bytecode(src) {
|
||||||
Ok(ip) => {
|
Ok(ip) => {
|
||||||
@@ -63,12 +79,9 @@ fn run_compile(eval: &mut Evaluator, src: ExprSource, silent: bool) -> Result<()
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn run_eval(eval: &mut Evaluator, src: ExprSource) -> Result<()> {
|
fn run_eval(eval: &mut Evaluator, src: ExprSource) -> Result<()> {
|
||||||
let src = if let Some(expr) = src.expr {
|
let src = match src {
|
||||||
Source::new_eval(expr)?
|
ExprSource::Expr(expr) => Source::new_eval(expr)?,
|
||||||
} else if let Some(file) = src.file {
|
ExprSource::File(file) => Source::new_file(file)?,
|
||||||
Source::new_file(file)?
|
|
||||||
} else {
|
|
||||||
unreachable!()
|
|
||||||
};
|
};
|
||||||
match eval.eval_deep(src) {
|
match eval.eval_deep(src) {
|
||||||
Ok(value) => {
|
Ok(value) => {
|
||||||
@@ -93,7 +106,9 @@ fn run_repl(eval: &mut Evaluator) -> Result<()> {
|
|||||||
if line.trim().is_empty() {
|
if line.trim().is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let _ = rl.add_history_entry(line.as_str());
|
if let Err(err) = rl.add_history_entry(line.as_str()) {
|
||||||
|
eprintln!("[WARN] Failed to add history entry: {err}");
|
||||||
|
}
|
||||||
if let Some([Some(_), Some(ident), Some(rest)]) = RE.exec(&line) {
|
if let Some([Some(_), Some(ident), Some(rest)]) = RE.exec(&line) {
|
||||||
if let Some(expr) = rest.strip_prefix('=') {
|
if let Some(expr) = rest.strip_prefix('=') {
|
||||||
let expr = expr.trim_start();
|
let expr = expr.trim_start();
|
||||||
@@ -137,15 +152,15 @@ fn run_repl(eval: &mut Evaluator) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
fix::logging::init_logging();
|
fix::logging::init_logging()?;
|
||||||
|
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
|
|
||||||
let mut eval = Evaluator::new();
|
let mut eval = Evaluator::new();
|
||||||
|
|
||||||
match cli.command {
|
match cli.command {
|
||||||
Command::Compile { source, silent } => run_compile(&mut eval, source, silent),
|
Command::Compile { source, silent } => run_compile(&mut eval, source.into(), silent),
|
||||||
Command::Eval { source } => run_eval(&mut eval, source),
|
Command::Eval { source } => run_eval(&mut eval, source.into()),
|
||||||
Command::Repl => run_repl(&mut eval),
|
Command::Repl => run_repl(&mut eval),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -402,7 +402,6 @@ fn fixed_output_sha256_flat() {
|
|||||||
|
|
||||||
#[test_log::test]
|
#[test_log::test]
|
||||||
fn fixed_output_missing_hashalgo() {
|
fn fixed_output_missing_hashalgo() {
|
||||||
assert!(
|
|
||||||
eval_deep_result(
|
eval_deep_result(
|
||||||
r#"derivation {
|
r#"derivation {
|
||||||
name = "default";
|
name = "default";
|
||||||
@@ -411,8 +410,7 @@ fn fixed_output_missing_hashalgo() {
|
|||||||
outputHash = "0000000000000000000000000000000000000000000000000000000000000000";
|
outputHash = "0000000000000000000000000000000000000000000000000000000000000000";
|
||||||
}"#,
|
}"#,
|
||||||
)
|
)
|
||||||
.is_err()
|
.unwrap_err();
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test_log::test]
|
#[test_log::test]
|
||||||
|
|||||||
@@ -350,7 +350,7 @@ fn read_dir_nonexistent_fails() {
|
|||||||
let expr = r#"builtins.readDir "/nonexistent/directory""#;
|
let expr = r#"builtins.readDir "/nonexistent/directory""#;
|
||||||
let result = eval_result(expr);
|
let result = eval_result(expr);
|
||||||
|
|
||||||
assert!(result.is_err());
|
result.unwrap_err();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test_log::test]
|
#[test_log::test]
|
||||||
|
|||||||
+15
-4
@@ -5,6 +5,7 @@ use std::path::PathBuf;
|
|||||||
use fix::Evaluator;
|
use fix::Evaluator;
|
||||||
use fix_error::{Source, SourceType};
|
use fix_error::{Source, SourceType};
|
||||||
use fix_lang::Value;
|
use fix_lang::Value;
|
||||||
|
use serial_test::serial;
|
||||||
|
|
||||||
fn get_lang_dir() -> PathBuf {
|
fn get_lang_dir() -> PathBuf {
|
||||||
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/tests/lang")
|
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/tests/lang")
|
||||||
@@ -160,9 +161,14 @@ mod okay {
|
|||||||
eval_okay_test!(getattrpos);
|
eval_okay_test!(getattrpos);
|
||||||
eval_okay_test!(getattrpos_functionargs);
|
eval_okay_test!(getattrpos_functionargs);
|
||||||
eval_okay_test!(getattrpos_undefined);
|
eval_okay_test!(getattrpos_undefined);
|
||||||
eval_okay_test!(getenv, || {
|
eval_okay_test!(
|
||||||
|
#[serial(env)]
|
||||||
|
getenv,
|
||||||
|
|| {
|
||||||
|
// SAFETY: guarded with #[serial_test::serial]
|
||||||
unsafe { std::env::set_var("TEST_VAR", "foo") };
|
unsafe { std::env::set_var("TEST_VAR", "foo") };
|
||||||
});
|
}
|
||||||
|
);
|
||||||
eval_okay_test!(groupBy);
|
eval_okay_test!(groupBy);
|
||||||
eval_okay_test!(r#if);
|
eval_okay_test!(r#if);
|
||||||
eval_okay_test!(ind_string);
|
eval_okay_test!(ind_string);
|
||||||
@@ -194,11 +200,16 @@ mod okay {
|
|||||||
eval_okay_test!(partition);
|
eval_okay_test!(partition);
|
||||||
eval_okay_test!(path);
|
eval_okay_test!(path);
|
||||||
eval_okay_test!(pathexists);
|
eval_okay_test!(pathexists);
|
||||||
eval_okay_test!(path_string_interpolation, || {
|
eval_okay_test!(
|
||||||
|
#[serial(env)]
|
||||||
|
path_string_interpolation,
|
||||||
|
|| {
|
||||||
|
// SAFETY: guarded with #[serial_test::serial]
|
||||||
unsafe {
|
unsafe {
|
||||||
std::env::set_var("HOME", "/fake-home");
|
std::env::set_var("HOME", "/fake-home");
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
);
|
||||||
eval_okay_test!(patterns);
|
eval_okay_test!(patterns);
|
||||||
eval_okay_test!(print);
|
eval_okay_test!(print);
|
||||||
eval_okay_test!(readDir);
|
eval_okay_test!(readDir);
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
|
#![allow(clippy::allow_attributes_without_reason)]
|
||||||
|
#![allow(
|
||||||
|
dead_code,
|
||||||
|
clippy::unwrap_used,
|
||||||
|
clippy::unwrap_in_result,
|
||||||
|
clippy::panic
|
||||||
|
)]
|
||||||
|
|
||||||
mod derivation;
|
mod derivation;
|
||||||
mod findfile;
|
mod findfile;
|
||||||
mod io_operations;
|
mod io_operations;
|
||||||
|
|||||||
@@ -348,7 +348,7 @@ fn substring_zero_length_empty_value() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test_log::test]
|
#[test_log::test]
|
||||||
#[allow(non_snake_case)]
|
#[expect(non_snake_case)]
|
||||||
fn concatStringsSep_preserves_context() {
|
fn concatStringsSep_preserves_context() {
|
||||||
let result = eval(
|
let result = eval(
|
||||||
r#"
|
r#"
|
||||||
@@ -365,7 +365,7 @@ fn concatStringsSep_preserves_context() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test_log::test]
|
#[test_log::test]
|
||||||
#[allow(non_snake_case)]
|
#[expect(non_snake_case)]
|
||||||
fn concatStringsSep_merges_contexts() {
|
fn concatStringsSep_merges_contexts() {
|
||||||
let result = eval(
|
let result = eval(
|
||||||
r#"
|
r#"
|
||||||
@@ -383,7 +383,7 @@ fn concatStringsSep_merges_contexts() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test_log::test]
|
#[test_log::test]
|
||||||
#[allow(non_snake_case)]
|
#[expect(non_snake_case)]
|
||||||
fn concatStringsSep_separator_has_context() {
|
fn concatStringsSep_separator_has_context() {
|
||||||
let result = eval(
|
let result = eval(
|
||||||
r#"
|
r#"
|
||||||
@@ -398,7 +398,7 @@ fn concatStringsSep_separator_has_context() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test_log::test]
|
#[test_log::test]
|
||||||
#[allow(non_snake_case)]
|
#[expect(non_snake_case)]
|
||||||
fn replaceStrings_input_context_preserved() {
|
fn replaceStrings_input_context_preserved() {
|
||||||
let result = eval(
|
let result = eval(
|
||||||
r#"
|
r#"
|
||||||
@@ -413,7 +413,7 @@ fn replaceStrings_input_context_preserved() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test_log::test]
|
#[test_log::test]
|
||||||
#[allow(non_snake_case)]
|
#[expect(non_snake_case)]
|
||||||
fn replaceStrings_replacement_context_collected() {
|
fn replaceStrings_replacement_context_collected() {
|
||||||
let result = eval(
|
let result = eval(
|
||||||
r#"
|
r#"
|
||||||
@@ -428,7 +428,7 @@ fn replaceStrings_replacement_context_collected() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test_log::test]
|
#[test_log::test]
|
||||||
#[allow(non_snake_case)]
|
#[expect(non_snake_case)]
|
||||||
fn replaceStrings_merges_contexts() {
|
fn replaceStrings_merges_contexts() {
|
||||||
let result = eval(
|
let result = eval(
|
||||||
r#"
|
r#"
|
||||||
@@ -446,7 +446,7 @@ fn replaceStrings_merges_contexts() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test_log::test]
|
#[test_log::test]
|
||||||
#[allow(non_snake_case)]
|
#[expect(non_snake_case)]
|
||||||
fn replaceStrings_lazy_evaluation_context() {
|
fn replaceStrings_lazy_evaluation_context() {
|
||||||
let result = eval(
|
let result = eval(
|
||||||
r#"
|
r#"
|
||||||
@@ -461,7 +461,7 @@ fn replaceStrings_lazy_evaluation_context() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test_log::test]
|
#[test_log::test]
|
||||||
#[allow(non_snake_case)]
|
#[expect(non_snake_case)]
|
||||||
fn baseNameOf_preserves_context() {
|
fn baseNameOf_preserves_context() {
|
||||||
let result = eval(
|
let result = eval(
|
||||||
r#"
|
r#"
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
use fix::Evaluator;
|
use fix::Evaluator;
|
||||||
use fix_error::{Result, Source};
|
use fix_error::{Result, Source};
|
||||||
use fix_lang::Value;
|
use fix_lang::Value;
|
||||||
|
|||||||
Generated
+9
-150
@@ -1,63 +1,5 @@
|
|||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"blueprint": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"llm-agents",
|
|
||||||
"nixpkgs"
|
|
||||||
],
|
|
||||||
"systems": [
|
|
||||||
"llm-agents",
|
|
||||||
"systems"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1776249299,
|
|
||||||
"narHash": "sha256-Dt9t1TGRmJFc0xVYhttNBD6QsAgHOHCArqGa0AyjrJY=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "blueprint",
|
|
||||||
"rev": "56131e8628f173d24a27f6d27c0215eff57e40dd",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "blueprint",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"bun2nix": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-parts": [
|
|
||||||
"llm-agents",
|
|
||||||
"flake-parts"
|
|
||||||
],
|
|
||||||
"nixpkgs": [
|
|
||||||
"llm-agents",
|
|
||||||
"nixpkgs"
|
|
||||||
],
|
|
||||||
"systems": [
|
|
||||||
"llm-agents",
|
|
||||||
"systems"
|
|
||||||
],
|
|
||||||
"treefmt-nix": [
|
|
||||||
"llm-agents",
|
|
||||||
"treefmt-nix"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1778446047,
|
|
||||||
"narHash": "sha256-oQvcadh2BCkrog+SGrG6YffKJrveYpjj3TdQJWaKhaM=",
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "bun2nix",
|
|
||||||
"rev": "f2bc12af1a6369648aac41041ceeaa0b866599c6",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "bun2nix",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fenix": {
|
"fenix": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
@@ -66,11 +8,11 @@
|
|||||||
"rust-analyzer-src": "rust-analyzer-src"
|
"rust-analyzer-src": "rust-analyzer-src"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1781343250,
|
"lastModified": 1784017020,
|
||||||
"narHash": "sha256-KBJktAwDG9+10j2wMfvOVkBEhZr3yS769xoqqdFI62s=",
|
"narHash": "sha256-49WO85egjNtN1vMgJ3zUjDh5IqO+ou4zZY80WQ6EKGg=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "fenix",
|
"repo": "fenix",
|
||||||
"rev": "aad7d8bb6936d473c2b9d1a5846a1fe1bc92767a",
|
"rev": "fa2a0be0f712d7147d1677d33d15ea7b0589cdb4",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -93,59 +35,13 @@
|
|||||||
"url": "https://git.lix.systems/lix-project/flake-compat/archive/main.tar.gz"
|
"url": "https://git.lix.systems/lix-project/flake-compat/archive/main.tar.gz"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"flake-parts": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs-lib": [
|
|
||||||
"llm-agents",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1778716662,
|
|
||||||
"narHash": "sha256-m1Yf0wZ8j1OHjTc2UwHwyQRSnNeSgLJOd7q5Y45hzi4=",
|
|
||||||
"owner": "hercules-ci",
|
|
||||||
"repo": "flake-parts",
|
|
||||||
"rev": "f7c1a2d347e4c52d5fb8d10cb4d94b5884e546fb",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "hercules-ci",
|
|
||||||
"repo": "flake-parts",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"llm-agents": {
|
|
||||||
"inputs": {
|
|
||||||
"blueprint": "blueprint",
|
|
||||||
"bun2nix": "bun2nix",
|
|
||||||
"flake-parts": "flake-parts",
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
],
|
|
||||||
"systems": "systems",
|
|
||||||
"treefmt-nix": "treefmt-nix"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1781330261,
|
|
||||||
"narHash": "sha256-2fFAGel2VVXr5mwrTXldqXva2ng3T3HHxyuBKRIxauI=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "llm-agents.nix",
|
|
||||||
"rev": "24ec6b7b1ddf8896ac8df3b65dc564575e0a1928",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "llm-agents.nix",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1781074563,
|
"lastModified": 1784007870,
|
||||||
"narHash": "sha256-md8WlXOlfnIeHeOScMTTHFyf2d6iaTwPl2apR5EQ3P4=",
|
"narHash": "sha256-djcLt/JJphyNt4eDY9XTly+/WbCK5lqWq9lSgCmJkkQ=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "9ae611a455b90cf061d8f332b977e387bda8e1ca",
|
"rev": "18b9261cb3294b6d2a06d03f96872827b8fe2698",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -159,18 +55,17 @@
|
|||||||
"inputs": {
|
"inputs": {
|
||||||
"fenix": "fenix",
|
"fenix": "fenix",
|
||||||
"flake-compat": "flake-compat",
|
"flake-compat": "flake-compat",
|
||||||
"llm-agents": "llm-agents",
|
|
||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rust-analyzer-src": {
|
"rust-analyzer-src": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1781294997,
|
"lastModified": 1783975201,
|
||||||
"narHash": "sha256-XjCyIvJw4JtcwItTKRdQz5h1pLF9hr8ZSYeMP+/1d3A=",
|
"narHash": "sha256-oyHWTfKWk06nTynCO8ByEAx/KEARvOOyTVnwinOmK3Q=",
|
||||||
"owner": "rust-lang",
|
"owner": "rust-lang",
|
||||||
"repo": "rust-analyzer",
|
"repo": "rust-analyzer",
|
||||||
"rev": "3f92cd1612268995d5667bd04fa03ba2916413d9",
|
"rev": "63a6f0d4bcfd3bbcf36383fcbcbcd93456ed1653",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -179,42 +74,6 @@
|
|||||||
"repo": "rust-analyzer",
|
"repo": "rust-analyzer",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"systems": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681028828,
|
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"treefmt-nix": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"llm-agents",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1780220602,
|
|
||||||
"narHash": "sha256-eynAfOmbmxJnkp7YewvCEbShNnnYJ9gLLqkzsYtBPeM=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "treefmt-nix",
|
|
||||||
"rev": "db947814a175b7ca6ded66e21383d938df01c227",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "treefmt-nix",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": "root",
|
"root": "root",
|
||||||
|
|||||||
@@ -3,17 +3,13 @@
|
|||||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||||
fenix.url = "github:nix-community/fenix";
|
fenix.url = "github:nix-community/fenix";
|
||||||
fenix.inputs.nixpkgs.follows = "nixpkgs";
|
fenix.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
llm-agents = {
|
|
||||||
url = "github:numtide/llm-agents.nix";
|
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
};
|
|
||||||
flake-compat = {
|
flake-compat = {
|
||||||
url = "https://git.lix.systems/lix-project/flake-compat/archive/main.tar.gz";
|
url = "https://git.lix.systems/lix-project/flake-compat/archive/main.tar.gz";
|
||||||
flake = false;
|
flake = false;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
outputs =
|
outputs =
|
||||||
inputs@{ nixpkgs, fenix, ... }:
|
{ nixpkgs, fenix, ... }:
|
||||||
let
|
let
|
||||||
forAllSystems = nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed;
|
forAllSystems = nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed;
|
||||||
in
|
in
|
||||||
@@ -24,37 +20,11 @@
|
|||||||
pkgs = import nixpkgs {
|
pkgs = import nixpkgs {
|
||||||
inherit system;
|
inherit system;
|
||||||
config.allowUnfree = true;
|
config.allowUnfree = true;
|
||||||
|
overlays = [ fenix.overlays.default ];
|
||||||
};
|
};
|
||||||
llm-agents = inputs.llm-agents.packages.${pkgs.stdenv.hostPlatform.system};
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
default = pkgs.mkShell {
|
default = import ./devShell.nix { inherit pkgs; };
|
||||||
packages = with pkgs; [
|
|
||||||
(fenix.packages.${system}.latest.withComponents [
|
|
||||||
"cargo"
|
|
||||||
"clippy"
|
|
||||||
"rust-src"
|
|
||||||
"rustc"
|
|
||||||
"rustfmt"
|
|
||||||
"rust-analyzer"
|
|
||||||
])
|
|
||||||
cargo-machete
|
|
||||||
cargo-bloat
|
|
||||||
lldb
|
|
||||||
valgrind
|
|
||||||
kdePackages.kcachegrind
|
|
||||||
hyperfine
|
|
||||||
just
|
|
||||||
samply
|
|
||||||
tokei
|
|
||||||
tombi
|
|
||||||
|
|
||||||
# llm-agents.codex
|
|
||||||
llm-agents.claude-code
|
|
||||||
llm-agents.opencode
|
|
||||||
# llm-agents.forge
|
|
||||||
];
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
let
|
|
||||||
lockFile = builtins.fromJSON (builtins.readFile ./flake.lock);
|
|
||||||
flake-compat-node = lockFile.nodes.${lockFile.nodes.root.inputs.flake-compat};
|
|
||||||
flake-compat = builtins.fetchTarball {
|
|
||||||
inherit (flake-compat-node.locked) url;
|
|
||||||
sha256 = flake-compat-node.locked.narHash;
|
|
||||||
};
|
|
||||||
|
|
||||||
flake = (
|
|
||||||
import flake-compat {
|
|
||||||
src = ./.;
|
|
||||||
copySourceTreeToStore = false;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
in
|
|
||||||
flake.shellNix
|
|
||||||
Reference in New Issue
Block a user