我想为一种非常简单的语言开发一个非常简单的编译器。编译器将把代码编译成一些基本的字节码。稍后,虚拟机程序将运行字节码,并执行该程序。
关于字节码实际上是什么,我有几个问题:
print命令将在字节码中转换为1。稍后,当VM运行字节码时,字节码中的每个元素将被转换为程序中的某些执行。例如,如果VM扫描字节码并根据1来,它将打印某些内容。这种做法常见吗?有效吗?什么是最常见的方法,如何和什么字节码构成?发布于 2014-03-03 11:39:18
发布于 2014-03-03 12:33:51
实际上,您所做的是设计新的计算机指令,以及作为程序实现的新计算机(您的虚拟机)。
通常,指令(即字节码是什么)有一个初始部分,由解释器检查,以确定下一步将发生什么。例如,正如您所建议的,1可能意味着打印以下字符串。2可能意味着添加以下两个整数。
当您计算出您需要的指令时,您会发现您需要一种保存数据的方法(称为内存组织),以及一种组织指令的方法。指令是做非常复杂的事情,还是非常简单的事情?如果他们做复杂的事情(打印这个字符串),那么就需要很多,但是数据可以用一种更简单的方式来组织。如果指令做简单的事情(将这个整数加载到工作区域1),那么指令就会减少,但是数据组织将需要支持更丰富的结构和更多的数据类型。
一开始很简单,比如说1字节的操作码.当你解决这个问题的时候,你会看到很多版本的代码。
好好享受吧。你将比你想象的更多地了解计算机的工作原理。
发布于 2014-03-03 14:17:33
对于一个简单的例子,让我们来看看PHP的Zend引擎。这是一个相对简单的VM,完全在内存中工作,因此不必涉及序列化等。
在PHP中,字节码由167个不同的操作码组成,它们可以在一个标题中找到:http://lxr.php.net/xref/PHP_主干/zend/zend_vm_opcodes.h
所有这些操作码都存储在数据结构中,并以数组的形式存储一些元信息(表示为指向第一个元素的指针)。
struct _zend_op_array {
/* ... */
zend_op *opcodes;
/* ... */
}然后,每个操作都将它自己的数据结构存储在其中:
struct _zend_op {
opcode_handler_t handler;
znode_op op1;
znode_op op2;
znode_op result;
ulong extended_value;
uint lineno;
zend_uchar opcode;
zend_uchar op1_type;
zend_uchar op2_type;
zend_uchar result_type;
};在这里我们可以看到很多东西:
opcode字段中。op1和op2),并且有一个返回值(result)。extended_value,用来存储更多的信息,例如在foreach循环中。handler是一个小的优化,直接指向实现这个优化的函数。在编译脚本时,编译器将创建这些数据结构,并确保操作数和返回值显示到相同的位置,因此,对于像foo(bar())这样的调用,对bar的调用的返回值将与操作码的操作数相同,该操作数将在函数参数堆栈上推送参数。等等-争论堆!?您可能会问:由于每个操作码只接受两个操作数,所以我们不能直接传递函数参数,但是首先ZEND_SEND_*操作填充堆栈,然后使用该堆栈执行ZEND_DO_FCALL/ZEND_DO_FCALL_BY_NAME操作。
使用vld工具,我们可以转储这个已编译的表单:
php -dextension=modules/vld.so -dvld.active -r 'foo(bar());'
Finding entry points
Branch analysis from position: 0
Return found
filename: Command line code
function name: (null)
number of ops: 6
compiled vars: none
line # * op fetch ext return operands
---------------------------------------------------------------------------------
1 0 > INIT_FCALL_BY_NAME 'foo', 'foo'
1 INIT_FCALL_BY_NAME 'bar', 'bar'
2 DO_FCALL_BY_NAME 0
3 SEND_VAR_NO_REF 4 $0
4 DO_FCALL_BY_NAME 1
5 > RETURN null
branch: # 0; line: 1- 1; sop: 0; eop: 5
path #1: 0, 下一步是执行。这本质上就是这个循环(缩短):
while (1) {
if ((ret = OPLINE->handler(execute_data TSRMLS_CC)) > 0) {
switch (ret) {
case 1:
return;
/* ... */
default:
break;
}
}
}OPLINE表示我们的zend_op结构数组的当前元素。每个op都有一个可执行的处理程序。(为了查看正确的循环:它是在zend_vm_execute.h中由zend_vm_gen.php生成的zend_vm_execute.skl和zend_vm_def.h),如果处理程序返回一个脚本/函数/.我们目前正在执行返回,这样循环就结束了。
可以像这样定义一个简单的操作码处理程序(请参阅zend_vm_def.h):
ZEND_VM_HANDLER(1, ZEND_ADD, CONST|TMP|VAR|CV, CONST|TMP|VAR|CV)
{
USE_OPLINE
zend_free_op free_op1, free_op2;
SAVE_OPLINE();
fast_add_function(&EX_T(opline->result.var).tmp_var,
GET_OP1_ZVAL_PTR(BP_VAR_R),
GET_OP2_ZVAL_PTR(BP_VAR_R) TSRMLS_CC);
FREE_OP1();
FREE_OP2();
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}此代码看起来类似于C,但已填充到前面提到的gen脚本中。我们看到这是操作码1,它的名字是ZEND_ADD,它代表一个加法。它需要两个操作数,它们都可以是任意类型的(常量、临时变量、任意变量、编译器缓存变量)。在操作结束时,指针被设置为数组中的下一个操作码。
https://softwareengineering.stackexchange.com/questions/231054
复制相似问题