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)
Ответ на: комментарий от alysnix

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

anonymous
()

Пункт 6. Это лишь на основании того, что идёт последовательное присваивание констант одного типа или всё сложнее?

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

Разновидность pipehole optimization.

Специальный код отслеживает наличие констант в регистрах и переиспользует существующие константы при необходимости, избавляясь таким образом от длинных операций mov reg, const.

Для малых констант (в переделах signed char) можно также использовать:

; 3 байта
push 10
pop EAX

вместо

; 5 байт
mov EAX, 10
wandrien ★★
() автор топика
Последнее исправление: wandrien (всего исправлений: 1)
Ответ на: комментарий от anonymous

Что ты душишь его эффективностью и обоснованностью, как будто деньги ему за это платишь и хочешь получить результат нужный тебе?

кого я душу-то? и где вообще труп. нет тела - нет дела.

ps… вообще блин… только вошел.. и уже мокрое дело шьют..

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

А оно не медленнее будет по итогу исполняться из-за того, что 2 инструкции не могут выполняться параллельно, т.к. обе модифицируют один регистр rsp да ещё и в память(кэш) пишут? Такое прикольно в шеллкоде использовать или в демке где в 256 байт надо уместиться

cobold ★★★★★
()
Последнее исправление: cobold (всего исправлений: 1)
Ответ на: комментарий от wandrien
; 3 байта
push 10
pop EAX

это с памятью команды! ты будешь кеш насильничать и шину памяти.

а мов в регист константы - кладет константу в регистр за ноль тактов считай, с учетом конвейера.

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

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

clang -Oz делает такие оптимизации по размеру кода в ущерб скорости, в отличии от -Os

anonymous
()

Еще что касается скорости, dec EAX была медленнее чем sub EAX, 1, как минимум, на P4. Из-за необходимости частично модифицировать биты состояния операции, а не просто заменять их целиком.

Но на современных интелах вроде разницы быть не должно.

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

Запилил еще один вариант оптимизации по размеру за счёт коротких sub/add вместо длинных mov.

                                         ; #line tests/expr_0.ctx:8
                                         ; PHO: Constant: EAX = 0
        xor     EAX,  EAX
        mov     dword [EBP-4], EAX
                                         ; #line tests/expr_0.ctx:9
                                         ; PHO: Constant: EAX = 1
                                         ; PHO: Instruction: (mov     EAX,  1) -> (inc     EAX)
        inc     EAX
        mov     dword [EBP-8], EAX
                                         ; #line tests/expr_0.ctx:10
                                         ; PHO: Constant: EAX = 2
                                         ; PHO: Instruction: (mov     EAX,  2) -> (inc     EAX)
        inc     EAX
        mov     dword [EBP-12], EAX
                                         ; #line tests/expr_0.ctx:11
                                         ; PHO: Constant: EAX = 3
                                         ; PHO: Instruction: (mov     EAX,  3) -> (inc     EAX)
        inc     EAX
        mov     dword [EBP-16], EAX
                                         ; #line tests/expr_0.ctx:12
                                         ; PHO: Constant: EAX = 5
                                         ; PHO: Instruction: (mov     EAX,  5) -> (sub     EAX, -2)
        sub     EAX, -2
        mov     dword [EBP-20], EAX
                                         ; #line tests/expr_0.ctx:13
                                         ; PHO: Constant: EAX = 100
                                         ; PHO: Instruction: (mov     EAX,  100) -> (sub     EAX, -95)
        sub     EAX, -95
        mov     dword [EBP-24], EAX
                                         ; #line tests/expr_0.ctx:14
                                         ; PHO: Constant: EAX = 200
                                         ; PHO: Instruction: (mov     EAX,  200) -> (add     EAX, EAX)
        add     EAX, EAX
        mov     dword [EBP-28], EAX
wandrien ★★
() автор топика
Ответ на: комментарий от EXL

Раздела статей на ЛОРе не хватает.

А что тебе мешает публиковаться как ТС? Неужели токсичность коммьюнити? А забаниться не хочешь?

anonymous
()

Добавил для режима -Os загрузку констант через push + pop. Работает, но выглядит в листинге очень непривычно.

Вот такой баг еще пофиксил. Не во всех случаях срабатывало:

        mov     EAX,  dword [EBP-20]
        mov     EBX,  dword [EBP-12]
        cmp     EAX,  EBX
        jle     @12897
        mov     EAX,  dword [EBP-20]
        cmp     EAX,  dword [EBP-12]
        jle     @12897
wandrien ★★
() автор топика

Кстати, интеловский компилятор для выражения

volatile int x2 = 100 - num;

генерирует такой код:

 mov    eax,DWORD PTR [esp+0x10]
 neg    eax
 add    eax,0x64
 mov    DWORD PTR [esp],eax

То есть вычисляет как x2 = (-num) + 100.

gcc и clang используют в этом случае sub.

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

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

То что вас эти вопросы интересуют, понятно.
Цель же какая-то есть?

anonymous
()

Если ты всё равно мозги разминаешь, то можешь сделать бекенд для 64-разрядного ARM для А2/ЯОС. 32-разрядный уже есть. А ещё была бы классной такая штука, как отладчик через UART для него же.

den73 ★★★★★
()
Последнее исправление: den73 (всего исправлений: 1)
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);

Так ведь ¬(A∧B) — 2 действия, а ¬A∨¬B — 3.

sudopacman ★★★★★
()

Поздравляю с успехами. Надеюсь, найдёте применение для языка.

NAY_GIGGER
()

Синтаксис пока такой: https://pastebin.com/MiQnCNGa

Нет указателей, вместо них «ссылки». Добавить в парсер указатели не сложно, но в бэке есть пока сложности с этим.

Исходники занимают ~300 килобайт в 37 файлах, не считая файлов с библиотечными функциями (strcpy и т.п.) и тестов.

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

Пару нубских вопросов появилось.

Почему для сложения получается sub eax, -число, а не add eax, число? Отрицание -число не несёт накладных расходов, или оно как константа интерпретируется?

А если в eax 100, и следующее присваивание константы -100, будет выполнено sub eax, 200 или neg eax? Этот вопрос навеял ассемблерный код компилятора Intel строки volatile int x2 = 100 - num; из другого сообщения. Вроде он заточен под всякие хитрые оптимизации, если не ошибаюсь, но как и что конкретно это оптимизирует (явно не размер команды)?

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

Почему для сложения получается sub eax, -число, а не add eax, число?

Потому что короткая форма команд add и sub выделяет 8 бит под операнд со знаком. А число со знаком в дополнительном коде имеет диапазон от -128 до + 127.

Таким образом через sub EAX, -128 можно прибавить к регистру число 128 трёхбайтовой командой.

А если написать add EAX, 128, то это уже 5 байт.

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

Про neg в интел. По размеру кода разницы нет, так как получается по 13 байт:

mov    eax, dword [esp + 8]    ; 4 байта
neg    eax                     ; 2 байта
add    eax, 100                ; 3 байта
mov    dword [esp + 4], eax    ; 4 байта
mov    eax, 100                ; 5 байт
sub    eax, dword [esp + 8]    ; 4 байта
mov    dword [esp + 4], eax    ; 4 байта

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

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

Благодарю за разжевывание, давно не писал на асме, тем не менее, до сих пор интересны всякие низкоуровневые штуки.

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

А если в eax 100, и следующее присваивание константы -100, будет выполнено sub eax, 200 или neg eax?

neg. Он занимает 2 байта. А sub тут не имеет смысла, так как уже не укладывается в 3 байта.

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

В связи с предыдущей информацией это поведение более чем оправдано.

P.S. С самого начала подсказывали голоса в голове, что в таких случаях всё же должно быть neg.

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

Я еще и на nor проверяю.)

Сейчас подумал, что и обычные выражения x + 128 надо проверять и генерировать короткий код.

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

Почитал про систему команд MSP430.

Думаю, в текущем варианте кодогенератор можно относительно малыми затратами на него перенастроить.

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

Планирую такую систему типов. Пока набросок.

Целые типы

  • uint8, uint16, uint32, uint64 и т.п. — целые без знака. Значение результата при переполнении не является UB. (Ну это везде так.)
  • int8, int16, int32, int64 и т.п. — целые со знаком в дополнительном коде. Значение результата при переполнении не является UB. (В отличие от Си.)

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

Типы могут идти со следующими префиксами:

  • checked — компилятор вставляет код для проверки результатов арифметических операций, и если результат не может быть представлен без переполнения, выполняет вызов abort(). Специальная конструкция языка будет предназначена для ветвления по условию переполнения при вычислении выражения вместо вызова abort(). (Т.е. в одну ветку коду управление попадает при отсутствии переполнения, в другую при наличии.)
  • wrapped — в случае переполнения старшие биты просто отбрасываются.
  • saturating — в случае переполнения результат заменяется на соответствующее минимальное или максимальное значение типа.
  • safe — отдельное числовое значение (0xFFF... для беззнаковых и 0x800... для знаковых) представляет значение NaN. Переполнение порождает результат NaN. Любая арифметическая операция с использованием NaN также порождает NaN.

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

Явное приведение типов возможно между любыми числовыми типами, но следует логике соответствующего целевого типа. (Для checked переполнение при приведении типа вызывает abort(), для wrapped биты просто обрезаются и т.п.)

При отсутствии одного из указанных префиксов компилятор использует префикс, указанный в опциях компиляции. Допустимы только варианты: checked и wrapped.

Синонимы целых типов

Синонимы типов, завязанные на особенности реализации:

  • sys.word, sys.iword — целые без знака и со знаком, соответствующие натуральному размеру регистров машины. Разрядность типа может быть переопределена вручную через опции компилятора.
  • sys.smallword, sys.ismallword — целые без знака и со знаком, имеющие разрядность в половину sys.word, sys.iword, в случае, если архитектура машины предоставляет эффективные инструкции для арифметики такого размера. В ином случае эквивалентны sys.word, sys.iword. Разрядность типа может быть переопределена вручную через опции компилятора.
  • sys.longword, sys.ilongword — целые без знака и со знаком, имеющие разрядность в два раза больше sys.word, sys.iword, либо равные им, в зависимости от настроек компилятора для конкретной архитектуры. Разрядность типа может быть переопределена вручную через опции компилятора.
  • sys.storage_size — целое без знака, достаточное для хранения максимального размера объекта в оперативной памяти.
  • sys.storage_offset — целое со знаком, достаточное для хранения разности указателей.

Далее:

  • char, char16, char32 — типы, предназначенные для хранения соответственно 8-битных символов, 16-битных код-пойнтов UCS-2 и 32-битных код-пойнтов UCS-4. Автоматические касты этих типов в другие численные типы (и обратно) запрещены, но они могут быть сконвертированны явным приведением типа.

Типы с фиксированной точкой

  • fixedN.M и ufixedN.M — соответственно знаковый и беззнаковый тип с фиксированной точкой. Число N задаёт общую разрядности типа (включая бит знака), а число M — количество бит после двоичной точки. Например fixed32.8 — число со знаком общей разрядностью 32 бита, из которых 8 бит приходятся на дробную часть.

В том числе, число M может быть больше N, что означает, что двоичная точка уезжает влево за пределы разрядной сетки типа.

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

Также как и целые, типы с фиксированной точкой могут быть checked, wrapped, saturating и safe.

Типы с плавающей точкой

  • Гарантируются только следующие стандартные типы: float32, float64.
  • float — по умолчанию синоним для float64. Разрядность типа может быть переопределена вручную через опции компилятора.
wandrien ★★
() автор топика
Последнее исправление: wandrien (всего исправлений: 7)

Продолжение.

Булев тип

Типы bool8, bool16, bool32 и т.п.

Все типы, независимо от размера, имеют одинаковую семантику: все нулевые биты — FALSE, любое иное значение — TRUE. Типы разного размер введены чтобы облегчить задачу согласования с кодом на других ЯП и правилам ABI разных платформ.

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

Автоматическая конвертация в численные типы и обратно отсутствует, но приведение типа можно выполнить явно. При приведении типа значение FALSE преобразуется в 0, и TRUE преобразуется в 1 в случае, если число 1 может быть представлено в целевом типе. (Для типов с фиксированной точкой это не всегда возможно.) Если 1 не может быть представлено в целевом типе, вместо него используется максимальное для целевого типа значение.

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

Специальная конструкция языка будет предназначена для ветвления по условию переполнения

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

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

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

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

Если так, то да. А если операнды — это, например, переменные, отрицание которых уже некуда упрощать?

На данный момент реализация не поддерживает булев тип в битовом представлении. Соответственно любое булево выражение можно упростить до отсутствия в нём NOT.

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

Однако, не совсем понятно как такие фичи выразить в языке.

В SDCC, вроде бы, была какая-то возможность получать доступ к регистрам и флагам напрямую из сишного кода. Но он не оптимизирующий, насколько мне известно.

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

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

Спасибо, что напомнили. Я размышлял об этом, но как-то позабыл.

Однако, не совсем понятно как такие фичи выразить в языке.

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

В коде программы использование будет типа такого:

int div, mod;
__divmod(@div, @mod, x, y);

Какой-то более удобный синтаксис не придумывается. Впрочем, операция деления сама по себе достаточно редкая, чтобы заморачиваться отдельным синтаксисом для divmod.

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

А если операнды — это, например, переменные, отрицание которых уже некуда упрощать?

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

Это вычисляется как арифметика:

word z = !(x & y);

А это как блок ветвлений:

if !(x != 0 & y != 0) then
wandrien ★★
() автор топика
Ответ на: комментарий от anonymous

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

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

Вот только синтаксис для конструкции я пока придумать не могу.

Пример синтаксиса от балды:

checked int alloc_size;
with checked (alloc_size = obj_size * obj_count) do
    return mem_alloc(alloc_size);
else
    return NULL;
end:with
wandrien ★★
() автор топика
Последнее исправление: wandrien (всего исправлений: 2)
Ответ на: комментарий от wandrien

если архитектура поддерживает

Хах, да кстати, на некоторых младших армах есть операция деления, но полностью отсутствует операция вычисления остатка. То есть поделил и получаешь только частное. Остаток вычисляется дополнительными операциями умножения и вычитания.

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

а комитет стандартизации вместо этого

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

anonymous
()

@wandrien, до сих пор не могу понять, чем «A Retargetable C Compiler: Design and Implementation» не угодил. Авторский код lcc доступен через ftp, пиратскую копию книги тоже можно найти.

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

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

Особенно печально, если делитель — степень двойки.

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

lcc

Проприетарная лицензия, нет, спасибо.

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

Copyright (С) 1995-2007 Andrei Hohlov.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.

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

Особенно печально, если делитель — степень двойки.

Ну если делитель константа, всё намного проще.

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

Надо на ЛОРе отдельный раздел для модераторов ввести, а всех модераторов забанить.

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