概述
AVM2在处理ABC文件时,会经过4个阶段,载入(loading)、链接(linking)、校验(verification)和执行(execution)。这些阶段之间会有重叠,而校验阶段会覆盖其他所有阶段。
- 载入: 将ABC文件读入内存并解码。在该阶段中,会对ABC文件的结构是否符合规范进行校验。
- 链接: 解析ABC文件中属性域的名字引用,将多个对象组合为一个更加复杂一些的数据结构。例如,某个类定义中,属性域“base class”指明了该类的基类,在该阶段中就会对这个基类的名字进行解析,找该基类的定义,并结合该类与基类的信息创建新对象。在这个阶段,会对该对象中所涉及到的所有名字进行解析,保证有效。
- 执行: 在执行阶段会对ABC文件中的字节码做解释执行。同时,会对指令流和执行栈的内存进行校验,例如,指令不能跳出字节码数组,指令类型和操作数类型必须吻合,字节码所使用的栈和局部变量区不能超出预分配的容量。
载入与链接
这其中会经过4个步骤:
- 将ABC文件载入到内存,并解码。在解码过程中会对ABC文件做校验,确保符合正确的文件结构,以及相关属性域的引用都是有效的。
- 根据类和相关代码创建特征对象。
- 解析相关类的父子关系,将父类的特征集合融入到当前类中,查找该类的相关接口。在解析过程中,需要确保整个继承树的完整性。
- 构建常量池。
- 将方法体链接到方法信息结构中。
执行
程序的调用于退出
ABC文件的其中一个入口是包含了script_info
结构的数组,其中的每个元素都包含了一个指向初始化方法的引用和定义在脚本上下文的特征(trait)集合。这个数组的最后一个元素是ABC文件的入口,即最后一个元素的初始化方法是执行该ABC文件时会首先被调用,而其他的初始化方法会按需调用。
当初始化方法执行OP_returnvalue
指令和OP_returnvoid
指令时,该方法正确退出,同时方法的返回值会被忽略掉。
类的初始化
当调用newclass
指令时,会调用类的静态初始化方法。
方法入口
当首次调用方法时,AVM2会以一种特殊的方式创建执行上下文。当调用方法时,会创建3个本地数据区,分别是操作数栈、域栈和本地寄存器,这些数据区的状态如下:
- 操作数栈是空的,最多可以容纳元素的个数是
method_body_info.max_stack
; - 域栈是空的,最多可以容纳元素的个数是
method_body_info.max_scope_stack
; - 寄存器的个数是
method_body_info.local_count
; - 寄存器0中存储的是
this
,该值永不为null
; - 编号为1到
method_info.param_count
的寄存器中存储的是经过类型强制转换的参数值,如果调用时提供的参数比声明时设置的参数少,则会使用默认参数值或undefined
; - 如果
method_info.flags
中设置了NEED_REST
标志,则编号为method_info.param_count + 1
的寄存器会保存指向额外参数数组的引用; - 如果
method_info.flags
中设置了NEED_REST
标志,则编号为method_info.param_count + 1
的寄存器会保存指向arguments
对象的引用,保存所有的实际参数(更内容请参见ECMA-262,不过AVM2与ECMA-262并非完全兼容,AVM2使用一个Array
类型的对象,来表示arguments
对象,而ECMA-262中则要求使用一个普通的Object
对象)。
(相对来说,JVM这几个阶段的划分更清楚,也更复杂一些,回头应该把两者做个比较。)