1个稳定版本
1.0.0 | 2024年4月1日 |
---|
75 / 模拟器
100KB
2K SLoC
BUND虚拟机
在探讨BUND虚拟机的本质之前,让我们首先了解虚拟机的一般概念。
什么是“虚拟机”
虚拟机代表了一种计算范式,它解释特定的语言以执行计算。在计算历史中已经开发了大量的虚拟机实现,采用了各种计算方法。一些虚拟机在计算前使用数据存储原语,如存储寄存器,这些在业界被广泛采用。另一个基本元素是“栈”,在特定的架构中,它是用于保存过程调用历史的,而在其他架构中,它可以用于数据存储和调用历史。第三种选择是使用栈进行数据存储。在提出的BUND虚拟机中,这种方法构成了架构基础。大致上被归类为实施连续计算范式的虚拟机,BUND虚拟机是这个计算方法的一个例子。
近90年来,我们知道图灵机,这是一种由英国数学家Alan Turing首次提出的数学计算模型。图灵机的基本原理是一条无限长的带子,被分割成离散的单元格。每个单元格可以存储一些数据。图灵机可以从单元格中读取数据,将数据写入单元格,向左或向右移动到前一个或下一个单元格,或停止执行。这个简单的模型,尽管如此,为大多数现代计算设备奠定了基础。虽然图灵机的架构简约而优雅,但在现实中构建它几乎是不可能的。其中一个限制是需要“无限长的带子”。但是,虽然创建真实的“金属”图灵机是不可能的任务,通过一些近似,我们可以构建一个支持数据隔离的实际的基于栈的图灵机。BUND虚拟机的第一个主要架构元素是一个无限长的栈,形成了一个环。栈没有开始或结束,只有一个“当前位置”。机器可以在“纸带”上左右移动,而不触及终点。BUND虚拟机可以使用栈执行特定的基本操作
- 将数据推入当前位置。在此操作后,当前位置指向新推入的数据元素。
- 从栈中拉取数据。栈的大小减少。
- 查看当前位置的数据,而不移除。
- 左右循环旋转栈,改变栈中的当前位置。
- 当前位置存在重复数据;在此操作后,同一数据将位于堆栈相邻的单元格中。
现在让我们开始探索BUND的功能。
使用存储在环形环结构中的数据执行操作将帮助我们通过使用“环形缓冲区”来近似“无限带”来构建实际的转换机。所有具有单个堆栈的连接计算模型都面临相同的问题:弱数据隔离。如果我们将多个计算上下文的数据存储在同一个堆栈中,我们可以避免来自恶意代码的数据保护不足。BUND虚拟机引入了“堆栈中的堆栈”这一范式,为不同的计算分支强制执行数据隔离。虚拟机的根堆栈是一个环形堆栈,它不存储数据。它存储环形堆栈上的引用,每个单元格存储一个动态数据元素。与数据存储相关的操作在逻辑上分为两个级别
与存储数据引用的堆栈进行操作。与存储实际数据的环形堆栈中的数据进行操作。每个数据堆栈通过第一级,即“堆栈中的堆栈”进行引用。
“堆栈中的堆栈”和“堆栈中的数据”操作的原则本质上相同。
基于这些原则构建虚拟机的理由如下
- 我正在尝试在一个与图灵机背后的思想“精神”上接近的执行模型中,对虚拟机和连接算法进行实验。
- 我正在尝试在提供数据隔离的执行模型中实验连接算法。
- 我将连接编程中遗留的思想带入到现代计算的领域。
- BUND虚拟机是连接函数式编程语言BUND的基础。
虽然BUND虚拟机的“堆栈中的堆栈”旨在存储数据元素,但让我们讨论它可以存储和使用哪些数据元素。单个数据元素是一个可以存储任何支持的数据类型的动态结构。BUND的第一个定义是一个针对动态类型数据类型处理的虚拟机。目前,VM支持以下数据类型:64位整数和浮点数、字符串、布尔值和列表。除了这些“基本”数据类型之外,以下数据类型具有特殊含义,并改变代码执行流程
- CALL - 包含与立即堆栈执行应用、延迟堆栈执行应用或已注册lambda函数执行相关的信息的数据类型。
- LAMBDA - 包含虚拟机执行器调用Lambda函数时执行的指令的数据类型。
虽然BUND虚拟机对即时执行的支持有限,但它对大多数函数或原语调用使用延迟执行。区别在于,如果函数或原语定义在特殊的适用调用表中,这个表在调用时仅仅是名称与适用函数之间的引用,那么在这个调用中定义的函数将明确地立即执行。表中描述的函数以及因此预定为立即执行的函数通常是指改变虚拟机自身状态的作业。定义在通用函子表和用户定义的lambda函数中的原语是为延迟执行设计的。如果函数需要延迟执行,虚拟机将暂时将这个函数的引用放入调用栈中。当虚拟机接收到执行调用时,如果这个函数已在适用表中注册,虚拟机将从调用栈中取出该函数的引用并执行该函数。每次虚拟机执行已注册的内部函数时,它都会引用该函数的属性,这些属性需要从栈中取出一定数量的参数并传递给函数。将栈参数转换为函数参数的选项在函数签名中定义,并且没有额外的参数 - 这将保持栈不变,函数本身将以适合函数的方式与当前栈交互。JUSTONE - 从栈中取一个值并将其作为参数传递给函数。JUSTTWO - 虚拟机将从栈中移除两个值并将其传递给函数。TAKEALL - 虚拟机将取出之前推入的值放入当前栈中并传递给函数。如果函数返回值,虚拟机将把它推入当前栈。
展示其工作原理。
每个虚拟机都需要一些输入指令,它可以执行这些指令来执行用户需要的操作。目前,我选择JSON作为主要指令格式。我将在未来添加其他更优化的格式,但现在它已经足够了。展示机器如何操作的第一个经典例子是著名的“Hello World”。
{"type": "STRING", "value": "Hello world!"}
{"type": "CALL", "value": "print"}
第一条指令将通过“value”键传递的值作为类型为“STRING”的动态元素推入栈中。第二条指令将查找名为“print”的调用。这个调用将在适用表中找到,因此它将立即执行。调用签名要求函数参数只有一个值,因此在调用此函数之前,将从一个值中取出一个值并传递给函数。
要执行这些指令,首先,您必须在构建主机上安装Rust工具链。此时,我正在使用Rust版本1.76.0,因此您应使用此版本或更高版本。为了获取所有必需的库,您必须连接到互联网。首先,从https://github.com/vulogov/bundvm检出BUND VM的最新版本。
git clone [email protected]:vulogov/bundvm.git
cd bundvm
make
构建成功后,您将在./target/debug目录中有一个可执行的bundvm。您可以使用分发中的指令,或者在任何选择的文件中编写两行的JSON文件。然后执行这些指令。
./target/debug/bundvm -dd vm --file {I mask full path to the file with instructions}/examples/helloworld.json
执行指令的注意事项
- 通过--file传递的文件路径必须是完整路径,而不是相对路径。您可以从STDIN传递执行指令,然后您必须传递一个--stdin参数而不是--file,或者您可以将您的指令放在Web上的某个位置,并通过vm选项的--url参数传递指令的URL。
- -d(或 -dd 或 -ddd)将指示虚拟机显示来自虚拟机的信息性、调试或跟踪级别的消息。如果您想了解虚拟机的工作原理,请使用此选项。
此命令的输出将
[2024-03-30T21:00:36Z DEBUG bundvm::cmd::setloglevel] Set loglevel=debug
[2024-03-30T21:00:36Z DEBUG bundvm::cmd::setloglevel] setloglevel::setloglevel() reached
[2024-03-30T21:00:36Z DEBUG bundvm::stdlib] Running STDLIB init
[2024-03-30T21:00:36Z DEBUG bundvm::cmd] ZENOH bus set to: tcp/127.0.0.1:7447
[2024-03-30T21:00:36Z DEBUG bundvm::cmd] ZENOH listen set to default
[2024-03-30T21:00:36Z DEBUG bundvm::cmd] ZENOH configured in PEER mode
[2024-03-30T21:00:36Z DEBUG bundvm::cmd] ZENOH config is OK
[2024-03-30T21:00:36Z DEBUG bundvm::cmd] Configuring VM
[2024-03-30T21:00:36Z DEBUG bundvm::vm_stdlib] Init VM standard library
[2024-03-30T21:00:36Z DEBUG bundvm::vm_stdlib::stdlib_print] Init VM standard library: print
[2024-03-30T21:00:36Z DEBUG bundvm::vm_stdlib::stdlib_stack] Init VM standard library: stack
[2024-03-30T21:00:36Z DEBUG bundvm::vm_stdlib::stdlib_terminate_and_execute] Init VM standard library: terminate_and_execute
[2024-03-30T21:00:36Z DEBUG bundvm::vm_stdlib::stdlib_lambda] Init VM standard library: lambda
[2024-03-30T21:00:36Z DEBUG bundvm::cmd] VM is ready
[2024-03-30T21:00:36Z DEBUG bundvm::cmd] Execute VM instructions
[2024-03-30T21:00:36Z DEBUG bundvm::cmd::bund_vm] If there is an error, VM will enter into interactive shell: false
[2024-03-30T21:00:36Z DEBUG bundvm::stdlib::vm_execute] Received 79 bytes of instructions
[2024-03-30T21:00:36Z DEBUG bundvm::vm::vm_call] BUND VM: applicative found: print
[2024-03-30T21:00:36Z DEBUG bundvm::vm::vm_call] BUND VM: calling applicative: print
Hello world!
如何与虚拟机进行交互
您可以通过输入shell命令请求交互式REPL提示并与虚拟机交互。BUND shell是一个LISP解释器,对于敢于尝试的用户,它提供了LISP的全部功能来自动化和探索BUND虚拟机。我不会冒险教你LISP课程,但我会告诉你可以使用的命令来更改机器状态和执行原语,最终执行lambda。
我在虚拟机shell中的第一个交互命令
首先,如何执行shell并检查虚拟机状态
% ./target/debug/bundvm -d shell
____ _ _ _ _ ____ _ ___ ___
| __ ) | | | | | \ | | | _ \ / | / _ \ / _ \
| _ \ | | | | | \| | | | | | | | | | | | | | | |
| |_) | | |_| | | |\ | | |_| | | | _ | |_| | _ | |_| |
|____/ \___/ |_| \_| |____/ |_| (_) \___/ (_) \___/
[2024-03-30T21:20:56Z WARN bundvm::cmd::bund_shell] No previous history discovered
[BUND > (full-status)
╭─────────────────────────────────────┬───────────────╮
│ Message ┆ (full-status) │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ Instruction ┆ N/A │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ Number of stacks ┆ 1 │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ Size of call stack ┆ 0 │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ Number of functors ┆ 0 │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ Lambda scaffolding ┆ 0 │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ Registered lambdas ┆ 0 │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ Number of elements in current stack ┆ 0 │
╰─────────────────────────────────────┴───────────────╯
╭───────────────────────╮
│ List of stacks │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ TdCpag2wTL-qG6BxLgRzG │
╰───────────────────────╯
╭───────────────────────────╮
│ Elements in current stack │
╰───────────────────────────╯
╭────────────────────────╮
│ Elements in call stack │
╰────────────────────────╯
命令(full-status)将显示以下信息
- 堆栈数量。这是它的意思——"堆栈堆"结构中的堆栈数量。每个堆栈是"数据堆栈"。
- 调用堆栈大小。这个数字表示为延迟执行存储的CALL元素的数量。
- 函数子数量。用于延迟执行的功能引用表的尺寸。
- Lambda结构。正在创建但尚未完成的lambda函数的当前数量。
- 已注册的lambda。已最终确定并注册的lambda函数的数量。
- 当前堆栈中的元素数量。当前"数据堆栈"中的数据元素数量。
然后您将看到"数据堆栈"元素的名称列表。接下来,您将看到当前"数据堆栈"中元素的内容转储。最后,您将看到调用堆栈中的元素。
您可以在任何时候调用(full-status)。
我的其他shell命令是什么?
命令 | 描述 | 示例 |
---|---|---|
(push-integer number) | 将整数值推送到当前"数据堆栈"的位置 | (push-integer 42) |
(push-float number) | 将浮点值推送到当前"数据堆栈"的位置 | (push-float 3.14) |
(push-string string) | 将字符串值推送到当前"数据堆栈"的位置 | (push-string "Hello world") |
(push-true) | 将布尔值true推送到当前"数据堆栈"的位置 | (push-true) |
(push-false) | 将布尔值false推送到当前"数据堆栈"的位置 | (push-false) |
(push-list) | 将空列表值推送到当前"数据堆栈"的位置 | (push-list) |
(push-separator) | 将数据分隔符推送到当前"数据堆栈"的位置。当虚拟机发现数据分隔符时,它将自动创建一个新的匿名堆栈或停止处理TAKEALL参数 | (push-separator) |
(push-lambda) | 将新的空lambda推入lambda结构中 | (push-lambda) |
(push-call) | 将新的CALL推入调用堆栈 | (push-call "printall") |
(pull) | 从当前"数据堆栈"的当前位置移除并显示值 | (pull) |
(pull-raw) | 从当前"数据堆栈"的当前位置移除并转储值 | (pull-raw) |
(vm-clear) | 将虚拟机重置为原始和空状态 | (vm-clear) |
(vm-call) | 执行调用堆栈中的CALL | (vm-call "printall") |
(vm-exec) | 执行调用堆栈、应用或函数子或lambda中的CALL | (vm-exec "printall") |
(display-vm-lambdas) | 打印已注册的lambda列表 | (display-vm-lambdas) |
(display-vm-lambda name) | 打印已注册lambda的内容 | (display-vm-lambda "answer") |
依赖关系
~91MB
~1.5M SLoC