Эта идея зреет во мне весьма давно, и кажется обстоятельства приперли, соответственно, я решил приступить к ее реализации. Обстоятельства - это немерянно выросший размер экзешника, уже 1,68М несжатый, или ~650К сжатый. На PC код растет очень быстро, особенно если его делать на ЯВУ. Я могу переписать весь интерфейс на асм, но опыт показывает, что код сократится не на очень много, а вот мороки с его сопровождением тогда будет выше крыши. Как раз потому, что PC-Asm, это вам не тут. Поэтому на PC-Asm я делаю только эмуляцию ядра, и видео-рендеринг. Весь интерфейс на данный момент сделан в Delphi+KOL.
В чем суть идеи (разрешаю пользоваться, не жадный я по части идей). Часть кода переписывается на асме Z2x80 (сейчас объясню, чем оно отличается от чистого Z80), и выполняется под управлением той части эмулятора, что написана на асме, и умеет выполнять код Z80. Замечу, что у меня используется свой макро-ассемблер, который компилирует код на лету, поэтому для всех возможных вариантов эмуляции, в том числе Gfx256, с мультиколором, без него, и прочими вариантами (а теперь еще один добавится) - код используется для эмуляции общий. Так было не всегда, в первых версиях для каждого варианта был свой собственный код, зато он был сразу скомпилирован, и быстрее запрягал (теперь запрягает дольше чуток, зато едет еще быстрее чем раньше).
Теперь о Z2x80. Это не реальный проц из жизни. Это просто тот же Z80, но с удвоенной шиной данных. Т.е. 1-байтовые регистры A, B, C, D, E, H, L становятся 2х-байтными, 2х-байтовые регистровые пары соответственно 4х-байтовыми. Естественно, 2 набора регистров, основной и альтернативный. Внешне команды не меняются, коды операции у них не меняются, иногда увеличивается (удваивается в основном) разрядность непосредственного операнда. Иногда нужно переключаться как бы в "обычный" 1-байтный режим, на 1 команду или на несколько. Для этого можно использовать 1-байтовые префиксы, эквивалентные "ненужным" nop-эквивалентным командам. Например LD B,B - как префикс, превращающий следующую команду в 1-байтовую. Адресное пространство для этой "машины" - совпадает с адресным пространством самого эмулятора. Стек - со стеком машины PC. Чтобы не создавать проблем с абсолютной адресацией, команды JP и CALL разрядность не меняют, т.е. занимают те же 3 байта, но переходы становятся относительными. С командами загрузки / записи по непосредственному адресу я пока не решил, что делать. Вроде бы относительная адресация была бы полезна, чтобы так же делать самомодифицирующийся код или брать данные из кода. Но и обращение к глобальным переменным может потребоваться, а о том, как передать адрес этих переменных в Z-код, я еще только подумать успел. Самый простой вариант - загружать в EBP на входе в эмулятор (при вызове функции, переписанной на Z2x80) адрес некоторой общей структуры, через которую происходит обмен данными между кодом PC и кодом Z2x80. И использовать далее EBP как базу при обращениях с абсолютной адресацией. Можно сделать относительную адресацию к коду основной, а обращение к общим данным - через префикс, например LD E,E. Есть еще ряд команд, которые вообще не требуются при эмуляции Z2x80. Например, IN A,(d), OUT (d),A. Их можно задействовать как короткие вызовы до 256 часто используемых функций по таблицам. Например, IN - для вызова таких же функций, уже переделанных с PC на Z, OUT - для вызова машинного кода PC.
Хочется привести пример, и наконец закончить на этом. Допустим есть у меня такой код:
procedure TFormConfig.SetupColors;
var I: Integer;
begin
for I := 0 to 15 do
ColorPanels[ I ].Color := Color16ToColor( Colors16[ I ] );
end;
Как бы его можно было переписать на Z2x80:
TFormConfig_SetupColors PROC
DEFB StartZ2x80
E=E : HL=adr_Colors16 : E=E: IX=adr_ColorPanels : B=16
LOOP
E=(HL) : DE><HL
CALL Color16ToColor
PUSH HL : HL=(IX) : PUSH HL
C=C : CALL TControl_SetColor
DE><HL
ELOOPB
RET
Пояснения. StartZ2x80 - это байт $E7, вопринимается PC как недопустимая команда, после чего срабатывает мой SEH-обработчик, и передает управление эмулятору Z2x80, с тем чтобы он начал эмулировать код со следующего байта. E=E : HL=adr_Colors16 загружает поле "общей структуры", хранящее адрес начала массива Colors16. Аналогично, в IX далее грузится адрес массива 16 панелей (типа TControl). Далее в 16-битный регистр E загружается слово, являющееся цветом в формате pf16bit, из очередного элемента массива Colors16. Функция Z2x80 конвертирует его в формат pf32bit (получает параметр в L, возвращает результат в HL, например). Кто не знаком с нотациями C--, >< это обмен (т.е. DE><HL ~ EX DE,HL). С нотацией = проблем быть не должно. LOOP ... ELOOPB это цикл DJNZ с авто-меткой (LOOP - это как бы метка). RET выполнит переход в точку, которую подготовил эмулятор при входе в процедуру из PC-кода (и тогда вернет на место регистры, задействованные для эмуляции, и вернутся в PС-код), либо если вход был выполнен непосредственно из Z2x80-кода, будет продолжатся эмуляция вызвавшей Z-процедуры.
Пострадает, конечно, скорость выполнения. Но это будет касаться только интерфейсной части, поскольку только эту часть я и буду пробовать переводить на Z2x80. Пострадает наверное и скорость написания. Для того чтобы переписать несколько мегабайт исходного кода с ЯВУ на асм Z2x80, конечно уйдет немало времени. Но мой подход вроде бы позволяет сделать это постепенно.
Теперь можно посчитать прибыль (в смысле убыль, кода). На PC-Asm тот же код занимает ... щас гляну ... 56 байтов. На Z2x80 - 30 байтов. Хм, я уже занимался подобными "переводами" на асм на PC (см. KOL), и могу заметить, что чем больше переводимая процедура, тем лучше результаты. Но даже на такой мелочи почти вдвое выиграть, я не очень ожидал такого.
Собственно, частично идея мной уже опробована (SEH-обработчик например), но я пробовал другое - форт-машину. Эмулятор для нее занял в коде PC всего 300 байт. И код мог бы быть весьма краток, если бы... я умел писать на форте. Как показала реальность, "думать" в терминах стековой машины мне неудобно. Отладка занимает бОльшую часть программирования, а я этого не люблю. Z80 все-таки мне более привычен, как бывшему спектрумисту, и хотя его эмулятор занимает до 100К, он ведь все равно уже есть в коде, то грех им не воспользоваться как интерпретатором байт-кода. Получается эмулятор на эмуляторе, или сам себя эмулирует, вот так.