ImportC
資料
- 公式ドキュメント : https://dlang.org/spec/importc.html
- 紹介(日本語) : 2.098.0
基本
Cコードをビルド
以下のようなCコードを dmd でコンパイルできる。
int printf(const char *, ...); // instead of #include<stdio.h>
int main(void) {
printf("Hello, world!\n");
return 0;
}
// dmd hello.c
rdmd hello.c
は動かなかった。
C関数のDでの利用
以下のようにCの関数をDから呼び出すことができる。
// lib.c
int add(int a, int b) { return a + b; }
// app.d
import lib;
import std.stdio;
void main() {
add(1, 2).writeln;
}
ビルド方法
dmd app.d lib.c
The module name assigned to the ImportC file is the filename stripped of its path and extension.
とあるので、ディレクトリ名は無視されるらしい。
ちなみに dmd app.d
とした場合、リンクエラーになる。rdmd app.d lib.c
は動いた。
C言語の標準ライブラリを使ってみる
まず、2.098.0 の時点でプリプロセッサに対応していないため、多くの既存のCコードについて、dmd に入れる前にプリプロセッサで処理する必要がある。gccの場合、以下のように処理する。(他
gcc -E file.c > file.i
なお、*.h
のファイルをdmd は弾くので、マクロがなくてもヘッダの場合 *.i
に変換する必要がある。
こことか見ると、既存のCライブラリを使うのにやらなければならないことがわかる。
Gnu 拡張などの対応
ISO/IEC 9899:2011 をベースにしているので各コンパイラの拡張に対応していなかったりする。そのあたりを諸々置換なりする必要がある。 2.098.0 のリリースノートに書かれている範囲では以下のもの。
#define __attribute __attribute__
#define __asm asm
#define __asm__ asm
#define __const const
#define __const__ const
#define __inline inline
#define __inline__ inline
#define __extension__
#include <stdlib.h>
しかし、これだけでは動かない。ここ に書いてあるように、 __restrict
を削除する必要がある。(タイミング 的にドキュメントから漏れたのかな)
stdio.h
についてはこれで十分だが、 例として使われていた stdlib.h
では以下のようなエラーが出て不十分だった。
usr/include/x86_64-linux-gnu/bits/byteswap.h(47): Error: undefined identifier `__builtin_bswap32`
/usr/include/x86_64-linux-gnu/bits/byteswap.h(111): Error: undefined identifier `__builtin_bswap64`
これはここ で報告されている問題で、コンパイラのビルドイン関数を用意する必要がある。結果として以下のようになる。(今後のバージョンで変わり得る)
#define __attribute __attribute__
#define __asm asm
#define __asm__ asm
#define __const const
#define __const__ const
#define __inline inline
#define __inline__ inline
#define __extension__
#define __restrict restrict
#define __builtin_bswap16(x) \
((u_int16_t)((((x) >> 8) & 0xff) | (((x)&0xff) << 8)))
#define __builtin_bswap32(x) \
((((x)&0xff000000u) >> 24) | (((x)&0x00ff0000u) >> 8) | \
(((x)&0x0000ff00u) << 8) | (((x)&0x000000ffu) << 24))
#define __builtin_bswap64(x) \
((((x)&0xff00000000000000ull) >> 56) | (((x)&0x00ff000000000000ull) >> 40) | \
(((x)&0x0000ff0000000000ull) >> 24) | (((x)&0x000000ff00000000ull) >> 8) | \
(((x)&0x00000000ff000000ull) << 8) | (((x)&0x0000000000ff0000ull) << 24) | \
(((x)&0x000000000000ff00ull) << 40) | (((x)&0x00000000000000ffull) << 56))
#include <stdlib.h>
エラーの読み方
前述の通り、例えば、単に gcc -E /usr/include/stdlib.h > stdlib.i
して作った stdlib.i
はビルドできず、以下のようなエラーが出る。
/usr/include/stdlib.h(80): Error: illegal combination of type specifiers
/usr/include/stdlib.h(112): Error: illegal combination of type specifiers
/usr/include/stdlib.h(112): Error: illegal combination of type specifiers
/usr/include/stdlib.h(112): Error: illegal combination of type specifiers
/usr/include/stdlib.h(117): Error: found `__nptr` when expecting `,`
/usr/include/stdlib.h(118): Error: found `__endptr` when expecting `,`
...
どの辺が illegal combination
なのかこれだけではよくわからないが、/usr/include/stdlib.h(80)
を見に行くと以下のようになっている。
__extension__ typedef struct
{
long long int quot; /* Quotient. */
long long int rem; /* Remainder. */
} lldiv_t; // ← ここが L80
この場合、__extension__
が悪い。L117 は以下のようになっている。
extern double strtod (const char *__restrict __nptr, // ← ここが L117
char **__restrict __endptr)
この場合、__restrict
が悪い。
こういう風にC11 標準と乖離している部分を見つける作業になりそうではある。C11のキーワードを覚えるか、ここ を見て使えるかどうか頑張って解釈するかとかになるだろうか。#define __restrict restrict
により置換している場合はその結果どうなっているかを解釈しながらエラー箇所を読む必要がありそうだが、stdio.i
を見ると以下のようになっているので、一応どうなっているかわかりそう。(これは gcc -E
がどういう表現を出すかという話)
# 58 "/usr/include/stdlib.h" // ←ここの次が L58 という意味
typedef struct
{
int quot;
int rem;
} div_t;
typedef struct
{
long int quot;
long int rem;
} ldiv_t;
__extension__ typedef struct
{
long long int quot;
long long int rem;
} lldiv_t;
DuB で使う
source
ディレクトリに Cコードを入れただけではリンクエラーになる。dmd app.d
とだけやった状態なのだろう。とりあえず、dub.json
を以下のようにすると読み込まれる。
"sourceFiles": [
"source/*"
]
良い感じにワイルドカードを使えば良いだろう。
forward reference エラー
以下のようなエラーが出ることがある。
struct `hoge` no size because of forward reference
Segmentation fault (core dumped)
細かい話はよくわらかないが、https://github.com/dlang/dmd/pull/13183 で修正されていて master ブランチのdmdを使ったら大丈夫だったので次期バージョンでは大丈夫なはず。
CTFE で使う。
D言語のCTFE
CTFE をあまり覚えていなかったので軽く復習。以下のようなDコードがある。
int add(int a, int b) { return a + b; }
int sub() {
int a = add(2, 3);
return a;
}
これを dmd -c dlib.d
とコンパイルして objdump -D dlib.o
を見ると以下のようになる。
0000000000000000 <_D4dlib3addFiiZi>:
0: 55 push %rbp
1: 48 8b ec mov %rsp,%rbp
4: 48 83 ec 10 sub $0x10,%rsp
8: 89 7d f8 mov %edi,-0x8(%rbp)
b: 89 f0 mov %esi,%eax
d: 03 45 f8 add -0x8(%rbp),%eax
10: c9 leaveq
11: c3 retq
...
(中略)
...
Disassembly of section .text._D4dlib3subFZi:
0000000000000000 <_D4dlib3subFZi>:
0: 55 push %rbp
1: 48 8b ec mov %rsp,%rbp
4: be 02 00 00 00 mov $0x2,%esi
9: bf 03 00 00 00 mov $0x3,%edi
e: e8 00 00 00 00 callq 13 <_D4dlib3subFZi+0x13>
13: 5d pop %rbp
14: c3 retq
15: 00 00 add %al,(%rax)
callq 13 <_D4dlib3subFZi+0x13>
がある通り、関数が呼ばれる。sub
を次のようにすると、これがadd
がコンパイル時に呼ばれ、計算結果がバイナリに書かれる。
int sub() {
enum a = add(2, 3);
return a;
}
Disassembly of section .text._D4dlib3subFZi:
0000000000000000 <_D4dlib3subFZi>:
0: b8 05 00 00 00 mov $0x5,%eax
5: c3 retq
ImportCで使う
まず、CTFE でないケース。以下のようなCコードとDコードがある。
// clib.c
int add(int a, int b) { return a + b; }
// app.d
import clib;
int sub() {
int a = add(2, 3);
return a;
}
これを dmd -c app.d
とすると、コンパイルのみされる。アセンブリは以下のようになる。
Disassembly of section .text._D3app3subFZi:
0000000000000000 <_D3app3subFZi>:
0: 55 push %rbp
1: 48 8b ec mov %rsp,%rbp
4: be 03 00 00 00 mov $0x3,%esi
9: bf 02 00 00 00 mov $0x2,%edi
e: e8 00 00 00 00 callq 13 <_D3app3subFZi+0x13>
13: 5d pop %rbp
14: c3 retq
15: 00 00 add %al,(%rax)
ぶっちゃけマングリングがよくわからないが、関数呼び出しをしている。これを以下のように変更する。
import clib;
int sub() {
enum a = add(2, 3);
return a;
}
コンパイルコマンドは dmd -c app.d
のままで良い。アセンブリは以下のようになる。
Disassembly of section .text._D3app3subFZi:
0000000000000000 <_D3app3subFZi>:
0: b8 05 00 00 00 mov $0x5,%eax
5: c3 retq
コンパイル時に計算されている。また、このケースでは実行時に add
が不要になるので clib.c をリンクする必要がなくなる。
ヘッダーがある場合
以下のように、*.c
がなく、オブジェクトファイルとヘッダー(をプリプロセスかけたもの)だけが存在しているとする。
app.d
clib.i
clib.o
この時、CTFE なしであれば dmd app.d clib.o
で実行バイナリを得ることができる。しかし、CTFEあり、つまり enum a = add(2,3)
などとした場合は以下のようなエラーが出る。
$ dmd app.d clib.o
app.d(4): Error: `add` cannot be interpreted at compile time, because it has no available source code
app.d(4): compile time context created here
app.d(5): Error: `return` expression expected
C言語関数もD言語と同じようにCTFE可能だが、これはソースコードがある場合のみで、オブジェクト+ヘッダでの提供の場合はCTFEできない。