[指令生成]指令数据结构及虚拟机指令集

    这里要说的指令,并不是x86体系结构下的机器指令,而是虚拟机指令。Jerry虚拟机的设计并不是非常复杂——甚至看起来很不灵活——它是一个栈式虚拟机,不包括任何寄存器(除了指令计数器之外),所有的指令都是基于栈进行的。在文章最后将给出完整的指令集。现在先给出指令数据结构,它们包括

typedef enum {
    // ... 指令集,文章最后会贴出完整的
} InstructionCode;

// 依旧 COOL 风格的成员
// segOffset 指令的偏移值
// code 指令码
#define memberAbstractInstruction \
    int segOffset;                \
    InstructionCode code; /*******/

// 指令数据结构基类
struct AbstractInstruction {
    memberAbstractInstruction
};

// 无参数的指令
struct NoParamInstruction {
    memberAbstractInstruction
};

// 带整型参数的指令
struct IntParamInstruction {
    memberAbstractInstruction
    int param;
};

// 带实型参数的指令
struct RealParamInstruction {
    memberAbstractInstruction
    double param;
};

// 跳转指令
// targetIns 目标指令指针
struct JumpInstruction {
    memberAbstractInstruction
    struct AbstractInstruction* targetIns;
};

也许你会觉得奇怪,为什么跳转指令不计入整型参数的指令,要单独拿出来,还弄个指针参数?如果这是个解释器的话,问题还不大,但指针如果存放进字节码文件再取回内存,如果运气不是好到雇佣大猩猩能敲出Jerry编译程序的话,那么指针指向的地址将不是我们期望的。因此,这里的目标指令指针的含义将会在最后写出指令时变化为整数偏移量。

    不在这一步就算好所有指令的地址,然后用整数来保存这个域,是考虑到后期优化。否则,某些删去无用指令的优化,将会导致跳转指令的偏移失效。为了避免每次这样小动作都重新计算跳转指令的偏移,方法就是,让跳转指令的偏移量在写出指令到文件之前的最后关口才确定下来。确定的方法就是用指令的 segOffset 域差值来计算——这样一来 segOffset 的值也必须到那个时候(之前一点点)计算才行。

 

    刚才提到了,虚拟机是基于栈的,这就带来了很多偷懒的机会,比如加法指令就不用去操心拿那几个寄存器摆弄了。顺带提一下,Jerry的指令大多数是定长的,只有一部分带参数指令会将参数放在指令末尾,它们的长度较其他指令长。

 

    运算指令

 

 INT_PLUS       弹出栈顶 B 和次栈顶 A,计算 A + B 压入栈中

 INT_MINUS                                 -

 INT_MULTIPLY                              *

 INT_DIVIDE                                /

 INT_LT                                    <

 INT_LE                                    <=

 INT_EQ                                    ==

 INT_NE                                    !=

 INT_GT                                    >

 INT_GE                                    >=

 

注意上面 A 是次栈顶,而 B 是栈顶,下面也是。

 

 INT_ASSIGN 弹出栈顶 B 和次栈顶 A,将 B 的值写入 A 指向的地址中去,然后在栈顶压入 B 的值。

 

对于实型数,有一整套对应的指令:

    REAL_PLUS, REAL_MINUS, REAL_MULTIPLY, REAL_DIVIDE, REAL_ASSIGN,

    REAL_LT, REAL_LE, REAL_EQ, REAL_NE, REAL_GT, REAL_GE

 

    IO 指令

 

    READ_INT   弹出栈顶 A,输入一个整数,将值放入 A 指向的地址中去。

    READ_REAL                   实数

    WRITE_INT  弹出栈顶整数 A 输出。

    WRITE_REAL        实数

 

    运行时类型转换

 

      INT_2_REAL 弹出栈顶整数,转换成实数压回栈中

      REAL_2_INT        实数       整数

 

 

    常数加载

 

    LOAD_INT  弹出栈顶 A,输入一个整数,从 A 指向的地址加载一个整数到栈顶

    LOAD_REAL                                            实数

 

    弹栈

 

    POP 其实就是单纯地让栈顶指针向基部靠拢,这是一个带整型参数的指令,参数说明了弹出的字节数。

 

 

    常数加载

 

    CONST_INT  加载一个常数整数到栈顶,这是一个带整型参数的指令。

    CONST_REAL           实数                实型

 

    控制指令

 

    JMP         无条件跳转,参数为整数,表示偏移量

    JMP_IF_TOP  弹出栈顶整数 A,如果 A 不为 0,则跳转,参数为整数,表示偏移量

    JMP_NOT_TOP                      为

    END_PROGRAM 终止程序

 

 

    其他

    NOP 无操作

 

结束。

typedef enum {
    INT_PLUS, INT_MINUS, INT_MULTIPLY, INT_DIVIDE, INT_ASSIGN,
    INT_LT, INT_LE, INT_EQ, INT_NE, INT_GT, INT_GE,
    REAL_PLUS, REAL_MINUS, REAL_MULTIPLY, REAL_DIVIDE, REAL_ASSIGN,
    REAL_LT, REAL_LE, REAL_EQ, REAL_NE, REAL_GT, REAL_GE,
    INS_NOT,
    READ_INT, READ_REAL, WRITE_INT, WRITE_REAL,
    INT_2_REAL, REAL_2_INT,
    LOAD_INT, LOAD_REAL, POP,
    CONST_INT, CONST_REAL,
    JMP, JMP_IF_TOP, JMP_NOT_TOP,
    NOP, END_PROGRAM
} InstructionCode;

// 根据算术符号的枚举值获取对应操作码,注意两个枚举的顺序要一致
#define getCodeByIntOp(x) ((x) - PLUS + INT_PLUS)
#define getCodeByRealOp(x) ((x) - PLUS + REAL_PLUS)

// 是否为无参数操作
#define isNoParamIns(x) (((x) >= INT_PLUS && (x) <= LOAD_REAL) \
                            || NOP == (x) || END_PROGRAM == (x))
// 是否为整型参数操作
#define isIntParamIns(x) (POP == (x) || CONST_INT == (x))
// 是否为实型参数操作
#define isRealParamIns(x) (CONST_REAL == (x))
// 是否为跳转
#define isJump(x) (JMP <= (x) && (x) <= JMP_NOT_TOP)
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值