Rust: Problema de seguimiento para `asm` (ensamblado en línea)

Creado en 9 nov. 2015  ·  111Comentarios  ·  Fuente: rust-lang/rust

Este problema rastrea la estabilización del ensamblaje en línea. La función actual no ha pasado por el proceso de RFC y probablemente deba hacerlo antes de la estabilización.

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

Comentario más útil

Me gustaría señalar que la sintaxis de asm en línea de LLVM es diferente de la utilizada por clang / gcc. Las diferencias incluyen:

  • LLVM usa $0 lugar de %0 .
  • LLVM no admite operandos asm con nombre %[name] .
  • LLVM admite diferentes tipos de restricciones de registro: por ejemplo, "{eax}" lugar de "a" en x86.
  • LLVM admite restricciones de registro explícitas ( "{r11}" ). En C, debe usar en su lugar variables de registro asm para vincular un valor a un registro ( register asm("r11") int x ).
  • Las restricciones LLVM "m" y "=m" están básicamente rotas. Clang los traduce en restricciones de memoria indirectas "*m" y "=*m" y pasa la dirección de la variable a LLVM en lugar de la variable en sí.
  • etc ...

Clang convertirá asm en línea del formato gcc al formato LLVM antes de pasarlo a LLVM. También realiza alguna validación de las restricciones: por ejemplo, asegura que los operandos "i" son constantes en tiempo de compilación,


A la luz de esto, creo que deberíamos implementar la misma traducción y validación que hace clang y admitir la sintaxis gcc inline asm adecuada en lugar de la extraña LLVM.

Todos 111 comentarios

¿Habrá alguna dificultad para garantizar la compatibilidad con versiones anteriores del ensamblado en línea en código estable?

@ main-- tiene un gran comentario en https://github.com/rust-lang/rfcs/pull/1471#issuecomment -173982852 que estoy reproduciendo aquí para la posteridad:

¡Con todos los errores abiertos e inestabilidades que rodean a asm! () (Hay muchos ), realmente no creo que esté listo para la estabilización, aunque me encantaría tener un conjunto en línea estable en Rust.

También deberíamos discutir si asm! () De hoy es realmente la mejor solución o si algo como RFC # 129 o incluso D sería mejor. Un punto importante a considerar aquí es que asm () no admite el mismo conjunto de restricciones que gcc. Por lo tanto, podemos:

  • Siga el comportamiento de LLVM y escriba documentos para eso (porque no he podido encontrar ninguno). Agradable porque evita la complejidad en óxido. Malo porque confundirá a los programadores que vienen de C / C ++ y porque algunas restricciones pueden ser difíciles de emular en el código Rust.
  • Emule gcc y simplemente vincule a sus documentos : bueno porque muchos programadores ya lo saben y hay muchos ejemplos que se pueden copiar y pegar con pequeñas modificaciones. Malo porque es una extensión no trivial del compilador.
  • Haga otra cosa (como hace D): mucho trabajo que puede o no dar sus frutos. Si se hace bien, esto podría ser muy superior al estilo gcc en términos de ergonomía, mientras que posiblemente se integre más bien con el lenguaje y el compilador que solo una gota opaca (muchos movimientos de la mano aquí, ya que no estoy lo suficientemente familiarizado con los componentes internos del compilador para evaluar esto) .

Finalmente, otra cosa a considerar es el # 1201, que en su diseño actual (creo) depende en gran medida del ensamblaje en línea, o del ensamblaje en línea bien hecho, para el caso.

Personalmente, creo que sería mejor hacer lo que hizo Microsoft en MSVC x64: definir un conjunto (casi) completo de funciones intrínsecas, para cada instrucción ASM, y hacer "ASM en línea" exclusivamente a través de esos intrínsecos. De lo contrario, es muy difícil optimizar el código que rodea al ensamblaje en línea, lo cual es irónico, ya que muchos usos del ensamblaje en línea están pensados ​​para optimizar el rendimiento.

Una ventaja del enfoque intrínseco es que no tiene por qué ser un todo o nada. Puede definir primero los elementos intrínsecos más necesarios y construir el conjunto de forma incremental. Por ejemplo, para cripto, tener _addcarry_u64 , _addcarry_u32 . Tenga en cuenta que el trabajo para hacer los instrínsecos parece que ya se ha hecho bastante a fondo: https://github.com/huonw/llvmint.

Además, sería una buena idea agregar los intrínsecos incluso si finalmente se decidiera admitir asm en línea, ya que son mucho más convenientes de usar (según mi experiencia al usarlos en C y C ++), así que comenzando con los intrínsecos y viendo lo lejos que llegamos parece una cosa sin riesgo de equivocarnos.

Los intrínsecos son buenos, pero asm! puede usarse para algo más que insertar instrucciones.
Por ejemplo, vea la forma en que estoy generando notas ELF en mi caja probe .
https://github.com/cuviper/rust-libprobe/blob/master/src/platform/systemtap.rs

Espero que ese tipo de piratería sea poco común, pero creo que aún es útil respaldarlo.

@briansmith

Asm en línea también es útil para el código que quiere hacer su propia asignación de registro / pila (por ejemplo, funciones desnudas).

@briansmith sí, esas son algunas razones excelentes para usar intrínsecos siempre que sea posible. Pero es bueno tener el ensamblaje en línea como la última escotilla de escape.

@briansmith Tenga en cuenta que asm!() es _una especie de_ un superconjunto de intrínsecos, ya que puede construir el último utilizando el primero. (El argumento común en contra de este razonamiento es que el compilador teóricamente podría optimizar _a través_ intrínsecos, por ejemplo, sacarlos de los bucles, ejecutar CSE en ellos, etc. Sin embargo, es un contrapunto bastante fuerte que cualquiera que escriba un ASM con fines de _optimización_ haría un mejor trabajo en eso que el compilador de todos modos.) Consulte también https://github.com/rust-lang/rust/issues/29722#issuecomment -207628164 y https://github.com/rust-lang/rust/issues/29722# issuecomment -207823543 para los casos en los que el ensamblaje en línea funciona pero los intrínsecos no.

Por otro lado, los elementos intrínsecos dependen críticamente de un "compilador suficientemente inteligente" para lograr _al menos_ el rendimiento que se obtendría con una implementación de ensamblado manual. Mi conocimiento sobre esto está desactualizado, pero a menos que haya habido un progreso significativo, las implementaciones basadas en intrínsecos siguen siendo considerablemente inferiores en muchos, si no en la mayoría, de los casos. Por supuesto, son mucho más convenientes de usar, pero yo diría que a los programadores realmente no les importa mucho _ eso_ cuando están dispuestos a descender al mundo de las instrucciones específicas de la CPU.

Ahora, otra consideración interesante es que los intrínsecos podrían combinarse con código de reserva en arquitecturas donde no son compatibles. Esto le brinda lo mejor de ambos mundos: su código aún es portátil; solo puede emplear algunas operaciones aceleradas por hardware donde el hardware las admita. Por supuesto, esto solo vale la pena para instrucciones muy comunes o si la aplicación tiene una arquitectura de destino obvia. Ahora, la razón por la que menciono esto es que, si bien se podría argumentar que esto puede incluso ser _indeseable_ con los intrínsecos _proporcionados por el compilador_ (ya que probablemente le importaría si realmente obtiene las versiones aceleradas más la complejidad del compilador nunca es bueno) yo Diría que es una historia diferente si los intrínsecos los proporciona una _library_ (y solo se implementan usando asm en línea). De hecho, este es el panorama general que preferiría a pesar de que puedo verme usando intrínsecos más que ensamblajes en línea.

(Considero que los elementos intrínsecos de RFC # 1199 son algo ortogonales a esta discusión, ya que existen principalmente para hacer que SIMD funcione).

@briansmith

De lo contrario, es muy difícil optimizar el código que rodea al ensamblaje en línea, lo cual es irónico, ya que muchos usos del ensamblaje en línea están pensados ​​para optimizar el rendimiento.

No estoy seguro de a qué te refieres aquí. Es cierto que el compilador no puede dividir el conjunto en sus operaciones individuales para realizar una reducción de resistencia o optimizaciones de mirilla en él. Pero en el modelo GCC, al menos, el compilador puede asignar los registros que usa, copiarlo cuando replica rutas de código, eliminarlo si nunca se usa, y así sucesivamente. Si el asm no es volátil, GCC tiene suficiente información para tratarlo como cualquier otra operación opaca como, por ejemplo, fsin . Toda la motivación para el diseño extraño es hacer que el ensamblaje en línea sea algo con lo que el optimizador pueda meterse.

Pero no lo he usado mucho, especialmente no recientemente. Y no tengo experiencia con la interpretación de LLVM de la función. Así que me pregunto qué ha cambiado o qué he entendido mal todo este tiempo.

Discutimos este tema en la semana laboral reciente, ya que la encuesta de @japaric del ecosistema no_std tiene la macro asm! como una de las características más utilizadas. Desafortunadamente, no vimos un camino fácil para estabilizar esta función, pero quería apuntar las notas que teníamos para asegurarnos de no olvidarnos de todo esto.

  • Primero, actualmente no tenemos una gran especificación de la sintaxis aceptada en la macro asm! . En este momento, normalmente termina siendo "mira LLVM" que dice "mira clang" que dice "mira gcc", que no tiene buenos documentos. Al final, esto suele tocar fondo en "lee el ejemplo de otra persona y adáptalo" o "lee el código fuente de LLVM". Para la estabilización, lo mínimo es que necesitamos tener una especificación de la sintaxis y la documentación.

  • En este momento, hasta donde sabemos, LLVM no garantiza la estabilidad. La macro asm! es un enlace directo a lo que LLVM hace en este momento. ¿Significa esto que todavía podemos actualizar LLVM libremente cuando lo deseemos? ¿LLVM garantiza que nunca romperá esta sintaxis? Una manera de aliviar este problema sería tener nuestra propia capa que compila a la sintaxis de LLVM. De esa manera podemos cambiar LLVM cuando queramos y si la implementación del ensamblaje en línea en LLVM cambia, podemos simplemente actualizar nuestra traducción a la sintaxis de LLVM. Para que asm! se estabilice, básicamente necesitamos algún mecanismo para garantizar la estabilidad en Rust.

  • En este momento hay bastantes errores relacionados con el ensamblaje en línea. La etiqueta A-inline-assembly es un buen punto de partida y actualmente está plagada de ICE, segfaults en LLVM, etc. En general, esta característica, tal como se implementó hoy, no parece estar a la altura de las garantías de calidad que otros esperan de un establo. característica en Rust.

  • La estabilización del ensamblaje en línea puede dificultar la implementación de un backend alternativo. Por ejemplo, los backends como miri o cranelift pueden tardar mucho en alcanzar la paridad de características con el backend LLVM, según la implementación. Esto puede significar que hay una porción más pequeña de lo que se puede hacer aquí, pero es algo importante a tener en cuenta al considerar la estabilización del ensamblaje en línea.


A pesar de los problemas enumerados anteriormente, queríamos asegurarnos de al menos tener alguna capacidad para hacer avanzar este problema. Con ese fin, hicimos una lluvia de ideas sobre algunas estrategias de cómo podemos impulsar el ensamblaje en línea hacia la estabilización. El camino principal a seguir sería investigar qué hace el clang. Presumiblemente, clang y C tienen una sintaxis de ensamblaje en línea estable de manera efectiva y es probable que podamos reflejar cualquier cosa que haga clang (especialmente wrt LLVM). Sería genial comprender con mayor profundidad cómo clang implementa el ensamblaje en línea. ¿Clang tiene su propia capa de traducción? ¿Valida algún parámetro de entrada? (etc)

Otra posibilidad para avanzar es ver si hay un ensamblador que podamos sacar del estante de otro lugar que ya sea estable. Algunas ideas aquí fueron nasm o el ensamblador de plan9. El uso del ensamblador de LLVM tiene los mismos problemas sobre garantías de estabilidad que las instrucciones de ensamblaje en línea en el IR. (es una posibilidad, pero necesitamos una garantía de estabilidad antes de usarlo)

Me gustaría señalar que la sintaxis de asm en línea de LLVM es diferente de la utilizada por clang / gcc. Las diferencias incluyen:

  • LLVM usa $0 lugar de %0 .
  • LLVM no admite operandos asm con nombre %[name] .
  • LLVM admite diferentes tipos de restricciones de registro: por ejemplo, "{eax}" lugar de "a" en x86.
  • LLVM admite restricciones de registro explícitas ( "{r11}" ). En C, debe usar en su lugar variables de registro asm para vincular un valor a un registro ( register asm("r11") int x ).
  • Las restricciones LLVM "m" y "=m" están básicamente rotas. Clang los traduce en restricciones de memoria indirectas "*m" y "=*m" y pasa la dirección de la variable a LLVM en lugar de la variable en sí.
  • etc ...

Clang convertirá asm en línea del formato gcc al formato LLVM antes de pasarlo a LLVM. También realiza alguna validación de las restricciones: por ejemplo, asegura que los operandos "i" son constantes en tiempo de compilación,


A la luz de esto, creo que deberíamos implementar la misma traducción y validación que hace clang y admitir la sintaxis gcc inline asm adecuada en lugar de la extraña LLVM.

Hay un excelente video sobre resúmenes con D, MSVC, gcc, LLVM y Rust con diapositivas en línea

Como alguien a quien le encantaría poder usar ASM en línea en Rust estable, y con más experiencia de la que quiero al intentar acceder a algunas de las API de LLVM MC de Rust, algunas reflexiones:

  • ASM en línea es básicamente copiar y pegar un fragmento de código en el archivo .s de salida para ensamblar, después de alguna sustitución de cadenas. También tiene archivos adjuntos de registros de entrada y salida, así como registros golpeados. Es poco probable que este marco básico cambie realmente en LLVM (aunque algunos de los detalles pueden variar ligeramente), y sospecho que esta es una representación bastante independiente del marco.

  • No es difícil construir una traducción de una especificación orientada a Rust a un formato IR orientado a LLVM. Y podría ser aconsejable: la sintaxis rust {} para formatear no interfiere con el lenguaje ensamblador, a diferencia de la notación $ y GCC % LLVM.

  • LLVM hace un trabajo sorprendentemente malo en la práctica al identificar realmente qué registros se estropean, particularmente en instrucciones no generadas por LLVM. Esto significa que es bastante necesario que el usuario especifique manualmente qué registros se eliminan.

  • Es probable que intentar analizar el ensamblado usted mismo sea una pesadilla. La API LLVM-C no expone la lógica de MCAsmParser, y estas clases son bastante molestas para trabajar con bindgen (lo he hecho).

  • Para la portabilidad a otros backends, siempre que mantenga el ensamblaje en línea principalmente en el nivel de "copiar y pegar esta cadena con un poco de asignación de registros y sustitución de cadenas", no debería inhibir tanto los backends. Eliminar la constante entera y las restricciones de memoria y mantener solo las restricciones del banco de registros no debería plantear ningún problema.

He estado jugando un poco para ver qué se puede hacer con las macros de procedimiento. He escrito uno que convierte el ensamblaje en línea de estilo GCC en estilo óxido https://github.com/parched/gcc-asm-rs. También comencé a trabajar en uno que usa un DSL donde el usuario no tiene que entender las restricciones y todas se manejan automáticamente.

Así que llegué a la conclusión de que creo que el óxido debería estabilizar los bloques de construcción desnudos, luego la comunidad puede iterar fuera del árbol con macros para encontrar las mejores soluciones. Básicamente, solo estabilice el estilo llvm que tenemos ahora con solo restricciones "r" e "i" y tal vez "m", y sin tachaduras. Otras restricciones y golpes se pueden estabilizar más tarde con sus propias cosas de tipo mini rfc.

Personalmente, estoy empezando a sentir que estabilizar esta función es el tipo de tarea masiva que nunca se completará a menos que de alguna manera alguien contrate a un contratista experto a tiempo completo para impulsar esto durante todo un año. Quiero creer que la sugerencia de @parched de estabilizar asm! poco a poco hará que esto sea manejable. Espero que alguien lo recoja y corra con él. Pero si no es así, entonces debemos dejar de intentar buscar la solución satisfactoria que nunca llegará y buscar la solución insatisfactoria que: estabilizará asm! tal cual, verrugas, ICE, errores y todo. , con advertencias brillantes y audaces en los documentos que anuncian el jank y la no portabilidad, y con la intención de desaprobar algún día si una implementación satisfactoria debería descender milagrosamente, enviada por Dios, sobre su anfitrión celestial. IOW, deberíamos hacer exactamente lo que hicimos por macro_rules! (y por supuesto, al igual que por macro_rules! , podemos tener un breve período de frenético curita y fugas a prueba de futuro). Estoy triste por las ramificaciones de los backends alternativos, pero es vergonzoso que un lenguaje de sistemas relegue el ensamblado en línea a tal limbo, y no podemos permitir que la posibilidad hipotética de múltiples backends continúe obstruyendo la existencia de un backend realmente utilizable. ¡Te lo ruego, demuéstrame que estoy equivocado!

Es vergonzoso que un lenguaje de sistemas relegue el ensamblado en línea a un limbo

Como punto de datos, resulta que estoy trabajando en una caja en este momento que depende de gcc con el único propósito de emitir algunos asm con Rust estable: https://github.com/main--/unwind- rs / blob / 266e0f26b6423f4a2b8a8c72442b319b5c33b658 / src / wind_helper.c


Si bien ciertamente tiene sus ventajas, soy un poco cauteloso con el enfoque de "estabilizar los bloques de construcción y dejar el resto para proc-macros". Básicamente, subcontrata el proceso de diseño, RFC e implementación a quien quiera hacer el trabajo, potencialmente a nadie. Por supuesto, tener garantías de estabilidad / calidad más débiles es todo el punto (la compensación es que tener algo imperfecto ya es mucho mejor que no tener nada), lo entiendo.

Al menos los bloques de construcción deben estar bien diseñados y, en mi opinión, "expr" : foo : bar : baz definitivamente no lo es. No recuerdo haber recibido el pedido correctamente en el primer intento, siempre tengo que buscarlo. "Categorías mágicas separadas por dos puntos en las que especificas cadenas constantes con caracteres mágicos que terminan haciendo cosas mágicas con los nombres de las variables que también combinas de alguna manera" es simplemente malo.

Una idea, …

Hoy en día, ya existe un proyecto, llamado dynasm, que puede ayudarlo a generar código ensamblador con un complemento que se usa para preprocesar el ensamblado con un tipo de código x64.

Este proyecto no responde al problema del ensamblaje en línea, pero ciertamente puede ayudar, si rustc proporcionara una forma de asignar variables a los registros y aceptara insertar un conjunto de bytes en el código, dicho proyecto también podría usarse para completar configurar estos conjuntos de bytes.

De esta manera, la única parte de estandarización necesaria desde el punto de vista de rustc, es la capacidad de inyectar cualquier secuencia de bytes en el código generado y hacer cumplir asignaciones de registro específicas. Esto elimina todas las opciones para sabores de idiomas específicos.

Incluso sin dynasm, esto también se puede usar como una forma de hacer macros para las instrucciones cpuid / rtdsc, que simplemente se traducirían en la secuencia de bytes sin procesar.

Supongo que la siguiente pregunta podría ser si queremos agregar propiedades / restricciones adicionales a las secuencias de bytes.

[EDITAR: No creo que nada de lo que dije en este comentario sea correcto].

Si queremos continuar utilizando el ensamblador integrado de LLVM (supongo que esto es más rápido que generar un ensamblador externo), entonces la estabilización significa estabilizar exactamente las expresiones de ensamblaje en línea de LLVM y el soporte del ensamblador integrado, y compensar los cambios en esos, en caso de que ocurra alguno.

Si estamos dispuestos a generar un ensamblador externo, entonces podemos usar cualquier sintaxis que queramos, pero entonces renunciamos a las ventajas del ensamblador integrado y estamos expuestos a cambios en cualquier ensamblador externo que estemos llamando.

Creo que sería extraño estabilizarse en el formato LLVM cuando ni siquiera Clang hace eso. Es de suponer que utiliza internamente el soporte de LLVM, pero presenta una interfaz más parecida a GCC.

Estoy 100% de acuerdo con decir "Rust es compatible exactamente con lo que admite Clang" y llamarlo un día, especialmente porque la postura de AFAIK Clang es "Clang admite exactamente lo que admite GCC". Si alguna vez tenemos una especificación de Rust real, podemos suavizar el lenguaje a "el ensamblaje en línea está definido por la implementación". La precedencia y la estandarización de facto son herramientas poderosas. Si podemos reutilizar el propio código de Clang para traducir la sintaxis de GCC a LLVM, mucho mejor. Las preocupaciones sobre el backend alternativo no desaparecen, pero teóricamente una interfaz de Rust para GCC no estaría muy molesta. Menos para que diseñemos, menos para que pasemos en bicicleta sin cesar, menos para que enseñemos, menos para que mantengamos.

Si estabilizamos algo definido en términos de lo que admite clang, entonces deberíamos llamarlo clang_asm! . El nombre asm! debe reservarse para algo que ha sido diseñado a través de un proceso RFC completo, como otras características importantes de Rust. #bikeshed

Hay algunas cosas que me gustaría ver en el ensamblaje en línea de Rust:

  • El patrón de plantilla con sustituciones es feo. Siempre estoy saltando de un lado a otro entre el texto de ensamblaje y la lista de restricciones. La brevedad anima a las personas a utilizar parámetros posicionales, lo que empeora la legibilidad. Los nombres simbólicos a menudo significan que tiene el mismo nombre repetido tres veces: en la plantilla, nombrando el operando y en la expresión que está vinculada al operando. Las diapositivas mencionadas en el comentario de Alex muestran que D y MSVC le permiten simplemente hacer referencia a variables en el código, lo que parece mucho mejor.

  • Las restricciones son difíciles de entender y (en su mayoría) redundantes con el código ensamblador. Si Rust tuviera un ensamblador integrado con un modelo suficientemente detallado de las instrucciones, podría inferir las restricciones en los operandos, eliminando una fuente de error y confusión. Si el programador necesita una codificación específica de la instrucción, entonces necesitaría proporcionar una restricción explícita, pero esto normalmente no sería necesario.

Norman Ramsey y Mary Fernández escribieron algunos artículos sobre el kit de herramientas de código de máquina de Nueva Jersey cuando tenían excelentes ideas para describir pares de lenguaje ensamblador / máquina de una manera compacta. Abordan las codificaciones de instrucciones (Pentium Pro-era) iA-32; no se limita en absoluto a las NIA RISC ordenadas.

Me gustaría reiterar nuevamente las conclusiones de la semana laboral más reciente :

  • Hoy, hasta donde sabemos, básicamente no hay documentación para esta función. Esto incluye los componentes internos de LLVM y todo.
  • Hasta donde sabemos, no tenemos garantía de estabilidad por parte de LLVM. Por lo que sabemos, la implementación del ensamblaje en línea en LLVM podría cambiar cualquier día.
  • Esta es, actualmente, una característica con muchos errores en rustc. Está repleto de (en tiempo de compilación) segfaults, ICE y errores extraños de LLVM.
  • Sin una especificación, es casi imposible imaginar un backend alternativo para esto.

Para mí esta es la definición de "si estabilizamos esto ahora te garantizamos que lo lamentaremos en el futuro", y no solo "lo lamentamos" sino que parece muy probable que "cause serios problemas para implementar cualquier nuevo sistema".

Como mínimo, creo firmemente que no se puede comprometer la bala (2) (también conocida como la definición de estable en "canal estable"). Las otras viñetas estarían bastante tristes en renunciar, ya que erosiona la calidad esperada del compilador de Rust, que actualmente es bastante alta.

@jcranmer escribió:

LLVM hace un trabajo sorprendentemente malo en la práctica al identificar realmente qué registros se estropean, particularmente en instrucciones no generadas por LLVM. Esto significa que es bastante necesario que el usuario especifique manualmente qué registros se eliminan.

Creo que, en la práctica, sería bastante difícil inferir listas de golpes. El hecho de que un fragmento de lenguaje de máquina use un registro no significa que lo golpee; quizás lo salve y lo restaure. Los enfoques conservadores podrían disuadir al generador de código de usar registros que estarían bien de usar.

@alexcrichton escribió:

Hasta donde sabemos, no tenemos garantía de estabilidad por parte de LLVM. Por lo que sabemos, la implementación del ensamblaje en línea en LLVM podría cambiar cualquier día.

Los documentos de LLVM garantizan que "las versiones más recientes pueden ignorar características de versiones anteriores, pero no pueden compilarlas incorrectamente". (con respecto a la compatibilidad con infrarrojos). Eso limita más bien cuánto pueden cambiar el ensamblaje en línea y, como dije anteriormente, en realidad no hay ningún reemplazo viable a nivel de LLVM que cambie radicalmente la semántica de la situación actual (a diferencia de, digamos, los problemas en curso sobre veneno y undef). Por lo tanto, decir que su posible inestabilidad excluye su uso como base para un bloque Rust asm! es algo deshonesto. Eso no quiere decir que haya otros problemas con él (documentación deficiente, aunque eso ha mejorado; problemas de restricciones; diagnósticos deficientes; y errores en escenarios menos comunes son los que me vienen a la mente).

Mi mayor preocupación al leer el hilo es que hacemos que lo perfecto sea enemigo de lo bueno. En particular, me preocupa que la búsqueda de algún intermediario DSL mágico lleve algunos años para tratar de encontrar una forma utilizable para el ensamblaje en línea a medida que las personas descubran que la integración de analizadores ASM y el intento de que funcionen con LLVM causan más problemas en casos de borde.

¿LLVM realmente garantiza que nunca compilarán incorrectamente una función cuyo comportamiento nunca han especificado? ¿Cómo decidirían siquiera si un cambio fue un error de compilación o no? Pude verlo en las otras partes del IR, pero parece mucho esperar.

Creo que sería extraño estabilizarse en el formato LLVM cuando ni siquiera Clang hace eso.

Clang no hace eso porque tiene como objetivo poder compilar código que fue escrito para GCC. rustc no tiene ese objetivo. El formato GCC no es muy ergonómico, así que en última instancia creo que no queremos eso, pero no estoy seguro de si sería mejor hacerlo por ahora. Hay una gran cantidad de código (nocturno) que usa el formato actual de Rust que se rompería si cambiamos al estilo GCC, por lo que probablemente solo valga la pena cambiarlo si podemos encontrar algo notablemente mejor.

Al menos los bloques de construcción deben estar bien diseñados y, en mi opinión, "expr" : foo : bar : baz definitivamente no lo es.

Acordado. Como mínimo, prefiero el formato LLVM sin formato, donde las restricciones y los golpes están todos en una lista. Actualmente hay una redundancia que tiene que especificar el prefijo "=" y ponerlo en la lista de salida. También creo que LLVM lo trata más como una llamada de función donde las salidas son el resultado de la expresión, AFAIK, la implementación actual de asm! es la única parte de rust que tiene parámetros "out".

LLVM hace un trabajo sorprendentemente malo en la práctica de identificar realmente qué registros se golpean

AFAIK LLVM no intenta hacer esto, ya que la razón principal para el ensamblaje en línea es incluir algún código que LLVM no comprende. Solo registra la asignación y la sustitución de plantillas sin mirar el ensamblaje real. (Obviamente, analiza el ensamblado real en algún momento para generar el código de máquina, pero creo que eso sucede más tarde)

Si estamos dispuestos a generar un ensamblador externo

No estoy seguro de que alguna vez pueda haber una alternativa al uso del ensamblador en línea integrado porque de alguna manera tendría que hacer que LLVM asigne registros para él. Sin embargo, para el ensamblaje global, sería viable un ensamblador externo.

Con respecto a los cambios importantes en el ensamblador en línea LLVM, estamos en el mismo barco que Clang. Es decir, si hacen algunos cambios, solo tenemos que solucionarlos cuando sucedan.

Si estabilizamos algo definido en términos de lo que soporte clang, entonces deberíamos llamarlo clang_asm !. ¡El asm! El nombre debe reservarse para algo que ha sido diseñado a través de un proceso RFC completo, como otras características importantes de Rust. #bikeshed

Estoy por ello totalmente. +1

Hay una gran cantidad de código (nocturno) que usa el formato actual de Rust que se rompería si cambiamos al estilo GCC, por lo que probablemente solo valga la pena cambiarlo si podemos encontrar algo notablemente mejor.

@parched Siguiendo la sugerencia de @jimblandy citada anteriormente, cualquiera que use asm! podrá seguir utilizándolo.

Hoy, hasta donde sabemos, básicamente no hay documentación para esta función. Esto incluye los componentes internos de LLVM y todo.

Si la sintaxis de ensamblado de GCC realmente no se especifica o documenta después de 30 años, entonces parece seguro asumir que producir un sublenguaje de ensamblaje documentado es una tarea que es tan difícil que está más allá de la capacidad de Rust de lograr dados nuestros recursos limitados, o que a las personas que quieren usar el ensamblaje simplemente no les importa.

Hasta donde sabemos, no tenemos garantía de estabilidad por parte de LLVM. Por lo que sabemos, la implementación del ensamblaje en línea en LLVM podría cambiar cualquier día.

Parece poco probable que la implementación de GCC / Clang del ensamblado en línea cambie alguna vez, ya que eso rompería todo el código C escrito desde los años 90.

Sin una especificación, es casi imposible imaginar un backend alternativo para esto.

A riesgo de ser insensible, la perspectiva de backends alternativos es discutible si Rust como lenguaje no sobrevive debido a su vergonzosa incapacidad para entrar en ensamblaje. Nightly no es suficiente, a menos que uno quiera respaldar tácitamente la idea de que Nightly es Rust, que hace más para socavar la garantía de estabilidad de Rust que la perspectiva de cambios LLVM.

Las otras viñetas estarían bastante tristes en renunciar, ya que erosiona la calidad esperada del compilador de Rust, que actualmente es bastante alta.

No miento cuando digo que todos los días estoy agradecido por la actitud de los desarrolladores de Rust y el enorme estándar de calidad al que se aferran (de hecho, a veces deseo que todos ustedes frenan para que puedan mantener esa calidad sin quemarse como lo hizo Brian). Sin embargo, hablando como alguien que estaba aquí cuando luqmana agregó la macro asm! hace cuatro años , y que no ha observado ningún progreso desde entonces para estabilizarlo, y que está triste de que la criptografía en Rust todavía sea imposible y que SIMD en Rust ni siquiera tiene una solución mientras la interfaz multiplataforma se está determinando lentamente, me siento abatido. Si parezco enfático aquí es porque veo este tema como existencial para la supervivencia del proyecto. Puede que no sea una crisis en este momento, pero tomará tiempo estabilizar algo en absoluto, y no tenemos los años que se necesitarán para diseñar e implementar un dialecto de ensamblaje de clase mundial desde cero (probado por el hecho de que no hemos avanzado en este sentido en los últimos cuatro años). Rust necesita un ensamblaje en línea estable en algún momento de 2018. Necesitamos el estado de la técnica para lograrlo. La situación macro_rules! reconoció que a veces peor es mejor. Una vez más, le ruego a alguien que demuestre que estoy equivocado.

FWIW y llegando tarde a la fiesta me gusta lo que propuso @florob 's the cologne talk . Para aquellos que no lo han visto, esta es la esencia:

// 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é tal la siguiente estrategia: cambiar el nombre actual asm a llvm_asm (más quizás algunos cambios menores) e indicar que su comportamiento es un detalle de implementación de LLVM, por lo que la garantía de estabilidad de Rust no se extiende por completo? El problema de los diferentes backends debería resolverse más o menos con la funcionalidad similar a target_feature para la compilación condicional dependiendo del backend utilizado. Sí, tal enfoque desdibujará un poco la estabilidad de Rust, pero mantener el montaje en un limbo como este es perjudicial para Rust a su manera.

Publiqué un RFC previo con una propuesta de sintaxis alternativa en el foro interno: https://internals.rust-lang.org/t/pre-rfc-inline-assembly/6443 . Comentarios bienvenidos.

Me parece que lo mejor es definitivamente el enemigo del tipo de ok aquí. Apoyo totalmente la colocación de una macro gcc_asm! o clang_asm! o llvm_asm! (o cualquier subconjunto adecuado de la misma) en estable con sintaxis y semántica compatibles por ahora, mientras se resuelve una mejor solución . No veo que soportar algo así para siempre sea una gran carga de mantenimiento: los sistemas más sofisticados propuestos anteriormente parecen admitir con bastante facilidad simplemente convirtiendo las macros de estilo antiguo en sacarina sintáctica para la nueva.

Tengo un programa binario http: //[email protected]/BartMassey/popcount que requiere ensamblaje en línea para la instrucción x86_64 popcntl . Este ensamblado en línea es lo único que mantiene este código todas las noches. El código se derivó de un programa en C de 12 años.

Ahora mismo, mi montaje está condicionado a

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

y luego obtiene la información cpuid para ver si popcnt está presente. Sería bueno tener algo en Rust similar a la reciente biblioteca cpu_features de Google https://opensource.googleblog.com/2018/02/cpu-features-library.html en Rust, pero c'est la vie.

Debido a que este es un programa de demostración tanto como cualquier otra cosa, me gustaría mantener el ensamblaje en línea. Para programas reales, el count_ones() intrínseco sería suficiente, excepto que conseguir que use popcntl requiere pasar "-C target-cpu = native" a Cargo, probablemente a través de RUSTFLAGS (vea el número 1137 y varios problemas relacionados) ya que distribuir un .cargo/config con mi fuente no parece una gran idea, lo que significa que ahora mismo tengo un Makefile que llama a Cargo.

En resumen, sería bueno si uno pudiera usar la elegante instrucción popcount de Intel y de otros en aplicaciones reales, pero parece más difícil de lo que debería ser. Los intrínsecos no son del todo la respuesta. El actual asm! es una buena respuesta si estuviera disponible en estable. Sería genial tener una mejor sintaxis y semántica para el ensamblaje en línea, pero realmente no la necesito. Sería genial poder especificar target-cpu=native directamente en Cargo.toml , pero realmente no resolvería mi problema.

Lo siento, divagando. Solo pensé en compartir por qué me preocupo por esto.

@BartMassey No entiendo, ¿por qué necesitas tan desesperadamente compilar en popcnt? La única razón por la que puedo ver es el rendimiento y, en mi opinión, definitivamente debería usar count_ones () en ese caso. Lo que está buscando no es asm en línea sino target_feature (rust-lang / rfcs # 2045) para que pueda decirle al compilador que puede emitir popcnt.

@BartMassey , ni siquiera necesita usar ensamblaje en línea para esto, solo use coresimd cfg_feature_enabled!("popcnt") para consultar si la CPU en la que se ejecuta su binario admite la instrucción popcnt (lo hará resuelva esto en tiempo de compilación si es posible hacerlo).

coresimd también proporciona un popcnt intrínseco que está garantizado para usar la instrucción popcnt .

@gnzlbg

coresimd también proporciona un intrínseco popcnt que está garantizado para usar la instrucción popcnt.

Está un poco fuera de tema, pero esta afirmación no es estrictamente cierta. _popcnt64 usa leading_zeros debajo del capó, por lo tanto, si el usuario de la caja no habilitará la función popcnt y el autor de la caja se olvidará de usar #![cfg(target_feature = "popcnt")] esta característica intrínseca se obtendrá compilado en ensamblado ineficaz y no hay salvaguardas contra él.

por lo tanto, si el usuario de la caja no habilitará la función popcnt

Esto es incorrecto ya que el intrínseco usa el atributo #[target_feature(enable = "popcnt")] para habilitar la función popcnt para el intrínseco incondicionalmente, independientemente de lo que el usuario de la caja habilite o deshabilite. Además, el atributo assert_instr(popcnt) asegura que lo intrínseco se desarme en popcnt en todas las plataformas x86 compatibles con Rust.

Si uno está usando Rust en una plataforma x86 que Rust no admite actualmente, entonces depende de quien esté transfiriendo core asegurarse de que estos elementos intrínsecos generen popcnt en ese objetivo.


EDITAR: @newpavlov

por lo tanto, si el usuario de la caja no habilita la función popcnt y el autor de la caja se olvidará de usar #! [cfg (target_feature = "popcnt")], este elemento intrínseco se compilará en un ensamblaje ineficaz y no hay salvaguardas contra él.

Al menos en el ejemplo que mencionaste en el número, hacer esto introduce un comportamiento indefinido en el programa y, en este caso, el compilador puede hacer cualquier cosa. El mal codegen que funciona es uno de los múltiples resultados que se pueden obtener.

En primer lugar, disculpas por haber descarrilado la discusión. Solo quería reiterar mi punto principal, que era "Apoyo totalmente la colocación de una macro gcc_asm! O clang_asm! O llvm_asm! (O cualquier subconjunto adecuado de las mismas) en estable con sintaxis y semántica compatibles por ahora, mientras se resuelve una mejor solución. "

El punto del ensamblaje en línea es que este es un punto de referencia / demostración de conteo emergente. Quiero una verdadera instrucción popcntl garantizada cuando sea posible, tanto como línea de base como para ilustrar cómo usar el ensamblaje en línea. También quiero garantizar que count_ones() use una instrucción popcount cuando sea posible para que Rustc no se vea terrible en comparación con GCC y Clang.

Gracias por señalar target_feature=popcnt . Pensaré en cómo usarlo aquí. Creo que quiero comparar count_ones() independientemente de para qué CPU esté compilando el usuario e independientemente de si tiene una instrucción popcount. Solo quiero asegurarme de que si la CPU de destino tiene popcount count_ones() use.

Las cajas stdsimd / coresimd ven bien y probablemente deberían estar habilitadas para estos puntos de referencia. ¡Gracias! Para esta aplicación, preferiría usar la menor cantidad posible de funciones fuera del lenguaje estándar (ya me siento culpable por lazy_static ). Sin embargo, estas instalaciones parecen demasiado buenas para ignorarlas y parece que están en camino de convertirse en "oficiales".

Hay una idea flotando alrededor de @nbp donde podría haber alguna implementación que vaya desde alguna representación del código hasta los bytes de la máquina (¿podría ser una caja proc-macro o algo así?) Y luego esos bytes se incluyen directamente en la ubicación particular en el código.

Empalmar bytes de código arbitrario en lugares arbitrarios dentro de una función parece un problema mucho más fácil de resolver (aunque la capacidad de especificar entradas, salidas y sus restricciones tan ell como clobbers aún sería necesaria).

cc @eddyb

@nagisa es un poco más que un simple trozo de código de máquina, sin embargo, también debe tener cuidado con los registros de entrada, salida y clobber. Si el fragmento de ASM dice que quiere una determinada variable en% rax y que golpeará a% esi, debe asegurarse de que el código circundante funcione bien. Además, si el desarrollador permite que el compilador asigne los registros, probablemente querrá optimizar la asignación para evitar derramar y mover valores.

@simias , de hecho, tendrá que especificar cómo se asocian las variables a registros específicos, y qué registros se golpean, pero todo esto es más pequeño que estandarizar cualquier lenguaje ensamblador o cualquier lenguaje ensamblador LLVM.

Estandarizar en secuencias de bytes es probablemente la forma más fácil de avanzar moviendo el tipo de ensamblaje a un controlador / proc-macro.

Un problema de tener bytes textuales en lugar de un ensamblaje en línea adecuado es que el compilador no tendría la opción de hacer un cambio de nombre alfa de registro, lo que tampoco espero que las personas que escriben ensamblados en línea estén esperando.

Pero, ¿cómo funcionaría eso con la asignación de registros si quiero dejar que el compilador se encargue de eso? Por ejemplo, usando la sintaxis (atroz) de GCC:

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

En algo como esto, dejo que el compilador asigne los registros, esperando que me dé lo que sea más conveniente y eficiente. Por ejemplo, en x86 64 si five_time_x es el valor de retorno, entonces el compilador podría asignar eax y si x es un parámetro de función, es posible que ya esté disponible en algún registro. Por supuesto, el compilador solo sabe exactamente cómo asignará los registros bastante tarde en la secuencia de compilación (especialmente si no es tan trivial como simplemente parámetros de función y valores de retorno).

¿Su solución propuesta funcionaría con algo así?

@nbp Debo decir que esta propuesta me confunde un poco.
En primer lugar, la estandarización del lenguaje ensamblador nunca fue algo que quisiéramos lograr con el ensamblaje en línea. Al menos para mí, la premisa siempre fue que se aceptaría el lenguaje ensamblador utilizado por el ensamblador del sistema.
El problema es no analizar / ensamblar el ensamblaje, podemos pasarlo a LLVM fácilmente.
El problema es llenar el ensamblaje con plantilla (o darle a LLVM la información requerida para hacerlo) y especificar entradas, salidas y clobbers.
El problema posterior no se resuelve realmente con su propuesta. Sin embargo, se alivia, porque no admitiría / no podría admitir clases de registros (sobre lo que
En el punto en el que las restricciones se simplifican hasta ese punto, en realidad es igual de fácil admitir un ensamblaje en línea "real". El primer argumento es una cadena que contiene un ensamblado (sin plantilla), los otros argumentos son las restricciones. Esto se asigna de alguna manera fácilmente a las expresiones del ensamblador en línea de LLVM.
Insertar bytes sin procesar, por otro lado, no es hasta donde yo sé (o puedo decir en el Manual de referencia de LLVM IR) compatible con LLVM. Así que básicamente estaríamos ampliando LLVM IR y reimplementando una característica (ensamblaje del sistema de ensamblaje) que ya está presente en LLVM usando cajas separadas.

@nbp

de hecho, tendrá que especificar cómo se asocian las variables a registros específicos y qué registros se golpean, pero todo esto es más pequeño que estandarizar cualquier lenguaje ensamblador o cualquier lenguaje ensamblador LLVM.

Entonces, ¿cómo se haría eso? Tengo una secuencia de bytes con registros codificados que básicamente significa que los registros de entrada / salida, clobbers, etc.están codificados dentro de esta secuencia de bytes.

Ahora inyecto estos bytes en algún lugar de mi binario rust. ¿Cómo le digo a rustc qué registros son de entrada / salida, qué registros se golpearon, etc.? ¿Cómo es este un problema menor de resolver que estabilizar el ensamblaje en línea? Me parece que esto es exactamente lo que hace el ensamblaje en línea, solo que quizás un poco más difícil porque ahora uno necesita especificar las entradas / salidas dos veces, en el ensamblaje escrito, y de cualquier manera que le pasemos esta información a rustc. Además, rustc no lo tendría fácil para validar esto, porque para eso necesitaría poder analizar la secuencia de bytes en ensamblado y luego inspeccionar eso. ¿Qué me estoy perdiendo?

@simias

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

Esto no sería posible, ya que el crudo de bytes no permite el cambio de nombre alfa de los registros, y los registros tendrían que ser reforzados por la secuencia de código anterior.

@Florob

Al menos para mí, la premisa siempre fue que se aceptaría el lenguaje ensamblador utilizado por el ensamblador del sistema.

Tengo entendido que confiar en el ensamblador del sistema no es algo en lo que queramos confiar, sino más bien un defecto aceptado como parte del ensamblador. macro. ¡También confiando en asm! ser la sintaxis LLVM sería doloroso para el desarrollo de backend adicional.

@gnzlbg

Entonces, ¿cómo se haría eso? Tengo una secuencia de bytes con registros codificados que básicamente significa que los registros de entrada / salida, clobbers, etc.están codificados dentro de esta secuencia de bytes.

La idea sería tener una lista de entradas, salidas y registros machacados, donde las entradas serían una tupla del nombre de registro asociado con una referencia o copia (mutable), el registro machacado sería una lista de nombres de registro, y la salida sería una lista de registros de salida que formaría una tupla de registros con nombre a los que están asociados los tipos.

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

Esta secuencia de código puede ser el resultado de alguna macro de procedimiento del compilador, que podría verse así:

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

Estas secuencias, no podrán incrustar directamente ningún símbolo o dirección y tendrían que ser computadas y dadas como registros. Estoy seguro de que podemos averiguar cómo agregar la capacidad de insertar algunas direcciones de símbolo dentro de la secuencia de bytes más adelante.

La ventaja de este enfoque es que solo se debe estandarizar la lista de registros y restricciones, y esto es algo que fácilmente sería compatible con cualquier backend futuro.

@nbp

Tengo entendido que confiar en el ensamblador del sistema no es algo en lo que queramos confiar, sino más bien un defecto aceptado como parte del ensamblador. macro. ¡También confiando en asm! ser la sintaxis LLVM sería doloroso para el desarrollo de backend adicional.

¿No creo que sea una evaluación precisa? Con la excepción menor de las dos sintaxis diferentes para el ensamblaje x86, la sintaxis del ensamblador es en gran parte estándar y portátil. El único problema con el ensamblador del sistema podría ser que carece de instrucciones más nuevas, pero esa es una situación de nicho que no vale la pena optimizar.

El problema real es el pegamento en la asignación de registros. Pero, en lo que respecta a la cadena de ensamblaje real en sí, esto simplemente significa que alguien tiene que hacer algunas cosas de sustitución de cadenas y tal vez algo de análisis, y este tipo de sustitución debería estar disponible de manera trivial para cualquier backend putativo.

Estoy de acuerdo en que la sintaxis de LLVM (o gcc) para estas cosas es una porquería, pero pasar a bytes precompilados significa que cualquier caja de ensamblaje ahora necesita instalar un ensamblador completo y posiblemente un asignador de registros completo (o hacer que los programadores asignen registros manualmente), o intentar para utilizar el ensamblador del sistema. En ese punto, no parece que realmente esté agregando mucho valor.

@jcranmer

... pero pasar a bytes precompilados significa que cualquier caja de ensamblaje ahora necesita instalar un ensamblador completo y posiblemente un asignador de registros completo (o hacer que los programadores asignen registros manualmente), o intentar usar el ensamblador del sistema

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

Esta caja usa una macro manejada por un complemento para ensamblar el código ensamblador y generar vectores de código ensamblador sin procesar para concatenar en tiempo de ejecución.

@nbp tal vez mis casos de uso sean peculiares, pero la falta de cambio de nombre de registros y dejar que el compilador asigne registros por mí sería un poco decisivo porque significa que debo tener mucha suerte con mi elección de registros y pasar a " presione a la derecha "o el compilador tendrá que emitir un código no óptimo para mezclar los registros para que coincidan con mis convenciones arbitrarias.

Si el blob de ensamblaje no se integra bien con el ensamblado circundante emitido por el compilador, también podría factorizar el stub de ASM en un método de estilo C externo en un archivo de ensamblaje .s independiente, ya que las llamadas a funciones tienen el mismo tipo de registro -restricciones de asignación. Esto ya funciona hoy, aunque supongo que tenerlo integrado en rustc podría simplificar el sistema de compilación en comparación con tener un archivo de ensamblaje independiente. Supongo que lo que estoy diciendo es que, en mi opinión, su propuesta no nos lleva muy lejos en comparación con la situación actual.

¿Y qué pasa si el código ASM llama a símbolos externos que serían resueltos por el enlazador? Debe transmitir esa información, ya que no es posible resolverlos hasta el final del proceso de compilación. Tendría que pasar esa referencia junto con su matriz de bytes y dejar que el enlazador los resuelva mucho más tarde.

@jcranmer

Con la excepción menor de las dos sintaxis diferentes para el ensamblaje x86, la sintaxis del ensamblador es en gran parte estándar y portátil.

No estoy seguro de entender lo que quiere decir con eso, obviamente la sintaxis de ASM no es portátil entre arquitecturas. E incluso dentro de la misma arquitectura, a menudo hay variaciones y opciones que cambian la forma en que se ensambla el lenguaje.

Puedo dar MIPS como ejemplo, hay dos indicadores de configuración importantes que modifican el comportamiento del ensamblador: at y reorder . at dice si el ensamblador puede usar implícitamente el registro AT (ensamblador temporal) al ensamblar ciertas pseudoinstrucciones. El código que usa explícitamente AT para almacenar datos debe ensamblarse con at o se romperá.

reorder define si el codificador maneja manualmente las ranuras de retardo de bifurcación o si confía en que el ensamblador se ocupará de ellas. Ensamblar código con la configuración reorder incorrecta generará casi con certeza un código de máquina falso. Cuando escribe el ensamblaje de MIPS, debe conocer el modo actual en todo momento si contiene alguna instrucción de bifurcación. Por ejemplo, es imposible saber el significado de esta lista de MIPS si no sabe si reorder está habilitado:

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

El ensamblaje ARM de 32 bits tiene las variaciones Thumb / ARM, es importante saber a qué conjunto de instrucciones se dirige (y puede cambiar sobre la marcha en las llamadas de función). La mezcla de ambos juegos debe hacerse con mucho cuidado. El código ARM también suele cargar grandes valores inmediatos utilizando una carga implícita relativa a la PC, si preensambla su código, tendrá que tener cuidado con la forma en que pasa estos valores, ya que deben permanecer cerca, pero no son instrucciones reales con una ubicación bien definida. Estoy hablando de pseudoinstrucciones como:

   ldr   r2, =0x89abcdef

MIPS, por otro lado, tiende a dividir el valor inmediato en dos valores de 16 bits y usa un combo lui / ori o lui / andi. Por lo general, está oculto detrás de las pseudoinstrucciones li / la , pero si está escribiendo código con noreorder y no quiere desperdiciar la ranura de retardo, a veces debe manejar a mano, lo que da como resultado un código de aspecto divertido:

.set noreorder

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

.data

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

Las construcciones %hi y %lo son una forma de decirle al ensamblaje que genere una referencia a los 16 bits altos y bajos del símbolo hello respectivamente.

Algunos códigos necesitan restricciones de alineación muy peculiares (comunes cuando se trata de código de invalidación de caché, por ejemplo, debe asegurarse de no llevar una sierra a la rama en la que está sentado). Y está el problema de manejar símbolos externos que no se pueden resolver en este punto del proceso de compilación, como mencioné anteriormente.

Estoy seguro de que podría encontrar peculiaridades para un montón de otras arquitecturas con las que estoy menos familiarizado. Por estas razones, no estoy seguro de ser muy optimista para el enfoque macro / DSL. Entiendo que tener un literal de cadena opaco aleatorio en el medio del código no es muy elegante, pero realmente no veo qué nos daría la integración de la sintaxis ASM completa en rust de una forma u otra, excepto dolores de cabeza adicionales al agregar soporte para una nueva arquitectura.

Escribir un ensamblador es algo que puede parecer trivial de un vistazo, pero puede resultar muy complicado si quieres soportar todas las campanas, silbidos y peculiaridades de todas las arquitecturas que existen.

Por otro lado, tener una buena manera de especificar enlaces y golpes sería extremadamente valioso (en comparación con la sintaxis perfectible de gcc).

Hola tios,

Perdón por molestarte, solo quería dejar caer mis dos centavos, porque soy solo un usuario, y muy tímido / callado de hecho, oh, y un recién llegado, acabo de aterrizar recientemente en Rust, pero ya lo soy. enamorado de ella.

Pero esto de la asamblea es una locura, quiero decir, es una conversación de tres años, con un montón de ideas y quejas, pero nada que parezca un consenso mínimo. Tres años y no un RFC, parece un final de muerte. Estoy desarrollando una humilde biblioteca de matemáticas (que con suerte se materializará en dos o tres cajas), y para mí (y sospecho que para cualquier otro compañero interesado en escribir ensamblaje en óxido), lo más importante es poder realmente ¡hazlo! con una mínima garantía de que no todo va a cambiar al día siguiente (eso es lo que me hace sentir el canal inestable, y especialmente esta conversación).

Entiendo que todos aquí quieren la mejor solución, y tal vez algún día alguien salga con esa, pero por hoy creo que la macro actual está bien (bueno, tal vez un poco restrictivo de alguna manera, pero con suerte nada que no pueda abordarse de forma incremental). Escribir ensamblado es como lo más importante en un lenguaje de sistemas, una característica muy, muy necesaria, y aunque estoy bien confiando en cpp_build hasta que esto se solucione, me temo que si lleva mucho más tiempo se convertirá en una dependencia eterna. No sé por qué, llámalo una idea irracional, pero encuentro que tener que llamar a cpp para llamar al ensamblaje es un poco triste, quiero una solución de óxido puro.

Fwiw Rust no es tan especial aquí, MSVC no tiene asm en línea para x86_64 tampoco. Tienen esa implementación realmente extraña en la que puedes usar variables como operandos, pero eso solo funciona para x86.

@josevalaad ¿Podrías hablar más sobre para qué estás usando el ensamblaje en línea?

Por lo general, solo lo vemos usado en situaciones similares a las del sistema operativo, que generalmente también se atascan todas las noches por otras razones, e incluso entonces apenas usan asm! , por lo que estabilizar asm! no ha sido un prioridad lo suficientemente alta para diseñar y desarrollar algo que pueda sobrevivir adecuadamente fuera de LLVM y complacer a todos.

Además, la mayoría de las cosas se pueden hacer utilizando los elementos intrínsecos de la plataforma expuestos. Se han estabilizado x86 y x86_64 y hay otras plataformas en curso. La expectativa de la mayoría de la gente es que estos logren el 95-99% de las metas. Puede ver mi propia caja jetscii como un ejemplo del uso de algunos de los intrínsecos.

Acabamos de fusionar un PR de jemalloc que usa ensamblado en línea para solucionar errores de generación de código en LLVM: https://github.com/jemalloc/jemalloc/pull/1303 . Alguien usó ensamblaje en línea en este problema (https://github.com/rust-lang/rust/issues/53232#issue-349262078) para solucionar un error de generación de código en Rust (LLVM) que sucedió en la caja jetscii. Ambos sucedieron en las últimas dos semanas, y en ambos casos los usuarios intentaron con intrínsecos pero el compilador falló.

Cuando la generación de código para un compilador de C resulta inaceptable, en el peor de los casos, el usuario puede usar ensamblado en línea y continuar trabajando en C.

Cuando esto sucede en Rust estable, en este momento tenemos que decirle a la gente que use un lenguaje de programación diferente o que espere una cantidad de tiempo indeterminada (a menudo en el orden de años). Eso no es agradable.

@eddyb Bueno, estoy escribiendo una pequeña biblioteca de álgebra matricial. Dentro de esa biblioteca, estoy implementando BLAS, tal vez algunas rutinas LAPACK (aún no están allí) en Rust, porque quería que la biblioteca fuera una implementación de rust pura. No es nada grave todavía, pero de todos modos, quería que el usuario pudiera optar por algo de velocidad y diversión de ASM, especialmente con la operación GEMM, que solía ser imprescindible (la más utilizada, de todos modos, y si sigues el enfoque de BLIS people es todo lo que necesita), al menos en x86 / x86_64. Y esa es la historia completa. Obviamente, también puedo usar el canal nocturno, solo quería presionar un poco en la dirección pragmática de estabilización de la función.

@shepmaster Hay muchos casos de uso para los que los intrínsecos no son suficientes. En la parte superior de mi cabeza de cosas recientes donde pensé "¿por qué, oh, por qué Rust no tiene un conjunto estable?", No hay intrínsecos XACQUIRE / XRELEASE.

El ensamblaje en línea estable es fundamental y no, los elementos intrínsecos no son suficientes.

Mi punto original fue intentar ayudar a alguien a tener la capacidad de escribir código más rápido. No mencionaron que sabían que los intrínsecos estaban disponibles, y eso es todo lo que buscaba compartir. El resto fue información de fondo.

Ni siquiera estoy defendiendo un punto de vista específico, así que no intentes discutir conmigo, no tengo ningún interés en esta carrera. Simplemente repito cuál es el punto de vista actual tal como yo lo entiendo . Participo en un proyecto que requiere ensamblaje en línea que es muy poco probable que tenga elementos intrínsecos en un futuro cercano, por lo que también estoy interesado en una cierta cantidad de ensamblaje en línea estable, pero el ensamblaje nocturno no me molesta indebidamente, ni la invocación de un ensamblador.

Sí, hay casos que requieren ensamblaje por ahora y hay casos que lo necesitarán para siempre, lo dije originalmente (énfasis agregado para mayor claridad):

La expectativa de la mayoría de la gente es que [los elementos intrínsecos] van a lograr el 95-99% de las metas .

En mi opinión, si desea ver un ensamblaje estable, alguien (o un grupo de personas) necesitará obtener un consenso general del equipo de Rust sobre una dirección para comenzar y luego hacer un gran esfuerzo para actualizarla. .

No es nada grave todavía, pero de todos modos, quería que el usuario pudiera optar por algo de velocidad y diversión de ASM, especialmente con la operación GEMM, que solía ser imprescindible (la más utilizada, de todos modos, y si sigues el enfoque de BLIS people es todo lo que necesita), al menos en x86 / x86_64.

Todavía no entiendo qué instrucciones necesita para acceder a las que no puede sin ensamblaje en línea. ¿O es solo una secuencia específica de instrucciones aritméticas?
Si es así, ¿ha comparado una fuente de óxido equivalente con el ensamblaje en línea?

qué instrucciones necesita para acceder a las que no puede sin ensamblaje en línea

Bueno, cuando habla de ensamblaje en matemáticas, básicamente está hablando de usar los registros e instrucciones SIMD como _mm256_mul_pd, _mm256_permute2f128_pd, etc. y operaciones de vectorización donde proceden. La cuestión es que puede adoptar diferentes enfoques para la vectorización y, por lo general, es un poco de prueba y error hasta que obtenga un rendimiento optimizado para el procesador al que se dirige y el uso que tiene en mente. Por lo general, a nivel de biblioteca, primero debe consultar el procesador que inyecta código ASM para conocer el conjunto de instrucciones y registros admitidos, y luego compilar condicionalmente una versión específica de su kernel ASM matemático.

Si es así, ¿ha comparado una fuente de óxido equivalente con el ensamblaje en línea?

En este momento no tengo una prueba específica a la mano, y estoy de vacaciones, por lo que preferiría no involucrarme mucho en ella, pero sí, si me das un par de semanas puedo publicar una comparativa de desempeño. En cualquier caso, solía ser imposible para el compilador producir código lo más rápido posible con un ensamblaje ajustado manualmente. No es posible en C al menos, incluso si usa las técnicas de interpretación clásicas como el desenrollado manual de bucles donde sea necesario, etc., por lo que imagino que no debería ser posible en Rust.

Taylor Cramer sugirió que publique aquí. Perdóname porque no he leído todos los comentarios para ponerme al día con el estado actual de la discusión; esta es solo una voz de apoyo y declaración de nuestra situación.

Para un proyecto completo en Google, nos encantaría ver algún movimiento en la estabilización del ensamblador en línea y a nivel de módulo. La alternativa es usar el FFI para llamar a funciones escritas en ensamblado puro y ensambladas por separado y enlazadas juntas en un binario.

Podríamos definir funciones en ensamblador y llamarlos a través de la FFI, vinculándolos en una etapa distinta, pero no conozco ningún proyecto-metal desnudo grave que hace que exclusivamente, ya que presenta problemas tanto en términos de complejidad y rendimiento. Redox usa 'asm!'. Los sospechosos habituales de Linux, BSD, macOS, Windows, etc., hacen un uso copioso del ensamblador en línea. Zircon y SEL4 lo hacen. Incluso Plan 9 cedió en esto hace unos años en la bifurcación de Harvey.

Para cosas críticas para el rendimiento, la sobrecarga de llamadas a funciones puede dominar dependiendo de la complejidad de la función llamada. En términos de complejidad, definir funciones de ensamblador separadas solo para invocar una sola instrucción, leer o escribir un registro, o manipular el estado de la máquina que normalmente está oculto para un programador de espacio de usuario significa que equivocarse es más tedioso. En cualquier caso, tendríamos que ser más creativos en nuestro uso de Cargo (o complementar con un sistema de compilación externo o un script de shell o algo así) para hacer esto. Quizás build.rs podría ayudar aquí, pero introducirlo en el enlazador parece más desafiante.

También me gustaría mucho si hubiera alguna forma de sondear los valores de las constantes simbólicas en la plantilla del ensamblador.

Nos encantaría ver algún movimiento en la estabilización del ensamblador en línea y a nivel de módulo.

El último pre-RFC (https://internals.rust-lang.org/t/pre-rfc-inline-assembly/6443) logró consenso hace 6 meses (al menos en la mayoría de las cuestiones fundamentales), por lo que el siguiente paso es enviar un RFC que se base en eso. Si desea que esto suceda más rápido, le recomiendo que se comunique con

Por lo que vale, necesito acceso directo a los registros FSGS para obtener el puntero a la estructura TEB en Windows, también necesito un _bittest64 -como intrínseco para aplicar bt a una ubicación de memoria arbitraria, ninguno de los cuales pude encontrar una manera de hacerlo sin ensamblado en línea o llamadas externas.

Sin embargo, el tercer punto mencionado aquí me preocupa, ya que LLVM de hecho prefiere a Just Crash si algo está mal y no proporciona ningún mensaje de error.

@MSxDOS

También necesito un intrínseco similar a _bittest64 para aplicar bt a una ubicación de memoria arbitraria, ninguno de los cuales podría encontrar una manera de hacerlo sin ensamblado en línea o llamadas externas.

No debería ser difícil agregar ese a stdsimd , clang los implementa usando ensamblado en línea (https://github.com/llvm-mirror/clang/blob/c1c07cca8cae5f924cedaac7b202b0f3c167111d/test/CodeGen/bittest-intrin .c # L45) pero podemos usar eso en la biblioteca estándar y exponer el intrínseco a Rust seguro.

Siéntase animado a abrir un problema en el repositorio stdsimd sobre los elementos intrínsecos que faltan.

@josevalaad

Bueno, cuando habla de ensamblaje en matemáticas, básicamente está hablando de usar los registros e instrucciones SIMD como _mm256_mul_pd, _mm256_permute2f128_pd, etc. y operaciones de vectorización donde proceden.

Ah, sospechaba que ese podría ser el caso. Bueno, si quieres probarlo, puedes traducir el ensamblado en llamadas intrínsecas std::arch y ver si obtienes el mismo rendimiento.

Si no lo hace , presente los problemas. LLVM no es mágico, pero al menos los intrínsecos deberían ser tan buenos como asm.

@dancrossnyc Si no le importa que le pregunte, ¿existen casos de uso / características de la plataforma en particular que requieran ensamblaje en línea, en su situación?

@MSxDOS ¿ Quizás deberíamos exponer intrínsecos para leer los registros de "segmento"?


Tal vez deberíamos hacer una recopilación de datos y obtener un desglose de lo que la gente realmente quiere asm! , y ver cuántos de ellos podrían ser compatibles de alguna otra manera.

¡Quizás deberíamos recopilar datos y obtener un desglose de lo que la gente realmente quiere!

Quiero asm! para:

  • trabajar alrededor de intrínsecos no proporcionados por el compilador
  • trabajar alrededor de errores del compilador / generación de código subóptimo
  • realizar operaciones que no se pueden realizar a través de una secuencia de llamadas intrínsecas individuales, por ejemplo, una lectura EFLAGS-modificar-escribir EFLAGS donde LLVM puede modificar eflags entre lectura y escritura, y donde LLVM también asume que el usuario no modificará esto a sus espaldas (es decir, la única forma de trabajar de forma segura con EFLAGS es escribir las operaciones de lectura-modificación-escritura como un solo bloque atómico asm! ).

y vea cuántos de ellos podrían recibir apoyo de alguna otra manera.

No veo ninguna otra forma de soportar ninguno de esos casos de uso que no implique algún tipo de ensamblaje en línea, pero mi mente está abierta.

Copiado de mi publicación en el hilo anterior a RFC, aquí hay un ensamblaje en línea (ARM64) que estoy usando en mi proyecto actual:

// 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 nota que @japaric está trabajando hacia lo intrínseco de ARM . Valdría la pena comprobar si esa propuesta cubre sus necesidades.

@hepmaster

@Amanieu nota que @japaric está trabajando hacia lo intrínseco de ARM. Valdría la pena comprobar si esa propuesta cubre sus necesidades.

Vale la pena señalar que:

  • este trabajo no reemplaza el ensamblaje en línea, simplemente lo complementa. Este enfoque implementa las API del proveedor en std::arch , estas API ya son insuficientes para algunas personas.

  • este enfoque solo se puede usar cuando una secuencia de llamadas intrínsecas como foo(); bar(); baz(); produce un código indistinguible de esa secuencia de instrucciones; este no es necesariamente el caso, y cuando no lo es, el código que parece correcto produce, en el mejor de los casos, incorrecto resultados, y en el peor de los casos tiene un comportamiento indefinido (ya tuvimos errores debido a esto en x86 y x86_64 en std , por ejemplo, https://github.com/rust- lang-nursery / stdsimd / blob / master / coresimd / x86 / cpuid.rs # L108 - otras arquitecturas también tienen estos problemas).

  • algunos intrínsecos tienen argumentos de modo inmediato, que no puede pasar a través de una llamada de función, por lo que foo(3) no funcionará. Cada solución a este problema es actualmente una solución loca y, en algunos casos, actualmente no hay soluciones posibles en Rust, por lo que simplemente no proporcionamos algunos de estos elementos intrínsecos.

Entonces, si las API del proveedor se pueden implementar en Rust, están disponibles en std::arch y se pueden combinar para resolver un problema, estoy de acuerdo en que son mejores que el ensamblaje en línea. Pero de vez en cuando las API no están disponibles, tal vez ni siquiera se pueden implementar y / o no se pueden combinar correctamente. Si bien podríamos solucionar los "problemas de implementación" en el futuro, si la API del proveedor no expone lo que desea hacer, o si las API no se pueden combinar, este enfoque no lo ayudará.

Lo que puede ser muy sorprendente acerca de la implementación de intrínsecos de LLVM (especialmente SIMD) es que no se ajustan en absoluto a la asignación explícita de intrínsecos a instrucciones de Intel: están sujetos a una amplia gama de optimizaciones del compilador. Por ejemplo, recuerdo una vez en la que intenté reducir la presión de la memoria calculando algunas constantes a partir de otras constantes en lugar de cargarlas desde la memoria. Pero LLVM simplemente procedió a doblar constantemente todo de nuevo en la carga de memoria exacta que estaba tratando de evitar. En un caso diferente, quería investigar el reemplazo de una reproducción aleatoria de 16 bits con una reproducción aleatoria de 8 bits para reducir la presión del puerto 5. Sin embargo, en su sabiduría interminable, el siempre útil optimizador LLVM notó que mi reproducción aleatoria de 8 bits es de hecho una reproducción aleatoria de 16 bits y la reemplazó.

Ambas optimizaciones ciertamente producen un mejor rendimiento (especialmente frente al hyperthreading) pero no la reducción de latencia que esperaba lograr. Terminé bajando hasta nasm para ese experimento, pero tener que reescribir el código de intrínsecos a simples fue una fricción innecesaria. Por supuesto, quiero que el optimizador maneje cosas como la selección de instrucciones o el plegado constante cuando se usa alguna API vectorial de alto nivel. Pero cuando decidí explícitamente qué instrucciones usar, realmente no quiero que el compilador se meta con eso. La única alternativa es el ensamblaje en línea.

Entonces, si las API del proveedor se pueden implementar en Rust, están disponibles en std::arch y se pueden combinar para resolver un problema, estoy de acuerdo en que son mejores que el ensamblaje en línea

Eso es todo lo que he estado diciendo al principio

lograr el 95-99% de las metas

y otra vez

Sí, hay casos que requieren ensamblaje por ahora y hay casos que lo necesitarán para siempre, lo dije originalmente (énfasis agregado para mayor claridad):

Es la expectativa de la mayoría de la gente que [los elementos intrínsecos] van a lograr el 95-99% de las metas.

Esto es lo mismo que @eddyb está diciendo en paralelo. No tengo claro por qué varias personas actúan como si estuviera ignorando por completo la utilidad del ensamblaje en línea mientras trato de señalar las realidades de la situación actual .

He

  1. Señaló un cartel que no mencionaba saber que existían intrínsecos hacia intrínsecos estables de hoy .
  2. Señaló con otro cartel los elementos intrínsecos propuestos para que puedan proporcionar retroalimentación temprana a la propuesta.

Permítanme decir esto muy claramente: sí, a veces se requiere un ensamblaje en línea y es bueno . No estoy discutiendo eso. Solo intento ayudar a las personas a resolver problemas del mundo real con las herramientas que están disponibles ahora.

Lo que estaba tratando de decir es que deberíamos tener un enfoque más organizado para esto, una encuesta adecuada y recopilar muchos más datos que los pocos de nosotros en este hilo, y luego usar eso para señalar las necesidades más comunes de ensamblaje en línea (ya que está claro que los intrínsecos no pueden reemplazarlo por completo).

Sospecho que cada arquitectura tiene un subconjunto difícil de modelar, que se usa en línea con asm! , y tal vez deberíamos centrarnos en esos subconjuntos y luego tratar de generalizar.

cc @ óxido-lang / lang

@eddyb _require_ es una palabra fuerte, y me vería obligado a decir que no, no estamos estrictamente obligados a usar ensamblador en línea. Como mencioné anteriormente, _podríamos_ definir procedimientos en lenguaje ensamblador puro, ensamblarlos por separado y vincularlos a nuestros programas Rust a través del FFI.

Sin embargo, como dije antes, no conozco ningún proyecto serio a nivel de sistema operativo que haga eso. Significaría mucha placa de caldera (léase: más posibilidades de cometer un error), un proceso de construcción más complejo (en este momento somos lo suficientemente afortunados de poder salirnos con una simple invocación cargo y un vínculo y El kernel casi listo para ejecutarse aparece por el otro extremo; tendríamos que invocar el ensamblador y el enlace en un paso separado), y una disminución drástica en la capacidad de insertar cosas, etc. Es casi seguro que se produzca un impacto en el rendimiento.

Cosas como los intrínsecos del compilador ayudan en muchos casos, pero para cosas como el conjunto de instrucciones de supervisión de la ISA de destino, particularmente características de hardware más esotéricas (características de hipervisor y enclave, por ejemplo), a menudo no hay intrínsecas y estamos en un entorno no_std. Los elementos intrínsecos que existen a menudo no son suficientes; Por ejemplo, la convención de llamadas de interrupción x86 parece genial, pero no le da acceso mutable a los registros de propósito general en un marco de trampa: suponga que tomo una excepción de instrucción indefinida con la intención de hacer una emulación, y supongo que la instrucción emulada devuelve un valor en% rax o algo así; la convención de llamadas no me da una buena manera de pasar eso de vuelta al sitio de llamadas, por lo que tuvimos que lanzar el nuestro. Eso significaba escribir mi propio código de manejo de excepciones en ensamblador.

Entonces, para ser honesto, no, no necesitamos un ensamblador en línea, pero es lo suficientemente útil como para no tenerlo.

@dancrossnyc Soy específicamente curiosidad acerca de cómo evitar el montaje por separado, es decir, qué tipo de montaje que necesita en absoluto en su proyecto, no importa cómo se vincula en.

En su caso, parece ser un subconjunto ISA privilegiado de supervisor / hipervisor / enclave, ¿es correcto?

a menudo no hay intrínsecos

¿Es esto por necesidad, es decir, las instrucciones tienen requisitos que son irrazonablemente difíciles o incluso imposibles de cumplir cuando se compilan como llamadas intrínsecas, por ejemplo, LLVM?
¿O es esto solo porque se supone que son demasiado especiales para ser útiles para la mayoría de los desarrolladores?

y estamos en un entorno no_std

Para que conste, los elementos intrínsecos del proveedor están tanto en std::arch como en core::arch (el primero es una reexportación).

la convención de llamadas de interrupción x86 parece genial, pero no le da acceso mutable a los registros de propósito general en un marco de trampa

cc @rkruppe ¿Se puede implementar esto en LLVM?

@eddyb correcto; necesitamos el subconjunto supervisor de la ISA. Me temo que no puedo decir mucho más en este momento sobre nuestro caso de uso específico.

¿Es esto por necesidad, es decir, las instrucciones tienen requisitos que son irrazonablemente difíciles o incluso imposibles de cumplir cuando se compilan como llamadas intrínsecas, por ejemplo, LLVM?
¿O es esto solo porque se supone que son demasiado especiales para ser útiles para la mayoría de los desarrolladores?

Hasta cierto punto, ambos son ciertos, pero a fin de cuentas, diría que el último es más relevante aquí. Algunas cosas son específicas de la microarquitectura y dependen de las configuraciones específicas del paquete del procesador. ¿Sería razonable que un compilador (por ejemplo) exponga algo como intrínseco que es parte del subconjunto de instrucciones privilegiadas _y_ condicionado a una versión específica del procesador? Sinceramente, no lo sé.

Para que conste, los elementos intrínsecos del proveedor están tanto en std :: arch como en core :: arch (el primero es una reexportación).

Es realmente bueno saberlo. ¡Gracias!

¿Sería razonable que un compilador (por ejemplo) exponga algo como intrínseco que es parte del subconjunto de instrucciones privilegiadas y está condicionado a una versión específica del procesador? Sinceramente, no lo sé.

Ya lo hacemos. Por ejemplo, las instrucciones xsave x86 se implementan y exponen en core::arch , no están disponibles en todos los procesadores y la mayoría de ellas requieren el modo privilegiado.

@gnzlbg xsave no tiene privilegios; ¿quiso decir xsaves ?

Eché un vistazo a https://rust-lang-nursery.github.io/stdsimd/x86_64/stdsimd/arch/x86_64/index.html y las únicas instrucciones privilegiadas que vi en mi barrido rápido (no hice un búsqueda exhaustiva) fueron xsaves , xsaves64 , xrstors y xrstors64 . Sospecho que son intrínsecos porque pertenecen a la familia general XSAVE* y no generan excepciones en modo real, y algunas personas quieren usar clang / llvm para compilar código en modo real.

@dancrossnyc sí, algunos de esos son los que quise decir (implementamos xsave , xsaves , xsaveopt , ... en el módulo xsave : https: //github.com/rust-lang-nursery/stdsimd/blob/master/coresimd/x86/xsave.rs).

Estos están disponibles en core , por lo que puede usarlos para escribir un kernel de sistema operativo para x86. En el espacio de usuario, son AFAICT inútiles (siempre generarán una excepción), pero no tenemos una manera de distinguir esto en core . Sin embargo, solo pudimos exponerlos en core y no en std , pero como ya están estables, ese barco ha zarpado. Quién sabe, tal vez algún sistema operativo ejecute todo en el anillo 0 algún día, y puedas usarlos allí ...

@gnzlbg No sé por qué xsaveopt o xsave generarían una excepción en el espacio de usuario: xsaves es el único de la familia que está definido para generar una excepción (#GP si CPL> 0), y luego solo en modo protegido (SDM vol. 1 canal 13; vol. 2C canal 5 XSAVES). xsave y xsaveopt son útiles para implementar, por ejemplo, hilos de espacio de usuario preventivos, por lo que su presencia como intrínsecos en realidad tiene sentido. Sospecho que lo intrínseco de xsaves fue porque alguien acaba de agregar todo de la familia xsave sin darse cuenta del problema de privilegios (es decir, asumiendo que era invocable desde el espacio de usuario), o alguien quería llamarlo desde el modo real. Esto último puede parecer descabellado, pero sé que la gente, por ejemplo, está construyendo firmware en modo real con Clang y LLVM.

No me malinterpretes; la presencia de intrínsecos LLVM en core es excelente; Si nunca tengo que escribir esa secuencia tonta de instrucciones para obtener los resultados de rdtscp en un formato útil nuevamente, estaré feliz. Pero el conjunto actual de elementos intrínsecos no sustituye al ensamblador en línea cuando se escribe un kernel u otro tipo de supervisión básica.

@dancrossnyc cuando mencioné xsave me refería a algunos de los intrínsecos que están disponibles detrás de los bits de CPUID XSAVE, XSAVEOPT, XSAVEC, etc. Algunos de estos intrínsecos requieren un modo privilegiado.

¿Sería razonable que un compilador (por ejemplo) exponga algo como intrínseco que es parte del subconjunto de instrucciones privilegiadas y está condicionado a una versión específica del procesador?

Ya lo hacemos y están disponibles en Rust estable.

Sospecho que lo intrínseco de xsaves fue porque alguien acaba de agregar todo de la familia xsave sin darse cuenta del problema de los privilegios

Agregué estos intrínsecos. Nos dimos cuenta de los problemas de privilegios y decidimos agregarlos de todos modos porque está perfectamente bien que un programa que depende de core sea ​​un kernel del sistema operativo que quiera usarlos, y son inofensivos en el espacio de usuario (como en, si intente usarlos, su proceso termina).

Pero el conjunto actual de elementos intrínsecos no sustituye al ensamblador en línea cuando se escribe un kernel u otro tipo de supervisión básica.

De acuerdo, es por eso que este tema aún está abierto;)

@gnzlbg lo siento, no me refiero a descarrilar esto con un agujero de conejo en xsave et al.

Sin embargo, por lo que puedo decir, los únicos intrínsecos que requieren ejecución privilegiada son los relacionados con xsaves e incluso entonces no siempre es privilegiado (de nuevo, al modo real no le importa). Es maravilloso que estén disponibles en Rust estable (en serio). Los otros pueden ser útiles en el espacio de usuario y, de manera similar, creo que es genial que estén allí. Sin embargo, xsaves y xrstors son una porción muy, muy pequeña del conjunto de instrucciones privilegiadas y tener intrínsecos agregados para dos instrucciones es cualitativamente diferente a hacerlo en general y creo que la pregunta sigue siendo si es apropiado _ en general_. Considere la instrucción VMWRITE de las extensiones VMX, por ejemplo; Me imagino que un intrínseco haría algo como ejecutar una instrucción y luego "devolver" rflags . Eso es algo extrañamente especializado para tener como intrínseco.

Creo que, de lo contrario, estamos de acuerdo aquí.

FWIW según el std::arch RFC, actualmente solo podemos agregar elementos intrínsecos a std::arch que los proveedores exponen en sus API. Para el caso de xsave , Intel los expone en su API C , por eso está bien que esté ahí. Si necesita algún elemento intrínseco del proveedor que no esté expuesto actualmente, abra un problema, ya sea que requiera el modo privilegiado o no, es irrelevante.

Si el proveedor no expone un elemento intrínseco para él, entonces std::arch podría no ser el lugar para ello, pero hay muchas alternativas a eso (ensamblaje en línea, ensamblaje global, llamada C, ...).

Lo siento, entendí que dijiste que escribiste los intrínsecos de xsave para referirse a los intrínsecos de Intel; Mis comentarios anteriores todavía se aplican a por qué creo que xsaves es un intrínseco entonces (ya sea un accidente por parte de un escritor de compiladores en Intel o porque alguien lo quería para el modo real; siento que el primero se notaría muy rápido pero el firmware hace cosas raras, por lo que este último no me sorprendería en absoluto).

De todos modos, sí, creo que estamos fundamentalmente de acuerdo: los intrínsecos no son el lugar para todo, y por eso nos gustaría ver asm! () Trasladado a estable. Estoy muy emocionado de saber que se está progresando en esta área, como dijiste ayer, y si podemos empujar suavemente a @Florob para que suba más cerca de la parte superior de la pila, ¡estaremos encantados de hacerlo!

Algunos detalles adicionales y casos de uso para asm! :

Cuando escribe un sistema operativo, firmware, ciertos tipos de bibliotecas o ciertos otros tipos de código del sistema, necesita acceso completo al ensamblaje a nivel de plataforma. Incluso si tuviéramos elementos intrínsecos que expusieran cada instrucción en cada arquitectura que Rust admite (que no estamos ni cerca de tener), eso aún no sería suficiente para algunas de las acrobacias que la gente realiza regularmente con el ensamblaje en línea.

Aquí hay una pequeña fracción de las cosas que puede hacer con el ensamblaje en línea que no puede hacer fácilmente de otras maneras. Cada uno de estos es un ejemplo del mundo real que he visto (o en algunos casos escrito), no hipotético.

  • Recopile todas las implementaciones de un patrón particular de instrucciones en una sección ELF separada y luego, en el código de carga, parchee esa sección en tiempo de ejecución según las características del sistema en el que se ejecuta.
  • Escriba una instrucción de salto cuyo objetivo se parchee en tiempo de ejecución.
  • Emite una secuencia exacta de instrucciones (para que no puedas contar con intrínsecos para las instrucciones individuales), de modo que puedas implementar un patrón que maneje cuidadosamente las posibles interrupciones en el medio.
  • Emite una instrucción, seguida de un salto al final del bloque ASM, seguido de un código de recuperación de fallas para que un manejador de fallas de hardware salte si la instrucción genera una falla.
  • Emite una secuencia de bytes correspondiente a una instrucción que el ensamblador aún no conoce.
  • Escriba un fragmento de código que cambie cuidadosamente a una pila diferente y luego llame a otra función.
  • Llame a rutinas de ensamblaje o llamadas al sistema que requieran argumentos en registros específicos.

+ 1e6

@eddyb

Bien, intentaré el enfoque intrínseco y veré dónde se lleva. Probablemente tenga razón y ese es el mejor enfoque para mi caso. ¡Gracias!

¡@joshtriplett lo clavó! Estos son los casos de uso exactos que tenía en mente.

loop {
   :thumbs_up:
}

Agregaría un par de otros casos de uso:

  • escribir código en modos arquitectónicos extraños, como llamadas BIOS / EFI y modo real de 16 bits.
  • escribir código con modos de direccionamiento extraños / inusuales (que aparece a menudo en modo real de 16 bits, cargadores de arranque, etc.)

@ mark-im ¡Por supuesto! Y generalizando un punto que tiene sub-casos en nuestras dos listas: traducir entre convenciones de llamada.

Estoy cerrando el # 53118 a favor de este problema y copiando el PR aquí para que conste. Tenga en cuenta que esto es de agosto, pero una breve mirada parece indicar que la situación no ha cambiado:


La sección sobre ensamblaje en línea necesita una revisión; en su estado actual implica que el comportamiento y la sintaxis están ligados a rustc y al lenguaje rust en general. Prácticamente toda la documentación es específica para el ensamblaje x86 / x86_64 con la cadena de herramientas llvm. Para ser claros, no me refiero al código ensamblador en sí, que obviamente es específico de la plataforma, sino a la arquitectura general y el uso del ensamblaje en línea.

No encontré una fuente autorizada para el comportamiento del ensamblaje en línea cuando se trata del objetivo ARM, pero según mi experimentación y haciendo referencia a la documentación del ensamblaje en línea de ARM GCC , los siguientes puntos parecen estar completamente fuera de lugar:

  • La sintaxis ASM, como ARM / MIPS (¿y la mayoría de los otros CISC?), Usa la sintaxis intel-esque con el registro de destino primero. Entendí que la documentación significaba / implicaba que asm en línea tomó la sintaxis at & t que se transpiró a la sintaxis específica de la plataforma / compilador real, y que simplemente debería sustituir los nombres de los registros x86 por los de los registros ARM únicamente.
  • De manera relacionada, la opción intel no es válida, ya que causa errores de "directiva desconocida" al compilar .
  • Adaptado de la documentación del ensamblaje en línea de ARM GCC (para construir contra thumbv7em-none-eabi con la cadena arm-none-eabi-* herramientas $0 refiere al primer registro de salida y no al primer registro de entrada, como es el caso de las instrucciones x86 llvm.
  • Al mismo tiempo, otras características específicas del compilador _no_ están presentes; No puedo usar referencias con nombre a registros, solo índices (por ejemplo, asm("mov %[result],%[value],ror #1":[result] "=r" (y):[value] "r" (x)); no es válido).
  • (Incluso para los objetivos x86 / x86_64, el uso de $0 y $2 en el ejemplo de ensamblaje en línea es muy confuso, ya que no explica por qué se eligieron esos números).

Creo que lo que más me sorprendió es la declaración final:

La implementación actual del asm! macro es un enlace directo a las expresiones del ensamblador en línea de LLVM, así que asegúrese de consultar su documentación también para obtener más información sobre los golpes, las restricciones, etc.

Lo que no parece ser universalmente cierto.

Entendí que la documentación significaba / implicaba que asm en línea tomó la sintaxis at & t que se transpiró a la sintaxis específica de la plataforma / compilador real, y que simplemente debería sustituir los nombres de los registros x86 por los de los registros ARM únicamente.

Una noción de sintaxis intel vs at & t solo existe en x86 (aunque puede haber otros casos que no conozco). Es único en el sentido de que son dos idiomas diferentes que comparten el mismo conjunto de mnemónicos para representar el mismo conjunto de código binario. El ecosistema GNU ha establecido la sintaxis de at & t como el valor predeterminado dominante para el mundo x86, razón por la cual esto es lo que asm en línea tiene por defecto. Se equivoca en el sentido de que es un enlace directo a las expresiones del ensamblador en línea de LLVM que, a su vez, en su mayoría simplemente descargan texto sin formato (después de procesar las sustituciones) en el programa de ensamblaje textual. Nada de esto es único (o incluso relevante) para o acerca de asm!() ya que es completamente específico de la plataforma y completamente sin sentido más allá del mundo x86.

De manera relacionada, la opción intel no es válida, ya que causa errores de "directiva desconocida" al compilar.

Esta es una consecuencia directa de la inserción de texto plano "tonto" / simple que describí anteriormente. Como indica el mensaje de error, la directiva .intel_syntax no es compatible. Esta es una solución antigua y conocida para usar el asm en línea de estilo intel con GCC (que emite estilo att): uno simplemente escribiría .intel_syntax al comienzo del bloque de asm en línea, luego escribiría algo de intel- style asm y finalmente termina con .att_syntax para volver a configurar el ensamblador en modo att para que procese correctamente el (siguiente) código generado por el compilador una vez más. Es un truco sucio y recuerdo que al menos la implementación de LLVM ha tenido algunas peculiaridades extrañas durante mucho tiempo, por lo que parece que está viendo este error porque finalmente se eliminó. Lamentablemente, el único curso de acción correcto aquí es eliminar la opción "intel" de rustc.

parece que incluso algunas suposiciones básicas sobre el formato del ensamblaje en línea son específicas de la plataforma

Su observación es completamente correcta, cada plataforma crea tanto su propio formato binario como su propio lenguaje ensamblador. Son completamente independientes y (en su mayoría) no procesados ​​por el compilador, ¡que es el punto principal de la programación en ensamblador sin formato!

No puedo usar referencias con nombre a registros, solo índices

Lamentablemente, existe un gran desajuste entre la implementación de LLVM en línea asm que rustc expone y la implementación de GCC (que clang emula). Sin una decisión sobre cómo avanzar con asm!() hay poca motivación para mejorar esto; además, describí las principales opciones hace mucho tiempo, todas ellas tienen claros inconvenientes. Dado que esto no parece ser una prioridad, probablemente se quedará atascado con los asm!() durante al menos algunos años. Hay soluciones decentes:

  • confíe en el optimizador para producir un código óptimo (con un pequeño empujón, por lo general, puede obtener exactamente lo que desea sin tener que escribir el ensamblaje en bruto)
  • use intrínsecos, otra solución bastante elegante que es mejor que el ensamblaje en línea en casi todos los sentidos (a menos que necesite un control exacto sobre la selección y programación de instrucciones)
  • invocar la caja cc de build.rs para vincular un objeto C con asm en línea

    • Básicamente, simplemente invoque cualquier ensamblador que desee desde build.rs , usar un compilador de C puede parecer una exageración, pero le ahorra la molestia de integrarse con el sistema build.rs

Estas soluciones se aplican a todos menos a un pequeño conjunto de casos extremos muy específicos. Sin embargo, si golpeas uno de esos (afortunadamente todavía no lo he hecho), no tienes suerte.

Estoy de acuerdo en que la documentación es bastante mediocre, pero es lo suficientemente buena para cualquiera que esté familiarizado con el ensamblaje en línea. Si no es así, probablemente no debería usarlo . No me malinterpretes, definitivamente deberías sentirte libre de experimentar y aprender, pero como asm!() es inestable y está descuidado, y debido a que hay soluciones alternativas realmente buenas, te recomiendo encarecidamente que no lo uses en cualquier proyecto serio, si es posible. .

invocar la caja cc de build.rs para vincular un objeto C con asm en línea

También puede invocar la caja cc desde build.rs para crear archivos de ensamblaje simples, lo que brinda la máxima cantidad de control. Recomiendo encarecidamente hacer exactamente esto en caso de que las dos "soluciones alternativas" anteriores no funcionen para su caso de uso.

@ main-- escribió:

Estas soluciones se aplican a todos menos a un pequeño conjunto de casos extremos muy específicos. Sin embargo, si golpeas uno de esos (afortunadamente todavía no lo he hecho), no tienes suerte.

Quiero decir, no del todo sin suerte. Solo tienes que usar inline asm Rust. Tengo un caso límite que ninguna de las soluciones alternativas enumeradas aquí cubre. Como dice, si está familiarizado con el proceso de otros compiladores, en general está bien.

(Tengo otro caso de uso: algún día me gustaría enseñar arquitectura de computadora de programación de sistemas y otras cosas usando Rust en lugar de C. No tener ensamblaje en línea haría que esto sea mucho más incómodo).

Desearía que hiciéramos del ensamblaje en línea una prioridad en Rust y lo estabilizáramos más temprano que tarde. Quizás este debería ser un objetivo de Rust 2019. Estoy bien con cualquiera de las soluciones que enumeraste en tu bonito comentario anterior : podría vivir con los problemas de cualquiera de ellas. Ser capaz de insertar código ensamblador es para mí un requisito previo para escribir Rust en lugar de C en todas partes: realmente lo necesito para que sea estable.

Desearía que hiciéramos del ensamblaje en línea una prioridad en Rust y lo estabilizáramos más temprano que tarde. Quizás este debería ser un objetivo de Rust 2019.

Escribe una publicación de blog de Rust 2019 y expresa esta preocupación. Creo que si suficientes de nosotros hacemos eso, podemos influir en la hoja de ruta.

Para aclarar mi comentario anterior, el problema es que la documentación no explica cuán "profundamente" se analiza / interactúa con el contenido de la macro asm!(..) . Estoy familiarizado con el ensamblaje x86 y MIPS / ARM, pero supuse que llvm tenía su propio formato de lenguaje ensamblador. He usado ensamblado en línea para x86 antes, pero no estaba claro hasta qué punto fue la bastardización de asm a brige C y ASM. Mi presunción (ahora invalidada) basada en la redacción de la sección de ensamblaje en línea de rust era que LLVM tenía su propio formato ASM que se construyó para imitar el ensamblaje x86 en los modos at & t o intel, y necesariamente se parecía a los ejemplos x86 que se muestran.

(Lo que me ayudó fue estudiar la salida macro ampliada, que aclaró lo que estaba sucediendo)

Creo que debe haber menos abstracción en esa página. Aclare lo que LLVM analiza y lo que interpreta directamente como ASM. Qué partes son específicas del óxido, qué partes son específicas del hardware en el que está ejecutando y qué partes pertenecen al pegamento que las mantiene unidas.

invocar la caja cc de build.rs para vincular un objeto C con asm en línea

El progreso reciente en LTO en varios probablemente no )

invocar la caja cc de build.rs para vincular un objeto C con asm en línea

El progreso reciente en LTO en varios

Incluso si esto funciona, no quiero escribir mi ensamblado en línea en C. Quiero escribirlo en Rust. :-)

No quiero escribir mi ensamblado en línea en C.

Puede compilar y vincular archivos .s y .S directamente (ver, por ejemplo, esta caja ), que en mi libro están lo suficientemente lejos de C. :)

si algunas de las desventajas de esta avenida se pueden reducir

Creo que esto no es factible actualmente, ya que LTO en varios idiomas se basa en tener LLVM IR y el ensamblaje no generaría esto.

Creo que esto no es factible actualmente, ya que LTO en varios idiomas se basa en tener LLVM IR y el ensamblaje no generaría esto.

Puede rellenar el ensamblaje en el ensamblaje de nivel de módulo en módulos LLVM IR.

¿Alguien sabe cuál es la propuesta / estado actual más reciente? Dado que el tema del año es "madurez y terminando lo que comenzamos", parece una gran oportunidad para finalmente terminar asm .

En febrero pasado se discutieron vagos planes para una nueva sintaxis (que se estabilizará): https://paper.dropbox.com/doc/FFI-5NmXV30TGiSsr9dIxpqpq

Según esas notas, @joshtriplett y @Amanieu se inscribieron para escribir un RFC.

¿Cuál es el estado de la nueva sintaxis?

Necesita ser RFC e implementado todas las noches.

ping @joshtriplett @Amanieu ¡Avíseme si puedo ayudar a mover las cosas aquí! Estaré en contacto en breve.

@cramertj AFAICT cualquiera puede hacer avanzar esto, esto está desbloqueado y esperando que alguien intervenga y se ponga a trabajar. Hay un pre-RFC que esboza el diseño general, y los siguientes pasos podrían ser implementarlo y ver si realmente funciona, ya sea como una macro proc, en una bifurcación o como una característica inestable diferente.

Probablemente se podría intentar convertir ese pre-RFC en un RFC adecuado y enviarlo, pero dudo que sin una implementación tal RFC pueda ser convincente.


EDITAR: para ser claros, al convencer me refiero específicamente a partes del pre-RFC como este:

Además, se agregan asignaciones para las clases de registro según corresponda (cf. llvm-constraint 6)

donde hay docenas de clases de registro específicas de arch en el lang-ref. Un RFC no puede simplemente descartar todos estos, y asegurarse de que todos funcionen como se supone que deben hacerlo, o que sean significativos o que sean lo suficientemente "estables" en LLVM como para ser expuestos desde aquí, etc., se beneficiaría de una implementación que se pueda solo pruébalos en.

¿Se admite aquí el ensamblaje en línea RISC-V con #![feature(asm)] ?

A mi leal saber y entender, se admite todo el montaje en plataformas compatibles; es prácticamente un acceso sin formato al soporte de asm del compilador llvm.

Sí, se admite RISC-V. Las clases de restricción de entrada / salida / clobber específicas de la arquitectura están documentadas en LLVM langref .

Sin embargo, hay una advertencia: si necesita restringir los registros individuales en las restricciones de entrada / salida / clobber, debe usar los nombres de los registros arquitectónicos (x0-x31, f0-f31), no los nombres ABI. En el fragmento de ensamblado en sí, puede usar cualquier tipo de nombre de registro.

Como alguien nuevo en estos conceptos, puedo decir ... toda esta discusión parece una tontería. ¿Cómo es posible que un lenguaje (ensamblador) que se supone que es un mapeo 1 a 1 con su código de máquina cause tanto dolor de cabeza?

Estoy bastante confundido:

  • Si está escribiendo ASM, ¿no debería tener que ser reescrito (por un humano con #[cfg(...)] ) para cada arquitectura _y backend_ que está tratando de admitir?
  • Esto significa que la cuestión de la "sintaxis" es discutible ... simplemente use la sintaxis para esa arquitectura y el backend que el compilador está usando.
  • Rust solo necesitaría funciones std inseguras para poder colocar bytes en los registros correctos y empujar / pop a la pila para cualquier arquitectura con la que se compile; nuevamente, esto puede tener que reescribirse para cada arquitectura y tal vez incluso para cada backend.

Entiendo que la compatibilidad con versiones anteriores es un problema, pero con la gran cantidad de errores y el hecho de que esto nunca se estabilizó, tal vez sea mejor pasarlo al backend. Rust no debería estar en el negocio de tratar de corregir errores de sintaxis extraña de LLVM o gcc o de cualquier otra persona. Rust se dedica a emitir código de máquina para la arquitectura y el compilador al que se dirige ... ¡y asm ya es básicamente ese código!

La razón por la que no hay progreso aquí es que nadie está invirtiendo tiempo en solucionar este problema. Esa no es una buena razón para estabilizar una función.

Mientras leía este hilo, tuve una idea y tuve que publicarla. Lo siento si estoy respondiendo a una publicación anterior, pero pensé que valió la pena:

@ main-- dijo:

Ambas optimizaciones ciertamente producen un mejor rendimiento (especialmente frente al hyperthreading) pero no la reducción de latencia que esperaba lograr. Terminé bajando hasta nasm para ese experimento, pero tener que reescribir el código de intrínsecos a simples fue una fricción innecesaria. Por supuesto, quiero que el optimizador maneje cosas como la selección de instrucciones o el plegado constante cuando se usa alguna API vectorial de alto nivel. Pero cuando decidí explícitamente qué instrucciones usar, realmente no quiero que el compilador se meta con eso. La única alternativa es el ensamblaje en línea.

Quizás en lugar de ASM en línea, lo que realmente necesitamos aquí son atributos de función para LLVM que le dijeran al optimizador: "optimizar esto para rendimiento", "optimizar esto para latencia", "optimizar esto para tamaño binario". Sé que esta solución es ascendente, pero no solo resolvería su problema particular automáticamente (al proporcionar una latencia más baja, pero también una implementación isomórfica del algoritmo), sino que también permitiría a los programadores de Rust tener un control más detallado sobre las características de rendimiento. eso les importa.

@ felix91gr Eso no resuelve los casos de uso que requieren emitir una secuencia exacta de instrucciones, por ejemplo, manipuladores de interrupciones.

@ mark-im, por supuesto que no. ¡Por eso pongo una cita literal! 🙂

Mi punto fue que aunque podría resolver el "compilador optimiza de una manera opuesta a lo que necesito" (que es clásico en su caso: latencia frente a rendimiento) mediante el uso de funciones de ASM en línea, tal vez (y en mi opinión definitivamente) ese caso de uso sería ser atendido mejor por un control más detallado de las optimizaciones :)

A la luz de los próximos cambios en el ensamblaje en línea, la mayor parte de la discusión en este número ya no es relevante. Como tal, voy a cerrar este problema a favor de dos problemas de seguimiento separados para cada tipo de ensamblaje en línea que tenemos:

  • Problema de seguimiento para el ensamblaje en línea estilo LLVM ( llvm_asm ) # 70173
  • Problema de seguimiento para el ensamblaje en línea ( asm! ) # 72016
¿Fue útil esta página
0 / 5 - 0 calificaciones