LoginSignup
3
1

More than 1 year has passed since last update.

ZephyrでD言語を動かす話

Last updated at Posted at 2023-03-06

レポジトリはこちらにおいています: https://github.com/kubo39/zephyr_ldc_hello

なので(?)細かい解説はあまりしません。

環境構築

Zephyrの環境構築は https://docs.zephyrproject.org/latest/develop/getting_started/index.html みれば問題なくいけると思います。

D言語のコンパイラで今回使ってるLDCは https://dlang.org/download.html とかをみて適当に入れてください。

Qemuで動かす

今回は qemu_cortex_m3 で動かすというのをまず目標におきました。

そのためLDCのtargetは thumbv7em-none-linux-musl-gnueabi にしています。
またnewlibの関数を使いたかったのでprj.confで CONFIG_NEWLIB_LIBC=y を定義します。
newlibを使っているのにtarget tripleでmuslを指定しているのはなんで?という部分は後述します。

あとはまあなんやかんや指定してCMakeからExternalProjectとして定義して呼び出す形にし、いい感じに配置してやると動きます。

$ cd $ZEPHYR_BASE
$ west build -b qemu_cortex_m3 samples/ldc_hello
$ west build -t run
(...)
Hello from 'LDC'!
assertion "array index out of bounds" failed: file "d_src/hello.d", line 29, function: hello.d_main
exit

D言語のコード

コードはこんな感じになりました。

import ldc.attributes : cold;

@nogc:
nothrow:

// newlib
extern (C)
{
    pragma(printf)
    int printf(scope const char* fmt, ...);

    noreturn __assert_func(scope const char* file, int line, scope const char* func, scope const char* failedexpr);
}

// Wrapping newlib's __assert_func.
//
// LDC: https://github.com/ldc-developers/ldc/blob/9976807e0e1acf24edfb4ba35d28c19a3f0227f2/gen/runtime.cpp#L367
//     void __assert(const char *msg, const char *file, unsigned line)
// newlib: https://github.com/bminor/newlib/blob/80cda9bbda04a1e9e3bee5eadf99061ed69ca5fb/newlib/libc/stdlib/assert.c#L68-L70
//     void __assert(const char *file, int line, const char *failedexpr)
private extern (C) @cold noreturn __assert_fail(const(char)* msg, const(char)* file, int line, const(char)* func)
{
    __assert_func(file, line, func, msg);
}

extern (C) noreturn d_main()
 {
    string ldc = "LDC";
    printf("Hello from '%.*s'!\n", cast(int)ldc.length, ldc.ptr);
    int[2] arr;
    int x;
    foreach (i; 0..3)
        x = arr[i];  // assertion "array index out of bounds" failed!
    while (true) {}
}

コード本体の解説はしませんが、ここで使っているD言語の機能としていくつかあげておきます:

musl target と __assert

D言語のコンパイラは配列の境界チェックをサポートしています。
その際に--checkactionオプションで範囲外アクセスが起きた時の挙動をhalt/C/D/contextから選択できるのですが、
ここで --checkaction=C を選択した場合、範囲外アクセスが起きた場合はC言語のassert関数を呼び出す処理になっています。
ただし正確にはassert関数を直接呼ぶのではなく、各プラットフォーム・アーキテクチャごとに異なる定義になっているassert相当の関数を呼んでいます。

コンパイラでいうと以下の箇所がその処理の一例です。

// C assert function:
// OSX:     void __assert_rtn(const char *func, const char *file, unsigned line,
//                            const char *msg)
// Android: void __assert(const char *file, int line, const char *msg)
// MSVC:    void  _assert(const char *msg, const char *file, unsigned line)
// Solaris: void __assert_c99(const char *assertion, const char *filename, int line_num,
//                            const char *funcname);
// Musl:    void __assert_fail(const char *assertion, const char *filename, int line_num,
//                             const char *funcname);
// uClibc:  void __assert(const char *assertion, const char *filename, int linenumber,
//                        const char *function);
// else:    void __assert(const char *msg, const char *file, unsigned line)

static const char *getCAssertFunctionName() {
  const auto &triple = *global.params.targetTriple;
  if (triple.isOSDarwin()) {
    return "__assert_rtn";
  } else if (triple.isWindowsMSVCEnvironment()) {
    return "_assert";
  } else if (triple.isOSSolaris()) {
    return "__assert_c99";
  } else if (triple.isMusl()) {
    return "__assert_fail";
  }
  return "__assert";
}
void DtoCAssert(Module *M, const Loc &loc, LLValue *msg) {
  const auto &triple = *global.params.targetTriple;
  const auto file =
      DtoConstCString(loc.filename ? loc.filename : M->srcfile.toChars());
  const auto line = DtoConstUint(loc.linnum);
  const auto fn = getCAssertFunction(loc, gIR->module);

  llvm::SmallVector<LLValue *, 4> args;
  if (triple.isOSDarwin()) {
    const auto irFunc = gIR->func();
    const auto funcName =
        irFunc && irFunc->decl ? irFunc->decl->toPrettyChars() : "";
    args.push_back(DtoConstCString(funcName));
    args.push_back(file);
    args.push_back(line);
    args.push_back(msg);
  } else if (triple.isOSSolaris() || triple.isMusl() ||
             global.params.isUClibcEnvironment) {
    const auto irFunc = gIR->func();
    const auto funcName =
        (irFunc && irFunc->decl) ? irFunc->decl->toPrettyChars() : "";
    args.push_back(msg);
    args.push_back(file);
    args.push_back(line);
    args.push_back(DtoConstCString(funcName));
  } else if (triple.getEnvironment() == llvm::Triple::Android) {
    args.push_back(file);
    args.push_back(line);
    args.push_back(msg);
  } else {
    args.push_back(msg);
    args.push_back(file);
    args.push_back(line);
  }

  gIR->CreateCallOrInvoke(fn, args);

  gIR->ir->CreateUnreachable();
}

ところが、ここで一点問題がありました。

LDCにおけるglibc/muslでないLinux環境のassert関数とnewlibのassert関数では引数の対応順序が異なっています。

// LDC: https://github.com/ldc-developers/ldc/blob/9976807e0e1acf24edfb4ba35d28c19a3f0227f2/gen/runtime.cpp#L367
//     void __assert(const char *msg, const char *file, unsigned line)
// newlib: https://github.com/bminor/newlib/blob/80cda9bbda04a1e9e3bee5eadf99061ed69ca5fb/newlib/libc/stdlib/assert.c#L68-L70
//     void __assert(const char *file, int line, const char *failedexpr)

そのため、単純にこのまま呼んでしまうと一見不可解なエラーメッセージが表示されてしまいます。

$ west build -t run
(...)
Hello from 'LDC'!
assertion "" failed: file "array index out of bounds", line 40362

本来であればコンパイラ側に手を入れたいところですが、現状LLVMはtarget tripleでnewlibを対応していません。

LDCも現時点ではハンドリングをあきらめています: https://github.com/ldc-developers/ldc/blob/master/driver/main.cpp#L625-L628 (追記: ふつうにできたのでパッチ書いた https://github.com/ldc-developers/ldc/pull/4351)

そこで今回はtargetをMuslにして、 __assert_fail という関数を定義してnewlibの __assert_func 関数をラップして引数の対応が期待通りになるような修正を行っています。

ここでmuslを使う理由ですが、 __assert の多重定義を避けることが可能なもののうち、最も環境的に影響が小さいと考えられるものとして選択しました。
コンパイラはmuslターゲットの場合は __assert_fail を呼び出そうとしますが、newlibは当然そんなものは持っていないので自前で定義したほうを呼び出します。

// Wrapping newlib's __assert_func.
//
// LDC: https://github.com/ldc-developers/ldc/blob/9976807e0e1acf24edfb4ba35d28c19a3f0227f2/gen/runtime.cpp#L367
//     void __assert(const char *msg, const char *file, unsigned line)
// newlib: https://github.com/bminor/newlib/blob/80cda9bbda04a1e9e3bee5eadf99061ed69ca5fb/newlib/libc/stdlib/assert.c#L68-L70
//     void __assert(const char *file, int line, const char *failedexpr)
private extern (C) @cold noreturn __assert_fail(const(char)* msg, const(char)* file, int line, const(char)* func)
{
    __assert_func(file, line, func, msg);
}

将来的にはLLVMのnewlib対応を待ってコンパイラ側に手を入れることができるとよいなあと思っています。

参考資料

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1