Rust: Verwenden Sie #[repr(C)] HLists, um typgelöschte fmt fn-Zeiger in den statischen Daten von format_args! abzuleiten.

Erstellt am 5. Sept. 2017  ·  3Kommentare  ·  Quelle: rust-lang/rust

Im Moment verwendet format_args! zB ArgumentV1::new(&runtime_data, Debug::fmt) (für {:?} ) zur Laufzeit, wobei zur Laufzeit zwei Zeiger pro Argument verwendet werden, anstatt nur einen ( &runtime_data ) .

Mit allow_internal_unsafe und #44240 können wir die (zB Debug::fmt ) fn Zeiger in (rvalue-promoted) 'static Daten platzieren, die verbleibende Hürde ist, wie um auf den Typ der Laufzeitdaten zu schließen.
Das heißt, Debug::fmt ist in Wirklichkeit <_ as Debug>::fmt und _ wird gerade abgeleitet, weil ArgumentV1::new sie zusammen tippt. Wenn sie getrennt sind, brauchen wir etwas Neues.

Ich schlage vor, das Muster HList ( struct HCons<H, T>(H, T); struct HNil; - also für 3 Elemente der Typen A , B und C HCons<A, HCons<B, HCons<C, HNil>>> ), mit #[repr(C)] , was ihm ein deterministisches Layout geben würde, das dem eines Arrays entspricht, d. h. diese beiden:

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

haben die gleiche Darstellung, und letztere kann in eine Scheibe skaliert werden. Diese Transformation von HList zu Array (und dann Slice) kann auf einem sicheren, rvalue-beförderten HCons , was eine notwendige Voraussetzung für das Verschieben der fn Zeiger ist überhaupt in 'static Daten.

Zur Schlussfolgerung können wir einfach einige Funktionsaufrufe einfügen, um die Typen abzugleichen, z. B. um B abzuleiten, könnten wir fmt::unify_fn_with_data((list.1).0, &b) tun, was aus B typeof b .

Es könnte tatsächlich einfacher sein, eine völlig sichere "Builder" -Schnittstelle zu haben, die die HList von Formatierern mit HList von Laufzeitreferenzen kombiniert und die Typen vereinheitlicht, aber ich mache mir ein bisschen Sorgen Compile-Zeiten aufgrund des Trait-Dispatchings - auf jeden Fall sollte die Wirkung gemessen werden.


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

Hilfreichster Kommentar

Zur Schlussfolgerung können wir einfach einige Funktionsaufrufe einfügen, um die Typen abzugleichen, zB um B abzuleiten, könnten wir fmt::unify_fn_with_data((list.1).0, &b) tun, was aus B typeof b .

Ich bin mir nicht sicher, was ich mir da gedacht habe, es sollte viel einfacher sein!

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]) },
        }
    }
}

dh wir bauen zwei HList s "parallel", eines mit vollständig konstanten Metadaten und das andere mit den Referenzen auf die Laufzeitdaten, und dann kann die gesamte Typinferenz aus dem where Klausel über fmt::Arguments::new , mit Null codegen cruft!

BEARBEITEN : @m-ou-se musste mich daran erinnern, warum ich überhaupt mit dem expliziten Inferenz-Trick gegangen bin: Random-Access-Argumente: enttäuscht:
(vielleicht könnten wir mit genügend const generischem ab D: IndexHList<i, Output = T> aber das ist viel Aufwand)

Alle 3 Kommentare

@rustbot behauptet

Zur Schlussfolgerung können wir einfach einige Funktionsaufrufe einfügen, um die Typen abzugleichen, zB um B abzuleiten, könnten wir fmt::unify_fn_with_data((list.1).0, &b) tun, was aus B typeof b .

Ich bin mir nicht sicher, was ich mir da gedacht habe, es sollte viel einfacher sein!

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]) },
        }
    }
}

dh wir bauen zwei HList s "parallel", eines mit vollständig konstanten Metadaten und das andere mit den Referenzen auf die Laufzeitdaten, und dann kann die gesamte Typinferenz aus dem where Klausel über fmt::Arguments::new , mit Null codegen cruft!

BEARBEITEN : @m-ou-se musste mich daran erinnern, warum ich überhaupt mit dem expliziten Inferenz-Trick gegangen bin: Random-Access-Argumente: enttäuscht:
(vielleicht könnten wir mit genügend const generischem ab D: IndexHList<i, Output = T> aber das ist viel Aufwand)

Ich habe eine neue Implementierung von fmt::Arguments , die nur zwei Zeiger groß ist, indem ich eine neue Form von "statischen Metadaten" verwende, die sowohl die Zeichenfolgenteile als auch alle Formatierungsoptionen (falls vorhanden) enthalten. (So ​​passt es jetzt in ein Registerpaar, was wirklich nett ist.) Es braucht auch nur noch einen Zeiger auf dem Stack pro Argument statt zwei, wie @eddyb anscheinend schon vor drei Jahren in dieser Ausgabe vorgeschlagen hat. ^^ (Diese Ausgabe wurde komplett übersehen, bis @eddyb gestern darauf hingewiesen hat. ^^')

Was noch übrig bleibt, ist die Aktualisierung von format_args!() , um stattdessen diesen neuen Typ zu erzeugen, was zu dem Problem führt, den Objektzeiger und den Funktionszeiger (die derzeit zusammen in ArgumentV1 ) als Funktionszeiger aufzuteilen sollte jetzt stattdessen in die statischen Metadaten gehen. Der Vorschlag in dieser Ausgabe scheint eine gute Möglichkeit zu sein, dies zu tun. Werde versuchen das demnächst umzusetzen. Freue mich auf den Perflauf :)

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen