STVC-TAC
(以下简称STVC
)是一种以三地址码为基础的平面字节码规范。
STVC-TAC
区别于AST-IR
,前者的字节码是线性且平面的,而后者则是递归且树状的。
比起形似AST的AST-IR
,STVC
更容易优化,且执行速度相对更快。
与AST-IR相似的,AST-IR具有:
之前似乎未曾说明常量表的格式,故接下来详述。
一个常量表由常量表长度(占4字节)和若干个常量组成,其中常量又由常量类型(占1字节)和常量值组成:
其中常量值有以下类型:
将所有涉及到的数据和标识符存入常量表,这样在字节码代码中,若涉及到某数值或标识符,只需指定一个下标,虚拟机就能通过下标在常量表中查找出对应的数值或标识符。这么做极大的减少了冗余数据的存储,减小了程序体积。
注意:按照规定,常量表的第一条常量必须是一个名为__init__
的标识符
标识符有三个种类:用户标识符,临时标识符和内部标识符。
用户标识符一般为用户自定义的标识符,其格式与C标识符格式相同。
临时标识符则是表达式计算过程当中会用到的标识符,此类标识符的格式为.XXX
,其中“XXX”通常为数字。
内部标识符则是用于匿名类、匿名函数的声明,此类标识符的格式为#XXX
,其中“XXX”通常为数字
function identifier: arg1 arg2 arg3 ...
...some codes...
end
其中identifier为函数名。arg1、arg2、arg3等为函数的参数名。“...some codes...”为函数体代码
函数内部不能嵌套声明函数或类。
class identifier: member1 member2 member3 ...
...some codes...
end
其中identifier为类名。member1、member2、member3等为类成员名。“...some codes...”为类初始化赋值代码。
在初始化类对象时,会先执行初始化赋值代码,再调用构造函数。
类内部不能嵌套声明函数或类。
x = y op z
将y和z进行运算(运算符为op)之后的值存入x
x = y op
将y进行单目运算(运算符为op)之后的值存入x。如果op为=
,则不做任何运算,这样做能达成“将y直接赋值于x”的效果
goto addr
无条件跳转至相对addr行指令所在处。若addr<0,向上跳转,否则向下跳转。
if condition => addr
如果condition不为null
或0
则跳转至第addr行指令所在处。若addr<0,向上跳转,否则向下跳转。
call result function: arg1 arg2 ...
调用function值。参数为arg1、arg2等标识符或值。返回值存入result当中。
return value
返回value值
new object source arg1 arg2 ...
将source标识符作为类,新建对象,构造参数为arg1、arg2等,新建后的对象值存入object标识符
sfn port arg
设置SFN,port为端口号标识符。arg参数标识符。
SFN的介绍见编译器开发文档.md
list identifier element1 element2...
将element1、element2...作为元素,组合成数列,并存入identifier标识符中s
pushscope
压入一个作用域,用于跳转指令
popscope
弹出一个作用域,用于跳转指令
free identifier
如果identifier所存储的是字面量值(如整型),则释放。该指令和C语言的register关键字类似,是否释放取决于虚拟机状态。该指令通常用于释放临时标识符。
双目运算符有以下种类:
而单目运算符有以下种类:
假设有如下代码:
``` class c { def a = func { return 114; }; func f(x) { return { 2x+1, 3x+2 }; } }
obj = c.new;
rst = obj.f(obj.a())[0]; ```
那么其对应的STVC应为:
``` function #1: return 114 end
function #2: x .1 = 2 mul x .2 = .1 add 1 .3 = 3 mul x .4 = .3 add 2 list .5 .2 .4 return .5 end
class c: a f a = #1 f = #2 end
new obj c
.1 = obj member a call .2 .1 .3 = obj member f call .4 .3: .2 .5 = .4 index 0 rst = .5 ```