Rust: Use # [repr (C)] HList's para inferir ponteiros fmt fn apagados por tipo nos dados estáticos de format_args !.

Criado em 5 set. 2017  ·  3Comentários  ·  Fonte: rust-lang/rust

No momento format_args! usa, por exemplo, ArgumentV1::new(&runtime_data, Debug::fmt) (para {:?} ), em tempo de execução, usando até dois ponteiros por argumento em tempo de execução em vez de apenas um ( &runtime_data ) .

Com allow_internal_unsafe e # 44240, podemos colocar os (por exemplo, Debug::fmt ) fn ponteiros em dados (promovidos por rvalue) 'static , o obstáculo restante é como para inferir o tipo dos dados de tempo de execução.
Ou seja, Debug::fmt é realmente <_ as Debug>::fmt e que _ está agora inferido por causa da assinatura de ArgumentV1::new digitando-os juntos. Se eles estiverem separados, precisamos de algo novo.

Proponho usar o padrão HList ( struct HCons<H, T>(H, T); struct HNil; - então, para 3 elementos, dos tipos A , B e C você teria HCons<A, HCons<B, HCons<C, HNil>>> ), com #[repr(C)] , o que daria a ele um layout determinístico que corresponde ao de uma matriz, ou seja, estes dois:

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

têm a mesma representação e a última pode ser desdimensionada em uma fatia. Esta transformação de HList para array (e então fatia) pode ser realizada em cima de um HCons seguro promovido por rvalue, que é um requisito necessário para mover os ponteiros fn em 'static dados em tudo.

Para inferência, podemos simplesmente inserir algumas chamadas de função para combinar os tipos, por exemplo, para inferir B , poderíamos fazer fmt::unify_fn_with_data((list.1).0, &b) , o que tornaria B em typeof b .

Na verdade, pode ser mais simples ter uma interface de "construtor" completamente segura, que combina HList de formatadores com HList de referências de tempo de execução, unificando os tipos, mas estou um pouco preocupado com tempos de compilação devido a todo o despacho do traço - em qualquer caso, o impacto deve ser medido.


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

Comentários muito úteis

Para inferência, podemos simplesmente inserir algumas chamadas de função para combinar os tipos, por exemplo, para inferir B , poderíamos fazer fmt::unify_fn_with_data((list.1).0, &b) , o que tornaria B em typeof b .

Não tenho certeza do que estava pensando lá, deveria ser muito mais fácil do que isso!

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

ou seja, construímos dois HList s "em paralelo", um com metadados inteiramente constantes e o outro com as referências aos dados de tempo de execução, e então todas as inferências de tipo podem vir de where cláusula em fmt::Arguments::new , com zero codegen cruft!

EDIT : @ m-ou-se teve que me lembrar por que eu usei o truque de inferência explícita em primeiro lugar: argumentos de acesso aleatório: decepcionado:
(talvez com o uso de ab generics const suficiente, poderíamos ter D: IndexHList<i, Output = T> mas isso é um grande esforço)

Todos 3 comentários

@rustbot reivindicação

Para inferência, podemos simplesmente inserir algumas chamadas de função para combinar os tipos, por exemplo, para inferir B , poderíamos fazer fmt::unify_fn_with_data((list.1).0, &b) , o que tornaria B em typeof b .

Não tenho certeza do que estava pensando lá, deveria ser muito mais fácil do que isso!

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

ou seja, construímos dois HList s "em paralelo", um com metadados inteiramente constantes e o outro com as referências aos dados de tempo de execução, e então todas as inferências de tipo podem vir de where cláusula em fmt::Arguments::new , com zero codegen cruft!

EDIT : @ m-ou-se teve que me lembrar por que eu usei o truque de inferência explícita em primeiro lugar: argumentos de acesso aleatório: decepcionado:
(talvez com o uso de ab generics const suficiente, poderíamos ter D: IndexHList<i, Output = T> mas isso é um grande esforço)

Eu tenho uma nova implementação de fmt::Arguments que tem apenas dois indicadores de tamanho, usando uma nova forma de 'metadados estáticos que contém as partes da string e quaisquer opções de formatação (se houver). (Portanto, agora ele se encaixa em um par de registradores, o que é muito bom.) Também requer apenas um ponteiro na pilha por argumento em vez de dois, como @eddyb aparentemente já sugeriu três anos atrás nesta edição. ^^ ( Perdi completamente este problema até que

O que ainda resta é atualizar format_args!() para produzir este novo tipo em vez disso, o que resultará no problema de dividir o ponteiro do objeto e o ponteiro da função (que estão atualmente juntos em ArgumentV1 ), como o ponteiro da função agora deve ir para os 'metadados estáticos. A sugestão nesta edição parece uma boa maneira de fazer isso. Tentarei implementar isso em breve. Ansioso pelo desempenho do desempenho :)

Esta página foi útil?
0 / 5 - 0 avaliações