Reglas generales para escribir un compilador X en Z en Y

9

Supongamos que X es el idioma de entrada, Z es el idioma de salida, luego f es el compilador, que está escrito en el lenguaje Y.

f = X -> Z

Como f es solo un programa, creo que Y puede ser cualquier lenguaje, ¿verdad? Entonces podemos tener compiladores f1, f2, cada uno escrito en Y1, Y2.

f1 = f Y1    
f2 = f Y2

g = Z -> M
h = g . f    # We get a compiler X -> M

Tome el compilador cpython por ejemplo, X es Python, Z es el código de Python VM, Y es C.

cpython = Python -> PythonVMCode C
interpreter = PythonVMCode -> Nothing
interpreter2 = PythonVMCode -> MachineCode

Las fuentes de Python se compilan en el código de Python VM, los archivos .pyc, luego son interpretados por el intérprete. Parece que es posible que exista un compilador que pueda hacer directamente Python -> MachineCode, aunque es muy difícil de implementar:

   hardpython = interpreter2 . cpython 

También podemos escribir otro compilador para hacer el trabajo Python -> PythonVMCode, en otro idioma, digamos Python.

mypython = Python -> PythonVMCode Python
mypython2 = Python -> PythonVMCode Ruby

Ahora, aquí está el complicado ejemplo de PyPy. Solo soy un novato de PyPy, corrígeme si me equivoco:

Documento de PyPy http://doc.pypy.org/en/latest/architecture.html#pypy-the-translation-framework

Nuestro objetivo es proporcionar una posible solución al problema de los implementadores del lenguaje: tener que escribir l * o * p intérpretes para l lenguajes dinámicos y p plataformas con o decisiones de diseño cruciales.

Podemos pensar que l es X, p es Y. Existe un programa que traduce todos los programas RPython a C:

 rpython_compiler = RPython -> C  Python

 pypy = Python -> Nothing RPython

 translate = compile the program pypy written in RPython using rpython_compiler

 py2rpy = Python -> RPython  Python
 py2c = Python -> C Python 
 py2c = rpython_compiler . py2rpy

Los programas RPython son como las instrucciones de VM, rpython_compiler es la VM.

q1. Pypy es el intérprete, un programa RPython que puede interpretar el código Python, no hay lenguaje de salida, por lo que no podemos considerarlo como un compilador, ¿verdad?

Adicional:

  • Acabo de descubrir que, incluso después de la traducción, pypy sigue siendo un intérprete, solo que esta vez escrito en C.
  • Si observamos profundamente el intérprete pypy, creo que debe existir algún tipo de compilador, que compila las fuentes de Python para algún AST, luego ejecuta

Me gusta esto:

compiler_inside_pypy = Python -> AST_or_so

q2. ¿Puede existir el compilador py2rpy, transformando todos los programas de Python a RPython? En qué idioma está escrito es irrelevante. En caso afirmativo, obtenemos otro compilador py2c. ¿Cuál es la diferencia entre pypy y py2rpy en la naturaleza? ¿Py2rpy es mucho más difícil de escribir que pypy?

q3. ¿Hay algunas reglas generales o teorías disponibles sobre esto?

Más compiladores:

gcc_c = C -> asm? C  # not sure, gimple or rtl?
g++ =   C++ -> asm? C
clang = C -> LLVM_IR  C++
jython = Python -> JVMCode java
ironpython = Python -> CLI C#

q4. Dado f = X -> Z, un programa P escrito en X. Cuando queremos acelerar P, ¿qué podemos hacer? Posibles formas:

  • reescribir P en un algoritmo más eficiente

  • reescribe f para generar una mejor Z

  • si se interpreta Z, escriba un mejor intérprete de Z (¿PyPy está aquí?)

  • acelerar los programas escritos en Z recursivamente

  • obtener una mejor máquina

PD. Esta pregunta no se trata de los aspectos tecnológicos de cómo escribir un compilador, sino de la viabilidad y complejidad de escribir un compilador de cierto tipo.

jaimechen
fuente
No está directamente relacionado, pero es un concepto similar: en.wikipedia.org/wiki/Supercompilation
SK-logic
1
No estoy seguro de que esta pregunta realmente se adapte a Stack Overflow, especialmente porque hay tantas preguntas secundarias, pero aún admiro la idea de esto.
44
A pesar de lo que le hayan enseñado, no se requiere un AST, es simplemente una estrategia que utilizan algunos compiladores.
1
Probablemente esto pertenece a cstheory.stackexchange.com
9000
3
La implementación Python de PyPy, como la mayoría de los "intérpretes", es realmente un compilador de bytecode y un intérprete para ese formato de bytecode en uno.

Respuestas:

4

q1. pypy es el intérprete, un programa RPython que puede interpretar el código Python, no hay lenguaje de salida, por lo que no podemos considerarlo como un compilador, ¿verdad?

PyPy es similar a CPython, ambos tienen un compilador + intérprete. CPython tiene un compilador escrito en C que compila el código de bytes Python to Python VM y luego ejecuta el bytecode en un intérprete escrito en C. PyPy tiene un compilador escrito en RPython que compila el código de bytes Python to Python VM, luego lo ejecuta en PyPy Interpreter escrito en RPython.

q2. ¿Puede existir el compilador py2rpy, transformando todos los programas de Python a RPython? En qué idioma está escrito es irrelevante. En caso afirmativo, obtenemos otro compilador py2c. ¿Cuál es la diferencia entre pypy y py2rpy en la naturaleza? ¿Py2rpy es mucho más difícil de escribir que pypy?

¿Puede existir un compilador py2rpy? Teóricamente si. La integridad de Turing lo garantiza.

Un método para construir py2rpyes simplemente incluir el código fuente de un intérprete de Python escrito en RPython en el código fuente generado. Un ejemplo del compilador py2rpy, escrito en Bash:

// suppose that /pypy/source/ contains the source code for pypy (i.e. Python -> Nothing RPython)
cp /pypy/source/ /tmp/py2rpy/pypy/

// suppose $inputfile contains an arbitrary Python source code
cp $inputfile /tmp/py2rpy/prog.py

// generate the main.rpy
echo "import pypy; pypy.execfile('prog.py')" > /tmp/py2rpy/main.rpy

cp /tmp/py2rpy/ $outputdir

ahora, cada vez que necesite traducir un código de Python a código de RPython, llama a este script, que produce, en $ outputdir, un RPython main.rpy, el código fuente del intérprete de Python de RPython y un blob binario prog.py. Y luego puede ejecutar el script RPython generado llamando rpython main.rpy.

(nota: dado que no estoy familiarizado con el proyecto rpython, la sintaxis para llamar al intérprete rpython, la capacidad de importar pypy y hacer pypy.execfile, y la extensión .rpy está puramente inventada, pero creo que entiendes el punto)

q3. ¿Hay algunas reglas generales o teorías disponibles sobre esto?

Sí, cualquier idioma de Turing Complete se puede traducir teóricamente a cualquier idioma de Turing Complete. Algunos idiomas pueden ser mucho más difíciles de traducir que otros idiomas, pero si la pregunta es "¿es posible?", La respuesta es "sí"

q4. ...

No hay duda aquí.

Lie Ryan
fuente
Su compilador py2rpy es realmente inteligente. Me lleva a otra idea. 1. ¿Pypy tiene que estar escrito en RPython en su compilador? Todo lo que necesitas es algo que pueda interpretar archivos Python, ¿verdad? 2. os.system ('python $ inputfile') también puede funcionar si es compatible con RPython. No estoy seguro de si todavía se puede llamar compilador, al menos no literalmente.
¿Pypy todavía usa la máquina virtual Python? Ahora está claro. pypy_the_compiler = Python -> PythonVMCode RPython, pypy_the_interpreter = PythonVMCode -> Nothing RPython, cpython_the_compiler = Python -> PythonVMCode C, cpython_the_interpreter = PythonVMCode -> Nothing C
@jaimechen: Does pypy have to be written in RPython in your compiler?No, no es necesario que esté escrito en RPython, pero RPython debe poder decirle al "intérprete auxiliar" / "tiempo de ejecución" que ejecute un código de Python. Sí, es cierto, este no es un "compilador" en sentido práctico, pero es una prueba constructiva de que es posible escribir Python -> RPython. Is pypy still using the Python VM?Creo que pypy no usa CPython en absoluto (podría estar equivocado), en cambio PyPy tiene su propia implementación de "Python VM" que está escrita en RPython.
Lie Ryan
@jaimechen: un compilador más práctico posiblemente podría analizar el archivo de entrada en busca de secuencias de código que sepa cómo compilar y compilar estas por separado y también una forma de saltar de un lado a otro entre el Python "recompilado a RPython" y el "intérprete- ayudado "Python. También puede usar técnicas comúnmente utilizadas en la compilación JIT para detectar si una entrada particular puede producir una salida diferente debido a las diferencias en la semántica de RPython y Python y el retroceso a la interpretación en esos casos. Todo eso es sofisticación que podría verse en un Python -> RPythoncompilador más práctico .
Lie Ryan
Tal vez debería agregarse una restricción aquí: transformar la máquina de estado X en la máquina de estado Z, sin la ayuda de una tercera máquina existente. Este es el caso cuando X es completamente nuevo, hasta ahora no existe ningún compilador o intérprete.
jaimechen
2

Para responder solo a q2, hay un libro compilador de William McKeeman en el que se explora la teoría de compiladores para el lenguaje X escrita en el lenguaje Y que produce el lenguaje de salida Z a través de un sistema de diagramas T. Publicado en la década de 1970, título no a mano, lo siento.

usuario207421
fuente
Sí, ya está, gracias. en.wikipedia.org/wiki/Tombstone_diagram
jaimechen
1

q1. Generalmente, un intérprete no es un compilador. La diferencia clave entre un compilador y un intérprete es que un intérprete comienza de nuevo, con código fuente en el idioma fuente, cada vez. Si su pypy era en cambio pyAST, o código pyP, y luego tenía un intérprete de AST o código P, entonces podría llamar a pyAST un compilador. Así es como funcionaba el antiguo compilador PASCAL de UCSD (así como algunos otros): compilaron algún código P, que se interpretó cuando se ejecutó el programa. (Incluso .NET proporciona algo como esto, cuando la compacidad del código objeto generado es mucho más importante que la velocidad).

q2. Sí, por supuesto. Ver UCSD PASCAL (y muchos otros).

q3. Profundiza en los textos clásicos de la informática. Lea sobre PASCAL concurrente, por Per Brinch-Hansen (si la memoria me sirve). Mucho se ha escrito sobre compiladores y generación de código. Generar un pseudocódigo independiente de la máquina suele ser mucho más fácil que generar código de máquina: el pseudocódigo generalmente no tiene las peculiaridades que las máquinas reales contienen invariablemente.

q4. Si desea que su objeto generado se ejecute más rápido, puede hacer que el compilador sea más inteligente, para una mejor optimización. Si su objeto es interpretado, considera empujar operaciones más complejas hacia pseudoinstrucciones primitivas (CISC vs. RISC es la analogía), entonces hace todo lo posible para optimizar el frack de su intérprete.

Si desea que su compilador se ejecute más rápido, debe mirar TODO lo que hace, incluido repensar su código fuente. Después de cargar el compilador en sí, la parte más lenta de la compilación es SIEMPRE leer el código fuente en el compilador. (Considere C ++ por ejemplo. Todas las demás cosas son relativamente iguales, un compilador que tiene que reducir 9,000 (o quizás 50,000) líneas de #incluir archivos para compilar un simple programa "Hola, Mundo" nunca será tan rápido como uno eso solo tiene que leer cuatro o cinco líneas).

No recuerdo dónde lo leí, pero el compilador original de Oberon en ETH-Zurich tenía un mecanismo de tabla de símbolos muy sofisticado, bastante elegante. El punto de referencia de Wirth para el rendimiento del compilador fue el tiempo que le tomó al compilador compilarse. Una mañana, entró, sacó la hermosa tabla de símbolos de árbol múltiple con múltiples enlaces y la reemplazó con una simple matriz lineal y búsquedas lineales rectas. Los estudiantes graduados en su grupo fueron CONMOCIONADOS. Después del cambio, el compilador fue más rápido, porque los módulos que estaba compilando siempre eran lo suficientemente pequeños como para que el monstruo elegante impusiera más sobrecarga total que la matriz lineal y la búsqueda lineal.

John R. Strohm
fuente
1
Gracias. Un compilador 'compila', mientras que un intérprete 'ejecuta', ¿puede haber más información sobre los dos tipos de programas, como si sus tipos fueran diferentes?
jaimechen
1

Sus preguntas como se indica me llevan a creer que lo que realmente quiere / necesita es una explicación de qué es un compilador, qué es un intérprete y las diferencias entre los dos.

Un compilador asigna un programa escrito en lenguaje X a un programa funcionalmente equivalente escrito en lenguaje Y. Como ejemplo, un compilador de Pascal a C podría compilar

function Square(i: Integer)
begin
    Square := i * i
end

a

int Square(int i)
{
    return i * i;
}

La mayoría de los compiladores compilan 'hacia abajo', por lo que compilan lenguajes de programación de nivel superior en lenguajes de nivel inferior, siendo el último lenguaje de nivel inferior el código de máquina.

La mayoría de los compiladores compilan directamente en código máquina, pero algunos (especialmente los lenguajes Java y .NET) compilan en 'bytecode' ( Java bytecode y CIL ). Piense en bytecode como código de máquina para una computadora hipotética. Este bytecode luego se interpreta o JITted cuando se ejecuta (más sobre eso más adelante).

Un intérprete ejecuta un programa escrito en algún lenguaje Z. Un intérprete lee un programa poco a poco, ejecutándolo a medida que avanza. Por ejemplo:

int i = 0;
while (i < 1)
{
    i++
}
return i;

Imagine al intérprete mirando esa línea de programa por línea, examinando la línea, ejecutando lo que hace, mirando la siguiente línea y así sucesivamente.

El mejor ejemplo de un intérprete es la CPU de su computadora. Interpreta el código de la máquina y lo ejecuta. El funcionamiento de la CPU se especifica por cómo está físicamente construido. El funcionamiento de un programa de intérprete se especifica por el aspecto de su código. Por lo tanto, la CPU interpreta y ejecuta el programa de intérprete, que a su vez interpreta y ejecuta su entrada. Puede encadenar intérpretes de esta manera.

Un JITter es un compilador Just-In-Time. Un JITter es un compilador. La única diferencia es el tiempo que se ejecuta: la mayoría de los programas se escriben, compilan, envían a sus usuarios y luego se ejecutan, pero el código de bytes de Java y CIL se envían primero a sus usuarios, y luego, justo antes de que se ejecuten, se compilan en la máquina código de sus usuarios.

C # -> (compilar) -> CIL -> enviado al cliente -> (compilar justo antes de la ejecución) -> código de máquina -> (ejecutar)

Lo último que querrá saber es la integridad de Turing ( enlace ). Un lenguaje de programación es Turing Complete si puede calcular todo lo que puede hacer una ' máquina de Turing ', es decir, es al menos tan 'poderoso' como una máquina de Turing. La tesis de Church-Turing establece que una máquina de Turing es al menos tan poderosa como cualquier máquina que podamos construir. Se deduce que cada lenguaje completo de Turing es exactamente tan poderoso como la máquina de Turing y, por lo tanto, todos los idiomas completos de Turing son igualmente poderosos.

En otras palabras, mientras su lenguaje de programación esté Turing completo (casi todos lo están), no importa qué idioma elija, ya que todos pueden calcular las mismas cosas. Esto también significa que no es muy relevante el lenguaje de programación que elija para escribir su compilador o su intérprete. Por último, pero no menos importante, significa que siempre puedes escribir un compilador del lenguaje X a Y si X e Y están completos en Turing.

Tenga en cuenta que estar Turing completo no dice nada sobre si su lenguaje es eficiente, ni sobre todos los detalles de implementación de su CPU y otro hardware, o la calidad del compilador que usa para el idioma. Además, su sistema operativo podría decidir que su programa no tiene los derechos para abrir un archivo, pero eso no impide su capacidad de calcular nada; deliberadamente no definí la informática, ya que eso requeriría otro muro de texto.

Alex ten Brink
fuente