一、解释执行流程
虚拟机中java指令的执行只有两种方式:解释执行或执行编译后的机器码。在普遍情况下,这两种方式将穿插进行。本章主要描述解释执行。
1.1、栈帧管理
函数的参数如何传递,以及它们的具体含义。对函数的执行至关重要。需要注意的是机器码执行层和虚拟机执行层不需要单独的栈管理对象。
1.1.1、ShadowFrame
首先描述ShadowFrame的数据结构,然后再结合QuickArgumentVisitor的构造函数,描述如何填充ShadowFrame。
PushShadowFrame和PopShadowFrame,这两个函数专供解释执行使用,代表某个被调用的java方法所用的栈帧。
1.1.2、ManagedStack
PushManagedStackFragment和PopManagedStackFragment,这两个函数成对出现。当从虚拟机执行层进入机器码执行层或者解释执行层是,虚拟机会创建一个ManagedStack对象,并调用PushManagedStackFragment。当目标函数返回后,PopManagedStackFragment会被调用。
1.2、解释执行主要流程
一个方法如果采取解释执行的话,其ArtMethod对象的机器码入口将指向一段跳转代码art_quick_to_interpreter_bridge。即 ptr_sized_fields_中data_==NULL(机器码入口地址为空),而解释执行入口地址entry_point_from_quick_compiled_code_,在链接阶段则已赋值,即为解释执行的入口函数。解释执行主要流程如下:
1.2.1、art_quick_to_interpreter_bridge
art_quick_to_interpreter_bridge(art/runtime/arch/x86_64/quick_entrypoints_x86_64.S)主要是设置栈帧及保存参数(参考邓凡平10.1.3.1.3节),并通过跳转到目标函数artQuickToInterpreterBridge(art/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc)进入到解释器中。
artQuickToInterpreterBridge主要功能就是:
1、找到callee method的DEX字节码code_item,然后根据调用传入的参数构造一个解释器调用栈帧shadow_frame对象,并借助BuildQuickShadowFrameVisitor::Visit将该方法method所需参数存储到ShadowFrame对象中。
2、进入解释执行模式的核心处理函数interpreter::EnterInterpreterFromEntryPoint。
if (UNLIKELY(deopt_frame != nullptr)) {
HandleDeoptimization(&result, method, deopt_frame, &fragment);
} else {
const char* old_cause = self->StartAssertNoThreadSuspension(
"Building interpreter shadow frame");
uint16_t num_regs = accessor.RegistersSize();
// No last shadow coming from quick.
ShadowFrameAllocaUniquePtr shadow_frame_unique_ptr =
CREATE_SHADOW_FRAME(num_regs, /* link= */ nullptr, method, /* dex_pc= */ 0);
ShadowFrame* shadow_frame = shadow_frame_unique_ptr.get();
size_t first_arg_reg = accessor.RegistersSize() - accessor.InsSize();
BuildQuickShadowFrameVisitor shadow_frame_builder(sp, method->IsStatic(), shorty, shorty_len, shadow_frame, first_arg_reg);
shadow_frame_builder.VisitArguments();
1.2.2、EnterInterpreterFromEntryPoint
此函数代码如下:
JValue EnterInterpreterFromEntryPoint(Thread* self, const CodeItemDataAccessor&
accessor,ShadowFrame* shadow_frame) {
DCHECK_EQ(self, Thread::Current());
bool implicit_check = !Runtime::Current()->ExplicitStackOverflowChecks();
if (UNLIKELY(__builtin_frame_address(0) < self->GetStackEndForInterpreter(implicit_check))) {
ThrowStackOverflowError(self);
return JValue();
}
jit::Jit* jit = Runtime::Current()->GetJit();
if (jit != nullptr) {
jit->NotifyCompiledCodeToInterpreterTransition(self, shadow_frame->GetMethod());
}
return Execute(self, accessor, *shadow_frame, JValue());
}
此函数有几点注意:
- 入参中的accessor引用的是callee method的dex指令码内容;shadow_frame存储的是callee method所需的参数;
- 判断jit如果可用,则对此callee method进行AddSamples埋点操作,即对此方法进行性能统计,如果统计为热点函数,则会对其进行jit编译(详见jit研究文档)。
- 然后进入到解释执行的关键函数Execute(art/runtime/interpreter/interpreter.cc)。
1.2.3、Execute
先来看Execute函数的入参:
- Thread* self :当前线程;
- const CodeItemDataAccessor& accessor :类 CodeItemDataAccessor通过继承CodeItemInstructionAccessor(art/libdexfile/dex/code_item_accessors.h)去访问callee method的dex字节码内容;
- ShadowFrame& shadow_frame :callee method对应的shadow frame对象;
- JValue result_register :JValue类型的返回值
- bool stay_in_interpreter = false :此标志位表示是否强制使用解释执行模式,默认为false,它表示如果callee method存在jit编译得到的机器码,则转到jit去执行。
- bool from_deoptimize = false :反优化HDeoptimize标志位,默认为false,表示不经过反优化路径进入方法;
整个Execute函数通过from_deoptimize标志位分为两部分,先说明不经过HDoptimize,即不经过反优化的路径。
1.2.3.1、jit路径
if (LIKELY(!from_deoptimize)) { // Entering the method, but not via deoptimization
如果是HDoptimize的情况,ShadowFrame的dex_pc不是0(这表示有一部分指令以机器码方式执行)。如果dex_pc=0,则表示该方法从一开始就将以解释方式执行,即纯解释执行,此时就需要检查该方法是否存在jit情况:
if (!stay_in_interpreter && !self->IsForceInterpreter()) {
jit::Jit* jit = Runtime::Current()->GetJit();
if (jit != nullptr) {
jit->MethodEntered(self, shadow_frame.GetMethod());
if (jit->CanInvokeCompiledCode(method)) {
JValue result;
// Pop the shadow frame before calling into compiled code.
self->PopShadowFrame();
// Calculate the offset of the first input reg. The input registers are in the high regs.
// It's ok to access the code item here since JIT code will have been touched by the
// interpreter and compiler already.
uint16_t arg_offset = accessor.RegistersSize() - accessor.InsSize();
ArtInterpreterToCompiledCodeBridge(self, nullptr, &shadow_frame, arg_offset, &result);
// Push the shadow frame back as the caller will expect it.
self->PushShadowFrame(&shadow_frame);
return result;
}
}
}
上述执行jit的代码有几点需要注意的地方:
- 判断条件中的IsForceInterpreter()定义如下:
uint32_t ForceInterpreterCount() const {
return tls32_.force_interpreter_count;
}
bool IsForceInterpreter() const {
return tls32_.force_interpreter_count != 0;
}
代表线程执行解释执行的次数,当次数为0时并且满足stay_in_interpreter标志位才会执行jit编译流程。
- 进入jit流程中先通过Jit::MethodEntered函数( EnterInterpreterFromEntryPoint中已经执行jit埋点操作),如果ProfilingInfo中存在机器码入口点,则更新入口点,如果没有,则进行埋点等其他工作。
- 如果存在可调用的jit机器码,则将callee method的shadow_frame pop出ShadowFrame链表(?)此处待补充method A B C shadowframe关系图。
- arg_offset操作中各参数含义如下:
最终,method经过ArtInterpreterToCompiledCodeBridge函数调用Invoke函数执行jit编译的机器码。
1.2.3.2、解释执行路径
在Execute函数中如果不执行jit编译的机器码,则会走纯解释的路径,主要流程如下图所示:
从以上流程可知,ART虚拟机中一共有两种解释执行的实现方式:
- ExecuteMtrep:使用汇编代码编写
- ExecuteSwitchImpl:C++编写,基于switch/case逻辑实现。
此处疑问为:SkipAccessChecks和transaction_active
1.2.4、ExecuteSwitchImpl
位置(art/runtime/interpreter/interpreter_switch_impl.h)
// Wrapper around the switch interpreter which ensures we can unwind through it.
template<bool do_access_check, bool transaction_active>
ALWAYS_INLINE JValue ExecuteSwitchImpl(Thread* self, const CodeItemDataAccessor& accessor,
ShadowFrame& shadow_frame, JValue result_register,
bool interpret_one_instruction)
REQUIRES_SHARED(Locks::mutator_lock_) {
SwitchImplContext ctx {
.self = self,
.accessor = accessor,
.shadow_frame = shadow_frame,
.result_register = result_register,
.interpret_one_instruction = interpret_one_instruction,
.result = JValue(),
};
void* impl = reinterpret_cast<void*>(&ExecuteSwitchImplCpp<do_access_check, transaction_active>);
const uint16_t* dex_pc = ctx.accessor.Insns();
ExecuteSwitchImplAsm(&ctx, impl, dex_pc);
return ctx.result;
}
1.2.5、ExecuteSwitchImplAsm
1.2.6、ExecuteSwitchImplCpp
解释执行的主要处理函数:ExecuteSwitchImplCpp(art/runtime/interpreter/interpreter_
switch_impl_inl.h)。通过while和switch/case的大表,结合起来处理每一种dex对应的opcode。当解释执行的方法执行完毕时,通过Preamble的判断,退出循环。
如果解释执行的方法,不产生调用函数调用,则一条条dex执行下去。如果产生函数调用,则通过invoke的方式进行调用。
首先我们要认识dex字节码中的opcode:
1.2.6.1、dex opcode
1.2.6.2、处理逻辑
在说明dex字节码while处理函数之前,有几个参数需要说明:
- bool interpret_one_instruction :解释一条指令,标志位通常为false,只有一种情况为true,即Mtrep不喜欢这条指令,需要调用SwitchImpl单条解释;
- uint32_t dex_pc : 指向要执行的dex指令;
- const uint16_t* const insns : 代表callee method的dex指令码数组;
接下来正式进入到while(true)循环中解释dex字节码:
dex_pc = inst->GetDexPc(insns);
shadow_frame.SetDexPC(dex_pc);
TraceExecution(shadow_frame, inst, dex_pc);
inst_data = inst->Fetch16(0);
上述代码中显示设置了要执行解释的dex字节码的dex_pc;TraceExecution()默认是不执行的,主要是打印一些跟踪信息;Fetch16(0)获得一个dex代码单元(一个代码单元为两字节)。
上述代码为解释一条dex字节码的操作,其中DEX_INSTRUCTION_LIST(OPCODE_CASE)定义在(art/libdexfile/dex/dex_instruction_list.h),如下所示:
dex指令码列表中一共有256条指令(0x00—0xff),在宏定义OPCODE_CASE(OPCODE, OPCODE_NAME, pname, f, i, a, e, v)中会全部扩展,并通过inst->Opcode(inst_data)获得的opcode进行选择,并最终通过handler.OPCODE_NAME()调用InstructionHandler类中的具体实现函数进行解释,下面以MOVE指令进行举例:
MOVE指令在DEX_INSTRUCTION_LIST(OPCODE_CASE)中为:
OPCODE_CASE(0x01, MOVE, "move", k12x, kIndexNone, kContinue, 0, kVerifyRegA | kVerifyRegB)
在switch中宏定义扩展为:
switch (inst->Opcode(inst_data)) {
......
OPCODE_CASE(0x01, MOVE, "move", k12x, kIndexNone, kContinue, 0, kVerifyRegA | kVerifyRegB) \
case 0x01: { \
bool exit_loop = false; \
InstructionHandler<do_access_check, transaction_active> handler( \
ctx, instrumentation, self, shadow_frame, dex_pc, inst, inst_data, exit_loop); \
handler.MOVE(); \
/* TODO: Advance 'inst' here, instead of explicitly in each handler */ \
if (UNLIKELY(exit_loop)) { \
return; \
} \
break; \
}
......
}
通过handler调用类InstructionHandler中的MOVE函数解释此条dex code:
ALWAYS_INLINE void MOVE() REQUIRES_SHARED(Locks::mutator_lock_) {
shadow_frame.SetVReg(inst->VRegA_12x(inst_data),
shadow_frame.GetVReg(inst->VRegB_12x(inst_data)));
inst = inst->Next_1xx();
}
其中Next_1xx()为获得下一条dex指令赋给inst,即执行完本条dex指令继续执行下一条dex指令,直到此方法所有dex指令执行完退出循环。
执行退出的操作为exit_interpreter_loop标志位,当以下几种情况时会给标志位赋true操作:
1.2.7、DoInvoke
DoInvoke是一个模板函数,能处理invoke-direct/static/super/virtual/interface等指令。
如上一节介绍,当解释method过程中,发生调用,即,method中有一条DoInvoke指令,如下所示:
下文中的方法B指caller method;方法C指callee method。
ALWAYS_INLINE void INVOKE_VIRTUAL() REQUIRES_SHARED(Locks::mutator_lock_) {
bool success = DoInvoke<kVirtual, false, do_access_check, /*is_mterp=*/ false>(
self, shadow_frame, inst, inst_data, ResultRegister());
POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE(!success);
}
ALWAYS_INLINE void INVOKE_VIRTUAL_RANGE() REQUIRES_SHARED(Locks::mutator_lock_) {
bool success = DoInvoke<kVirtual, true, do_access_check, /*is_mterp=*/ false>(
self, shadow_frame, inst, inst_data, ResultRegister());
POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE(!success);
}
ALWAYS_INLINE void INVOKE_SUPER() REQUIRES_SHARED(Locks::mutator_lock_) {
bool success = DoInvoke<kSuper, false, do_access_check, /*is_mterp=*/ false>(
self, shadow_frame, inst, inst_data, ResultRegister());
POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE(!success);
}
此时我们就要分析函数DoInvoke(art/runtime/interpreter/interpreter_common.h)的调用过程:
先来看DoInvoke的参数:
// Handles all invoke-XXX/range instructions except for invoke-polymorphic[/range].
// Returns true on success, otherwise throws an exception and returns false.
template<InvokeType type, bool is_range, bool do_access_check, bool is_mterp, bool is_quick = false>
static ALWAYS_INLINE bool DoInvoke(Thread* self,
ShadowFrame& shadow_frame,
const Instruction* inst,
uint16_t inst_data,
JValue* result)
REQUIRES_SHARED(Locks::mutator_lock_) {
1)模板参数type:指明调用类型(kInterface/ kStatic/ kVirtual/ kSuper/ kDirect)。
2)模板参数is_range:如果该方法有多于5个参数的的话,则需要invoke-xxx-range这样的指令。
3)模板参数do_access_check:是否需要访问检查,即是否有权限调用invoke指令的目标方法C。
4)shadow_frame: 方法B对应的ShadowFrame对象。
5)inst: invoke指令对应的Instruction对象。
6)inst_data: invoke指令对应的参数
7)result: 用于存储方法C执行的结果。
接下来需要从dex字节码中读取方法C以及方法C的参数,如下所示:
const uint32_t method_idx = (is_range) ? inst->VRegB_3rc() : inst->VRegB_35c();
const uint32_t vregC = (is_range) ? inst->VRegC_3rc() : inst->VRegC_35c();
ArtMethod* sf_method = shadow_frame.GetMethod();
待更新……