Rev 120 | Blame | Compare with Previous | Last modification | View Log | Download
а) с линкером:
# сделать экспорт деклараций из асма (и предусмотреть токен?) СДЕЛАНО
# сделать EXPORT в компиляторе СДЕЛАНО
# сделать ассемблирование цепочки файлов - СДЕЛАН INCLUDE (теперь надо сделать #include в компиляторе)
# сделать экспорт объектника в асме (патчи на месте и на будущее патчи назад)
# сделать линкер
- после этого можно развивать компилятор и асм
#
б) без линкера:
# сделать экспорт деклараций из асма (и предусмотреть токен?) СДЕЛАНО
# сделать EXPORT в компиляторе - для процедур, функций, переменных, констант, константных строк/массивов/структур СДЕЛАНО
# сделать ассемблирование цепочки файлов - СДЕЛАН INCLUDE (теперь надо сделать #include в компиляторе)
# сделать подклеивание бинарников с меткой (вместо nedodefb и для подключения модулей) СДЕЛАНО
# сделать экспорт таблицы релокации (или обжа?) СДЕЛАНО
# сделать подклеивание бинарников с релокацией (или обжей?)
- после этого можно развивать компилятор и асм
как отличать в команде, где асм, где бинарник? или реализовать incbin (он невложенный)? тогда сделать USEUNIT (#include), который переводится в incbin? или все incbin разместить в стартапе?
надо типизировать oldvalue и стек pushvalue, тогда можно _isaddr сбрасывать при вычитании двух адресов (сейчас приходится юзать 1*(addr-addr))
вместо usemacro лучше #?
если поле метки=поле команды, то вообще убрать нельзя, т.к. с точки зрения токенизатора строка "macro1" - это объявление метки, а строка "macro1 par1,par2" - ошибка
или можно без #, если ловить макросы в tokcalllbl вместо ошибки, но вставлять невидимый токен usemacro
как вычитывать макросы в asmtg:
а) отдельный цикл с тем же switch - жирно
б) не читать непосредственно readfin, а через процедуру - замедлит, или процедура на асме? какая? (идея была читать всегда из 256-байтного буфера в памяти, но как это сделать на ЯВУ? вместо вызова должен был быть макрос)
в) генерировать файлы и читать их
г) switch выделить в процедуру - однозначно замедлит
д) патч вызова readfin?
е)
таблица релокации - для всех адресов, т.е. чисел, которые вычисляются из адресов? но $-label должно иметь размерность числа! а label/256 (в компиляторе нет)?
нужен флаг "адрес" в метке ("метка=число" делает число, "метка" делает адрес, "метка=метка+1" (просто по факту чтения метки типа "адрес" или $ в выражении) делает адрес)
при всех арифметических операциях отслеживать тип адрес/число?
как проще? или помечать выражения типа "число" знаком #?
или помечать только выражения типа $-label? (в обычных программах не используются)
или разрешить $-label только в специальной команде определения метки?
релоцируем не только переходы и вызовы, но и вычисления адреса switch и таблицы dw
обнулять _islabel имеет смысл только в командах, которые генерятся с записью слова или пишут метку (label, =, dw, jp, call, ld)
пока сделал так, что нельзя $-label (можно 1*($-label))
пока просто выгружаем адреса патчей в forg (вместо размеров блоков)
как строить проект компилятора?
нельзя взаимные ссылки - допустим, вынесем в модуль
один модуль может быть включён в разные модули - как избежать дублирования кода? emit используется почти везде, т.к. он выводит ошибки
даже если разрешить дублировать ошибки, то asmstr есть в compile (asm, switch, case, константные строки), commands (можно убрать), codetg
выигрыш по меткам будет только при инкобже какой-то терминальной ветви включения модулей, с кучей меток
таких в компиляторе нет!
как релоцировать обращения к другим модулям? (если как и внутри, то ещё и нельзя будет распихать программу в память по частям!)
надо чтобы при компиляции модуля был известен номер блока (глобальный для проекта!) и смещение каждого обращения наружу
глобальный номер блока можно взять только из комстроки компилятора, он должен попасть в декларацию (в каком виде?)
как сэкономить в ассемблере память на локальных переменных? (надо не более 55к для самокомпиляции, а лучше 48 для CP/M - вроде уже 32к, но надо добавить токенизацию (выражения только с >>16 и $+num, КРОМЕ АСМОВСТАВОК!!!), буферизацию)
- линковать (в идеале вложенно) (в идеале с взаимозависимостями)
- сбрасывать посты после каждой функции, переменные прилеплять в хвост функции. тогда можно и локальное пространство имён для небольшого сокращения данных (но увеличения кода)
чтобы можно было удалять локальные метки функции (те, к которым обращение только назад и не было обращения вперёд), достаточно помнить массив адресов хэшей. Запомнить его надо после объявления заголовка функции. в модуле так не получится, т.к. там объявляются ещё экспортируемые метки, которые удалять нельзя.
локальные переменные функции придётся компилировать в едином блоке с функцией, иначе нельзя будет их удалять.
- на втором проходе оставить в таблице только метки, к которым были обращения до присваивания, остальные стереть (или две разных таблицы? тогда на первом проходе не присваивать такие метки, даже если они потом используются после определения)
- вместо имён CRC32
- СДЕЛАНО: сократить названия процедур и переменных в компиляторе
- СДЕЛАНО: сократить число параметров и локальных переменных в компиляторе
- СДЕЛАНО: сократить число процедур в компиляторе
- СДЕЛАНО: односимвольные автометки (с префиксом функции)
- заменить компилятором в процедурах локальные переменные на автометки без префикса (но тогда будет конфликт в разных модулях!)
генерировать user.l для отладки в анриле
можно разделить асм;
1. обработка мнемоник и генерация кода + объявлений + полей (выражений)
2. линковка кода + объявлений + полей, но там все существующие метки!
можно для второго прохода подготовить другой асмотекст, где половина скомпилирована в db и без части меток (к которым не было обращения вперёд)
но так всё равно в первом проходе надо пройти все существующие метки!
и писать-читать такой объём данных будет очень медленно
метки в компиляторе:
1) глобальные константы - короткое имя
2) глобальные переменные - короткое имя
3) внешние метки параметров -
4) локальные метки параметров - МОЖНО УДАЛЯТЬ
5) точки входа в функции - короткое имя
6) переходы назад - МОЖНО УДАЛЯТЬ, генерировать $-...
7) переходы вперёд -
[8) локальные константы - МОЖНО С КОРОТКИМ ИМЕНЕМ, МОЖНО УДАЛЯТЬ]
9) локальные переменные - МОЖНО С КОРОТКИМ ИМЕНЕМ, МОЖНО УДАЛЯТЬ
можно сделать make независимым от редактора
для этого
в редакторе memory mapped file, при любой записи в любой файл в директории АТОМАРНО С ЭТИМ появляется файл "обновлено"
при любом чтении проверяется наличие файла "обновлено" - если он есть, то чтение прерывается с ошибкой "устарело"
ошибка прерывает и перезапускает всю цепочку компиляции, а также запуск результата
make вызывается из главного цикла ожидания "обновлено" (раз в секунду?)
перекомпилируем только изменённые файлы - как узнать? время файла ненадёжно, можно тикать номером версии файла в параметре start? а в FAT тикать секундами???
obj реально лежат в файловой системе, тоже с уникальным номером версии/временем (что делать при переполнении? смотреть знак разницы номера?)
в редакторе кнопка "запустить" обновляет файл проекта, добавляет туда флаг "с запуском". при чтении файла проекта этот флаг снимается - конфликт!!!
надо отслеживать текущий номер запуска и обновлять его? при первой сборке (при старте редактора) перед входом в цикл смотреть текущий номер запуска, чтобы не запустить?
как обновлять неатомарно?
если поздно создать файл "обновлено", то make может докомпилировать и запустить старую версию проекта, несмотря на то, что исправили и снова нажали "запустить"
но это возможно и при чтении блоками - во время обработки последнего блока появится файл "обновлено", но мы его уже не увидим
так что лучше просто не нажимать два раза "запустить"
!?!настоящая проблема - если изменить текст файла, а файл "обновлено" не успеет создаться, тогда будет ошибка компиляции или неожиданный результат (частичное обновление исходника). то же при слишком раннем создании файла "обновлено"
послать сигнал не получится - каждый раз работает неизвестная утилита
в своей фс мы ещё как-то можем сделать атомарное создание
НО КАК БЫТЬ ПРИ FAT?
нам надо разбить текст на полуфайлы по курсору
stdio должен уметь находить оба полуфайла как один файл и читать его полностью через все блоки
как сделать добавление в начало второго полуфайла? и чтобы работало в FAT?
при удалении начала второго полуфайла могут образоваться пустые блоки, но удалять их нельзя, т.к. идёт чтение!
у второго полуфайла надо знать номер первого блока - где его класть? или сделать полный набор пустых блоков в начале на всякий случай?
тогда блоки у каждого полуфайла нумеруются с 0
где хранить номер блока файла?
start/время? тело файла? последние 2 символа расширения?
где хранить начало данных внутри блока? при удалении данных из начала мы корректируем именно это число!!! start занят
вперёд смотрят метки if, while (выход), repeat (выход) а также forward процедур - их оставить линкеру?
назад смотрят метки while (цикл), repeat (цикл), par (т.к. можно рассчитать), var (т.к. можно рассчитать)
в каком виде чередовать код и команды линкеру?
1а) код блоками максимум 256 байт (размер буфера) с заголовком, где длина
1б) данные так же - в отдельном файле?
определения декларируемых меток передаются линкеру в виде:
2) локальная метка перехода (или инлайн var/par): имя + смещение в текущем сегменте
3) экспортируемая метка перехода (или экспортируемый инлайн par): имя + смещение в текущем сегменте
4) экспортируемая метка var/par: имя + смещение
5) экспортируемая константа: имя + значение
нерассчитанные обращения к меткам (выражение (для >>) с именами и рассчитанными обращениями - распарсено с явными push-pop?) передаются линкеру для заполнения в таких форматах:
6) byte (младший)
7) uint (младший)
8) long (для DL)
[...) uint (старший)]
[...) byte (>>8), byte (>>16), byte (>>24)]
[...) смещение для jr??? нереально угадать jr/jp вперёд, а назад уже рассчитано]
рассчитанные обращения к меткам передаются линкеру в виде:
A) "текущий код" + смещение (uint)
Б) "текущие данные" + смещение (uint)
В) "long константа" + данные (long)
формат заголовка блока:
+0 1 ⨯:
0"конец"
1"код"/"данные" + длина + байты
2"объявление локальной метки перехода" + имя + смещение (в текущем сегменте кода)
3"объявление экспортируемой метки перехода" + имя + смещение (в текущем сегменте кода)
4"объявление экспортируемой метки var/par" + имя + смещение (в текущем сегменте var)
5"объявление экспортируемой константы" + имя + значение
6"выражение byte"
7"выражение uint"
8"выражение long"
если компилятор будет чередовать в одном файле блоки кода и данных, то линкер не сможет их раскидать в один проход
прицеплять данные строго в конец файла (надо огромный буфер в компиляторе) - поможет линкеру только если разрешить чередование кода и данных в бинарнике
если два файла, то как линкер поймёт, какой сегмент - "текущие данные"? заранее знать число блоков кода и прибавлять его?
чтобы линкер мог не подцепить ненужную процедуру, она должна быть в отдельном файле. но номер этого файла надо тогда учесть в общем счёте!
как узнать, что не надо подцеплять? а вдруг она используется ниже? или все либы в конце в иерархическом порядке - тогда не надо подцеплять либу, если ни одна декларация из либы не использована (кроме локальных меток перехода)
объявления надо в отдельных файлах, иначе не получится в один проход!!!
порядок подачи obj (startup рассматривается как обычный код, обжи умеют выгружаться и из асма, и впоследствии из компилятора):
decl1 + decl2 + libdecl1 + libdecl2 +
code1 + code2 + lib1 + lib2 +
var1 + var2 + libvar1 + libvar2
блок неинициализированных переменных не нужен - это решается пакером?
файл декларации закрывается только после окончания компиляции соответствующего файла, т.е. обжи code и var от него должны буферизироваться (в памяти или в файле)
более того, поскольку сначала мы читаем ВСЕ файлы декларации, то буферизируются ВСЕ обжи code и var!!! для компилятора их объём будет порядка 48К (весь код с данными (32К) + половина меток (16К) + заголовки (16К?))
как сэкономить хоть сколько-то буферной памяти?
это можно только при чередовании кода и данных, т.к. само разделение code и var требует буферизировать целиком оба, прежде чем можно начать склеивать
или склеить в два бинарника?
но всё равно - как быть с декларациями? как-то выгружать файл патчей, который будет использовать лоадер?
пусть мы чередуем вызовы компилятора и асма, а асм выгружает обжи и декларации (label1=M1+123 label2=M1+456 и т.д. - лучше в токенизированном виде, т.к. это личное дело асма)
(причём выгружает столько копий деклараций, сколько нужно?)
взаимозависимость модулей запрещена - только зависимость главного от нижних (ТОГДА ВМЕСТО ЛИНКЕРА МОЖНО include "mod1.dec":incbin "mod1.bin")
тогда не требуется память одновременно под исходник, асм, токенизированный асм и обж, а для каждого требуется максимум память под обж и декларации
итого максимальная память требуется, когда скомпилированы все модули, кроме главного:
вся система займёт в памяти как минимум:
32K редактор (как ACEdit)
32K текущий редактируемый исходник (может быть не один! весь проект компилятора 210K)
24K компилятор
8K метки компилятора [(+8K метки перехода?)]
[10K токенизатор ассемблера в компиляторе или больше отдельно (можно убрать, если редактируем ассемблер уже токенизированный, а асмовставок нет)]
10K ассемблер
16K метки ассемблера (вдвое меньше, чем сейчас - под один модуль + использумые декларации)
4K линкер
[16K метки линкера (все экспортируемые + метки перехода вперёд)]
40K выходной бинарник (по размеру самой большой программы с данными: редактор с маленьким текстом или компилятор с метками)
48K буферизация всех обжей проекта
8K экран, системные переменные, стек
4K ось (память, ввод-вывод, шедулер, ФС, лоадер)
[16K файловые буфера (надо на все обжи одновременно! или уметь замораживать незахваченный пайп без буфера?)]
---
~240K (будет больше из-за многотекстовости - до 454K, не считая запасов под хвосты текстов)
как при этих лишних файлах не потерять скорость? делать однопроходный асм с патчами в обже (линкер/лоадер применит патчи)?
как передать длинный мейкфайл типа такого?
//при изменении mod1.c или mod1.h
compile mod1.h mod1.c
tokenize mod1.asm mod1.var
assemble mod1.tok //делает mod1.obj, mod1.dec
//при изменении asm1.asm
tokenize asm1.asm
assemble asm1.tok //делает asm1.obj, asm1.dec
//при изменении main.c или mod1.h или (как отследить?) деклараций модулей
compile mod1.h main.c //тот же хедер, что выше
tokenize mod1.dec asm1.dec main.asm main.var
assemble main.tok //делает main.obj
//при изменении любого файла
link mod1.obj asm1.obj main1.obj
или без линкера (та же скорость и на один вызов меньше):
//при изменении mod1.c или mod1.h
compile mod1.h mod1.c
tokenize mod1.asm mod1.var
assemble mod1.tok //делает mod1.bin, mod1.dec
//при изменении asm1.asm
tokenize asm1.asm
assemble asm1.tok //делает asm1.bin, asm1.dec
//при изменении любого файла
compile mod1.h main.c //тот же хедер, что выше
tokenize main.asm main.var
assemble main.tok mod1.dec mod1.bin asm1.dec asm1.bin //делает main.bin
сейчас сравнения < <= > >= делаются в UINT, а надо в LONG (пока не поддержано в компиляторе)
надо такой порядок переменных, где не понадобятся post labels?
т.е. нужны предописания процедур, но как их компилить? даже джамп вперёд не получится
а что делать с if, while, где тоже джамп вперёд? компилить тело, потом патчить? (так делает Turbo Pascal) это не вывод в последовательный файл!
это допустимо для hex-файла? только с пропуском байт, чтобы не писать 2 раза в одно место?
или генерировать процедуру целиком в массив, потом скидывать в поток?
или генерировать патчи в последовательный файл obj
как делать dup-edup?
а) буферизировать текст тела (как макрос)
б) прогонять файл несколько раз от середины (так нельзя стримить)
в) буферизировать выходной бинарник (так нельзя переприсваивать метки внутри dup и делать разное наполнение проходов)
как делать постметки?
а) запоминать всю строку с выражением (т.е. все строки надо считывать целиком вне зависимости от выражения)
б) два прохода - на первом смотрим длины команд и генерим обычные (не пост) метки, на втором генерим оставшиеся метки и пишем код
в) разрешить вперёд обращаться только к метке (без выражения)
т.е. если метка не определена, то проверяем, что это конец выражения, иначе ошибка
а как получить адрес, куда писать? на момент разбора выражения он неизвестен! добавить операции записи в середину команды?
г) при неопределённой метке сохранить в пост всё состояние калькулятора и остаток выражения
а как получить адрес, куда писать? на момент разбора выражения он неизвестен! добавить операции записи в середину команды?
бывают однобайтные и двухбайтные посты
двухбайтные могут быть только curaddr (только для dw) и curaddr+1
(curaddr+2 только для ix,iy - но как пост узнает, что такая команда? по регистрам не узнает!)
однобайтные бывают:
- db (curaddr)
- jr (curaddr+1)
- смещения для ix,iy (curaddr+2)
- числа (обычно curaddr+1, но для h/lx/y curaddr+2, для ld (ix/y+d),N curaddr+3 - как пост узнает тип команды? по регистрам не узнает!)
или можно сохранить всё состояние ассемблера и строку до конца, а посты компилировать так же, как обычный токенизированный текст
но для того, чтобы пропустить команду в первом проходе, надо её всю интерпретировать
(как минимум прочитать регистры и огранизовать проверку формата с учётом регистров)
тогда можно огранизовать пропуск в asmbyte, asmword итп
а) скопировать код всех форматов и регистров
б) поставить ловушку в readfin()? будет замедление! там предполагался только системный макрос!
в) без постов. два прохода, на первом команда не пишется
г) без постов. бесконечное число первых проходов (где команда не пишется) - пока не определены все метки. на втором проходе метки не определять
д) без постов. два прохода, на первом команда тоже пишется - т.е. одинаковые проходы с общей таблицей меток, но надо по-разному обрабатывать display и ошибку переопределения метки
так можно бесконечное число проходов - пока не определены все метки или не перестало изменяться число неопределённых меток.
так нельзя генерить в hex, т.к. повторы записи в одно и то же место.
решается либо моделью памяти, либо игнорированием бинарника, полученного на предыдущем проходе.
а где генерировать ошибки "метка не определена"? надо в конце отдельный обход таблицы меток для этого. но в метке не хранится номер строки!
е) исходник в памяти, всегда можно вернуться на начало строки.
ё) предварительное чтение строки в память. Будет замедление.
ж) отматывать указатель файла (как это будет в оси? пайп так нельзя).
з) добавить операции записи в середину команды. при этом ld hx,expr надо писать код ld hx ДО ВЫЧИСЛЕНИЯ (он определяет смещение данных, а регистр hx может быть сам из метки). в токенизаторе придётся читать строку целиком?