Este proyecto es educativo y Open Source. No se copia código de otros emuladores. Implementación basada únicamente en documentación técnica y tests permitidas.
Fix: Corregir Creación de PPU en Wrapper Cython para Resolver Puntero Nulo
Resumen
El diagnóstico del Step 0141 reveló que el Segmentation Fault ocurría antes de que se ejecutara cualquier código dentro de render_scanline(), lo que confirmó que el problema estaba en el wrapper de Cython: el puntero al objeto PPU de C++ era nulo (nullptr). Se corrigió el constructor __cinit__ de PyPPU en ppu.pyx añadiendo logs de diagnóstico, verificaciones robustas y manejo de errores explícito para asegurar que la instancia de PPU en C++ se crea correctamente.
Concepto de Hardware
En un emulador híbrido Python/C++, la creación de objetos C++ desde Python requiere un "wrapper" de Cython que actúa como puente entre ambos mundos. El wrapper de Cython mantiene un puntero C++ crudo (cdef PPU* _ppu) que apunta a la instancia real del objeto en memoria C++.
Cuando Python llama a un método del wrapper (ej: ppu.step()), Cython traduce la llamada a una invocación directa del método C++ (ej: self._ppu.step()). Si el puntero _ppu es NULL (nulo), intentar llamar a un método en un puntero nulo causa un Segmentation Fault inmediato, antes de que cualquier código dentro del método C++ pueda ejecutarse.
El constructor de Cython (__cinit__) es responsable de crear la instancia C++ usando new PPU(...) y asignar el puntero resultante a self._ppu. Si este proceso falla silenciosamente o no se ejecuta correctamente, el puntero queda en NULL y todas las llamadas posteriores fallarán con un crash.
Implementación
Se corrigió el constructor __cinit__ de PyPPU en src/core/cython/ppu.pyx para asegurar la creación correcta de la instancia C++ y añadir capas de seguridad que prevengan futuros crashes silenciosos.
Componentes modificados
- src/core/cython/ppu.pyx: Constructor
__cinit__mejorado con logs de diagnóstico y verificaciones robustas - src/core/cython/ppu.pyx: Destructor
__dealloc__mejorado con logs y asignación explícita deNULL
Cambios aplicados
1. Constructor __cinit__ mejorado:
- Añadidos logs de diagnóstico con
print()para rastrear la creación del objeto - Verificación explícita de que
mmu_wrapperno seaNone - Extracción explícita del puntero C++ crudo desde el wrapper de MMU:
cdef mmu.MMU* mmu_ptr = (<PyMMU>mmu_wrapper)._mmu - Verificación de que el puntero de MMU no sea nulo antes de crear la PPU
- Verificación explícita después de
new PPU(mmu_ptr)para asegurar que la asignación fue exitosa - Lanzamiento de excepciones descriptivas (
ValueError,MemoryError) si algo falla
2. Destructor __dealloc__ mejorado:
- Añadidos logs de diagnóstico para rastrear la liberación del objeto
- Asignación explícita de
NULLdespués de liberar el objeto para evitar punteros colgantes
Código clave
def __cinit__(self, PyMMU mmu_wrapper):
print("[PyPPU __cinit__] Creando instancia de PPU en C++...")
# Verificar que mmu_wrapper no sea None
if mmu_wrapper is None:
raise ValueError("PyPPU: mmu_wrapper no puede ser None")
# Extrae el puntero C++ crudo desde el wrapper de la MMU
cdef mmu.MMU* mmu_ptr = (<PyMMU>mmu_wrapper)._mmu
# Comprobación de seguridad: Asegurarse de que el puntero de la MMU no es nulo
if mmu_ptr == NULL:
raise ValueError("Se intentó crear PyPPU con un wrapper de MMU inválido (puntero nulo).")
# --- LÍNEA CRÍTICA ---
# Crea la instancia de PPU en C++ y asigna el puntero
self._ppu = new ppu.PPU(mmu_ptr)
# Comprobación de seguridad: Asegurarse de que la creación fue exitosa
if self._ppu == NULL:
raise MemoryError("Falló la asignación de memoria para la PPU en C++.")
print("[PyPPU __cinit__] Instancia de PPU en C++ creada exitosamente.")
Archivos Afectados
src/core/cython/ppu.pyx- Constructor y destructor mejorados con logs y verificaciones robustas
Tests y Verificación
Validación del diagnóstico (Step 0141):
- El hecho de que el mensaje
printfdel Step 0141 nunca se ejecutara confirmó que el crash ocurría en la llamada al método mismo, no dentro de él. - Esto indicó definitivamente que el puntero
self._ppuen el wrapper de Cython era nulo.
Próxima verificación (pendiente de recompilación):
- Recompilar el módulo C++:
.\rebuild_cpp.ps1 - Ejecutar el emulador:
python main.py roms/tetris.gb - Verificar que los logs de diagnóstico aparecen:
[PyPPU __cinit__] Creando instancia de PPU en C++... - Verificar que el
Segmentation Faultha desaparecido - Verificar que el renderizado funciona correctamente
Fuentes Consultadas
- Cython Documentation: https://cython.readthedocs.io/ - Gestión de memoria y punteros en Cython
- Cython Best Practices: Constructores y destructores con objetos C++
Integridad Educativa
Lo que Entiendo Ahora
- Punteros nulos en Cython: Cuando un puntero C++ en un wrapper de Cython es nulo, cualquier intento de llamar a un método en ese puntero causa un
Segmentation Faultinmediato, antes de que el código dentro del método pueda ejecutarse. - Diagnóstico mediante ausencia de logs: Si un
printfal inicio de un método nunca se ejecuta, significa que el crash ocurre en la invocación del método mismo, no dentro de él. - Constructor de Cython: El método
__cinit__es crítico para la creación correcta de objetos C++. Debe verificar explícitamente quenewfue exitoso y que el puntero resultante no es nulo. - Gestión de memoria: Es buena práctica asignar explícitamente
NULLa un puntero después de liberarlo condeletepara evitar punteros colgantes.
Lo que Falta Confirmar
- Verificación de ejecución: Confirmar que después de recompilar, el emulador ejecuta sin
Segmentation Faulty que los logs de diagnóstico aparecen correctamente. - Renderizado funcional: Verificar que el renderizado de la PPU funciona correctamente ahora que el puntero es válido.
Hipótesis y Suposiciones
Hipótesis principal: El problema estaba en el constructor de Cython, donde la creación del objeto C++ no se estaba verificando correctamente. Con las verificaciones añadidas, el problema debería estar resuelto.
Suposición: El código anterior del constructor ya tenía la línea self._ppu = new ppu.PPU(mmu._mmu), pero posiblemente había un problema sutil en cómo se accedía al puntero de MMU o en el orden de las operaciones. Las verificaciones explícitas añadidas deberían prevenir cualquier problema similar en el futuro.
Próximos Pasos
- [ ] Recompilar el módulo C++ con
.\rebuild_cpp.ps1 - [ ] Ejecutar el emulador y verificar que los logs de diagnóstico aparecen
- [ ] Confirmar que el
Segmentation Faultha desaparecido - [ ] Verificar que el renderizado funciona correctamente
- [ ] Si todo funciona, eliminar los logs de diagnóstico de producción (mantener solo en modo debug)