interpreter
前言
ART 虚拟机执行 java 方法主要有两种模式:quick code 模式和 interpreter 模式
本篇文章就来讲一下,Interpreter 模式是如何运行的(基于 Android 8.1)
一、 Interpreter 模式
点击查看大图
上图是将断点打在 art_quick_invoke_stub 时出现的一段 backtraces,这段 backtraces 很好地描述出了 Interpreter 模式是如何运转的,以及 quick code 模式与 Interpreter 模式之间是如何切换的
1.1 art_quick_to_interpreter_bridge
从 f 19、f 18 可以看到由 quick code 模式进入 Interpreter 模式需要通过 art_quick_to_interpreter_bridge 这个 bridge,
点击查看大图
从 f 18 可以看到,artQuickToInterpreterBridge 会通过调用 interpreter::EnterInterpreterFromEntryPoint(self, code_item, shadow_frame); 来进入 Interpreter 模式,查看一下 EnterInterpreterFromEntryPoint 的定义:
JValue EnterInterpreterFromEntryPoint(Thread* self, const DexFile::CodeItem* code_item,
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, code_item, *shadow_frame, JValue());
}
可以看到其会调用 Execute() 函数,结合上面的 backtraces,我们可以将 Execute() 函数看作是 Interpreter 模式的起点
1.2 Execute()
art/runtime/interpreter/interpreter.cc
enum InterpreterImplKind {
kSwitchImplKind, // Switch-based interpreter implementation.
kMterpImplKind // Assembly interpreter
};
static constexpr InterpreterImplKind kInterpreterImplKind = kMterpImplKind; // 默认使用 Mterp 类型的实现
static inline JValue Execute(
Thread* self,
const DexFile::CodeItem* code_item,
ShadowFrame& shadow_frame,
JValue result_register,
bool stay_in_interpreter = false) requires_SHARED(Locks::mutator_lock_) {
...
if (LIKELY(shadow_frame.GetDexPC() == 0)) { // Entering the method, but not via deoptimization.
if (kIsDebugbuild) {
self->AssertNoPendingException();
}
instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
ArtMethod *method = shadow_frame.GetMethod();
...
if (!stay_in_interpreter) {
jit::Jit* jit = Runtime::Current()->GetJit();
if (jit != nullptr) {
jit->MethodEntered(self, shadow_frame.GetMethod());
if (jit->CanInvokeCompiledCode(method)) { // 1、jit 不为 nullptr,并且 jit 编译出了对应的 quick code,那么 ArtInterpreterToCompiledCodeBridge
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 = code_item->registers_size_ - code_item->ins_size_;
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;
}
}
}
}
shadow_frame.GetMethod()->GetDeclaringClass()->AssertInitializedOrInitializingInThread(self);
// Lock counting is a special version of accessibility checks, and for simplicity and
// reduction of template parameters, we gate it behind access-checks mode.
ArtMethod* method = shadow_frame.GetMethod();
DCHECK(!method->SkipAccessChecks() || !method->MustCountLocks());
bool transaction_active = Runtime::Current()->IsActiveTransaction();
if (LIKELY(method->SkipAccessChecks())) {
// Enter the "without access check" interpreter.
if (kInterpreterImplKind == kMterpImplKind) {
if (transaction_active) {
// No Mterp variant - just use the switch interpreter.
return ExecuteSwitchImpl<false, true>(self, code_item, shadow_frame, result_register,
false);
} else if (UNLIKELY(!Runtime::Current()->IsStarted())) {
...
} else {
while (true) {
...
bool returned = ExecuteMterpImpl(self, code_item, &shadow_frame, &result_register);
if (returned) {
return result_register;
} else {
// Mterp didn't like that instruction. Single-step it with the reference interpreter.
result_register = ExecuteSwitchImpl<false, false>(self, code_item, shadow_frame,
result_register, true);
if (shadow_frame.GetDexPC() == DexFile::kDexNoIndex) {
// Single-stepped a return or an exception not handled locally. Return to caller.
return result_register;
}
}
}
}
} else {
...
}
} else {
// Enter the "with access check" interpreter.
if (kInterpreterImplKind == kMterpImplKind) {
// No access check variants for Mterp. Just use the switch version.
if (transaction_active) {
return ExecuteSwitchImpl<true, true>(self, code_item, shadow_frame, result_register,
false);
} else {
return ExecuteSwitchImpl<true, false>(self, code_item, shadow_frame, result_register,
false);
}
} else {
DCHECK_EQ(kInterpreterImplKind, kSwitchImplKind);
if (transaction_active) {
return ExecuteSwitchImpl<true, true>(self, code_item, shadow_frame, result_register,
false);
} else {
return ExecuteSwitchImpl<true, false>(self, code_item, shadow_frame, result_register,
false);
}
}
}
}
从上面可以看到,Execute() 的作用就是:
- 如果有 jit,并且 jit 编译出了对应 method 的 quick code,那么选择通过 ArtInterpreterToCompiledCodeBridge 这个去执行对应的 quick code
- 如果上面的条件不满足,那么根据情况选择 Mterp 或者 Switch 类型的解释器实现来解释执行对应的 Dalvik 字节码
因为默认使用 Mterp 类型的 Interpreter 实现,所以大多数情况下会调用 ExecuteMterpImpl() 函数来解释执行 Dalvik 字节码,下面来重点看一下 ExecuteMterpImpl() 的实现
1.3 ExecuteMterpImpl
art/runtime/interpreter/mterp/out/mterp_arm64.S
/* During bringup, we'll use the shadow frame model instead of xFP */
/* single-purpose registers, given names for clarity */
#define xPC x20
#define xFP x21
#define xSELF x22
#define xINST x23
#define wINST w23
#define xIBASE x24
#define xREFS x25
#define wprofile w26
#define xPROFILE x26
#define ip x16
#define ip2 x17
.macro EXPORT_PC
str xPC, [xFP, #OFF_FP_DEX_PC_PTR]
.endm
.macro FETCH_INST
ldrh wINST, [xPC]
.endm
.macro GOTO_OPCODE reg
add \reg, xIBASE, \reg, lsl #7
br \reg
.endm
.macro GET_INST_OPCODE reg
and \reg, xINST, #255
.endm
/*
* Interpreter entry point.
* On entry:
* x0 Thread* self/
* x1 code_item
* x2 ShadowFrame
* x3 JValue* result_register
*
*/
.global ExecuteMterpImpl
.type ExecuteMterpImpl, %function
.balign 16
ExecuteMterpImpl:
.cfi_startproc
SAVE_TWO_REGS_INCREASE_FRAME xPROFILE, x27, 80
SAVE_TWO_REGS xIBASE, xREFS, 16
SAVE_TWO_REGS xSELF, xINST, 32
SAVE_TWO_REGS xPC, xFP, 48
SAVE_TWO_REGS fp, lr, 64
add fp, sp, #64
/* Remember the return register */
str x3, [x2, #SHADOWFRAME_RESULT_REGISTER_OFFSET]
/* Remember the code_item */
str x1, [x2, #SHADOWFRAME_CODE_ITEM_OFFSET]
/* set up "named" registers */
mov xSELF, x0
ldr w0, [x2, #SHADOWFRAME_NUMBER_OF_VREGS_OFFSET]
add xFP, x2, #SHADOWFRAME_VREGS_OFFSET // point to vregs.
add xREFS, xFP, w0, lsl #2 // point to reference array in shadow frame
ldr w0, [x2, #SHADOWFRAME_DEX_PC_OFFSET] // Get starting dex_pc.
add xPC, x1, #CODEITEM_INSNS_OFFSET // Point to base of insns[]
add xPC, xPC, w0, lsl #1 // Create direct pointer to 1st dex opcode
EXPORT_PC
/* Starting ibase */
ldr xIBASE, [xSELF, #THREAD_CURRENT_IBASE_OFFSET]
/* Set up for backwards branches & osr profiling */
ldr x0, [xFP, #OFF_FP_METHOD]
add x1, xFP, #OFF_FP_SHADOWFRAME
bl MterpSetUpHotnessCountdown
mov wPROFILE, w0 // Starting hotness countdown to xPROFILE
/* start executing the instruction at rPC */
FETCH_INST // load wINST from rPC
GET_INST_OPCODE ip // extract opcode from wINST
GOTO_OPCODE ip // jump to next instruction
/* NOTE: no fallthrough */
在 gdb 中查看上面这一段即为:
Dump of assembler code for function ExecuteMterpImpl:
0x000000790e52e280 <+0>: stp x26, x27, [sp,#-80]!
0x000000790e52e284 <+4>: stp x24, x25, [sp,#16]
0x000000790e52e288 <+8>: stp x22, x23, [sp,#32]
0x000000790e52e28c <+12>: stp x20, x21, [sp,#48]
0x000000790e52e290 <+16>: stp x29, x30, [sp,#64]
0x000000790e52e294 <+20>: add x29, sp, #0x40
/* Remember the return register */
0x000000790e52e298 <+24>: str x3, [x2,#16]
/* Remember the code_item */
0x000000790e52e29c <+28>: str x1, [x2,#32]
/* set up "named" registers */
0x000000790e52e2a0 <+32>: mov x22, x0
0x000000790e52e2a4 <+36>: ldr w0, [x2,#48]
0x000000790e52e2a8 <+40>: add x21, x2, #0x3c
0x000000790e52e2ac <+44>: add x25, x21, x0, uxtx #2
0x000000790e52e2b0 <+48>: ldr w0, [x2,#52] // w0 指向 SHADOWFRAME 的 dex_pc_
0x000000790e52e2b4 <+52>: add x20, x1, #0x10 // xPC 指向 CodeItem 中 bytecode array 的起点, 即 base of insns[]
0x000000790e52e2b8 <+56>: add x20, x20, x0, uxtx #1 // xPC 指向第一条 dex opcode
0x000000790e52e2bc <+60>: stur x20, [x21,#-36]
/* Starting ibase */
0x000000790e52e2c0 <+64>: ldr x24, [x22,#1736]
0x000000790e52e2c4 <+68>: ldur x0, [x21,#-52]
0x000000790e52e2c8 <+72>: sub x1, x21, #0x3c
0x000000790e52e2cc <+76>: bl 0x790e52e02c <MterpSetUpHotnessCountdown(art::ArtMethod*, art::ShadowFrame*)>
0x000000790e52e2d0 <+80>: mov w26, w0
/* start executing the instruction at rPC */
0x000000790e52e2d4 <+84>: ldrh w23, [x20] // Fetch the next instruction from xPC into w23
0x000000790e52e2d8 <+88>: and x16, x23, #0xff // 将 instruction's opcode field 放到特殊寄存器 x16 当中
0x000000790e52e2dc <+92>: add x16, x24, x16, lsl #7
0x000000790e52e2e0 <+96>: br x16 // Begin executing the opcode in x16
0x000000790e52e2e4 <+100>: nop
1.4
执行 opcode,每个 opcode 以 128 字节对齐,并且绝大多数 opcode 都会包含如下指令:
FETCH_ADVANCE_INST 2 // 此处的数字可以是其他的,譬如 1、3
GET_INST_OPCODE ip
GOTO_OPCODE ip
看一下 FETCH_ADVANCE_INST 的定义:
.macro FETCH_ADVANCE_INST count
ldrh wINST, [xPC, #((\count)*2)]!
.endm
这几条指令的作用是,移动 xPC 到下一条 instruction,并将移动后的 xPC 的值 load 到 wINST 中,然后跳转去执行 opcode
例如:
/* ------------------------------ */
.balign 128
.L_op_nop: /* 0x00 */
/* File: arm64/op_nop.S */
FETCH_ADVANCE_INST 1 // advance to next instr, load rINST
GET_INST_OPCODE ip // ip<- opcode from rINST
GOTO_OPCODE ip // execute it
/* ------------------------------ */
这条 opcode 相当于什么都没做,然后移动 xPC,再去执行下一条 instruction;
通过上面这些分析,我们可以看出对 Dalvik 字节码解释执行的运行模式:
- 在 Mterp 解释器当中维护了一种对应关系:opcode 与实现这个 opcode 的汇编指令的对应关系
- 我们在解释执行的时候,实际上是取出一条指令,通过 opcode 找到对应的汇编实现,然后运行
- 大部分 opcode 中都会包含取出下一条 instruction、然后跳转执行的操作,形成一个循环
- 一些带有 invoke 操作的 opcode 将会开启下一个 Java 调用
例如,图1中的 f 16:
/* ------------------------------ */
.balign 128
.L_op_invoke_virtual_quick: /* 0xe9 */
/* File: arm64/op_invoke_virtual_quick.S */
/* File: arm64/invoke.S */
/*
* Generic invoke handler wrAPPer.
*/
/* op vB, {vD, vE, vF, vG, vA}, class@CCCC */
/* op {vCCCC..v(CCCC+AA-1)}, meth@BBBB */
.extern MterpInvokeVirtualQuick
EXPORT_PC
mov x0, xSELF
add x1, xFP, #OFF_FP_SHADOWFRAME
mov x2, xPC
mov x3, xINST
bl MterpInvokeVirtualQuick
cbz w0, MterpException
FETCH_ADVANCE_INST 3
bl MterpShouldSwitchInterpreters
cbnz w0, MterpFallback
GET_INST_OPCODE ip
GOTO_OPCODE ip
/* ------------------------------ */
从图1中可以看到,其后会经过:
MterpInvokeVirtualQuick
|_
DoInvokeVirtualQuick
|_
DoCall
|_
DoCallCommon
|_
PerformCall
|_
ArtInterpreterToInterpreterBridge
|_
Execute
的调用栈开启下一个 method 的解释执行
1.4.2 PerformCall
art/runtime/common_dex_operations.h
inline void PerformCall(Thread* self,
const DexFile::CodeItem* code_item,
ArtMethod* caller_method,
const size_t first_dest_reg,
ShadowFrame* callee_frame,
JValue* result,
bool use_interpreter_entrypoint)
REQUIRES_SHARED(Locks::mutator_lock_) {
if (LIKELY(Runtime::Current()->IsStarted())) {
if (use_interpreter_entrypoint) {
interpreter::ArtInterpreterToInterpreterBridge(self, code_item, callee_frame, result);
} else {
interpreter::ArtInterpreterToCompiledCodeBridge(
self, caller_method, callee_frame, first_dest_reg, result);
}
} else {
interpreter::UnstartedRuntime::Invoke(self, code_item, callee_frame, result, first_dest_reg);
}
}
可以看到在上面的调用栈中,在执行 PerformCall 方法时会判断 use_interpreter_entrypoint 是否为 true,从而选择是跳转到 ArtInterpreterToInterpreterBridge 进行解释执行还是通过 ArtInterpreterToCompiledCodeBridge 跳转到被调用 method 的 entry_point_from_quick_compiled_code_ 入口
1.4.3 DoCallCommon
art/runtime/interpreter/interpreter_common.cc
template <bool is_range,
bool do_assignability_check>
static inline bool DoCallCommon(ArtMethod* called_method,
Thread* self,
ShadowFrame& shadow_frame,
JValue* result,
uint16_t number_of_inputs,
uint32_t (&arg)[Instruction::kMaxVarArgRegs],
uint32_t vregC) {
bool string_init = false;
...
// Compute method information.
const DexFile::CodeItem* code_item = called_method->GetCodeItem();
// Number of registers for the callee's call frame.
uint16_t num_regs;
// 1、是否使用 interpreter 模式
const bool use_interpreter_entrypoint = !Runtime::Current()->IsStarted() ||
ClassLinker::ShouldUseInterpreterEntrypoint(
called_method,
called_method->GetEntryPointFromQuickCompiledCode());
if (LIKELY(code_item != nullptr)) {
...
} else {
DCHECK(called_method->IsNative() || called_method->IsProxyMethod());
num_regs = number_of_inputs;
}
// 2、Hack for String init:
...
// Parameter registers go at the end of the shadow frame.
DCHECK_GE(num_regs, number_of_inputs);
size_t first_dest_reg = num_regs - number_of_inputs;
DCHECK_NE(first_dest_reg, (size_t)-1);
// 3、Allocate shadow frame on the stack.
const char* old_cause = self->StartAssertNoThreadSuspension("DoCallCommon");
ShadowFrameAllocauniquePtr shadow_frame_unique_ptr =
CREATE_SHADOW_FRAME(num_regs, &shadow_frame, called_method, /* dex pc */ 0);
ShadowFrame* new_shadow_frame = shadow_frame_unique_ptr.get();
// 4、Initialize new shadow frame by copying the registers from the callee shadow frame.
...
// 5、PerformCall
PerformCall(self,
code_item,
shadow_frame.GetMethod(),
first_dest_reg,
new_shadow_frame,
result,
use_interpreter_entrypoint);
if (string_init && !self->IsExceptionPending()) {
SetStringInitValueToAllAliases(&shadow_frame, string_init_vreg_this, *result);
}
return !self->IsExceptionPending();
}
可以看到在 DoCallCommon 中主要做了5件事,详细的细节暂时先不关注,主要看一下 use_interpreter_entrypoint,其是通过 ClassLinker::ShouldUseInterpreterEntrypoint 方法取得的值
1.4.4 ClassLinker::ShouldUseInterpreterEntrypoint
art/runtime/class_linker.cc
bool ClassLinker::ShouldUseInterpreterEntrypoint(ArtMethod* method, const void* quick_code) {
if (UNLIKELY(method->IsNative() || method->IsProxyMethod())) {
return false;
}
if (quick_code == nullptr) {
return true;
}
Runtime* runtime = Runtime::Current();
instrumentation::Instrumentation* instr = runtime->GetInstrumentation();
if (instr->InterpretOnly()) {
return true;
}
if (runtime->GetClassLinker()->IsQuickToInterpreterBridge(quick_code)) {
// Doing this check avoids doing compiled/interpreter transitions.
return true;
}
if (Dbg::IsForcedInterpreterneededForCalling(Thread::Current(), method)) {
// Force the use of interpreter when it is required by the debugger.
return true;
}
if (runtime->IsJavaDebuggable()) {
// For simplicity, we ignore precompiled code and go to the interpreter
// assuming we don't already have jitted code.
// We could look at the oat file where `quick_code` is being defined,
// and check whether it's been compiled debuggable, but we decided to
// only rely on the JIT for debuggable apps.
jit::Jit* jit = Runtime::Current()->GetJit();
return (jit == nullptr) || !jit->GetCodecache()->ContainsPc(quick_code);
}
if (runtime->IsNativeDebuggable()) {
DCHECK(runtime->UseJitCompilation() && runtime->GetJit()->JitAtFirstUse());
// If we are doing native debugging, ignore application's AOT code,
// since we want to JIT it (at first use) with extra stackmaps for native
// debugging. We keep however all AOT code from the boot image,
// since the JIT-at-first-use is blocking and would result in non-negligible
// startup performance impact.
return !runtime->GetHeap()->IsInBootImageOatFile(quick_code);
}
return false;
}
可以看到上面每个判断条件都会作为是否使用 Interpreter 模式的一个依据,我们主要关注一下下面几个条件:
- quick_code == nullptr
- instr->InterpretOnly()
- IsQuickToInterpreterBridge(quick_code)
- ……
当上面这几个条件有一个满足时,ShouldUseInterpreterEntrypoint 就会返回 true,使用 Interpreter 模式
二、总结
Interpreter 模式的运行流程如下所示:
相关阅读
simple-quartz-tasks基于quartz的任务调度插件,引入到spring项目中实现任务信息装载接口即可使用,依赖于redis的订阅完成对任务的立
前言 FusionCharts是一个Flash的图表组件,它可以用来制作数据动画图表,其中动画效果用的是Adobe Flash 8 (原Macromedia Flash的)制
概述 地图在我们日常的数据可视化分析中是很常见的一种展示手段,不仅美观而且很大气。尤其是在大屏展示中更是扮演着必不可缺的角
以下文章转载于S
smartqq介绍: http://w.qq.com/在线WebQQ网页平台是腾讯在WebOS云平台上推出的一款单纯的聊天工具。SmartQQ JAVA开源项目:https: