Пожалуй, приложу сюда статью из журнала 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 кб, то есть, плотность кода высокая – компилятор хороший. Вот программа для поиска простых чисел.
Однако у языка есть и недостатки – во первых, как уже говорилось выше – перегруженность. В языке много средств, которые не понадобятся при программировании для intel 8080, однако они там есть и сидят в памяти. Во вторых, я не нашел в языке команд для низкоуровневой работы - прямой доступ к памяти, ассемблерные вставки. Это очень большой минус, так как мы много чего не сможем сделать, например, в случае с Апогеем БК-01 нарисовать что-нибудь на экране, воспроизвести звук, ничего кроме алфавитно-цифрового ввода/вывода. ПечальКод: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;
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. После всех мучений и изучения документации была написана знакомая на программа для поиска простых чисел, вот она:
Если вы внимательно смотрели предыдущие исходники, то наверняка заметили что PL/M очень похож на PL/1, практически один язык. Да, синтаксисом они похоже, но только им. Внутри это абсолютно разные языки, с разной парадигмой и сферами использования. PL/1 – это мощный, всеобъемлющий язык. А — PL/M – маленький, компактный, быстрый, в нем всего два типа данных — BYTE и ADDRESS. Я откомпилировал эту программу, и, о чудо! Размер бинарника — 254 байта. А если посмотреть в hex-редакторе, то оказывается там внутри почти половина нулей, т. е. Компилятор подгоняет размер до 256 байт. Это лучшее значение из всех, было решено зять этот язык на вооружение и развивать его.Код: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 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 из одного в другой и прочитать значение переменных из другого.
Для этого авторы компилятора нам помогли. Когда происходит сборка, то создается файлик, в котором описаны все переменные и процедуры и их адреса, вот так:
А когда происходит сборка ассемблерной части программы, то тоже генерируется файл, из которого можно узнать все метки, объявленные в нем. Что мы делаем дальше — мы приписываем к PL/M-части исходника заголовочную область, где описываем метки из асма с адресами, а к ассемблерной части — заголовочник, где описываем все PL/M переменные. Таким образом мы можем обращаться к пермеменным и процедурам, объявленным в другой среде.Код:5 MEMORY 00100H 24 PUTCH 00006H 25 P 000F5H 27 UKR 000F6H 28 OUTS 000F8H 37 PRINTSTR 0001FH 38 A 000FEH
После того, как листинги сформированы, они компилирутся каждый своим компилятором и объединяются в один файл, к которому мы приписываем заголовки, в зависимости от компьютера и запускаем.
Для примера, вот программа, где используются большинство функций данного компилятора.
Здесь используются как средства самого PL/M, так и Super PL/M Compiler. Мы делаем ассемблерную вставку, где вызываем подпрограмму биоса компьютера Апогей-БК-01ц для печати строки. Конструкция .(‘HELLO WORLD’,0) означает разместить в памяти строку HELLO WORLD (с нулём в конце) и возвратить на неё адрес. Далее, этот адрес мы передаем в процедуру putstr, которая передает управление ассемблерной вставке. Далее, надо знать особенности компилятора. Если у процедуры один параметр типа address, то он помещается в регистровую пару bc, а подпрограмма биоса для вывода строки требует параметры в hl. Мы переносим параметры в нужные регистры, и передаем управление системной процедуре, которая уже печатает строку.Код: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 очень простой, в нем мало конструкций, но они очень ёмкие и позволяют делать интересные приёмы. А добавить к нему современные средства сборки, добавить ассемблера, то получится вообще супер!
На данный момент система еще находится в разработке, отлавливаются баги и пишется библиотека — пока для компьютера Апогей БК-01ц, а далее - и для других компьютеров.




Ответить с цитированием