Пожалуй, приложу сюда статью из журнала Downgrade и сам компилятор, вот он: https://yadi.sk/d/uL3BhwpF3CqodA
Там в папке samples - небольшой пример, его можно откомпилировать и поиграться
Кросс-компиляторы для Intel 8080
Несколько лет назад меня заинтересовала тема программирования для компьютера Апогей БК-01ц и я стал стал искать средства кросс-компиляции. Кросс ассемблер нашелся достаточно быстро, однако с компиляторами высокоуровневых языков программирования были проблемы. В отличии от Z80, для которого есть масса средств, для I8080 не было ничего пригодного для полноценной работы. Я бросил поиски и стал писать на ассемблере. Однако, недавно я снова взялся за это дело. Дело в том, что я вспомнил про существование CP/M – операционной системы того времени для компьютеров на Intel 8080, и для нее было написано много хороших компиляторов. Надо было найти эмулятор этой системы и как-то организовать «переброс» информации, то есть, чтоб листинг программы из файлов ПК переносился в файлы эмулироемой CP/M, там производилась компиляция и потом, уже откомпилированный бинарник отправлялся в файлы ПК, где мы делали с ним нужные нам действия, например, запускали в эмуляторе. Я начал искать эмуляторы CP/M и обнаружил отличный эмулятор – 22nice, который работает очень необычным образом.
Большинство эмуляторов работают следующим образом – берется образ диска с нужными программами и передается эмулятору. Создается некая песочница – файлы ПК отдельно, а файлы эмулируемой машины отдельно, и из эмулируемой нами машины нет никакой возможности изменить файлы вне образа, и наоборот, у стандартных средств windows нет возможности посмотреть что находится внутри образа. Образно говоря, файловые системы хост- и эмулируемой машины сильно разделены, и обмен файлами затруднен. Однако, 22nice работает по другому. Он не берет образ с CP/M системой, он берет отдельные исполняемые файлы CP/M и генерирует для них исполняемые «оболочки», с которыми можно работать как с обычными MS-DOS приложениями, получаются, как бы, порты CP/M софта под MS-DOS. Например, у нас есть CP/M программа, do.com которая берет файл a.txt, что-то с ним делает и сохраняет как b.txt, и мы хотим запустить ее при помощи 22nice. Для этого, при помощи специальной утилиты генерируется фаил, который мы называем do.com, а оригинальный do.com переименовываем в do.cpm и кладем в эте же директорию. Также, сюда помещаем фаил эмулятора 22nice. Все, теперь можно работать с do.com так, как будто он был написан для дос. Как же это работает? А втот так – при запуске do.com ищется фаил do.cpm, он находится и передается эмулятору 22nice, который уже исполняет находящийся там код. И если в программе есть обращения к диску, то они превращаются в обращения к диску дос, и файлы читаются от туда. То есть, наша программа будет читать фаил a.txt из текущего каталога, а когда что-то с ним сделает, то сохранит b.txt также в текущем каталоге. Короче говоря, действия будут абсолютно аналогичны если б её запускали где-нибудь с 8-дюймовой дискете на Альтаир 8800
Такая система нам очень подходит, так как запустить компиляторы в i8080 не заставит труда, а для CP/M их было написано множеством для раных языков. Я остановился на следующих:
MICROSOFT FORTRAN-80.
Фортран – очень старый язык, имеющий свои особенности. У него непривычный современному программисту синтаксис, однако он является самым быстрым, если нужны программы для наукоёмких вычислений (Например, расчет ядерного реактора), то у фортрана до сих пор нет конкурентов. Однако, эта версия скоростью не блещет. Она показала средние результата, местами быстрее, а местами даже медленнее нижеописанных компиляторов. И вообще, скорости исполнения программ практически одинаковы для всего набора, так что я не буду далее заострять внимание.
Что еще меня расстроило, так это устройство компилятора – он компилирует программу в объектный код, а потом соединяет его с рантайм-библиотекой, размер которой – 6.4 кб! Да, сейчас это кажется немного, но если объем всей памяти – 60 кб, то это уже существенный размер. Даже если вы написали Hello World, размер программы будет также 6.4 кб. Вот пример программы для поиска простых чисел на этом фортране
Код:
INTEGER A,B,C,D
5 DO 20 A=2,2000
D=A
B=1
C=1
15 IF (C.EQ.0) GOTO 20
IF (D.LT.B) GOTO 19
B=B+1
D=A/B
C=A-(D*B)
GOTO 15
19 IF (C.NE.1) WRITE(1,100) A
20 CONTINUE
100 FORMAT(5I5)
END
Также, одной из негативных сторон данного компилятора был жесткий синтаксис. То есть, если сделать лишний пробел между командами, то будет ошибка. Сделать пустую строку – ошибка. Пробелы вместо табуляции – также ошибка. В общем – не пойдет.
PL/1-80
Данный язык печально известен своей громоздкостью. Он появился так – в 60-х годах было два основных языка –КОБОЛ, на котором писали финансовые программы и ФОРТРАН, на котором писали научные. Было решено сделать новый язык, который объединял бы возможности этих двух языков, в итоге получился монстровский язык, стандарт которого был очень длинный и запутанный, так что ни один компилятор не поддерживает все его возможности. А их у него много – поддержка рекурсии, структурного, модульного программирования, длинной арифметики, многозадачности, форматированного вывода.
Если говорить о компиляторе для CP/M, то он намного лучше фортрана – есть свободный синтаксис, больше возможностей, да и вообще, язык удобней. Размер рантайм-библиотеки практически такой же – 6.5 кб. Однако, что интересно, в комплекте с компилятором шло несколько примеров, среди них – шахматы - 900+ строк. После компиляции размер программы был (вместе с рантаймом) всего 12 кб, то есть, плотность кода высокая – компилятор хороший. Вот программа для поиска простых чисел.
Код:
dtest:
proc options(main);
dcl (a,b,c,d) int;
do a=2 to 2000;
d=a; b=1; c=1;
do while ((d>b)&(c~=0));
b=b+1;
d=a / b;
c=a-(d*b);
end;
if (c~=0) then put skip list(a);
end;
end;
end dtest;
Однако у языка есть и недостатки – во первых, как уже говорилось выше – перегруженность. В языке много средств, которые не понадобятся при программировании для intel 8080, однако они там есть и сидят в памяти. Во вторых, я не нашел в языке команд для низкоуровневой работы - прямой доступ к памяти, ассемблерные вставки. Это очень большой минус, так как мы много чего не сможем сделать, например, в случае с Апогеем БК-01 нарисовать что-нибудь на экране, воспроизвести звук, ничего кроме алфавитно-цифрового ввода/вывода. Печаль
PASCAL/MT
Паскаль, мой любимый язык, сколько строк кода я на нем написал и продалжаю писать. И для CP/M он тоже есть. Вообще, есть даже Turbo Pascal, но он требует процессора Z-80, а нам нужен i8080. Поэтому я выбрал PASCAL/MT. Он достаточно быстрый, имеет много функций — как и высокоуровневых, так и низкоуровневых. Все бы хорошо, кроме одного но — рантайм библиотека размером 16 кб. И это без плавающей арифметики, с ней — 25. Это очень много, практически половина оперативной памяти уходит разом и для самого кода и данных остается мало времени. Также, получаемый код очень трудно отлаживать, постоянные вызовы подпрограмм сильно сбивают с толку. Вот аналогичная программа:
Код:
program demo;
var a,b,c,d:integer;
begin
for a:=2 to 2000 do
begin
d:=a;
b:=1;
c:=1;
while (d>b)and(c<>0) do
begin
b:=b+1;
d:=a div b;
c:=a-(d*b);
end;
if c<>0 then writeln(a);
end;
end.
Все плохо?
Мы просмотрели все основные решения компиляторов и ничего не нашли, что действительно все так плохо? Почти. Я тоже сначала впал в уныние и разуверился в жизни, но потом прочитал что сама ос CP/M была написана не на ассемблере, а на языке PL/M. Хм, написано что язык был разработан специально для микрокомпьютеров, в отличии от других языков, которые разрабатывали для больших рабочих станций. Все силы были брошены на поиск его компилятора для i8080. Постепенно находилось о нем все больше и больше информации, и в итоге, на одном непримечательном сайте был найден исходник оригинального компилятора PL/M 1974 (!) года в виде исходника на фортране, который был сконвертирован в си и скомпилирован современным компилятором для windows. Эта была хорошая новость, но недолго. Компилятор был очень старым (1974 год же), стандарты и методы работы сильно отличались от современных, адаптироваться. Во первых, транслятор ничего не выводит на экран, весь обмен идет с файлами fort.1 … fort.12. Исходник читается из одного файла, байт-код переносится в другой фаил, логи — в третий, ошибки — в четвертый и т. д. Также, своеобразным был формат выходного hex-файла, пришлось написать свой hextobin. После всех мучений и изучения документации была написана знакомая на программа для поиска простых чисел, вот она:
Код:
DECLARE (A,B,C,D) ADDRESS;
DO A=2 TO 2000;
D=A; B=1; C=1;
DO WHILE ((D>B) AND (C<>0));
B=B+1;
D=A / B;
C=A-(D*B);
END;
IF (C<>0) THEN CALL PUTCH(51);
END;
GOTO 63605;
EOF
Если вы внимательно смотрели предыдущие исходники, то наверняка заметили что PL/M очень похож на PL/1, практически один язык. Да, синтаксисом они похоже, но только им. Внутри это абсолютно разные языки, с разной парадигмой и сферами использования. PL/1 – это мощный, всеобъемлющий язык. А — PL/M – маленький, компактный, быстрый, в нем всего два типа данных — BYTE и ADDRESS. Я откомпилировал эту программу, и, о чудо! Размер бинарника — 254 байта. А если посмотреть в hex-редакторе, то оказывается там внутри почти половина нулей, т. е. Компилятор подгоняет размер до 256 байт. Это лучшее значение из всех, было решено зять этот язык на вооружение и развивать его.
PL/M Super Compiler
Однако, в данном трансляторе было несколько недостатков -
- Программа должна быть написана большими буквами.
- Не было модульности — вся программа должна быть одним большим исходником.
- Отсутствовали ассемблерные вставки.
- Точка с запятой в конце каждой строки.
Немного подумав было решено сделать для компилятора обертку, которая будет лишена этих недостатков. В итоге должен получиться Super PL/M Compiler. :-)
В качестве скриптового языка я выбрал Lua. Этот язык очень маленький (интерпретатор весит 200 кб), простой, быстрый и имеет богатый функционал. Первый недостаток — обязательный капслок был решен просто - переводим все в верхний регистр и готово. Дальше — посложнее. Модульность можно сделать просто объединив несколько файлов в один, однако возникает загвоздка — в случае ошибки в коде компилятор выдаст номер строки, но это будет строка в объединенном файле, и в каком из исходных файлов эта ошибка была и где - не узнать. Проблема решилась так — когда файлы объединяются, создается таблица, где заданы соответствия: номер строки объединённого файла – наименование исходного файла и номер строки в нем. После этого, в случае ошибки мы можем узнать в каком из исходных файлов она произошла и где.
С ассемблерными вставками было достаточно много мороки. Ведь надо не просто чтоб модно было писать на ассемблере и на PL/M, надо и чтоб переменные, объявленные в одном языке виделись в другом. Как вообще работает добавление ассемблерных вставок? Вот так — есть специальные команды — asm и endasm, между которыми пишется ассемблерный код. Как их обрабатывает парсер: сначала создаются пустые файлы out.plm и out.asm. Сначала строки кода переносятся в out.plm, но если встречается команда asm, то вывод переключается на out.asm и так до тех пор, пока не встретится endasm. Потом все повторяется с начала. Итого, на выходе у нас 2 файла — в одном у нас ассемблерная часть исходника, в другом — PL/M код. Но эти два исходника изолированны — мы не можем сделать JMP из одного в другой и прочитать значение переменных из другого.
Для этого авторы компилятора нам помогли. Когда происходит сборка, то создается файлик, в котором описаны все переменные и процедуры и их адреса, вот так:
Код:
5 MEMORY 00100H
24 PUTCH 00006H
25 P 000F5H
27 UKR 000F6H
28 OUTS 000F8H
37 PRINTSTR 0001FH
38 A 000FEH
А когда происходит сборка ассемблерной части программы, то тоже генерируется файл, из которого можно узнать все метки, объявленные в нем. Что мы делаем дальше — мы приписываем к PL/M-части исходника заголовочную область, где описываем метки из асма с адресами, а к ассемблерной части — заголовочник, где описываем все PL/M переменные. Таким образом мы можем обращаться к пермеменным и процедурам, объявленным в другой среде.
После того, как листинги сформированы, они компилирутся каждый своим компилятором и объединяются в один файл, к которому мы приписываем заголовки, в зависимости от компьютера и запускаем.
Для примера, вот программа, где используются большинство функций данного компилятора.
Код:
putstr: procedure (uk)
declare uk address
goto printstra
end
asm
printstra:
mov h,b
mov l,c
jmp 0f818h
endasm
call putstr(.(‘HELLO WORLD’,0))
Здесь используются как средства самого PL/M, так и Super PL/M Compiler. Мы делаем ассемблерную вставку, где вызываем подпрограмму биоса компьютера Апогей-БК-01ц для печати строки. Конструкция .(‘HELLO WORLD’,0) означает разместить в памяти строку HELLO WORLD (с нулём в конце) и возвратить на неё адрес. Далее, этот адрес мы передаем в процедуру putstr, которая передает управление ассемблерной вставке. Далее, надо знать особенности компилятора. Если у процедуры один параметр типа address, то он помещается в регистровую пару bc, а подпрограмма биоса для вывода строки требует параметры в hl. Мы переносим параметры в нужные регистры, и передаем управление системной процедуре, которая уже печатает строку.
Что дальше?
Данная сборка имеет очень большой потенциал, с помощью её можно написать хорошие программы, игры и при том не сложно. Язык PL/M очень простой, в нем мало конструкций, но они очень ёмкие и позволяют делать интересные приёмы. А добавить к нему современные средства сборки, добавить ассемблера, то получится вообще супер!
На данный момент система еще находится в разработке, отлавливаются баги и пишется библиотека — пока для компьютера Апогей БК-01ц, а далее - и для других компьютеров.