PDA

Просмотр полной версии : Как сократить код эмулятора Z80 на PC



Vladimir Kladov
08.09.2005, 21:40
Эта идея зреет во мне весьма давно, и кажется обстоятельства приперли, соответственно, я решил приступить к ее реализации. Обстоятельства - это немерянно выросший размер экзешника, уже 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К, он ведь все равно уже есть в коде, то грех им не воспользоваться как интерпретатором байт-кода. Получается эмулятор на эмуляторе, или сам себя эмулирует, вот так.

Dexus
08.09.2005, 21:52
Получается эмулятор на эмуляторе, или сам себя эмулирует, вот так.
"Роботы делают роботов - какое извращение..."(с)c3po
По-моему это изврат. ПРоблема найдена на ровном месте.
Может быть стОит перейти на CPP? Unreal вот например, будучи запакованным UPX'ом весит всего 170 кил.

SMT
08.09.2005, 22:45
половинное решение. я так понимаю, pc-asm не устраивает низкой плотностью кода, но если не торопиться и не жалеть времени на реализацию, выгоднее брать за основу не z80-asm (почему именно z80?), а псевдо-код, максимально приспособленный под задачу, ну или максимально удобный (много регистров, свои режимы адресации и т.п.)

пускай поначалу не затачивать систему команд, а хотя бы статистически анализировать байт-код и менять способ кодирования на оптимальный, прописывая таблицы в интерпретаторе

ну и самый рулез - сделать оптимизирующий компилятор с си (если удобнее - с паскаля), потому что переписывать код с каждой новой ревизией скрипта - мартышкин труд

SMT
09.09.2005, 07:40
форт идеален в плане сокращения объема тем, что нет избыточной информации, в каких регистрах/ячейках хранятся промежуточные результаты. в записи арифметического или логического выражения лишь операнды и операторы, и то все в кучу

плюс можно паковать чем-то вроде словарного метода (идентификаторов нет, код повторяется в точности) - ищем одинаковые куски и оформляем их в новые слова, если они конечно не начинаются внутри цикла и заканчиваются вне

ну и дожимать - хранить не адрес слова, а его номер. причём юзать код переменной длины (1 байт - часто используемые слова, 2 - остальные). в идеале Хаффманом/арифметиком жать, если скорость не важна :)

Vladimir Kladov
11.09.2005, 18:47
да я это все сделал, замучиться только код на этом форте писать. Надо же по-другому, по-стековому думать. Все время помнить, сколько в стеке чего и в каком порядке лежит.

А насчет С++ - не надо. У меня GUI интерфейс тянет, а не код эмулятора. Просто полазьте по меню, пооткрывайте окна. Их там - море и маленький вагон. На С++ это еще тяжелее будет весить. И переписывать все это - спасибки. Мне на паскале удобнее, не говоря уже о скорости компиляции. (Люди говоривали, что Delphi частично подготавливает код к компиляции прямо во время редактирования текста. А и правильно, чего процессор простаивает, пока мы тут клаву давим, пусть работает).

Dexus
11.09.2005, 19:20
Люди говоривали, что Delphi частично подготавливает код к компиляции прямо во время редактирования текста. А и правильно, чего процессор простаивает, пока мы тут клаву давим, пусть работает
Ничего он не подготавливает. Байки это.

SMT
11.09.2005, 20:30
c++ был как вариант, паскаль тоже можно, если удобнее (впрочем, большой разницы нет). главное, чтобы можно было конвертить любой компилируемый кусок программы в форт-код с минимальным вмешательством

SMT
11.09.2005, 21:09
то есть все функции пишутся и отлаживаются на си-паскале, и любую можно заменить п-кодом с минимальными исправлениями в исходнике. например, так


#ifdef FUNC1_REAL
void func1(int a) { ... }
#else
#include "func1_pcode.cpp"
#end

а препроцессор читает func1 из основного исходника и делает файл func1_pcode.cpp вида
static const func1_code[] = { 0xC3, 0x00, 0x00, ... };
#define func1(a) forth_call(func1_code,a)

Vladimir Kladov
12.09.2005, 17:17
Я много пишу на Delphi, и много пишу мини-компиляторов с разных языков. Даже с паскале-подобных делал в последнее время. Мой опыт говорит, что невозможно за доли секунды перекомпилировать огромный модуль после внесения в него некоторого количества изменений. А Delphi делает вид, что как-то успевает это сделать. И успевает за ту же секунду еще и экзешник пересобрать, и на отладку запустить. Другого способа, как пред-компиляция на этапе редактирования текста, просто не видно.

thims
12.09.2005, 17:48
Ну это-то проверить легко. Запускаем filemon, ставим фильтр на delphi, топчем исходник и смотрим... Вряд ли он в память компилит (если компилит, я очень сомневаюсь).

SMT
12.09.2005, 19:02
вообще-то можно сравнить, сколько тратит времени консольный компилятор. мне кажется, столько же. завтра проверю...

Vladimir Kladov
12.09.2005, 19:36
а почему и не в память, dcu (предкомпилированный модуль) не обновляется пока на нажать "компилировать", фокус в том, что происходит это мгновенно для исходника размером 2Мбайта текстов.

AlexCrush
13.09.2005, 09:23
(ОФФ) Просто дельфийский компилятор - самый быстрый компилятор паскаля в мире. Кроме того сам паскаль проще чем C++ (нет препроцессора и шаблонов, порождающих мильоны строк левого текста), поэтому получается быстро. Предкомпиляцию ни разу не наблюдал (хотя она может и есть), но почему тогда борландовцы так же в C++Builder не сделали - там все не так, все по VC-шному медленно? Вывод (имхо): предкомпиляции нет, есть супербыстрый компилятор

thims
13.09.2005, 15:33
Предкомпиляцию ни разу не наблюдал (хотя она может и есть), но почему тогда борландовцы так же в C++Builder не сделали - там все не так, все по VC-шному медленно?
Это не по VC-шному, а по C++-ному. Ибо ты же сам сказал: "препроцессор, шаблоны". Предкомпиляцию сделать можно, наверное, но очень сложно и неоправданно.

Вывод (имхо): предкомпиляции нет, есть супербыстрый компилятор
Ну вывод не из этого следует. А то, что дельфовый компилятор просто очень быстрый - это факт. Объясняется существенной упрощенностью паскаля по сравнению с Си++.

Vladimir Kladov
13.09.2005, 18:44
вообще-то если удалить dcu-файл на диске, то "сверх-быстрый компилятор от борланда" на полминутки, но задумается. 2 метра откомпилить на пол-секунды - нужен супер-компьютер какой-нибудь... Я скорее поверю в пред-компиляцию.

Vladimir Kladov
13.09.2005, 18:47
А в С++ это невозможно, потому что в С++ как и в С нет модулей. ВСЯ программа на С - это один большой листинг, склеенный из #include, и компилироваться должен всегда весь и сразу. Ну нету там модулей. А по процедурам отдельно тоже сильно не разбежишься предкомпилировать.

SMT
13.09.2005, 19:02
ага. зато inline'ы, препроцессинг и глобальные оптимизации невозможны (блин, забыл сегодня потестить скорость компилятора с ком. строки)

AlexCrush
14.09.2005, 09:47
Это не по VC-шному, а по C++-ному.
Да, согласен.


Ну вывод не из этого следует.
Это я неверно выразился. Я имел в виду, что вывод в моем размышлении - из всего вместе взятого, а не только из медленности C++Builder'a.



ВСЯ программа на С - это один большой листинг, склеенный из #include, и компилироваться должен всегда весь и сразу.
Не верно. Вся программа - набор отдельных модулей *.cpp и/или *.c. В них, возможно, включены какие либо *.h или *.hpp файлы. Каждая такая cpp-шка с подставленными инклюдами компилируется ОТДЕЛЬНО! а затем делается link. при изменении одного модуля (есть такое понятие!) перекомпиливается только он, затем все линкуется.

Все равно не верю в предкомпиляцию в Delphi. :-)
Сорри за совсем-совсем офф...

Vladimir Kladov
26.10.2005, 09:05
вернуться к идеет форт-подобной машины. Все равно много пришлось изменить (например система команд П-машины сократилась до 6 команд), зато эмуляция такой машины занимает теперь 128 байт кода на PC. И еще 100 байт - SEH-обработчик и его установка. Но надо много еще сделать: перелопатить компилятор форт-подобного П-языка, отладчик, генератор кода... Хорошо бы за месяц уложиться......

jerri
26.10.2005, 10:46
вернуться к идеет форт-подобной машины. Все равно много пришлось изменить (например система команд П-машины сократилась до 6 команд), зато эмуляция такой машины занимает теперь 128 байт кода на PC. И еще 100 байт - SEH-обработчик и его установка. Но надо много еще сделать: перелопатить компилятор форт-подобного П-языка, отладчик, генератор кода... Хорошо бы за месяц уложиться......
а можно поподробнее? но только в спековском коде а то вот уже интересно стало про эмуляцию в 128 байт

SMT
26.10.2005, 17:57
6 команд, неплохо. а что за команды? форт-слова, которые выполняются непосредственно, без ссылки на определение слова? можешь дать список?


надо много еще сделать: перелопатить компилятор форт-подобного П-языкаа из какого формата планируется компилировать и в какой?

Vladimir Kladov
26.10.2005, 18:43
6 команд, неплохо. а что за команды? форт-слова, которые выполняются непосредственно, без ссылки на определение слова? можешь дать список?

а из какого формата планируется компилировать и в какой?

--- начало
0xxx.xxxx - загрузки констант и команды переходов

000х.хххх - загрузка коротких констант от -16 до +15
001х.хххх yyyy.yyyy - безусловный переход на -4096..+4095 байт
010x.xxxx yyyy.yyyy - условный переход (если на вершине 0)
011х.хххх yyyy.yyyy - условный переход (если не 0)

1xxx.xxxx - команды вызова подпрограмм

11хх.xxxx - короткие вызовы первых 64 подпрограмм с номерами 0..63
10xх.хххх yyyy.yyyy - вызовы до 16384 подпрограмм

При наличии значительно меньшего общего числа подпрограмм, возможно изменение веса (в сторону увеличения числа подпрограмм, вызываемых 1-байтовой командой), например:
1000.00xx yyyy.yyyy - вызов 1024 подпрограмм
1000.0100 = 84..FF = 0..7B = 0..123. Т.е. 124 подпрограммы может быть вызвано в таом случае 1-байтовой командой, но общее число подпрограмм ограничено 1024 штуками.


Все прочие команды, в том числе возвраты из подпрограмм, загрузка констант 1 байт и более длиной, и т.д. выполняется попрограммами, которые и расширяют функциональность форт-машины.

--- конец

Дык из (т)екстового формата. Имя процедуры - компилируется в вызов. L(n) в загрузку константы n. И т.д.

Правка: все уже сделано, надо только до ума довести.

SMT
26.10.2005, 21:25
аа. понятно. я думал, ты взялся за оптимизирующий компилятор с ЯВУ

Vladimir Kladov
26.10.2005, 22:12
оптимизирующий компилятор с ЯВУ
нет с ЯВУ пока вручную или генератором кода (у меня в KOL+MCK часть Паскаль кода генерится, - и совсем несложно там же приделать генерацию исходного П-кода). Может когда-нибудь и из Паскаля в исходный П-код компилятор смогу сделать. Пока хотя бы сам.

Vladimir Kladov
07.01.2006, 19:13
Первый этап закончил: эмулятор байт-кода есть, генерация П-кода и компиляция П-кода в байт-код есть. Уменьшил размер экзешника на 100К (сжатый уменьшился на 21К всего. Немного, но и исходного кода, для которого генерится П-код на автомате, тоже не очень-то много). Байт-код получился в 2 раза компактнее такого же машинного. Замедление скорости на глаз неразличимо (тем более что заменяется код инициализации диалогов). Следующим ходом попробую сделать компилятор Паскаль->П-код, и тогда можно будет сжать весь паскаль-код, который не требует высокого быстродействия.

ZXMAK
01.02.2006, 18:04
Первый этап закончил: эмулятор байт-кода есть, генерация П-кода и компиляция П-кода в байт-код есть. Уменьшил размер экзешника на 100К (сжатый уменьшился на 21К всего. Немного, но и исходного кода, для которого генерится П-код на автомате, тоже не очень-то много). Байт-код получился в 2 раза компактнее такого же машинного. Замедление скорости на глаз неразличимо (тем более что заменяется код инициализации диалогов). Следующим ходом попробую сделать компилятор Паскаль->П-код, и тогда можно будет сжать весь паскаль-код, который не требует высокого быстродействия.

Идея конечно интересная, если я правильно понял ты хочешь сделать JIT компилятор псевдоязыка, на котором описывается логика обработки инструкций Z80, верно?

правда я уже успел полюбить .net, где подход с JIT компиляцией не так прост и может вылится в еще больший код... :) впрочем чистый код полного эмулятора Z80, включая и дизассемблер на IL у меня занимает 73 кб (в архиве 13,5 кб), что на мой взгляд не так и много... но я всетаки хочу переписать его в некую форму микрокода, где базовые операции строятся на основе машинных циклов Z80... может чтото и получится...

Vladimir Kladov
01.02.2006, 21:01
нет, я отказался от этой идеи. Вместо этого я сделал эмулятор байт-кода "вообще". Полезнее для уменьшения размеров не только одной этой программы эмулятора, но и кучи других.

.net это когда софт уже (почти) есть, а железо, которое его может принять, еще пока дорого. Так что я не тороплюсь. Кроме того, это надо юзеру ставить runtime-библиотеки от .net, а вот надо ли это ему.

Vladimir Kladov
01.02.2006, 21:18
и про JIT: как я понимаю, это совсем не то, потому что мой байт-код эмулируется, а не транслируется перед выполнением. Свой JIT я сделал еще раньше в эмуляторе, и как раз для увеличения скорости, и это как раз встроенный в эмулятор макро-компилятор PC-Asm, который компилирует в память в зависимости от опций, и у меня получается максимально быстрый код для эмуляции Z80 во всех сочетаниях режимов и для вывода графики. И без всяких (почти if-ов внутри, потому что if-ы отрабатываются компилятором. Это несколько как раз увеличило размер, но зато дало поддержать гораздо больше режимов.