¿Los compiladores-escritores realmente necesitan 'entender' el código de la máquina? [cerrado]

10

Podría ser una especie de pregunta extraña.

Un tipo que escribe un compilador de C ++ (o cualquier lenguaje que no sea VM): ¿necesita poder leer / escribir lenguaje máquina sin procesar? ¿Cómo funciona?

EDITAR: Me refiero específicamente a los compiladores que compilan en código máquina, no a algún otro lenguaje de programación.

Aviv Cohn
fuente
1
No. Ni siquiera necesita saberlo, simplemente puede copiar ciegamente, sin pensar, su especificación ISA de destino: dl.acm.org/citation.cfm?id=1706346
SK-logic
1
Coffescript compila a javascript.
Kartik
@Kartik ¿El compilador de CoffeeScript que compila a Javascript, también incluye un compilador de Javascript que compila a lo que se compila Javascript? ¿O solo se compila en código fuente Javascript y nada más?
Aviv Cohn
El compilador coffeescript simplemente convierte cofeescript a javascript. Javascript no está compilado, es manejado por el navegador. Quería decir que puedes escribir un compilador que compila un idioma a otro, no tienes que saber el lenguaje de máquina para eso. Otro ejemplo es el compilador 'SPL' que compila las reproducciones de Shakespeare para C ++. shakespearelang.sourceforge.net
Kartik

Respuestas:

15

No, en absoluto. Es perfectamente posible (y a menudo incluso preferible) que su compilador emita un código de ensamblaje. El ensamblador se encarga de crear el código de máquina real.

Por cierto, su distinción entre la implementación que no es VM y la implementación de VM no es útil.

  • Para empezar, el uso de una máquina virtual o la precompilación para codificar a máquina son solo diferentes formas de implementar un lenguaje; en la mayoría de los casos, se puede implementar un lenguaje utilizando cualquiera de las estrategias. De hecho, tuve que usar un intérprete de C ++ una vez.

  • Además, muchas máquinas virtuales como JVM tienen un código de máquina binario y un ensamblador, al igual que una arquitectura ordinaria.

El LLVM (que usan los compiladores de Clang) merece una mención especial aquí: define una VM para la cual las instrucciones pueden representarse como código de byte, ensamblaje textual o una estructura de datos que hace que sea muy fácil emitir desde un compilador. Entonces, aunque sería útil para la depuración (y para comprender lo que está haciendo), ni siquiera tendría que saber sobre el lenguaje ensamblador, solo sobre la API LLVM.

Lo bueno de LLVM es que su VM es solo una abstracción, y que el código de bytes generalmente no se interpreta, sino que se JIT de forma transparente. Por lo tanto, es completamente posible escribir un lenguaje que se compile efectivamente, sin tener que saber sobre el conjunto de instrucciones de su CPU.

amon
fuente
Y otra buena propiedad de LLVM es que no es necesario comprender profundamente el ISA objetivo para implementar un backend eficiente. Es bastante declarativo, por lo que casi se puede copiar y pegar una especificación ISA en archivos .td sin siquiera tratar de entenderla.
SK-logic
Gracias por responder. Pregunta: Entiendo por su respuesta y las respuestas de los demás, el compilador-escritor no tiene que entender el código de la máquina, puede usar otra herramienta que haga el código real de conversión a máquina para él. Sin embargo, el tipo que escribió esa herramienta se tiene que entender en lenguaje de máquina, ¿verdad? El tipo que escribió el software que realiza la conversión real de algún idioma a código de máquina tiene que entender el lenguaje de máquina, ¿verdad?
Aviv Cohn
55
@Prog sí. Si crea una capa de abstracción, solo tiene que entender la capa debajo de usted. Si bien es útil tener una comprensión básica de todas las capas, esto no es realmente necesario. No necesita comprender la física cuántica para usar un transistor. No necesita comprender el diseño del chip para usar una CPU. No necesita saber el código de máquina para escribir el ensamblaje. No necesita conocer el conjunto de instrucciones de la plataforma cuando utiliza una VM, etc. Pero alguien tuvo que construir esos niveles de abstracción por debajo del suyo.
amon
1
@ SK-logic No es cierto (al menos si quieres un buen código) por lo que he escuchado. Las personas que implementaron el backend Aarch64 para llvm tuvieron bastantes desafíos (reubicaciones para uno, patrones de carga de almacenamiento horribles, ...). Y eso es ignorar el mayor elefante en la habitación: modelo de memoria de la ISA y el modelo de memoria de la lengua que está interesado en Usted puede trabajar en un compilador, pero no se puede trabajar en un motor sin entender la arquitectura ...
Voo
1
Agregaría que realmente no hay una diferencia sustancial entre el ensamblaje y el código de la máquina, conceptualmente. Realmente no tendrá muchos beneficios, una vez que sepa cómo usar una instrucción en particular, cuál es el código de operación de esa instrucción. Si sabe cómo usar MOV, realmente no importa si no sabe que es la instrucción 27, que creo que es similar a lo que describe @ SK-logic.
whatsisname
9

No. El punto clave de su pregunta es que la compilación es un término extremadamente amplio. La compilación puede pasar de cualquier idioma a cualquier idioma. Y el código de ensamblado / máquina es solo uno de los muchos idiomas para el objetivo de compilación. Por ejemplo, los lenguajes Java y .NET como C #, F # y VB.NET se compilan en algún tipo de código intermedio en lugar de código específico de la máquina. No importa si luego se ejecuta en VM, el lenguaje aún se compila. También hay una opción para compilar en otro lenguaje, como C. C es en realidad un objetivo de compilación bastante popular y muchas herramientas lo hacen. Y finalmente, podría usar alguna herramienta o biblioteca para hacer el trabajo duro de producir código de máquina para usted. hay, por ejemplo, LLVM que puede reducir el esfuerzo necesario para crear un compilador independiente.

Además, su edición no tiene ningún sentido. Es como preguntar "¿Necesita cada ingeniero entender cómo funciona el motor? Y estoy preguntando acerca de los ingenieros que trabajan en motores". Si está trabajando en un programa o biblioteca que emite un código de máquina, debe comprenderlo. El punto es que no tienes que hacer tal cosa al escribir el compilador. Muchas personas lo hicieron antes que usted, por lo que debe tener razones serias para hacerlo nuevamente.

Eufórico
fuente
Y la persona que escribe la herramienta o biblioteca que realiza la conversión real al lenguaje de máquina, tiene que entender el lenguaje de máquina por completo, ¿verdad?
Aviv Cohn el
3
@Prog ¿Necesita comprender completamente un lenguaje de programación para programar en él? No, pero posiblemente escribirás un código subóptimo y no podrás hacer ciertas cosas que otros podrían ser capaces de hacer. ¿Necesita comprender completamente el lenguaje de máquina si escribe un compilador que se traduce en eso? No, pero su compilador será subóptimo e incapaz de hacer ciertas cosas.
Sumurai8
@ Sumurai8: aunque hasta cierto punto puede "comprender" el lenguaje de máquina por partes para escribir un emisor de código de máquina que comprenda todo mejor que usted. Por ejemplo, si escribe un buen marco, puede configurar la definición de cada código de operación junto con sus costos y consideraciones de canalización, y luego su marco puede escribir código de máquina optimizado a pesar de que no tiene experiencia en la optimización de esa máquina en particular. Ser capaz de programar ese código de máquina de manera competente por ti mismo probablemente no hace daño.
Steve Jessop
@SteveJessop Si comprende cada código de operación hasta el punto de que puede aprender a una máquina cómo encadenar ese código de operación junto con otros códigos de operación para expresar un concepto de nivel superior, comprende completamente el lenguaje de máquina. Entonces eres demasiado vago para encontrar la solución óptima para cada problema que existe ;-)
Sumurai8
@ Sumurai8: hmm, pero al menos en principio podría "entender" cada código de operación brevemente durante los cinco minutos que me lleva configurarlo, y luego lo he olvidado para cuando "entienda" el código de operación después del siguiente. Probablemente eso no sea lo que el interlocutor quiere decir con "ser capaz de leer / escribir lenguaje máquina sin procesar". Por supuesto, estoy asumiendo un marco bastante bueno aquí, que es lo suficientemente configurable como para definir y usar toda la información útil sobre cada código de operación del conjunto de instrucciones. LLVM de alguna manera apunta a esto, pero de acuerdo con "Voo" (en un comentario a continuación) no lo ha alcanzado.
Steve Jessop
3

Clásicamente, un compilador tiene tres partes: análisis léxico, análisis y generación de código. El análisis léxico divide el texto del programa en palabras clave, nombres y valores del idioma. El análisis muestra cómo se combinan los tokens que provienen del análisis léxico en declaraciones sintácticamente correctas para el lenguaje. La generación de código toma las estructuras de datos producidas por el analizador y las traduce en código de máquina o alguna otra representación. Hoy en día, el análisis léxico y el análisis pueden combinarse en un solo paso.

Claramente, la persona que escribe el generador de código debe comprender el código de la máquina de destino a un nivel muy profundo, incluidos los conjuntos de instrucciones, las canalizaciones del procesador y el comportamiento de la memoria caché. De lo contrario, los programas producidos por el compilador serían lentos e ineficientes. Es muy posible que puedan leer y escribir código de máquina representado por números octales o hexadecimales, pero generalmente escribirán funciones para generar el código de máquina, refiriéndose internamente a tablas de instrucciones de máquina. Teóricamente, la gente que escribe el lexer y el analizador podría no saber nada sobre la generación del código de máquina. De hecho, algunos compiladores modernos le permiten conectar sus propias rutinas de generación de código que pueden emitir código de máquina para algunas CPU de las que los escritores lexer y parser nunca han oído hablar.

Sin embargo, en la práctica, los escritores de compiladores en cada paso saben mucho sobre las diferentes arquitecturas de procesador, y eso les ayuda a diseñar las estructuras de datos que necesitará el paso de generación de código.

Charles E. Grant
fuente
2

Hace mucho tiempo escribí un compilador que convirtió entre dos scripts de shell diferentes. No fue de ninguna manera cerca del código de máquina.

Una escritura del compilador tiene que entender su salida , pero eso a menudo no es código de máquina.

La mayoría de los programadores nunca escribirán un compilador que genere código de máquina o código de ensamblaje, pero los compiladores personalizados pueden ser muy útiles en muchos proyectos para producir otros resultados.

YACC es uno de esos compiladores que no genera código de máquina ...

Ian
fuente
0

No necesita comenzar con un conocimiento detallado de la semántica de sus lenguajes de entrada y salida, pero es mejor que termine con un conocimiento exquisitamente detallado de ambos, de lo contrario su compilador tendrá errores innecesarios. Entonces, si su entrada es C ++ y su salida es un lenguaje de máquina específico, eventualmente necesitará conocer la semántica de ambos.

Estas son algunas de las sutilezas en la compilación de C ++ al código de máquina: (justo fuera de mi cabeza, estoy seguro de que hay más que estoy olvidando).

  1. ¿De qué tamaño será int? La elección "correcta" aquí es un arte, basado tanto en el tamaño del puntero natural de la máquina, el rendimiento de la ALU para varios tamaños de operaciones aritméticas, y las elecciones realizadas por los compiladores existentes para la máquina. ¿La máquina tiene incluso aritmética de 64 bits? De lo contrario, la suma de enteros de 32 bits debería traducirse en una instrucción, mientras que la suma de enteros de 64 bits debería traducirse en una llamada a la función para hacer la suma de 64 bits. ¿La máquina tiene operaciones de adición de 8 bits y 16 bits o tiene que simular aquellas con operaciones y enmascaramiento de 32 bits (por ejemplo, el DEC Alpha 21064)?

  2. ¿Cuál es la convención de llamada utilizada por otros compiladores, bibliotecas e idiomas en la máquina? ¿Se introducen los parámetros en la pila de derecha a izquierda o de izquierda a derecha? ¿Algunos parámetros van en registros mientras que otros van en la pila? ¿Están los ints y flotantes en diferentes espacios de registro? ¿Los parámetros asignados al registro necesitan ser tratados especialmente en llamadas varargs? ¿Qué registros se guardan y quién se guarda? ¿Se pueden realizar optimizaciones de llamadas de hoja?

  3. ¿Qué hace cada una de las instrucciones de cambio de la máquina? Si pide cambiar un número entero de 64 bits por 65 bits, ¿cuál es el resultado? (En muchas máquinas, el resultado es el mismo que el desplazamiento de 1 bit, en otras el resultado es "0").

  4. ¿Cuáles son las semánticas de consistencia de memoria de la máquina? C ++ 11 tiene una semántica de memoria muy bien definida que impone restricciones en algunas optimizaciones en algunos casos, pero permite optimizaciones en otros casos. Si está compilando un lenguaje que no tiene una semántica de memoria bien definida (como todas las versiones de C / C ++ antes de C ++ 11, y muchos otros lenguajes imprescindibles), tendrá que inventar la semántica de memoria a medida que avanza, y generalmente querrá inventar la semántica de memoria que mejor se adapte a la semántica de su máquina.

Lógica Errante
fuente