Паскалевские хитрости
Apr. 18th, 2019 02:47 pmВложенные процедуры делаются относительно просто. Основной механизм таков:
превращается в
Если вложенная процедура не имеет собственных вложенных и вызывается только из сиблингов, то она может быть оформлена как метод класса.
Но это всё цветочки. Ягодки заключаются в том, что в БЭСМ-6 разрядность адреса была всего 15, поэтому можно было кастовать указатель к целому, добавлять в старшие разряды разнообразные флаги, и передавать это целое в процедуры, которые анализировали флаги, при необходимости кастовали целое обратно в указатель, и что-то там с ним делали. Так, например, устроена работа с переходами вперёд.
Придется делать собственный аллокатор, что на С++ несложно, а вот какие усилия потребовались бы для достижения функциональности при портировании на FreePascal, я даже боюсь себе представить.
Заодно реализация работы с динамической памятью путем setup/rollup станет тривиальной.
function Func(args...): RetType;
var
...vars...
...nested procedures...
begin
...body...
end;
превращается в
struct Func {
static std::vector<Func*> super;
RetType ret; // Assignments to Func become assignments to ret
operator RetType() const { return ret; }
...vars...
Func(args...);
~Func() { super.pop_back(); }
};
...nested procedures... using Func::super.back()-> to access vars
Func::Func(args...) {
super.push_back(this);
...body...
}
Если вложенная процедура не имеет собственных вложенных и вызывается только из сиблингов, то она может быть оформлена как метод класса.
Но это всё цветочки. Ягодки заключаются в том, что в БЭСМ-6 разрядность адреса была всего 15, поэтому можно было кастовать указатель к целому, добавлять в старшие разряды разнообразные флаги, и передавать это целое в процедуры, которые анализировали флаги, при необходимости кастовали целое обратно в указатель, и что-то там с ним делали. Так, например, устроена работа с переходами вперёд.
Придется делать собственный аллокатор, что на С++ несложно, а вот какие усилия потребовались бы для достижения функциональности при портировании на FreePascal, я даже боюсь себе представить.
Заодно реализация работы с динамической памятью путем setup/rollup станет тривиальной.
no subject
Date: 2019-04-26 05:12 am (UTC)Не заглядывая в код, по описанию подумал, что раз речь шла про магию с указателями, то Вы сделали на С++ так, что написанное в том паскалевском стиле, под капотом распутывается образуя иерархию классов без магии.
Сейчас в исходный pas-код посмотрел хороший такой ребус :)
Получается там top-down парсер, машинный код образуется в памяти, все таблицы хранятся в виде однонаправленных связанных списков, компилируется без всяких там AST-деревеьев, SSA-form и прочих модных штучек :D
Интересно, с учетом, что памяти 32К слова, какой максимальный размер одного модуля получался?
no subject
Date: 2019-04-26 06:34 am (UTC)Машинный код в памяти образуется для одной процедуры, и ограничен одним листом (1024 слова); по окончании процедуры выталкивается на барабан. Для выражений строится дерево из структур Expr, к которому по ходу компиляции в код применяются простенькие оптимизации, типа смены порядка вычисления операндов коммутативных операций, если это упрощает работу со стеком; глубина стека временных значений при компиляции одного выражения тоже ограничена; чрезмерно сложные выражения (сумма произведений сумм произведений сумм ....) приводят к краху.
Всего в программе может быть не более 500 разных литеральных констант.
Для SSA - или даже для CSE - места было бы маловато, не говоря уже об оптимизациях управления, отличных от тривиальных peephole optimizations. Поэтому в коде куча мест, где вызов процедуры, печатающей сообщение об ошибке, написан однажды, и на него делается много goto; или где ссылка на элемент структуры присваивается временному указателю, чтобы сложное адресное выражение вычислялось лишь однажды.
Насколько я помню, мониторная система способна съесть объектный модуль размером вплоть до 23 или 24 листов. Компилятор в принципе в состоянии такое породить.