2010-03-01 14 views
7

Actualmente estoy trabajando en la implementación del compilador moderno de Andrew Appel en Java, y estoy justo en el punto donde construyo la representación intermedia de bajo nivel.Generando ensamblaje para un procesador x86

Inicialmente, había decidido apuntar a la JVM e ignorar todas las cosas de bajo nivel de la máquina, pero con el interés de aprender cosas de las que no sé mucho, he tenido un cambio de opinión. Esto cambia mi IR, porque dirigirme a la JVM me permite (más o menos) agitar las manos para hacer una llamada a un método o construir un objeto.

El libro de Appel no entra en detalles sobre una arquitectura de máquina específica, por lo que me gustaría saber dónde puedo encontrar todo lo que necesito saber para ir más allá.

Las cosas que estoy actualmente cuenta que necesito saber son:

  • ¿Qué conjunto de instrucciones para su uso. Tengo dos computadoras portátiles que podría desarrollar; ambos tienen procesadores Core 2 Duo. Mi comprensión actual es que los procesadores x86 usan principalmente el mismo conjunto de instrucciones, pero no son exactamente iguales.

  • Si el sistema operativo afecta al paso de generación de código de compilación, o si es completamente dependiente del procesador. Por ejemplo, sé que algo diferente es generar código para ejecutar en una plataforma de 32 bits frente a una de 64 bits.

  • Cómo se organizan los marcos de pila y demás. Cuándo usar registros vs. poner parámetros en la pila, llamador-save vs. callee-save, todo eso. Pensé que esto se describiría junto con el conjunto de instrucciones, pero hasta ahora no he visto esta información en particular. Tal vez estoy malinterpretando algo aquí?

Los enlaces a los recursos en lugar de las respuestas son perfectamente bienvenidos.

+0

Votación para cerrar como demasiado amplia. –

Respuesta

5

La mayor parte del conjunto de instrucciones x86 es común a todos los procesadores - es una apuesta razonablemente seguro de que los procesadores ambos tienen el mismo conjunto de instrucciones, excepto posiblemente para obtener instrucciones SIMD que probablemente no serán muy útiles a la hora de implementando un compilador simple (estas instrucciones se usan normalmente para hacer que las aplicaciones multimedia y similares vayan más rápido). El conjunto de instrucciones se enumera en Intel's manuals - 2A y 2B, en particular, tienen una lista completa de instrucciones y su comportamiento, aunque vale la pena echarle un vistazo a los otros volúmenes.

Al generar código de espacio de usuario, la elección del sistema operativo es importante cuando se trata de llamadas de sistema. Por ejemplo, si quieres un programa para demostrar algo al terminal de 64 bits de Linux, es necesario hacer una llamada al sistema por:

  • cargar el valor 1 en el registro rax para indicar que esto es una llamada write sistema.
  • cargar el valor 1 en el registro rdi para indicar la salida estándar se debe utilizar (1 es el descriptor de fichero para la salida estándar)
  • cargar la dirección de comienzo de lo que desea imprimir en el registro rsi
  • cargar la longitud de lo que desea imprimir en el registro rdx
  • ejecutando la instrucción syscall una vez que los registros (y la memoria) se hayan configurado.

El valor de retorno de write se almacena en rax.

un sistema operativo diferente podría tener un número de llamada de sistema diferente para write, podría tener una forma diferente de pasar en argumentos (x86-64 sistema Linux siempre llama a utilizar rdi, rsi, rdx, r10, r8, y en ese r9 orden para los parámetros, con el número de llamada del sistema en rax), y podría tener diferentes llamadas al sistema por completo.

La convención para la función ordinaria llama en Linux es similar - el orden de los registros es rdi, rsi, rdx, rcx, r8 y r9 (por lo que todo el mismo, excepto que se usa en lugar de rcxr10), con más argumentos en la pila y un valor de retorno en rax. De acuerdo con this page, registra rbp, rbx, y r12 hasta r15 debe ser preservado a través de llamadas a funciones. Por supuesto, puede crear su propia convención (a menos que haga una llamada al sistema), pero eso hace que sea más difícil llamar a partir de un código generado o escrito por otros.

+0

Gracias, Michael: esta respuesta también fue muy útil. Ojalá pudiera aceptarlo también; mi propia culpa por combinar demasiadas preguntas. +1, sin embargo. – danben

+0

En realidad, al leer esto por segunda vez, creo que esto responde a todas mis preguntas más a fondo. – danben

3

Cómo se organizan los marcos de pila y otros tales como . Cuándo utilizar los registros frente a poniendo los parámetros en la pila, llamador-save vs. callee-save, todos de eso. Pensé que esto sería descrito junto con el conjunto de instrucciones , pero hasta ahora no he visto esta información en particular en cualquier lugar. ¿Tal vez estoy malentendiendo algo aquí?

En general, no hay respuestas correctas a estas preguntas. Puede usar las convenciones de llamada que desee ... a menos que desee interoperar con el código de otras personas. Para la interoperabilidad, los compiladores se estandarizan en las interfaces binarias de aplicaciones. Según entiendo, el Itanium C++ ABI se ha convertido en un estándar popular en los últimos años. Intenta comenzar allí.

+0

Gracias, Nathan. No entiendo muy bien el propósito de Itanium C++ ABI en lo que respecta a mis propósitos (por ejemplo, ¿qué papel juega C++ al desarrollar un compilador para otro idioma?); sin embargo, este enlace finalmente me llevó a las diversas convenciones de llamadas x86 (cdecl, etc.) que es lo que estaba buscando. – danben

1

No puedo responder a todas sus preguntas; pero

  • conjunto de instrucciones x86 básico es compatible a través de la familia x86 de procesadores. No está planeando implementar extensiones específicas, ¿verdad?
  • No creo que su sistema operativo o arquitectura asuntos mucho para el código generación
  • respuesta predeterminada para compilador de todo lo relacionado es el Dragon book. ¿Ya miró ?
Cuestiones relacionadas