Antes de laburar en esto, tenia una idea bastante ingenua. Pensaba que "VM" queria decir "ejecutar bytecodes y todas las optimizaciones que tienen que ver con eso" y nada mas. Grave error...
Por ejemplo, si, hay que programar primitivas, pero no son todas iguales. Por ejemplo, tenes cosas como identityHash, basicAt:put:, size, en algunos Smalltalks next/nextPut:, replaceFrom:to:with:startingAt:, new, small integer, large integer, float, double, y todo eso. Hasta ahi viene mas o menos facil la mano. Pero tambien hay cosas como archivos, sockets, impresoras, mouse, teclado, graficos, ventanas, eventos (digo de Windows)... eso ya es mas pesado, en particular si tu VM tiene que andar en N plataformas. Encima de eso hay cosas ya mas fuleras como signals (que te interrumpen tu VM en cualquier momento, en particular mientras tu VM esta en codigo JITeado), y timers asi te anda Delay. Donde estan los standards de esto para cada plataforma asi se pueden programar solo una vez? Los timers que estas usando son independientes del cambio de hora o no? Resolver eso, y dejar a la imagen andando "como siempre" no es facil. Como te las arreglas para que un signal *siempre* interrumpa a Smalltalk asi la VM puede fijarse que paso y reaccionar acorde, pero sin matar la performance ni hacer polls (por ejemplo, salto un timer asociado con un delay, asi que ahora hay que hacerle signal a un semaphore y correr de nuevo el process scheduler porque el proceso que estaba esperando en el semaphore puede tener mas prioridad que el proceso que esta corriendo ahora --- y si salta un timer mientras te estas fijando que proceso correr (porque x ej la imagen hizo Processor yield o Semaphore wait), que?)? Tambien pensaba que el GC era bastante mas simple de lo que es. Mas que garbage collect hay que pensar en memory management. Cuando la imagen dice "new", donde pones ese objeto? Como decidis eso rapido? Si la imagen te dice "quiero mas memoria", que haces? malloc()? mmap()? sbrk()? Y si alguno de esos falla, que pasa? Y si la memoria que te da el sistema operativo no te sirve porque la direccion no es la que queres, que pasa? Esta bueno tener varios espacios de objetos (new, old, fixed, etc). Que pasa cuando la imagen hace un become: entre objetos en diferentes espacios? Que pasa con oneWayBecome:? Y si hay que copiar objetos y el nuevo no entra, que? Como haces que la VM proteste cuando no hay espacio? Esto requiere una cantidad de laburo grande en la imagen para que funcione bien el tandem. Como se graba la imagen, y como la volves a cargar? Eso tampoco es facil, tener que manosear todos los punteros. Mas espacios de objetos tenes, mas complicado se pone. Si tenes un incremental garbage collector, mas complicado todavia. Ademas le tenes que agregar weak objects, ephemerons, y finalization. En VW, todo esto tiene que andar junto y bien: el scavenger de new space, el IGC, el data compactor, el GC (mark/sweep), el global GC (mark/sweep, pero tambien de perm space), weak objects, ephemerons, finalization, los remember tables de turno, y tambien en 64 bits la tabla de clases que es weak pero que durante un scavenge es strong si esta funcionando el IGC. Solo esto ya es complicado... Ademas de que la imagen llame a cosas y permita callbacks, tambien esta bueno que alguien llame a la VM y le diga cosas como "che fiera ejecutame este codigo y decime que pasa". O que te caiga un callback que no pediste inmediatamente antes (o sea que se ejecuta en un thread que no es el que lo pidio --- o sea que el thread ese no puede tocar objetos porque en otro thread puede estar funcionando el garbage collector, ouch). Lo que si es la muerte es algo de lo que hable alla en Smalltalks: los standards que rigen el mundo de C son demasiado grandes para una sola persona (o para un grupo reducido de personas). Mencione el manual de GCC que en su momento tenia 662 paginas. Bueno, porque la VM de Linux en x86/64 se colgaba con SIGSEGV, mientras todas las otras VMs andaban? Porque el compilador era GCC, y ademas porque en 64 bits se dan todas estas casualidades juntas: 1. las entradas de la tabla de objetos tienen un bitfield al final. 2. las entradas de la tabla de objetos estan contra el borde superior de un cacho de memoria que te dio mmap() (o malloc(), o sbrk()...). 3. el codigo C que lee esos bitfields resulta en diferente assembler segun como cambia otro codigo C que no tiene nada que ver... o sea, el estado interno del compilador resulta en unas instrucciones u otras instrucciones. 4. Algunas de esas instrucciones hacen, esencialmente, mov 12+[objectTableEntryPtr], %eax o si no mov 13+[objectTableEntryPtr], %eax Estoy haciendo el ejemplo asi no mas, pero esa es la idea: el compilador hace un read, y despues hace shl y and de %eax para conseguir el valor del bitfield que corresponde. Cada object entry tiene 16 bytes. Que pasa cuando el objectTableEntryPtr apunta al ultimo object table entry y el CPU trata de leer 4 bytes empezando en 13+objectTableEntryPtr? El ultimo byte esta afuera del segmento otorgado por el sistema operativo => SIGSEGV. Y cuando ocurre el bug? Cuando la imagen le pide el identityHash a un objeto que tiene el ultimo objectTableEntry en un segmento. La imagen le pide identityHash a los objetos con frecuencia? Si, pero obviamente no a todos... asi que si tenes suerte, no pasa nada. Y si no tenes suerte, bueh... En 32 bits, no se dan todas las casualidades asi que las VMs compiladas con GCC andan igual. Pero si no te pasa el problema en 64 bits, tenes idea real de lo que esta pasando? Igual hay que arreglarlo, y para arreglar tenes que encontrar el error primero. Podes pensar "jaja, es un error del optimizador, asi que ahora compilamos con -O0", pero eso no es "encontrar el error" sino "voy a cambiar cosas al azar hasta que el error desaparezca, y despues digo que arregle lo que estaba mal". Eso no esta bien. Asi que hay que ponerse a investigar. Y, investigando, al final uno encuentra que el manual de GCC dice claramente que esto es un problema del compilador: ============ # Accesses to bit-fields even in volatile objects works by accessing larger objects, such as a byte or a word. You cannot rely on what size of object is accessed in order to read or write the bit-field; it may even vary for a given bit-field according to the precise usage. If you care about controlling the amount of memory that is accessed, use volatile but do not use bit-fields. ============ http://gcc.gnu.org/onlinedocs/gcc-4.5.0/gcc/Disappointments.html#Disappointments Pero quien se lee todo el manual de GCC con cada version nueva que sacan ANTES DE USARLO? Y quien se lee todo MSDN, la documentacion de Apple, etc? Eso si que es dificil: te la pasas leyendo documentacion para ver si lo que estas haciendo esta bien o mal. Invariablemente, como cada cosita que haces esta relacionada con alguna otra cosa, eso te lleva de documento en documento como si estuvieras webeando en la Wikipedia --- pero esta vez tenes que estudiar! Por ejemplo: muchas funciones comunes de Unix te dicen como fallaron a traves de un coso que se llama errno. A veces tambien tenes que usar esas funciones en signal handlers. El standard de turno te dice cuales podes usar y cuales no en un signal handler (printf(), por ejemplo, no se puede). Esas funciones tambien manosean el valor de errno si fallan. Y que pasa si un signal handler interrumpe tu codigo justo despues de que falla una funcion pero antes de que el codigo se fije el valor de errno, y el signal handler llama a una funcion que tambien falla y cambia el valor de errno? Cuando vuelve el signal handler a tu programa, te podes encontrar que no pudiste abrir un archivo porque (para inventar) la ultima vez que dividiste un numero de punto flotante por otro el resultado fue NaN... Para mi, el problema mas serio es que determinar que algo esta Bien(TM) es MUY DIFICIL. Esa investigacion constante lleva un monton de tiempo y esfuerzo. Por eso me parece mas valioso lo que esta tratando de hacer Alan Kay... que se pueda programar una computadora de manera decente pero que tengas que estudiar, como mucho, 20 mil lineas de codigo. 20 mil lineas no es nada! Ah, pero como, habia que ejecutar bytecodes?... :). Bueno, exactamente que haces cuando en el JIT tenes que hacer shl %cl, %reg, y resulta que 1. %ecx lo estas usando para otra cosa, 2. %reg puede ser %ecx, 3. no podes hacer push/pop, 4. no hay registros disponibles (estan todos asignados), 5. y ademas te tenes que guardar una copia de %reg antes de hacer el shift en algun lado. Ahi te pones a investigar la papeleria de Intel y descubris que aun hoy solo se puede hacer shl/shr/sar usando a %cl. Y ahora?... a investigar de nuevo... Andres. 2010/7/26 Gerardo Richarte <[email protected]>: > On 07/26/2010 09:25 AM, Angel "Java" Lopez wrote: >> Andres, interesante... Podrias enumerar por aquí una lista de lo que hace >> una VM? >> > che, a mi también me interesa mucho la verdad, ya a esta idea nos fuimos > armando una idea, pero estaría bueno escuchar tu opinion. Para mi en > este momento, con toda la experiencia que ganaste en estos años siendo > el que está a cargo [una] de las VM de Smalltalk más usadas [la de VW] > tu palabra vale mucho. > > Yo tiro algo, veamos: > > . Bytecode execution engine (interprete y/o JIT) > . primitives > . garbage collection > . method lookup > . FFI > . Process scheduler (?) > . Callback mechanism (?) > > a ver? > saludos > > -- > To post to this group, send email to [email protected] > To unsubscribe from this group, send email to > [email protected] > > http://www.clubSmalltalk.org -- To post to this group, send email to [email protected] To unsubscribe from this group, send email to [email protected] http://www.clubSmalltalk.org
