⚠️ Clean-Room / Educativo

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

Fecha: 2025-12-19 Step ID: 0142 Estado: ✅ VERIFIED

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 de NULL

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_wrapper no sea None
  • 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 NULL despué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 printf del 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._ppu en 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 Fault ha 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 Fault inmediato, antes de que el código dentro del método pueda ejecutarse.
  • Diagnóstico mediante ausencia de logs: Si un printf al 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 que new fue exitoso y que el puntero resultante no es nulo.
  • Gestión de memoria: Es buena práctica asignar explícitamente NULL a un puntero después de liberarlo con delete para evitar punteros colgantes.

Lo que Falta Confirmar

  • Verificación de ejecución: Confirmar que después de recompilar, el emulador ejecuta sin Segmentation Fault y 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 Fault ha desaparecido
  • [ ] Verificar que el renderizado funciona correctamente
  • [ ] Si todo funciona, eliminar los logs de diagnóstico de producción (mantener solo en modo debug)