В процессоре MOS 6502 как полагается были и аппаратные прерывания - маскируемое (IRQ) и немаскируемое (NMI), а так же была и инструкция программного прерывания BRK, которая срабатывала в целом как аппаратное маскируемое прерывание. По сравнению с i8086 или даже i8080/Z80, однако, векторов прерываний было только два и они были неизменные - располагались в конце адресного пространства.
Интересно, что код инструкции BRK был 0x00. Это несколько контрастирует с i8080/Z80 где 0x00 - это NOP (отсутствие операции). В MOS 6502 код операции NOP был 0xEA.
В принципе BRK можно использовать и для входа в некие функции ОС (более того инструкция работала так что как бы содержала IMM-байт сразу за своим кодом, т.е. по сути является двухбайтовой) и для отладки, но было еще одно забавное применение: внесение горячих правок в... ПЗУ.
Как это делалось - дело в том, что первые способные к прожигу в домашних условиях ПЗУ (PROM) были не перезаписываемые, а просто изначально содержали все замкнутые контакты в матрице контактов и это часто воспринималось как байт 0xFF. А после подачи большого напряжение на конкретный контакт он перегорает и там образуется нулевой бит.
Допустим мы хитрые создатели ПО в таких плашках памяти и делаем вот что: прожигаем программу сбрасывая некоторые биты в 0 в начале плашки, а в конце где то нередко остаётся свободное место занятое 0xFF. И вот допустим мы уже прожгли 10000 чипов ПЗУ и вдруг обнаружили ошибку в логике программы. Фиаско?
Еще нет. Мы можем пережечь все контакты в первой ошибочной инструкции и там образуется 0x00 - код BRK, и когда программа до него дойдёт - вызовется прерывание. Этим можно воспользоваться если нам не нужны IRQ (или мы хитро их обрабатываем) - можно прописать такой его обработчик который проанализирует адрес возврата и сделает прыжок в "фикс" действительно горячий как дымящаяся в булочке сосиска - фиксы так же допрожигаются в конец свободного места и если их несколько растут как стек сверху-вниз.
Довольно забавно. Техника померкла и забылась когда стали доступны и популярны EEPROM которые уже можно было перезаписывать как угодно.
Если задуматься, то на i8080/Z80 такое тоже можно было проворачивать ведь там была инструкция RST 38h с кодом 0xFF. Но тут надо было принять инвертированную по сравнению со схемой у 6502 схему считывания бит - да просто инвертором.
Забавно, тут вчера на форумах на nesdev.com появился интересный топик - некто с ником JesperGravgaard заявил, что его разработка - компилятор KickC может теперь делать код для Famicom/NES/Денди (ранее оно было ориентировано на восьмибитную звезду северной Америки - Commodore 64).
KickC - это реально оптимизирующий компилятор для процессора MOS 6502 базированный на языке Си, но Си он овосьмибичивает максимально вплоть до несовместимости.
Как и GCC KickC компилирует не сразу в машинный код, а в текст программы на ассемблере причём в диалекте ассемблера для MOS 6502 который создан опять таки самим же автором проекта и называется KickAssembler. Тоже забавная штука, покажу её ниже.
Первое и самое главное - KickC не поддерживает рекурсию функций.
На MOS 6502 со стеком вообще всё плохо - регистр указателя стека восьмибитен и данные стека могут лежать только во второй 256-байтной странице памяти. Более того - система команд процессора устроена так, что по человечески этот стек полезен только для хранения адресов возврата процедур, ну и каких то текущих 8-битных данных push/pop аккумулятора. Вообще явно пушить/пОпить можно только аккумулятор и регистр флагов (восьмибитные). Поэтому ну прям такое...
В связи с этим в совместимом со стандартом Си компиляторе для этого же процессора - CC65 указатель "Си-стека" для параметров и локальных переменных это отдельный глобальный указатель и работа с этими вещами превращается в достаточно серьёзный геморрой с точки зрения производительности.
Так вот - отказавшись от реентерабельности KickC вылил все эти проблемы из тазика древних архитектур.
Почему так: во первых - параметры в функции стали просто глобальными переменными. Причём в синтаксисе KickAssembler они получают имена имя_процедуры.имя_параметра. То же самое с локальными переменными - никакие они уже не локальные, а как примерно как static в Си, но компилятор оптимизирующий, поэтому отслеживает потенциальные деревья вызовов и размещает параметры и переменные разных функций в одних и тех же ячейках памяти это не вызовет проблем.
И это уже настолько мощная потенция к оптимизации, что как выражаются англоязычные - это "game changer"!
Сразу раскрою все карты:
KickC не поддерживает по сравнению с Си:
- типы с плавающей точкой (float, double)
- умножение, деление и взятие остатка где оба параметра не константы (причём с константами работает только для степеней двойки)
- union
- массивы массивов
- указатели на функции возвращающие значение или имеющие параметры (т.е. чтобы на функция можно было взять указатель она должна быть void())
- рекурсию
- оператор alignof
- вариадические параметры ... (кроме встроенного printf)
KickC имеет некоторые атипичные ключевые слова иногда встречающиеся в других компиляторах Си (нестандарт):
- директива выравнивания, например: align($100)
- директива принудительного размещения переменной в регистре, например: register(X)
- встроенный ассемблер, например: asm { SEI CLD };
- функция main всегда имеет прототип void main()
Что своего из расширений появилось в KickC:
- циклы for вида for( char i: 0.. 10) { }
- компоновка слова из двух байт фигурными скобками, например: unsigned int w = { hi, lo };
- операторы ссылки на нижний и верхний байты слова - '>' и '<', например: char lo = <w; <w = 12;
Давно уже я привык тестировать 8-битные компиляторы "на вшивость" путём рассмотрения того что они порождают в машинном коде из следующей фукнции на Си:
void str_cpy(char *dst, char *src ) { while ( *dst++ = *src++ ) {}; };
Для того чтобы понять какие этот компилятор предоставляет следует рассмотреть какой код тут будет идеальным если его написать на ассемблере вручную.
Насчёт идеальности тут конечно можно еще спорить, но от компилятора хотелось бы минимум следующего:
Во первых в 6502 не было 16-битных регистров кроме счётчика инструкций и косвенная адресация любого фрагмента памяти производилась через адреса хранимые в первых 256 байтах памяти (так называемый zero page). Поэтому адреса в процедуру следует передавать в известных "слотах" в zero page, тогда код по копированию будет выглядеть так:
; на входе: ; dst - адрес в zero page где лежит указатель на блок памяти куда копируем ; src - адрес в zero page где лежит указатель на блок памяти откуда копируем .proc str_cpy ldy # 0 ; загрузим в индексный регистр Y ноль loop: lda (src),y ; загрузим в аккумулятор байт из [src+Y] sta (dst),y ; сохраним его в [dst+y] bne cont ; если он не был равен 0, то продолжаем rts ; иначе - выходим из процедуры cont: inc src ; увеличиваем нижний байт src прямо в памяти bne skip ; если он не стал 0 - пропускаем inc src+1 ; инкремент старшего байта src skip: inc dst ; увеличиваем нижний байт dst прямо в памяти bne loop ; если он не стал 0 - идём в начало цикла inc dst+1 ; иначе увеличиваем страший байт dst jmp loop ; и возвращаемся на следующую итерацию .endproc
Первое полевое испытание проведём на компиляторе для MOS 6502 - CC65:
char* dst1 = (char*)0x0400; char* dst2 = ( char*)0x0428; void str_cpy( char *dst, char const *src ) { while ( *dst++ = *src++ ) {} } void main( ) { str_cpy( dst1, "hello"); str_cpy( dst2, "world"); }
.proc _str_cpy: near .segment "CODE" jsr pushax L0006: ldy #$03 lda (sp),y tax dey lda (sp),y sta regsave stx regsave+1 clc adc #$01 bcc L0008 inx L0008: jsr staxysp lda regsave ldx regsave+1 jsr pushax ldy #$03 lda (sp),y tax dey lda (sp),y sta regsave stx regsave+1 clc adc #$01 bcc L000A inx L000A: jsr staxysp ldy #$00 lda (regsave),y jsr staspidx tax bne L0006 jmp incsp4 .endproc
Что же родит компилятор KickC?
А вот что:
// str_cpy(byte* zp(4) dst, byte* zp(2) src) str_cpy: { .label dst = 4 .label src = 2 __b1: // *dst++ = *src++ ldy #0 lda (src),y sta (dst),y // while ( *dst++ = *src++ ) lda (dst),y inc.z dst bne !+ inc.z dst+1 !: inc.z src bne !+ inc.z src+1 !: cmp #0 bne __b1 // } rts }
=A=L=X=
> Оно многое показывает. Например то, что все компиляторы под 8-битные платформы
> что я видел до сих пор (около 4-х штук на самом деле) - говно в плане
> оптимизации и проигрывает в скорости ручному написанию кода на ассемблере
> примерно на порядок, т.е. примерно "в 10 раз".
Я бы не полагался на сырые, любительские компиляторы. Моя практика показывает, что при написании серьёзных проектов словить глюк из-за компилятора неизбежно! Тут профессиональные компиляторы часто дефекты имеют, что говорить о любительских тогда.
Расово верный путь - использовать GCC (не знаю как с 6502, но для М68К есть). Нет ограничений в синтаксисе и надёжен. Стартап есть у Стефана.
Копирование память-память лучше писать ассемблерными вставками. Си использовать только для вызова функций верхнего уровня и для общей логики работы программы.
=A=L=X=
> Что же родит компилятор KickC?
> А вот что:
И это удивительно! Потому что на максимальной оптимизации этот код должен быть просто уничтожен! Вы просто гоняете байты туда-обратно, даже не выводите их на дисплей. Данные не волатильны. Этот код хороший компилятор должен был просто вырезать!
Gradius
> Я бы не полагался на сырые, любительские компиляторы.
Угу. Я бы тоже, НООООО.... (огромное и большое но)
https://gamedev.ru/flame/forum/?id=240745&page=5&m=5172157#m67
Можно скачать прям сейчас экстремально оптимизирующий компилятор Си под платформу микроконтроллерную e280 от фирмы Zilog той самой что создала процессор Z80 в 80-е годы и сохраняет с ней совместимость до сих пор...
И насладится по полной.
Прям по полной - полным гавнищем оптимизации, их "высоко оптимизированный компилятор" рожает стыд и стыдобищу.
По ссылке всё показано.
И это всё происходит не в 80е, а прямо сейчас. Сегодня. В микроконтроллерах. Рядом с нами. Кродётся. МЫШЪ.
Сегодня и рядом с нами такое - такое говнище.
Поэтому компилятор KickC реально - глоток свежего воздуха.
Он реально не заявляет что может оптимизировать код под 8 бит, а оптимизирует его.
=A=L=X=
> Поэтому компилятор KickC реально - глоток свежего воздуха.
> Он реально не заявляет что может оптимизировать код под 8 бит, а оптимизирует
> его.
Я не умаляю его достоинств. Но он должен пройти закалку. Попробуйте написать хотя бы 20 000 строк на Си и скомпилировать этим компилятором. Бинарник будет 100% рабочим?
Gradius
> Бинарник будет 100% рабочим?
Он сам пишет что версия 0.8 и поэтому могут быть нестабильности. Естественно если они встретятся, то желательно писать багрепорты.
Я сам не буду на нём писать потому что хочу ощутить весь вкус ассемблера. Но выглядит реально недурно - все компиляторы что я видел до сих пор на восьми битах оптимизируют хреновенько. Во многом это вызвано тем что на 8 битах реально переменные в стеке - это геморрой и код быстро превращается в перекладывания туда-сюда байт. Тот же CC65 несмотря на совместимость со стандартом Си содержит в документации трюки как оптимизировать код и это отказ от параметров в стеке и тому подобное. Но Си не очень хорош даже когда используем для переменных ключевое слово static, т.к. его смысл мешает повторно использовать одну и ту же память.
KickC же отказавшись от локальных переменных очень хорошо заруливает эти проблемы. Это примерно то какие были компиляторы в 60-е годы - тот же фортран ключевое слово REENTRANT (или RECURSIVE) получил много лет позже того как был создан.
cc64 is a small-C compiler, written in Forth, targeting the 6502 CPU and hosted on the C64. I wrote it during my university years; the majority of the code was written 1989-1991.
https://github.com/pzembrod/cc64
P.S. Тоже, много чего не поддерживает. :)
Здесь ещё некоторое количество С, http://www.6502.org/tools/lang/
А, некоторые, ждут коллапса и пишут ОС под 8-ми битные Z80
https://collapseos.org/why.html
Traditional assembly
Assembly optimized with my 65C02 Assembly Optimizer
C compiled with the CC65 C compiler
Forth compiled with a modified version of Tali Forth 2
Each version of the game runs on a JavaScript-based 65C02 simulator in the browser:
KPG
> А, некоторые, ждут коллапса и пишут ОС под 8-ми битные Z80
Надо срочно ламповый аналог Z80 делать. :) Так будет гарантия, что процессор будет маловосприимчив к электро-магнитным атакам.