Write your Own Virtual Machine - L5_Implementing Instructions.

前言:这是 Write your Own Virtual Machine 的第五篇文章。主要是来介绍指令是如何实现的。原文在这里

指令的实现(Implementing Instructions)

现在的任务是完成上一篇文章中 opcode 对应的代码 。每个指令的详细规格包含在项目文档中。你需要了解每条指令如何从其规范中工作并编写实现。这比听起来容易。我将在这里演示如何实现其中两个。其余的代码可以在下一节中找到。

  • ADD

ADD 指令采用两个数字,将它们相加,并将结果存储在寄存器中。有关它的规范,请参见第 526 页。每条 ADD 指令如下所示:

Add Encoding

编码显示两行,因为此指令有两种不同的“模式”。在我解释模式之前,让我们试着找出它们之间的相似之处。在这两行中,我们可以看到我们从 4 位 0001 开始。这是 OP_ADD 的操作码值。接下来的 3 位标记为 DR。这代表目的地寄存器(destination register)。目的寄存器是存储和的位置。接下来的 3 位是 SR1。这是要求和的第一个数字的寄存器。

所以我们知道我们想要存储结果的位置,我们知道要求和的第一个数字。我们需要的最后一点信息是要求和的第二个数字。此时,两行开始看起来不同。请注意,在第一行,第 5 位为 0,第二行为 1。该位指示是立即模式还是寄存器模式。在寄存器模式下,第二个数字与第一个数字一样存储在寄存器中。这标记为 SR2,包含在位 2-0 中。第 3 和第 4 位未使用。在汇编中,这将写成:

ADD R2 R0 R1 ; add the contents of R0 to R1 and store in R2.

在立即模式中,不是添加寄存器的内容,而是将值嵌入到指令本身中。这很方便,因为程序不需要更多指令将该数字从内存加载到寄存器中。相反,当我们需要它时,它就在指令内部。它只能存储一个小数字 2^{5} = 32 (无符号)。这对于递增非常有用。在汇编中,它可以写成:

ADD R0 R0 1 ; add 1 to R0 and store back in R0

以下摘自项目文档

If bit [5] is 0, the second source operand is obtained from SR2. If bit [5] is 1, the second source operand is obtained by sign-extending the imm5 field to 16 bits. In both cases, the second source operand is added to the contents of SR1 and the result stored in DR. (Pg. 526)

这看起来就和我们讨论的一模一样。但是什么是 sign-extending?虽然立即模式值只有 5 位,但需要将其扩展到 16 位数。这 5 位需要扩展到 16 以匹配另一个数字。对于正数,我们可以为附加位填充0,并且值相同。但是,对于负数,这会导致问题。例如,以 5 位存储的 -1 是 11111。如果我们只是将其扩展为 0,则为 0000 0000 0001 1111,等于 32!符号扩展通过填充 0 表示正数而 1 表示负数来防止此问题。

// 符号扩展算法
uint16_t sign_extend(uint16_t x, int bit_count)
{
    if ((x >> (bit_count - 1)) & 1) {
        x |= (0xFFFF << bit_count);
    }
    return x;
}

Note:如果你对二进制中的负数如何表示感兴趣,可以阅读 Two's Complement。但是,这不是必需的。你可以复制上面的代码,并在需要的时候使用它。

说明书中有最后一句话:

根据结果是负,零还是正,设置条件代码。 (第 526 页)(The condition codes are set, based on whether the result is negative, zero, or positive. (Pg. 526))

之前我们定义了一个条件标志 enum,现在是时候使用它们了。每次将值写入寄存器时,我们都需要更新标志以指示其符号。我们将编写一个函数,以便可以重用它:

void update_flags(uint16_t r)
{
    if (reg[r] == 0)
    {
        reg[R_COND] = FL_ZRO;
    }
    else if (reg[r] >> 15) /* a 1 in the left-most bit indicates negative */
    {
        reg[R_COND] = FL_NEG;
    }
    else
    {
        reg[R_COND] = FL_POS;
    }
}

现在我们准备为 ADD 编写代码了:

{
    /* destination register (DR) */
    uint16_t r0 = (instr >> 9) & 0x7;
    /* first operand (SR1) */
    uint16_t r1 = (instr >> 6) & 0x7;
    /* whether we are in immediate mode */
    uint16_t imm_flag = (instr >> 5) & 0x1;

    if (imm_flag)
    {
        uint16_t imm5 = sign_extend(instr & 0x1F, 5);
        reg[r0] = reg[r1] + imm5;
    }
    else
    {
        uint16_t r2 = instr & 0x7;
        reg[r0] = reg[r1] + reg[r2];
    }

    update_flags(r0);
}

这一节包含了很多信息,让我们来总结一下:

  • ADD 采用两个值并将它们存储在寄存器中

  • 在寄存器模式中,要添加的第二个值在寄存器中找到

  • 在立即模式下,第二个值嵌入在指令的最右边 5 位中

  • 短于 16 位的值需要进行符号扩展

  • 只要指令修改寄存器,就需要更新条件标志

你可能会对写 15 条指令感到不知所措。但是,你在这里学到的所有内容都将被重用。大多数指令使用符号扩展,不同模式和更新标志的某种组合。

LDI

LDI 代表“间接负载”。该指令用于将值从存储器中的位置加载到寄存器中。规范见第 532 页。

这是二进制代码的样子

LDI Encoding

与 ADD 相比,没有不同的模式而且参数更少。这次,操作码是1010,它对应于 OP_LDI 枚举值。与 ADD 一样,它包含一个 3 位DR(目标寄存器),用于存储加载的值。其余位标记为 PCoffset9。这是嵌入指令的直接值(类似于 imm5 )。由于该指令从内存加载,我们可以猜测这个数字是某种地址告诉我们从哪里加载。说明书提供了更多细节:

通过将位 [8:0] 符号扩展到 16 位并将该值加上递增的 PC 来计算地址。存储在该地址中的地址是要加载到 DR 中的数据的地址。 (第532页)(An address is computed by sign-extending bits [8:0] to 16 bits and adding this value to the incremented PC. What is stored in memory at this address is the address of the data to be loaded into DR. (Pg. 532))

就像以前一样,我们需要有符号扩展这个 9 位值,但这次将它与当前的 PC 相加中。 (如果回头看执行循环,PC 在加载此指令后立即递增。)结果总和是内存中某个位置的地址,该地址包含另一个值。这个值是要加载的数据的地址。

这似乎是一种从内存中读取的迂回方式,但它是不可或缺的。 LD 指令仅限于 9 位的地址偏移,而存储器需要 16 位来寻址。 LDI 对于加载存储在远离当前 PC 的位置的值很有用,但是要使用它,最终位置的地址需要存储在附近的邻域中。你可以把它想象成在 C 中有一个局部变量,它是一个指向某些数据的指针:

// the value of far_data is an address
// of course far_data itself (the location in memory containing the address) has an address
char* far_data = "apple";

// In memory it may be layed out like this:

// Address Label      Value
// 0x123:  far_data = 0x456
// ...
// 0x456:  string   = 'a'

// if PC was at 0x100
// LDI R0 0x023
// would load 'a' into R0

与之前相同,在将值放入 DR 后需要更新标志

以下是此 case 的代码:(mem_read将在后面的部分中讨论。)

{
    /* destination register (DR) */
    uint16_t r0 = (instr >> 9) & 0x7;
    /* PCoffset 9*/
    uint16_t pc_offset = sign_extend(instr & 0x1ff, 9);
    /* add pc_offset to the current PC, look at that memory location to get the final address */
    reg[r0] = mem_read(mem_read(reg[R_PC] + pc_offset));
    update_flags(r0);
}

正如我所说,这条指令共享了很多从 ADD 学到的代码和知识。你会发现其余指令也是这种情况。

你现在需要回到之前实现其余的指令。遵循说明书并使用之前列出的代码完成其他代码。所有指令的代码都列在本教程的末尾。之前指定的操作码中有两个操作码将不会被使用,它们是 OP_RTI 和OP_RES。你可以忽略这些情况。完成后,你的大部分 VM 将完成!

最后

GitHub

点个喜欢呗
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容