@ledsun blog

無味の味は佳境に入らざればすなわち知れず

rustのWASI用のThreadのsleep関数を読む

https://github.com/rust-lang/rust/blob/e9271846294c4ee5bd7706df68180320c0b5ff20/library/std/src/sys/wasi/thread.rs#L137 *1

   pub fn sleep(dur: Duration) {
        let nanos = dur.as_nanos();
        assert!(nanos <= u64::MAX as u128);

        const USERDATA: wasi::Userdata = 0x0123_45678;

        let clock = wasi::SubscriptionClock {
            id: wasi::CLOCKID_MONOTONIC,
            timeout: nanos as u64,
            precision: 0,
            flags: 0,
        };

        let in_ = wasi::Subscription {
            userdata: USERDATA,
            u: wasi::SubscriptionU { tag: 0, u: wasi::SubscriptionUU { clock } },
        };
        unsafe {
            let mut event: wasi::Event = mem::zeroed();
            let res = wasi::poll_oneoff(&in_, &mut event, 1);
            match (res, event) {
                (
                    Ok(1),
                    wasi::Event {
                        userdata: USERDATA,
                        error: wasi::ERRNO_SUCCESS,
                        type_: wasi::EVENTTYPE_CLOCK,
                        ..
                    },
                ) => {}
                _ => panic!("thread::sleep(): unexpected result of poll_oneoff"),
            }
        }
    }

このファイルが何かはあまりよくわかっていません。 src/sysディレクトリにunixwindowsがあります。 rustでクロスコンパイルするときに、ターゲットOSに合わせて埋め込まれる実装だろうと思っています。

なんとなく読み方がわかったのでメモしておきます。

poll_oneoff in wasi - Rustpoll_oneoff関数のリファレンスがあります。

pub unsafe fn poll_oneoff(
    in_: *const Subscription, 
    out: *mut Event, 
    nsubscriptions: Size
) -> Result<Size, Errno>

実際の呼び出しが

wasi::poll_oneoff(&in_, &mut event, 1);

です。引数を3つ受け取っています。

  1. _in
  2. event
  3. 1

_inは少し上で定義している構造体です。

let in_ = wasi::Subscription {
  userdata: USERDATA,
  u: wasi::SubscriptionU { tag: 0, u: wasi::SubscriptionUU { clock } },
};

APIリファレンスとSubscriptionという型名が一致しています。 SubscriptionにはSubscriptionUと言う構造体が入っていて、さらにその中にSubscriptionUU構造体があります。 その中にclockが入ります。 clock

let clock = wasi::SubscriptionClock {
  id: wasi::CLOCKID_MONOTONIC,
  timeout: nanos as u64,
  precision: 0,
  flags: 0,
};

です。 要するにこの関数はpoll_oneoffを呼ぶのに必要なデータを作って、poll_oneoffを呼び出しています。 CLOCKID_MONOTONICというのは

CLOCK_REALTIMEとCLOCK_MONOTONIC #Linux - Qiita

CLOCK_MONOTONIC 時刻はかならず単調増加する システムの時刻変更の影響を受けるが、大きく変化することはないし、時間が戻ったりもしない

経過時間をみる指定のようです。 timeout: nanos as u64タイムアウトまでのナノ秒を指定しているようです。

poll_oneoff関数の戻り値の処理は

match (res, event) {
    (
        Ok(1),
        wasi::Event {
            userdata: USERDATA,
            error: wasi::ERRNO_SUCCESS,
            type_: wasi::EVENTTYPE_CLOCK,
            ..
        },
    ) => {}
    _ => panic!("thread::sleep(): unexpected result of poll_oneoff"),
}

パターンマッチで成功か失敗を判定して、失敗したら例外をあげているようです。

おそらくこれと似たような関数を実装して、Kernel#sleepを置き換えるgemを作れば、ruby.wasmでKernel#sleepが使えるようになるはずです。 そうそうruby.wasmはごく最近、gemをwasmバイナリにpackできるようになりました*2。 さて、RubyのRust拡張ってどうやって作ればいいのでしょうか?

*1:前回の記事の後により新しいバージョンを発見しました。

*2:https://github.com/ruby/ruby.wasm/pull/358