La traducción al código C es un hábito muy bien establecido. El C original con clases (y las primeras implementaciones de C ++, luego llamadas Cfront ) lo hicieron con éxito. Varias implementaciones de Lisp o Scheme lo están haciendo, por ejemplo, Chicken Scheme , Scheme48 , Bigloo . Algunas personas traducidos Prolog a C . Y también algunas versiones de Mozart (y ha habido intentos de compilar el código de bytes de Ocaml en C ). El sistema CAIA de inteligencia artificial de J.Pitrat también se inicia y genera todo su código C. Vala también se traduce a C, para el código relacionado con GTK. El libro de Queinnec Lisp In Small Pieces tener un capítulo sobre la traducción a C.
Uno de los problemas al traducir a C son las llamadas recursivas de cola . El estándar C no garantiza que un compilador de C los esté traduciendo correctamente (a un "salto con argumentos", es decir, sin comer pila de llamadas), incluso si en algunos casos, las versiones recientes de GCC (o de Clang / LLVM) hacen esa optimización .
Otro problema es la recolección de basura . Varias implementaciones solo usan el recolector de basura conservador Boehm (que es amigable con C ...). Si desea recolectar código basura (como lo hacen varias implementaciones de Lisp, por ejemplo, SBCL) eso podría ser una pesadilla (le gustaría dlclose
en Posix).
Otro problema más es tratar con las continuas de primera clase y call / cc . Pero son posibles trucos ingeniosos (mira dentro de Chicken Scheme). Acceder a la pila de llamadas podría requerir muchos trucos (pero vea GNU backtrace , etc.). La persistencia ortogonal de las continuaciones (es decir, de pilas o hilos) sería difícil en C.
El manejo de excepciones a menudo es un asunto para emitir llamadas inteligentes a longjmp, etc.
Es posible que desee generar (en su código C emitido) #line
directivas apropiadas . Esto es aburrido y requiere mucho trabajo (querrás que, por ejemplo, produzca un gdb
código más fácil de depurar).
Mi lenguaje específico de dominio lispy MELT (para personalizar o ampliar GCC ) se traduce a C (en realidad a C ++ pobre ahora). Tiene su propio recolector de basura de copia generacional. (Quizás te interese Qish o Ravenbrook MPS ). En realidad, el GC generacional es más fácil en el código C generado por máquina que en el código C escrito a mano (porque personalizará su generador de código C para su barrera de escritura y maquinaria GC).
No conozco ninguna implementación de lenguaje que se traduzca a código C ++ genuino , es decir, que use alguna técnica de "recolección de basura en tiempo de compilación" para emitir código C ++ usando muchas plantillas STL y respetando el lenguaje RAII . (por favor diga si conoce uno).
Lo que es divertido hoy es que (en los escritorios actuales de Linux) los compiladores de C pueden ser lo suficientemente rápidos como para implementar un bucle de lectura-evaluación-impresión interactivo de nivel superior traducido a C: emitirá código C (unos cientos de líneas) a cada usuario interacción, lo fork
compilarás en un objeto compartido, lo que harías entonces dlopen
. (MELT lo está haciendo todo listo, y generalmente es lo suficientemente rápido). Todo esto puede tomar algunas décimas de segundo y ser aceptado por los usuarios finales.
Cuando sea posible, recomendaría traducir a C, no a C ++, en particular porque la compilación de C ++ es lenta.
Si está implementando su lenguaje, también puede considerar (en lugar de emitir código C) algunas bibliotecas JIT como libjit , GNU lightning , asmjit o incluso LLVM o GCCJIT . Si desea traducir a C, a veces puede usar tinycc : compila muy rápidamente el código C generado (incluso en la memoria) para ralentizar el código de la máquina. Pero, en general, desea aprovechar las optimizaciones realizadas por un compilador de C real como GCC
Si traduce a C su lenguaje, asegúrese de construir primero todo el AST del código C generado en la memoria (esto también hace que sea más fácil generar primero todas las declaraciones, luego todas las definiciones y el código de función). Podrías hacer algunas optimizaciones / normalizaciones de esta manera. Además, podría estar interesado en varias extensiones de GCC (por ejemplo, gotos calculados). Probablemente querrá evitar generar funciones C enormes , por ejemplo, de una línea de cientos de miles de C generadas (será mejor que las divida en partes más pequeñas) ya que la optimización de los compiladores C es muy infeliz con funciones C muy grandes (en la práctica, y experimentalmente,gcc -O
El tiempo de compilación de las funciones grandes es proporcional al cuadrado del tamaño del código de la función). Por lo tanto, limite el tamaño de sus funciones C generadas a unos pocos miles de líneas cada una.
Tenga en cuenta que tanto los compiladores Clang (thru LLVM ) como GCC (thru libgccjit ) C & C ++ ofrecen alguna forma de emitir algunas representaciones internas adecuadas para estos compiladores, pero hacerlo podría (o no) ser más difícil que emitir código C (o C ++), y es específico para cada compilador.
Si diseña un lenguaje para traducirlo a C, probablemente desee tener varios trucos (o construcciones) para generar una mezcla de C con su lenguaje. Mi papel DSL2011 MELT: un lenguaje específico de dominio traducido incrustado en el compilador de GCC debería darle consejos útiles.