LINUX.ORG.RU

Отлаживаю кодогенератор

 , ,


2

1

В свободное время попиливаю компилятор ЯП для разминки мозгов и чтобы не забыть ассемблер окончательно.

По семантике ЯП Си-подобный, по синтаксису больше похож на что-то из линейки Паскаль/Модула/Оберон.

За основу брал Context Хохлова, но перепилил практически все сорцы.

Компилятор многопроходный. Сначала строится синтаксическое дерево. Потом на нём выполняются некоторые простые оптимизации. Потом запускается бэкэнд. (Другой подход применён, например, в tcc, где программа компилируется за один проход — ради скорости компиляции.)

Некоторые вещи вычисляются/оптимизируются сразу при парсинге. Например, логическое отрицание вообще не сохраняется в синтаксическое дерево, если может быть заменено на эквивалентную трансформацию отрицаемого выражения:

word Not(word P)
	switch Node[P].ID of
	case iOR:   Node[P].ID = iAND; Not(Node[P].pLeft); Not(Node[P].pRight);
	case iAND:  Node[P].ID = iOR;  Not(Node[P].pLeft); Not(Node[P].pRight);
	case iXOR:  Node[P].ID = iEQV;
	case iEQV:  Node[P].ID = iXOR;
	case iLT:   Node[P].ID = iGE;
	case iLE:   Node[P].ID = iGT;
	case iEQ:   Node[P].ID = iNE;
	case iNE:   Node[P].ID = iEQ;
	case iGE:   Node[P].ID = iLT;
	case iGT:   Node[P].ID = iLE;
	default:
		word P2 = Peek();
		Node[P2].ID    = iNOT;
		Node[P2].pLeft = P;
		return P2;
	end:switch

	return P;
end

Бэкэнд не строит промежуточного представления, на котором можно было бы гонять умные алгоритмы, соответственно 90% оптимизаций, описанных в «Книге дракона» невозможны. Пока я пытаюсь выжать максимум из компиляции на основе синтаксического дерева.

Ассемблерный листинг строится рекурсивным обходом дерева. При этом оптимизация осуществляется локально, в каждом отдельном узле, исходя только из информации доступной для узла и пары его дочерних элементов (или иногда — заглядывая чуть глубже).

Хочу показать, чего удалось добиться с этим подходом на примере компиляции арифметических выражений:

Оптимизация умножения на константу при помощи add, shl и lea. Пример умножения на 15 при помощи lea:

        mov     EAX,  dword [EBP-24]
        lea     EAX,  [EAX*2+EAX]
        lea     EAX,  [EAX*4+EAX]
        push    EAX
        call    @10013

Компиляция выражения put_word(v100 * 18 + v1 * 27);:

        mov     EAX,  dword [EBP-24]
        lea     EAX,  [EAX*8+EAX]
        add     EAX,  EAX
        mov     EBX,  dword [EBP-8]
        lea     EBX,  [EBX*2+EBX]
        lea     EBX,  [EBX*8+EBX]
        add     EAX,  EBX
        push    EAX
        call    @10013

Здесь умножения заменены более оптимальными инструкциями и разумно распределены регистры.

Компиляция выражения word v500 = v2 * v100 + v3 * v100;:

        mov     EAX,  dword [EBP-12]
        mul     dword [EBP-24]
        push    EAX
        mov     EAX,  dword [EBP-16]
        mul     dword [EBP-24]
        pop     EBX
        add     EAX,  EBX
        mov     dword [EBP-32], EAX

Более оптимальным кодом был бы вариант:

        mov     EAX,  dword [EBP-12]
        mul     dword [EBP-24]
        mov     EBX, EAX
        mov     EAX,  dword [EBP-16]
        mul     dword [EBP-24]
        add     EAX,  EBX
        mov     dword [EBP-32], EAX

Но компилятору в текущей реализации недоступна информация, потребуется ли EBX при вычислении второго слагаемого. Поэтому он сохраняет EAX на стек и затем восстанавливает оттуда значение в EBX уже после того как второе слагаемое вычислено.

Компиляция выражения put_word(v500 - v100 * v5);:

        mov     EAX,  dword [EBP-24]
        mul     dword [EBP-20]
        mov     EBX,  dword [EBP-32]
        xchg    EAX,  EBX
        sub     EAX,  EBX
        push    EAX
        call    @10013

Компилятор требует от выражения результат во вполне определённом регистре. Поэтому столкнувшись с тем, что операнды оказались в обратном порядке, он вынужден вставлять лишний xchg. Ситуацию можно решить, если разрешить выражению в ряде случаев свободно выбирать регистр для результата. Тогда код был бы таким:

        mov     EAX,  dword [EBP-24]
        mul     dword [EBP-20]
        mov     EBX,  dword [EBP-32]
        sub     EBX,  EAX
        push    EBX
        call    @10013

Компиляция выражения put_word((v500 * 2 + v2) + (v200 & v1) + (v200 - 100) * 10 - 100 - v200 - v100);:

        mov     EAX,  dword [EBP-32]
        add     EAX,  EAX
        add     EAX,  dword [EBP-12]
        mov     EBX,  dword [EBP-28]
        and     EBX,  dword [EBP-8]
        add     EAX,  EBX
        mov     EBX,  dword [EBP-28]
        sub     EBX,  100
        lea     EBX,  [EBX*4+EBX]
        add     EBX,  EBX
        add     EAX,  EBX
        sub     EAX,  100
        sub     EAX,  dword [EBP-28]
        sub     EAX,  dword [EBP-24]
        push    EAX
        call    @10013

Пример компиляции ветвлений для блока switch (табличная реализация switch пока отсутствует) :

        mov     EAX,  dword [@@DATA+EAX+7177840]
        sub     EAX, 0xD
        je      @11097
        sub     EAX, 0x3
        je      @11098
        sub     EAX, 0xFFFFFFFE
        je      @11099
        dec     EAX
        je      @11100
        sub     EAX, 0x5
        je      @11101
        dec     EAX
        je      @11102
        dec     EAX
        je      @11103
        dec     EAX
        je      @11104
        dec     EAX
        je      @11105
        dec     EAX
        je      @11106
        jmp     @11107

Пример переиспользования значения константы в регистре вместо загрузки другой константы:

	word v0 = 0;
	word v1 = 1;
	word v2 = 2;
	word v3 = 3;
	word v5 = 5;
	word v100 = 100;
	word v200 = 200;
        xor     EAX,  EAX
        mov     dword [EBP-4], EAX
        inc     EAX
        mov     dword [EBP-8], EAX
        inc     EAX
        mov     dword [EBP-12], EAX
        inc     EAX
        mov     dword [EBP-16], EAX
        mov     EAX,  5
        mov     dword [EBP-20], EAX
        mov     EAX,  100
        mov     dword [EBP-24], EAX
        add     EAX,  EAX
        mov     dword [EBP-28], EAX

Сам компилятор полностью работоспособен, запускается в 32-разрядном режиме под виндой и линуксом и, в принципе, легко может быть портирован на другие ОС. Написан на самом себе (self-hosted) и частично обвешан тестами. Далее в планах научить его генерировать код для какой-нибудь другой системы команд. ARM, MIPS, RISC-V… что-то из этого.

Задавайте ваши вопросы.

★★

Последнее исправление: wandrien (всего исправлений: 2)

Ответ на: комментарий от wandrien

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

anonymous
()

и чё, хоть быстрее педона? а pypy?

anonymous
()
Ответ на: комментарий от wandrien

гцц не инлайнит divmod, Тк это нарушает какие-то там стандарты. если отдельно писать рядом / и %, то быстрее работает

anonymous
()
Ответ на: комментарий от wandrien

там в процессорах float16 запилили, а он 64 дефолтом делает

anonymous
()
Ответ на: комментарий от wandrien

Лицензия конечно такая себе, но

lcc is available free for your personal research and instructional use under the `fair use’ provisions of the copyright law.

Как раз что доктор прописал.

luke ★★★★★
()
Ответ на: комментарий от wandrien

Типы могут идти со следующими префиксами: checked — компилятор вставляет код для проверки результатов арифметических операций, и если результат не может быть представлен без переполнения, выполняет вызов abort().

эта кухня с переполнениями не может быть связана с типами, а должна быть связана с операциями.

потому, что запись вида

checked int x = func(...)

вовсе не гарантирует, что не было переполнений внутри func(…) при вычислении результата. и при присваивании тут у тебя нет никакой об этом инфы.

таким образом спецификатор checked не дает гарантий корректности x. то есть ему доверять нельзя, а раз нельзя - он не нужен.

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от wandrien

Плюс к вышеупомянутому, при анализе исходного кода битовые операции отделяются от тех логических операций, которые на самом деле ветвления.

чтобы вы там не заблудились в самовыдуманных терминах(и было понятно о чем речь), это в компиляторостроении назвается short curcuit evaluation логических выражений.

смотреть в сюда. https://en.wikipedia.org/wiki/Short-circuit_evaluation

а кто решительно не может в туда, то в сюда https://ru.wikipedia.org/wiki/%D0%92%D1%8B%D1%87%D0%B8%D1%81%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F_%D0%BF%D0%BE_%D0%BA%D0%BE%D1%80%D0%BE%D1%82%D0%BA%D0%BE%D0%B9_%D1%81%D1%85%D0%B5%D0%BC%D0%B5

alysnix ★★★
()
Ответ на: комментарий от wandrien

Булев тип Типы bool8, bool16, bool32 и т.п.

поскольку вы решили (и зря) что true - это хоть один ненулевой бит в значении… то преобразования - bool32->bool16->bool8 вам придется делать с неустранимой проверкой, поскольку ненулевой бит может не полезть в более узкий тип. это раз.

по той же самой причине операции сравнения двух значений типа bool будут тоже наворотом с фактически приведением к каноническому true=1…. это двас.

не копируйте подход си. он не всегда полезный.

зы. типы с «фиксированной точкой» вообще тут рассматривать не надо, поскольку бул еще можно как-то (скрепя сердцем) преобразовывать к целым, но преобразовывать его к числам вообще - это перебор. то есть прямое явное преобразование була к фиксированной точке и float - надо запрещать(это спасет вас от случайных ошибок). если такой бред кто-то захочет сделать, пусть кастит бул в целое, а потом целое в вещественное.

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от den73

можешь сделать бекенд для 64-разрядного ARM для А2/ЯОС.

Там есть какой-то ассемблер или используется безассемблерная технология?

vM ★★
()
Ответ на: комментарий от vM

Там оберон с ассемблерными вставками, всего ассемблерных вставок порядка 1000 на систему. Вот, например, кусок для RPi, включая точку входа в систему

https://gitlab.com/budden/ja-o-s/-/blob/26ab761b431c93d34154f198372d3ca44331e65c/source/RPI.Environment.Mod#L195

den73 ★★★★★
()
Ответ на: комментарий от wandrien

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

den73 ★★★★★
()
Ответ на: комментарий от mv

Зато у меня отладочный дамп синтаксического дерева выводится скобочками :)

; == str_find_chars_from =======================================================
; 
; (iDEF_LOCAL i <53 word>
;  (iASSIGN 1 <100000 INVALID_DICT>
;   (iEMPTY 0 <100000 INVALID_DICT>
;    (iLOCAL 0 <100000 INVALID_DICT>  0  0)
;    (iPARM P <53 word> (iLOAD 0 <100000 INVALID_DICT>  0  0)  0)
;   )
;  )
; *iWHILE 0 <100000 INVALID_DICT>
;  (iBODY 0 <100000 INVALID_DICT>
;   (iDEF_LOCAL j <53 word>
;    (iASSIGN 1 <100000 INVALID_DICT>
;     (iEMPTY 0 <100000 INVALID_DICT> (iLOCAL 1 <100000 INVALID_DICT>  0  0) (iWORD 0 <53 word>  0  0))
;    )
;   *iWHILE 0 <100000 INVALID_DICT>
;    (iBODY 0 <100000 INVALID_DICT>
;     (iSELECT 0 <100000 INVALID_DICT>
;      (iCASE 0 <100000 INVALID_DICT>
;       (iCOND 0 <100000 INVALID_DICT>
;        (iEQ 0 <34 bool>
;         (iPARM S <35 char>
;          (iADDR 0 <100000 INVALID_DICT>
;           (iLOCAL 0 <53 word> (iLOAD 0 <100000 INVALID_DICT>  0  0)  0)
;           (iLOAD 0 <100000 INVALID_DICT>  0  0)
;          )
;          0
;         )
;         (iPARM Chars <35 char>
;          (iADDR 0 <100000 INVALID_DICT>
;           (iLOCAL 1 <53 word> (iLOAD 0 <100000 INVALID_DICT>  0  0)  0)
;           (iLOAD 0 <100000 INVALID_DICT>  0  0)
;          )
;          0
;         )
;        )
;       *iBODY 0 <100000 INVALID_DICT>
;        (iRETURN 0 <100000 INVALID_DICT>
;         (iLOCAL 0 <53 word> (iLOAD 0 <100000 INVALID_DICT>  0  0)  0)
;        )
;       )
;      )
;      (iINC 0 <100000 INVALID_DICT> (iLOCAL 1 <53 word>  0  0)  0)
;     )
;    *iCOND 0 <100000 INVALID_DICT>
;     (iNE 0 <34 bool>
;      (iPARM Chars <35 char>
;       (iADDR 0 <100000 INVALID_DICT>
;        (iLOCAL 1 <53 word> (iLOAD 0 <100000 INVALID_DICT>  0  0)  0)
;        (iLOAD 0 <100000 INVALID_DICT>  0  0)
;       )
;       0
;      )
;      (iCHAR 0 <35 char>  0  0)
;     )
;    )
;   *iINC 0 <100000 INVALID_DICT>
;    (iLOCAL 0 <53 word>  0  0)
;   )
;  *iCOND 0 <100000 INVALID_DICT>
;   (iNE 0 <34 bool>
;    (iPARM S <35 char>
;     (iADDR 0 <100000 INVALID_DICT>
;      (iLOCAL 0 <53 word> (iLOAD 0 <100000 INVALID_DICT>  0  0)  0)
;      (iLOAD 0 <100000 INVALID_DICT>  0  0)
;     )
;     0
;    )
;    (iCHAR 0 <35 char>  0  0)
;   )
;  )
; *iRETURN 0 <100000 INVALID_DICT>
;  (iLOCAL 0 <53 word> (iLOAD 0 <100000 INVALID_DICT>  0  0)  0)
; )

wandrien ★★
() автор топика
Последнее исправление: wandrien (всего исправлений: 1)
Ответ на: комментарий от wandrien

Зато у меня отладочный дамп синтаксического дерева выводится скобочками :)

Ну вот, ты что-то начинаешь понимать! ;)

Писать на лиспе и не сбацать своего микроязыка - невозможно. Он для этого создан.

mv ★★★★★
()

прикольно. а я тут ковырялся в A2 на Active Oberon, там внезапно есть вполне себе приличный ассемблер и дизассемблер, написанный как модуль на Active Oberon. объектно-ориентированный, то есть.

AMD64 форк на голом железе, EFI на нём написаны. занятная штука. ещё в SVN ocp есть какой-то паскаль препроцессор, и JAOS: JVM на обероне, рейкастер в духе вольфа.

ну и схема на обероне, в sources/Oberon.Scheme.MOD

есть мнение, что из-под A2 и активного оберона играться с сабжем было бы проще.

ну и до кучи, Ants/Voyager – что-то типа платформы анализа данных через интерактивные документы, где можно тыкать и запускать примеры средней кнопкой (типа такой Jupyter Notebook). но оно под совсем древний Oberon V3 ещё из Project Oberon с гаджетами, ЕМНИП.

anonymous
()
Ответ на: комментарий от anonymous

потихоньку для себя тыкаю форт под EFI !ъ:ссылка под A2 и его ассемблер.

потом планирую компилятор креншоу паскаля на форте под него запустить.

в общем, если есть форт на ассемблере – почему бы не появиться форту на обероне.

anonymous
()
Ответ на: комментарий от vM

тогда придётся всё-таки написать форт ос ради веб-браузера :)

что самое смешное, у меня есть какой-то диплом с веб-браузером на форте (под SPF под винду. там всюду HTML3.2 не полностью, однобайтные кодировки и таблицы решений, деревья решений, конечные автоматы во все поля)

может его допилить до конформного WhatWG стандартам HTML5, но я сходу столько не выпью, все тесты писать :)))

anonymous
()
Ответ на: комментарий от wandrien

ещё вот вспомнил паскалей до кучи: язык Ксерион

описание языка Xerion

проект на sourceforge https://sourceforge.net/projects/xerion/

репозиторий в CVS:

#> cvs -z3 -d:pserver:anonymous@a.cvs.sourceforge.net:/cvsroot/xerion co -P xerion

cvs checkout: Updating xerion
cvs checkout: Updating xerion/compiler
U xerion/compiler/analise.cpp
U xerion/compiler/analise.h
U xerion/compiler/arena.cpp
U xerion/compiler/arena.h
U xerion/compiler/codebuf.cpp
U xerion/compiler/codebuf.h
U xerion/compiler/compile.cpp
U xerion/compiler/compile.h
U xerion/compiler/decoder.cpp
U xerion/compiler/exp_imp.cpp
U xerion/compiler/lexer.cpp
U xerion/compiler/lexer.h
U xerion/compiler/main.cpp
U xerion/compiler/nametab.cpp
U xerion/compiler/op_dir.cpp
U xerion/compiler/op_dir.h
U xerion/compiler/op_gen.cpp
U xerion/compiler/opcodes.h
U xerion/compiler/synbase.cpp
U xerion/compiler/synlog.cpp
U xerion/compiler/synlog.h
U xerion/compiler/syntax.l
U xerion/compiler/syntax.y
U xerion/compiler/synterm.h
U xerion/compiler/syntor.cpp
U xerion/compiler/util.cpp
U xerion/compiler/util.h
cvs checkout: Updating xerion/docs
U xerion/docs/xerion_rus.doc

#> du -hs
757K    .

там у него специфический полиморфизм и специфическое ООП.

anonymous
()
Ответ на: комментарий от anonymous

A2 на Active Oberon

Жива, старушка. Всё еще вызывает интерес у людей.

wandrien ★★
() автор топика

а чем тебе раст не нравится?

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.