peta - ¿Cómo se compilan y ejecutan internamente los programas de Haskell?
peta haskell en español (3)
Tengo problemas para entender cómo Haskell (GHC) compila programas y cómo se ejecutan esos programas.
- GHC es el ejemplo canónico de un programa no trivial escrito en Haskell. Sin embargo, partes de GHC no parecen estar escritas en Haskell, a saber, el entorno de ejecución (en C / C--). ¿Porqué es eso? Razones de rendimiento? (Estoy al tanto de este sitio y sus amigos, pero no puedo entenderlos mucho).
- Hablando del entorno de ejecución: ¿Por qué un lenguaje compilado necesita uno? ¿No debería el programa compilado ser un código de máquina y nada más? Por lo que entiendo, un entorno de ejecución es algo similar a una máquina virtual o un intérprete de código de bytes, que se ocupa de algún tipo de código de metadatos y realiza los cálculos reales basados en eso. Entonces, ¿qué hace exactamente el tiempo de ejecución de GHC y por qué es necesario en primer lugar?
- Con respecto a la FFI: ¿Cómo se manejan las llamadas C? Inicialmente, pensé que usar FFI genera un único ejecutable donde Haskell y C se compilan juntos. Sin embargo, leí varias veces que los programas de GHC hacen una llamada del programa a la función C. Esto es especialmente relevante para comprender el problema que tiene la FFI con la programación paralela. Entonces, ¿en qué se diferencian las funciones FFI de las funciones normales de Haskell?
Para compilar y ejecutar un lenguaje de programación en hardware en stock, necesita varias cosas:
- un compilador para traducir su idioma de origen en código ensamblador ejecutable por el host nativo
- una biblioteca de soporte (también conocida como runtime) para servicios de lenguaje primitivo, como administración de memoria, IO y administración de subprocesos. Cosas que deben aprovecharse de los servicios del sistema de nivel inferior.
C, Java y GHC Haskell son ejemplos de tales sistemas. En el caso de GHC, toda la arquitectura se describe aquí . Las piezas también se describen individualmente, y en detalle.
- El compilador (escrito en Haskell), traduce Haskell a C, ensamblado, código de bits LLVM y otros formatos. La estrategia que utiliza se describe mejor aquí: Implementación de lenguajes funcionales perezosos en el hardware original: la máquina G sin etiquetas sin espinas .
Los servicios de tiempo de ejecución (también conocidos como "el tiempo de ejecución de GHC") se describen en varios documentos:
Puedo ofrecer algo de precisión sobre lo que es un runtime.
Una máquina virtual es "un" tipo de tiempo de ejecución, pero no la única. Un sistema de tiempo de ejecución es simplemente el entorno (y el conjunto de servicios) que su programa puede asumir que estaría presente durante su ejecución. Incluso los lenguajes de muy bajo nivel como C y C ++ tienen sistemas de tiempo de ejecución (piense en malloc ... alguien / algo está haciendo la asignación por usted, o incluso la división por cero).
En general, los idiomas de nivel superior tienen un tiempo de ejecución más rico (lo que significa que el tiempo de ejecución ofrece más servicios al programa de ejecución); abarcan desde la administración de memoria (por ejemplo, recolección de basura) a la infraestructura de reflexión / introspección (piense en ruby, etc.) hasta la verificación de límites de matriz, pero casi todos los lenguajes tienen algún tipo de sistema de tiempo de ejecución (aunque solo sea el sistema operativo).
1: ¿Por qué el RTS no está escrito en Haskell?
Porque hace cosas de bajo nivel que no se pueden expresar en Haskell. Al igual que el kernel de Linux es un sistema para ejecutar programas en C, y sin embargo, algunas partes del kernel de Linux están escritas en ensamblador, no en C.
2: ¿Por qué un programa compilado necesita un entorno de ejecución? Por lo que entiendo, eso es algo así como el intérprete de bytecode de Java.
GHCi usa algo casi exactamente igual que el intérprete de bytecode de Java. Los programas compilados de GHC no lo hacen; El programa compilado es un código de máquina en bruto.
Más bien, el Haskell RTS es más como un tipo de mini-OS. Realiza la gestión de la memoria, realiza la gestión de subprocesos, realiza ciertos aspectos del manejo de excepciones, realiza la gestión de transacciones Todos los programas de Haskell se ejecutan bajo este mini-OS.
(Es un poco como si se compilara un programa en C, es un código de máquina en bruto, pero todavía no puede ejecutarlo sin un sistema operativo como Windows o Linux o algo así).
Por ejemplo, cada vez que un programa Haskell se queda sin memoria, el programa Haskell deja de ejecutarse y el recolector de basura comienza a ejecutarse. El recolector de basura intenta liberar algo de memoria y, una vez que lo ha hecho, el programa Haskell comienza a ejecutarse nuevamente.
Cada programa Haskell compilado tiene una copia de este programa de recolector de basura, que es solo una parte del Haskell RTS. Del mismo modo, varios subprocesos de Haskell pueden ejecutarse dentro de un subproceso del sistema operativo, por lo que el RTS tiene un programador de subprocesos dentro de él. Podría seguir...
3: ¿Cómo se maneja FFI? Pensé que las cosas estaban compiladas juntas.
Todo está compilado [o, más bien, unido]. Si escribe un programa C, una función C puede llamar a otra función C. Cuando Haskell llama a una función C, es muy parecido a cualquier otra función que llame a esa función C. Sin embargo, dependiendo de lo que haga la llamada a la función, hay algunas cosas que suceden en el lado de Haskell, que pueden agregar cierta sobrecarga.