博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Ruby 2.x 源代码学习:语法分析 & 中间代码生成 之 指令链表序列化
阅读量:6240 次
发布时间:2019-06-22

本文共 3151 字,大约阅读时间需要 10 分钟。

前言

回顾一下 Ruby 解释器处理流程

  • 词法分析

  • 语法分析

  • AST 生成

  • 虚拟机指令生成

虚拟机指令有两种存储结构:

  • 链式结构,通过 LINK_ELEMENT 链表节点

  • 数组(线性)结构

解释器会先遍历 AST 生成链式结构存储的指令列表,但是虚拟机并不会直接解释执行该列表,而是进一步将指令列表“序列化”成字节数组,PC(指令指针)寄存器可以索引指令数组(取指,分支跳转)

本文简要介绍 Ruby 2.x 源代码中将链式结构转化成数组结构的源代码,从中可以进一步了解 Ruby 虚拟机相关数据结构的设计与实现

由 AST 生成虚拟机指令(简要回顾)

由此前的系列文章可以,rb_iseq_compile_node 函数负责遍历 AST 生成虚拟机指令:

// compile.cVALUE rb_iseq_compile_node(rb_iseq_t *iseq, NODE *node) {    DECL_ANCHOR(ret);    INIT_ANCHOR(ret);    ...    return iseq_setup(iseq, ret);}

DECL_ANCHOR 和 INIT_ANCHOR 声明了一个双向链表用于收集在遍历 AST 根节点 node 过程中生成的虚拟机指令,到达函数末尾时,ret 指向指令链表的头部,然后 iseq_setup 函数登场,对指令链表进行优化,序列化等操作

iseq_setup

为了便于分析,去掉了 iseq_setup 函数中 compile_debug 开关控制的调试输出以及一些编译选项(比如 stack caching, instructions unification .etc)

// compile.cstatic int iseq_setup(rb_iseq_t *iseq, LINK_ANCHOR *const anchor) {    iseq_optimize(idea, anchor);    ...    if (!iseq_set_sequence(iseq, anchor)) {        return COMPILE_NG;    }    ...    return COMPILE_OK;}

iseq_set_sequence 将 anchor 转化为 instruction sequence 存储在 iseq 中

iseq_set_sequence

iseq_set_sequence 函数有一段注释表明了该函数的用途

// compile.c/** ruby insn object list -> raw instruction sequence **/static int iseq_set_sequence(rb_iseq_t *iseq, LINK_ANCHOR *const anchor) {    struct iseq_line_info_entry *line_info_table;    unsigned int last_line = 0;    LINK_ELEMENT *list;    VALUE *generated_iseq;    int insn_num, code_index, line_info_index, sp, stack_max = 0, line = 0;}

函数开头的局部变量定义,信息量还是很大的

  • line_info_table,行号表

  • last_line

  • list,遍历 anchor 的中间变量

  • generated_iseq,VALUE 数组,用于存放序列化后的指令

  • insn_num,anchor 中包含的指令个数

  • code_index

  • line_info_index

  • sp

  • stack_max,执行指令过程中堆栈的最大值

  • line

iseq_set_sequence 会遍历两次 anchor(指令链表),第一次用于搜集指令个数 insn_num,序列后的指令数组的大小 generated_iseq 以及确定 label(标签)的跳转位置。第二次遍历用于生成指令序列

第一次遍历指令链表

第一次遍历指令链表的主体代码框架如下:

// compile.cstatic int iseq_set_sequence(rb_iseq_t *iseq, LINK_ANCHOR *const anchor) {    ...    /* fix label position */    list = FIRST_ELEMENT(anchor);    insn_num = code_index = 0;    while (list) {        switch(list->type) {            case ISEQ_ELEMENT_INSN:                ...                break;            case ISEQ_ELEMENT_LABEL:                ...                break;            case ISEQ_ELEMENT_ADJUST:                ...                break;            default:                ...                return COMPILE_NG        }        list = list->next;    }}

ISEQ_ELEMENT_INSN

// iseq_set_sequence @ compile.ccase ISEQ_ELEMENT_INSN: {    INSN *iobj = (INSN*) list;    line = iobj->line_no;    // 调用 insn_data_length 获取指令占用的字节数并累加到 code_index    code_index += insn_data_length(jobs);    // 指令个数加 1    insn_num++;    break;}

ISEQ_ELEMENT_LABEL

ISEQ_ELEMENT_LABEL 类型的指令是一条伪指令,并不生成具体的指令序列,只是为了确定分支跳转的指令偏移量。在创建 label 的时候 position,set 属性并未赋值,要等到此处才能确定它们的真实值

// iseq_set_sequence @ compile.ccase ISEQ_ELEMENT_LABEL: {    LABEL *lobj = (LABEL*) list;    lobj->position = code_index;    lobj->set = TRUE;    break;}

ISEQ_ELEMENT_NONE

ISEQ_ELEMENT_ADJUST

第二次遍历指令链表

准备工作

使用第一次遍历得到的 code_index 和 insn_num 为 generated_iseq 和 line_info_table 分配空间

// iseq_set_sequence @ compile.cgenerated_iseq = ALLOC_N(VALUE, code_index);line_info_table = ALLOC_N(struct iseq_line_info_entry, insn_num);...

总结

转载地址:http://ahdia.baihongyu.com/

你可能感兴趣的文章
Quartz2D简单绘制之饼状图
查看>>
你优化系统的目标是什么?
查看>>
SVN(64位)报 Failed to load JavaHL Library. 的解决方法
查看>>
基本运算符
查看>>
黄聪:WordPress 多站点建站教程(三):主站如何调用子站的文章内容、SQL语句如何写?...
查看>>
Activity的启动模式 4种launchMode Intent.FLAG_NEW_TASK 详解
查看>>
hdu 2254 奥运 **
查看>>
数据结构基础
查看>>
UltraISO制作ISO镜像文件
查看>>
ASP.NET MVC 之自定义HtmlHelper
查看>>
声明顺序
查看>>
memcpy内存重叠的解决
查看>>
保存和恢复activity的状态数据[转]
查看>>
JS中call、apply的用法说明
查看>>
C#中对于Enum类型的遍历
查看>>
使用tomcat启动dubbo项目
查看>>
crontab + shell脚本实现文件重命名
查看>>
谈谈-ConstraintLayout完全解析
查看>>
fluent-ffmpeg 常用函数
查看>>
Robot Framework(十五) 扩展RobotFramework框架——远程库接口
查看>>