#vm #register #store #frame #instructions #run-time #value

fn_vm

一个轻量级的基于框架的虚拟机,作为 rigz_vm 的基础。

12 个稳定版本 (4 个主要版本)

4.0.0 2024年7月17日
3.0.1 2024年7月17日
2.2.0 2024年7月15日
1.5.0 2024年7月13日
0.1.0 2024年7月1日

#184 in Rust 模式

Download history 355/week @ 2024-06-28 50/week @ 2024-07-05 1486/week @ 2024-07-12 94/week @ 2024-07-19 108/week @ 2024-07-26 12/week @ 2024-08-02

1,700 每月下载量

MIT 许可证

55KB
1K SLoC

fn_vm

一个轻量级的基于框架/寄存器的虚拟机,可用于运行函数。

All problems in computer science can be solved by another level of indirection... 
Except for the problem of too many layers of indirection.

灵感来自 iridium

工作原理

这基于这样的想法:如果我的虚拟机和抽象语法树使用相同的底层类型来表示值,但不仅仅是字节数组,我将能够构建一个更简单的运行时来连接这两个部分。此外,我不想实现我在大多数其他虚拟机中看到的所有指令,因为我目前只需要调用几个 Rust 函数。

fn_vm 可以存储任何实现 VMValue 特质的值,所有原始类型都有一个实现。

默认情况下,所有操作都在第一个框架中发生;每个框架都是一个函数定义,可以通过调用指令进入/退出。一旦使用值寄存器,该寄存器就会被删除(除非指令旨在为后续步骤(如 COPY)提供寄存器)

因此,您获得的是一些类型、一个 builder 和虚拟机(由框架组成)。

pub type VMFunction<T> = fn(&mut VM<T>, args: Vec<T>) -> Result<Option<T>, VMError>;
pub type HCFFunction<T> = fn(&mut VM<T>) -> Result<(), VMError>;


pub trait LazyCompiler<T: VMValue<T>> {
  fn compile(&self, length: Length) -> Result<Frame<T>, VMError>;
}

#[derive(Clone, Debug, PartialEq)]
pub enum FrameValue<T: VMValue<T>> {
  Value(T),
  Frame(Frame<T>),
}

#[derive(Clone, Debug, PartialEq)]
pub struct Frame<T: VMValue<T>> {
  pub instructions: Vec<u8>,
  pub pc: usize,
  pub locals: IndexMap<String, FrameValue<T>>,
  pub parent: Option<usize>,
}

pub struct VM<T: VMValue<T>> {
    pub fp: usize,
    pub frames: Vec<Frame<T>>,
    pub functions: Vec<VMFunction<T>>,
    pub registers: HashMap<Length, T>,
    pub index_registers: HashMap<Length, Length>,
    pub bit_registers: HashMap<Length, bool>,
    pub stack: Vec<usize>,
    pub lazy_compiler: Option<Box<dyn LazyCompiler<T>>>,
    pub program_data: Bytes,
    pub hcf_trigger: Option<HCFFunction<T>>,
}

pub trait VMValue<T: VMValue<T>>: Display + Debug + Clone + PartialEq + Logical<T> + Add<Output=T> + Mul<Output=T> + Div<Output=T> + PartialOrd + Sub<Output=T> + BitAnd<Output=T> + BitOr<Output=T> + BitXor<Output=T> + Rem<Output=T> + Not<Output=T> + Neg<Output=T> + Shr<Output=T> + Shl<Output=T> {
  
}

指令

  • r 是输入寄存器,通常输出寄存器是传入的第一个寄存器
  • a、b 或 i 前缀是布尔或索引寄存器。
  • f# 是框架索引
  • l# 是调用延迟编译器的索引

变量

  • let: 默认不可变变量
  • mut: 可变变量
  • frame: 可变框架,其值等于一个变量

此虚拟机提供了以下指令

  • NOP: 无操作,将程序计数器移到下一个指令。 NOP
  • ADD: 将两个寄存器相加,存储输出。 ADD r1 r2
  • MOD: 两个寄存器求模,存储输出。 MOD r1 r2
  • SUB: 从两个寄存器中减去,存储输出。 SUB r1 r2
  • 乘法:将两个寄存器的值相乘,并将结果存储。 MUL r1 r2
  • 除法:将两个寄存器的值相除,并将结果存储。 DIV r1 r2
  • 右移:将两个寄存器的值右移,并将结果存储。 SHR r1 r2
  • 左移:将两个寄存器的值左移,并将结果存储。 SHL r1 r2
  • 异或:将两个寄存器的值进行按位异或操作,并将结果存储。 XOR r1 r2
  • 与运算:将两个寄存器的值进行按位与操作,并将结果存储。 BAND r1 r2
  • 或运算:将两个寄存器的值进行按位或操作,并将结果存储。 BOR r1 r2
  • 异或运算:将两个寄存器的值进行按位异或操作,并将结果存储。 BXOR r1 r2
  • 逻辑异或:将两个寄存器的值进行逻辑异或操作,并将结果存储。 LXOR r1 r2
  • 逻辑与:将两个寄存器的值进行逻辑与操作,并将结果存储。 LAND r1 r2
  • 逻辑或:将两个寄存器的值进行逻辑或操作,并将结果存储。 LOR r1 r2
  • 非:对寄存器的值进行非操作,并将结果存储。 NOT r1
  • 布尔非:对寄存器的值进行布尔非操作,并将结果存储。 BNOT r1
  • 负:对寄存器的值进行负操作,并将结果存储。 NEG r1
  • 与运算:将两个寄存器的值进行与操作,并将结果存储。 AND r1 r2
  • 或运算:将两个寄存器的值进行或操作,并将结果存储。 OR r1 r2
  • 等于:比较两个寄存器的值,并将结果存储。 EQ r1 r2
  • 等于(位比较):比较两个位寄存器的值,并将结果存储。 BEQ br1 br2
  • 等于(索引比较):比较两个索引寄存器的值,并将结果存储。 IEQ ir1 ir2
  • 不等于:比较两个寄存器的值,并将结果存储。 NEQ r1 r2
  • 不等于(位比较):比较两个位寄存器的值,并将结果存储。 BNEQ br1 br2
  • 不等于(索引比较):比较两个索引寄存器的值,并将结果存储。 INEQ ir1 ir2
  • 反转:将寄存器中的值进行反转,并将结果存储。 REV r1
  • 小于:比较两个寄存器的值,并将结果存储。 LT r1 r2
  • 小于等于:比较两个寄存器的值,并将结果存储。 LTE r1 r2
  • 大于:比较两个寄存器的值,并将结果存储。 GT r1 r2
  • 大于等于:比较两个寄存器的值,并将结果存储。 GTE r1 r2
  • 复制:将一个寄存器的值复制到另一个寄存器中, COPY r1 r2
  • 复制索引寄存器:从索引寄存器到。 CIR ir1 ir2
  • 复制位寄存器:从位寄存器到。 CBR br1 br2
  • 调用:调用一个帧,用于调用函数。 CALL f#
  • 调用索引寄存器:调用存储在索引寄存器#中的帧,用于调用由CFR创建的帧。 CALLR ir1
  • 返回:从一个帧中返回, RET <from> <to>
  • 获取局部值: GLV r1 <name>
  • MVL: 从当前帧将局部值移动到fr1(跳过存在性检查),MVL fr1 <name>
  • CPL: 从当前帧复制局部值到fr1(跳过存在性检查),CPL fr1 <name>
  • CPM: 从当前帧复制局部值到fr1(跳过存在性检查),作为可变的。 CPM fr1 <name>
  • DLV: 从帧中删除局部值,DLV fr1 <name>
  • DFV: 从当前帧中删除局部值,DLV <name>
  • SLR: 从寄存器设置局部值,SLR r1 <name>
  • SMR: 从寄存器设置局部可变值,SMR r1 <name>
  • SLF: 从帧寄存器设置局部可变值,SMF fr1 <name>
  • CFR: 创建帧,CFR ir1 <len> [instructions]
  • DFR: 删除帧(软删除,否则其他所有帧索引都会损坏),重置帧并将指令设置为IVF。 DFR f#
  • DFI: 删除索引寄存器中的帧。 DFI ir1 ,// ir1, 删除帧
  • FN: 调用提供的函数,FN <op> <len> [registers] <out_register>
  • IVF: 无效帧,不直接使用。调用DFR的结果,IVF
  • PSH: 将r1推入value_stack,PSH r1
  • PSHV: 将r1推入value_stack,PSHV <value>
  • POP: 从value_stack弹出到r1,POP r1
  • HCF: 停止并引发火灾,停止VM(如果接收到此命令,则可以传递可选的hcf_trigger)。 HCF
  • LZY: 调用懒编译器以生成包含下一组指令的帧,并将fp移动到该帧,需要将lazy_compiler设置为true。 LZY l#
    pub trait LazyCompiler<T: Clone + PartialEq> {
        fn compile(&self, length: Length) -> Result<Frame<T>, VMError>;
    }
    

使用IVD作为任何无效命令的默认命令。

FN

这是迄今为止最复杂的指令,因为它实际上是一个传递到你的指令。

以下是一个使用构建器调用它的示例

注意:into()用于将长度转换为长度,从small_len到支持up to usize args。

fn run() {
    let vm = VMBuilder::new()
            .set_value(5.into(), 42) // store 42 in r5
            .set_value(4.into(), 42) // store 42 in r4
        .add_function_instruction(0, vec![5.into(), 4.into()], 3.into())
        .add_function(move |_, args| {
            let a = args[0];
            let b = args[1];
            Ok(Some(a + b))
        })
        .build();
    vm.run().unwrap();
    assert_eq!(vm.registers[3], 84);
}

依赖项

~2–2.8MB
~48K SLoC