Ruby 2.x 源代碼學習:YARV 虛擬機 指令

前言

YARV 大約有 90 多條指令,這些指令定義在 insns.def 文件中,編譯 Ruby 源代碼的時候會根據該文件生成 vm.inc 和 insns.inc 兩個(include)文件,這兩個文件會被包含在 Ruby 虛擬器核心代碼裏頭函數

棧幀

虛擬機模擬物理機執行方法調用的方式, 每執行一個方法(or block)都會將一個 棧幀壓入堆棧中
rb_control_frame_t 結構封裝了棧幀結構編碼

// vm_core.h

typedef struct rb_control_frame_struct {
    const VALUE *pc;        /* cfp[0] */
    VALUE *sp;            /* cfp[1] */
    const rb_iseq_t *iseq;    /* cfp[2] */
    VALUE self;            /* cfp[3] / block[0] */
    const VALUE *ep;        /* cfp[4] / block[1] */
    const void *block_code;     /* cfp[5] / block[2] */ /* iseq or ifunc */

#if VM_DEBUG_BP_CHECK
    VALUE *bp_check;        /* cfp[6] */
#endif
} rb_control_frame_t;
  • pc, 指令指針spa

  • sp,操做數棧指針線程

  • iseq,當前執行的代碼序列(使用 pc 索引)指針

  • self,略code

  • ep,本地存儲指針,方法(or block)參數和局部變量存儲在 ep 指向的區域orm

  • block_code,略blog

附上一張 棧幀 以及 本地存儲的圖
棧幀以及本地存儲索引

壓入棧幀

函數 vm_push_frame 實現了將一個棧幀壓入當前線程的堆棧之中ip

// vm_insnhelper.c

static inline rb_control_frame_t *
vm_push_frame(rb_thread_t *th, const rb_iseq_t *iseq, VALUE type, VALUE self, VALUE specval, VALUE cref_or_me, const VALUE *pc, VALUE *sp, int local_size, int stack_max)
{
    // 每一個線程都有一個棧,cfp 指向棧頂部(向下生長),指針減 1 給新的 rb_control_frame_t 預留空間
    rb_control_frame_t *const cfp = th->cfp - 1;
    int i;

    ...

    // 更新線程棧幀
    th->cfp = cfp;

    // 根據函數輸入參數,初始化新的棧幀
    /* setup new frame */
    cfp->pc = (VALUE *)pc;
    cfp->iseq = (rb_iseq_t *)iseq;
    cfp->self = self;
    cfp->block_code = NULL;

    /* setup vm value stack */
    // sp 指向線程棧空間底部(向上生長),這裏根據 本地變量 的大小,預留空間
    /* initialize local variables */
    for (i=0; i < local_size; i++) {
    *sp++ = Qnil;
    }

    /* setup ep with managing data */
    // 爲虛擬機內部使用的變量 cref, specval, type 預留空間
    ...
    *sp++ = cref_or_me; /* ep[-2] / Qnil or T_IMEMO(cref) or T_IMEMO(ment) */
    *sp++ = specval     /* ep[-1] / block handler or prev env ptr */;
    *sp   = type;       /* ep[-0] / ENV_FLAGS */

    cfp->ep = sp;
    cfp->sp = sp + 1;

#if VM_DEBUG_BP_CHECK
    cfp->bp_check = sp + 1;
#endif

    if (VMDEBUG == 2) {
    SDR();
    }

    return cfp;
}

彈出棧幀

彈出棧幀的操做相對比較簡單:

/* return TRUE if the frame is finished */
static inline int
vm_pop_frame(rb_thread_t *th, rb_control_frame_t *cfp, const VALUE *ep)
{
    VALUE flags = ep[VM_ENV_DATA_INDEX_FLAGS];

    if (VM_CHECK_MODE >= 4) rb_gc_verify_internal_consistency();
    if (VMDEBUG == 2)       SDR();

    th->cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp);

    return flags & VM_FRAME_FLAG_FINISH;
}

void
rb_vm_pop_frame(rb_thread_t *th)
{
    vm_pop_frame(th, th->cfp, th->cfp->ep);
}

指令格式定義

insns.def 文件的開頭註釋部分對指令格式進行了說明:

// insns.def

/** ##skip
  instruction comment
  @c: category
  @e: english description
  @j: japanese description

  instruction form:
    DEFINE_INSN
    instruction_name
    (instruction_operands, ..)
    (pop_values, ..)
    (return value)
    {
       .. // insn body
    }

 */
  • instruction_name 指令名稱

  • (instruction_operatns, ..) 指令操做數

  • (pop_values, ..) 指令執行時從操做數棧彈出的 VALUE

  • (return value) 指令完成以後壓如操做數棧 VALUE

Ruby 虛擬機和 Java 虛擬機同樣是基於棧的虛擬機

  • 指令須要的操做數必須先壓入操做數棧

  • 指令結果保存在操做數棧

  • 操做數棧和本地存儲(存放方法參數和局部變量的地方)之間能夠交換(load or store)數據

getlocal 指令

getlocal 指令將指定的本地變量(local var)從本地存儲加載到操做數棧
先來看看 getlocal 指令在 insns.def 文件中的定義:

// insns.def

/**
  @c variable
  @e Get local variable (pointed by `idx' and `level').
     'level' indicates the nesting depth from the current block.
  @j level, idx で指定されたローカル変數の値をスタックに置く。
     level はブロックのネストレベルで、何段上かを示す。
 */
DEFINE_INSN
getlocal
(lindex_t idx, rb_num_t level)
()
(VALUE val)
{
    int i, lev = (int)level;
    const VALUE *ep = GET_EP();

    /* optimized insns generated for level == (0|1) in defs/opt_operand.def */
    for (i = 0; i < lev; i++) {
    ep = GET_PREV_EP(ep);
    }
    val = *(ep - idx);
}

指令須要 兩個參數 idx 和 level

  • idx,本地變量在 本地存儲中的索引

  • level,本地存儲容許嵌套,level 用於指定本地存儲的級別

GET_EP 宏用於訪問當前 棧幀 的 ep 寄存器(能夠理解成 本地存儲的基地址)

// vm_insnhelper.h

#if VM_COLLECT_USAGE_DETAILS
#define COLLECT_USAGE_REGISTER_HELPER(a, b, v) \
  (COLLECT_USAGE_REGISTER((VM_REGAN_##a), (VM_REGAN_ACT_##b)), (v))
#else
#define COLLECT_USAGE_REGISTER_HELPER(a, b, v) (v)
#endif

#define GET_EP()   (COLLECT_USAGE_REGISTER_HELPER(EP, GET, VM_REG_EP))

這裏又是一大堆嵌套的宏定義,根據是否認義了 VM_COLLECT_USAGE_DETAILS,GET_EP 會以不一樣的形式展開,咱們先看簡單的狀況,即沒有定義 VM_COLLECT_USAGE_DETAILS,此時 GET_EP 被展開成 VM_REG_EP
VM_REG_CFP 最終展開成 reg_cfg->ep,reg_cfg 即上文咱們提到的 虛擬機 當前 棧幀

#define VM_REG_CFP (reg_cfg)
#define VM_REG_EP (VM_REG_CFP->ep)

咱們來看一下 vm.inc 中最終生成的 getlocal 指令的處理函數

// vm.inc

INSN_ENTRY(getlocal){
{
  VALUE val;
  // 獲取第二個操做數
  rb_num_t level = (rb_num_t)GET_OPERAND(2);
  // 獲取第一個操做數
  lindex_t idx = (lindex_t)GET_OPERAND(1);

  DEBUG_ENTER_INSN("getlocal");
  // PC 指針指向下一條指令,本指令佔用一個字節,操做數佔用兩個字節,因此增量爲 1 + 2
  ADD_PC(1+2);
  // gcc 編譯器 hack,模擬 CPU 取下一條指令
  PREFETCH(GET_PC());
  #define CURRENT_INSN_getlocal 1
  #define INSN_IS_SC()     0
  #define INSN_LABEL(lab)  LABEL_getlocal_##lab
  #define LABEL_IS_SC(lab) LABEL_##lab##_##t
  COLLECT_USAGE_INSN(BIN(getlocal));
  COLLECT_USAGE_OPERAND(BIN(getlocal), 0, idx);
  COLLECT_USAGE_OPERAND(BIN(getlocal), 1, level);
{
// 這部分代碼是從 insns.def 經過生成器拷貝過來的
#line 60 "insns.def"
    int i, lev = (int)level;
    const VALUE *ep = GET_EP();

    /* optimized insns generated for level == (0|1) in defs/opt_operand.def */
    for (i = 0; i < lev; i++) {
    ep = GET_PREV_EP(ep);
    }
    val = *(ep - idx);

#line 65 "vm.inc"
  CHECK_VM_STACK_OVERFLOW_FOR_INSN(REG_CFP, 1);
  // 將本地變量 val 壓入堆棧
  PUSH(val);
#undef CURRENT_INSN_getlocal
#undef INSN_IS_SC
#undef INSN_LABEL
#undef LABEL_IS_SC
  END_INSN(getlocal);}}}

上面對關鍵代碼段進行了註釋,這裏再介紹一下里面用到的幾個宏定義

GET_OPERAND

getlocal 指令的操做數被編碼在指令序列中,GET_OPERAND 經過 當前 PC 指針以及偏移量 n 獲取操做數

// vm_insnhelper.h

#define GET_OPERAND(n) (GET_PC()[(n)])

PUSH

PUSH 包含兩個操做來模擬 getlocal 獲取到的本地變量壓入操做數棧的操做

  • SET_SV,將變量設置到 棧幀 中 sp 指針指向的位置

  • INC_SP,遞增 sp 指針

// vm_insnhelper.h

#define PUSH(x) (SET_SV(x), INC_SP(1))
#define SET_SV(x) (*GET_SP() = (x))
#define INC_SP(x)  (VM_REG_SP += (COLLECT_USAGE_REGISTER_HELPER(SP, SET, (x))))
相關文章
相關標籤/搜索