Compare commits
6 Commits
291e3a9a13
...
e527d31450
| Author | SHA1 | Date | |
|---|---|---|---|
|
e527d31450
|
|||
|
ca7f7a5ec8
|
|||
|
f66752afa5
|
|||
|
74866ec1d3
|
|||
|
df9664f5c4
|
|||
|
f372ebcb8e
|
@@ -10,3 +10,4 @@ prof.json
|
|||||||
*.cpuprofile
|
*.cpuprofile
|
||||||
*.cpuprofile.gz
|
*.cpuprofile.gz
|
||||||
*v8.log*
|
*v8.log*
|
||||||
|
callgrind.out.*
|
||||||
|
|||||||
@@ -29,3 +29,8 @@
|
|||||||
[no-exit-message]
|
[no-exit-message]
|
||||||
@evalp expr:
|
@evalp expr:
|
||||||
cargo run --release --features prof -- eval --expr '{{expr}}'
|
cargo run --release --features prof -- eval --expr '{{expr}}'
|
||||||
|
|
||||||
|
[no-exit-message]
|
||||||
|
[positional-arguments]
|
||||||
|
@cg *args='':
|
||||||
|
valgrind --tool=callgrind --dump-instr=yes --collect-jumps=yes "$@"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::fmt::Write;
|
|||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use num_enum::TryFromPrimitive;
|
use num_enum::TryFromPrimitive;
|
||||||
|
|
||||||
use crate::{InstructionPtr, Op};
|
use crate::{AttrKeyType, InstructionPtr, OperandType, Op};
|
||||||
|
|
||||||
pub trait DisassemblerContext {
|
pub trait DisassemblerContext {
|
||||||
fn resolve_string(&self, id: u32) -> &str;
|
fn resolve_string(&self, id: u32) -> &str;
|
||||||
@@ -25,12 +25,14 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
fn read_u8(&mut self) -> u8 {
|
fn read_u8(&mut self) -> u8 {
|
||||||
let b = self.code[self.pc];
|
let b = self.code[self.pc];
|
||||||
self.pc += 1;
|
self.pc += 1;
|
||||||
b
|
b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
fn read_u16(&mut self) -> u16 {
|
fn read_u16(&mut self) -> u16 {
|
||||||
let bytes = self.code[self.pc..self.pc + 2]
|
let bytes = self.code[self.pc..self.pc + 2]
|
||||||
.try_into()
|
.try_into()
|
||||||
@@ -39,6 +41,7 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
|||||||
u16::from_le_bytes(bytes)
|
u16::from_le_bytes(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
fn read_u32(&mut self) -> u32 {
|
fn read_u32(&mut self) -> u32 {
|
||||||
let bytes = self.code[self.pc..self.pc + 4]
|
let bytes = self.code[self.pc..self.pc + 4]
|
||||||
.try_into()
|
.try_into()
|
||||||
@@ -47,6 +50,7 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
|||||||
u32::from_le_bytes(bytes)
|
u32::from_le_bytes(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
fn read_i32(&mut self) -> i32 {
|
fn read_i32(&mut self) -> i32 {
|
||||||
let bytes = self.code[self.pc..self.pc + 4]
|
let bytes = self.code[self.pc..self.pc + 4]
|
||||||
.try_into()
|
.try_into()
|
||||||
@@ -55,6 +59,7 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
|||||||
i32::from_le_bytes(bytes)
|
i32::from_le_bytes(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
fn read_i64(&mut self) -> i64 {
|
fn read_i64(&mut self) -> i64 {
|
||||||
let bytes = self.code[self.pc..self.pc + 8]
|
let bytes = self.code[self.pc..self.pc + 8]
|
||||||
.try_into()
|
.try_into()
|
||||||
@@ -63,6 +68,7 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
|||||||
i64::from_le_bytes(bytes)
|
i64::from_le_bytes(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
fn read_f64(&mut self) -> f64 {
|
fn read_f64(&mut self) -> f64 {
|
||||||
let bytes = self.code[self.pc..self.pc + 8]
|
let bytes = self.code[self.pc..self.pc + 8]
|
||||||
.try_into()
|
.try_into()
|
||||||
@@ -71,6 +77,25 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
|||||||
f64::from_le_bytes(bytes)
|
f64::from_le_bytes(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn read_operand_data(&mut self) {
|
||||||
|
let tag = self.read_u8();
|
||||||
|
let ty = OperandType::try_from_primitive(tag).expect("invalid operand type");
|
||||||
|
match ty {
|
||||||
|
OperandType::Const => {
|
||||||
|
self.read_u32();
|
||||||
|
}
|
||||||
|
OperandType::Local => {
|
||||||
|
self.read_u8();
|
||||||
|
self.read_u32();
|
||||||
|
}
|
||||||
|
OperandType::Builtins => {}
|
||||||
|
OperandType::BigInt => {
|
||||||
|
self.read_i64();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn disassemble(&mut self) -> String {
|
pub fn disassemble(&mut self) -> String {
|
||||||
self.disassemble_impl(false)
|
self.disassemble_impl(false)
|
||||||
}
|
}
|
||||||
@@ -255,27 +280,51 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
|||||||
("MakePatternClosure", arg_str)
|
("MakePatternClosure", arg_str)
|
||||||
}
|
}
|
||||||
|
|
||||||
Op::Call => {
|
Op::Call => ("Call", String::new()),
|
||||||
("Call", String::new())
|
|
||||||
},
|
|
||||||
|
|
||||||
Op::MakeAttrs => {
|
Op::MakeAttrs => {
|
||||||
let count = self.read_u32();
|
let count = self.read_u32();
|
||||||
("MakeAttrs", format!("size={}", count))
|
let mut args = format!("size={}", count);
|
||||||
|
for _ in 0..count {
|
||||||
|
let key_tag = self.read_u8();
|
||||||
|
let key_ty = AttrKeyType::try_from_primitive(key_tag)
|
||||||
|
.expect("invalid attr key type");
|
||||||
|
match key_ty {
|
||||||
|
AttrKeyType::Static => {
|
||||||
|
let key_id = self.read_u32();
|
||||||
|
let _ = write!(args, " [{}={}", self.ctx.resolve_string(key_id), key_id);
|
||||||
|
}
|
||||||
|
AttrKeyType::Dynamic => {
|
||||||
|
let _ = write!(args, " [dyn");
|
||||||
|
self.read_operand_data();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.read_operand_data();
|
||||||
|
let _span_id = self.read_u32();
|
||||||
|
args.push(']');
|
||||||
|
}
|
||||||
|
("MakeAttrs", args)
|
||||||
}
|
}
|
||||||
Op::MakeEmptyAttrs => ("MakeEmptyAttrs", String::new()),
|
Op::MakeEmptyAttrs => ("MakeEmptyAttrs", String::new()),
|
||||||
|
|
||||||
Op::Select => {
|
Op::SelectStatic => {
|
||||||
let path_len = self.read_u16();
|
|
||||||
let span_id = self.read_u32();
|
|
||||||
("Select", format!("path_len={} span={}", path_len, span_id))
|
|
||||||
}
|
|
||||||
Op::SelectDefault => {
|
|
||||||
let path_len = self.read_u16();
|
|
||||||
let span_id = self.read_u32();
|
let span_id = self.read_u32();
|
||||||
|
let key_id = self.read_u32();
|
||||||
(
|
(
|
||||||
"SelectDefault",
|
"SelectStatic",
|
||||||
format!("path_len={} span={}", path_len, span_id),
|
format!("key={} span={}", self.ctx.resolve_string(key_id), span_id),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Op::SelectDynamic => {
|
||||||
|
let span_id = self.read_u32();
|
||||||
|
("SelectDynamic", format!("span={}", span_id))
|
||||||
|
}
|
||||||
|
Op::JumpIfSelectSucceeded => {
|
||||||
|
let offset = self.read_i32();
|
||||||
|
let target = (current_pc as isize + 1 + 4 + offset as isize) as usize;
|
||||||
|
(
|
||||||
|
"JumpIfSelectSucceeded",
|
||||||
|
format!("-> {:04x} offset={}", target, offset),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Op::HasAttr => {
|
Op::HasAttr => {
|
||||||
@@ -285,6 +334,9 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
|||||||
|
|
||||||
Op::MakeList => {
|
Op::MakeList => {
|
||||||
let count = self.read_u32();
|
let count = self.read_u32();
|
||||||
|
for _ in 0..count {
|
||||||
|
self.read_operand_data();
|
||||||
|
}
|
||||||
("MakeList", format!("size={}", count))
|
("MakeList", format!("size={}", count))
|
||||||
}
|
}
|
||||||
Op::MakeEmptyList => ("MakeEmptyList", String::new()),
|
Op::MakeEmptyList => ("MakeEmptyList", String::new()),
|
||||||
@@ -336,10 +388,11 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
|||||||
}
|
}
|
||||||
Op::PushWith => ("PushWith", String::new()),
|
Op::PushWith => ("PushWith", String::new()),
|
||||||
Op::PopWith => ("PopWith", String::new()),
|
Op::PopWith => ("PopWith", String::new()),
|
||||||
Op::WithLookup => {
|
Op::PrepareWith => ("PrepareWith", String::new()),
|
||||||
|
Op::LookupWith => {
|
||||||
let idx = self.read_u32();
|
let idx = self.read_u32();
|
||||||
let name = self.ctx.resolve_string(idx);
|
let name = self.ctx.resolve_string(idx);
|
||||||
("WithLookup", format!("{:?}", name))
|
("LookupWith", format!("{:?}", name))
|
||||||
}
|
}
|
||||||
|
|
||||||
Op::LoadBuiltins => ("LoadBuiltins", String::new()),
|
Op::LoadBuiltins => ("LoadBuiltins", String::new()),
|
||||||
|
|||||||
+33
-31
@@ -45,8 +45,9 @@ pub enum Op {
|
|||||||
|
|
||||||
MakeAttrs,
|
MakeAttrs,
|
||||||
MakeEmptyAttrs,
|
MakeEmptyAttrs,
|
||||||
Select,
|
SelectStatic,
|
||||||
SelectDefault,
|
SelectDynamic,
|
||||||
|
JumpIfSelectSucceeded,
|
||||||
HasAttr,
|
HasAttr,
|
||||||
|
|
||||||
MakeList,
|
MakeList,
|
||||||
@@ -79,7 +80,8 @@ pub enum Op {
|
|||||||
|
|
||||||
PushWith,
|
PushWith,
|
||||||
PopWith,
|
PopWith,
|
||||||
WithLookup,
|
LookupWith,
|
||||||
|
PrepareWith,
|
||||||
|
|
||||||
LoadBuiltins,
|
LoadBuiltins,
|
||||||
LoadBuiltin,
|
LoadBuiltin,
|
||||||
@@ -113,13 +115,6 @@ pub enum OperandType {
|
|||||||
BigInt,
|
BigInt,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u8)]
|
|
||||||
#[derive(Debug, Clone, Copy, TryFromPrimitive)]
|
|
||||||
pub enum AttrKeyType {
|
|
||||||
Static,
|
|
||||||
Dynamic,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum Const {
|
pub enum Const {
|
||||||
Smi(i32),
|
Smi(i32),
|
||||||
Float(f64),
|
Float(f64),
|
||||||
@@ -129,6 +124,13 @@ pub enum Const {
|
|||||||
Null,
|
Null,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
#[derive(Debug, Clone, Copy, TryFromPrimitive)]
|
||||||
|
pub enum AttrKeyType {
|
||||||
|
Static,
|
||||||
|
Dynamic,
|
||||||
|
}
|
||||||
|
|
||||||
pub enum InlineOperand {
|
pub enum InlineOperand {
|
||||||
Const(Const),
|
Const(Const),
|
||||||
Local { layer: u16, local: u32 },
|
Local { layer: u16, local: u32 },
|
||||||
@@ -625,7 +627,9 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
self.emit_with(namespace, body, thunks);
|
self.emit_with(namespace, body, thunks);
|
||||||
}
|
}
|
||||||
&Ir::WithLookup(name) => {
|
&Ir::WithLookup(name) => {
|
||||||
self.emit_op(Op::WithLookup);
|
// TODO: specialize shallow with lookups
|
||||||
|
self.emit_op(Op::PrepareWith);
|
||||||
|
self.emit_op(Op::LookupWith);
|
||||||
self.emit_str_id(name);
|
self.emit_str_id(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -817,34 +821,32 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
span: TextRange,
|
span: TextRange,
|
||||||
) {
|
) {
|
||||||
self.emit_expr(expr);
|
self.emit_expr(expr);
|
||||||
for attr in attrpath.iter() {
|
|
||||||
if let Attr::Dynamic(expr, _) = *attr {
|
|
||||||
self.emit_expr(expr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(default) = default {
|
|
||||||
self.emit_expr(default);
|
|
||||||
let span_id = self.ctx.register_span(span);
|
|
||||||
self.emit_op(Op::SelectDefault);
|
|
||||||
self.emit_u16(attrpath.len() as u16);
|
|
||||||
self.emit_u32(span_id);
|
|
||||||
} else {
|
|
||||||
let span_id = self.ctx.register_span(span);
|
|
||||||
self.emit_op(Op::Select);
|
|
||||||
self.emit_u16(attrpath.len() as u16);
|
|
||||||
self.emit_u32(span_id);
|
|
||||||
}
|
|
||||||
for attr in attrpath.iter() {
|
for attr in attrpath.iter() {
|
||||||
match *attr {
|
match *attr {
|
||||||
Attr::Str(sym, _) => {
|
Attr::Str(sym, _) => {
|
||||||
self.emit_u8(AttrKeyType::Static as u8);
|
let span_id = self.ctx.register_span(span);
|
||||||
|
self.emit_op(Op::SelectStatic);
|
||||||
|
self.emit_u32(span_id);
|
||||||
self.emit_str_id(sym);
|
self.emit_str_id(sym);
|
||||||
}
|
}
|
||||||
Attr::Dynamic(_, _) => {
|
Attr::Dynamic(key_expr, _) => {
|
||||||
self.emit_u8(AttrKeyType::Dynamic as u8);
|
self.emit_expr(key_expr);
|
||||||
|
let span_id = self.ctx.register_span(span);
|
||||||
|
self.emit_op(Op::SelectDynamic);
|
||||||
|
self.emit_u32(span_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(default) = default {
|
||||||
|
self.emit_op(Op::JumpIfSelectSucceeded);
|
||||||
|
let placeholder = self.emit_i32_placeholder();
|
||||||
|
let before: i32 = self.ctx.get_code().len().try_into().unwrap();
|
||||||
|
self.emit_expr(default);
|
||||||
|
let after: i32 = self.ctx.get_code().len().try_into().unwrap();
|
||||||
|
self.patch_i32(placeholder, after - before);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emit_has_attr(&mut self, lhs: RawIrRef<'_>, rhs: &[Attr<RawIrRef<'_>>]) {
|
fn emit_has_attr(&mut self, lhs: RawIrRef<'_>, rhs: &[Attr<RawIrRef<'_>>]) {
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
use fix_codegen::OperandType;
|
||||||
|
use fix_common::StringId;
|
||||||
|
use num_enum::TryFromPrimitive;
|
||||||
|
use string_interner::Symbol as _;
|
||||||
|
|
||||||
|
use crate::OperandData;
|
||||||
|
|
||||||
|
pub(crate) struct BytecodeReader<'a> {
|
||||||
|
bytecode: &'a [u8],
|
||||||
|
pc: usize,
|
||||||
|
inst_start_pc: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> BytecodeReader<'a> {
|
||||||
|
pub(crate) fn new(bytecode: &'a [u8], pc: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
bytecode,
|
||||||
|
pc,
|
||||||
|
inst_start_pc: pc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
#[cfg_attr(debug_assertions, track_caller)]
|
||||||
|
fn read_array<const N: usize>(&mut self) -> [u8; N] {
|
||||||
|
let ret = self.bytecode[self.pc..self.pc + N]
|
||||||
|
.try_into()
|
||||||
|
.expect("read_array failed");
|
||||||
|
self.pc += N;
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn read_op(&mut self) -> fix_codegen::Op {
|
||||||
|
use fix_codegen::Op;
|
||||||
|
self.inst_start_pc = self.pc;
|
||||||
|
let byte = self.bytecode[self.pc];
|
||||||
|
if !likely_stable::likely((0..Op::Illegal as u8).contains(&byte)) {
|
||||||
|
panic!("unknown opcode: {byte:#04x}")
|
||||||
|
}
|
||||||
|
self.pc += 1;
|
||||||
|
unsafe { std::mem::transmute::<u8, Op>(byte) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn read_u8(&mut self) -> u8 {
|
||||||
|
let val = self.bytecode[self.pc];
|
||||||
|
self.pc += 1;
|
||||||
|
val
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn read_u16(&mut self) -> u16 {
|
||||||
|
u16::from_le_bytes(self.read_array())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn read_u32(&mut self) -> u32 {
|
||||||
|
u32::from_le_bytes(self.read_array())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn read_i32(&mut self) -> i32 {
|
||||||
|
i32::from_le_bytes(self.read_array())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn read_i64(&mut self) -> i64 {
|
||||||
|
i64::from_le_bytes(self.read_array())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn read_f64(&mut self) -> f64 {
|
||||||
|
f64::from_le_bytes(self.read_array())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn read_string_id(&mut self) -> StringId {
|
||||||
|
let raw = self.read_u32();
|
||||||
|
StringId(string_interner::symbol::SymbolU32::try_from_usize(raw as usize).unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn read_operand_data<C: crate::VmContext>(&mut self, ctx: &C) -> OperandData {
|
||||||
|
let tag = self.read_u8();
|
||||||
|
let Ok(ty) = OperandType::try_from_primitive(tag)
|
||||||
|
.map_err(|err| panic!("unknown operand tag: {:#04x}", err.number));
|
||||||
|
match ty {
|
||||||
|
OperandType::Const => {
|
||||||
|
let id = self.read_u32();
|
||||||
|
OperandData::Const(ctx.get_const(id))
|
||||||
|
}
|
||||||
|
OperandType::Local => {
|
||||||
|
let layer = self.read_u8();
|
||||||
|
let idx = self.read_u32();
|
||||||
|
OperandData::Local { layer, idx }
|
||||||
|
}
|
||||||
|
OperandType::Builtins => OperandData::Builtins,
|
||||||
|
OperandType::BigInt => {
|
||||||
|
let val = self.read_i64();
|
||||||
|
OperandData::BigInt(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn read_attr_key_data<C: crate::VmContext>(&mut self, ctx: &C) -> crate::AttrKeyData {
|
||||||
|
use fix_codegen::AttrKeyType;
|
||||||
|
let tag = self.read_u8();
|
||||||
|
let ty = AttrKeyType::try_from_primitive(tag)
|
||||||
|
.unwrap_or_else(|err| panic!("unknown key tag: {:#04x}", err.number));
|
||||||
|
match ty {
|
||||||
|
AttrKeyType::Static => crate::AttrKeyData::Static(self.read_string_id()),
|
||||||
|
AttrKeyType::Dynamic => {
|
||||||
|
crate::AttrKeyData::Dynamic(self.read_operand_data(ctx))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn pc(&self) -> usize {
|
||||||
|
self.pc
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_pc(&mut self, pc: usize) {
|
||||||
|
self.pc = pc;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn inst_start_pc(&self) -> usize {
|
||||||
|
self.inst_start_pc
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn bytecode(&self) -> &'a [u8] {
|
||||||
|
self.bytecode
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-13
@@ -1,18 +1,7 @@
|
|||||||
use fix_error::Error;
|
use fix_error::Error;
|
||||||
|
|
||||||
use crate::value::StrictValue;
|
use crate::VmError;
|
||||||
use crate::{NixNum, VmError};
|
|
||||||
|
|
||||||
pub(super) fn vm_err(msg: impl Into<String>) -> VmError {
|
pub(crate) fn vm_err(msg: impl Into<String>) -> VmError {
|
||||||
VmError::Uncatchable(Error::eval_error(msg.into()))
|
VmError::Uncatchable(Error::eval_error(msg.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn get_num(val: StrictValue<'_>) -> Option<NixNum> {
|
|
||||||
if let Some(i) = val.as_inline::<i32>() {
|
|
||||||
Some(NixNum::Int(i as i64))
|
|
||||||
} else if let Some(gc_i) = val.as_gc::<i64>() {
|
|
||||||
Some(NixNum::Int(*gc_i))
|
|
||||||
} else {
|
|
||||||
val.as_float().map(NixNum::Float)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,416 @@
|
|||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
use gc_arena::{Gc, Mutation};
|
||||||
|
|
||||||
|
use crate::{BytecodeReader, NixNum, StepResult, StrictValue, VmError, Value};
|
||||||
|
use crate::VmContextExt;
|
||||||
|
|
||||||
|
impl<'gc> crate::Vm<'gc> {
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_add(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut impl crate::VmContext,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> StepResult<'gc> {
|
||||||
|
if let Some(step) = self.try_force_resolved(1, reader.inst_start_pc(), mc) {
|
||||||
|
return step;
|
||||||
|
}
|
||||||
|
if let Some(step) = self.try_force_resolved(0, reader.inst_start_pc(), mc) {
|
||||||
|
return step;
|
||||||
|
}
|
||||||
|
let rhs = self.pop_stack_forced();
|
||||||
|
let lhs = self.pop_stack_forced();
|
||||||
|
if let (Some(ls), Some(rs)) = (VmContextExt::get_string(ctx, lhs), VmContextExt::get_string(ctx, rhs)) {
|
||||||
|
let ns = Gc::new(mc, crate::NixString::new(format!("{ls}{rs}")));
|
||||||
|
self.push_stack(Value::new_gc(ns));
|
||||||
|
return StepResult::Continue;
|
||||||
|
}
|
||||||
|
let res = numeric_binop(lhs, rhs, mc, i64::wrapping_add, |a, b| a + b);
|
||||||
|
match res {
|
||||||
|
Ok(val) => {
|
||||||
|
self.push_stack(val);
|
||||||
|
StepResult::Continue
|
||||||
|
}
|
||||||
|
Err(e) => e.into_step_result(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_sub(
|
||||||
|
&mut self,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> StepResult<'gc> {
|
||||||
|
self.op_arith(mc, i64::wrapping_sub, |a, b| a - b, reader.inst_start_pc())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_mul(
|
||||||
|
&mut self,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> StepResult<'gc> {
|
||||||
|
self.op_arith(mc, i64::wrapping_mul, |a, b| a * b, reader.inst_start_pc())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn op_arith(
|
||||||
|
&mut self,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
int_op: fn(i64, i64) -> i64,
|
||||||
|
float_op: fn(f64, f64) -> f64,
|
||||||
|
inst_start_pc: usize,
|
||||||
|
) -> StepResult<'gc> {
|
||||||
|
if let Some(step) = self.try_force_resolved(1, inst_start_pc, mc) {
|
||||||
|
return step;
|
||||||
|
}
|
||||||
|
if let Some(step) = self.try_force_resolved(0, inst_start_pc, mc) {
|
||||||
|
return step;
|
||||||
|
}
|
||||||
|
let rhs = self.pop_stack_forced();
|
||||||
|
let lhs = self.pop_stack_forced();
|
||||||
|
let res = numeric_binop(lhs, rhs, mc, int_op, float_op);
|
||||||
|
match res {
|
||||||
|
Ok(val) => {
|
||||||
|
self.push_stack(val);
|
||||||
|
StepResult::Continue
|
||||||
|
}
|
||||||
|
Err(e) => e.into_step_result(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_div(
|
||||||
|
&mut self,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> StepResult<'gc> {
|
||||||
|
if let Some(step) = self.try_force_resolved(1, reader.inst_start_pc(), mc) {
|
||||||
|
return step;
|
||||||
|
}
|
||||||
|
if let Some(step) = self.try_force_resolved(0, reader.inst_start_pc(), mc) {
|
||||||
|
return step;
|
||||||
|
}
|
||||||
|
let rhs = self.pop_stack_forced();
|
||||||
|
let lhs = self.pop_stack_forced();
|
||||||
|
match (get_num(rhs), get_num(lhs)) {
|
||||||
|
(_, Some(NixNum::Int(0))) | (_, Some(NixNum::Float(0.))) => {
|
||||||
|
return VmError::Uncatchable(fix_error::Error::eval_error(
|
||||||
|
"division by zero",
|
||||||
|
))
|
||||||
|
.into_step_result();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
let res = numeric_binop(lhs, rhs, mc, |a, b| a / b, |a, b| a / b);
|
||||||
|
match res {
|
||||||
|
Ok(val) => {
|
||||||
|
self.push_stack(val);
|
||||||
|
StepResult::Continue
|
||||||
|
}
|
||||||
|
Err(e) => e.into_step_result(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_eq(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut impl crate::VmContext,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> StepResult<'gc> {
|
||||||
|
if let Some(step) = self.try_force_resolved(1, reader.inst_start_pc(), mc) {
|
||||||
|
return step;
|
||||||
|
}
|
||||||
|
if let Some(step) = self.try_force_resolved(0, reader.inst_start_pc(), mc) {
|
||||||
|
return step;
|
||||||
|
}
|
||||||
|
let eq = match self.values_equal(ctx) {
|
||||||
|
Ok(eq) => eq,
|
||||||
|
Err(e) => return e.into_step_result(),
|
||||||
|
};
|
||||||
|
self.push_stack(Value::new_inline(eq));
|
||||||
|
StepResult::Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_neq(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut impl crate::VmContext,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> StepResult<'gc> {
|
||||||
|
if let Some(step) = self.try_force_resolved(1, reader.inst_start_pc(), mc) {
|
||||||
|
return step;
|
||||||
|
}
|
||||||
|
if let Some(step) = self.try_force_resolved(0, reader.inst_start_pc(), mc) {
|
||||||
|
return step;
|
||||||
|
}
|
||||||
|
let eq = match self.values_equal(ctx) {
|
||||||
|
Ok(eq) => eq,
|
||||||
|
Err(e) => return e.into_step_result(),
|
||||||
|
};
|
||||||
|
self.push_stack(Value::new_inline(!eq));
|
||||||
|
StepResult::Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_lt(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut impl crate::VmContext,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> StepResult<'gc> {
|
||||||
|
self.compare_values(ctx, reader, mc, Ordering::is_lt)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_gt(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut impl crate::VmContext,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> StepResult<'gc> {
|
||||||
|
self.compare_values(ctx, reader, mc, Ordering::is_gt)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_leq(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut impl crate::VmContext,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> StepResult<'gc> {
|
||||||
|
self.compare_values(ctx, reader, mc, Ordering::is_le)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_geq(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut impl crate::VmContext,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> StepResult<'gc> {
|
||||||
|
self.compare_values(ctx, reader, mc, Ordering::is_ge)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compare_values(
|
||||||
|
&mut self,
|
||||||
|
ctx: &impl crate::VmContext,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
pred: fn(Ordering) -> bool,
|
||||||
|
) -> StepResult<'gc> {
|
||||||
|
if let Some(step) = self.try_force_resolved(1, reader.inst_start_pc(), mc) {
|
||||||
|
return step;
|
||||||
|
}
|
||||||
|
if let Some(step) = self.try_force_resolved(0, reader.inst_start_pc(), mc) {
|
||||||
|
return step;
|
||||||
|
}
|
||||||
|
match self.compare_values_inner(ctx, pred) {
|
||||||
|
Ok(()) => StepResult::Continue,
|
||||||
|
Err(e) => e.into_step_result(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_concat(
|
||||||
|
&mut self,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> StepResult<'gc> {
|
||||||
|
if let Some(step) = self.try_force_resolved(1, reader.inst_start_pc(), mc) {
|
||||||
|
return step;
|
||||||
|
}
|
||||||
|
if let Some(step) = self.try_force_resolved(0, reader.inst_start_pc(), mc) {
|
||||||
|
return step;
|
||||||
|
}
|
||||||
|
let rhs = self.pop_stack_forced();
|
||||||
|
let lhs = self.pop_stack_forced();
|
||||||
|
let Some(l) = lhs.as_gc::<crate::List>() else {
|
||||||
|
return VmError::Uncatchable(fix_error::Error::eval_error(
|
||||||
|
"cannot concatenate: left operand is not a list",
|
||||||
|
))
|
||||||
|
.into_step_result();
|
||||||
|
};
|
||||||
|
let Some(r) = rhs.as_gc::<crate::List>() else {
|
||||||
|
return VmError::Uncatchable(fix_error::Error::eval_error(
|
||||||
|
"cannot concatenate: right operand is not a list",
|
||||||
|
))
|
||||||
|
.into_step_result();
|
||||||
|
};
|
||||||
|
let mut items = smallvec::SmallVec::new();
|
||||||
|
items.extend_from_slice(&l);
|
||||||
|
items.extend_from_slice(&r);
|
||||||
|
self.push_stack(Value::new_gc(Gc::new(mc, crate::List { inner: items })));
|
||||||
|
StepResult::Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_update(
|
||||||
|
&mut self,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
inst_start_pc: usize,
|
||||||
|
) -> StepResult<'gc> {
|
||||||
|
if let Some(step) = self.try_force_resolved(1, inst_start_pc, mc) {
|
||||||
|
return step;
|
||||||
|
}
|
||||||
|
if let Some(step) = self.try_force_resolved(0, inst_start_pc, mc) {
|
||||||
|
return step;
|
||||||
|
}
|
||||||
|
let rhs = self.pop_stack_forced();
|
||||||
|
let lhs = self.pop_stack_forced();
|
||||||
|
let Some(l) = lhs.as_gc::<crate::AttrSet>() else {
|
||||||
|
return VmError::Uncatchable(fix_error::Error::eval_error(
|
||||||
|
"cannot update: left operand is not a set",
|
||||||
|
))
|
||||||
|
.into_step_result();
|
||||||
|
};
|
||||||
|
let Some(r) = rhs.as_gc::<crate::AttrSet>() else {
|
||||||
|
return VmError::Uncatchable(fix_error::Error::eval_error(
|
||||||
|
"cannot update: right operand is not a set",
|
||||||
|
))
|
||||||
|
.into_step_result();
|
||||||
|
};
|
||||||
|
self.push_stack(Value::new_gc(l.merge(&r, mc)));
|
||||||
|
StepResult::Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_neg(&mut self) -> StepResult<'gc> {
|
||||||
|
todo!("implement unary operation");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_not(&mut self) -> StepResult<'gc> {
|
||||||
|
todo!("implement unary operation");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn values_equal(&mut self, ctx: &impl crate::VmContext) -> crate::VmResult<bool> {
|
||||||
|
let rhs = self.pop_stack_forced();
|
||||||
|
let lhs = self.pop_stack_forced();
|
||||||
|
|
||||||
|
if let (Some(a), Some(b)) = (get_num(lhs), get_num(rhs)) {
|
||||||
|
return Ok(match (a, b) {
|
||||||
|
(NixNum::Int(a), NixNum::Int(b)) => a == b,
|
||||||
|
(NixNum::Float(a), NixNum::Float(b)) => a == b,
|
||||||
|
(NixNum::Int(a), NixNum::Float(b)) => a as f64 == b,
|
||||||
|
(NixNum::Float(a), NixNum::Int(b)) => a == b as f64,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if let (Some(a), Some(b)) = (lhs.as_inline::<bool>(), rhs.as_inline::<bool>()) {
|
||||||
|
return Ok(a == b);
|
||||||
|
}
|
||||||
|
if lhs.is::<crate::Null>() && rhs.is::<crate::Null>() {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
if let (Some(a), Some(b)) = (VmContextExt::get_string(ctx, lhs), VmContextExt::get_string(ctx, rhs)) {
|
||||||
|
return Ok(a == b);
|
||||||
|
}
|
||||||
|
if let (Some(a), Some(b)) = (lhs.as_gc::<crate::List>(), rhs.as_gc::<crate::List>()) {
|
||||||
|
if a.inner.len() != b.inner.len() {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
let len = a.inner.len();
|
||||||
|
for (x, y) in a.inner.iter().zip(b.inner.iter()).rev() {
|
||||||
|
self.push_stack(*x);
|
||||||
|
self.push_stack(*y);
|
||||||
|
}
|
||||||
|
for i in 0..len {
|
||||||
|
let eq = self.values_equal(ctx)?;
|
||||||
|
if !eq {
|
||||||
|
let rem = len - 1 - i;
|
||||||
|
self.stack.truncate(self.stack.len() - rem * 2);
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
if let (Some(a), Some(b)) = (lhs.as_gc::<crate::AttrSet>(), rhs.as_gc::<crate::AttrSet>()) {
|
||||||
|
if a.len() != b.len() {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
let len = a.len();
|
||||||
|
for ((k1, v1), (k2, v2)) in a.iter().zip(b.iter()).rev() {
|
||||||
|
if k1 != k2 {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
self.push_stack(*v1);
|
||||||
|
self.push_stack(*v2);
|
||||||
|
}
|
||||||
|
for i in 0..len {
|
||||||
|
let eq = self.values_equal(ctx)?;
|
||||||
|
if !eq {
|
||||||
|
let rem = len - 1 - i;
|
||||||
|
self.stack.truncate(self.stack.len() - rem * 2);
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compare_values_inner(
|
||||||
|
&mut self,
|
||||||
|
ctx: &impl crate::VmContext,
|
||||||
|
pred: fn(Ordering) -> bool,
|
||||||
|
) -> crate::VmResult<()> {
|
||||||
|
let rhs = self.pop_stack_forced();
|
||||||
|
let lhs = self.pop_stack_forced();
|
||||||
|
|
||||||
|
if let (Some(a), Some(b)) = (get_num(lhs), get_num(rhs)) {
|
||||||
|
let ord = match (a, b) {
|
||||||
|
(NixNum::Int(a), NixNum::Int(b)) => a.cmp(&b),
|
||||||
|
(NixNum::Float(a), NixNum::Float(b)) => {
|
||||||
|
a.partial_cmp(&b).unwrap_or(Ordering::Less)
|
||||||
|
}
|
||||||
|
(NixNum::Int(a), NixNum::Float(b)) => (a as f64)
|
||||||
|
.partial_cmp(&b)
|
||||||
|
.unwrap_or(Ordering::Less),
|
||||||
|
(NixNum::Float(a), NixNum::Int(b)) => a
|
||||||
|
.partial_cmp(&(b as f64))
|
||||||
|
.unwrap_or(Ordering::Less),
|
||||||
|
};
|
||||||
|
self.push_stack(Value::new_inline(pred(ord)));
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
if let (Some(a), Some(b)) = (VmContextExt::get_string(ctx, lhs), VmContextExt::get_string(ctx, rhs)) {
|
||||||
|
self.push_stack(Value::new_inline(pred(a.cmp(b))));
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
Err(crate::vm_err("cannot compare these types"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_num(val: StrictValue<'_>) -> Option<NixNum> {
|
||||||
|
if let Some(i) = val.as_inline::<i32>() {
|
||||||
|
Some(NixNum::Int(i as i64))
|
||||||
|
} else if let Some(gc_i) = val.as_gc::<i64>() {
|
||||||
|
Some(NixNum::Int(*gc_i))
|
||||||
|
} else {
|
||||||
|
val.as_float().map(NixNum::Float)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn numeric_binop<'gc>(
|
||||||
|
lhs: StrictValue<'gc>,
|
||||||
|
rhs: StrictValue<'gc>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
int_op: fn(i64, i64) -> i64,
|
||||||
|
float_op: fn(f64, f64) -> f64,
|
||||||
|
) -> crate::VmResult<Value<'gc>> {
|
||||||
|
match (get_num(lhs), get_num(rhs)) {
|
||||||
|
(Some(NixNum::Int(a)), Some(NixNum::Int(b))) => Ok(Value::make_int(int_op(a, b), mc)),
|
||||||
|
(Some(NixNum::Float(a)), Some(NixNum::Float(b))) => Ok(Value::new_float(float_op(a, b))),
|
||||||
|
(Some(NixNum::Int(a)), Some(NixNum::Float(b))) => {
|
||||||
|
Ok(Value::new_float(float_op(a as f64, b)))
|
||||||
|
}
|
||||||
|
(Some(NixNum::Float(a)), Some(NixNum::Int(b))) => {
|
||||||
|
Ok(Value::new_float(float_op(a, b as f64)))
|
||||||
|
}
|
||||||
|
_ => Err(crate::vm_err("cannot perform arithmetic on non-numbers")),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
use crate::{BytecodeReader, PrimOp, StepResult, Value};
|
||||||
|
use fix_builtins::BuiltinId;
|
||||||
|
use num_enum::TryFromPrimitive;
|
||||||
|
|
||||||
|
impl<'gc> crate::Vm<'gc> {
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_load_builtins(&mut self) -> StepResult<'gc> {
|
||||||
|
self.push_stack(self.builtins);
|
||||||
|
StepResult::Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_load_builtin(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> {
|
||||||
|
let Ok(id) = BuiltinId::try_from_primitive(reader.read_u8())
|
||||||
|
.map_err(|err| panic!("unknown builtin id: {}", err.number));
|
||||||
|
self.push_stack(Value::new_inline(PrimOp {
|
||||||
|
id,
|
||||||
|
arity: fix_builtins::BUILTINS[id as usize].1,
|
||||||
|
}));
|
||||||
|
StepResult::Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_mk_pos(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> {
|
||||||
|
let _span_id = reader.read_u32();
|
||||||
|
todo!("MkPos");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_load_repl_binding(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> {
|
||||||
|
let _name = reader.read_string_id();
|
||||||
|
todo!("LoadReplBinding");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_load_scoped_binding(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> {
|
||||||
|
let _name = reader.read_string_id();
|
||||||
|
todo!("LoadScopedBinding");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_concat_strings(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut impl crate::VmContext,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
_mc: &gc_arena::Mutation<'gc>,
|
||||||
|
) -> StepResult<'gc> {
|
||||||
|
let _parts_count = reader.read_u16() as usize;
|
||||||
|
let _force_string = reader.read_u8() != 0;
|
||||||
|
let mut _operands: smallvec::SmallVec<[crate::OperandData; 4]> =
|
||||||
|
smallvec::SmallVec::with_capacity(_parts_count);
|
||||||
|
for _ in 0.._parts_count {
|
||||||
|
_operands.push(reader.read_operand_data(ctx));
|
||||||
|
}
|
||||||
|
todo!("implement ConcatStrings (force parts, coerce to string, concatenate)");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_resolve_path(&mut self, _ctx: &mut impl crate::VmContext) -> StepResult<'gc> {
|
||||||
|
todo!("implement ResolvePath");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
use fix_error::Error;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
BytecodeReader, CallFrame, Closure, Env, StepResult, ThunkState,
|
||||||
|
};
|
||||||
|
use crate::VmContextExt;
|
||||||
|
use gc_arena::{Gc, Mutation, RefLock};
|
||||||
|
|
||||||
|
impl<'gc> crate::Vm<'gc> {
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_call(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut impl crate::VmContext,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> StepResult<'gc> {
|
||||||
|
if let Some(step) = self.try_force_resolved(0, reader.inst_start_pc(), mc) {
|
||||||
|
return step;
|
||||||
|
}
|
||||||
|
if self.call_depth > 10000 {
|
||||||
|
return StepResult::Done(Err(Error::eval_error(
|
||||||
|
"stack overflow; max-call-depth exceeded",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
self.call_depth += 1;
|
||||||
|
let func = self.pop_stack();
|
||||||
|
let arg = reader.read_operand_data(ctx).resolve(mc, self);
|
||||||
|
if let Some(closure) = func.as_gc::<Closure>() {
|
||||||
|
let ip = closure.ip;
|
||||||
|
let n_locals = closure.n_locals;
|
||||||
|
let env = closure.env;
|
||||||
|
if let Some(ref _pattern) = closure.pattern {
|
||||||
|
todo!("pattern call")
|
||||||
|
} else {
|
||||||
|
let new_env = Gc::new(mc, RefLock::new(Env::with_arg(arg, n_locals, env)));
|
||||||
|
self.call_stack.push(CallFrame {
|
||||||
|
pc: reader.pc(),
|
||||||
|
stack_depth: 0,
|
||||||
|
thunk: None,
|
||||||
|
env: self.env,
|
||||||
|
with_env: self.with_env,
|
||||||
|
});
|
||||||
|
reader.set_pc(ip as usize);
|
||||||
|
self.env = new_env;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
todo!("call other types: {func:?}")
|
||||||
|
}
|
||||||
|
StepResult::Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_return(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut impl crate::VmContext,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> StepResult<'gc> {
|
||||||
|
match self.handle_return(reader, ctx, mc) {
|
||||||
|
Some(result) => StepResult::Done(result),
|
||||||
|
None => StepResult::Continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn handle_return<C: crate::VmContext>(
|
||||||
|
&mut self,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
ctx: &C,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Option<std::result::Result<fix_common::Value, Box<Error>>> {
|
||||||
|
let ret_inst_pc = reader.pc() - 1;
|
||||||
|
let Some(CallFrame {
|
||||||
|
pc: ret_pc,
|
||||||
|
stack_depth,
|
||||||
|
thunk,
|
||||||
|
env,
|
||||||
|
with_env,
|
||||||
|
}) = self.call_stack.pop()
|
||||||
|
else {
|
||||||
|
let val = self.pop_stack();
|
||||||
|
return Some(Ok(ctx.convert_value(val)));
|
||||||
|
};
|
||||||
|
reader.set_pc(ret_pc);
|
||||||
|
if let Some(outer_thunk) = thunk {
|
||||||
|
let val = self.pop_stack();
|
||||||
|
match val.restrict() {
|
||||||
|
Ok(val) => {
|
||||||
|
*outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val);
|
||||||
|
if reader
|
||||||
|
.bytecode()
|
||||||
|
.get(ret_pc)
|
||||||
|
.copied()
|
||||||
|
== Some(fix_codegen::Op::Return as u8)
|
||||||
|
{
|
||||||
|
self.push_stack(val.relax());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(inner_thunk) => {
|
||||||
|
let mut state = inner_thunk.borrow_mut(mc);
|
||||||
|
match *state {
|
||||||
|
ThunkState::Pending {
|
||||||
|
ip: inner_ip,
|
||||||
|
env: inner_env,
|
||||||
|
with_env: inner_with_env,
|
||||||
|
} => {
|
||||||
|
self.call_stack.push(CallFrame {
|
||||||
|
pc: ret_pc,
|
||||||
|
stack_depth,
|
||||||
|
thunk: Some(outer_thunk),
|
||||||
|
env,
|
||||||
|
with_env,
|
||||||
|
});
|
||||||
|
self.call_stack.push(CallFrame {
|
||||||
|
pc: ret_inst_pc,
|
||||||
|
stack_depth: 0,
|
||||||
|
thunk: Some(inner_thunk),
|
||||||
|
env: inner_env,
|
||||||
|
with_env: inner_with_env,
|
||||||
|
});
|
||||||
|
*state = ThunkState::Blackhole;
|
||||||
|
reader.set_pc(inner_ip);
|
||||||
|
self.env = inner_env;
|
||||||
|
self.with_env = inner_with_env;
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
ThunkState::Evaluated(val) => {
|
||||||
|
*outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val);
|
||||||
|
if reader
|
||||||
|
.bytecode()
|
||||||
|
.get(ret_pc)
|
||||||
|
.copied()
|
||||||
|
== Some(fix_codegen::Op::Return as u8)
|
||||||
|
{
|
||||||
|
self.push_stack(val.relax());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ThunkState::Apply { func: _, arg: _ } => todo!("force Apply thunk"),
|
||||||
|
ThunkState::Blackhole => {
|
||||||
|
return Some(Err(Error::eval_error(
|
||||||
|
"infinite recursion encountered",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.call_depth -= 1;
|
||||||
|
}
|
||||||
|
self.env = env;
|
||||||
|
self.with_env = with_env;
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
use gc_arena::{Gc, Mutation, RefLock};
|
||||||
|
|
||||||
|
use crate::{BytecodeReader, StepResult, ThunkState, Value};
|
||||||
|
|
||||||
|
impl<'gc> crate::Vm<'gc> {
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_make_thunk(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> StepResult<'gc> {
|
||||||
|
let entry_point = reader.read_u32();
|
||||||
|
let thunk = Gc::new(
|
||||||
|
mc,
|
||||||
|
RefLock::new(ThunkState::Pending {
|
||||||
|
ip: entry_point as usize,
|
||||||
|
env: self.env,
|
||||||
|
with_env: self.with_env,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
self.push_stack(Value::new_gc(thunk));
|
||||||
|
StepResult::Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_make_closure(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> StepResult<'gc> {
|
||||||
|
let entry_point = reader.read_u32();
|
||||||
|
let n_locals = reader.read_u32();
|
||||||
|
let closure = Gc::new(
|
||||||
|
mc,
|
||||||
|
crate::Closure {
|
||||||
|
ip: entry_point,
|
||||||
|
n_locals,
|
||||||
|
env: self.env,
|
||||||
|
pattern: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
self.push_stack(Value::new_gc(closure));
|
||||||
|
StepResult::Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_make_pattern_closure(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> StepResult<'gc> {
|
||||||
|
let entry_point = reader.read_u32();
|
||||||
|
let n_locals = reader.read_u32();
|
||||||
|
let req_count = reader.read_u16() as usize;
|
||||||
|
let opt_count = reader.read_u16() as usize;
|
||||||
|
let has_ellipsis = reader.read_u8() != 0;
|
||||||
|
|
||||||
|
let mut required = smallvec::SmallVec::new();
|
||||||
|
for _ in 0..req_count {
|
||||||
|
required.push(reader.read_string_id());
|
||||||
|
}
|
||||||
|
let mut optional = smallvec::SmallVec::new();
|
||||||
|
for _ in 0..opt_count {
|
||||||
|
optional.push(reader.read_string_id());
|
||||||
|
}
|
||||||
|
let total = req_count + opt_count;
|
||||||
|
let mut param_spans = Vec::with_capacity(total);
|
||||||
|
for _ in 0..total {
|
||||||
|
let name = reader.read_string_id();
|
||||||
|
let span_id = reader.read_u32();
|
||||||
|
param_spans.push((name, span_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
let pattern = Gc::new(
|
||||||
|
mc,
|
||||||
|
crate::PatternInfo {
|
||||||
|
required,
|
||||||
|
optional,
|
||||||
|
ellipsis: has_ellipsis,
|
||||||
|
param_spans: param_spans.into_boxed_slice(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
let closure = Gc::new(
|
||||||
|
mc,
|
||||||
|
crate::Closure {
|
||||||
|
ip: entry_point,
|
||||||
|
n_locals,
|
||||||
|
env: self.env,
|
||||||
|
pattern: Some(pattern),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
self.push_stack(Value::new_gc(closure));
|
||||||
|
StepResult::Continue
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
use fix_error::Error;
|
||||||
|
use gc_arena::Gc;
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
AttrKeyData, AttrSet, BytecodeReader, List, NixString, OperandData, StepResult, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl<'gc> crate::Vm<'gc> {
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_make_attrs(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut impl crate::VmContext,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &gc_arena::Mutation<'gc>,
|
||||||
|
) -> StepResult<'gc> {
|
||||||
|
let count = reader.read_u32() as usize;
|
||||||
|
let mut entries: SmallVec<[AttrEntry; 4]> = SmallVec::with_capacity(count);
|
||||||
|
for _ in 0..count {
|
||||||
|
let key = reader.read_attr_key_data(ctx);
|
||||||
|
let val = reader.read_operand_data(ctx);
|
||||||
|
let _span_id = reader.read_u32();
|
||||||
|
entries.push(AttrEntry { key, val });
|
||||||
|
}
|
||||||
|
let mut kv: SmallVec<[(crate::StringId, Value); 4]> = SmallVec::with_capacity(count);
|
||||||
|
for entry in &entries {
|
||||||
|
let key_sid = match &entry.key {
|
||||||
|
AttrKeyData::Static(sid) => *sid,
|
||||||
|
AttrKeyData::Dynamic(op) => {
|
||||||
|
let v = op.resolve(mc, self);
|
||||||
|
v.as_inline::<crate::StringId>()
|
||||||
|
.expect("dynamic attr key must be a string")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let val = entry.val.resolve(mc, self);
|
||||||
|
kv.push((key_sid, val));
|
||||||
|
}
|
||||||
|
kv.sort_by_key(|(k, _)| *k);
|
||||||
|
let attrs = Gc::new(mc, AttrSet::from_sorted_unchecked(kv));
|
||||||
|
self.push_stack(Value::new_gc(attrs));
|
||||||
|
StepResult::Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_make_empty_attrs(&mut self) -> StepResult<'gc> {
|
||||||
|
self.push_stack(self.empty_attrs);
|
||||||
|
StepResult::Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_select_static(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut impl crate::VmContext,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &gc_arena::Mutation<'gc>,
|
||||||
|
) -> StepResult<'gc> {
|
||||||
|
let _span_id = reader.read_u32();
|
||||||
|
let key = reader.read_string_id();
|
||||||
|
|
||||||
|
if let Some(step) = self.try_force_resolved(0, reader.inst_start_pc(), mc) {
|
||||||
|
return step;
|
||||||
|
}
|
||||||
|
|
||||||
|
let attrs = self.peek_stack(0).restrict().expect("forced");
|
||||||
|
let Some(attrset) = attrs.as_gc::<AttrSet>() else {
|
||||||
|
return StepResult::Done(Err(Error::eval_error(
|
||||||
|
"value is not a set while a set was expected",
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
|
||||||
|
match attrset.lookup(key) {
|
||||||
|
Some(v) => {
|
||||||
|
self.replace_stack(0, v);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
loop {
|
||||||
|
let byte = reader.bytecode()[reader.pc()];
|
||||||
|
if byte == fix_codegen::Op::SelectStatic as u8 {
|
||||||
|
reader.set_pc(reader.pc() + 1 + 4 + 4);
|
||||||
|
} else if byte == fix_codegen::Op::SelectDynamic as u8 {
|
||||||
|
reader.set_pc(reader.pc() + 1 + 4);
|
||||||
|
} else if byte == fix_codegen::Op::JumpIfSelectSucceeded as u8 {
|
||||||
|
reader.set_pc(reader.pc() + 1 + 4);
|
||||||
|
let _ = self.pop_stack();
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
let name = ctx.resolve_string(key);
|
||||||
|
return StepResult::Done(Err(Error::eval_error(format!(
|
||||||
|
"attribute '{name}' missing"
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StepResult::Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_select_dynamic(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut impl crate::VmContext,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &gc_arena::Mutation<'gc>,
|
||||||
|
) -> StepResult<'gc> {
|
||||||
|
let _span_id = reader.read_u32();
|
||||||
|
|
||||||
|
if let Some(step) = self.try_force_resolved(0, reader.inst_start_pc(), mc) {
|
||||||
|
return step;
|
||||||
|
}
|
||||||
|
if let Some(step) = self.try_force_resolved(1, reader.inst_start_pc(), mc) {
|
||||||
|
return step;
|
||||||
|
}
|
||||||
|
|
||||||
|
let key_val = self.stack[self.stack.len() - 1]
|
||||||
|
.restrict()
|
||||||
|
.expect("dynamic key must be forced");
|
||||||
|
let key_sid = if let Some(sid) = key_val.as_inline::<crate::StringId>() {
|
||||||
|
sid
|
||||||
|
} else if let Some(ns) = key_val.as_gc::<NixString>() {
|
||||||
|
ctx.intern_string(ns.as_str())
|
||||||
|
} else {
|
||||||
|
return StepResult::Done(Err(Error::eval_error(
|
||||||
|
"dynamic select key must be a string",
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
|
||||||
|
let attrset_val = self.stack[self.stack.len() - 2].restrict().expect("forced");
|
||||||
|
let Some(attrset) = attrset_val.as_gc::<AttrSet>() else {
|
||||||
|
return StepResult::Done(Err(Error::eval_error(
|
||||||
|
"value is not a set while a set was expected",
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
|
||||||
|
match attrset.lookup(key_sid) {
|
||||||
|
Some(v) => {
|
||||||
|
self.stack.truncate(self.stack.len() - 2);
|
||||||
|
self.push_stack(v);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let name = ctx.resolve_string(key_sid);
|
||||||
|
return StepResult::Done(Err(Error::eval_error(format!(
|
||||||
|
"attribute '{name}' missing"
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StepResult::Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_jump_if_select_succeeded(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> {
|
||||||
|
let offset = reader.read_i32();
|
||||||
|
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
|
||||||
|
StepResult::Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_has_attr(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> {
|
||||||
|
let _n = reader.read_u16() as usize;
|
||||||
|
todo!("HasAttr");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_make_list(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut impl crate::VmContext,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &gc_arena::Mutation<'gc>,
|
||||||
|
) -> StepResult<'gc> {
|
||||||
|
let count = reader.read_u32() as usize;
|
||||||
|
let mut items: SmallVec<[Value; 4]> = SmallVec::with_capacity(count);
|
||||||
|
for _ in 0..count {
|
||||||
|
items.push(reader.read_operand_data(ctx).resolve(mc, self));
|
||||||
|
}
|
||||||
|
let list = Gc::new(mc, List { inner: items });
|
||||||
|
self.push_stack(Value::new_gc(list));
|
||||||
|
StepResult::Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_make_empty_list(&mut self) -> StepResult<'gc> {
|
||||||
|
self.push_stack(self.empty_list);
|
||||||
|
StepResult::Continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct AttrEntry {
|
||||||
|
pub(crate) key: AttrKeyData,
|
||||||
|
pub(crate) val: OperandData,
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
use crate::{BytecodeReader, StepResult};
|
||||||
|
|
||||||
|
impl<'gc> crate::Vm<'gc> {
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_jump_if_false(
|
||||||
|
&mut self,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &gc_arena::Mutation<'gc>,
|
||||||
|
) -> StepResult<'gc> {
|
||||||
|
let offset = reader.read_i32();
|
||||||
|
if let Some(step) = self.try_force_resolved(0, reader.inst_start_pc(), mc) {
|
||||||
|
return step;
|
||||||
|
}
|
||||||
|
let cond = self.pop_stack();
|
||||||
|
if cond.as_inline::<bool>() == Some(false) {
|
||||||
|
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
|
||||||
|
}
|
||||||
|
StepResult::Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_jump_if_true(
|
||||||
|
&mut self,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &gc_arena::Mutation<'gc>,
|
||||||
|
) -> StepResult<'gc> {
|
||||||
|
let offset = reader.read_i32();
|
||||||
|
if let Some(step) = self.try_force_resolved(0, reader.inst_start_pc(), mc) {
|
||||||
|
return step;
|
||||||
|
}
|
||||||
|
let cond = self.pop_stack();
|
||||||
|
if cond.as_inline::<bool>() == Some(true) {
|
||||||
|
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
|
||||||
|
}
|
||||||
|
StepResult::Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_jump(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> {
|
||||||
|
let offset = reader.read_i32();
|
||||||
|
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
|
||||||
|
StepResult::Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_assert(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> {
|
||||||
|
let _raw_idx = reader.read_u32();
|
||||||
|
let _span_id = reader.read_u32();
|
||||||
|
todo!("implement Assert (force TOS)");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
use gc_arena::{Gc, Mutation};
|
||||||
|
|
||||||
|
use crate::{BytecodeReader, StepResult, Value};
|
||||||
|
|
||||||
|
impl<'gc> crate::Vm<'gc> {
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_push_smi(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> {
|
||||||
|
let val = reader.read_i32();
|
||||||
|
self.push_stack(Value::new_inline(val));
|
||||||
|
StepResult::Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_push_bigint(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> StepResult<'gc> {
|
||||||
|
let val = reader.read_i64();
|
||||||
|
self.push_stack(Value::new_gc(Gc::new(mc, val)));
|
||||||
|
StepResult::Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_push_float(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> {
|
||||||
|
let val = reader.read_f64();
|
||||||
|
self.push_stack(Value::new_float(val));
|
||||||
|
StepResult::Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_push_string(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> {
|
||||||
|
let sid = reader.read_string_id();
|
||||||
|
self.push_stack(Value::new_inline(sid));
|
||||||
|
StepResult::Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_push_null(&mut self) -> StepResult<'gc> {
|
||||||
|
self.push_stack(Value::new_inline(crate::Null));
|
||||||
|
StepResult::Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_push_true(&mut self) -> StepResult<'gc> {
|
||||||
|
self.push_stack(Value::new_inline(true));
|
||||||
|
StepResult::Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_push_false(&mut self) -> StepResult<'gc> {
|
||||||
|
self.push_stack(Value::new_inline(false));
|
||||||
|
StepResult::Continue
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
pub(crate) mod literals;
|
||||||
|
pub(crate) mod variables;
|
||||||
|
pub(crate) mod closures;
|
||||||
|
pub(crate) mod calls;
|
||||||
|
pub(crate) mod collections;
|
||||||
|
pub(crate) mod arithmetic;
|
||||||
|
pub(crate) mod control;
|
||||||
|
pub(crate) mod with_scope;
|
||||||
|
pub(crate) mod builtins_misc;
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
use crate::{BytecodeReader, Mutation, StepResult, Value};
|
||||||
|
|
||||||
|
impl<'gc> crate::Vm<'gc> {
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_load_local(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> {
|
||||||
|
let idx = reader.read_u32() as usize;
|
||||||
|
self.push_stack(self.env.borrow().locals[idx]);
|
||||||
|
StepResult::Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_load_outer(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> {
|
||||||
|
let layer = reader.read_u8();
|
||||||
|
let idx = reader.read_u32() as usize;
|
||||||
|
let mut cur = self.env;
|
||||||
|
for _ in 0..layer {
|
||||||
|
let prev = cur.borrow().prev.expect("LoadOuter: env chain too short");
|
||||||
|
cur = prev;
|
||||||
|
}
|
||||||
|
let val = cur.borrow().locals[idx];
|
||||||
|
self.push_stack(val);
|
||||||
|
StepResult::Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_store_local(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> StepResult<'gc> {
|
||||||
|
let idx = reader.read_u32() as usize;
|
||||||
|
let val = self.pop_stack();
|
||||||
|
self.env.borrow_mut(mc).locals[idx] = val;
|
||||||
|
StepResult::Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_alloc_locals(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> StepResult<'gc> {
|
||||||
|
let count = reader.read_u32() as usize;
|
||||||
|
self.env
|
||||||
|
.borrow_mut(mc)
|
||||||
|
.locals
|
||||||
|
.extend(std::iter::repeat_n(Value::default(), count));
|
||||||
|
StepResult::Continue
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
use fix_error::Error;
|
||||||
|
use fix_common::Symbol;
|
||||||
|
|
||||||
|
use crate::{BytecodeReader, CallFrame, StepResult, WithEnv};
|
||||||
|
use gc_arena::Gc;
|
||||||
|
|
||||||
|
impl<'gc> crate::Vm<'gc> {
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_push_with(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut impl crate::VmContext,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &gc_arena::Mutation<'gc>,
|
||||||
|
) -> StepResult<'gc> {
|
||||||
|
let env = reader.read_operand_data(ctx).resolve(mc, self);
|
||||||
|
let scope = Gc::new(
|
||||||
|
mc,
|
||||||
|
WithEnv {
|
||||||
|
env,
|
||||||
|
prev: self.with_env,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
self.with_env = Some(scope);
|
||||||
|
StepResult::Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_pop_with(&mut self) -> StepResult<'gc> {
|
||||||
|
let Some(scope) = self.with_env else {
|
||||||
|
unreachable!("no with_scope to pop");
|
||||||
|
};
|
||||||
|
self.with_env = scope.prev;
|
||||||
|
StepResult::Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_prepare_with(&mut self) -> StepResult<'gc> {
|
||||||
|
self.call_stack.push(CallFrame {
|
||||||
|
pc: usize::MAX,
|
||||||
|
stack_depth: 0,
|
||||||
|
thunk: None,
|
||||||
|
env: self.env,
|
||||||
|
with_env: self.with_env,
|
||||||
|
});
|
||||||
|
StepResult::Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn op_lookup_with(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut impl crate::VmContext,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &gc_arena::Mutation<'gc>,
|
||||||
|
) -> StepResult<'gc> {
|
||||||
|
let name = reader.read_string_id();
|
||||||
|
|
||||||
|
let Some(&WithEnv { env, prev }) = self.with_env.as_deref() else {
|
||||||
|
let Some(CallFrame { with_env, .. }) = self.call_stack.pop() else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
self.with_env = with_env;
|
||||||
|
return StepResult::Done(Err(Error::eval_error(format!(
|
||||||
|
"undefined variable '{}'",
|
||||||
|
Symbol::from(ctx.resolve_string(name))
|
||||||
|
))));
|
||||||
|
};
|
||||||
|
self.push_stack(env);
|
||||||
|
if let Some(step) = self.try_force_resolved(0, reader.inst_start_pc(), mc) {
|
||||||
|
return step;
|
||||||
|
}
|
||||||
|
|
||||||
|
let env = self.pop_stack().as_gc::<crate::AttrSet>().unwrap();
|
||||||
|
let Some(val) = env.lookup(name) else {
|
||||||
|
reader.set_pc(reader.inst_start_pc());
|
||||||
|
self.with_env = prev;
|
||||||
|
return StepResult::Continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
self.push_stack(val);
|
||||||
|
let Some(CallFrame { with_env, .. }) = self.call_stack.pop() else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
self.with_env = with_env;
|
||||||
|
StepResult::Continue
|
||||||
|
}
|
||||||
|
}
|
||||||
+255
-1018
File diff suppressed because it is too large
Load Diff
+1
-2
@@ -87,8 +87,7 @@ impl Evaluator {
|
|||||||
) -> Result<fix_common::Value> {
|
) -> Result<fix_common::Value> {
|
||||||
let root = self.downgrade(source, extra_scope)?;
|
let root = self.downgrade(source, extra_scope)?;
|
||||||
let ip = fix_codegen::compile_bytecode(root.as_ref(), self);
|
let ip = fix_codegen::compile_bytecode(root.as_ref(), self);
|
||||||
let vm = Vm::new(self, ip, force_mode);
|
Vm::run(self, ip, force_mode)
|
||||||
vm.run()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_binding(
|
pub fn add_binding(
|
||||||
|
|||||||
+1
-1
@@ -87,7 +87,7 @@ fn run_repl(eval: &mut Evaluator) -> Result<()> {
|
|||||||
let mut scope = HashSet::new();
|
let mut scope = HashSet::new();
|
||||||
const RE: ere::Regex<3> = ere::compile_regex!("^[ \t]*([a-zA-Z_][a-zA-Z0-9_'-]*)[ \t]*(.*)$");
|
const RE: ere::Regex<3> = ere::compile_regex!("^[ \t]*([a-zA-Z_][a-zA-Z0-9_'-]*)[ \t]*(.*)$");
|
||||||
loop {
|
loop {
|
||||||
let readline = rl.readline("nix-js-repl> ");
|
let readline = rl.readline("fix-repl> ");
|
||||||
match readline {
|
match readline {
|
||||||
Ok(line) => {
|
Ok(line) => {
|
||||||
if line.trim().is_empty() {
|
if line.trim().is_empty() {
|
||||||
|
|||||||
Reference in New Issue
Block a user