À l'heure actuelle, format_args!
utilise, par exemple ArgumentV1::new(&runtime_data, Debug::fmt)
(pour {:?}
), à l'exécution, en utilisant jusqu'à deux pointeurs par argument au lieu d'un seul ( &runtime_data
) .
Avec allow_internal_unsafe
et #44240, nous pouvons placer les (par exemple Debug::fmt
) fn
pointeurs dans (rvalue-promoted) 'static
data, l'obstacle restant est de savoir comment pour déduire le type des données d'exécution.
C'est-à-dire que Debug::fmt
est vraiment <_ as Debug>::fmt
et que _
est maintenant déduit à cause de la signature de ArgumentV1::new
qui les a tapés ensemble. S'ils sont séparés, nous avons besoin de quelque chose de nouveau.
Je propose d'utiliser le motif HList
( struct HCons<H, T>(H, T); struct HNil;
- donc pour 3 éléments, de types A
, B
et C
vous auriez HCons<A, HCons<B, HCons<C, HNil>>>
), avec #[repr(C)]
, ce qui lui donnerait une disposition déterministe qui correspond à celle d'un tableau, c'est-à-dire ces deux :
&'static HCons<fn(&A), HCons<fn(&B), HCons<fn(&C), HNil>>>
&'static [unsafe fn(*const Opaque); 3]
ont la même représentation, et cette dernière peut être dédimensionnée en tranche. Cette transformation de HList
en tableau (puis slice) peut être effectuée sur un HCons
sécurisé et promu par rvalue, ce qui est une condition nécessaire pour déplacer les pointeurs fn
en données 'static
.
Pour l'inférence, nous pouvons simplement insérer des appels de fonction pour faire correspondre les types, par exemple pour déduire B
nous pourrions faire fmt::unify_fn_with_data((list.1).0, &b)
, ce qui ferait B
en typeof b
.
Il pourrait en fait être plus simple d'avoir une interface "builder" complètement sûre, qui combine les HList
de formateurs avec un HList
de références d'exécution, unifiant les types, mais je suis un peu inquiet à propos de temps de compilation en raison de tous les caractéristiques de répartition - dans tous les cas, l'impact doit être mesuré.
réclamation @rustbot
Pour l'inférence, nous pouvons simplement insérer des appels de fonction pour faire correspondre les types, par exemple pour déduire
B
nous pourrions fairefmt::unify_fn_with_data((list.1).0, &b)
, ce qui feraitB
entypeof b
.
Je ne sais pas trop à quoi je pensais, ça devrait être beaucoup plus facile que ça !
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]) },
}
}
}
c'est-à-dire que nous construisons deux HList
s "en parallèle", l'un avec des métadonnées entièrement constantes, et l'autre avec les références aux données d'exécution, puis toute l'inférence de type peut provenir du where
clause sur fmt::Arguments::new
, avec zéro codegen cruft !
EDIT : @m-ou-se a dû me rappeler pourquoi je suis parti avec l'astuce d'inférence explicite en premier lieu : arguments d'accès aléatoire :déçu:
(peut-être qu'avec suffisamment d'utilisation de génériques const ab , nous pourrions avoir D: IndexHList<i, Output = T>
mais c'est beaucoup d'efforts)
J'ai une nouvelle implémentation de fmt::Arguments
qui n'a que deux pointeurs en taille en utilisant une nouvelle forme de 'métadonnées statiques qui contient à la fois les morceaux de chaîne et toutes les options de formatage (le cas échéant). (Il tient donc maintenant dans une paire de registres, ce qui est vraiment bien.) Il ne nécessite également qu'un seul pointeur sur la pile par argument au lieu de deux, comme @eddyb l'a apparemment déjà suggéré il y a trois ans dans ce numéro. ^^ ( J'ai complètement raté ce problème jusqu'à ce que
Il ne reste plus qu'à mettre format_args!()
jour ArgumentV1
), en tant que pointeur de fonction devrait maintenant aller dans les « métadonnées statiques ». La suggestion dans ce numéro semble être un bon moyen de le faire. J'essaierai de le mettre en œuvre bientôt. J'attends avec impatience le perf run :)
Commentaire le plus utile
Je ne sais pas trop à quoi je pensais, ça devrait être beaucoup plus facile que ça !
c'est-à-dire que nous construisons deux
HList
s "en parallèle", l'un avec des métadonnées entièrement constantes, et l'autre avec les références aux données d'exécution, puis toute l'inférence de type peut provenir duwhere
clause surfmt::Arguments::new
, avec zéro codegen cruft !EDIT : @m-ou-se a dû me rappeler pourquoi je suis parti avec l'astuce d'inférence explicite en premier lieu : arguments d'accès aléatoire :déçu:
(peut-être qu'avec suffisamment d'utilisation de génériques const
ab, nous pourrions avoirD: IndexHList<i, Output = T>
mais c'est beaucoup d'efforts)