Skip to content

Latest commit

 

History

History
33 lines (18 loc) · 3.48 KB

File metadata and controls

33 lines (18 loc) · 3.48 KB

The Optimization Strategy of Interface Allocation and Virtual Invocation Reduction 接口分配和虚函数调用消除优化策略

接口的创建和使用主要有如下两个场景:接口创建并成为一个局部变量或结构体成员,并以此身份参与虚函数调用;接口经 construct_interface_impl 创建后立即进行虚函数调用。对于这两种场景,在综合考虑后,我们所采用的实现策略命名为“接口的延迟实例化”。

在创建接口时,我们赋予接口一组记录了实现类型信息的元数据,此时,接口对象并未真正创建,栈值仍为对应类型对象,直至发生控制流变换并存在多个来自不同控制分支的栈值发生合并为一个栈值时,对应元数据被特殊标记,并实例化成为对应接口对象。

对于接口对象引入外部控制流时,存在两种情况:接口对象此时由 construct_interface_impl 贡献,其延迟创建的开销不变;而对于由 load_local 贡献的接口对象,若存在有效元数据(即退化)时,存在较大开销,在某些情况会存在负优化的现象,故不对涉及外部控制流的接口对象局部变量进行优化。

特殊地,当栈值存入一个局部变量时,对于 IROptimizer 阶段,我们会对对应元数据和变量具有的元数据进行比较,若变量未具有对应元数据,我们对该变量进行标记,若变量已经具有了不一致的元数据或元数据被特殊标记,我们将其特殊标记,最后合并信息时,若变量元数据有效,其类型将被退化成对应实现的类型信息;对于 LLVMCodegen 阶段,我们检测是否存在有效的元数据,若存在,接口便以对应类型的形式存入局部变量。

实现主要分为 IROptimizer 和 LLVMCodegen 两部分工作

对于 IROptimizer,我们对不同指令的处理分别如下:

  1. 对于 construct_interface_impl 命令,我们并不需要考虑此时变量类型,我们需要向 StackItem 中增加 IRMetadata,记录延迟实现的接口实现类型,并将其入栈。
  2. 对于 invoke 系列命令,我们检查加载的参数中是否存在由 load_local 贡献的,且存在有效元数据的栈值,若存在,我们将其变量元数据标记为无效。
  3. 对于 ret 命令,我们对返回值同样做如上检查。

对于分析阶段的合并状态,我们增加对 IRMetadata 的状态合并,对接口实现信息的元数据做对应的修改,同时修改局部变量接口对象的元数据。

对于指令变换及进行针对性的优化,我们不使用单独指令区分虚函数调用,故不置于 IROptimizer 进行优化。

对于 LLVMCodegen,其对应处理如下:

在值栈中,我们增加记录对应值的元数据,在合并栈中元素时,我们同样对元数据一致性进行校验,若不符合一致性或元数据无效,则为持有有效元数据的值分配接口,标记产生元数据无效后进行 Phi 操作。

对于 construct_interface_impl 命令,我们不进行任何操作,仅改变IRValueType类型,并增加元数据入栈。

对于 invoke 系列命令的参数处理、各类 store 命令以及 ret 命令,我们检查是否有未实例化的参数入栈,若有,对其进行接口分配后入栈。

对于 invoke_virtual 命令,若接口对象存在有效元数据,则不进行 this 指针拆箱,直接使用给出指针进行后续操作。

此外,我们也需要修改 callGcFunction 以便正确析构 struct 指针。