Dec 22, 2023
Un lenguaje ensamblador alfabetizado
Una edición reciente de The Chip Letter [de Babbage] analiza la oscuridad del lenguaje ensamblador. Señala, y creo que correctamente, que el lenguaje ensamblador se lee más que se escribe, pero casi
Una edición reciente de The Chip Letter [de Babbage] analiza la oscuridad del lenguaje ensamblador. Señala, y creo que tiene razón, que el lenguaje ensamblador se lee con más frecuencia que se escribe, pero casi todos se ven obstaculizados por la oscuridad que queda de los días en que las tarjetas perforadas tenían 80 columnas y lo único que se podía manejar era un símbolo de seis letras. en el espacio de memoria limitado de la computadora. Por ejemplo, sin buscarlo, ¿qué hace la instrucción ARM FJCVTZS? El nombre completo de la instrucción es Convertir Javascript de punto flotante a redondeo de punto fijo con signo hacia cero. No es muy útil.
Pero se me ocurrió que nada te impide escribir un ensamblador alfabetizado que sea más fácil de leer. En primer lugar, la mayoría de los compiladores de C aceptarán algún tipo de declaración asm, y probablemente podría gestionarla con macros y construcción de cadenas en tiempo de compilación. Sin embargo, creo que hay una mejor posibilidad.
Como a veces desarrollo nuevas arquitecturas de CPU, tengo un ensamblador cruzado universal que, sinceramente, es un truco feo, pero funciona bastante bien. He hablado de ello antes, pero si no quieres leer toda la publicación al respecto, utiliza algunos trucos simples para convertir formatos de lenguaje ensamblador de apariencia estándar en código C que luego se compila. La ejecución del programa resultante genera el lenguaje de máquina deseado en el formato de archivo deseado. Es muy fácil de configurar y, en el medio, hay un bonito programa en C que emite código de máquina. No es mucho más legible que el ensamblaje sin formato, pero no deberías tener que verlo. Pero, ¿qué pasaría si comenzamos el proceso allí y hacemos que el formato sea legible?
En el corazón del sistema hay un programa C que reside en soloasm.c. Maneja las opciones de la línea de comandos y la generación de archivos de salida. Llama a una función externa, genasm, con un único argumento entero. Cuando ese argumento se establece en 1, indica que el ensamblador está en su primera pasada y solo necesita completar los valores de las etiquetas con números reales. Si el pase es un 2, significa que en realidad completa la matriz que contiene el código.
Esa matriz se define en la instrucción __solo_info (soloasm.h). Incluye el tamaño de la memoria, un puntero al código, el tamaño de palabra del procesador, las direcciones inicial y final y un indicador de error. Normalmente, el sistema convierte la entrada del lenguaje ensamblador en un montón de llamadas a funciones que escribe dentro de la función genasm. Pero en este caso, quiero reutilizar soloasm.c para crear un lenguaje ensamblador competente.
Escribí todo esto hace mucho tiempo, pero quería que la creación de un ensamblado alfabetizado fuera más fácil, así que decidí hacer una conversión a C++ con poco esfuerzo. Esto le permite utilizar estructuras de datos agradables para la tabla de símbolos, por ejemplo. Sin embargo, no utilicé todas las funciones de C++ que podía tener, simplemente por cuestión de tiempo.
La clase base es razonablemente independiente del procesador y, como ejemplo, he proporcionado un ensamblador RCA 1802 competente. Solo una prueba de concepto, por lo que probablemente podría nombrar las instrucciones de manera un poco más consistente, y hay mucho espacio para otras mejoras, pero deja claro mi punto.
Aquí hay un extracto de un programa de luz parpadeante escrito para el 1802 usando la sintaxis estándar del ensamblador:
Ahora aquí está exactamente lo mismo escrito para el ensamblador alfabetizado:
Bueno, es cierto que hay comentarios y símbolos, pero aún así. Puedes descargar ambos archivos si quieres comparar. También puedes encontrar el proyecto completo online.
La idea es sencilla. Cada función simplemente llena una matriz con el byte o bytes necesarios. Es cierto que el 1802 es bastante simple. Sería más difícil hacer esto para un procesador moderno con muchas instrucciones y modos complejos. Pero no imposible.
Puedes hacer muchas cosas para hacer la vida más fácil, tanto mientras programas como mientras configuras las instrucciones. Por ejemplo, si quisieras 100 instrucciones NOP, podrías escribir:
para (int i = 0; i < 100; i++) NOP();
Por otro lado, NOP tiene un argumento opcional que lo hará por usted. Puede utilizar libremente el compilador de C++ y el preprocesador de macros para hacerle la vida más fácil. Por ejemplo, una tarea común en el 1802 es poner un valor constante como una etiqueta en un registro. El archivo lit1802.h tiene una macro para facilitar esto:
Evidentemente, puedes cambiar los nombres a tu gusto o tener tantos alias como quieras. No olvide que la sobrecarga de llamadas a funciones, como llamar a Load_R_Label, se produce en el momento de la compilación. Terminas con el mismo código de máquina de cualquier manera.
El ensamblador es de dos pasadas. El primer paso solo define etiquetas. El segundo paso genera código real. Esto haría difícil, por ejemplo, crear una instrucción de salto inteligente que usara una rama cuando el objetivo estaba cerca y un salto largo cuando estaba lejos, a menos que no te importe rellenar la rama con un NOP, lo que no ahorraría espacio. pero podría ahorrar tiempo de ejecución.
Habría otras complicaciones para un procesador moderno. Por ejemplo, no intentar asignar todo el espacio de memoria ni generar resultados reubicables. Pero esto es verdaderamente una prueba de concepto. Ninguna de esas cosas es imposible, simplemente son más trabajo.
He escrito y leído docenas de lenguajes ensambladores durante años, por lo que me siento bastante cómodo con el status quo y es poco probable que yo mismo use litasmo. Sin embargo, pensé que el punto [de Babbage] estaba bien planteado. Si desea que el ensamblaje sea más legible, existen beneficios y esto demuestra que no tiene por qué ser tan difícil de lograr. También puedes escribir un desensamblador litasm para convertir el código objeto a este tipo de formato.
¿Quieres saber más sobre el Ensamblador Universal? Si prefiere abordar el ensamblaje práctico x86-64, conocemos un buen lugar para comenzar.