art/compiler/dex/quick/quick_compiler.cc中:
QuickCompiler::Compile(...)函数内:
Compiler::IsPathologicalCase(...) : 当前方法的指令条数和寄存器个数超过 UINT16_MAX / 4 时,编译器将不编译此方法,直接返回。
创建编译单元: CompilationUnit cu(...),对应一个方法
初始化编译单元:InitCompilationUnit(cu) ,根据指令集禁止一些优化选项
创建一个MIR Graph对象: cu.mir_graph.reset(new MIRGraph(&cu, &cu.arena))
生成Code Generator: cu.cg.reset(GetCodeGenerator(&cu, nullptr)) --> MipsCodeGenerator(...) --> MipsMir2Lir(...) (创建 Mir2Lir* 指针)
建立raw MIR graph: cu.mir_graph->InlineMethod(...)依次调用 ParseInsn(...)(解析一条dalvik字节码)、DumpCFG(...)、DumpMIRGraph(...)
关联SSA形式的寄存器和Dalvik寄存器:cu.mir_graph->RemapRegLocations()
生成最终代码:cu.cg->Materialize()依次调用SimpleRegAlloc()、MethodMIR2LIR()、AssembleLIR()
一个MIRGraph对应着一个编译单元即一个方法,对一个方法进行控制流分析,划分出BasicBlock,并在BasicBlock中的fall_through和taken域中指向下一个BasicBlock(适用于分支出口)。每一个BasicBlock包含若干MIR语句,每一条dalvik指令对应一条MIR语句(有待考证),这些MIR结构体之间形成双向链表。每一个BasicBlock也指示了第一条和最后一条MIR语句。
InlineMethod函数主要是解析一个方法,并划分BasicBlock边界,但是只是简单地把BasicBlock连接成一个链表,利用fall_through指示。
在CodeLayout函数中具体地再次遍历BasicBlock链表,并根据每个BasicBlock出口的指令,再次调整taken域和fall_through域,形成完整的控制流图结构。
SSATransformation函数是对每条指令进行静态单赋值变换。先对控制流图进行深度优先遍历,并计算出BasicBlock之间的支配关系,插入Phi函数,并对变量进行命名更新。
其余的方法主要是一些代码优化过程,例如常量传播、消除空指针检查;并在BasicBlock组合之后再进行BasicBlock的优化,消除冗余指令。
这样基本上就完成了MIR的生成过程,在某种程度上,可以认为MIR即为对dalvik指令进行SSA变换之后的指令形态。
接着就调用cu.cg->Materialize()用来产生最终代码,其中重要的两个调用就是MethodMIR2LIR()和AssembleLIR()。
MethodMIR2LIR()将MIR转化为LIR,遍历每个BasicBlock,对每个基本块执行MethodBlockCodeGen,本质上最后是执行了CompileDalvikInstruction。CompileDalvikInstruction也就是通过解析指令,然后根据opcode进行分支判断,调用最终不同的指令生成函数。最后将LIR之间也形成一个双向链表。
AssembleLIR()最终调用的是AssembleInstructions函数。程序中维护了一个编码指令表MipsMir2Lir::EncodingMap,AssembleInstructions即是通过查找这个表来进行翻译,将LIR转化为了MIPS指令,并将所翻译的指令存储到CodeBufferMir2Lir::code_buffer_之中。
这样就完成了一次编译的完整流程。
本文参考:
Android的ART运行时: http://drops.wooyun.org/tips/10841