Rust: Problème de suivi pour `asm` (assemblage en ligne)

Créé le 9 nov. 2015  ·  111Commentaires  ·  Source: rust-lang/rust

Ce problème suit la stabilisation de l'assemblage en ligne. La fonctionnalité actuelle n'est pas passée par le processus RFC et devra probablement le faire avant la stabilisation.

A-inline-assembly B-unstable C-tracking-issue T-lang requires-nightly

Commentaire le plus utile

Je tiens à souligner que la syntaxe asm en ligne de LLVM est différente de celle utilisée par clang/gcc. Les différences incluent :

  • LLVM utilise $0 au lieu de %0 .
  • LLVM ne prend pas en charge les opérandes asm nommés %[name] .
  • LLVM supporte différents types de contraintes de registre : par exemple "{eax}" au lieu de "a" sur x86.
  • LLVM prend en charge les contraintes de registre explicites ( "{r11}" ). En C, vous devez plutôt utiliser les variables asm register pour lier une valeur à un registre ( register asm("r11") int x ).
  • Les contraintes LLVM "m" et "=m" sont fondamentalement brisées. Clang les traduit en contraintes de mémoire indirectes "*m" et "=*m" et passe l'adresse de la variable à LLVM au lieu de la variable elle-même.
  • etc...

Clang convertira l'asm en ligne du format gcc au format LLVM avant de le transmettre à LLVM. Il effectue également une validation des contraintes : par exemple, il s'assure que les opérandes "i" sont des constantes de compilation,


À la lumière de cela, je pense que nous devrions implémenter la même traduction et la même validation que clang et prendre en charge la syntaxe asm en ligne gcc appropriée au lieu de l'étrange LLVM.

Tous les 111 commentaires

Y aura-t-il des difficultés à garantir la compatibilité descendante de l'assemblage en ligne dans du code stable ?

@main-- a un excellent commentaire sur https://github.com/rust-lang/rfcs/pull/1471#issuecomment -173982852 que je reproduis ici pour la postérité :

Avec tous les bogues ouverts et les instabilités entourant asm!() (il y en a beaucoup ), je ne pense vraiment pas qu'il soit prêt pour la stabilisation - même si j'aimerais avoir un asm en ligne stable dans Rust.

Nous devrions également discuter si l'asm!() d'aujourd'hui est vraiment la meilleure solution ou si quelque chose du genre RFC #129 ou même D serait mieux. Un point important à considérer ici est que asm() ne prend pas en charge le même ensemble de contraintes que gcc. Par conséquent, nous pouvons soit :

  • Tenez-vous en au comportement de LLVM et écrivez des documents pour cela (car je n'en ai pas trouvé). Sympa car il évite la complexité en rouille. Mauvais car cela va dérouter les programmeurs venant de C/C++ et parce que certaines contraintes peuvent être difficiles à émuler dans le code Rust.
  • Émulez gcc et créez simplement un lien vers leurs docs : Bien car de nombreux programmeurs le savent déjà et il y a beaucoup d'exemples que l'on peut simplement copier-coller avec de petites modifications. Mauvais car c'est une extension non triviale du compilateur.
  • Faire autre chose (comme le fait D) : beaucoup de travail qui peut ou non porter ses fruits. Si c'est bien fait, cela pourrait être largement supérieur au style gcc en termes d'ergonomie tout en s'intégrant peut-être mieux avec le langage et le compilateur qu'un simple blob opaque (beaucoup de main ici car je ne suis pas assez familier avec les internes du compilateur pour évaluer cela) .

Enfin, une autre chose à considérer est #1201 qui dans sa conception actuelle (je pense) dépend assez fortement de l'asm en ligne - ou de l'asm en ligne bien fait, d'ailleurs.

Personnellement, je pense qu'il serait préférable de faire ce que Microsoft a fait dans MSVC x64 : définir un ensemble (presque) complet de fonctions intrinsèques, pour chaque instruction asm, et faire "inline asm" exclusivement via ces intrinsèques. Sinon, il est très difficile d'optimiser le code entourant l'asm en ligne, ce qui est ironique car de nombreuses utilisations de l'asm en ligne sont destinées à être des optimisations de performances.

L'un des avantages de l'approche intrinsèque est qu'elle n'a pas besoin d'être un tout ou rien. Vous pouvez d'abord définir les éléments intrinsèques les plus nécessaires et construire l'ensemble de manière incrémentielle. Par exemple, pour la crypto, avoir _addcarry_u64 , _addcarry_u32 . Notez que le travail pour faire l'intrinsèque semble avoir déjà été fait de manière assez approfondie : https://github.com/huonw/llvmint.

De plus, les intrinsèques seraient une bonne idée d'ajouter même s'il était finalement décidé de prendre en charge l'asm en ligne, car ils sont beaucoup plus pratiques à utiliser (d'après mon expérience de les utiliser en C et C++), donc en commençant par les intrinsèques et en voyant jusqu'où nous arrivons semble être une chose à risque zéro de se tromper.

Les intrinsèques sont bonnes, mais asm! peut être utilisé pour plus que simplement insérer des instructions.
Par exemple, voyez comment je génère des notes ELF dans ma caisse probe .
https://github.com/cuviper/rust-libprobe/blob/master/src/platform/systemtap.rs

Je m'attends à ce que ce genre de piratage soit rare, mais je pense que c'est toujours une chose utile à soutenir.

@briansmith

L'asm en ligne est également utile pour le code qui veut faire sa propre allocation de registre/pile (par exemple les fonctions nues).

@briansmith ouais, ce sont d'excellentes raisons d'utiliser des intrinsèques dans la mesure du possible. Mais c'est bien d'avoir un assemblage en ligne comme trappe d'évacuation ultime.

@briansmith Notez que asm!() est _en quelque sorte_ un sur-ensemble d'intrinsèques car vous pouvez construire le dernier en utilisant le premier. (L'argument commun contre ce raisonnement est que le compilateur pourrait théoriquement optimiser les intrinsèques _through_, par exemple les sortir des boucles, exécuter CSE dessus, etc. Cependant, c'est un contrepoint assez fort que quiconque écrivant asm à des fins d'_optimisation_ ferait un meilleur travail à cela que le compilateur de toute façon.) Voir aussi https://github.com/rust-lang/rust/issues/29722#issuecomment -207628164 et https://github.com/rust-lang/rust/issues/29722# issuecomment -207823543 pour les cas où l'asm en ligne fonctionne mais pas les intrinsèques.

D'un autre côté, les intrinsèques dépendent de manière critique d'un "compilateur suffisamment intelligent" pour atteindre _au moins_ les performances que l'on obtiendrait avec une implémentation asm roulée à la main. Mes connaissances à ce sujet sont obsolètes, mais à moins qu'il n'y ait eu des progrès significatifs, les implémentations basées sur les intrinsèques sont encore sensiblement inférieures dans de nombreux - sinon la plupart - des cas. Bien sûr, ils sont beaucoup plus pratiques à utiliser, mais je dirais que les programmeurs ne se soucient pas vraiment de _ça_ quand ils sont prêts à descendre dans le monde des instructions CPU spécifiques.

Maintenant, une autre considération intéressante est que les intrinsèques pourraient être couplés avec du code de secours sur des architectures où ils ne sont pas pris en charge. Cela vous donne le meilleur des deux mondes : votre code est toujours portable - il peut simplement utiliser certaines opérations d'accélération matérielle là où le matériel les prend en charge. Bien sûr, cela ne rapporte vraiment que pour des instructions très courantes ou si l'application a une architecture cible évidente. Maintenant, la raison pour laquelle je mentionne cela est que même si l'on pourrait affirmer que cela peut même potentiellement être _indésirable_ avec les intrinsèques _fournis par le compilateur_ (car vous vous soucierez probablement de savoir si vous obtenez réellement les versions accélérées et que la complexité du compilateur n'est jamais bonne) je Je dirais que c'est une autre histoire si les intrinsèques sont fournis par une _library_ (et uniquement implémentés à l'aide d'asm en ligne). En fait, c'est la vue d'ensemble que je préférerais même si je peux me voir utiliser des intrinsèques plus que l'asm en ligne.

(Je considère que les éléments intrinsèques de la RFC #1199 sont quelque peu orthogonaux à cette discussion car ils existent principalement pour faire fonctionner SIMD.)

@briansmith

Sinon, il est très difficile d'optimiser le code entourant l'asm en ligne, ce qui est ironique car de nombreuses utilisations de l'asm en ligne sont destinées à être des optimisations de performances.

Je ne suis pas sûr de ce que vous voulez dire ici. Il est vrai que le compilateur ne peut pas décomposer l'asm en ses opérations individuelles pour effectuer une réduction de la force ou des optimisations de judas dessus. Mais dans le modèle GCC, au moins, le compilateur peut allouer les registres qu'il utilise, le copier lorsqu'il réplique les chemins de code, le supprimer s'il n'est jamais utilisé, etc. Si l'asm n'est pas volatile, GCC a suffisamment d'informations pour le traiter comme n'importe quelle autre opération opaque comme, disons, fsin . Toute la motivation de la conception étrange est de faire de l'asm en ligne quelque chose avec lequel l'optimiseur peut jouer.

Mais je ne l'ai pas beaucoup utilisé, surtout pas récemment. Et je n'ai aucune expérience avec le rendu de la fonctionnalité par LLVM. Alors je me demande ce qui a changé, ou ce que j'ai mal compris tout ce temps.

Nous avons discuté de ce problème lors de la récente semaine de travail, car l' enquête de @japaric sur l'écosystème no_std a la macro asm! comme l'une des fonctionnalités les plus couramment utilisées. Malheureusement, nous n'avons pas vu de solution simple pour stabiliser cette fonctionnalité, mais je voulais noter les notes que nous avions pour m'assurer de ne pas oublier tout cela.

  • Premièrement, nous n'avons pas actuellement une grande spécification de la syntaxe acceptée dans la macro asm! . À l'heure actuelle, cela finit généralement par être "regardez LLVM" qui dit "regardez clang" qui dit "regardez gcc" qui n'a pas de bons documents. En fin de compte, cela se termine généralement par "allez lire l'exemple de quelqu'un d'autre et adaptez-le" ou "lisez le code source de LLVM". Pour la stabilisation, un strict minimum est d'avoir une spécification de la syntaxe et de la documentation.

  • À l'heure actuelle, à notre connaissance, il n'y a aucune garantie de stabilité de la part de LLVM. La macro asm! est un lien direct avec ce que LLVM fait actuellement. Cela signifie-t-il que nous pouvons toujours mettre à jour librement LLVM quand nous le souhaitons ? LLVM garantit-il qu'il ne brisera jamais cette syntaxe ? Un moyen d'atténuer ce problème serait d'avoir notre propre couche qui compile selon la syntaxe de LLVM. De cette façon, nous pouvons modifier LLVM quand nous le souhaitons et si l'implémentation de l'assemblage en ligne dans LLVM change, nous pouvons simplement mettre à jour notre traduction avec la syntaxe de LLVM. Si asm! doit devenir stable, nous avons essentiellement besoin d' un mécanisme garantissant la stabilité dans Rust.

  • À l'heure actuelle, il y a pas mal de bugs liés à l'assemblage en ligne. La balise A-inline-assembly est un bon point de départ, et elle est actuellement jonchée d'ICE, d'erreurs de segmentation dans LLVM, etc. Dans l'ensemble, cette fonctionnalité, telle qu'elle est implémentée aujourd'hui, ne semble pas à la hauteur des garanties de qualité que les autres attendent d'un fonctionnalité dans Rust.

  • La stabilisation de l'assemblage en ligne peut rendre très difficile la mise en œuvre d'un backend alternatif. Par exemple, les backends tels que miri ou cranelift peuvent prendre beaucoup de temps pour atteindre la parité des fonctionnalités avec le backend LLVM, selon l'implémentation. Cela peut signifier qu'il y a une plus petite tranche de ce qui peut être fait ici, mais c'est quelque chose d'important à garder à l'esprit lorsque l'on envisage de stabiliser l'assemblage en ligne.


Malgré les problèmes énumérés ci-dessus, nous voulions être sûrs d'avoir au moins une certaine capacité à faire avancer ce problème ! À cette fin, nous avons réfléchi à quelques stratégies sur la façon dont nous pouvons pousser l'assemblage en ligne vers la stabilisation. La principale voie à suivre serait d'enquêter sur ce que fait le clang. Vraisemblablement, clang et C ont une syntaxe d'assemblage en ligne effectivement stable et il est probable que nous puissions simplement refléter tout ce que clang fait (en particulier par rapport à LLVM). Ce serait formidable de comprendre plus en profondeur comment clang implémente l'assemblage en ligne. Est-ce que clang a sa propre couche de traduction ? Valide-t-il des paramètres d'entrée ? (etc)

Une autre possibilité d'aller de l'avant est de voir s'il existe un assembleur que nous pouvons simplement sortir de l'étagère d'ailleurs qui est déjà stable. Certaines idées ici étaient nasm ou l'assembleur plan9. L'utilisation de l'assembleur de LLVM pose les mêmes problèmes de garantie de stabilité que l'instruction d'assemblage en ligne dans l'IR. (c'est une possibilité, mais nous avons besoin d'une garantie de stabilité avant de l'utiliser)

Je tiens à souligner que la syntaxe asm en ligne de LLVM est différente de celle utilisée par clang/gcc. Les différences incluent :

  • LLVM utilise $0 au lieu de %0 .
  • LLVM ne prend pas en charge les opérandes asm nommés %[name] .
  • LLVM supporte différents types de contraintes de registre : par exemple "{eax}" au lieu de "a" sur x86.
  • LLVM prend en charge les contraintes de registre explicites ( "{r11}" ). En C, vous devez plutôt utiliser les variables asm register pour lier une valeur à un registre ( register asm("r11") int x ).
  • Les contraintes LLVM "m" et "=m" sont fondamentalement brisées. Clang les traduit en contraintes de mémoire indirectes "*m" et "=*m" et passe l'adresse de la variable à LLVM au lieu de la variable elle-même.
  • etc...

Clang convertira l'asm en ligne du format gcc au format LLVM avant de le transmettre à LLVM. Il effectue également une validation des contraintes : par exemple, il s'assure que les opérandes "i" sont des constantes de compilation,


À la lumière de cela, je pense que nous devrions implémenter la même traduction et la même validation que clang et prendre en charge la syntaxe asm en ligne gcc appropriée au lieu de l'étrange LLVM.

Il y a une excellente vidéo sur les résumés avec D, MSVC, gcc, LLVM et Rust avec des diapositives en ligne

En tant que personne qui aimerait pouvoir utiliser l'ASM en ligne dans Rust stable, et avec plus d'expérience que je ne le souhaite en essayant d'accéder à certaines des API LLVM MC de Rust, voici quelques réflexions :

  • L'ASM en ligne est essentiellement un copier-coller d'un extrait de code dans le fichier .s de sortie pour l'assemblage, après une substitution de chaîne. Il a également des pièces jointes de registres d'entrée et de sortie ainsi que des registres écrasés. Il est peu probable que ce cadre de base change vraiment dans LLVM (bien que certains détails puissent varier légèrement), et je soupçonne qu'il s'agit d'une représentation assez indépendante du cadre.

  • Construire une traduction d'une spécification face à Rust vers un format IR face à LLVM n'est pas difficile. Et cela pourrait être conseillé - la syntaxe rouillée {} pour le formatage n'interfère pas avec le langage assembleur, contrairement % notation $ LLVM et GCCs % .

  • LLVM fait un travail étonnamment mauvais dans la pratique pour identifier les registres qui sont écrasés, en particulier dans les instructions non générées par LLVM. Cela signifie qu'il est à peu près nécessaire pour l'utilisateur de spécifier manuellement quels registres sont écrasés.

  • Essayer d'analyser l'assemblage vous-même est susceptible d'être un cauchemar. L'API LLVM-C n'expose pas la logique MCAsmParser, et ces classes sont assez ennuyeuses pour travailler avec bindgen (je l'ai fait).

  • Pour la portabilité vers d'autres backends, tant que vous gardez l'assembly en ligne principalement au niveau de "copier-coller cette chaîne avec un peu d'allocation de registre et de substitution de chaîne", cela ne devrait pas trop inhiber les backends. La suppression de la constante entière et des contraintes de mémoire et de ne conserver que les contraintes de banque de registre ne devrait pas poser de problèmes.

J'ai eu un peu de jeu pour voir ce qui peut être fait avec les macros procédurales. J'en ai écrit un qui convertit l'assemblage en ligne de style GCC en style rouille https://github.com/parched/gcc-asm-rs. J'ai également commencé à travailler sur un qui utilise un DSL où l'utilisateur n'a pas à comprendre les contraintes et elles sont toutes gérées automatiquement.

Je suis donc arrivé à la conclusion que je pense que la rouille devrait simplement stabiliser les blocs de construction nus, puis la communauté peut parcourir l'arbre avec des macros pour trouver les meilleures solutions. Fondamentalement, stabilisez simplement le style llvm que nous avons maintenant avec uniquement les contraintes "r" et "i" et peut-être "m", et sans écraser. D'autres contraintes et obstacles peuvent être stabilisés plus tard avec leurs propres choses de type mini rfc.

Personnellement, je commence à avoir l'impression que la stabilisation de cette fonctionnalité est le genre de tâche colossale qui ne sera jamais accomplie à moins que quelqu'un n'engage un entrepreneur expert à temps plein pour poursuivre cette tâche pendant une année entière. Je veux croire que la suggestion de @parched de stabiliser asm! coup par coup rendra cela acceptable. J'espère que quelqu'un le ramassera et l'utilisera. Mais si ce n'est pas le cas, alors nous devons arrêter d'essayer d'atteindre la solution satisfaisante qui n'arrivera jamais et atteindre la solution insatisfaisante qui : stabilisera asm! en l'état, les verrues, les ICE, les bugs et tout , avec des avertissements audacieux et brillants dans les documents annonçant le jank et la non-portabilité, et avec l'intention de désapprouver un jour si une mise en œuvre satisfaisante devait jamais miraculeusement descendre, envoyée par Dieu, sur son hôte céleste. IOW, nous devrions faire exactement ce que nous avons fait pour macro_rules! (et bien sûr, tout comme pour macro_rules! , nous pouvons avoir une brève période de pansement frénétique et de fuite à l'épreuve du futur). Je suis triste des ramifications pour les backends alternatifs, mais il est honteux pour un langage système de reléguer l'assemblage en ligne dans un tel vide, et nous ne pouvons pas laisser la possibilité hypothétique de plusieurs backends continuer à entraver l'existence d'un backend réellement utilisable. Je t'en prie, prouve-moi que j'ai tort !

il est honteux pour un langage système de reléguer l'assemblage en ligne dans un tel vide

En tant que point de données, il se trouve que je travaille actuellement sur une caisse qui dépend de gcc dans le seul but d'émettre de l'asm avec Rust stable : https://github.com/main--/unwind- rs/blob/266e0f26b6423f4a2b8a8c72442b319b5c33b658/src/unwind_helper.c


Bien qu'il ait certainement ses avantages, je me méfie un peu de l'approche "stabiliser les blocs de construction et laisser le reste à la proc-macros". Il sous-traite essentiellement le processus de conception, de RFC et de mise en œuvre à quiconque veut faire le travail, potentiellement à personne. Bien sûr, avoir des garanties de stabilité/qualité plus faibles est l'essentiel (le compromis est qu'avoir quelque chose d'imparfait est déjà bien mieux que de n'avoir rien du tout), je le comprends.

Au moins, les blocs de construction devraient être bien conçus - et à mon avis, "expr" : foo : bar : baz ne l'est certainement pas. Je ne me souviens pas avoir réussi la commande du premier coup, je dois toujours la rechercher. "Catégories magiques séparées par des deux-points où vous spécifiez des chaînes constantes avec des caractères magiques qui finissent par faire des choses magiques aux noms de variables que vous écrasez également d'une manière ou d'une autre" est tout simplement mauvais.

Une idée, …

Aujourd'hui, il existe déjà un projet, nommé dynasm, qui peut vous aider à générer du code d'assemblage avec un plugin utilisé pour pré-traiter l'assemblage avec une variante de code x64.

Ce projet ne répond pas au problème de l'assemblage en ligne, mais il peut certainement aider, si rustc fournissait un moyen de mapper des variables sur des registres et acceptait d'insérer un ensemble d'octets dans le code, un tel projet pourrait également être utilisé pour remplir- ces ensembles d'octets.

De cette façon, la seule partie de normalisation nécessaire du point de vue de Rustc, est la capacité d'injecter n'importe quelle séquence d'octets dans le code généré et d'appliquer des allocations de registres spécifiques. Cela supprime tout le choix pour les saveurs de langues spécifiques.

Même sans dynasm, cela peut également être utilisé comme un moyen de créer des macros pour les instructions cpuid / rtdsc, qui seraient simplement traduites dans la séquence brute d'octets.

Je suppose que la question suivante pourrait être si nous voulons ajouter des propriétés/contraintes supplémentaires aux séquences d'octets.

[EDIT : je ne pense pas que tout ce que j'ai dit dans ce commentaire est correct.]

Si nous voulons continuer à utiliser l'assembleur intégré de LLVM (je suppose que c'est plus rapide que de générer un assembleur externe), alors la stabilisation signifie se stabiliser exactement sur les expressions d'assemblage en ligne de LLVM et la prise en charge de l'assembleur intégré - et compenser les modifications apportées à celles-ci, le cas échéant.

Si nous sommes prêts à générer un assembleur externe, alors nous pouvons utiliser n'importe quelle syntaxe que nous voulons, mais nous renonçons alors aux avantages de l'assembleur intégré et sommes exposés aux modifications de l'assembleur externe que nous appelons.

Je pense qu'il serait étrange de se stabiliser sur le format de LLVM alors que même Clang ne le fait pas. Vraisemblablement, il utilise le support de LLVM en interne, mais il présente une interface plus proche de GCC.

Je suis d'accord à 100% pour dire "Rust prend en charge exactement ce que Clang prend en charge" et l'appeler un jour, d'autant plus que la position d'AFAIK Clang est "Clang prend en charge exactement ce que GCC prend en charge". Si jamais nous avons une vraie spécification Rust, nous pouvons adoucir le langage en "l'assemblage en ligne est défini par l'implémentation". La priorité et la normalisation de facto sont des outils puissants. Si nous pouvons réutiliser le propre code de Clang pour traduire la syntaxe GCC en LLVM, tant mieux. Les problèmes de backend alternatifs ne disparaissent pas, mais théoriquement, un frontend Rust vers GCC ne serait pas très contrarié. Moins pour nous de concevoir, moins pour nous de bikeshed sans fin, moins pour nous d'enseigner, moins pour nous d'entretenir.

Si nous stabilisons quelque chose défini en fonction de ce que clang prend en charge, alors nous devrions l'appeler clang_asm! . Le nom asm! doit être réservé à quelque chose qui a été conçu via un processus RFC complet, comme les autres fonctionnalités majeures de Rust. #Garage à vélo

Il y a quelques choses que j'aimerais voir dans l'assemblage en ligne Rust :

  • Le modèle de modèle avec substitutions est moche. Je saute toujours entre le texte d'assemblage et la liste de contraintes. La concision encourage les gens à utiliser des paramètres de position, ce qui aggrave la lisibilité. Les noms symboliques signifient souvent que vous avez le même nom répété trois fois : dans le modèle, en nommant l'opérande, et dans l'expression étant liée à l'opérande. Les diapositives mentionnées dans le commentaire d'Alex montrent que D et MSVC vous permettent simplement de référencer des variables dans le code, ce qui semble beaucoup plus agréable.

  • Les contraintes sont à la fois difficiles à comprendre et (principalement) redondantes avec le code assembleur. Si Rust disposait d'un assembleur intégré avec un modèle suffisamment détaillé des instructions, il pourrait en déduire les contraintes sur les opérandes, supprimant une source d'erreur et de confusion. Si le programmeur a besoin d'un codage spécifique de l'instruction, il devra alors fournir une contrainte explicite, mais cela ne sera généralement pas nécessaire.

Norman Ramsey et Mary Fernández ont écrit des articles sur le New Jersey Machine Code Toolkit à l'époque où ils avaient d'excellentes idées pour décrire les paires de langages assembleur/machine de manière compacte. Ils s'attaquent aux encodages d'instructions iA-32 (Pentium Pro-ère) ; il n'est pas du tout limité aux ISA RISC soignées.

Je voudrais réitérer les conclusions de la dernière semaine de travail :

  • Aujourd'hui, pour autant que nous le sachions, il n'y a pratiquement aucune documentation pour cette fonctionnalité. Cela inclut les internes de LLVM et tout.
  • Nous n'avons, à notre connaissance, aucune garantie de stabilité de la part de LLVM. Pour autant que nous sachions, la mise en œuvre de l'assemblage en ligne dans LLVM pourrait changer d'un jour à l'autre.
  • C'est, actuellement, une fonctionnalité très boguée dans rustc. Il regorge (au moment de la compilation) d'erreurs de segmentation, d'ICE et d'erreurs LLVM étranges.
  • Sans spécification, il est presque impossible d' imaginer même un backend alternatif pour cela.

Pour moi, c'est la définition de « si nous stabilisons cela maintenant, nous garantirons de le regretter à l'avenir », et non seulement « le regretter », mais il semble très probable que « provoque de graves problèmes pour mettre en œuvre tout nouveau système ».

Au strict minimum absolu, je crois fermement que la puce (2) ne peut pas être compromise (c'est-à-dire la définition de stable dans "canal stable"). Les autres balles seraient assez tristes car cela érode la qualité attendue du compilateur Rust qui est actuellement assez élevée.

@jcranmer a écrit :

LLVM fait un travail étonnamment mauvais dans la pratique pour identifier les registres qui sont écrasés, en particulier dans les instructions non générées par LLVM. Cela signifie qu'il est à peu près nécessaire pour l'utilisateur de spécifier manuellement quels registres sont écrasés.

Je pense que, dans la pratique, il serait assez difficile d'inférer des listes de tricheurs. Ce n'est pas parce qu'un fragment de langage machine utilise un registre qu'il l'écrase ; peut-être qu'il le sauve et le restaure. Des approches conservatrices pourraient décourager le générateur de code d'utiliser des registres qu'il conviendrait d'utiliser.

@alexcrichton a écrit :

Nous n'avons, à notre connaissance, aucune garantie de stabilité de la part de LLVM. Pour autant que nous sachions, la mise en œuvre de l'assemblage en ligne dans LLVM pourrait changer d'un jour à l'autre.

La documentation LLVM garantit que « les nouvelles versions peuvent ignorer les fonctionnalités des anciennes versions, mais elles ne peuvent pas les compiler de manière erronée ». (en ce qui concerne la compatibilité IR). Cela limite plutôt à quel point ils peuvent changer l'assemblage en ligne, et, comme je l'ai expliqué ci-dessus, il n'y a pas vraiment de remplacement viable au niveau LLVM qui changerait radicalement la sémantique de la situation actuelle (contrairement, disons, aux problèmes en cours autour de poison et undef). Dire que son instabilité potentielle empêche de l'utiliser comme base pour un bloc Rust asm! est donc quelque peu malhonnête. Cela ne veut pas dire qu'il y a d'autres problèmes avec cela (mauvaise documentation, bien que cela se soit amélioré ; manque de contraintes ; mauvais diagnostics ; et bogues dans des scénarios moins courants sont ceux qui me viennent à l'esprit).

Ma plus grande inquiétude en lisant le fil est que nous faisons du parfait l'ennemi du bien. En particulier, je crains que la recherche d'un intermédiaire DSL magique ne prenne quelques années pour essayer de trouver une forme utilisable pour l'asm en ligne, car les gens découvrent que l'intégration des analyseurs asm et les tentatives de les faire fonctionner avec les LLVM causent plus de problèmes dans cas de bord.

LLVM garantit-il vraiment qu'il ne compilera jamais une fonctionnalité dont il n'a jamais spécifié le comportement ? Comment pourraient-ils même décider si un changement était une erreur de compilation ou non ? Je pouvais le voir pour les autres parties de l'IR, mais cela semble être beaucoup à attendre.

Je pense qu'il serait étrange de se stabiliser sur le format de LLVM alors que même Clang ne le fait pas.

Clang ne le fait pas car il vise à être capable de compiler du code qui a été écrit pour GCC. rustc n'a pas cet objectif. Le format GCC n'est pas très ergonomique donc finalement je pense que nous ne le voulons pas, mais je ne sais pas si cela serait mieux pour l'instant. Il y a beaucoup de code (de nuit) utilisant le format Rust actuel qui serait cassé si nous passions au style GCC, donc cela ne vaut probablement la peine de changer que si nous pouvons proposer quelque chose de nettement meilleur.

Au moins, les blocs de construction devraient être bien conçus - et à mon avis, "expr" : foo : bar : baz ne l'est certainement pas.

D'accord. À tout le moins, je préfère le format LLVM brut où les contraintes et les obstacles sont tous dans une seule liste. Actuellement, il y a une redondance devant spécifier le préfixe "=" et le mettre dans la liste de sortie. Je pense également à la façon dont LLVM le traite davantage comme un appel de fonction où les sorties sont le résultat de l'expression, AFAIK, l'implémentation actuelle de asm! est la seule partie de rust qui a des paramètres "out".

LLVM fait un travail étonnamment mauvais dans la pratique pour identifier les registres qui sont écrasés

AFAIK LLVM n'essaie pas de le faire car la principale raison de l'assemblage en ligne est d'inclure du code que LLVM ne comprend pas. Il n'enregistre que l'allocation et la substitution de modèle sans regarder l'assembly réel. (Évidemment, il analyse l'assembly à un moment donné pour générer le code machine, mais je pense que cela se produit plus tard)

Si nous sommes prêts à générer un assembleur externe

Je ne suis pas sûr qu'il puisse y avoir une alternative à l'utilisation de l'assembleur en ligne intégré, car il faudrait parfois que LLVM lui alloue des registres. Pour l'assemblage global cependant, un assembleur externe serait réalisable.

En ce qui concerne les changements de rupture dans l'assembleur en ligne LLVM, nous sommes dans le même bateau que Clang. C'est-à-dire que s'ils apportent des changements, nous devons simplement les contourner lorsqu'ils se produisent.

Si nous stabilisons quelque chose défini en termes de ce que clang prend en charge, alors nous devrions l'appeler clang_asm!. L'asme ! Le nom doit être réservé à quelque chose qui a été conçu via un processus RFC complet, comme les autres fonctionnalités majeures de Rust. #Garage à vélo

Je suis tout à fait pour. +1

Il y a beaucoup de code (de nuit) utilisant le format Rust actuel qui serait cassé si nous passions au style GCC, donc cela ne vaut probablement la peine de changer que si nous pouvons proposer quelque chose de nettement meilleur.

@parched En suivant la suggestion de asm! pourra toujours l'utiliser avec plaisir.

Aujourd'hui, pour autant que nous le sachions, il n'y a pratiquement aucune documentation pour cette fonctionnalité. Cela inclut les internes de LLVM et tout.

Si la syntaxe d'assemblage de GCC n'est vraiment pas spécifiée ou documentée après 30 ans, alors il semble sûr de supposer que soit produire un sous-langage d'assemblage documenté est une tâche qui est soit si difficile qu'elle dépasse la capacité de Rust à accomplir étant donné nos ressources limitées, soit que les gens qui veulent utiliser l'assemblage s'en moquent tout simplement.

Nous n'avons, à notre connaissance, aucune garantie de stabilité de la part de LLVM. Pour autant que nous sachions, la mise en œuvre de l'assemblage en ligne dans LLVM pourrait changer d'un jour à l'autre.

Il semble peu probable que l'implémentation de l'assemblage en ligne par GCC/Clang change un jour, car cela briserait tout le code C écrit depuis les années 90.

Sans spécification, il est presque impossible d'imaginer même un backend alternatif pour cela.

Au risque d'être insensible, la perspective de backends alternatifs est sans objet si Rust en tant que langage ne survit pas en raison de son incapacité embarrassante à passer à l'assemblage. Nightly ne suffit pas, à moins que l'on ne veuille approuver tacitement l'idée que Nightly est Rust, ce qui compromet davantage la garantie de stabilité de Rust que la perspective de changements LLVM.

Les autres balles seraient assez tristes car cela érode la qualité attendue du compilateur Rust qui est actuellement assez élevée.

Je ne mens pas quand je dis que chaque jour je suis reconnaissant de l'attitude des développeurs de Rust et de l'énorme standard de qualité auquel ils se tiennent (en fait, parfois, je souhaite que vous ralentissiez tous pour que vous puissiez maintenir cette qualité sans vous épuiser comme Brian l'a fait). Cependant, parlant en tant que quelqu'un qui était ici lorsque luqmana a ajouté la macro asm! il y a quatre ans , et qui n'a observé aucun progrès depuis lors pour stabiliser cela, et qui est triste que la crypto dans Rust soit toujours impossible et que SIMD dans Rust n'a même pas de solution de contournement alors que l'interface multiplateforme est lentement déterminée, je me sens découragé. Si je parais catégorique ici, c'est parce que je considère cet enjeu comme existentiel pour la survie du projet. Ce n'est peut-être pas une crise en ce moment, mais il faudra du temps pour stabiliser quoi que ce soit, et nous n'avons pas les années qu'il nous faudra pour concevoir et mettre en œuvre un dialecte d'assemblage de classe mondiale à partir de zéro (prouvé par le fait que nous n'avons fait aucun progrès dans ce sens au cours des quatre dernières années). Rust a besoin d'un assemblage en ligne stable dans le courant de 2018. Nous avons besoin d'un art antérieur pour y parvenir. La situation de macro_rules! reconnu que parfois pire c'est mieux. Encore une fois, je supplie quelqu'un de me prouver le contraire.

FWIW et arrivant en retard à la fête, j'aime ce que @florob 's the cologne talk a proposé. Pour ceux qui ne l'ont pas vu, voici l'essentiel :

// Add 5 to variable:
let mut var = 0;
unsafe {
    asm!("add $5, {}", inout(reg) var);
}

// Get L1 cache size
let ebx: i32;
let ecx: i32;
unsafe {
    asm!(r"
        mov $$4, %eax;
        xor %ecx, %ecx;
        cpuid;
        mov %ebx, {};",
        out(reg) ebx, out(ecx) ecx, clobber(eax, ebx, edx)
    );
}
println!("L1 Cache: {}", ((ebx >> 22) + 1)
    * (((ebx >> 12) & 0x3ff) + 1)
    * ((ebx & 0xfff) + 1) * (ecx + 1));

Qu'en est-il de la stratégie suivante : renommer le asm actuel en llvm_asm (plus peut-être quelques changements mineurs) et indiquer que son comportement est un détail d'implémentation de LLVM, donc la garantie de stabilité de Rust ne s'étend pas entièrement à cela ? Le problème des différents backends devrait être plus ou moins résolu avec target_feature fonctionnalité similaire à

J'ai posté une pré-RFC avec une proposition de syntaxe alternative sur le forum interne : https://internals.rust-lang.org/t/pre-rfc-inline-assembly/6443 . Commentaires bienvenus.

Il me semble que le meilleur est définitivement l'ennemi du genre d'accord ici. Je soutiens pleinement le fait de coller une macro gcc_asm! ou clang_asm! ou llvm_asm! (ou tout sous-ensemble approprié de celle-ci) dans stable avec une syntaxe et une sémantique compatibles pour le moment, pendant qu'une meilleure solution est élaborée . Je ne considère pas que supporter une telle chose pour toujours soit une énorme charge de maintenance : les systèmes les plus sophistiqués proposés ci-dessus semblent prendre en charge assez facilement la simple transformation des anciennes macros en saccharine syntaxique pour la nouvelle.

J'ai un programme binaire http://[email protected]/BartMassey/popcount qui nécessite un assemblage en ligne pour l'instruction x86_64 popcntl . Cet assembly en ligne est la seule chose qui garde ce code dans la nuit. Le code a été dérivé d'un programme C vieux de 12 ans.

En ce moment, mon assemblée est conditionnée à

    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]

puis obtient les informations cpuid pour voir si popcnt est présent. Ce serait bien d'avoir quelque chose dans Rust similaire à la récente bibliothèque Google cpu_features https://opensource.googleblog.com/2018/02/cpu-features-library.html dans Rust, mais c'est la vie.

Comme il s'agit avant tout d'un programme de démonstration, j'aimerais conserver l'assemblage en ligne. Pour de vrais programmes, l'intrinsèque count_ones() serait suffisant — sauf que pour qu'il utilise popcntl faut passer "-C target-cpu=native" à Cargo, probablement via RUSTFLAGS (voir le numéro #1137 et plusieurs problèmes liés) puisque distribuer un .cargo/config avec mes sources ne semble pas être une bonne idée, ce qui signifie qu'en ce moment j'ai un Makefile qui appelle Cargo.

En bref, ce serait bien si l'on pouvait utiliser les instructions de popcount sophistiquées d'Intel et d'autres dans des applications réelles, mais cela semble plus difficile que cela ne devrait l'être. Les intrinsèques ne sont pas tout à fait la réponse. Le asm! actuel est une réponse correcte s'il était disponible dans stable. Ce serait formidable d'avoir une meilleure syntaxe et sémantique pour l'assemblage en ligne, mais je n'en ai pas vraiment besoin. Ce serait formidable de pouvoir spécifier target-cpu=native directement dans Cargo.toml , mais cela ne résoudrait pas vraiment mon problème.

Désolé, divagation. Je pensais juste partager pourquoi je me soucie de ça.

@BartMassey Je ne comprends pas, pourquoi avez-vous désespérément besoin de compiler pour popcnt? La seule raison que je peux voir est la performance et l'OMI, vous devez absolument utiliser count_ones() dans ce cas. Ce que vous recherchez n'est pas asm inline mais target_feature (rust-lang/rfcs#2045) afin que vous puissiez dire au compilateur qu'il est autorisé à émettre popcnt.

@BartMassey, vous n'avez même pas besoin d'utiliser l'assemblage en ligne pour cela, utilisez simplement coresimd cfg_feature_enabled!("popcnt") pour demander si le processeur sur lequel votre binaire s'exécute prend en charge l'instruction popcnt (il sera résoudre cela au moment de la compilation si cela est possible).

coresimd fournit également un popcnt intrinsèque qui est garanti d'utiliser l'instruction popcnt .

@gnzlbg

coresimd fournit également un popcnt intrinsèque qui est garanti pour utiliser l'instruction popcnt.

C'est un peu hors sujet, mais cette affirmation n'est pas strictement vraie. _popcnt64 utilise leading_zeros sous le capot, donc si la fonctionnalité popcnt n'est pas activée par l'utilisateur de la caisse et que l'auteur de la caisse oubliera d'utiliser #![cfg(target_feature = "popcnt")] cet intrinsèque obtiendra compilé dans un assemblage inefficace et il n'y a aucune garantie contre cela.

donc si la fonctionnalité popcnt ne sera pas activée par l'utilisateur de caisse

Ceci est incorrect car l'intrinsèque utilise l'attribut #[target_feature(enable = "popcnt")] pour activer la fonctionnalité popcnt pour l'intrinsèque inconditionnellement, indépendamment de ce que l'utilisateur de la caisse active ou désactive. De plus, l'attribut assert_instr(popcnt) garantit que l'intrinsèque se désassemble en popcnt sur toutes les plateformes x86 prises en charge par Rust.

Si l'on utilise Rust sur une plate-forme x86 que Rust ne prend pas en charge actuellement, il appartient à celui qui porte core de s'assurer que ces intrinsèques génèrent popcnt sur cette cible.


EDIT : @newpavlov

ainsi, si la fonctionnalité popcnt n'est pas activée par l'utilisateur de crate et que l'auteur de crate oubliera d'utiliser #![cfg(target_feature = "popcnt")], cet intrinsèque sera compilé dans un assembly inefficace et il n'y a aucune protection contre cela.

Au moins dans l'exemple que vous avez mentionné dans le problème, cela introduit un comportement indéfini dans le programme, et dans ce cas, le compilateur est autorisé à faire n'importe quoi. Un mauvais codegène qui fonctionne est l'un des multiples résultats que l'on peut obtenir.

Tout d'abord, toutes mes excuses pour le déraillement de la discussion. Je voulais juste réitérer mon point principal, qui était "Je soutiens pleinement le collage d'une macro gcc_asm! ou clang_asm! ou llvm_asm! (ou tout sous-ensemble approprié de celle-ci) dans stable avec une syntaxe et une sémantique compatibles pour le moment, pendant qu'une meilleure solution est élaborée. "

Le point de l'assemblage en ligne est qu'il s'agit d'une référence / démo popcount. Je veux une véritable instruction garantie popcntl lorsque cela est possible à la fois comme référence et pour illustrer comment utiliser l'assemblage en ligne. Je veux également garantir que count_ones() utilise une instruction popcount lorsque cela est possible afin que Rustc n'ait pas l'air terrible par rapport à GCC et Clang.

Merci d'avoir signalé target_feature=popcnt . Je vais réfléchir à la façon de l'utiliser ici. Je pense que je veux tester count_ones() quel que soit le processeur pour lequel l'utilisateur compile et qu'il ait ou non une instruction popcount. Je veux juste m'assurer que si le processeur cible a popcount count_ones() utilise.

Les caisses stdsimd / coresimd jolies et devraient probablement être activées pour ces benchmarks. Merci! Pour cette application, je préférerais utiliser le moins possible les fonctionnalités du langage standard (je me sens déjà coupable à propos de lazy_static ). Cependant, ces installations semblent trop belles pour être ignorées, et il semble qu'elles soient en passe de devenir "officielles".

Il y a une idée lancée par @nbp où il pourrait y avoir une implémentation qui va d'une certaine représentation du code aux octets de la machine (pourrait être une caisse proc-macro ou quelque chose ?), puis ces octets sont inclus directement dans l'emplacement particulier dans le code.

Épisser des octets de code arbitraires à des endroits arbitraires au sein d'une fonction semble être un problème beaucoup plus facile à résoudre (bien que la capacité de spécifier les entrées, les sorties et leurs contraintes ainsi que les obus serait toujours nécessaire).

cc @eddyb

@nagisa, c'est un peu plus qu'un simple morceau de code machine, vous devez également faire attention aux registres d'entrée, de sortie et de clobber. Si le morceau ASM dit qu'il veut une certaine variable dans %rax et qu'il va écraser %esi, vous devez vous assurer que le code environnant joue bien. De plus, si le développeur laisse le compilateur allouer les registres, vous souhaiterez probablement optimiser l'allocation pour éviter de renverser et de déplacer des valeurs.

@simias , en effet, vous devrez spécifier comment les variables sont associées à des registres spécifiques et quels registres sont écrasés, mais tout cela est plus petit que la standardisation de n'importe quel langage assembleur ou de n'importe quel langage assembleur LLVM.

La standardisation sur les séquences d'octets est probablement la voie la plus simple en déplaçant la saveur de l'assemblage vers un pilote / proc-macro.

L'un des problèmes d'avoir des octets verbatim au lieu d'un assemblage en ligne approprié est que le compilateur n'aurait aucune option pour renommer alpha les registres, ce à quoi je ne m'attends pas non plus pour les personnes qui écrivent un assemblage en ligne.

Mais comment cela fonctionnerait-il avec l'allocation des registres si je veux laisser le compilateur gérer cela ? Par exemple, en utilisant la syntaxe (atroce) de GCC :

asm ("leal (%1, %1, 4), %0"
     : "=r" (five_times_x)
     : "r" (x));

Dans quelque chose comme ça, je laisse le compilateur allouer les registres, en espérant qu'il me donnera ce qui est le plus pratique et le plus efficace. Par exemple, sur x86 64, si five_time_x est la valeur de retour, le compilateur peut allouer eax et si x est un paramètre de fonction, il est peut-être déjà disponible dans certains registres. Bien sûr, le compilateur ne sait exactement comment il allouera les registres qu'assez tard dans la séquence de compilation (surtout si ce n'est pas aussi trivial que simplement les paramètres de fonction et les valeurs de retour).

Votre solution proposée fonctionnerait-elle avec quelque chose comme ça?

@nbp Je dois dire que je suis un peu confus par cette proposition.
Tout d'abord, la standardisation du langage d'assemblage n'a jamais été quelque chose que nous voulions réaliser avec l'assemblage en ligne. Au moins pour moi, la prémisse était toujours que le langage d'assemblage utilisé par l'assembleur du système serait accepté.
Le problème n'est pas d'analyser/assembler l'assembly, nous pouvons le transmettre facilement à LLVM.
Le problème réside dans le remplissage de l'assemblage modélisé (ou en donnant à LLVM les informations requises pour le faire) et en spécifiant les entrées, les sorties et les obturateurs.
Le dernier problème n'est pas réellement résolu par votre proposition. Il est cependant allégé, car vous ne voudriez/ne pourriez pas prendre en charge les classes de registres (sur lesquelles @simias pose des questions), mais juste des registres concrets.
Au point où les contraintes sont simplifiées à ce point, il est en fait tout aussi facile de prendre en charge un assemblage en ligne "réel". Le premier argument est une chaîne contenant un assembly (non modélisé), les autres arguments sont les contraintes. Ceci est assez facilement mappé aux expressions d'assembleur en ligne de LLVM.
D'un autre côté, l'insertion d'octets bruts n'est pas, à ma connaissance (ou peut le dire d'après le manuel de référence LLVM IR) prise en charge par LLVM. Nous étendrions donc essentiellement LLVM IR et réimplémenterions une fonctionnalité (assemblage du système d'assemblage) déjà présente dans LLVM à l'aide de caisses distinctes.

@nbp

en effet, vous devrez spécifier comment les variables sont associées à des registres spécifiques et quels registres sont écrasés, mais tout cela est plus petit que la standardisation de n'importe quel langage assembleur LLVM.

Alors comment cela se ferait-il ? J'ai une séquence d'octets avec des registres codés en dur qui signifie essentiellement que les registres d'entrée/sortie, les clobbers, etc. sont tous codés en dur à l'intérieur de cette séquence d'octets.

Maintenant, j'injecte ces octets quelque part dans mon binaire rouille. Comment puis-je dire à rustc quels registres sont en entrée/sortie, quels registres ont été écrasés, etc. ? En quoi est-ce un problème plus petit à résoudre que la stabilisation de l'assemblage en ligne ? Il me semble que c'est exactement ce que fait l'assembly en ligne, juste peut-être un peu plus difficile car maintenant il faut spécifier deux fois les entrées/sorties clobbers, dans l'assembly écrit, et de quelque manière que ce soit, nous transmettons ces informations à rustc. De plus, rustc n'aurait pas du mal à valider cela, car pour cela, il devrait être capable d'analyser la séquence d'octets dans l'assemblage, puis de l'inspecter. Qu'est-ce que je rate?

@simias

asm ("leal (%1, %1, 4), %0"
     : "=r" (five_times_x)
     : "r" (x));

Cela ne serait pas possible, car le brut d'octets ne permet pas le renommage alpha des registres, et les registres devraient être appliqués par la séquence de code en amont.

@Florob

Au moins pour moi, la prémisse était toujours que le langage d'assemblage utilisé par l'assembleur du système serait accepté.

D'après ce que j'ai compris, nous ne voulons pas nous fier à l'assembleur système, mais plutôt une faille acceptée dans le cadre de l'asm ! macro. Compter aussi sur asm! étant la syntaxe LLVM serait pénible pour le développement de backend supplémentaires.

@gnzlbg

Alors comment cela se ferait-il ? J'ai une séquence d'octets avec des registres codés en dur qui signifie essentiellement que les registres d'entrée/sortie, les clobbers, etc. sont tous codés en dur à l'intérieur de cette séquence d'octets.

L'idée serait d'avoir une liste d'entrées, de sorties et de registres écrasés, où les entrées seraient un tuple du nom de registre associé à une référence ou copie (mutable), le registre écrasé serait une liste de noms de registre, et la sortie serait une liste de registres de sortie qui formerait un tuple de registres nommés auxquels sont associés des types.

fn swap(a: u32, b: u32) -> (u32, u32) {
  unsafe{
    asm_raw!{
       bytes: [0x91],
       inputs: [(std::asm::eax, a), (std::asm::ecx, b)],
       clobbered: [],
       outputs: (std::asm::eax, std::asm::ecx),
    }
  }
}

Cette séquence de code peut être la sortie d'une macro procédurale du compilateur, qui pourrait ressembler à :

fn swap(a: u32, b: u32) -> (u32, u32) {
  unsafe{
    asm_x64!{
       ; <-- (eax, a), (ecx, b)
       xchg eax, ecx
       ; --> (eax, ecx)
    }
  }
}

Ces séquences, ne pourront pas embarquer directement de symbole ou d'adresses et devront être calculées et données sous forme de registres. Je suis sûr que nous pouvons comprendre comment ajouter la possibilité d'insérer ultérieurement des adresses de symboles dans la séquence d'octets.

L'avantage de cette approche est que seule la liste des registres et des contraintes doit être standardisée, et c'est quelque chose qui serait facilement pris en charge par tout futur backend.

@nbp

D'après ce que j'ai compris, nous ne voulons pas nous fier à l'assembleur système, mais plutôt une faille acceptée dans le cadre de l'asm ! macro. Compter aussi sur asm! étant la syntaxe LLVM serait pénible pour le développement de backend supplémentaires.

Je ne pense pas que ce soit une évaluation précise? À l'exception mineure des deux syntaxes différentes pour l'assembly x86, la syntaxe de l'assembly est largement standard et portable. Le seul problème avec l'assembleur système est peut-être qu'il manque d'instructions plus récentes, mais c'est une situation de niche qui ne vaut pas la peine d'être optimisée.

Le problème réel est la colle dans l'allocation des registres. Mais, en ce qui concerne la chaîne d'assemblage elle-même, cela signifie simplement que quelqu'un doit effectuer des substitutions de chaînes et peut-être une analyse syntaxique - et ce type de substitution devrait être trivialement disponible pour tout backend putatif.

Je suis d'accord que la syntaxe de LLVM (ou de gcc) pour ce truc est de la merde, mais passer aux octets précompilés signifie que toute caisse asm doit maintenant installer un assembleur complet et éventuellement un allocateur de registre complet (ou obliger les programmeurs à allouer des registres à la main), ou tenter pour utiliser l'assembleur système. À ce stade, il ne semble pas que cela ajoute vraiment beaucoup de valeur.

@jcranmer

... mais le passage aux octets précompilés signifie que toute caisse asm doit maintenant installer un assembleur complet et éventuellement un allocateur de registre complet (ou obliger les programmeurs à allouer des registres à la main), ou tenter d'utiliser l'assembleur système

https://github.com/CensoredUsername/dynasm-rs

Cette caisse utilise une macro gérée par un plugin pour assembler le code assembleur et générer des vecteurs de code assembleur brut à concaténer au moment de l'exécution.

@nbp peut-être que mes cas d'utilisation sont particuliers, mais le manque de renommage des registres et de laisser le compilateur allouer des registres pour moi serait un peu une rupture car cela signifie soit que je dois être très chanceux avec mon choix de registres et arriver à " hit right" ou le compilateur devra émettre du code non optimal pour mélanger les registres pour correspondre à mes conventions arbitraires.

Si le blob d'assembly ne s'intègre pas bien avec l'assembly émis par le compilateur environnant, je pourrais aussi bien factoriser le stub ASM dans une méthode externe de style C dans un fichier assembly .s autonome puisque les appels de fonction ont le même type de registre -les contraintes d'allocation. Cela fonctionne déjà aujourd'hui, même si je suppose que l'avoir intégré à rustc pourrait simplifier le système de construction par rapport à un fichier d'assemblage autonome. Je suppose que ce que je dis, c'est que l'OMI, votre proposition ne nous amène pas très loin par rapport à la situation actuelle.

Et si le code ASM appelait des symboles externes qui seraient résolus par l'éditeur de liens ? Vous devez faire circuler ces informations car vous ne pouvez pas les résoudre avant la fin du processus de compilation. Vous devrez y passer la référence à côté de votre tableau d'octets et laisser l'éditeur de liens les résoudre beaucoup plus tard.

@jcranmer

À l'exception mineure des deux syntaxes différentes pour l'assembly x86, la syntaxe de l'assembly est largement standard et portable.

Je ne suis pas sûr de comprendre ce que vous entendez par là, évidemment la syntaxe ASM n'est pas portable à travers les architectures. Et même au sein d'une même architecture, il existe souvent des variations et des options qui modifient la façon dont la langue est assemblée.

Je peux donner MIPS comme exemple, il existe deux indicateurs de configuration importants qui modifient le comportement de l'assembleur : at et reorder . at indique si l'assembleur est autorisé à utiliser implicitement le registre AT (assembleur temporaire) lors de l'assemblage de certaines pseudo-instructions. Le code qui utilise explicitement AT pour stocker des données doit être assemblé avec at ou il se brisera.

reorder définit si le codeur gère manuellement les créneaux de délai de branche ou s'il fait confiance à l'assembleur pour les gérer. Assembler du code avec le mauvais paramètre reorder générera presque certainement un faux code machine. Lorsque vous écrivez un assembly MIPS, vous devez toujours connaître le mode actuel s'il contient une instruction de branchement. Par exemple, il est impossible de connaître la signification de cette liste MIPS si vous ne savez pas si reorder est activé :

    addui   $a0, 4
    jal     some_func
    addui   $a1, $s0, 3

L'assemblage ARM 32 bits a les variantes Thumb/ARM, il est important de savoir quel jeu d'instructions vous ciblez (et vous pouvez modifier à la volée les appels de fonction). Le mélange des deux ensembles doit être fait très soigneusement. Le code ARM charge également généralement de grandes valeurs immédiates en utilisant une charge implicite relative au PC, si vous pré-assemblez votre code, vous devrez faire attention à la façon dont vous passez ces valeurs car elles doivent rester à proximité mais ne sont pas des instructions réelles avec un emplacement bien défini. Je parle de pseudo-instructions comme :

   ldr   r2, =0x89abcdef

MIPS, quant à lui, a tendance à diviser la valeur immédiate en deux valeurs 16 bits et à utiliser un combo lui/ori ou lui/andi. Il est généralement caché derrière les pseudo-instructions li / la mais si vous écrivez du code avec noreorder et que vous ne voulez pas gaspiller le délai, vous devez parfois gérer à la main, ce qui donne un code amusant :

.set noreorder

   /* Display a message using printf */
   lui $a0, %hi(hello)
   jal printf
   ori $a0, %lo(hello)

.data

hello:
.string "Hello, world!\n"

Les constructions %hi et %lo sont un moyen de dire à l'assembly de générer une référence aux hello bits haut et bas du symbole

Certains codes nécessitent des contraintes d'alignement très particulières (courantes lorsque vous traitez du code d'invalidation de cache par exemple, vous devez vous assurer que vous ne prenez pas de scie pour la branche sur laquelle vous êtes assis). Et il y a le problème de la gestion des symboles externes qui ne peut pas être résolu à ce stade du processus de compilation, comme je l'ai mentionné plus tôt.

Je suis sûr que je pourrais proposer des particularités pour un tas d'autres architectures que je connais moins. Pour ces raisons, je ne suis pas sûr d'être très optimiste pour l'approche macro/DSL. Je comprends qu'avoir un littéral de chaîne opaque aléatoire au milieu du code n'est pas très élégant, mais je ne vois pas vraiment ce que l'intégration de la syntaxe ASM complète dans Rust d'une manière ou d'une autre nous donnerait, à part des maux de tête supplémentaires lors de l'ajout du support pour une nouvelle architecture.

Écrire un assembleur est quelque chose qui peut sembler trivial à première vue, mais qui peut s'avérer très délicat si vous voulez prendre en charge toutes les cloches, sifflets et bizarreries de toutes les architectures.

D'un autre côté, avoir un bon moyen de spécifier les bindings et les clobbers serait extrêmement précieux (par rapport à la syntaxe perfectible de gcc).

Salut les gars,

Désolé de vous déranger, je voulais seulement lâcher mes deux centimes, parce que je ne suis qu'un utilisateur, et vraiment très timide/calme, oh, et un nouveau venu, je viens juste d'atterrir à Rust, mais je suis déjà amoureux d'elle.

Mais ce truc d'assemblage est juste fou, je veux dire, c'est une conversation de trois ans, avec un tas d'idées et de plaintes, mais rien qui ne ressemble à un consensus minimum. Trois ans et pas un RFC, ça ressemble un peu à une impasse. Je développe une humble bibliothèque mathématique (qui, espérons-le, se matérialisera dans deux ou trois caisses), et pour moi (et je soupçonne que pour tout autre homme intéressé par l'écriture d'assembly en rouille), la chose la plus importante est de pouvoir réellement fais-le! avec un minimum de garantie que tout ne va pas changer le lendemain (c'est ce que me fait ressentir la chaîne instable, et surtout cette conversation).

Je comprends que tout le monde ici veut la meilleure solution, et peut-être qu'un jour quelqu'un sortira avec celle-ci, mais quant à aujourd'hui, je pense que la macro actuelle est très bien (enfin, peut-être un peu restrictive à certains égards, mais j'espère rien qui ne puisse pas être traités de manière incrémentale). Écrire en assembleur est comme la chose la plus importante dans un langage système, une fonctionnalité très très nécessaire, et bien que je puisse compter sur cpp_build jusqu'à ce que cela soit corrigé, j'ai très peur que si cela prend beaucoup plus de temps, cela deviendra une dépendance éternelle. Je ne sais pas pourquoi, appelez cela une idée irrationnelle, mais je trouve que devoir appeler cpp pour appeler l'assembly est un peu triste, je veux une solution de rouille pure.

FWIW Rust n'est pas si spécial ici, MSVC n'a pas non plus d'asm en ligne pour x86_64. Ils ont cette implémentation vraiment étrange où vous pouvez utiliser des variables comme opérandes, mais cela ne fonctionne que pour x86.

@josevalaad Pourriez-vous nous en dire plus sur l'utilisation que vous faites de l'assemblage en ligne ?

Nous ne le voyons généralement que dans des situations de type système d'exploitation, qui sont généralement bloquées la nuit pour d'autres raisons également, et même alors, ils utilisent à peine asm! , donc stabiliser asm! n'a pas été un une priorité suffisamment élevée pour concevoir et développer quelque chose qui puisse correctement survivre en dehors de LLVM et plaire à tout le monde.

De plus, la plupart des choses peuvent être effectuées à l'aide des éléments intrinsèques de la plate-forme exposée. x86 et x86_64 ont été stabilisés et d'autres plateformes sont en cours. La plupart des gens s'attendent à ce que ceux-ci atteignent 95 à 99 % des objectifs. Vous pouvez voir ma propre caisse jetscii comme exemple d'utilisation de certains des éléments intrinsèques.

Nous venons de fusionner un jemalloc PR qui utilise un assemblage en ligne pour contourner les bogues de génération de code dans LLVM - https://github.com/jemalloc/jemalloc/pull/1303 . Quelqu'un a utilisé l'assemblage en ligne dans ce problème (https://github.com/rust-lang/rust/issues/53232#issue-349262078) pour contourner un bogue de génération de code dans Rust (LLVM) qui s'est produit dans la caisse jetscii. Les deux se sont produits au cours des deux dernières semaines, et dans les deux cas, les utilisateurs ont essayé avec des intrinsèques mais le compilateur a échoué.

Lorsque la génération de code pour un compilateur C s'avère inacceptable, dans le pire des cas, l'utilisateur peut utiliser l'assemblage en ligne et continuer à travailler en C.

Lorsque cela se produit dans Rust stable, nous devons maintenant dire aux gens d'utiliser un langage de programmation différent ou d'attendre un laps de temps indéterminé (souvent de l'ordre des années). C'est pas gentil.

@eddyb Eh bien, j'écris une petite bibliothèque d'algèbre matricielle. À l'intérieur de cette bibliothèque, j'implémente le BLAS, peut-être des routines LAPACK (pas encore là) dans Rust, car je voulais que la bibliothèque soit une pure implémentation de rouille. C'est encore rien de grave, mais de toute façon, je voulais que l'utilisateur puisse opter pour un peu de vitesse asm et de fun, notamment avec l'opération GEMM, qui était autrefois indispensable (le plus utilisé, de toute façon, et si vous suivez l'approche BLIS people c'est tout ce dont vous avez besoin), au moins en x86/x86_64. Et c'est toute l'histoire. Evidemment je peux aussi utiliser le canal nocturne, je voulais juste pousser un peu dans le sens pragmatique de la stabilisation de la fonctionnalité.

@shepmaster Il existe _beaucoup_ de cas d'utilisation pour lesquels les intrinsèques ne suffisent pas. Du haut de ma tête des trucs récents où je me suis dit "pourquoi oh pourquoi Rust n'a-t-il pas d'asm stable?", il n'y a pas d'intrinsèques XACQUIRE/XRELEASE.

Un asm en ligne stable est essentiel et non, les intrinsèques ne suffisent pas.

Mon point initial était d'essayer d'aider quelqu'un à avoir la capacité d'écrire du code plus rapidement. Ils n'ont fait aucune mention de savoir que les intrinsèques étaient même disponibles, et c'est tout ce que j'ai cherché à partager. Le reste était des informations générales.

Je ne défends même pas un point de vue spécifique, alors n'essayez pas de discuter avec moi - je n'ai aucun intérêt dans cette course. Je répète simplement quel est le point de vue actuel tel que je le comprends . Je participe à un projet qui nécessite un assemblage en ligne qui est très peu susceptible d'avoir des éléments intrinsèques dans un avenir proche, donc je suis également intéressé par une certaine quantité d'assemblage en ligne stable, mais l'assemblage nocturne ne me dérange pas indûment, pas plus que l'invocation d'un assembleur.

Oui, il y a des cas qui nécessitent un assemblage pour l'instant et il y a des cas qui en auront toujours besoin, je l'ai dit à l'origine (emphase ajoutée pour plus de clarté):

La plupart des gens s'attendent à ce que [les éléments intrinsèques] atteignent 95 à

À mon avis, si vous voulez voir un assemblage stable, quelqu'un (ou un groupe de personnes) devra obtenir un consensus général de la part de l'équipe Rust sur une direction dans laquelle commencer, puis faire beaucoup d'efforts pour l'actualiser .

C'est encore rien de grave, mais de toute façon, je voulais que l'utilisateur puisse opter pour un peu de vitesse asm et de fun, notamment avec l'opération GEMM, qui était autrefois indispensable (le plus utilisé, de toute façon, et si vous suivez l'approche BLIS people c'est tout ce dont vous avez besoin), au moins en x86/x86_64.

Je ne comprends toujours pas à quelles instructions vous devez accéder et auxquelles vous ne pouvez pas accéder sans assemblage en ligne. Ou s'agit-il simplement d'une séquence spécifique d'instructions arithmétiques ?
Si oui, avez-vous comparé une source Rust équivalente à l'assemblage en ligne ?

quelles instructions vous devez accéder auxquelles vous ne pouvez pas sans assemblage en ligne

Eh bien, lorsque vous parlez d'assemblage en mathématiques, vous parlez essentiellement d'utiliser les registres SIMD et les instructions telles que _mm256_mul_pd, _mm256_permute2f128_pd, etc. et les opérations de vectorisation là où elles se déroulent. Le fait est que vous pouvez adopter différentes approches pour la vectorisation, et généralement c'est un peu d'essais et d'erreurs jusqu'à ce que vous obteniez des performances optimisées pour le processeur que vous ciblez et l'utilisation que vous avez en tête. Donc, généralement au niveau de la bibliothèque, vous devez d'abord interroger le processeur en injectant du code asm pour connaître l'ensemble d'instructions et de registres pris en charge, puis compiler de manière conditionnelle une version spécifique de votre noyau asm mathématique.

Si oui, avez-vous comparé une source Rust équivalente à l'assemblage en ligne ?

Pour le moment, je n'ai pas de test spécifique sous la main, et je suis en vacances, donc je préférerais ne pas trop m'y impliquer, mais oui, si vous me donnez quelques semaines, je peux publier un comparatif des performances. Dans tous les cas, il était auparavant impossible pour le compilateur de produire du code aussi rapidement que possible avec un assembleur réglé manuellement. Ce n'est pas possible en C du moins, même si vous utilisez les techniques de performance classiques comme le déroulement manuel des boucles là où c'est nécessaire, etc., donc j'imagine que cela ne devrait pas être possible dans Rust.

Taylor Cramer m'a suggéré de poster ici. Pardonnez-moi car je n'ai pas lu tous les commentaires pour me rendre compte de l'état actuel de la discussion ; ce n'est qu'une voix de soutien et de déclaration de notre situation.

Pour un projet bare-metal chez Google, nous aimerions voir un certain mouvement sur la stabilisation de l'assembleur en ligne et au niveau du module. L'alternative consiste à utiliser le FFI pour appeler des fonctions écrites en assemblage pur et assemblées séparément et liées entre elles dans un binaire.

Nous pourrions définir des fonctions en assembleur et les appeler via le FFI, en les reliant dans une étape distincte, mais je ne connais aucun projet bare-metal sérieux qui le fasse exclusivement, car il présente des inconvénients en termes de complexité et de performances. Redox utilise 'asm!'. Les suspects habituels de Linux, BSD, macOS, Windows, etc., font tous un usage copieux de l'assembleur en ligne. Le zircon et le seL4 le font. Même Plan 9 a cédé là-dessus il y a quelques années dans la fourchette Harvey.

Pour les choses critiques pour les performances, la surcharge d'appel de fonction peut dominer en fonction de la complexité de la fonction appelée. En termes de complexité, définir des fonctions assembleur séparées juste pour invoquer une seule instruction, lire ou écrire un registre, ou autrement manipuler l'état de la machine qui est habituellement caché à un programmeur de l'espace utilisateur signifie un passe-partout plus fastidieux pour se tromper. Dans tous les cas, nous devrions être plus créatifs dans notre utilisation de Cargo (ou compléter avec un système de construction externe ou un script shell ou quelque chose) pour le faire. Peut-être que build.rs pourrait aider ici, mais l'introduire dans l'éditeur de liens semble plus difficile.

J'aimerais aussi beaucoup qu'il y ait un moyen de sonder les valeurs des constantes symboliques dans le modèle d'assembleur.

nous aimerions voir un certain mouvement sur la stabilisation de l'assembleur en ligne et au niveau du module.

Le dernier pré-RFC (https://internals.rust-lang.org/t/pre-rfc-inline-assembly/6443) a atteint un consensus il y a 6 mois (au moins sur la plupart des questions fondamentales), donc la prochaine étape est de soumettre une RFC qui s'appuie sur cela. Si vous voulez que cela se produise plus rapidement, je vous recommande de contacter @Florob à ce sujet.

Pour ce que ça vaut, j'ai besoin d'un accès direct aux registres FSGS pour obtenir le pointeur vers la structure TEB sous Windows, j'ai également besoin d'un _bittest64 -like intrinsèque pour appliquer bt à un emplacement mémoire arbitraire, ni l'un ni l'autre dont je ne pouvais trouver un moyen de se passer d'assembly en ligne ou d'appels externes.

Le troisième point mentionné ici me concerne, cependant, car LLVM préfère en effet Just Crash si quelque chose ne va pas, ne fournissant aucun message d'erreur.

@MSxDOS

J'ai également besoin d'un intrinsèque de type _bittest64 pour appliquer bt à un emplacement de mémoire arbitraire, ce que je n'ai pas pu trouver de moyen de faire sans assembly en ligne ni appels externes.

Il ne devrait pas être difficile d'ajouter celui-ci à stdsimd , clang les implémente en utilisant un assemblage en ligne (https://github.com/llvm-mirror/clang/blob/c1c07cca8cae5f924cedaac7b202b0f3c167111d/test/CodeGen/bittest-intrin .c#L45) mais nous pouvons l'utiliser dans la bibliothèque std et exposer l'intrinsèque à Rust sûr.

Sentez-vous encouragé à ouvrir un problème dans le référentiel stdsimd concernant les éléments intrinsèques manquants.

@josevalaad

Eh bien, lorsque vous parlez d'assemblage en mathématiques, vous parlez essentiellement d'utiliser les registres SIMD et les instructions telles que _mm256_mul_pd, _mm256_permute2f128_pd, etc. et les opérations de vectorisation là où elles se déroulent.

Ah, je me doutais que ça pouvait être le cas. Eh bien, si vous voulez essayer, vous pouvez traduire l'assembly en appels intrinsèques std::arch et voir si vous en obtenez les mêmes performances.

Si vous ne le faites pas , veuillez signaler les problèmes. LLVM n'est pas magique, mais au moins les intrinsèques devraient être aussi bons que asm.

@dancrossnyc Si cela ne vous dérange pas de demander, existe-t-il des cas d'utilisation/des fonctionnalités de plate-forme en particulier qui nécessitent un assemblage en ligne, dans votre situation ?

@MSxDOS Peut-être


Peut-être que nous devrions faire une collecte de données et obtenir une ventilation de ce que les gens veulent vraiment asm! , et voir combien d'entre eux pourraient être pris en charge d'une autre manière.

Peut-être que nous devrions faire une collecte de données et obtenir une ventilation de ce que les gens veulent vraiment !

Je veux asm! pour :

  • contourner les intrinsèques non fournis par le compilateur
  • contourner les bogues du compilateur / génération de code sous-optimale
  • effectuer des opérations qui ne peuvent pas être effectuées via une séquence d'appels intrinsèques uniques, par exemple, un EFLAGS de lecture EFLAGS-modify-write où LLVM est autorisé à modifier les eflags entre la lecture et l'écriture, et où LLVM suppose également que l'utilisateur ne modifiera pas ceci derrière son dos (c'est-à-dire que la seule façon de travailler en toute sécurité avec EFLAGS est d'écrire les opérations de lecture-modification-écriture sous la forme d'un seul bloc atomique asm! ).

et voyez combien d'entre eux pourraient être soutenus d'une autre manière.

Je ne vois aucun autre moyen de prendre en charge l'un de ces cas d'utilisation qui n'implique pas une forme d'assemblage en ligne, mais mon esprit est ouvert.

Copié de mon message dans le fil de discussion pré-RFC, voici un assemblage en ligne (ARM64) que j'utilise dans mon projet actuel :

// Common code for interruptible syscalls
macro_rules! asm_interruptible_syscall {
    () => {
        r#"
            # If a signal interrupts us between 0 and 1, the signal handler
            # will rewind the PC back to 0 so that the interrupt flag check is
            # atomic.
            0:
                ldrb ${0:w}, $2
                cbnz ${0:w}, 2f
            1:
               svc #0
            2:

            # Record the range of instructions which should be atomic.
            .section interrupt_restart_list, "aw"
            .quad 0b
            .quad 1b
            .previous
        "#
    };
}

// There are other versions of this function with different numbers of
// arguments, however they all share the same asm code above.
#[inline]
pub unsafe fn interruptible_syscall3(
    interrupt_flag: &AtomicBool,
    nr: usize,
    arg0: usize,
    arg1: usize,
    arg2: usize,
) -> Interruptible<usize> {
    let result;
    let interrupted: u64;
    asm!(
        asm_interruptible_syscall!()
        : "=&r" (interrupted)
          "={x0}" (result)
        : "*m" (interrupt_flag)
          "{x8}" (nr as u64)
          "{x0}" (arg0 as u64)
          "{x1}" (arg1 as u64)
          "{x2}" (arg2 as u64)
        : "x8", "memory"
        : "volatile"
    );
    if interrupted == 0 {
        Ok(result)
    } else {
        Err(Interrupted)
    }
}

@Amanieu note que @japaric travaille sur les intrinsèques pour ARM . Cela vaudrait la peine de vérifier si cette proposition répond à vos besoins.

@shepmaster

@Amanieu note que @japaric travaille sur les intrinsèques pour ARM. Cela vaudrait la peine de vérifier si cette proposition répond à vos besoins.

Il est bon de remarquer que :

  • ce travail ne remplace pas l'assemblage en ligne, il le complète simplement. Cette approche implémente des API de fournisseurs dans std::arch , ces API sont déjà insuffisantes pour certaines personnes.

  • cette approche n'est utilisable que lorsqu'une séquence d'appels intrinsèques comme foo(); bar(); baz(); produit du code indiscernable de cette séquence d'instructions - ce n'est pas nécessairement le cas, et quand ce n'est pas le cas, le code qui semble correct produit au mieux incorrect résultats, et au pire a un comportement indéfini (nous avons déjà eu des bogues à cause de cela dans x86 et x86_64 dans std , par exemple, https://github.com/rust- lang-nursery/stdsimd/blob/master/coresimd/x86/cpuid.rs#L108 - d'autres architectures ont également ces problèmes).

  • certains intrinsèques ont des arguments de mode immédiat, que vous ne pouvez pas passer via un appel de fonction, de sorte que foo(3) ne fonctionnera pas. Chaque solution à ce problème est actuellement une solution de contournement farfelue, et dans certains cas, aucune solution de contournement n'est actuellement possible dans Rust, nous ne fournissons donc tout simplement pas certains de ces éléments intrinsèques.

Donc, si les API du fournisseur sont implémentables dans Rust, disponibles sur std::arch , et peuvent être combinées pour résoudre un problème, je suis d'accord qu'elles sont meilleures que l'assemblage en ligne. Mais de temps en temps, soit les API ne sont pas disponibles, peut-être même pas implémentables, et/ou elles ne peuvent pas être combinées correctement. Bien que nous puissions résoudre les "problèmes de mise en œuvre" à l'avenir, si ce que vous voulez faire n'est pas exposé par l'API du fournisseur, ou si les API ne peuvent pas être combinées, cette approche ne vous aidera pas.

Ce qui peut être très surprenant à propos de l'implémentation des intrinsèques par LLVM (SIMD en particulier), c'est qu'ils ne sont pas du tout conformes au mappage explicite d'Intel des intrinsèques aux instructions - ils sont soumis à un large éventail d'optimisations de compilateur. Par exemple, je me souviens d'une fois où j'ai tenté de réduire la pression sur la mémoire en calculant certaines constantes à partir d'autres constantes au lieu de les charger à partir de la mémoire. Mais LLVM a simplement procédé à un repliement constant de l'ensemble dans la charge de mémoire exacte que j'essayais d'éviter. Dans un cas différent, je voulais étudier le remplacement d'un shuffle 16 bits par un shuffle 8 bits pour réduire la pression du port5. Pourtant, dans sa sagesse sans fin, l'optimiseur LLVM toujours utile a remarqué que mon shuffle 8 bits est en fait un shuffle 16 bits et l'a remplacé.

Les deux optimisations donnent certainement un meilleur débit (en particulier face à l'hyperthreading) mais pas la réduction de latence que j'espérais atteindre. J'ai fini par tomber jusqu'à nasm pour cette expérience, mais devoir réécrire le code des intrinsèques à l'asm simple n'était qu'une friction inutile. Bien sûr, je veux que l'optimiseur gère des choses comme la sélection d'instructions ou le pliage constant lors de l'utilisation d'une API vectorielle de haut niveau. Mais quand j'ai explicitement décidé quelles instructions utiliser, je ne veux vraiment pas que le compilateur s'embrouille avec ça. La seule alternative est l'asm en ligne.

Donc, si les API du fournisseur sont implémentables dans Rust, disponibles sur std::arch , et peuvent être combinées pour résoudre un problème, je suis d'accord qu'elles sont meilleures que l'assemblage en ligne

C'est tout ce que j'ai dit au début

atteindre 95-99% des objectifs

et encore

Oui, il y a des cas qui nécessitent un assemblage pour l'instant et il y a des cas qui en auront toujours besoin, je l'ai dit à l'origine (emphase ajoutée pour plus de clarté):

La plupart des gens s'attendent à ce que les [intrinsèques] atteignent 95 à 99 % des objectifs.

C'est la même chose que @eddyb dit en parallèle. Je ne comprends pas pourquoi plusieurs personnes agissent comme si j'ignorais complètement l'utilité de l'assemblage en ligne tout en essayant de souligner les réalités de la situation actuelle .

j'ai

  1. A pointé une affiche qui ne faisait aucune mention de savoir que les intrinsèques existaient vers des intrinsèques
  2. A dirigé une autre affiche vers les éléments intrinsèques proposés afin qu'ils puissent fournir des commentaires précoces sur la proposition.

Permettez-moi de le dire très clairement : oui, l'assemblage en ligne est parfois nécessaire et bon . Je ne conteste pas cela. J'essaie seulement d'aider les gens à résoudre des problèmes du monde réel avec les outils disponibles maintenant.

Ce que j'essayais de dire, c'est que nous devrions avoir une approche plus organisée à cet égard, une enquête appropriée, et rassembler beaucoup plus de données que quelques-uns d'entre nous dans ce fil, puis l' utiliser pour souligner les besoins les plus courants de assemblage en ligne (car il est clair que les intrinsèques ne peuvent pas le remplacer complètement).

Je soupçonne que chaque architecture a un sous-ensemble difficile à modéliser, qui tire une certaine utilité de l'inline asm! , et peut-être devrions-nous nous concentrer sur ces sous-ensembles, puis essayer de généraliser.

cc @rust-lang/lang

@eddyb _require_ est un mot fort, et je serais obligé de dire que non, nous ne sommes pas strictement tenus d'utiliser l'assembleur en ligne. Comme je l'ai mentionné plus tôt, nous _pourrions_ définir des procédures en langage assembleur pur, les assembler séparément et les lier à nos programmes Rust via le FFI.

Cependant, comme je l'ai dit plus tôt, je ne connais aucun projet sérieux au niveau du système d'exploitation qui fasse cela. Cela signifierait beaucoup de passe-partout (lire : plus de chances de faire une erreur), un processus de construction plus complexe (en ce moment, nous avons la chance de pouvoir nous en tirer avec une simple invocation cargo et un lien et le noyau presque prêt à fonctionner sort de l'autre extrémité ; nous devrions appeler l'assembleur et le lier dans une étape distincte), et une diminution drastique de la capacité d'intégrer des choses, etc ; il y aurait presque certainement un coup de performance.

Des choses comme les intrinsèques du compilateur aident dans de nombreux cas, mais pour des choses comme le jeu d'instructions de supervision de l'ISA cible, en particulier les fonctionnalités matérielles plus ésotériques (fonctionnalités d'hyperviseur et d'enclave, par exemple), il n'y a souvent pas d'intrinsèques et nous sommes dans un environnement no_std. Les éléments intrinsèques sont souvent insuffisants ; par exemple, la convention d'appel d'interruption x86 semble cool mais ne vous donne pas un accès modifiable aux registres à usage général dans un cadre de déroutement : supposons que je prenne une exception d'instruction non définie avec l'intention de faire de l'émulation, et supposons que l'instruction émulée renvoie une valeur en %rax ou quelque chose comme ça ; la convention d'appel ne me donne pas un bon moyen de transmettre cela au site d'appel, nous avons donc dû lancer la nôtre. Cela signifiait écrire mon propre code de gestion des exceptions en assembleur.

Donc, pour être honnête, non, nous n'avons pas besoin d'assembleur en ligne, mais il est suffisamment utile pour que ce ne soit presque pas un débutant de ne pas l'avoir.

@dancrossnyc Je suis particulièrement curieux de savoir éviter le montage séparé, qui est, quel genre de réunion dont vous avez besoin du tout dans votre projet, peu importe comment vous lien dans.

Dans votre cas, cela semble être un sous-ensemble ISA privilégié superviseur/hyperviseur/enclave, est-ce correct ?

il n'y a souvent pas d'intrinsèques

Est-ce par nécessité, c'est-à-dire que les instructions ont des exigences qui sont déraisonnablement difficiles ou même impossibles à respecter lorsqu'elles sont compilées en tant qu'appels intrinsèques, par exemple LLVM ?
Ou est-ce simplement parce qu'ils sont supposés être trop spéciaux pour être utiles à la plupart des développeurs ?

et nous sommes dans un environnement no_std

Pour mémoire, les intrinsèques du fournisseur sont à la fois en std::arch et en core::arch (le premier est une réexportation).

la convention d'appel d'interruption x86 semble cool mais ne vous donne pas un accès modifiable aux registres à usage général dans un cadre de déroutement

cc @rkruppe Cela peut-il être implémenté dans LLVM ?

@eddyb correct ; nous avons besoin du sous-ensemble superviseur de l'ISA. Je crains de ne pas pouvoir en dire beaucoup plus pour le moment sur notre cas d'utilisation spécifique.

Est-ce par nécessité, c'est-à-dire que les instructions ont des exigences qui sont déraisonnablement difficiles ou même impossibles à respecter lorsqu'elles sont compilées en tant qu'appels intrinsèques, par exemple LLVM ?
Ou est-ce simplement parce qu'ils sont supposés être trop spéciaux pour être utiles à la plupart des développeurs ?

Dans une certaine mesure, les deux sont vrais, mais dans l'ensemble, je dirais que ce dernier est plus pertinent ici. Certaines choses sont spécifiques à la microarchitecture et dépendent de configurations de progiciels spécifiques. Serait-il raisonnable pour un compilateur d'exposer (par exemple) quelque chose en tant qu'intrinsèque faisant partie du sous-ensemble d'instructions privilégié _et_ conditionné par une version de processeur spécifique ? Honnêtement, je ne sais pas.

Pour mémoire, les intrinsèques du fournisseur sont à la fois dans std::arch et core::arch (le premier est une réexportation).

C'est effectivement très bon à savoir. Merci!

Serait-il raisonnable pour un compilateur d'exposer (par exemple) quelque chose en tant qu'intrinsèque faisant partie du sous-ensemble d'instructions privilégié et conditionné par une version de processeur spécifique ? Honnêtement, je ne sais pas.

Nous le faisons déjà. Par exemple, les instructions xsave x86 sont implémentées et exposées dans core::arch , non disponibles sur tous les processeurs, et la plupart d'entre elles nécessitent un mode privilégié.

@gnzlbg xsave n'est pas privilégié ; vouliez-vous dire xsaves ?

J'ai jeté un œil à https://rust-lang-nursery.github.io/stdsimd/x86_64/stdsimd/arch/x86_64/index.html et les seules instructions privilégiées que j'ai vues dans mon balayage rapide (je n'ai pas fait de recherche exhaustive) étaient xsaves , xsaves64 , xrstors , et xrstors64 . Je soupçonne qu'il s'agit d'intrinsèques car ils appartiennent à la famille générale XSAVE* et ne génèrent pas d'exceptions en mode réel, et certaines personnes souhaitent utiliser clang/llvm pour compiler du code en mode réel.

@dancrossnyc oui certains d'entre eux sont ceux que je voulais dire (nous implémentons xsave , xsaves , xsaveopt , ... dans le module xsave : https : //github.com/rust-lang-nursery/stdsimd/blob/master/coresimd/x86/xsave.rs).

Ceux-ci sont disponibles en core , vous pouvez donc les utiliser pour écrire un noyau de système d'exploitation pour x86. Dans l'espace utilisateur, ils sont inutiles AFAICT (ils lèveront toujours une exception), mais nous n'avons aucun moyen de faire la distinction à ce sujet dans core . Nous ne pouvions les exposer que dans core et pas dans std cependant, mais comme ils sont déjà stables, ce navire a navigué. Qui sait, peut-être qu'un système d'exploitation exécutera tout dans l'anneau 0 un jour, et vous pourrez les utiliser là-bas...

@gnzlbg Je ne sais pas pourquoi xsaveopt ou xsave lèveraient une exception dans l'espace utilisateur : xsaves est le seul de la famille qui est défini pour générer une exception (#GP si CPL>0), et alors uniquement en mode protégé (SDM vol.1 ch. 13; vol.2C ch. 5 XSAVES). xsave et xsaveopt sont utiles pour implémenter, par exemple, des threads préemptifs dans l'espace utilisateur, de sorte que leur présence en tant qu'intrinsèque a un sens. Je soupçonne que l'intrinsèque pour xsaves était soit parce que quelqu'un venait d'ajouter tout de la famille xsave sans se rendre compte du problème de privilège (c'est-à-dire, en supposant qu'il était invocable depuis l'espace utilisateur), ou quelqu'un voulait l'appeler du mode réel. Ce dernier peut sembler tiré par les cheveux, mais je sais que des gens construisent par exemple un firmware en mode réel avec Clang et LLVM.

Ne vous méprenez pas ; la présence d'intrinsèques LLVM dans core est excellente ; si je n'ai plus jamais à écrire cette séquence d'instructions idiote pour obtenir les résultats de rdtscp dans un format utile, je serais heureux. Mais l'ensemble actuel d'intrinsèques ne remplace pas l'assembleur en ligne lorsque vous écrivez un noyau ou un autre type de supervision bare-metal.

@dancrossnyc quand j'ai mentionné xsave je faisais référence à certains des intrinsèques disponibles derrière les bits CPUID XSAVE, XSAVEOPT, XSAVEC, etc. Certains de ces intrinsèques nécessitent un mode privilégié.

Serait-il raisonnable pour un compilateur d'exposer (par exemple) quelque chose en tant qu'intrinsèque faisant partie du sous-ensemble d'instructions privilégié et conditionné par une version de processeur spécifique ?

Nous le faisons déjà et ils sont disponibles dans Rust stable.

Je soupçonne que l'intrinsèque de xsaves était soit parce que quelqu'un vient d'ajouter tout de la famille xsave sans se rendre compte du problème de privilège

J'ai ajouté ces intrinsèques. Nous avons réalisé les problèmes de privilèges et avons décidé de les ajouter de toute façon car il est parfaitement bien pour un programme dépendant de core d'être un noyau de système d'exploitation qui veut les utiliser, et ils sont inoffensifs dans l'espace utilisateur (comme dans, si vous essayez de les utiliser, votre processus se termine).

Mais l'ensemble actuel d'intrinsèques ne remplace pas l'assembleur en ligne lorsque vous écrivez un noyau ou un autre type de supervision bare-metal.

D'accord, c'est pourquoi ce problème est toujours ouvert ;)

@gnzlbg désolé, je ne veux pas faire dérailler cela en xsave et al.

Cependant, pour autant que je sache, les seuls intrinsèques qui nécessitent une exécution privilégiée sont ceux liés à xsaves et même alors, ce n'est pas toujours privilégié (encore une fois, le mode réel s'en moque). C'est merveilleux que ceux-ci soient disponibles dans Rust stable (sérieusement). Les autres pourraient être utiles dans l'espace utilisateur et de la même manière, je pense que c'est bien qu'ils soient là. Cependant, xsaves et xrstors sont une très, très petite partie de l'ensemble d'instructions privilégié et avoir ajouté des intrinsèques pour deux instructions est qualitativement différent que de le faire en général et je pense que la question reste de savoir si c'est approprié _en général_. Considérez l'instruction VMWRITE des extensions VMX, par exemple ; J'imagine qu'un intrinsèque ferait quelque chose comme exécuter une instruction puis "retourner" rflags . C'est une sorte de chose étrangement spécialisée à avoir comme intrinsèque.

Je pense qu'autrement nous sommes d'accord ici.

FWIW selon le std::arch RFC, nous ne pouvons actuellement ajouter des intrinsèques qu'aux std::arch que les fournisseurs exposent dans leurs API. Pour le cas de xsave , Intel les expose sur son API C , c'est pourquoi c'est ok c'est là. Si vous avez besoin d'intrinsèques de fournisseur qui ne sont pas actuellement exposés, ouvrez un ticket, qu'il nécessite ou non un mode privilégié n'a pas d'importance.

Si le fournisseur n'expose pas d'intrinsèque pour cela, alors std::arch n'est peut-être pas l'endroit pour cela, mais il existe de nombreuses alternatives à cela (assemblage en ligne, asm global, appel C, ...).

Désolé, j'ai compris que vous disiez que vous aviez écrit les intrinsèques pour xsave pour signifier les intrinsèques Intel ; mes commentaires précédents s'appliquent toujours à la raison pour laquelle je pense que xsaves est alors intrinsèque (soit un accident par un auteur de compilateur chez Intel, soit parce que quelqu'un le voulait pour le mode réel ; j'ai l'impression que le premier serait remarqué très rapidement mais firmware fait des trucs bizarres, donc ce dernier ne me surprendrait pas du tout).

Quoi qu'il en soit, oui, je pense que nous sommes fondamentalement d'accord : les intrinsèques ne sont pas faits pour tout, et c'est pourquoi nous aimerions voir asm!() déplacé vers stable. Je suis vraiment ravi d'apprendre que des progrès sont réalisés dans ce domaine, comme vous l'avez dit hier, et si nous pouvions doucement pousser @Florob pour qu'il se rapproche du sommet de la pile, nous serions heureux de le faire !

Quelques détails supplémentaires et cas d'utilisation pour asm! :

Lorsque vous écrivez un système d'exploitation, un micrologiciel, certains types de bibliothèques ou certains autres types de code système, vous avez besoin d' un accès ne serait

Voici une petite fraction des choses que vous pouvez faire avec l'assemblage en ligne que vous ne pouvez pas facilement faire autrement. Chacun d'entre eux est un exemple du monde réel que j'ai vu (ou dans certains cas écrit), pas un exemple hypothétique.

  • Collectez toutes les implémentations d'un modèle particulier d'instructions dans une section ELF distincte, puis, lors du chargement du code, corrigez cette section au moment de l'exécution en fonction des caractéristiques du système sur lequel vous exécutez.
  • Écrivez une instruction de saut dont la cible est corrigée au moment de l'exécution.
  • Émettez une séquence exacte d'instructions (vous ne pouvez donc pas compter sur les éléments intrinsèques pour les instructions individuelles), afin de pouvoir implémenter un modèle qui gère soigneusement les interruptions potentielles au milieu.
  • Émettez une instruction, suivie d'un saut à la fin du bloc asm, suivi d'un code de récupération de panne pour un gestionnaire de pannes matérielles auquel sauter si l'instruction génère une panne.
  • Emettre une séquence d'octets correspondant à une instruction que l'assembleur ne connaît pas encore.
  • Écrivez un morceau de code qui bascule avec précaution vers une pile différente, puis appelle une autre fonction.
  • Appelez des routines d'assembly ou des appels système qui nécessitent des arguments dans des registres spécifiques.

+1e6

@eddyb

Ok, je vais essayer l'approche intrinsèque et voir où cela mène. Vous avez probablement raison et c'est la meilleure approche pour mon cas. Merci!

@joshtriplett a réussi ! Ce sont les cas d'utilisation exacts que j'avais en tête.

loop {
   :thumbs_up:
}

J'ajouterais quelques autres cas d'utilisation :

  • écrire du code dans des modes architecturaux étranges, comme les appels BIOS/EFI et le mode réel 16 bits.
  • écrire du code avec des modes d'adressage étranges/inhabituels (qui apparaissent souvent en mode réel 16 bits, chargeurs de démarrage, etc.)

@mark-im Absolument ! Et en généralisant un point qui a des sous-cas dans nos deux listes : la traduction entre les conventions d'appel.

Je clôt le #53118 en faveur de ce problème et je copie le PR ici pour mémoire. Notez que cela date d'août, mais un bref aperçu semble indiquer que la situation n'a pas changé :


La section sur l'assemblage en ligne a besoin d'une refonte ; dans son état actuel, cela implique que le comportement et la syntaxe sont liés à rustc et au langage rust en général. La quasi-totalité de la documentation est spécifique à l'assemblage x86/x86_64 avec la chaîne d'outils llvm. Pour être clair, je ne fais pas référence au code d'assemblage lui-même, qui est évidemment spécifique à la plate-forme, mais plutôt à l'architecture générale et à l'utilisation de l'assemblage en ligne.

Je n'ai pas trouvé de source faisant autorité pour le comportement de l'assemblage en ligne en ce qui concerne la cible ARM, mais selon mes expérimentations et en faisant référence à la documentation de l'assemblage en ligne ARM GCC , les points suivants semblent être complètement erronés :

  • La syntaxe ASM, comme ARM/MIPS (et la plupart des autres CISC ?) utilise la syntaxe intel-esque avec le registre de destination en premier. J'ai compris que la documentation signifiait/impliquait que l'asm en ligne prenait la syntaxe at&t qui était transpilée en une syntaxe spécifique à la plate-forme/au compilateur, et que je devais simplement remplacer les noms des registres x86 par ceux des registres ARM uniquement.
  • De même, l'option intel n'est pas valide, car elle provoque des erreurs de "directive inconnue" lors de la compilation .
  • Adapté de la documentation de l'assemblage en ligne ARM GCC (pour construire contre thumbv7em-none-eabi avec la chaîne d'outils arm-none-eabi-* , il semble que même certaines hypothèses de base sur le format de l'assemblage en ligne sont spécifiques à la plate-forme. En particulier, il semble que pour ARM le registre de sortie (deuxième argument de la macro) compte comme une référence de registre, c'est- $0 dire que
  • En même temps, d'autres fonctionnalités spécifiques au compilateur ne sont _pas_ présentes ; Je ne peux pas utiliser de références nommées aux registres, uniquement des index (par exemple, asm("mov %[result],%[value],ror #1":[result] "=r" (y):[value] "r" (x)); n'est pas valide).
  • (Même pour les cibles x86/x86_64, l'utilisation de $0 et $2 dans l'exemple d'assemblage en ligne est très déroutant, car cela n'explique pas pourquoi ces nombres ont été choisis.)

Je pense que ce qui m'a le plus bouleversé, c'est la déclaration finale :

La mise en œuvre actuelle de l'asm! macro est une liaison directe aux expressions assembleur en ligne de LLVM, alors assurez-vous de consulter également leur documentation pour plus d'informations sur les clobbers, les contraintes, etc.

Ce qui ne semble pas être universellement vrai.

J'ai compris que la documentation signifiait/impliquait que l'asm en ligne prenait la syntaxe at&t qui était transpilée en une syntaxe spécifique à la plate-forme/au compilateur, et que je devais simplement remplacer les noms des registres x86 par ceux des registres ARM uniquement.

Une notion de syntaxe intel vs at&t n'existe que sur x86 (bien qu'il puisse y avoir d'autres cas que je ne connais pas). C'est unique en ce qu'il s'agit de deux langues différentes partageant le même ensemble de mnémoniques pour représenter le même ensemble de code binaire. L'écosystème GNU a établi la syntaxe at&t comme la valeur par défaut dominante pour le monde x86, c'est pourquoi c'est la valeur par défaut d'asm en ligne. Vous vous trompez en ce sens qu'il s'agit d'une liaison directe avec les expressions d'assembleur en ligne de LLVM qui, à leur tour, ne font que vider le texte en clair (après avoir traité les substitutions) dans le programme d'assemblage textuel. Rien de tout cela n'est unique (ni même pertinent) pour ou à propos des asm!() car il est entièrement spécifique à la plate-forme et n'a aucun sens au-delà du monde x86.

De même, l'option intel n'est pas valide, car elle provoque des erreurs de "directive inconnue" lors de la compilation.

Il s'agit d'une conséquence directe de l'insertion de texte en clair « muet »/simple que j'ai décrite ci-dessus. Comme l'indique le message d'erreur, la directive .intel_syntax n'est pas prise en charge. Il s'agit d'une solution de contournement ancienne et bien connue pour l'utilisation de l'asm en ligne de style intel avec GCC (qui émet un style att): il suffit d'écrire .intel_syntax au début du bloc asm en ligne, puis d'écrire quelques intel- style asm et enfin terminer avec .att_syntax pour remettre l'assembleur en mode att afin qu'il traite à nouveau correctement le code généré par le compilateur (suivant). C'est un sale hack et je me souviens au moins que l'implémentation LLVM avait eu des bizarreries étranges pendant longtemps, il semble donc que vous voyez cette erreur car elle a finalement été supprimée. Malheureusement, le seul plan d'action correct ici est de supprimer l'option "intel" de rustc.

il semble que même certaines hypothèses de base sur le format de l'assemblage en ligne soient spécifiques à la plate-forme

Votre observation est tout à fait correcte, chaque plate-forme constitue à la fois son propre format binaire et son propre langage d'assemblage. Ils sont complètement indépendants et (pour la plupart) non traités par le compilateur - ce qui est tout l'intérêt de la programmation en assembleur brut !

Je ne peux pas utiliser de références nommées aux registres, seulement des index

Malheureusement, il y a un assez gros décalage entre l'implémentation asm en ligne de LLVM que rustc expose et l'implémentation de GCC (que clang émule). Sans une décision sur la façon d'aller de l'avant avec asm!() il y a peu de motivation pour améliorer cela - d'ailleurs, j'ai décrit les principales options il y a longtemps, elles ont toutes des inconvénients évidents. Comme cela ne semble pas être une priorité, vous allez probablement être coincé avec les asm!() pendant au moins quelques années. Il existe des solutions de contournement décentes :

  • comptez sur l'optimiseur pour produire un code optimal (avec un petit coup de pouce, vous pouvez généralement obtenir exactement ce que vous voulez sans jamais écrire vous-même l'assemblage brut)
  • utiliser des intrinsèques, une autre solution assez élégante qui est meilleure que l'asm en ligne dans presque tous les sens (à moins que vous n'ayez besoin d'un contrôle exact sur la sélection et la planification des instructions)
  • invoquer la caisse cc partir de build.rs pour lier un objet C avec asm en ligne

    • fondamentalement, invoquez simplement n'importe quel assembleur que vous aimez à partir de build.rs , l'utilisation d'un compilateur C peut sembler exagérée mais vous évite les tracas de l'intégration avec le système build.rs

Ces solutions de contournement s'appliquent à tous, sauf à un petit ensemble de cas limites très spécifiques. Si vous touchez l'un d'entre eux (heureusement, je ne l'ai pas encore fait), vous n'avez pas de chance.

Je suis d'accord que la documentation est assez terne mais c'est assez bon pour quiconque est familiarisé avec l'asm en ligne. Si vous ne l'êtes pas, vous ne devriez probablement pas l'utiliser . Ne vous méprenez pas - vous devriez certainement vous sentir libre d'expérimenter et d'apprendre, mais comme asm!() est instable et négligé et parce qu'il existe de très bonnes solutions de contournement, je déconseille fortement de l'utiliser dans tout projet sérieux si possible .

invoquer la caisse cc de build.rs pour lier un objet C avec asm en ligne

Vous pouvez également invoquer la caisse cc partir de build.rs pour créer des fichiers d'assemblage simples, ce qui donne le maximum de contrôle. Je recommande fortement de faire exactement cela au cas où les deux "solutions de contournement" ci-dessus ne fonctionneraient pas pour votre cas d'utilisation.

@main-- a écrit :

Ces solutions de contournement s'appliquent à tous, sauf à un petit ensemble de cas limites très spécifiques. Si vous touchez l'un d'entre eux (heureusement, je ne l'ai pas encore fait), vous n'avez pas de chance.

Je veux dire, pas tout à fait par hasard. Il vous suffit d'utiliser le inline asm Rust. J'ai un cas limite qu'aucune de vos solutions de contournement répertoriées ne couvre ici . Comme vous le dites, si vous connaissez le processus d'autres compilateurs, c'est généralement très bien.

(J'ai un autre cas d'utilisation : j'aimerais un jour enseigner l'architecture informatique et la programmation de systèmes en utilisant Rust au lieu de C. Ne pas avoir d'assemblage en ligne rendrait cela beaucoup plus gênant.)

J'aimerais que nous fassions de l'assemblage en ligne une priorité dans Rust et que nous le stabilisions le plus tôt possible. Cela devrait peut-être être un objectif de Rust 2019. Je suis d'accord avec l'une des solutions que vous énumérez dans votre gentil commentaire plus tôt : je pourrais vivre avec les problèmes de n'importe laquelle d'entre elles. Pouvoir intégrer du code assembleur est pour moi un prérequis pour écrire partout en Rust au lieu de C : j'en ai vraiment besoin pour qu'il soit stable.

J'aimerais que nous fassions de l'assemblage en ligne une priorité dans Rust et que nous le stabilisions le plus tôt possible. Cela devrait peut-être être un objectif de Rust 2019.

Veuillez écrire un article de blog sur Rust 2019 et exprimer cette préoccupation. Je pense que si nous sommes assez nombreux à le faire, nous pouvons influencer la feuille de route.

Pour clarifier mon commentaire ci-dessus - le problème est que la documentation n'explique pas à quel point le contenu de la macro asm!(..) est analysé/interagi avec. Je connais l'assemblage x86 et MIPS/ARM, mais j'ai supposé que llvm avait son propre format de langage d'assemblage. J'ai déjà utilisé l'assemblage en ligne pour x86, mais je ne savais pas dans quelle mesure l'abâtardissement d'asm vers brige C et ASM était allé. Ma présomption (maintenant invalidée) basée sur le libellé de la section d'assemblage en ligne de rouille était que LLVM avait son propre format ASM qui a été conçu pour imiter l'assemblage x86 en modes at&t ou intel, et ressemblait nécessairement aux exemples x86 montrés.

(Ce qui m'a aidé, c'est d'étudier la sortie macro étendue, qui a éclairci ce qui se passait)

Je pense qu'il doit y avoir moins d'abstraction sur cette page. Indiquez plus clairement ce qui est analysé par LLVM et ce qui est directement interprété comme ASM. Quelles pièces sont spécifiques à la rouille, quelles pièces sont spécifiques au matériel sur lequel vous utilisez et quelles pièces appartiennent à la colle qui les maintient ensemble.

invoquer la caisse cc partir de build.rs pour lier un objet C avec asm en ligne

Les progrès récents sur les LTO multilingues me font me demander si certains des inconvénients de cette avenue peuvent être réduits, en incorporant efficacement ce "blob d'assemblage externe". ( probablement pas )

invoquer la caisse cc partir de build.rs pour lier un objet C avec asm en ligne

Les progrès récents sur les LTO multilingues me font me demander si certains des inconvénients de cette avenue peuvent être réduits, en incorporant efficacement ce "blob d'assemblage externe".

Même si cela fonctionne, je ne veux pas écrire mon assemblage en ligne en C. Je veux l'écrire en Rust. :-)

Je ne veux pas écrire mon assemblage en ligne en C.

Vous pouvez compiler et lier directement des fichiers .s et .S (voir par exemple ce crate ), qui dans mon livre sont assez éloignés du C. :)

si certains des inconvénients de cette avenue peuvent être réduits

Je pense que cela n'est actuellement pas faisable, car le LTO multilingue repose sur l'IR LLVM et l'assemblage ne générerait pas cela.

Je pense que cela n'est actuellement pas faisable, car le LTO multilingue repose sur l'IR LLVM et l'assemblage ne générerait pas cela.

Vous pouvez fourrer l'assemblage dans l'assemblage au niveau du module dans les modules LLVM IR.

Est-ce que quelqu'un sait quelle est la proposition/l'état actuel le plus récent ? Étant donné que le thème de l'année est "la maturité et la finition de ce que nous avons commencé", cela semble être une excellente occasion de terminer enfin asm .

Des plans vagues pour une nouvelle syntaxe (à stabiliser) ont été discutés en février dernier : https://paper.dropbox.com/doc/FFI-5NmXV30TGiSsr9dIxpqpq

Selon ces notes, @joshtriplett et @Amanieu se sont inscrits pour rédiger un RFC.

Quel est le statut de la nouvelle syntaxe ?

Il doit être RFC et mis en œuvre tous les soirs

ping @joshtriplett @Amanieu Faites-moi savoir si je peux aider à faire avancer les choses ici ! Je serai en contact sous peu.

@cramertj AFAICT, tout le monde peut faire avancer les choses, cela est débloqué et attend que quelqu'un intervienne et se mette au travail. Il existe un pré-RFC esquissant la conception globale, et les prochaines étapes pourraient être de l'implémenter et de voir si cela fonctionne réellement, soit en tant que macro proc, dans un fork, ou en tant que fonctionnalité instable différente.

On pourrait probablement essayer de simplement transformer cette pré-RFC en une RFC appropriée et la soumettre, mais je doute que sans implémentation une telle RFC puisse être convaincante.


EDIT : pour être clair, par convaincant j'entends spécifiquement des parties du pré-RFC comme celle-ci :

de plus, des mappages pour les classes de registre sont ajoutés le cas échéant (cf. llvm-contrainte 6)

où il y a des dizaines de classes de registres spécifiques à l'arch dans le lang-ref. Un RFC ne peut pas simplement écarter tout cela, et s'assurer qu'ils fonctionnent tous comme ils sont censés le faire, ou qu'ils sont significatifs, ou qu'ils sont suffisamment "stables" dans LLVM pour être exposés à partir d'ici, etc. essayez simplement ceux-ci.

L'assemblage en ligne RISC-V est-il pris en charge ici avec #![feature(asm)] ?

Au meilleur de ma connaissance, tout l'assemblage sur les plates-formes prises en charge est pris en charge ; c'est à peu près un accès brut au support asm du compilateur llvm.

Oui, RISC-V est pris en charge. Les classes de contraintes d'entrée/sortie/clobber spécifiques à l'architecture sont documentées dans le LLVM langref .

Il y a une mise en garde, cependant - si vous devez vous limiter à des registres individuels dans les contraintes d'entrée/sortie/clobber, vous devez utiliser les noms de registres architecturaux (x0-x31, f0-f31), pas les noms ABI. Dans le fragment Assembly lui-même, vous pouvez utiliser l'un ou l'autre type de nom de registre.

En tant que personne nouvelle dans ces concepts, puis-je simplement dire... toute cette discussion semble _idiote_. Comment se fait-il qu'un langage (assembly) qui est censé être un mappage 1 à 1 avec son code machine provoque autant de maux de tête ?

Je suis assez confus :

  • Si vous écrivez en asm, ne devrait-il pas être réécrit (par un humain avec #[cfg(...)] ) pour chaque architecture _et backend_ que vous essayez de prendre en charge ?
  • Cela signifie que la question de "syntaxe" est sans objet... utilisez simplement la syntaxe pour cette architecture et le backend que le compilateur utilise.
  • Rust aurait juste besoin de fonctions non sécurisées std pour pouvoir mettre des octets dans les registres corrects et pousser/pop dans la pile pour n'importe quelle architecture contre laquelle il est compilé -- encore une fois, cela devra peut-être être réécrit pour chaque architecture et peut-être même chaque backend.

Je comprends que la rétrocompatibilité est un problème, mais avec le grand nombre de bogues et le fait que cela n'a jamais été stabilisé, il serait peut-être préférable de simplement le transmettre au backend. Rust ne devrait pas essayer de corriger les erreurs de syntaxe étrange de LLVM ou de gcc ou de quelqu'un d'autre. Rust a pour mission d'émettre du code machine pour l'architecture et le compilateur qu'il cible... et asm est déjà essentiellement ce code !

La raison pour laquelle il n'y a pas de progrès ici est que personne n'investit du temps pour résoudre ce problème. Ce n'est pas une bonne raison pour stabiliser une fonctionnalité.

En parcourant ce fil, j'ai eu une idée et j'ai dû la poster. Désolé si je réponds à un vieux post, mais je pensais que ça valait le coup :

@main-- a dit :

Les deux optimisations donnent certainement un meilleur débit (en particulier face à l'hyperthreading) mais pas la réduction de latence que j'espérais atteindre. J'ai fini par tomber jusqu'à nasm pour cette expérience, mais devoir réécrire le code des intrinsèques à l'asm simple n'était qu'une friction inutile. Bien sûr, je veux que l'optimiseur gère des choses comme la sélection d'instructions ou le pliage constant lors de l'utilisation d'une API vectorielle de haut niveau. Mais quand j'ai explicitement décidé quelles instructions utiliser, je ne veux vraiment pas que le compilateur s'embrouille avec ça. La seule alternative est l'asm en ligne.

Peut-être qu'au lieu d'asm en ligne, ce dont nous avons vraiment besoin ici, ce sont des attributs de fonction pour LLVM qui indiquent à l'optimiseur : "optimisez ceci pour le débit", "optimisez cela pour la latence", "optimisez cela pour la taille binaire". Je sais que cette solution est en amont, mais elle résoudrait non seulement votre problème particulier automatiquement (en fournissant la mise en œuvre à faible latence mais sinon isomorphe de l'algorithme), elle permettrait également aux programmeurs Rust d'avoir un contrôle plus fin sur les caractéristiques de performance qui comptent pour eux.

@ felix91gr Cela ne résout pas les cas d'utilisation qui nécessitent l'émission d'une séquence exacte d'instructions, par exemple les gestionnaires d'interruption.

@mark-im bien sûr que non. C'est pourquoi j'ai mis une citation littérale! ??

Mon point était que même si vous pouvez résoudre le "compilateur optimise d'une manière opposée à ce dont j'ai besoin" (ce qui est classique dans leur cas: latence vs débit) en utilisant des fonctionnalités asm en ligne, peut-être (et imo certainement) ce cas d'utilisation serait être mieux servi par un contrôle plus fin des optimisations :)

À la lumière des changements à venir concernant l'assemblage en ligne, la plupart des discussions dans ce numéro ne sont plus pertinentes. En tant que tel, je vais clore ce problème en faveur de deux problèmes de suivi distincts pour chaque version d'assemblage en ligne que nous avons :

  • Problème de suivi pour l'assemblage en ligne de style LLVM ( llvm_asm ) #70173
  • Problème de suivi pour l'assemblage en ligne ( asm! ) #72016
Cette page vous a été utile?
0 / 5 - 0 notes