ART 虚拟机 — Interpreter 模式

前言

ART 虚拟机执行 Java 方法主要有两种模式:quick code 模式和 Interpreter 模式;

  • quick code 模式:执行 arm 汇编指令
  • Interpreter 模式:由解释器解释执行 Dalvik 字节码

本篇文章就来讲一下,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

扫描二维码关注公众号,回复: 1658606 查看本文章
/* 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 模式的运行流程如下所示:
这里写图片描述

猜你喜欢

转载自blog.csdn.net/u013989732/article/details/80717762