Closed7

ImportC

nonanonnononanonno

基本

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 は動いた。

nonanonnononanonno

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>
nonanonnononanonno

エラーの読み方

前述の通り、例えば、単に 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;
nonanonnononanonno

DuB で使う

source ディレクトリに Cコードを入れただけではリンクエラーになる。dmd app.d とだけやった状態なのだろう。とりあえず、dub.json を以下のようにすると読み込まれる。

	"sourceFiles": [
		"source/*"
	]

良い感じにワイルドカードを使えば良いだろう。

nonanonnononanonno

forward reference エラー

以下のようなエラーが出ることがある。

struct `hoge` no size because of forward reference
Segmentation fault (core dumped)

細かい話はよくわらかないが、https://github.com/dlang/dmd/pull/13183 で修正されていて master ブランチのdmdを使ったら大丈夫だったので次期バージョンでは大丈夫なはず。

nonanonnononanonno

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できない。

このスクラップは2021/10/24にクローズされました