ART世界探险(15) - CompilerDriver,ClassLinker,Runtime三大组件

ART世界探险(15) - CompilerDriver,ClassLinker,Runtime三大组件

CompilerDriver

调用编译器的接口是CompilerDriver。
我们看一看CompilerDriver的结构图吧:

这是我们在ART里能遇见的第一个复杂的大类。但凡编译相关,都要通过它来打交道。结果,它就把自己搞成了一个大杂烩。

ClassLinker

Java是门面向对象的语言,导致类相关的操作比较复杂。
在应用层有ClassLoader,在运行环境层就有ClassLinker。
我们看一下ClassLinker的公开方法,私有的还有同样多的,汗。

ClassLinker相对于CompilerDriver,逻辑上更为集中一些。
它主要是提供跟类相关的操作,包括类级的分配对象等。
CompilerDriver提供的主要是编译期底层代码的功能,而ClassLinker在面向对象的逻辑层提供服务。

Runtime

ART是Android Runtime的缩写,我们终于可以揭开Android Runtime的面纱了。

Runtime主要是提供一些运行时的服务,最重要的当然就是GC。另外,还有多线程和线程安全相关的支持,事务相关的支持等。

有了上面三个大组件的支持,不管是编译期还是运行时,我们都可以找到支持Java方法运行的基础设施。

最后,我们再复习一下上节最后出现的编译单元类:

CompilationUnit的作用是连接前端和后端。

将前端的DexFile通过CompilerDriver进行编译之后,我们先得到中间层中间代码MIR,MIRGraph就是这一步要做的工作。很多优化也是在这一步完成的。
然后,再通过Mir2Lir,将MIR转化成更接近于机器指令的低层中间代码LIR。
最后,再将LIR落地成目标机器的指令。

dex2oat编译流程(续)

首先我们复习一下之前学到的,dex2oat做为入口点,会调用CompilerDriver的方法对dex文件进行编译。

下面该开始CompilerDriver的CompileClass,看了CompilerDriver的大图之后,对于它是不是更亲切了呢?

CompileClass

编译类的重头戏还在于编译方法。
CompileClass类的主要逻辑,就是针对直接方法和虚拟方法,分别遍历然后编译。

我们将前面的判断和校验等细节都略过,这个函数的框架如下面所示:

void CompilerDriver::CompileClass(const ParallelCompilationManager* manager,
                                  size_t class_def_index) {
...
  CompilerDriver* const driver = manager->GetCompiler();
...

  // Compile direct methods
  int64_t previous_direct_method_idx = -1;
  while (it.HasNextDirectMethod()) {
    uint32_t method_idx = it.GetMemberIndex();
    if (method_idx == previous_direct_method_idx) {
      // smali can create dex files with two encoded_methods sharing the same method_idx
      // http://code.google.com/p/smali/issues/detail?id=119
      it.Next();
      continue;
    }
    previous_direct_method_idx = method_idx;
    driver->CompileMethod(self, it.GetMethodCodeItem(), it.GetMethodAccessFlags(),
                          it.GetMethodInvokeType(class_def), class_def_index,
                          method_idx, jclass_loader, dex_file, dex_to_dex_compilation_level,
                          compilation_enabled);
    it.Next();
  }
  // Compile virtual methods
  int64_t previous_virtual_method_idx = -1;
  while (it.HasNextVirtualMethod()) {
    uint32_t method_idx = it.GetMemberIndex();
    if (method_idx == previous_virtual_method_idx) {
      // smali can create dex files with two encoded_methods sharing the same method_idx
      // http://code.google.com/p/smali/issues/detail?id=119
      it.Next();
      continue;
    }
    previous_virtual_method_idx = method_idx;
    driver->CompileMethod(self, it.GetMethodCodeItem(), it.GetMethodAccessFlags(),
                          it.GetMethodInvokeType(class_def), class_def_index,
                          method_idx, jclass_loader, dex_file, dex_to_dex_compilation_level,
                          compilation_enabled);
    it.Next();
  }
  DCHECK(!it.HasNext());
}

CompileMethod

从这里开始,我们终于深入到可以生成代码的程度了。

void CompilerDriver::CompileMethod(Thread self, const DexFile::CodeItem code_item,
                                   uint32_t access_flags, InvokeType invoke_type,
                                   uint16_t class_def_idx, uint32_t method_idx,
                                   jobject class_loader, const DexFile& dex_file,
                                   DexToDexCompilationLevel dex_to_dex_compilation_level,
                                   bool compilation_enabled) {
  CompiledMethod* compiled_method = nullptr;
  uint64_t start_ns = kTimeCompileMethod ? NanoTime() : 0;
  MethodReference method_ref(&dex_file, method_idx);

首先是对JNI调用的处理,我们之前曾经看到过的序列。这里会调用JniCompile函数。下面开始处理JNI:

  if ((access_flags & kAccNative) != 0) {
    // Are we interpreting only and have support for generic JNI down calls?
    if (!compiler_options_->IsCompilationEnabled() &&
        InstructionSetHasGenericJniStub(instruction_set_)) {
      // Leaving this empty will trigger the generic JNI version
    } else {
      compiled_method = compiler_->JniCompile(access_flags, method_idx, dex_file);
      CHECK(compiled_method != nullptr);
    }

抽象方法不需要生成代码:

  } else if ((access_flags & kAccAbstract) != 0) {
    // Abstract methods don't have code.

下面再开始编普通方法,通过调用Compile方法来完成。

  } else {
    bool has_verified_method = verification_results_->GetVerifiedMethod(method_ref) != nullptr;
    bool compile = compilation_enabled &&
                   // Basic checks, e.g., not <clinit>.
                   verification_results_->IsCandidateForCompilation(method_ref, access_flags) &&
                   // Did not fail to create VerifiedMethod metadata.
                   has_verified_method &&
                   // Is eligable for compilation by methods-to-compile filter.
                   IsMethodToCompile(method_ref);
    if (compile) {
      // NOTE: if compiler declines to compile this method, it will return null.
      compiled_method = compiler_->Compile(code_item, access_flags, invoke_type, class_def_idx,
                                           method_idx, class_loader, dex_file);
    }
...
}

如上一讲我们所介绍的,ART有两种Compiler,QuickCompiler和OptimizationCompiler。
所以,根据dex2oat参数的不同,分别调用这两种Compiler的Compile方法来实现真正的编译。

我们看一个图来复习一下:

时间: 2024-08-03 21:05:36

ART世界探险(15) - CompilerDriver,ClassLinker,Runtime三大组件的相关文章

ART世界探险(16) - 快速编译器下的方法编译

ART世界探险(16) - 快速编译器下的方法编译 我们对三大组件有了了解之后,下面终于可以开始正餐,开始分析两种Compiler下的Compile函数. 我们先看一张图,对于整个流程有个整体的印象,然后我们再去看代码: QuickCompiler的Compile CompiledMethod QuickCompiler::Compile(const DexFile::CodeItem code_item, uint32_t access_flags, InvokeType invoke_typ

ART世界探险(20) - Android N上的编译流程

ART世界探险(20) - Android N上的编译流程 就在我们分析Android M版本的ART还只走出了一小段路的时候,Android N的新ART就问世了. Android N上的ART还是有不小的改进的.不过做为一个关注细节的系列文章,我们还是从Compile的过程说起. 流程概述 在安装的时候,默认情况下,Android N只做interpret-only的编译,如下命令行所示: /system/bin/dex2oat --zip-fd=7 --zip-location=base.

ART世界探险(19) - 优化编译器的编译流程

ART世界探险(19) - 优化编译器的编译流程 前面,我们对于快速编译器的知识有了一点了解,对于CompilerDriver,MIRGraph等都有了初步的印象. 下面,我们回头看一下优化编译器的编译过程.有了前面的基础,后面的学习过程会更顺利一些. 下面我们先看个地图,看看我们将遇到哪些新的对象: OptimizingCompiler::Compile 我们先来看看优化编译的入口点,Compile函数: CompiledMethod OptimizingCompiler::Compile(c

ART世界探险(13) - 初入dex2oat

ART世界探险(13) - 初入dex2oat dex2oat流程分析 进入整个流程之前,我们先看一下地图,大致熟悉一下我们下一步要去哪里: 主函数 dex2oat的main函数,直接是dex2oat工厂函数的封装. int main(int argc, char** argv) { int result = art::dex2oat(argc, argv); // Everything was done, do an explicit exit here to avoid running Ru

ART世界探险(14) - 快速编译器和优化编译器

ART世界探险(14) - 快速编译器和优化编译器 ART的编译器为两种,一种是QuickCompiler,快速编译器:另一种是OptimizingCompiler,优化编译器. 编译器的基类 - Compiler类 Compiler类是真正实现Java方法和Jni方法编译的入口. 我们先通过一个思维导图来看一下它的结构: 有了上面的结构图之后,我们再看下面的类结构就非常清晰了. class Compiler { public: enum Kind { kQuick, kOptimizing }

ART世界探险(9) - 同步锁

ART世界探险(9) - 同步锁 Java是一种把同步锁写进语言和指令集的语言. 从语言层面,Java提供了synchronized关键字. 从指令集层面,Java提供了monitorenter和monitorexit两条指令. 下面我们就看看它们是如何实现的吧. 三种锁的方式 Java代码 有三种方式来加锁: 直接在函数上加synchronized关键字 在函数内用某Object去做同步 调用concurrent库中的其他工具 public synchronized int newID(){

ART世界探险(18) InlineMethod

ART世界探险(18) InlineMethod 好,我们还是先复习一下上上节学到的图: 在开始InlineMethod之前,我们再继续补充一点BasicBlock的知识. BasicBlock中针对MIR的相关操作 AppendMIR AppendMIR的作用是将MIR增加到一个BasicBlock的结尾. / Insert an MIR instruction to the end of a basic block. / void BasicBlock::AppendMIR(MIR* mir

ART世界探险(12) - OAT文件分析(2) - ELF文件头分析(中)

ART世界探险(12) - OAT文件分析(2) - ELF文件头分析(中) 段(section)的概念 一块内存分配给应用程序之后,从代码的组织上,我们就有将它们分段的需求. 比如,可以分为代码段,数据段,只读数据段,堆栈段,未初始化的数据段等等. 在GAS汇编器中,我们通过.section伪指令来指定段名.ARM编译器我买不起,我就忽略它了. 标准section 段的描述 默认段名 代码段 .text 经过初始化的数据段 .data 未经初始化的数据段 .bss BSS是Block Star

ART世界探险(7) - 数组

ART世界探险(7) - 数组 Java针对数据是有专门的指令去处理的,这与C/C++有显著的不同. Java字节码对于数组的支持 一个极简的例子 Java源代码 为了简化,我们取一个极简的例子来说明Java的数组指令的用法: 我们new一个长度为1的字节数组,然后返回这个数组的长度. public static int testByteArrayLength(){ byte[] baArray = new byte[1]; return baArray.length; } Java字节码 有几