EdigE
← ЛЕНТА
MCU · 12.07.2026

MicroPython запустили на SNES — и нашли несколько настоящих 16-битных багов

Порт MicroPython для Super Nintendo проходит 430 из 468 запускаемых базовых тестов на процессоре 65C816 с частотой 3.58 МГц. Работа выявила переполнение индекса кадрового буфера уже со строки 128 и пропуск живых объектов консервативным сборщиком мусора. Эти сбои показывают, какие предположения о размерах типов и выравнивании остаются в зрелом переносимом коде.

Для Super Nintendo подготовлен порт MicroPython v1.28.0 под процессор Ricoh 5A22 на базе 65C816, работающий на частоте 3.58 МГц. Консоль располагает 128 КБ рабочей памяти, из которых порту выделено 56 КБ Python-heap. Интерпретатор загружается примерно за 20 секунд, а демонстрация с шестью движущимися спрайтами, где логика исполняется на Python, достигает около 0.8 кадра/с.

Результат тестов выглядит убедительнее производительности: успешно проходят 430 из 468 запускаемых файлов tests/basics, или 91.9%. В наборе всего 571 файл, но 103 исключены как reference-skips: они требуют возможностей, которых у порта или самой 16-битной платформы нет. Автоматический прогон построен вокруг эмулятора Mesen2: ROM передаёт транскрипт теста и статус завершения через mailbox в WRAM, а хостовый pytest сверяет результат. Репозиторий содержит ROM-образы, исходный код и тестовую обвязку; публично подтверждён именно эмуляторный прогон, собственный запуск автором на физической SNES не задокументирован.

Самым прямым дефектом стала арифметика смещений bytecode. Для обратного перехода компилятор вычислял разницу как unsigned `size_t`, а затем приводил её к знаковому `ssize_t`. На системе с 16-битным `size_t` смещение −20 превращалось в +65516, после чего компиляция циклов завершалась переполнением bytecode. Ошибка оформлена отдельным upstream issue MicroPython: порядок преобразований типов должен обеспечивать знаковое вычитание до потери разрядности.

В графическом коде выражение `x + y * stride` вычислялось в 16-битном `int`. При ширине буфера 256 пикселей произведение `y * 256` перестаёт представляться уже при `y = 128`. Запись по получившемуся адресу выходила за ожидаемую область framebuffer и повреждала Python-heap. Исправление потребовало перевести все 14 мест с такой индексацией на `size_t`, а не ограничиться одной функцией вывода пикселя.

Консервативный GC выявил ещё один слой предположений о памяти. Сканирование корней ожидало 32-битное выравнивание и пропускало указатели, лежащие по адресам с остатком 2 modulo 4. На 65C816 packed-структуры и 16-битные размеры делают такую раскладку штатной; после `gc.collect()` могли исчезать ещё живые глобальные объекты. Отдельно обнаружено сканирование дочерних ссылок шагом 4 байта, которое также не видело часть невыравненных указателей.

Порт превратил игровую консоль в строгий стенд проверки машинно-зависимых допущений: ширины `int` и `size_t`, промежуточные преобразования в выражениях, адресная арифметика и шаг обхода памяти здесь непосредственно определяют корректность интерпретатора. Для современных 32- и 64-битных целей эти дефекты могли оставаться скрытыми годами.