iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 22
0
IoT

拿到錘子的我想在微控制器上面執行 Ruby系列 第 22

Day 22 - 比較處理

經過將近一個月的努力,現在我們現在要擴充 OPCode 的處理也容易很多,階段性的目標是要能在 TFT 螢幕上顯示一些訊息跟動畫,因此除了前面能讓 Ruby 運作起來我們還需要一些像是邏輯判斷的機能來幫助我們做處理。

增加 OPCode

更新 lib/iron/opcode.h 增加比較相關的指令

// lib/iron/opcode.h

enum {
  // ...
  OP_EQ = 65,
  OP_LT,
  OP_LE,
  OP_GT,
  OP_GE,
  // ...
};

更新 lib/iron/vm.c 增加對應的處理

// lib/iron/vm.c

// ...

      CASE(OP_EQ, B) {
        reg[a] = reg[a] == reg[a + 1] ? 1 : 0;
        DEBUG_LOG("r[%d] = r[%d] == r[%d+1]", a, a, a);
        NEXT;
      }
      CASE(OP_LT, B) {
        reg[a] = reg[a] < reg[a + 1] ? 1 : 0;
        DEBUG_LOG("r[%d] = r[%d] < r[%d+1]", a, a, a);
        NEXT;
      }
      CASE(OP_LE, B) {
        reg[a] = reg[a] <= reg[a + 1] ? 1 : 0;
        DEBUG_LOG("r[%d] = r[%d] <= r[%d+1]", a, a, a);
        NEXT;
      }
      CASE(OP_GT, B) {
        reg[a] = reg[a] > reg[a + 1] ? 1 : 0;
        DEBUG_LOG("r[%d] = r[%d] > r[%d+1]", a, a, a);
        NEXT;
      }
      CASE(OP_GE, B) {
        reg[a] = reg[a] >= reg[a + 1] ? 1 : 0;
        DEBUG_LOG("r[%d] = r[%d] > r[%d+1]", a, a, a);
        NEXT;
      }

// ...

這樣我們就能夠根據 Ruby VM 裡面的寄存器所儲存的數值來做比較,並且用 10 表示 truefalse

if 處理

既然能夠比較數值,我們就還需要能夠進行判斷的 if 語法支援,繼續對 OPCode 擴充

// lib/iron/opecode.h

enum {
  // ...
  OP_JMP = 33,
  OP_JMPIF,
  OP_JMPNOT,
  OP_JMPNIL,
  // ...
};

在 mruby 的處理中,當判斷成立或者不成立的時候會「跳」到指定的位置執行,假設資料是一個陣列的話

[
 OP, # LOADI
 DATA,
 OP, // LOADI
 DATA,
 OP, // IF -> 跳到 A (true) 或 B (false)
 DATA,
 DATA,
 OP, // IF true (A)
 DATA,
 OP,
 DATA,
 OP, // IF false (A)
 DATA,
 OP,
 DATA
]

透過這樣的方式也能夠實現迴圈之類的效果,因此我們要實作的行為會是 JMPJMPIF 這類邏輯。

// lib/iron/vm.c

// 跳躍點紀錄
const uint8_t* prog = p;

while(!error) {
  // ...
      CASE(OP_JMP, S) {
        p = porg + a;
        DEBUG_LOG("jmp %d", b);
        NEXT;
      }
      CASE(OP_JMPIF, BS) goto L_JMPNOT;
      CASE(OP_JMPNOT, BS) {
      L_JMPNOT:
        if (insn == OP_JMPIF) {
          DEBUG_LOG("jmp %d if r[%d] == %ld", b, a, reg[a]);
        } else {
          DEBUG_LOG("jmp %d if !r[%d] == %ld", b, a, reg[a]);
        }

        if ((insn == OP_JMPIF) == (reg[a] != 0)) {
          p = porg + b;
        }
        NEXT;
      }
  // ...
}

這邊稍為不同的是要「跳躍」的目標我們在之前的實作是無法得知的,因此需要增加一個 prog 來記錄 ISEQ 的起始點,用這個數值當作參考紀錄 mruby 告訴我們要將處理的位置移動到哪裡繼續執行。

因為這次我們抓取的數值不太一樣,因此要在 lib/iron/opcode.h 另外增加 FETCH_ 系列的巨集來輔助我們做處理。

// lib/iron/opcode.h

// ...
#define FETCH_BS() do { a = READ_B(); b = READ_S(); } while(0)
#define FETCH_S() do { a = READ_S(); } while(0)

// ...

稍微修改 test/test_native/main.cpp 加入 puts 實作來做測試

// test/test_native/main.cpp

void mrb_puts(mrb_state* mrb) {
  int argc = mrb_get_argc(mrb);
  mrb_value* argv = mrb_get_argv(mrb);

  for(int i = 0; i < argc; i++) {
    printf("%d\n", mrb_fixnum(argv[i]));
  }
}

void test_mrb_run(void) {
  mrb_state* mrb = mrb_open();
  mrb_define_method(mrb, "puts", mrb_puts);
  mrb_run(mrb, app);
  mrb_close(mrb);
}

// ...

更新 app.rb 來加入可以做 if 判斷的處理

puts 1 if 10 > 5

最後我們就可以用 pio test 來驗證我們的實作是否正確。

下一篇我們會實作迴圈的機制,讓我們的程式可以維持運行,這樣就能讓我們在 TFT 螢幕上呈現一些動畫。


上一篇
Day 21 - 用 HAL 區分不同硬體
下一篇
Day 23 - 迴圈支援
系列文
拿到錘子的我想在微控制器上面執行 Ruby30

尚未有邦友留言

立即登入留言