Help us understand the problem. What is going on with this article?

D言語からC言語へトランスパイルすればどんなマイコンだってへっちゃらだね!

D言語をC言語にする利点

特集 D言語組み込みプログラミング入門1では、clangを使って直接ARM向けのバイナリを出力するという方法を用いました。
ここでお話しするのは、D言語から一度C言語のソースコードにトランスパイル2することによって、どんなマイコンでもC言語のコンパイラさえあればバイナリが得られるようにするというものです。

用意するもの

使用するものは以下の通りです。

  • LDC3
    LLVMベースのD言語コンパイラ。
    これをつかって、*.llまたは*.bcっていうファイルを作ります。
  • llvm-cbe4
    LLVM C言語バックエンド。
    *.ll*.bcから*.cbe.cというC言語ソースファイルを生成します。
  • C言語のコンパイラやリンカ、オブジェクトコンバータなど
    マイコンメーカ提供のコンパイラなど。上記ツイートの例ではgccを例として挙げています。
    *.cbe.cからバイナリ(*.exe, *.elf, *.bin, *.s, *.mot, *.hexなど)を得ます。
    本稿後半においては、RL78用のビルド環境としてCS+(CC-RL)5によるビルド例を紹介します。

llvm-cbeのビルド

Windowsユーザーの私はこの作業に非常に苦労しました。Ubuntu(WSL)ではあっさりビルドできました。Linux使ってる人はllvm-cbeの公式ページ4にビルド方法が載っているので、そのままやればいいと思います。

Windowsで私がビルドに成功したパターンは2パターンあって、MinGWを使う方法と、Visual Studio(cl.exe)6を使った方法です。ここではVisual Studioを使った方法を紹介します。

Visual Studio BuildTools 2019

Visual Studioといっても、フル機能をインストールする必要はありません。コマンドラインからビルドを行うので、BuildToolsで十分です。
Visual Studio BuildTools 2019は、以下のURLからインストーラをダウンロードしてインストールします。

https://aka.ms/vs/16/release/vs_buildtools.exe

上記URLは、Dockerとかのコンテナにインストールする際に使うものだそうです。7

CMake

CMakeの公式ページから8Windows版のCMakeをダウンロードしてきて、ビルドの際にはパスを通します。
また、ビルドの際にはコマンドラインも使えますが、今回はcmake-guiでビルド時の設定を行います。

Ninja

Ninjaは公式ページ9からWindows版をダウンロードして、ビルドの際にはパスを通します。

ソースコードの修正

Cloneしたままの状態ではコンパイルエラーが出てしまいます。(2019/7/7時点)
Visual Studioでは、 alloca() 関数を使う場合は #include <malloc.h> が必要なようです。
Issueで報告があります10ので、そのうち修正されるかもしれません。

llvm\projects\llvm-cbe\lib\Target\CBackend\CBackend.cpp
#if defined(_MSC_VER)
#include <malloc.h>
#endif

ビルド

コマンドラインでビルドを行います。
Visual Studio以外の必要なものは C:\app 以下にインストールし、ビルドは C:\work\llvm-cbe 以下で行うものとして説明します。

"C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\Common7\Tools\VsDevCmd.bat" -arch=x64
PATH=C:\app\CMake\bin;C:\app\Ninja;%PATH%
cd /D C:\work\llvm-cbe
mkdir build
git clone git clone https://github.com/llvm-mirror/llvm
cd llvm
git checkout release_70
cd projects
git clone git clone https://github.com/JuliaComputing/llvm-cbe
cd ../../build
cmake-gui

これで、CMakeの設定をGUIから行います。コマンドラインでも必要なものをすべて指定すればできますが、引数がとても多くなりますので、GUIから行った方がラクです。
CMakeの設定ができたらあとはビルドするだけです。先程のコマンドプロンプトを引き続き使用して以下を入力します。

ninja llvm-cbe

MinGWを使った場合より使用するRAMもコンパイル後のバイナリサイズも小さいので、オプションやビルド後の処理等は特に必要ありませんでした。

トランスパイル

以下の工程でD言語のソースコードをC言語に変換します。

D言語のソースコードを、-output-ll -betterCでコンパイルします。これで*.llが得られます。
*.bcが欲しい場合は-output-bc -betterCでコンパイルします。*.llでも*.bcでも内部の表現方法(前者はテキスト、後者はバイナリ)が違うだけで、根本は同じです。

以下のコードは、wait500ms, outputLEDという関数がそれぞれC言語によって記載されている前提で、それを使ってメイン関数でLチカさせるプログラムです。

source/app.d
module app;
extern (C) void wait500ms();
extern (C) void outputLED(ubyte cmd);

extern (C) void d_main() {
  while (1) {
    outputLED(1);
    wait500ms();
    outputLED(0);
    wait500ms();
  }
}

*.llファイルを作成します。

ldc2 -output-ll -betterC source/app.d -o app.ll

得られた*.ll, *.bcをllvm-cbeで*.cbe.cにトランスパイルします。

llvm-cbe app.ll -o app.cbe.c

コンパイルとビルド

今回は、ルネサスのCS+5を使ってRL78マイコン(RL78/G11 型式R5F1058AALA)11でのビルドを試してみます。RL78マイコンはARM以外のLLVMがバックエンドを持っていないアーキテクチャですので、C言語へのトランスパイルの効果が活きる環境といえます。
llvm-cbeで吐き出した*.cbe.cのファイルをRL78用コンパイラのCC-RLでコンパイルします。
評価ボードとして秋月電子通商より販売されている「RL78/G11 スティック型評価ボード12を使用しました。

C言語部分の実装

前述のD言語のコードでは、wait500ms, outputLEDの関数がないため、それぞれC言語で記載します。
最近のルネサスのマイコンでは、GUIによるコードジェネレーターが充実しているので、生成されたC言語コードの適切な箇所に関数を記載していきます。

タイマ設定

r_cg_tau_user.c
/** Renesas' maker provided function */
static void __near r_tau0_channel0_interrupt(void) {
  /* Start user code.
     Do not edit comment generated here */
  wk_cnt_tim500ms++;
  if (wk_cnt_tim500ms > 1000) {
    wk_cnt_tim500ms = 0;
    wk_flg_tim500ms = 1;
  }
  /* End user code.
     Do not edit comment generated here */
}

/* Start user code for adding.
   Do not edit comment generated here */
void wait500ms(void) {
  while (wk_flg_tim500ms == 0) {
    R_WDT_Restart();
  }
  wk_flg_tim500ms = 0;
}
/* End user code.
   Do not edit comment generated here */

ポート設定

r_cg_port_user.c
/* Start user code for adding.
   Do not edit comment generated here */
void outputLED(uint8_t cmd) {
  P5_bit.no6 = !cmd;
}
/* End user code.
   Do not edit comment generated here */

メイン関数の呼び出し

r_cg_main.c
/** Renesas' maker provided function */
void main(void) {
  R_MAIN_UserInit();
  /* Start user code.
     Do not edit comment generated here */
  d_main();
  /* End user code.
     Do not edit comment generated here */
}
/** Renesas' maker provided function */
static void R_MAIN_UserInit(void) {
  /* Start user code.
     Do not edit comment generated here */
  R_TAU0_Channel0_Start();
  EI();
  /* End user code.
     Do not edit comment generated here */
}

こんな感じにタイマ設定、ポート設定、メイン関数の箇所に関数を定義しました。

これだけでビルドできれば良いのですが、CS+(CC-RL)だとこれだけではビルドすることができませんでした。生成される*.cbe.cには、#define __noreturn __attribute__((noreturn))というものが定義されるようなのですが、CS+(CC-RL)ではこれを解釈することができないようです。したがって、CS+でincludeされるstdint.hなどのヘッダファイルに以下のようにこれを無視するようdefineを付け加えます。

stdint.h
#define __attribute__(x) 

これですべての準備が整いました。
先ほど用意した app.cbe.c ファイルをビルド対象に指定して、ビルドを行います。CS+ですと、F7キーを押下(またはメニューやツールバーからビルドを選択)することでビルドできます。

image.png

警告はいくつか出ますが、ビルド成功し、このままICEなどのデバッガで接続したり、プログラムライタでバイナリをマイコンへダウンロード(書き込み)することでプログラムが動作し、LEDが点滅します。

DUBを使う

基本的な原理としてはここまでに解説した内容で終わりなのですが、D言語を使っているとビルドに便利なのがdubです。今回においても、 app.cbe.c を生成するところまではdubで行うことができます。以下に設定例を記載します。

{
  "authors": [ "SHOO" ],
  "copyright": "Copyright © 2019, SHOO",
  "description": "A minimal D application.",
  "license": "public domain",
  "name": "rl78_test",
  "targetName": "app.ll",
  "buildTypes": {
    "release": {
      "buildOptions": [
        "releaseMode", "inline",
        "optimize", "noBoundsCheck", "betterC"],
      "dflags-ldc": ["--output-ll", "-conf="]
    }
  },
  "postBuildCommands-windows": [
    "llvm-cbe app.ll.exe -o d_llvm_cbe/app.cbe.c"
  ]
}

Windowsの場合、dubによって*.ll.exeとexe拡張子が付け加えられてしまいますが、そのままllvm-cbeに渡すことができるようです。
また、dubでビルドする際には、以下のように --compiler=ldc2 を指定するようにします。

dub build -b=release --compiler=ldc2

上記のようにdubで設定を行った場合、source以下のすべてのファイルが一つのapp.cbe.cにまとめられます。
この上で、再度C言語環境での(今回の例ではCS+で)ビルドを行うことで、バイナリを得ることができます。

最後に

今回のプロジェクトはGitHubのリポジトリ13にまとめてあります。
llvm-cbeをビルドし、パスを通した状態でdubでC言語のコード生成を行ったあとに、同梱のCS+用プロジェクトファイル(*.mtpj)によってビルドできるようにしておきました。
ぜひご参考になさってください。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした