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.
compiler
machine-code
Aviv Cohn
fuente
fuente
Respuestas:
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.
fuente
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.
fuente
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.
fuente
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 ...
fuente
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).
¿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)?¿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?
¿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").
¿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.
fuente