LINUX.ORG.RU

Пилю генератор кода для компилятора

 , , ,


2

5

Здравствуйте, теребятки! С вами наша непостоянная рубрика ненормальное программирование.

В качестве пет-проекта я потихоньку пилю собственный Ada-подобный ЯП. Планов на фичи языка очень много, но пока он по фичам не далеко ушел от ранних вариантов сишечки. Однако сегодня не об этом. Сегодня я вам хочу рассказать о генерации кода.

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

Сегодня мы посмотрим, что получается, если решать задачу заведомо неподходящим способом.

Некоторые используемые приёмы оптимизации:

  1. Отслеживание, какие константы или значения выражений загружены в регистры и устранение избыточных загрузок.
  2. Переупорядочивание вычисления операндов выражения.
  3. Устранение выдачи неиспользованных меток. (Помогает лучше отслеживать состояние регистров в п.1.)
  4. Устранение избыточных цепочек jmp-ов. (Когда jmp делается на следующий jmp.)

Далее я покажу примеры, как генератор кода устраняет избыточные загрузки значений. Про остальные пункты расскажу как-нибудь в другой раз. (Примерно через никогда.)

Пример 1.

Генератор кода устранил 4 инструкции mov EAX, dword [EBP+8], так как увидел, что значение переменной Index уже загружено в регистр.

Функция:

char @DictGetFullyQualifiedName(word D)
	when D >= nDict:
		StopInternal(__FILE__, __LINE__);

	word Index = Dict[D].FullyQualifiedName;
	when Index != 0:
		return @Char[Index];

	char Buff[nBUFF];
	strbuf buf;
	strbuf_init(@buf, @Buff, nBUFF);
	Dict_MakeFullyQualifiedName(@buf, D);

	Index = SaveString0(@Buff);
	Dict[D].FullyQualifiedName = Index;
	return @Char[Index];
end

Сгенерированный код:

@10342:                                  ; ## DictGetFullyQualifiedName ##
        push    EBP
        mov     EBP,  ESP
        sub     ESP,  144
                                         ; #line compiler_dict.ctxi:84
        mov     EAX,  dword [EBP+8]
        cmp     EAX,  dword [@@DATA+8476676]
        jb      @10343
                                         ; #line compiler_dict.ctxi:85
        push    dword 85
        push    dword @@ROLITERALS+7329
        call    @10235                   ; StopInternal
@10343: 
                                         ; #line compiler_dict.ctxi:87
        mov     EAX,  dword [EBP+8]
        imul    EAX,  77
        mov     EAX,  dword [@@DATA+EAX+776684]
        mov     dword [EBP-4], EAX
                                         ; #line compiler_dict.ctxi:88
                                         ; Node link reuse in CodePrimaryWrapped
        ; Переиспользуется значение Index в регистре EAX
        test    EAX,  EAX
        je      @10346
                                         ; #line compiler_dict.ctxi:89
                                         ; Node link reuse in CodePrimaryWrapped
        ; Переиспользуется значение Index в регистре EAX
        add     EAX,  @@DATA+49288
        leave
        ret     4
@10346: 
                                         ; #line compiler_dict.ctxi:91
                                         ; #line compiler_dict.ctxi:92
                                         ; #line compiler_dict.ctxi:93
        push    dword 128
        lea     EAX,  [EBP-132]
        push    EAX
        lea     EAX,  [EBP-144]
        push    EAX
        call    @10126                   ; strbuf_init
                                         ; #line compiler_dict.ctxi:94
        push    dword [EBP+8]
        lea     EAX,  [EBP-144]
        push    EAX
        call    @10335                   ; Dict_MakeFullyQualifiedName
                                         ; #line compiler_dict.ctxi:96
        lea     EAX,  [EBP-132]
        push    EAX
        call    @10272                   ; SaveString0
        mov     dword [EBP-4], EAX
                                         ; #line compiler_dict.ctxi:97
                                         ; Node link reuse in Code_ASSIGN (1)
        ; Переиспользуется значение Index в регистре EAX
        mov     EBX,  dword [EBP+8]
        imul    EBX,  77
        mov     dword [@@DATA+EBX+776684], EAX
                                         ; #line compiler_dict.ctxi:98
                                         ; Node link reuse in CodePrimaryWrapped
        ; Переиспользуется значение Index в регистре EAX
        add     EAX,  @@DATA+49288
        leave
        ret     4

В этом примере есть интересный фрагмент:

        mov     EAX,  dword [EBP+8]
        cmp     EAX,  dword [@@DATA+8476676]
        jb      @10343
                                         ; #line compiler_dict.ctxi:85
        push    dword 85
        push    dword @@ROLITERALS+7329
        call    @10235                   ; StopInternal
@10343: 
                                         ; #line compiler_dict.ctxi:87
        mov     EAX,  dword [EBP+8]

Здесь также можно было бы устранить повторную загрузку mov EAX, dword [EBP+8], но это пока не реализовано.

  1. Компилятор знает, что StopInternal не возвращает управление.
  2. Единственный переход по метке @10343 выполняется из места, в котором EAX уже содержит нужное значение.
  3. Таким образом вторая загрузка регистра избыточна.
  4. Но чтобы отследить это, необходим продвинутый анализ перехода по меткам, делать который в мои планы пока не входит.

Пример 2.

Вот еще пример устранения загрузок локальной переменной. Генератор кода дважды переиспользует значение переменой L в регистре EAX:

	word L = strlen(@Dst);
	if L > 1 then
		if str_has_char(@fpath_p_dir_separators, Dst[L - 1]) == 0 then
			strn_cat(@Dst, @fpath_p_dir_separator, Size);
		end:if
	end:if
                                         ; #line include/fpath.ctxi:131
        push    dword [EBP+8]
        call    @10013                   ; strlen
        mov     dword [EBP-4], EAX
                                         ; #line include/fpath.ctxi:132
                                         ; Node link reuse in CodePrimaryWrapped
        cmp     EAX,  1
        jbe     @10179
                                         ; #line include/fpath.ctxi:133
                                         ; Node link reuse in CodePrimaryWrapped
        dec     EAX
        add     EAX,  dword [EBP+8]
        mov      AL,  byte  [EAX]
        push    EAX
        push    dword [@@DATA+44]
        call    @10106                   ; str_has_char

Пример 3.

В этом примере генератор кода понимает, что можно повторно использовать константу в EAX, но не понимает, что можно повторно использовать указатель в EBX.

Для оптимизации использования указателя необходим анализ, какие переменные могут измениться при сохранении значения регистра, а какие - не могут. Так как сейчас такой анализ не реализован, то любая запись в память сбрасывает привязку вычисленных выражений.

        mov     EAX,  100000
        mov     EBX,  dword [EBP-8]
        mov     dword [EBX+44], EAX
                                         ; #line compiler_dict.ctxi:117
                                         ; Node link reuse in Code_ASSIGN (1)
        mov     EBX,  dword [EBP-8]
        mov     dword [EBX+32], EAX
                                         ; #line compiler_dict.ctxi:118
                                         ; Node link reuse in Code_ASSIGN (1)
        mov     EBX,  dword [EBP-8]
        mov     dword [EBX+36], EAX
                                         ; #line compiler_dict.ctxi:119
                                         ; Node link reuse in Code_ASSIGN (1)
        mov     EBX,  dword [EBP-8]
        mov     dword [EBX+40], EAX
                                         ; #line compiler_dict.ctxi:120
                                         ; Node link reuse in Code_ASSIGN (1)
        mov     EBX,  dword [EBP-8]
        mov     dword [EBX+12], EAX
                                         ; #line compiler_dict.ctxi:121
                                         ; Node link reuse in Code_ASSIGN (1)
        mov     EBX,  dword [EBP-8]
        mov     dword [EBX+16], EAX
                                         ; #line compiler_dict.ctxi:122
                                         ; Node link reuse in Code_ASSIGN (1)
        mov     EBX,  dword [EBP-8]
        mov     dword [EBX+20], EAX
                                         ; #line compiler_dict.ctxi:123
                                         ; Node link reuse in Code_ASSIGN (1)
        mov     EBX,  dword [EBP-8]
        mov     dword [EBX+24], EAX

Примеры 4, 5, 6.

Устранение загрузок в условных выражениях:

	if rs >= BRACKET_LEX_SIZE | rs < 1 then
		StopInternal(__FILE__, __LINE__);
	end
        mov     EAX,  dword [EBP-8]
        cmp     EAX,  3
        jae     @11156
                                         ; Node link reuse in CodePrimaryWrapped
        cmp     EAX,  1
        jae     @11155
@11156: 
                                         ; #line frontend_syn_brackets.ctxi:40
        push    dword 40
        push    dword @@ROLITERALS+7664
        call    @10235                   ; StopInternal
@11155: 

В теле цикла после проверки условия цикла:

	while i < nOperator do
		word j = i + 1;
		...
        mov     EAX,  dword [EBP-4]
        cmp     EAX,  dword [@@DATA+12180364]
        jae     @11180
                                         ; #line frontend_syn_operators.ctxi:66
                                         ; Node link reuse in CodePrimaryWrapped
        inc     EAX
        mov     dword [EBP-8], EAX
	while i < nOperator do
		if Operator[i].Prio > Prio then
		...
        mov     EAX,  dword [EBP-8]
        cmp     EAX,  dword [@@DATA+12180364]
        jae     @11197
                                         ; #line frontend_syn_operators.ctxi:120
                                         ; Node link reuse in CodePrimaryWrapped
        imul    EAX,  36
        mov     EAX,  dword [@@DATA+EAX+12178080]
        cmp     EAX,  dword [EBP+12]
        jbe     @11198
★★

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

Тред посвящается истории, рассказанной @XOXO: Сравнение нейронок (комментарий)

Путь джедая - чтобы самому не писать на асме, придумайте программу, которая пишет на асме за вас. Так люди изобрели высокоуровневые ЯП. :)

Надеюсь, меня не постигнет судьба того приятеля @XOXO.

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

Ты же знаешь, что такое пет-проекты, да?

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

это навроде того?

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

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

language report

Да, есть черновик, который я пока не готов выкатить в паблик.

Ну например отрывки для затравки:

# Значения времени компиляции и времени выполнения

Значение выражения может иметь время жизни, ограниченное фазой компиляции программы, или время жизни, ограниченное временем фазой выполнения программы, или же иметь смысл как при компиляции, так и при выполнении программы.

Значения, время жизни которых ограничено фазой компиляции программы, называются <типы>. Типы существуют для того, чтобы компилятор мог проверить статическую корректность программы на этапе компиляции.

Значения, время жизни которых ограничено фазой выполнения программы - это значения переменных и выражений в традиционном понимании.

Значения, которые имеют смысл как при компиляции, так и привыполнении программы — это константные литералы, такие как 0, 10 или "Hello, World!", а также константные выражения, вычислимые компилятором, такие как 10 * 25, sizeof(int), 10 + 5 < 20.

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

type_expr(expr) - выражение expr должно быть выражением типа.
const_expr(expr) - выражение expr должно быть константным выражением.
static_expr(expr) - выражение expr должно быть константным выражением или выражением типа.
tangible_expr(expr) - выражение expr не должно быть выражением типа.

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

int x;
static_assert(typeof(x + 1) == int);

Поскольку конструктор типа также является выражением, то и у него есть тип. Его тип — type:

static_assert(typeof(int) == type);
static_assert(typeof(void) == type);
static_assert(typeof(^struct(int x)) == type);
static_assert(typeof(qod) == type);

Поскольку ключевое слово type также само по себе является конструктором типа, то и у него есть тип:

static_assert(typeof(type) == type);
# Управление семантикой переполнения целых чисел

Ключевые слова checked, unchecked, nowraps управляют семантикой переполнения для типов signed() и unsigned().

Для ключевых слов checked, unchecked, nowraps есть две синтаксические формы. Если после ключевого слова расположена круглая скобка, ключевое слово применяется к выражению. Если после ключевого слова расположено двоеточие, ключевое слово применяется к оператору.
checked(expr)
checked: statement

x := checked(a + b);
checked: while a + b < c do
	f(a);
	inc a;
end

В checked-контексте арифметические операции с типами signed() и unsigned() приводят к остановке программы, если результат не может быть представлен целевым типом.

В unchecked-контексте результат арифметических операций с типами signed() и unsigned() усекается, если не может быть представлен целевым типом.

В nowraps-контексте переполнение арифметический операции с типами signed() и unsigned() считается неопределённым поведением. В частности, компилятор может полагать условие x + 1 > x всегда истинным, то есть интерпретировать x как целое число в математическом смысле. Это позволяет компилятору проводить дополнительные оптимизации кода, недоступные для семантики с переполнением.
wandrien ★★
() автор топика
Ответ на: комментарий от wandrien

для нормального програмирования нужны модули, структурные типы, классы(ооп), наследование, спецификаторы видимости членов, вложенные типы, конструкторы/деструктор, универсальная инициализация, блоки кода с локальными обьявлениями.

на завтра пока все.

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

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

про лямбды не знаю… не люблю я ламбды эти из за захватов, но некоторым нравится.

переопределять операторы бум/не бум?

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

для нормального програмирования нужны модули

Сишники заплакали, крестовики вздохнули.

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

если появятся constexpr - это вообще трешак. но внукам будет чем заняться.

кстати… без темплейтов счас ни один пет не обходится.

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

Так не интересно.

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

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

переопределять операторы бум/не бум?

Под переопределять операторы имеется в виду перегружать операции?

Сложный вопрос. Сам над ним голову ломаю.

В этом отношении есть мысль следовать принципу единой точки ответственности. По умолчанию должна быть только одна точка, где задана семантика <имени>.

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

Например. Пусть у нас есть X foo(X x) в модуле MOD1, и есть Y foo(Y y) в модуле MOD2. Таким образом это две разных точки (два разных модуля), которые делят ответственность за имя foo.

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

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

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

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

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

а если обе функции f(int)? надо просто вводить алиасы - то есть локальные переименования любого обьекта и использовать префиксы - называется квалификация имени.

то есть ты можешь одну функцию f импортировать как f_1, через алиас, если не хочешь пользоваться квалификацией.

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

Смотри, допустим у тебя есть модуль MOD1, и в нём определены:

  • foo - обычный шаблон
  • bar - шаблон + специализации
  • baz - функция с перегруженными вариантами void baz(T1) и void baz(T2)

И вот ты в своей программе импортируешь их к себе для использования:

import MOD1;
using MOD1;

Всё хорошо, всё работает.

А затем ты импортируешь другой модуль:

import MOD2;
using MOD2;

Но что если в этом модуле тоже есть определения для foo, bar или baz? Вдруг это не то, что ты хотел? И теперь когда компилятор будет резолвить одно из этих имён с учётом всех правил полиморфизма, он нарезолвит не то, что ты планировал?

По отношению к операциям то же самое, потому что операция просто сахар над функцией.

Короче говоря. Я делаю ЯП, в котором пытаюсь минимизировать НЕЯВНОЕ, чтобы код в как можно большем количестве кейсов считывался программистом именно так, как задуман, а не так, как программисту КАЖЕТСЯ, будто он был так задуман.

Но максимизация ЯВНОСТИ одновременно ведёт к потере выразительности, что тоже может быть нежелательной крайностью.

Финальных ответов по этому балансу у меня пока нет.

то есть ты можешь одну функцию f импортировать как f_1, через алиас

Ну этот-то вариант всегда есть, да.

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

В целом одобряю, но зачем такой странный синтаксис когда можно было взять сишный?

Генерация кода в настоящее время реализована из синтаксического дерева, без трансляции в отдельное представление для оптимизации. Обычно так не делают, так делать неправильно.

По-моему джава так и делает. По крайней мере, когда я писал декомпилятор к ней, обнаружил что порядок расположения байткода всегда соответствует синтаксическому дереву, что очень упростило задачу. Правда то было java 6-8, может сейчас по-другому.

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

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

Отслеживание загрузок выполнено так, что в таблице вида Узел Привязка[Регистр] мы указываем, какой узел дерева сейчас в регистре. А потом сравниваем с тем, какой узел сейчас компилируем.

Там конечно сложнее всё, так как нужно правильно учитывать сброс привязок, и также к одному регистру может быть назначена не 1 привязка, а 2.

Но общий принцип вот такой простой.

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

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

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

Поэтому если проект доживёт до отдалённого светлого будущего, я возможно заменю бэк на готовый: https://c9x.me/compile/ , потому что с нуля писать бэк такого вида в мои планы не входит.

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

Надеюсь, меня не постигнет судьба того приятеля

Всегда есть риск…


Беда его была в том, что он сидел упорно днями, ставил себе сверхзадачи, на свет не выходил из душной проперженной комнаты, мухи дохлые у него на подоконнике валялись горстями, кулер дребезжал в пожелтевшем системнике, обои отклеивались. Коле до этого не было дела, он с мутными глазами, как безумец, что-то писал все время, потом читал эху RU.ASM или что-то такое, не помню. Я в то время читал только RU.SEX и книги бумажные, а компьютер для меня был скорее просто бытовым прибром, иногда я пробовал паскалем баловаться, что бы голова не закисала, но получалось не очень, а друг мой паскаль презирал и стыдил меня, мол ламер, толку с тебя не будет, ерунда это все. Так в итоге и вышло, кстати.

Мы сначала часто заходили проведать, уговаривали, мол, Коля, пойдем портвешка тяпнем да с гитаркой, да под песни ГрОб, «Пластмассовый мир победиииил, Коля!», хлопая его по сутулой спине, пойдем к девкам Коля, пойдем на рыблаку…

Но Николай наш, ко всем самым замечательным вещам в жизни потерял интерес, осунулся, вцепился взглядом сумасшедшего в свой маленький пузатый монитор, от которого отвлекался лишь покормить своего большого пыльного рыжего кота по кличке Веня. У Николая была несбыточная мечта сделать что-то значимое, серьезное, нет не ЯП, он хотел написать свою операционную систему, не меньше, и засчет этого выстроить всю свою дальнейшую жизнь, разбогатеть, улететь в Америку и там зажить как человек, вместо пролетарской хрущевки на первом этаже маленького провинциального городка. Нас тогда это смешило, ну где Коля и где «операционная система», перебесится. Потом, некоторое время спустя стало уже не смешно, конечно, когда у человека психика дала сбой от напряжения и гнета нереализованных амбиций.

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

Отслеживание загрузок выполнено так, что в таблице вида Узел Привязка[Регистр] мы указываем, какой узел дерева сейчас в регистре. А потом сравниваем с тем, какой узел сейчас компилируем.

Ничего не понятно.

Там конечно сложнее всё, так как нужно правильно учитывать сброс привязок, и также к одному регистру может быть назначена не 1 привязка, а 2.

Что значит не 1 привязка? В каждый конкретный момент в регистре может быть только одна переменная. А я про то и пишу что таблица должна быть динамическая и на каждом шаге может меняться.

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

Я вот тоже щас болею непонятно чем – скорее всего из-за малоподвижного образа жизни. Бухать, правда, пока не планирую))

Как бронхитом переболел 2 месяца назад, так что-то совсем разваливаться стал после этого. (То ли какая-то разновидность ковида прилетела, то ли хз, но что-то жестко болел.)

Врачи меня прогнали по куче обследований, и еще сколько-то там обследований осталось.

Кстати, это и к вопросу про Gtk2, который выше по треду кто-то писал. Работаю с трудом из-за проблем с концентрацией внимания, так что Gtk2 пока офф. Эх, верните мне мои 25 лет, раньше такой фигни не было.

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

Ничего не понятно.

Может так понятнее)

word PHO_LookupRegForNode(word P1)
	when PHOptimization == 0:
		return PHO_REG_INVALID;

	word i = PHO_FIRST_REG;
	while i < PHO_FIRST_REG + PHO_REGS do
		when PHO_MatchNodes(PHO_RegNodeLink1[i], P1):
			return i;
		when PHO_MatchNodes(PHO_RegNodeLink2[i], P1):
			return i;
		inc i;
	end:while
	return PHO_REG_INVALID;
end

В каждый конкретный момент в регистре может быть только одна переменная.

void foo(int a; int b)
   int x = a; /* Теперь регистр связан с двумя выражениями: x и a */
   int y = b + 1; /* Теперь регистр связан с двумя выражениями: y и b + 1 */
end

Вообще говоря, содержимое регистра может быть эквивалентно неограниченному количеству выражений, если подряд присваивать переменные.

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

Кстати, про выбор порядка вычисления.

Вот так выглядит несложная логика выбора для оператора присваивания:

word Code_ASSIGN(word P)
	word P1 = Node[P].pLeft;
	if Node[P1].pRight < nNODE then
		word RegRight = LookupRegForNode(Node[P1].pRight);
		select
		case RegRight != RegNone & CodeRequiresSingleRegister(Node[P1].pLeft):
			EmitWithExprComment("", "Node link reuse in Code_ASSIGN (1)");
			Reg_ALT_TARGET = RegRight;
			Reg_TARGET = ChooseEmptyReg2();
			ValueRef Ref;
			CodePrimary(Node[P1].pLeft, @Ref);
			Code_STORE(@Ref, Node[P1].pLeft, Node[P1].pRight);
		case !CodeRequiresSingleRegister(Node[P1].pLeft)
		& CodeRequiresSingleRegister(Node[P1].pRight):
			Reg_TARGET = RegA;
			Reg_ALT_TARGET = RegNone;
			ValueRef Ref;
			CodePrimary(Node[P1].pLeft, @Ref);

			Reg_ALT_TARGET = Ref.IX;
			Reg_TARGET = ChooseEmptyReg2();
			Code(Node[P1].pRight);

			Reg_ALT_TARGET = Reg_TARGET;
			Reg_TARGET = RegNone;
			Code_STORE(@Ref, Node[P1].pLeft, Node[P1].pRight);
		default:
			Reg_ALT_TARGET = PHO_LookupRegForNode(Node[P1].pRight);
			if Reg_ALT_TARGET == RegNone then
				Reg_TARGET = RegA;
				Code(Node[P1].pRight);
				Reg_ALT_TARGET = Reg_TARGET;
			else
				EmitWithExprComment("", "Node link reuse in Code_ASSIGN (2)");
			end

			Reg_TARGET = ChooseEmptyReg2();
			ValueRef Ref;
			CodePrimary(Node[P1].pLeft, @Ref);
			Code_STORE(@Ref, Node[P1].pLeft, Node[P1].pRight);
		end
	else
		Reg_TARGET = RegA;
		Reg_ALT_TARGET = RegNone;
		Code(Node[P1].pLeft);
	end

	Reg_TARGET = RegA;
	Reg_ALT_TARGET = RegA;
	return Node[P].pType;
end

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

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

В целом одобряю, но зачем такой странный синтаксис когда можно было взять сишный?

Если ты про {}-блоки, то… Чтобы узнать ответ на этот вопрос, можно сравнить. Какой вариант надёжнее считывается глазами, и где меньше визуального мусора:

    if NodeCanBeEncodedAsImm(P1) then
        word Value = Node[P1].Value;
        word Size = T_SizeOf(Node[P1].pType);

        if Size == 4 | Size == 2 | Size == 1 then
            /* Если какой-либо регистр уже содержит нужную константу... */
            /* FIXME: проверять только часть регистра при необходимости */
            word Reg = PHO_FindRegWithConst(Value);
            if Reg != PHO_REG_INVALID then
                Emit(@strcpy2(@Buff, "push    ", @PHO_EncodeReg(Reg, 0xFFFF_FFFF)));
                return nDICT;
            end:if
        end:if

        if Size == 4 then
            /* Прямая загрузка числа. Кроме значения 0. Для 0 используем общий алгоритм,
               который позволяет оптимизировать до xor.
            */
            if Value != 0 then
                Emit(@strcpy2(@Buff, "push    dword ", @str(Value)));
                return nDICT;
            end:if
        end:if
    end:if
    if (NodeCanBeEncodedAsImm(P1)) {
        word Value = Node[P1].Value;
        word Size = T_SizeOf(Node[P1].pType);

        if (Size == 4 | Size == 2 | Size == 1) {
            /* Если какой-либо регистр уже содержит нужную константу... */
            /* FIXME: проверять только часть регистра при необходимости */
            word Reg = PHO_FindRegWithConst(Value);
            if (Reg != PHO_REG_INVALID) {
                Emit(@strcpy2(@Buff, "push    ", @PHO_EncodeReg(Reg, 0xFFFF_FFFF)));
                return nDICT;
            }
        }

        if (Size == 4) {
            /* Прямая загрузка числа. Кроме значения 0. Для 0 используем общий алгоритм,
               который позволяет оптимизировать до xor.
            */
            if (Value != 0) {
                Emit(@strcpy2(@Buff, "push    dword ", @str(Value)));
                return nDICT;
            }
        }
    }
wandrien ★★
() автор топика

И еще насчёт читаемости синтаксиса. Если смотреть не в виде цитаты на форуме, а в редакоре с подсветкой синтаксиса, то разница становится еще заметнее.

Для сравнении код на Си (из сорцов tcc):

Чем более лаконичный у ЯП синтаксис, тем более fancy он может выглядеть в изолированных примерах, но тем хуже синтаксис работает в реальной жизни. Питон прекрасно смотрится в виде примера на 5 строчек кода с одним коротким if. Проблемы начинаются при чтении реальных методов.

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

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

Второй лучше. Ещё надо вынести объявление переменных подальше наверх, чтобы слова «word» на засоряли экран, и засунуть PHO_FindRegWithConst внутрь if-условия, тогда ещё лучше станет.

            if ((Reg = PHO_FindRegWithConst(Value)) != PHO_REG_INVALID) {

А ещё можно так:

        switch(Size = T_SizeOf(Node[P1].pType)) {
        case 1: case 2: case 4:
Хотя в данном случае возможно это и лишнее и можно if оставить.

Хотя возможно это всё и правда мелочи, а моя придирка была не к самому синтаксису а к твоему стилю кода.

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

Хотя в данном случае возможно это и лишнее и можно if оставить.

Знаешь, как это делается…

  1. Ставим проверку на равенство 4.
  2. Чешем тыковку, добавляем проверки на равенство 1 и 2.
  3. Думаем: для порядка надо бы на switch заменить.
  4. Вспоминаем, что всё это тлен и говнокод, который один хрен придётся переписывать, когда дойдёт до поддержки компиляции под AMD64.
  5. Идём заниматься другими вещами в коде.
wandrien ★★
() автор топика
Последнее исправление: wandrien (всего исправлений: 1)
Ответ на: комментарий от XOXO

Надеюсь, братан, у тебя есть что вспомнить и чем гордится, побольше чем у «коли», кроме разумеется похмелья и регулярного посещения местного квд. Но судя по аватарке с певцом одной песни вряд ли.

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

Но судя по аватарке с певцом одной песни

На аватарке просто картинка, сгенерированная Stable Diffusion по промту, который я придумал пока пил чай сегодня утром, т.е чистой воды симулякр. Странно что ты там кого-то увидел, мне кажется, это повод для беспокойства, почти как с тестом Роршаха.

есть что вспомнить и чем гордится

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

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

На аватарке просто картинка

До этого был Гребеньщиков. Тот тоже куда-то шёл, как Макаревич на папины деньги, но никуда не пришёл. Кроме пары песен и вспомнить то не о чем.

материальный мир просто иллюзия в нейронах

Яж говорю Боренька, с его псевдобуддизмом. Дело то ваше гражданин, как вы там деградируете в чистых манямирах ботхистатв , вот только втюхивать другим эту блевотину не надо. Они эти другие может чего хорошего сделают, обществу пользу принесут. Блевотина то ваша, она для того, чтобы господину было сподручнее раба смирять. А вы себе этим «материальный мир просто иллюзия в нейронах» просто совесть успокаиваете и как мы оба понимаем: хреново получается.

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

До этого был Гребеньщиков.

Ты ошибаешься, до этого был Пахом, гениальный актёр, художник-акционист и музыкант. Пользуясь случаем рекомендую!

совесть успокаиваете и как мы оба понимаем: хреново получается.

Наоборот получается недурно, но есть над чем работать.

Блевотина

Это Будда, как в той чаньской истории:

Некто спросил Дзен Мастера Ун Муна: — Что есть Будда? — Сухое говно на палочке, — ответил Ун Мун

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

а почему ИИ все время генерирует Пахома?

Вчера утром меня посетила интересная мысль о «симулякрах и симуляциях» по Бодрийяру, и я решил поиграть «в симулякры», написав довольно абстрактый промт для локальной Stable Diffusion, он звучал в точности как: «Draw old man in glasses with blue hair and white beard. Draw green lens flare on glasses.» и запросил у модели 5 результатов, среди которых была эта картинка, отправленная в итоге на аватар, взамен оригинальному Пахому. Можно списать этот факт на забавное случайное совпадение.

XOXO
()

Продолжаю улучшать логику переиспользования регистров, вот такой кейс поймал на этот раз.

Было:

        push    dword [EBP-12]
        call    @13146                   ; DefaultInStackAlign
        add     EAX,  dword [EBP-4]
        mov     dword [EBP-4], EAX
                                         ; #line backend_labels.ctxi:210
        mov     EAX,  dword [EBP-8]
        cmp     EAX,  dword [EBP-4]
        jae     @13266
                                         ; #line backend_labels.ctxi:211
        mov     EAX,  dword [EBP-4]
        mov     dword [EBP-8], EAX
@13266: 

Стало:

        push    dword [EBP-12]
        call    @13146                   ; DefaultInStackAlign
        add     EAX,  dword [EBP-4]
        mov     dword [EBP-4], EAX
                                         ; #line backend_labels.ctxi:210
        cmp     EAX,  dword [EBP-8]
        jbe     @13266
                                         ; #line backend_labels.ctxi:211
        mov     dword [EBP-8], EAX
@13266: 

Соответствующая правка в кодогенераторе:

	word regReuse = 0;
	select
	case (rightOperand == operandImm) &
	     (T_SizeOf(Node[pRight].pType) == 4) &
	     (PHO_RegContainsConst(Reg_TARGET, Node[pRight].Value)):
		 regReuse = 1;
	case PHO_CheckRegForNode(Reg_TARGET, pRight):
		 regReuse = 2;
	end:select

	if regReuse != 0 then
		word swappedID = CheckOperationSwappable(ID);
		if (swappedID != 0) & CodeAsMemoryOperand(@BuffRightOperand, pLeft, NULL) then
			word tmp = pLeft; pLeft = pRight; pRight = tmp;
			ID = swappedID;
			leftOperand = operandReg;
			rightOperand = operandMemRef;
			pType = Node[pLeft].pType;
			if regReuse == 1 then
				EmitWithExprComment("", "Const reuse in CodeBinOp");
			else
				EmitWithExprComment("", "Node link reuse in CodeBinOp");
			end
		end:if
	end:if
wandrien ★★
() автор топика
Ответ на: комментарий от necromant

почему именно Ada, а не Oberon какой?

Мне не нравится подход Вирта к упрощению без конкретизации. Его языки простые не потому, что они технически совершенные. Они простые, потому что неконкретные и выразительно слабые.

С другой стороны, подход Ады это давать по рукам при подозрении, что что-то идёт не так. Поэтому у ней такой многословный синтаксис. И в месте с этим, она выразительно сильнее Оберона.

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

В ракурсе мира идей, я тоже делаю «убийцу Си».

Но я сознательно отказался от преемственности синтаксиса от Си: всех этих {}-скобочек, нотации указателей через звёздочку и амперсанд и т.п.

Конечно, скобочки всем привычны, и мне в том числе. Но отойдя от их привычности, я обнаружил, что Ада-подобная запись синтаксических форм банально… удобнее.

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

Всё это не то, а вот Хороший Учебный Язык – это было бы здорово.

Умные профессора давно решили, что Питон это Хороший Учебный Язык.

Так что страдайте наслаждайтесь. =)

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

Кстати сказать, в отличие от Си, где отсутствие return в функции, которая возвращает не-void значение, является UB, у меня в языке это является ошибкой типов.

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

Примеры:

/* Так не компилируется, ошибка:
   "Функция должна возвращать значение" */
int f(int x)
	if x == 0 then
		return 10;
	end
end
/* Так компилируется. */
int f(int x)
	if x == 0 then
		return 10;
	else
		return 20;
	end
end
/* Так тоже компилируется, для const expr в условиях гарантирована их оптимизация компилятором. */
int f(int x)
	if x == 0 then
		return 10;
	end
	if true then
		return 10;
	end
end
/* Так тоже компилируется + функция будет отмечена как noreturn-функция.
   (Возможна выдача предупреждения или ошибки в зависимости от директив pragma.)
*/
int f(int x)
	while true do
		pass;
	end:while
end

noreturn также может быть указан явно как тип:

/* Так не компилируется, ошибка:
   "Управление достигает конца noreturn функции" */
noreturn halt(word code);
noreturn nr1(word a, s)
	switch s of
	case 1:
		if a < 10 then
			put_word(a + 1); puts(" ");
			halt(0);
		else
			put_word(a); puts(" ");
		end:if
	default:
		halt(0);
	end:switch
end
/* А так компилируется. */
noreturn halt(word code);
noreturn nr1(word a, s)
	switch s of
	case 1:
		if a < 10 then
			put_word(a + 1); puts(" ");
			halt(0);
		else
			put_word(a); puts(" ");
			halt(0);
		end:if
	default:
		halt(0);
	end:switch
end
wandrien ★★
() автор топика
Последнее исправление: wandrien (всего исправлений: 2)
Ответ на: комментарий от wandrien

«Простота» - это достаточно важная характеристика. Имхо как раз простота и ясность обеспечивают низкий уровень входа для языка. То что этому уделяется большое внимание в Go, Swift и ранее в Oberon, это неспроста. Вполне допускаю, что именно это привлекло достаточное количество людей к ним. Правда и в Go завезли generics, а Swift затянуло в грех «Rustовщичества» с механизмом borrowing и «корпопаразитизма» - даже уже сам Крис Латтнер оставил работу над ним.

Мое мнение, что надо вовремя останавливаться и не переусложнять, не перегружать язык дополнительными возможностями. Вон Lisp, Scala достаточно выразительные языки, но не все смогут совладать с такой мощью языка, потому их мало кто использует. Нужно сбалансированное решение, которое будет помогать программистам «класть кирпичи» и закрывать тикеты в джире, вместо витания в облаках высокоуровневых абстракций.

Ada оставила приятное впечатление в принципе. То, что можно выставлять ограничения и бить по рукам, это очень хорошо :) Я как раз обдумывал, что продукт с Адово-Виртовским синтаксисом был бы достаточно неплох и успешен.

Желаю вам успеха в начинании 👍

necromant ★★
()

Сегодня реализовал такие возможности:

struct X of
    int x;
end

X x;
typeof(x) x2;

void foo()
	x.x = 10;
	x2.x = x.x * 2;
	type_expr(int) y = 15;

	if const_expr(
	   is_type_expr(int)
	&  is_type_expr(void)
	&  is_type_expr(type)
	&  is_type_expr(X)
	&  is_type_expr(typeof(x2))
	) then
		puts("OK1 ");
	else
		puts("OOPS ");
	end:if

	if const_expr(is_const_expr(sizeof(int) * 10 + sizeof(long))) then
		puts("OK2 ");
	else
		puts("OOPS ");
	end:if

	if const_expr(
	   is_static_expr(int)
	&  is_static_expr(void)
	&  is_static_expr(type)
	&  is_static_expr(X)
	&  is_static_expr(sizeof(int) * 10 + sizeof(long))
	) then
		puts("OK3 ");
	else
		puts("OOPS ");
	end:if

	if const_expr(
	   is_tangible_expr(x)
	&  is_tangible_expr(x.x)
	&  is_tangible_expr(x.x + 10)
	&  is_tangible_expr(@x)
	&  is_tangible_expr(y)
	&  is_tangible_expr(sizeof(int) * 10 + sizeof(long))
	) then
		puts("OK4 ");
	else
		puts("OOPS ");
	end:if

	if const_expr(!(
	   is_const_expr(int)
	|  is_const_expr(void)
	|  is_const_expr(type)
	|  is_const_expr(X)
	)) then
		puts("OK5 ");
	else
		puts("OOPS ");
	end:if

	if const_expr(!(
	   is_tangible_expr(int)
	|  is_tangible_expr(void)
	|  is_tangible_expr(type)
	|  is_tangible_expr(X)
	)) then
		puts("OK6 ");
	else
		puts("OOPS ");
	end:if

	if const_expr(!(
	   is_const_expr(x)
	|  is_const_expr(x.x)
	|  is_const_expr(x.x + 10)
	|  is_const_expr(@x)
	|  is_const_expr(y)
	)) then
		puts("OK7 ");
	else
		puts("OOPS ");
	end:if

	y = tangible_expr(x.x) + tangible_expr(y);
	if is_tangible_expr(y == 15) then
		puts("OK8 ");
	else
		puts("OOPS ");
	end:if
end
struct X of
    int x;
end

X x;
typeof(x) x2;
typeof(x2.x) i;

void foo()
	typeof(i) y = 15;
	typeof(false) b = true;

	if const_expr(
	   typeof(int) == type
	&  typeof(void) == type
	&  typeof(noreturn) == type
	&  typeof(bool) == type
	&  typeof(X) == type
	&  typeof(type) == type
	&  void == void
	&  uint16 == uint16
	&  uint16 != void
	&  uint16 != int16
	&  uint16 != char
	) then
		puts("OK1 ");
	else
		puts("OOPS ");
	end:if

	if const_expr(
	   typeof(x.x) == typeof(i)
	&  typeof(x.x) == int
	&  typeof(y) == int
	&  typeof(x) == X
	&  typeof(b) == bool
	) then
		puts("OK2 ");
	else
		puts("OOPS ");
	end:if

	if const_expr(
	   typeof(0) == typeof(10)
	&  typeof(false) == typeof(true)
	&  typeof(uint16(0)) == typeof(uint16(10))
	&  typeof(uint16(0)) != typeof(uint32(10))
	&  typeof(uint16(0)) != typeof(true)
	) then
		puts("OK3 ");
	else
		puts("OOPS ");
	end:if
end
wandrien ★★
() автор топика