iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 6
0
IoT

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

Day 6 - 讀取 IREP 資料(ㄧ)

經過前面幾天的分析,我們目前已經有一個可以撰寫 Ruby VM 的環境,也了解該如何從 mruby 的機器碼中讀取到實際上要執行的 ISEQ 區段,這篇文章會先進行第一階段,也就是讀取 IREP 資訊用於後續處理的部分。

自動產生機器碼

為了能讓我們的 app.rb 可以事先轉換成能夠被包含在 C 原始碼裡面的版本,我們需要對原本的 Makefile 做一些更新,加入轉換 Ruby 程式碼為 .c 的機制。

# 手動加入 `src/app.c` 作為任務的相依
%.o: %.c src/app.c
	$(CC) -I include -c $< -o $@

# src/app.c 這個檔案會基於 app.rb 透過 mrbc 產生
src/app.c: app.rb
	mrbc -B app -o $@ $^

# 加入 `src/app.o` 作為任務的相依,檔案由 `src/app.o` 任務生成
bin/iron-rb: $(OBJECTS) src/app.o
	@mkdir -p $(BIN_DIR)
	$(CC) -o $@ $^

# 增加刪除 `src/app.c` 的指令
clean:
	rm -rf $(BIN_DIR)/*
	rm -rf src/app.c
	rm -rf src/*.o

因為 src/app.c 並不是普通的 .c 原始碼而是透過 mruby 的編譯器產生的檔案,因此我們需要加入對應的任務來告知獲取這個檔案的方法。

mrbc 可以利用 -B 選項輸出成 C 語言的格式,而 -B 後的參數則是變數名稱,這邊我們用 app 作為變數名稱。

因為生成的是 .c 的原始碼檔案,我們還需要一個 .h 檔案才可以讀取到裡面的機器碼,因此需要再新增另一個 src/app.h 來輔助。

#ifndef _IRON_APP_H_
#define _IRON_APP_H_

#include <stdint.h>

extern uint8_t app[];

#endif

如此一來我們就可以在我們的專案中用 #include "app.h" 來讀取到 mruby 編譯出來的機器碼。

除了這個方法之外,我們還可以透過 #include "app.c" 的方式來讀取,不過比較好的做法是直接輸出為 src/app.h 就不用多製作一個檔案,在比較後面的步驟有修正為這個方式。

定義 API

因為我們跟 mruby-L1VM 的目標不同,我們希望製作的是一個比 mruby 本身精簡,但又不像 mruby-L1VM 省略大多數功能的方式設計,因此會稍微做簡單的檔案區分,除此之外將檔案分開專寫也有助於其他人閱讀原始碼。

// src/irep.h

#ifndef _IRON_IREP_H_
#define _IRON_IREO_H_

/**
 * mruby binary header
 *
 * uint8_t rite_binary_header[22]
 * uint8_t rite_section_irep_header[12]
 *
 */
#define irep_load(irep) ((irep) + 34)

#endif

首先,我們先參考 mruby-L1VM 的方式,製作一個跳過 mruby 的 RITE Header 和 IREP Header 的處理,直接進入到 IREP 資料的本體的 irep_load 巨集。

// src/vm.h

#ifndef _IRON_VM_H_
#define _IRON_VM_H_

#include <stdint.h>
#include "irep.h"

// TODO: pass mrb_state
#define mrb_run(irep) mrb_exec(irep_load(irep))

int mrb_exec(const uint8_t* irep);

#endif

然後我們再定義一個 mrb_run 巨集來當作執行的進入點,同時定義 mrb_exec 作為我們運行程式的本體,也是我們之後會讀取 ISEQ 執行的地方。

#include "vm.h"

int mrb_exec(const uint8_t* irep) {
  // TODO
  return 0;
}

最後我們將 mrb_exec 的實作放到定位先不做任何處理。

在主程式呼叫

完成上述步驟後,我們將 main.c 更新去呼叫這些 API 順便驗證前面的實作是沒有問題的。

#include "app.h"
#include "vm.h"

int main(int argc, char** argv) {
  mrb_run(app);

  return 0;
}

下一篇會開始針對 IREP 的讀取進行處理,我們會參考 mruby-L1VM 來讀取 IREP 的 nlocalsnregs 等資訊。


上一篇
Day 5 - 環境準備
下一篇
Day 7 - 讀取 IREP 資料(二)
系列文
拿到錘子的我想在微控制器上面執行 Ruby30

尚未有邦友留言

立即登入留言