3.3.执行(execution)

3.3.1.程序调用和退出(program invocation and exit)

ABC文件的条项之一是一个script_info项数组。这些项包括一个对脚本初始化方法的引用,和脚本环境中定义的trait集。数组中最后一项是ABC文件的入口;即最后一项的初始化方法包括第一个ABC运行时的字节码。

其他脚本的初始化代码块按需运行(当脚本导出的实体初次被程序引用)。

对虚拟机,初始化代码块(initialization blocks)通常是常规方法,它们通过执行返回指令(OP_returnvalue, OP_returnvoid)来发出正常结束的信号。一个脚本的返回值将被忽略。

3.3.2.类初始化(class initialization)

newclass指令在其class_info项上执行时,类的静态初始化方法才将执行。

3.3.3.方法项(method entry)

当初次进入方法体,它的执行环境被特别的方式设置。下文提到的method_infomethod_body_info将会在下章定义。

当控制进入方法,将有3个本地数据区域被分配给它,一个操作数栈分段(operand stack segment),一个作用域栈分段(scope stack segment),一个本地寄存器集(set of local registers)。

  1. 操作数栈分段保存指令的操作数栈;值被绝大多数指令压入栈或者弹出栈。
  2. 作用域栈分段保存虚拟机在执行期查找名称用的作用域对象。对象通过OP_pushscope, OP_pushwith压入作用域栈,OP_popscope和异常处理机制弹出栈。
  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保存被转换为对应声明类型的参数值。如果参数值少于method_info.param_count个,其他的值将会被赋值为默认值声明值或者undefined
  • 如果NEED_RESTmethod_info.flags被设置,method_info.param_count+1个寄存器将用来指向保存多余参数值的数组
  • 如果NEED_ARGUMENTmethod_info.flags被设置,method_info.param_count+1个寄存器将用来设置指向保存全部实际参数的参数对象(ECMAScript设置该参数对象为Object类型,AS3设置为Array对象)。

3.3.4.执行机制(execution mechanics)

执行开始于对method_body_info代码的第一条指令。第一条指令的地址为0。指令的第一个字节是操作码(opcode),跟着零个或多个字节的操作数。指令可能会改变本地数据区域,或者堆中的对象,也可能在堆中创建新对象。

分支和跳转指令(branch and jump instructions)会向程序计数器(program counter)添加符号偏移(signed offset)来影响分支。程序计数器的基值是指令后的下一条指令地址。

3.3.5.调用和返回(calling and returning)

当调用指令执行时,一个新的本地数据区域会为被调用的函数创建。被调用的函数不能访问调用者的本地数据区域。真实的参数值是根据被调用函数的调用协议规则经过类型转换的。

返回指令将返回函数栈中的一个值,或者默认的undefined传递给调用者。返回值会根据返回协议规则中返回函数的声明返回类型进行转换。转换后的值会替换调用方法栈中调用指令的操作数,同时被调用方法的本地数据区域被销毁。

3.3.6. 异常处理

异常处理器(exception handlers)由与每个方法关联的表定义。这个表定义了一组字节码地址,这些地址包括哪个处理器是活动的,处理器的字节码地址,类型(用于判定处理器是否可以处理某个特定的异常)。

异常抛出时,调用栈是松散的,直到找到一个方法,这个方法包含一个处理器,这个处理器包含当前程序计数器,并且类型是被抛出对象的类型的父类型。包含处理器的方法将被激活,程序计数器被设成指向处理器的首地址。处理方法的值和作用域栈将在进入处理器前被清理。

finally子句通常被转换为一个接受任意类型值的异常处理器,并在finally子句代码块结束执行后捕获和重新抛出这个对象值。