Вход

Просмотр полной версии : Нужна виртуальная машина на ZX



alone
08.01.2012, 18:02
Предлагаю всем вместе поломать головы над виртуальной машиной для Z80. Задачи, которые стоят:
1. Код плотнее, чем на Z80.
2. Перемещаемость (помогло бы для ОС с одним окном памяти, где код лежит внизу).
3. Реентерабельность (опять-таки для ОС).
4. Возможность использования ассемблерных вставок с ограничениями.

Raydac
08.01.2012, 18:07
виртуальная машина для Z80 что бы плотнее перемещаемее и реентабельность это уже придумано более 30 лет назад )) называется FORTH и пишется за пару дней и есть все перечисленное )))
p.s.
правда что бы на FORTH программить надо мыслить в обратной польской записи )))
p.p.s.
Иван Макарченко в остаток альтеры в спринтере forth-процессор зашивал насколько помню

alone
08.01.2012, 18:12
Трезвой форт-машины для Z80 не знаю. Покажите процедуру NEXT.

Raydac
08.01.2012, 18:16
>>Покажите процедуру NEXT.
если это насчет моей поделки, то там нет и не может быть процедуры NEXT, это не эмуль JVM это транслятор JVM байт кода напрямую в команды Z80
а так и FORTH ведь есть без NEXT, когда в прямой код компилится

mastermind
08.01.2012, 19:20
alone, а цели какие? Виртуальная машина для чего? Чтоб исполнять результат трансляции какого-то собственного языка с ассемблерными вставками?
Кроме п.1 из перечисленных не вижу зачем нужна именно виртуальная машина. П. 2 - вопрос дизайна/реализации загрузчика/OS, п. 3 - вопрос реализации того куда нужна эта реентерабельность ;) , п.4 - в том же LLVM это более чем предусмотрено (см. http://blog.llvm.org/2010/04/intro-to-llvm-mc-project.html про "Compiler-Integrated Assembler")

Я к тому что для достижения п.1 (для всего остального ВМ не обязательно) с помощью LLVM можно сделать решение еще лучше.
Можно сделать сначала просто бэкенд (таргет) для Z80, а затем на основе него сабтаргет (subtarget) с дополнительным набором "инструкций" (для экономии памяти), генерирующий прологи/эпилоги для нативного кода и т.п. + виртуальную машину для исполнения результата. (кстати, можно еще для всяких акселераторов и т.п. псевдоинструкции делать ;) )
В результате можно получить не "виртуальную машину с возможностью использовать ассемблерные вставки", а компилятор чего угодно в желаемый байткод в перемешку с нативным кодом (т.е. когда ради "кода плотнее" нет смысла использовать байткод, компилятор может генерить нативный код), ну и возможность использовать ассемблерные вставки никуда не денется. Если хочется еще какой-то свой язык специфичный, фронтэнд можно сделать.

Документации (и кода, т.к. не поспевает документация за изменениями), конечно, дофигища придется перечитать. Я читал/копался немного, голова кругом идет от объема информации, но могу сказать что штука очень хорошо задизайненная и крайне гибкая. Всем желающим делать какие либо виртуальные машины для чего угодно, компиляторы или JIT-ы (или все сразу), категорически рекомендую :) Отличный framework для всех перечисленных целей (и более того).

Инструкция по разработке backend-ов: http://llvm.org/docs/WritingAnLLVMBackend.html
Примеры реализации см. в исходниках в llvm/lib/Target (X86, естественно, самый вылизанный бэкенд на данный момент)

Raydac
08.01.2012, 19:26
на местном форуме я уже лет семь и могу сказать что тут всегда есть только одна проблема ))) куча народа предлагает какое то клевое решение или идею но никто (!) не догадывается что можно это запрограммить и показать как макет )))

alone
08.01.2012, 19:30
Цель такая: загнать многозадачную ОС в 128К. Пока нет решения, предлагаю закрыть вообще тему многозадачной ОС на 128К. Если есть как минимум два окна (ATM Turbo, NeoGS) такие вопросы не встают.

Впрочем, задача создания плотного кода интересна для разработки прошивок ПЗУ со всякими командерами, дискдокторами и т.п., где скорость не важна, а места мало.

mastermind
08.01.2012, 19:50
Raydac, я делюсь информацией о которой знаю. Для реализации вышеописанного у меня лично не хватает ни времени ни мотивации, но не хочется чтоб кто-то кто возьмется что-то такое делать, изобретал велосипед, когда уже есть отличный инструментарий.
А "макетов" в общем-то предостаточно :), только не для Z80, ссылку на демо одного из них я давал.

---------- Post added at 17:50 ---------- Previous post was at 17:40 ----------


Цель такая: загнать многозадачную ОС в 128К. Пока нет решения, предлагаю закрыть вообще тему многозадачной ОС на 128К. Если есть как минимум два окна (ATM Turbo, NeoGS) такие вопросы не встают.
Ну тут основная проблема, как я понимаю, в перемещаемости. Т.е. проблема прежде всего в том что существующие средства разработки не умеют генерировать перемещаемый код. (рискую стать похожим на заевшую пластинку ;) , но в том же LLVM все с этим в порядке, естественно, можно свои форматы исполняемых файлов с информацией о перемещениях реализовывать или адаптировать существующие)

Eltaron
08.01.2012, 20:39
Ну тут основная проблема, как я понимаю, в перемещаемости. Т.е. проблема прежде всего в том что существующие средства разработки не умеют генерировать перемещаемый код.
Большинство средств это как раз умеет. Ассемблер из GNU Binutils для z80 генерирует COFF, Си-компилятор gcc генерирует даже ELF. Оба формата содержат всю информацию о тех участках кода, куда нужно добавить какое-нибудь смещение при релоцировании.
ELF перебор, а вот COFF поддержать на спекки милое дело
sdcc тоже генерирует какой-то перемещаемый формат, но свой, не стандартный.

mastermind
08.01.2012, 21:38
Это вышеупомянутое "большинство" часто спектрумистами используется? :) Я неточно выразился, конечно. Имел ввиду наиболее употребляемые средства.
ELF да, наверное слишком громоздок. А хотя бы загрузчики COFF есть на спеки, интересно, хоть в каком-то виде?

jerri
08.01.2012, 21:47
mastermind, а оно надо?
и для чего?
для исдоса? для трдоса?

alone
08.01.2012, 22:36
Большинство средств это как раз умеет. Ассемблер из GNU Binutils для z80 генерирует COFF, Си-компилятор gcc генерирует даже ELF. Оба формата содержат всю информацию о тех участках кода, куда нужно добавить какое-нибудь смещение при релоцировании.
ELF перебор, а вот COFF поддержать на спекки милое дело
sdcc тоже генерирует какой-то перемещаемый формат, но свой, не стандартный.
Программа должна быть перемещаемой на лету, со всеми переменными-указателями, регистрами-указателями и массивами указателей.

vinxru
08.01.2012, 22:50
Предлагаю всем вместе поломать головы над виртуальной машиной для Z80. Задачи, которые стоят:
1. Код плотнее, чем на Z80.
4. Возможность использования ассемблерных вставок с ограничениями.

Код Z80 заархивированный RAR-ом

alone
09.01.2012, 01:23
С релокацией данных есть два решения:
а) Использовать только массивы, никаких указателей.
б) Типизировать переменные (указатель/не указатель). Регистры тоже.

С релокацией стека тоже два решения:
а) Типизировать значения в стеке (число/адрес).
б) Класть в стек не адреса процедур, а их номера.

bigral
10.01.2012, 03:06
С релокацией данных есть два решения:
а) Использовать только массивы, никаких указателей.
б) Типизировать переменные (указатель/не указатель). Регистры тоже.

С релокацией стека тоже два решения:
а) Типизировать значения в стеке (число/адрес).
б) Класть в стек не адреса процедур, а их номера.

ого никогда не мог понять язык гуру, есть вопросы

1. a) в чем суть? чтобы прога оперировала не куском данных а номером куска данных? это ж тормоз какой - сложи байт под рег. номером 2 с long-ом под номером 8 и положи результат в стринг под номером 88?

2. b) без стека вообще может? сначала define всех процедур\слов как в fort а потом применить их как новые "слова\команды"

vinxru
10.01.2012, 10:16
Это мне напоминает компьютер TI99/4

Как в других компьютерах того времени, сразу после включения компьютера, пользователю был доступен язык программирования Бэйсик. Но ПЗУ компьютера содержало не Бэйсик, как следовало ожидать. Оно содержало интерпретатор байт кода, который назывался GPL. Программы для этого интерпретатора были записаны в специализированные ПЗУ, которые назывались GROM (Graphics ROM).

Чтение данных из GROM осуществлялось через 8-битные порты ввода-вывода. Установка адреса чтения происходила за 2 команды процессора, сначала записывались старшие 8 бит адреса, затем младшие. При каждом чтении данных, адрес чтения автоматически увеличивался. При этом, GROM работал намного медленнее процессора.

Производительность программ записанных в GROM была крайне низкая. И никаких активных игр с использованием GROM написано не было. Почти для всех игр программисты использовали стандартное ПЗУ и машинный код. А GROM использовался лишь там, где производительность была не нужна. Например стартовое меню игр.

Интересной особенностью GROM было то, что последние 2 Кб каждых 8 Кб данных не использовались. Таким образом, адресное пространство GROM составляло 48 Кбайт.

Дак вот. Бэйсик, использовавшийся в этом компьютере, был написан на языке GPL и записан в микросхему GROM, установленную внутри компьютера. То есть, интерпретатор Бэйсика сам написан на интерпретаторе! Двойная интерпретация.

Программа, написанная на Бэйсике, хранится в видео памяти. Доступ к ней возможен только через медленные порты ввода вывода.

И получаются страшные тормоза...

ZEK
10.01.2012, 11:01
Самое серьезное байткодовое что есть для Z80 это http://ru.wikipedia.org/wiki/UCSD_p-System. Машина стековая. Исходники доступны на ассемблере 8080/Z80

DimkaM
10.01.2012, 11:54
Нужна виртуальная машина на ZX
Я так понял кое какие наработки в голове уже есть?
Можешь более развёрнуто описать сабж? Я так понимаю что это что то типа бейсик48 калькулятора(очень приближённое сравнение)?!

Предложение:
1.МиниРедактор ОПкода желателено в ПЗУ тоже иметь.
2.Сначала реализовать минимальный прототип онли 48к, чтобы оценить возможности и потребности.

alone
10.01.2012, 12:47
1. a) в чем суть? чтобы прога оперировала не куском данных а номером куска данных? это ж тормоз какой - сложи байт под рег. номером 2 с long-ом под номером 8 и положи результат в стринг под номером 88?
Примерно так. Естественно, константы будут непосредственно в коде.


b) без стека вообще может? сначала define всех процедур\слов как в fort а потом применить их как новые "слова\команды"
Вложенность вызовов требует стека. Надо же знать, куда возвращаться после подпрограммы!

DimkaM
10.01.2012, 14:51
Надо же знать, куда возвращаться после подпрограммы!Передавать в параметрах?

bigral
10.01.2012, 16:01
Вложенность вызовов требует стека. Надо же знать, куда возвращаться после подпрограммы!

Подпрограммы не нужны если есть "макросы" которые длинной как и все другие команды processor-a (в данном случае виртуального). В том же forth-e ничего такого нету, там каждое "слово" всего лишь ID соответствующего куска кода который может включать и другие "слова". Сначала идет define "слов" а потом прога из последовательности "слов" (все эти "слова" по сути подпрограммы). Как я понимаю postscript тоже похож.

И вот еще что. Не выйдет никакого virtual Z80 на 48k это может быть другой VM заточенный изначально на переносимость\hibernate-restore возможности.

Тормоз данного софта, если таковой будет написан, будет поражать умы светил этого форума. НО! Скорее всего будет довольно легко запустить его на прикрученном сбоку к спектруму акселераторе в виде какого-нибудь risc cpu на минимальной altera или parallax propeller.

alone
10.01.2012, 18:15
Передавать в параметрах?
А параметры где будут храниться? Представьте, что процедура A вызывает процедуру B, а та процедуру C.

bigral
10.01.2012, 21:21
А параметры где будут храниться? Представьте, что процедура A вызывает процедуру B, а та процедуру C.

Параметры в стеке - такие же данные как и те что лежат на heap-е ПО СУТИ! (понятно что стек аппаратно поддержан в CPU но в нашем случае это не важно так как речь идет об НАШЕЙ VM в которой можно отказаться от стека). Так что параметры можно передавать либо в регистрах либо через heap.

DATA& param = new DATA(); // выделить на heap место для param, param при этом указатель

procA {
procB(param); // param идет либо в регистре либо как в FORTH-e следующим за "словом" (УКАЗАТЕЛЬ КОМАНДЫ+1)
}
procB(DATA& param) {
procC(param);
}

procС(DATA& param) { }

GriV
10.01.2012, 21:56
1. Код плотнее, чем на Z80.
2. Перемещаемость (помогло бы для ОС с одним окном памяти, где код лежит внизу).
3. Реентерабельность (опять-таки для ОС).
4. Возможность использования ассемблерных вставок с ограничениями.
1. Смотря что понимать под плотностью кода: если ты про агрегатные функции типа печать строки/символа, то это ЯВУ или хотя бы уровень Си.
Требование непонятное, скорее всего тут логичнее, как уже советовали, какое-нибудь LZ*. Все биосы (ПО) современных компов сжимаются таким методом.
2. Перемещаемость на этапе загрузки реализована витамином. Смотрим вот (http://zx.pk.ru/showthread.php?t=5335) и вот (http://zx.pk.ru/showthread.php?t=3925). Перемещаемость после загрузки - слишком дорого в тактах процессора.
3. Это сложно. Современные программисты для ZX так не умеют. Они либо экстремально кодят, с самомодифируемым кодом, либо вообще не умеют писать адекватный код. Реализуемо, использовать стек как способ работы и требовать обязательно сохранение данных в некий аналог кучи (heap). Это требование замедляет результирующий код.
4. Таки ты хочешь некое ЯВУ?

alone
11.01.2012, 07:40
Параметры в стеке - такие же данные как и те что лежат на heap-е ПО СУТИ!
Вот мы и возвращаемся к написанному выше. Если это адреса, то их надо типизировать при записи в стек, чтобы уметь релоцировать на лету (грубо говоря, пишем в стек не по 2 байта, а по 3: данные+тип). Если это номера подпрограмм, то проще.


Перемещаемость после загрузки - слишком дорого в тактах процессора.
А без этого никак. Если не релоцировать на лету, то после нескольких запусков/снятий задач нижняя память превратится в решето, куда уже ничего не поместится.


Таки ты хочешь некое ЯВУ?
Естественно.

James DiGreze
11.01.2012, 08:21
Для начала надо договориться о неперемещении программ во время исполнения.
Условно, если прог1 вызвал на выполнение прог2, то и прог1, и прог2 должны блокироваться на перемещение. При этом если прог1 после прог2 (не из прог2) вызовет прог3, то прог3 может быть перемещен как при работе прог1, так и прог2.

...(грубо говоря, пишем в стек не по 2 байта, а по 3: данные+тип)...Первый байт: (4бита - тип) + (4бита - длина). Второй и последующие - данные. Правда тогда массивы только ссылками придется передавать, например через команды создания-высвобождения копий.

GriV
11.01.2012, 08:36
Предлагаю следющее: у нас уже фактически есть SOS c Басиком, если сделать п/п басика, которая будет выполняться, но храниться при этом в самом ПЗУ?
Расвивая идею, не обязательно ориентироваться на басик.
Надо определить список функций, который необходимы. Затем список операций, которые эти функции будут выполнять. Оттуда строить код, если так хочется. Компилятор это грубо, надо транслятор + парсер. Технически на функции биоса (если они тебе нужны) нужны простые функции вывода символов, построения рамок, менюселектор (это 1-2 кб максимум). Тогда проблема перемещаемости будет решена за счёт отсуствия машинного кода. Проблема реентерабельности тоже. Останется маленький вопрос разработки транслятора :)

NovaStorm
11.01.2012, 12:09
3. Это сложно. Современные программисты для ZX так не умеют. Они либо экстремально кодят, с самомодифируемым кодом, либо вообще не умеют писать адекватный код.
Это, на мой взгляд, довольно просто. Умеют =)
Но не хотят или это не требуется.

Реализуемо, использовать стек как способ работы и требовать обязательно сохранение данных в некий аналог кучи (heap). Это требование замедляет результирующий код.
Да куча тут не причём, это всё на стек-фрейме делается. Сильно код не замедляется, но к локальным переменным таки придётся обращаться через индексные регистры.

---------- Post added at 12:09 ---------- Previous post was at 11:45 ----------

Для хотелок АлКо, думаю, подойдёт гибридный шитый+машинный код. Но! Код плотнее Z80 сделать будет трудно, над этим думали создатели и 8080, и Z80. Гибридизация с машинным однозначно нужна для скорости.
Перемещаемость я пока не вижу способа организовать, тк в спеко-ВМ нельзя малой кровью сделать ни критические секции, ни тегирование.
На полную перемещаемость придётся плюнуть и довольствоваться для дефрагментации памяти перемещением между банками, где у нас есть хоть какая-то виртуальная память.

James DiGreze
11.01.2012, 12:48
Сильно код не замедляется, но к локальным переменным таки придётся обращаться через индексные регистры.Не обязательно, но возможно.


Но! Код плотнее Z80 сделать будет трудно, над этим думали создатели и 8080, и Z80. Гибридизация с машинным однозначно нужна для скорости.Ну не скажи! Банальное printf("Hello, peoples!"), будет в коде Z80 довольно витиевато выглядеть. Про скорость... тут надо определиться с областью применения. Собственно для рисования простых текстовых менюшек много скорости не надо. Ну ли там каких-нибудь дисковых операций.


Перемещаемость я пока не вижу способа организовать, тк в спеко-ВМ нельзя малой кровью сделать ни критические секции, ни тегирование.
На полную перемещаемость придётся плюнуть и довольствоваться для дефрагментации памяти перемещением между банками, где у нас есть хоть какая-то виртуальная память.Зачем так усложнять? Игры писать что-ли, али демки? Может тогда сделать проще - дать возможность вызывать интертрепатор байткода из кода Z80? Это будет, на мой взгляд, более рациональным.

alone
11.01.2012, 12:51
Может тогда сделать проще - дать возможность вызывать интертрепатор байткода из кода Z80?
При вызове в стек ляжет адрес возврата, который надо релоцировать. Та же проблема, только в профиль.

NovaStorm
11.01.2012, 13:16
>адрес возврата, который надо релоцировать
Ну положи туда виртуальный адрес, делов-то. Только если после вычисления реального прога будет перемещена ещё раз, тут-то мы и приплыли.
Или вот как, например, сделать простой инкремент переменной? Нельзя же мутексы на каждый чих городить.

James DiGreze
11.01.2012, 13:22
alone, пусть ляжет, оно ему как раз в прок.
Вспомни как калк бейсика 48 работает: попается адрес возврата, и оттуда начинает интерпретироваться байткод, до всречи на своем загогулистом рета самого калка. Тут тот же механизм будет как раз в пору. Ну и нужно сразу разделить понятия аппаратного стека Z80 от стека байткод интерпретатора.
Мне вот тут подумалось, что на самом деле можно реализовать практически интерпретатор Си. Просто за счет функций преобразования ссылок и динамического создания копий переменных.
А еще подумалось, что вот же были башковитые мужики, кто это все придумывал с нуля. И реально копаясь в своей памяти могу только пару своих личных изобретений вспомнить, причем не в компьютерной области, а все остальное либо компиляция информации, либо реверсинжениринг по внешнему виду.

NovaStorm
11.01.2012, 13:26
Не обязательно, но возможно.
Ну если про самомодификацию мы забыли, то неужели каждый раз считать адреса?
С IX/IY вполне хорошая скорость при минимуме объёма достигается. Или я ещё способы упускаю?

Ну не скажи! Банальное printf("Hello, peoples!"), будет в коде Z80 довольно витиевато выглядеть.
Пример с printf, по-моему, самый неудачный, наверное, из всей стандартной библиотеки =)

Про скорость... тут надо определиться с областью применения.
...
Зачем так усложнять? Игры писать что-ли, али демки?
Ну АлКо хочет, как я понимаю, ВМ и язык под неё общего назначения, и скорость тут будет играть весьма значительную роль.

GriV
11.01.2012, 13:30
Предлагаю следующую систему:
байт-код:
0 - печать следующей за ним аскии-Z строки в активное окно
1 - создание окна, указываются начало X,Y, длина ширина
2 - выбор активного окна, следующий байт - номер окна
3 - очистка окна
4 - ассемблерная вставка
5 - операция работы с переменными, тут непонятно как что. Предлагайте варианты.

По п.4 соглашения - запрешён самомодифицируемый код, запрещены сложные стековые операции, все доп. адресации через индексные регистры.
Какие ещё функции надо?

mastermind
11.01.2012, 13:36
Код плотнее Z80 сделать будет трудно, над этим думали создатели и 8080, и Z80.
Ну так система команд делалась под довольно ограниченное ALU. Сейчас же в байткоде можно предусмотреть, скажем 16-, 32- и т.д. разрядную арифметику, FP, более продвинутые логические, сдвиговые и пр. операции и т.п. За счет одного этого код можно сильно уплотнить.

James DiGreze
11.01.2012, 14:09
Ну если про самомодификацию мы забыли, то неужели каждый раз считать адреса?
С IX/IY вполне хорошая скорость при минимуме объёма достигается. Или я ещё способы упускаю?Самомодификацию байткода, я правильно понял?


Пример с printf, по-моему, самый неудачный, наверное, из всей стандартной библиотеки =)Хорошо, пусть будет uint32_t mul(uint16_t op1, uint16_t op2), или код x=mul(12345, x) ;), которое к примеру байткодом можно выразить так:


0x22, 0x35 /*copy var #35 and push result to stack */
0x25, 0x39, 0x30 /* push 12345 to stack */
0x49 /* mul stack */
0x32, 0x35 /*copy result from stack to var #35 */



Ну АлКо хочет, как я понимаю, ВМ и язык под неё общего назначения, и скорость тут будет играть весьма значительную роль.Вот я и пытаюсь понять, чего точно хочет AlCo.


0 - печать следующей за ним аскии-Z строки в активное окно
1 - создание окна, указываются начало X,Y, длина ширина
2 - выбор активного окна, следующий байт - номер окна
3 - очистка окна
4 - ассемблерная вставка
5 - операция работы с переменными, тут непонятно как что. Предлагайте варианты.
Имхо, слишком высокоуровнево пошел. Для начала простые операции с целочисленными переменными, потом работа с памятью и портами I/O, и уж потом всякие текстовые штучки.


Ну так система команд делалась под довольно ограниченное ALUТочнее сказать 4битное АЛУ.

NovaStorm
11.01.2012, 15:33
Самомодификацию байткода, я правильно понял
Не, я про машкод. Обращаться к локальным переменным через индексные регистры проще и быстрее всего на Z80, кажется.


Хорошо, пусть будет uint32_t mul(uint16_t op1, uint16_t op2), или код x=mul(12345, x) ;), которое к примеру байткодом можно выразить так:


0x22, 0x35 /*copy var #35 and push result to stack */
0x25, 0x39, 0x30 /* push 12345 to stack */
0x49 /* mul stack */
0x32, 0x35 /*copy result from stack to var #35 */


Ну это зависит от системы команд, хоть с непосредственной адресацией одной инструкцией со значениями сразу.
Вот на http://en.wikipedia.org/wiki/Threaded_code дофига вариантов рассмотрено.

Имхо, слишком высокоуровнево пошел. Для начала простые операции с целочисленными переменными, потом работа с памятью и портами I/O, и уж потом всякие текстовые штучки.
Согласен, сначала низкий уровень, а вот IO уже наверное можно и виртуализовать. Но уже на памяти можно на это дело начинать забивать из-за тормозов, по сравнению с машкодом.

bigral
11.01.2012, 21:13
0x22, 0x35 /*copy var #35 and push result to stack */
0x25, 0x39, 0x30 /* push 12345 to stack */
0x49 /* mul stack */
0x32, 0x35 /*copy result from stack to var #35 */


Я написал что стек нафиг не нужен как понятие и никто не заметил? Ну зачем он надо?

Вот к примеру в приведенном куске первая команда какую пользу приносит? Не проще ли заменить команды такого типа на 1 типа:
1 "выделить из кучи место и инициализировать его массивом байтов: 0x35, 0x39, 0x30" return addr
2 "умножить первый байт массива по addr на следующее за ним слово и результат вписать на их место"

Конечно стек прикольный тем что результат операции всегда в известном месте как и параметры для операции и это свойство на 100% используется forth-ом или java или этим: http://homepages.cwi.nl/~steven/pascal/book/10pcode.html

НО! в нашем случае можно от него отказаться так как команды не будут такими простыми как mov a,b а будут оперировать сразу кусками памяти минимум по несколько байт! кроме того сначала будет ити внушительный кусок который должен будет сначала обьявить что команды делают (тут можно простой язык применить с mapping-ом на z80 asm 1:1)

James DiGreze
12.01.2012, 05:31
1 "выделить из кучи место и инициализировать его массивом байтов: 0x35, 0x39, 0x30" return addr
2 "умножить первый байт массива по addr на следующее за ним слово и результат вписать на их место"Те же яйца, в анфас. Выделение памяти есть? Есть. Потом результат откуда брать? Типа ссылка? А потом как понять что данные уже нафиг надо удалить? Опять передавать ссылку и удалять ее из списка. Где выгода?

alone, своими мыслями поделись! А то мы тут уже скоро на кофейной гуще гадать начнем ;)

GriV
12.01.2012, 12:57
Ребята, вы вначале ТЗ составьте (убейте), а потом уже делите способ реализации (медведя). Я тут набор функций предлагаю, никто не поддержал, зато все ринулись в кодерство. Вначале думаем, потом кодим.

vinxru
12.01.2012, 13:42
Я написал свой интерпретатор. Стековый, но на этапе оптимизации стековые команды объединяются и рождаются безстековые операции. Дать его не могу, но могу помочь советами.

Там получился такой набор команд:

opCallFunction - вызвать стандартную функцию
opCallMethod - вызвать метод класса
opCall - вызов собственный метод (подпрограмму)
opPushSelf - поместить в стек this (указатель на свой объект)
opJmp - перейти на строку
opLongJmp - перейти на строку и освободить стек
opRet - завершить выполнение

Далее обработка исключений.

Для каждой команды указан адрес, куда следует перейти, если произошло исключение. А так же, что следует удалить из стека. После освобождения стека, в стек кладется объект Exception с описанием ошибки.

И опкоды, которые используются только в блоках FINALLY..END или EXCEPT..END (catch...end):

opTryFinallyEnd - если на верхушке стека лежит не NULL, то исключение. Иначе освободить стек.
opThrow - сгенерировать исключение по объекту лежащему в стеке.
opGetExceptionText - поместить в стек текст исключения из объекта лежащего в стеке.

Далее идут команды для работы с типами данных VARIANT, OBJECT, INTEGER, FLOAT, BOOLEAN, STRING, CURRENCY ...

Например набор команд для Integer:

opPush_i - Поместить в стек непосредственное значение
opPop_i - Освободить стек от Integer (увеличить указатель стека на sizeof(Integer))
opPushVar_i - Из переменных в стек
opPopVar_i - Сохранить из стека в переменные
opPushObj_i - Из объекта в стек
opPopObj_i - Из стека в объект
opDup_i - Дублировать значение в стеке

opAdd_i,opSub_i,opMul_i,opDivInt_i,opDiv_i,opMod_i ,
opAnd_i,opOr_i,opXor_i,opCmpE_i,opCmpNE_i,opCmpL_i ,
opCmpG_i,opCmpLE_i,opCmpGE_i,opCmpNEX_i,opNeg_i,
opShl_i, opShr_i - эти команды производят операцию с парой чисел на верхушке стека так, что остается одно число.

Да, а команда условного перехода всего одна. Она в наборе команд Boolean:

opJt_b - Перейти, если TRUE.

Но оптимизатор пораждает массу команд перехода. Например, opNeg_b + opJt_b заменяет на opJf_b

В результате оптимизации может рождится команда типа:
opPushVar_i_opPush_i_opCmpE_opJf_i(A, B, C)
которая вообще не работает со стеком. Она выполняет действие
if(variables[A] == B) ip += C;

Или вот пример оптимизации получения элемента массива:
opPush_i 0
opPushObj_o номер
opPushVar_i номер
opCallMethod arrayOfInteger::getItem
opPop_i
opPop_o
Заменяется на единственный опкод:
opArrayOfIntegerGet_o_v(A,B)
Который выполняет действие:
ArrayOfInteger* a = objectVariables+A; if(B>=a->count) throw_bound(); *st++ = a->items[B];

Эти все оптимизации ускоряют работу, но размер исходника интерпретатора занимает несколько сотен килобайт.

James DiGreze
12.01.2012, 16:38
Griv, это кто же это кинулся в кодерство? Я написал "от фонаря" байт код, типа с пояснениями, в стековой реализации, которая на мой, может быть не хужее "хипной". А ТЗ мы уже похерили, только вот медведь не делится на ноль, хоть тресни меня битой в лоб ;)
Уже несколько раз перечитал первый пост AlCo, возникло подозрение, что первоначальная мысль была отнюдь не за байт-код, а именно некоторое подобие муляции кода Z80 с возможностью дополнения новыми командами а-ля префикс типа неиспользуемого rst x.
AlCo, выходи уже из раздумий, а то я пошел за варенным кофе!

alone
12.01.2012, 17:52
Код плотнее Z80 сделать будет трудно, над этим думали создатели и 8080, и Z80.
Даже код калькулятора бейсика плотнее. См. питона в Info Guide #10.

У меня пока идей нет. Байт-код явы мне весьма понравился.

Oleg N. Cher
12.08.2012, 23:04
Трезвой форт-машины для Z80 не знаю. Покажите процедуру NEXT.
Есть одна идея, как можно реализовать наиболее быстрый вариант виртуальной машины на Z80 — может и не по всем пунктам, обозначенным топикстартером, но вариант интересный. Судите сами.

Это аналог Форт-машины с прямым шитым кодом. Подпрограммный шитый код мы не рассматриваем, потому что это фактически подмножество машинного кода, просто организованный массив подпрограмм со своей логикой.

1. Код плотнее, чем на Z80.

Отчасти, да. Интересные вещи писали авторы отечественных Форт-систем о компактности фортовского шитого кода, который превосходил реализации с машинным кодом.

2. Перемещаемость (помогло бы для ОС с одним окном памяти, где код лежит внизу).

Нет, потому что код шитый. Или разве что да, но будет требоваться простая настройка адресов, что не должно вызвать проблем.

3. Реентерабельность (опять-таки для ОС).

Вероятно, это можно реализовать.

4. Возможность использования ассемблерных вставок с ограничениями.

Безусловно.

Прямой шитый код Форт-машины — это последовательность адресов вызываемых подпрограмм на Форте или в машинном коде. Для Z80 может выглядеть примерно так:


DW LITERAL_BYTE ; Заносим на стек
DB 5 ; число 5
DW DUP ; Дублируем значение на стеке
DW MULT ; Умножаем само на себя (возводим в квадрат)
DW EMIT ; Печать числа

Вероятно, большой проблемой будет слово NEXT, от которого сильно зависит скорость работы всей машины. (NEXT передаёт управление следующему за текущим словом. Вероятно, имеет смысл модифицировать шитый код для большей производительности слова NEXT, если это вообще возможно. Моё предложение имеет один минус, который многим покажется критичным. Тем не менее, всё-таки посмотрите, что предлагается.


DI
LD SP, ExecThread
RET

DW EMIT ; Адреса слов потока исполнения, но в обратном порядке
DW MULT
DW DUP
DB 5
DW LITERAL_BYTE
ExecThread: ; Сюда устанавливается верхушка стека

В такой реализации словом NEXT в шитом коде будет адрес, указывающий на RET (и обычный RET в машинных подпрограммах). Не все проблемы решены. Остаются трудности с вложенными вызовами шитого кода из шитого кода (или из машинной подпрограммы) — нужно куда-то сохранить адреса возврата. Значит нужен стек, притом не SP, ибо он активно занят другими делами. Если вложенность гарантированно будет одинарная (ну, такой гипотетический случай), должно хватить одного регистра под это. Например, IY.

Теперь о самом главном минусе. Это конечно запрещённые прерывания. Не вижу способа их разрешить. Полумерой предлагаю лёгкую подпрограмму, которую можно будет вручную вызывать время от времени из своих подпрограмм.

Плюсы решения: при запрещённых прерываниях не нужно на них отвлекаться процу — код будет работать быстрее. Второй плюс — такая машина может быть регистровой, а не стековой, как Форт. И параметры будут передаваться в регистрах.

Практическое применение этой идее наверно найти трудно, но вдруг у кого-то появится мысль как её усовершенствовать.

P.S. На законченность концепции не претендую — так, сырая идея.

NovaStorm
13.08.2012, 08:28
А что бы и не JP (HL)? Да и это всё равно мелочи реализации.

jerri
13.08.2012, 11:16
Oleg N. Cher, Идея неплохая
рекомендую также посмотреть игру IronLord (http://www.worldofspectrum.org/infoseekid.cgi?id=0002543) от UbiSoft

Разумеется изнутри
есть вероятность что либо Форт либо какой то свой язык

NovaStorm
13.08.2012, 11:28
DI
LD SP, ExecThread
RET

Вот подумалось тут ещё, а адреса-то должны быть виртуальными...

Barmaley_m
13.08.2012, 20:05
Олег, данная концепция именно в том виде, как вы описали, используется в ксорке-защите от Max Iwamoto. Можно посмотреть во многих его Cracktro к играм. На стеке лежала куча адресов, каждый из которых указывал на коротенькую подпрограмму, заканчивавшуюся, как правило, командой RET.

Максом Ивамото, конечно, преследовалась цель не уплотнить код, а запутать его. Но эта задача не была им решена. Стоит только разобраться с основной идеей, что на стеке расположена фактически программа из инструкций, семантика которых задается лежащими на стеке адресами подпрограмм - как все становится понятно, а изучающий эту защиту получает чувство глубокого удовлетворения от знакомства с красивой концепцией и легкостью, с которой он ее взломал. Каждой "инструкции" присваивается мнемоника, потом адреса подпрограмм заменяются на эти мнемоники - и вуаля, программа как на ладони. Там и циклы были, и условные переходы и, конечно же, регистр R.

psb
13.08.2012, 22:44
а сейчас это называется Return Oriented Programming и широко используется во взломах программ.

Andrew771
23.06.2020, 14:42
Код:
DI
LD SP, ExecThread
RET

DW EMIT ; Адреса слов потока исполнения, но в обратном порядке
DW MULT
DW DUP
DB 5
DW LITERAL_BYTE
ExecThread: ; Сюда устанавливается верхушка стека

Теперь о самом главном минусе. Это конечно запрещённые прерывания. Не вижу способа их разрешить. Полумерой предлагаю лёгкую подпрограмму, которую можно будет вручную вызывать время от времени из своих подпрограмм.

А почему прерывания должны быть запрещены?
*(Извините, если что, башка от жары перестала варить)

Oleg N. Cher
23.06.2020, 14:57
Ну как же. На стеке лежат адреса подпрограмм, переход на них осуществляется командой RET. А любое пришедшее прерывание сразу затрёт адрес, как минимум, своим адресом возврата.

b2m
23.06.2020, 15:02
А почему прерывания должны быть запрещены?
Потому, что SP указывет тут не на стек, а на программу. Если возникнет прерывание, программа испортится (будет записан адрес возврата).
И кстати, "Адреса слов потока исполнения" должны быть как раз в прямом порядке. RET увеличивает указатель SP.

Andrew771
23.06.2020, 15:22
Вроде дошло, спасибо!
Может тогда при разрешенных прерываниях в процедуре прерывания вначале сохранять кусок стека, а затем в конце перед выходом восстанавливать?

Oleg N. Cher
23.06.2020, 15:35
Так было бы можно в случае более глубокого использования стека. Но на одно (то самое, для адреса возврата) слово стек будет испорчен всегда. До сохранения ещё дело не дошло. В этом-то и проблема.

Lethargeek
24.06.2020, 00:56
не такая уж и проблема, всегда можно по контрольной сумме/ксорке восстанавливать испорченный адрес в обработчике прерывания

Andrew771
24.06.2020, 11:08
не такая уж и проблема, всегда можно по контрольной сумме/ксорке восстанавливать испорченный адрес в обработчике прерывания
Можешь подробнее описать, каким образом?

b2m
24.06.2020, 11:47
Самый простой способ - держать копию программы, и в прерывании восстанавливать повреждённые байты.

Lethargeek
24.06.2020, 13:13
Можешь подробнее описать, каким образом?
да что там описывать, условно разбиваешь подверженную порче память на блоки размером, допустим, по 256 байт
для каждого блока вычисляешь эталонную двухбайтную контрольную сумму или две ксорки (для чётных и нечётных байт)
в обработчике прерывания находишь по адресу в sp блок, вычисляешь для него новую контрольную сумму/ксорки
восстанавливаешь испорченные два байта по разнице с эталоном (предварительно забрав оттуда адрес возврата)

и неважно, какой смысл имеют данные в блоке: например, в commando ксорки применяются аналогично для восстановления спрайтов


Самый простой способ - держать копию программы, и в прерывании восстанавливать повреждённые байты.
расход памяти может оказаться слишком большим, хотя и несколько быстрее будет, конечно