¿Cómo cargar dinámicamente a menudo el código c regenerado rápidamente?

Quiero ser capaz de generar código C dinámicamente y volver a cargarlo rápidamente en mi progtwig C en ejecución.

Estoy en Linux, ¿cómo podría hacerse esto?

¿Se puede volver a comstackr y volver a cargar un archivo .so de la biblioteca en Linux en tiempo de ejecución?

¿Podría ser comstackdo sin producir un archivo .so, podría la salida comstackda de alguna manera ir a la memoria y luego volver a cargarse? Quiero volver a cargar el código comstackdo rápidamente.

Lo que quiere hacer es razonable, y estoy haciendo exactamente eso en MELT (un lenguaje específico de dominio de alto nivel para extender GCC; MELT se comstack en C, a través de un traductor escrito en MELT).

En primer lugar, al generar código C (u otros muchos lenguajes de origen), un buen consejo es mantener algún tipo de árbol de syntax abstracta (AST) en la memoria. Construya primero todo el AST del código C generado y luego emítalo como syntax C. No piense en su marco de generación de código sin un AST explícito (en otras palabras, la generación de código C con un montón de printf es una pesadilla de mantenimiento, quiere tener alguna representación intermedia).

En segundo lugar, la razón principal para generar código C es aprovechar un buen comstackdor de optimización (otra razón es la portabilidad y la ubicuidad de C). Si no le importa el rendimiento del código generado (y TCC comstack muy rápidamente C en un código de máquina muy ingenuo y lento) podría usar otros enfoques, por ejemplo, utilizando algunas bibliotecas JIT como el rayo Gnu (generación muy rápida de máquina lenta) código), Gnu Libjit o ASMJIT (código de máquina generado es un poco mejor), LLVM o GCCJIT (buen código de máquina generado, pero tiempo de generación comparable a un comstackdor).

Por lo tanto, si genera código C y desea que se ejecute rápidamente, el tiempo de comstackción del código C no es insignificante (ya que probablemente bifurcaría un comando gcc -O -fPIC -shared para hacer que algún objeto compartido foo.so fuera generado foo.c ). Por experiencia, generar código C toma mucho menos tiempo que comstackrlo (con gcc -O ). En MELT, la generación de código C es más de 10 veces más rápida que su comstackción por GCC (y generalmente 30 veces más rápido). Pero las optimizaciones hechas por un comstackdor de C lo valen.

Una vez que emitió su código C, bifurcó su comstackción en un objeto compartido dlopen , puede dlopen . No seas tímido, mi ejemplo de manydl.c demuestra que en Linux puedes desplegar una gran cantidad de objetos compartidos (muchos cientos de miles). El verdadero cuello de botella es la comstackción del código C generado. En la práctica, no es necesario que dlclose Linux (a menos que esté codificando un progtwig de servidor que dlclose ejecutarse durante meses); un módulo compartido no utilizado puede mantenerse prácticamente dlopen y, en su mayoría, se está filtrando el espacio de direcciones de proceso (que es un recurso barato), ya que la mayor parte de ese .so no utilizado se intercambiaría. dlopen se realiza rápidamente, lo que toma tiempo es la comstackción de una fuente C, porque realmente desea que el comstackdor C realice la optimización.

Puede usar muchos otros enfoques diferentes, por ejemplo, tener un intérprete de códigos de bytes y generar para ese bytecode, usar Common Lisp (por ejemplo, SBCL en Linux que comstack dinámicamente a código de máquina), LuaJit, Java, MetaOcaml, etc.

Como otros sugirieron, no te importa mucho el tiempo para escribir un archivo C, y en la práctica se mantendrá en el caché del sistema de archivos (mira también esto ). Y escribirlo es mucho más rápido que comstackrlo, por lo que permanecer en la memoria no vale la pena. Use algunos tmpfs si le preocupan los tiempos de E / S.

adenda

Tu preguntaste

¿Se puede volver a comstackr y volver a cargar un archivo .so biblioteca en Linux en tiempo de ejecución?

Por supuesto que sí: debe teclear un comando para construir la biblioteca a partir del código C generado (por ejemplo, un gcc -O -fPIC -shared generated.c -o generated.so , pero podría hacerlo indirectamente, por ejemplo, ejecutando un make -j , especialmente si el generated.so es lo suficientemente grande como para hacerlo relevante para dividir el archivo generated.c en varios archivos generated.c en C!) y luego carga dinámicamente su biblioteca con dlopen (dando una ruta completa como /some/file/path/to/generated.so , y probablemente la bandera RTLD_NOW , a él) y tienes que usar dlsym para encontrar símbolos relevantes dentro. No piense en volver a cargar (una segunda vez) el mismo generated.so , mejor para emitir un único archivo generated1.c (luego generated2.c etc …) C, luego para comstackrlo en un único generated1.so (la segunda vez para generated2.so , etc …) luego para dlopen (y esto se puede hacer cientos de miles de veces). Es posible que desee tener, en los archivos generated*.c emitidos emitidos, algunas funciones de constructor que se ejecutarán en el tiempo dlopen del generated*.so dlopen generated*.so

Su progtwig de aplicación base debería haber definido una convención sobre el conjunto de nombres dlsym -ed (generalmente funciones) y cómo se llaman. Solo debería invocar directamente las funciones en los punteros de función generated*.so dlsym thru dlsym . En la práctica, usted decidiría, por ejemplo, que cada generated*.c define una función void dynfoo(int) e int dynbar(int,int) y utiliza dlsym con "dynfoo" y "dynbar" y los llama a través de punteros a funciones (devuelto por dlsym ) También debe definir las convenciones de cómo y cuándo se dynfoo estos dynfoo y dynbar . Mejor vinculará su aplicación base con -rdynamic para que sus archivos generated*.c puedan llamar a las funciones de su aplicación.

No quiere que su generated*.so vuelva a definir los nombres existentes . Por ejemplo, no desea redefinir malloc en su generated*.c y esperar que todas las funciones de asignación de montón usen mágicamente su nueva variante (que probablemente no funcionará, y si lo hiciera, sería peligroso).

Es probable que no se moleste en dlclose un objeto compartido cargado dinámicamente, excepto en la limpieza de la aplicación y el tiempo de salida (pero no me dlclose en absoluto por dlclose ). Si dlclose un archivo generated*.so dlclose generated*.so dinámicamente, asegúrese de que no se use nada: no existen punteros, ni siquiera direcciones de retorno en los marcos de llamada.

PS el traductor MELT es actualmente 57KLOC de código MELT traducido a casi 1770KLOC de código C.

Su mejor opción es probablemente el comstackdor de TCC , que le permite hacer exactamente esto: comstackr el código fuente, agregarlo a su progtwig, ejecutarlo, todo sin tocar los archivos.

Para una solución más robusta pero no basada en C, probablemente debería verificar el proyecto LLVM , que hace más o menos lo mismo pero desde la perspectiva de producir JIT. No puede ir a través de C, sino que utiliza un tipo de código de máquina portátil abstracto, pero el código generado es mucho más rápido y está en desarrollo más activo.

OTOH si quieres hacerlo todo manualmente, dlopen() a gcc, comstackndo .so y luego cargándolo tú mismo, dlopen() y dlclose() harán lo que quieras.

¿Estás seguro de que C es la respuesta correcta aquí? Hay varios lenguajes interpretados, como Lua, Bigloo Scheme, o incluso Python que se integran muy bien en una aplicación C existente. Puede escribir las partes dinámicas utilizando el lenguaje de extensión, que admitirá el código de recarga en tiempo de ejecución.

La desventaja obvia es el rendimiento: si necesitas absolutamente la velocidad bruta de la C comstackda, es posible que estas no avancen.

Si desea volver a cargar una biblioteca de forma dinámica, puede usar la función dlopen (ver mans). Abre un archivo .so de la biblioteca y le devuelve un puntero void *, luego puede obtener un puntero a cualquier función / variable de su biblioteca con dlsym .

Para comstackr sus bibliotecas en memoria, bueno, lo mejor que creo que puede hacer es crear un sistema de archivos de memoria como se describe aquí .