Rust: #[repr(C)] HListを使用して、format_args!の静的データ内の型消去されたfmtfnポインターを推測します。

作成日 2017年09月05日  ·  3コメント  ·  ソース: rust-lang/rust

現在、 format_args!は、実行時にArgumentV1::new(&runtime_data, Debug::fmt){:?} )などを使用し、実行時に引数ごとに1だけではなく&runtime_data )。 。

allow_internal_unsafeと#44240を使用すると、(たとえばDebug::fmtfnポインターを(右辺値でプロモートされた) 'staticデータに配置できます。残りのハードルは、ランタイムデータのタイプを推測します。
つまり、 Debug::fmtは実際には<_ as Debug>::fmtあり、 ArgumentV1::newの署名がそれらを一緒に入力しているため、 _は現在推測されています。 それらが分離している場合は、何か新しいものが必要です。

HListパターンを使用することを提案します( struct HCons<H, T>(H, T); struct HNil; -したがって、タイプAB 、およびC 3つの要素の場合HCons<A, HCons<B, HCons<C, HNil>>> )、 #[repr(C)]を使用すると、配列のレイアウトと一致する決定論的なレイアウト、つまり次の2つが得られます。

  • &'static HCons<fn(&A), HCons<fn(&B), HCons<fn(&C), HNil>>>
  • &'static [unsafe fn(*const Opaque); 3]

同じ表現を持ち、後者はスライスにサイズ変更することができます。 HListから配列(そしてスライス)へのこの変換は、安全で右辺値でプロモートされたHConsで実行できます。これは、 fnポインターを移動するために必要な要件です。 'staticデータにすべて。

推論のために、型を一致させるためにいくつかの関数呼び出しを挿入するだけです。たとえば、 Bを推論するには、 fmt::unify_fn_with_data((list.1).0, &b)を実行できます。これにより、 Btypeof b

HListのフォーマッターとHListのランタイム参照を組み合わせて型を統一する、完全に安全な「ビルダー」インターフェースを使用する方が実際には簡単かもしれませんが、少し心配です。すべてのトレイトディスパッチによるコンパイル時間-いずれの場合も、影響を測定する必要があります。


A-fmt C-enhancement I-heavy T-libs

最も参考になるコメント

推論のために、我々は単に推論するなど、種類を一致させるためにいくつかの関数呼び出しを挿入することができますB私たちは何ができるfmt::unify_fn_with_data((list.1).0, &b)になるだろうこれ、 Btypeof b

私がそこで何を考えていたのかわからない、それはそれよりはるかに簡単なはずです!

struct ArgMetadata<T: ?Sized> {
    // Only `unsafe` because of the later cast we do from `T` to `Opaque`.
    fmt: unsafe fn(&T, &mut Formatter<'_>) -> Result,
    // ... flags, constant string fragments, etc.
}

// TODO: maybe name this something else to emphasize repr(C)?
#[repr(C)]
struct HCons<T, Rest>(T, Rest);

// This would have to be in a "sealed module" to make it impossible to implement on more types.
trait MetadataFor<D> {
    const LEN: usize;
}
impl MetadataFor<()> for () {
    const LEN: usize = 0;
}
impl<'a, T: ?Sized, D, M> MetadataFor<HCons<&'a T, D>> for HCons<ArgMetadata<T>, M>
    where M: MetadataFor<D>
{
    const LEN: usize = M::LEN;
}

impl<'a> Arguments<'a> {
    fn new<M, D>(meta: &'a M, data: &'a D) -> Self
        where M: MetadataFor<D>
    {
        Self {
            meta: unsafe { &*(meta as *const _ as *const [ArgMetadata<Opaque>; M::LEN]) },
            data: unsafe { &*(data as *const _ as *const [&Opaque; M::LEN]) },
        }
    }
}

つまり、2つのHList 「並列」に構築します。1つは完全に一定のメタデータを使用し、もう1つはランタイムデータへの参照を使用します。その後、すべての型推論はwhereから取得できます。 fmt::Arguments::new句、codegen cruftはゼロです!

編集:@ m-ou-seは、なぜ私が最初に明示的な推論のトリックを採用したのかを私に思い出させなければなりませんでした:ランダムアクセス引数:失望:
(多分十分なのconstジェネリックABの使用を我々は持っている可能性がD: IndexHList<i, Output = T>それは多くの努力です)

全てのコメント3件

@rustbotクレーム

推論のために、我々は単に推論するなど、種類を一致させるためにいくつかの関数呼び出しを挿入することができますB私たちは何ができるfmt::unify_fn_with_data((list.1).0, &b)になるだろうこれ、 Btypeof b

私がそこで何を考えていたのかわからない、それはそれよりはるかに簡単なはずです!

struct ArgMetadata<T: ?Sized> {
    // Only `unsafe` because of the later cast we do from `T` to `Opaque`.
    fmt: unsafe fn(&T, &mut Formatter<'_>) -> Result,
    // ... flags, constant string fragments, etc.
}

// TODO: maybe name this something else to emphasize repr(C)?
#[repr(C)]
struct HCons<T, Rest>(T, Rest);

// This would have to be in a "sealed module" to make it impossible to implement on more types.
trait MetadataFor<D> {
    const LEN: usize;
}
impl MetadataFor<()> for () {
    const LEN: usize = 0;
}
impl<'a, T: ?Sized, D, M> MetadataFor<HCons<&'a T, D>> for HCons<ArgMetadata<T>, M>
    where M: MetadataFor<D>
{
    const LEN: usize = M::LEN;
}

impl<'a> Arguments<'a> {
    fn new<M, D>(meta: &'a M, data: &'a D) -> Self
        where M: MetadataFor<D>
    {
        Self {
            meta: unsafe { &*(meta as *const _ as *const [ArgMetadata<Opaque>; M::LEN]) },
            data: unsafe { &*(data as *const _ as *const [&Opaque; M::LEN]) },
        }
    }
}

つまり、2つのHList 「並列」に構築します。1つは完全に一定のメタデータを使用し、もう1つはランタイムデータへの参照を使用します。その後、すべての型推論はwhereから取得できます。 fmt::Arguments::new句、codegen cruftはゼロです!

編集:@ m-ou-seは、なぜ私が最初に明示的な推論のトリックを採用したのかを私に思い出させなければなりませんでした:ランダムアクセス引数:失望:
(多分十分なのconstジェネリックABの使用を我々は持っている可能性がD: IndexHList<i, Output = T>それは多くの努力です)

fmt::Arguments新しい実装があります。これは、文字列部分とフォーマットオプション(存在する場合)の両方を含む新しい形式の静的メタデータを使用することにより、サイズが2ポインターのみです。 (したがって、レジスタペアに収まるようになりました。これは非常に便利です。)また、 @ eddybが3年前にこの号ですでに提案したように、引数ごとにスタックに必要なポインタは2つではなく1つだけです。 ^^( @ eddybが昨日指摘するまで、この問題を完全に見逃していました。^^ ')

まだ残っているのは、 format_args!()を更新して、代わりにこの新しい型を生成することです。これにより、オブジェクトポインターと関数ポインター(現在ArgumentV1一緒にあります)を関数ポインターとして分割する問題が発生します。代わりに '静的メタデータに入れる必要があります。 この号の提案は、それを行うための良い方法のように見えます。 すぐにそれを実装しようとします。 パフォーマンスの実行を楽しみにしています:)

このページは役に立ちましたか?
0 / 5 - 0 評価