Posted at

RustでMac内でLinux用のコードのマクロを展開したコードを取得する方法

Rustその2 Advent Calendar 2018の14日目の記事です。

mioの実装を読むという記事を書いていたのですが、記述量が多くなって来たので、Mac内でLinux用のコードのマクロを展開したコードを取得する方法という内容だけ別記事に切り出しました。

まず、mioはRustの低レイヤーな非同期ioライブラリなのですが、内部はLinuxならepoll、Mac(BSD)ならkqueueを使うという実装になっています。(Windowsは...)

epoll.rsdlsym!(fn epoll_create1(c_int) -> c_int);というマクロを使っている所があります。

impl Selector {

pub fn new() -> io::Result<Selector> {
let epfd = unsafe {
// Emulate `epoll_create` by using `epoll_create1` if it's available
// and otherwise falling back to `epoll_create` followed by a call to
// set the CLOEXEC flag.
dlsym!(fn epoll_create1(c_int) -> c_int);

match epoll_create1.get() {
Some(epoll_create1_fn) => {
cvt(epoll_create1_fn(libc::EPOLL_CLOEXEC))?
}
None => {
let fd = cvt(libc::epoll_create(1024))?;
drop(set_cloexec(fd));
fd
}
}
};
...
}
...
}

マクロはdlsym.rs内に下記のものがあります。

macro_rules! dlsym {

(fn $name:ident($($t:ty),*) -> $ret:ty) => (
#[allow(bad_style)]
static $name: ::sys::unix::dlsym::DlSym<unsafe extern fn($($t),*) -> $ret> =
::sys::unix::dlsym::DlSym {
name: concat!(stringify!($name), "\0"),
addr: ::std::sync::atomic::ATOMIC_USIZE_INIT,
_marker: ::std::marker::PhantomData,
};
)
}

<unsafe extern fn($($t),*) -> $ret>は文法的に

<unsafe extern "C" fn ... -> ...> となると思ったんだけど、マクロ内には "C" が無いんだよな...と思い、これを展開したものを見たかったので調べたらcargo expandでマクロを展開出来ることを知りました。

ただ、上のロジックはMac(BSD)では通らないので、 単純にcargo expand しただけではみることが出来ません...

なので、 Linux用にコンパイルする必要があります。幸いRustはクロスコンパイル出来きます。

いける!と思ったのですが、rustupのデフォルトをnightlyにしないと上手く動かず、半日潰しました...

# rustupを事前にインストールしdefaultをnightlyにする

# クロスコンパイル用のmuslをインストール
$ brew install FiloSottile/musl-cross/musl-cross
# x86_64-unknown-linux-muslのコンパイル環境をセットアップ
$ rustup target add x86_64-unknown-linux-musl
# linux用のマクロを展開したソースコードを取得
$ cargo expand --release --target=x86_64-unknown-linux-musl

展開した結果が下記になります。

impl Selector {

pub fn new() -> io::Result<Selector> {
let epfd = unsafe {
#[allow(bad_style)]
static epoll_create1: ::sys::unix::dlsym::DlSym<unsafe extern "C" fn(c_int) -> c_int> =
::sys::unix::dlsym::DlSym{
name:"epoll_create1\u{0}",
addr: ::std::sync::atomic::ATOMIC_USIZE_INIT,
_marker: ::std::marker::PhantomData,
};

match epoll_create1.get() {
Some(epoll_create1_fn) => {
cvt(epoll_create1_fn(libc::EPOLL_CLOEXEC))?
}
None => {
let fd = cvt(libc::epoll_create(1024))?;
drop(set_cloexec(fd));
fd
}
}
};
...
}
...
}

展開してみると<unsafe extern "C" fn(c_int) -> c_int>にちゃんとなっています...:thinking:

何でだろう...

そもそも、FFIを調べてみるとextern fnでも良いけど、expand時に補ってくれているだけかも...?