LINUX.ORG.RU

Qod. Опубликовал исходники компилятора, над которым работаю

 , qod, ,


5

4

Финально определился с названием языка, подчистил разные хвосты и написал README. Теперь наконец-то можно посмотреть на нечто большее, чем просто фрагменты кода в постах на форуме: https://github.com/wandrien/qod/

Драфты по дизайну языка пока еще не готовы. Если перед НГ завала работы не будет, то может выложу их в течение пары недель. Черновики пишу на русском, осилить всё чётко сформулировать на английском в разумные сроки я точно не смогу. На русском-то не всегда получается.

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

А пока можно посмотреть на сам код вживую.

★★★

Немножко причесал код для генерации пролога и эпилога функций.

Improve code generation for function prologue and epilogue

* Emit "enter" opcode only when it actually shortens the code. (when optimizing for size)
* Prefer just plain "pop EBP" over "leave" when ESP doesn't need restoring. (No local variables in the stack frame).
* Add new optimization option OmitRedundantFramePointer and enable it for size and speed modes. The function prologue and epilogue are omitted when a function has neither parameters on the stack, nor local variables.
void EmitFunctionPrologue(char @Buff; FunctionContext @FnCtx)
	if OmitRedundantFramePointer & FnCtx.ParamFrameSize == 0 & FnCtx.StackSize == 0 then
		FnCtx.FramePointer = false;
	else
		FnCtx.FramePointer = true;
	end

	if FnCtx.FramePointer then
		/*
			enter 1FEh, 0        - c8 fe 01 00
			push  ebp            - 55
			mov   ebp, esp       - 89 e5
			sub   esp, 07Eh      - 83 ec 7e
			sub   esp, 0FEh      - 81 ec fe 00 00 00
			enter is always 4 bytes, with 0FFFFh is the maximum possible value
			sub esp, num is 3 bytes for signed byte operand, 6 bytes otherwise
		*/
		select
		case FnCtx.StackSize == 0:
			Emit("push    EBP");
			Emit("mov     EBP,  ESP");
		case EmitEnterInstruction & (FnCtx.StackSize <= 0xFFFF):
			Emit(@strcpy3(@Buff,"enter   ",@str(FnCtx.StackSize), ", 0"));
		default:
			Emit("push    EBP");
			Emit("mov     EBP,  ESP");
			Emit(@strcpy2(@Buff,"sub     ESP,  ",@str(FnCtx.StackSize)));
		end:select
	end:if
end

void EmitFunctionEpilogue(char @Buff; FunctionContext @FnCtx)
	if FnCtx.FramePointer then
		/*
			leave            - c9
			pop    ebp       - 5d
			mov    esp, ebp  - 89 ec
		*/
		select
		case FnCtx.StackSize == 0:
			Emit("pop     EBP");
		case EmitLeaveInstruction:
			Emit("leave");
		default:
			Emit("mov     ESP,  EBP");
			Emit("pop     EBP");
		end:select
	end:if

	if FnCtx.ParamFrameSize > 0 then
		Emit(@strcpy2(@Buff,"ret     ",@str(FnCtx.ParamFrameSize)));
	else
		Emit("ret");
	end:if
end
wandrien ★★★
() автор топика

Продолжение улучшений в генерации кода

Implement aliasing detection for PHO_LinkNode_Store()

1. New function bool PHO_CheckAliasing(word pStore; word pLoad) is added for the aliasing detection between pStore and pLoad subtrees.
2. PHO_LinkNode_Store(pStore) now resets any saved links that may be aliased by pStore.

This fixes potentially incorrect code optimizations, as in this example:

         mov     EBX,  dword [EBP-16]
         mov     EDX,  dword [EBP-8]
         mov     dword [EDX], EBX
                                          ; #line frontend_syn_statements.qdi:190
+        mov     EBX,  dword [EBP-16]
         imul    EBX,  37
         add     EBX,  @@DATA+8877685
         mov     dword [EBP-8], EBX

3. The implementation of aliasing checking allowed adding iINDEX to the list of node types available for linking by PHO_LinkNode() and PHO_LinkNode_Store().

This led to more efficient elimination of common subexpressions, as in the following examples:

         mov     EAX,  dword [EBP+12]
         lea     EAX,  [EAX*8+EAX]
         lea     EAX,  [EAX*8+EAX]
         mov     EAX,  dword [@@DATA+EAX+777204]
         cmp     EAX,  dword [EBP+12]
         je      @10472
                                          ; #line compiler_dict.qdi:115
-        mov     EAX,  dword [EBP+12]
-        lea     EAX,  [EAX*8+EAX]
-        lea     EAX,  [EAX*8+EAX]
-        mov     EAX,  dword [@@DATA+EAX+777204]
         push    EAX
         push    dword [EBP+8]
         call    @10468                   ; Dict_MakeFullyQualifiedName

........................................................................

         mov     EAX,  dword [EBP+8]
         imul    EAX,  37
         mov     EAX,  dword [@@DATA+EAX+8877685]
         cmp     EAX,  100000
         je      @11783
                                          ; #line frontend_syn_cte.qdi:477
         push    dword @@ROLITERALS+6581
-        mov     EAX,  dword [EBP+8]
-        imul    EAX,  37
-        mov     EAX,  dword [@@DATA+EAX+8877685]
         push    EAX
         call    @11006                   ; WarnAtNode

........................................................................

         mov     EBX,  dword [EBP-40]
         lea     EBX,  [EBX*8+EBX]
         lea     EBX,  [EBX*8+EBX]
         mov     dword [@@DATA+EBX+777236], EAX
                                          ; #line frontend_syn_up.qdi:406
-        mov     EAX,  dword [EBP-40]
-        lea     EAX,  [EAX*8+EAX]
-        lea     EAX,  [EAX*8+EAX]
-        mov     EAX,  dword [@@DATA+EAX+777236]
         push    EAX
         call    @10780                   ; T_IsFunc

........................................................................

         mov     EAX,  dword [EBP-20]
         imul    EAX,  37
         mov     EAX,  dword [@@DATA+EAX+8877672]
         cmp     EAX,  55
         je      @13658
                                          ; #line backend_labels.qdi:175
-        mov     EAX,  dword [EBP-20]
-        imul    EAX,  37
-        mov     EAX,  dword [@@DATA+EAX+8877672]
         cmp     EAX,  56
         je      @13658
                                          ; #line backend_labels.qdi:176
-        mov     EAX,  dword [EBP-20]
-        imul    EAX,  37
-        mov     EAX,  dword [@@DATA+EAX+8877672]
         cmp     EAX,  52
         je      @13658

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

Добавил возможность конвертировать булевы в целые и целые в булевы. Возможность, которая должна была быть, но которую я забыл добавить ранее.

Теперь код наподобие такого компилируется:

int x = int(5 + 5 == 10);

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

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

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

Некоторые примеры компиляции кода:

  • Устранение двух последующих вычислении смещения:
	P = Peek();
	Node[P].ID = iSTRING;
	Node[P].pType = pTypeI;
	when ROStrings != 0:
		Node[P].Value = I;
                                         ; #line frontend_syn_expr.qdi:550
        call    @10923                   ; Peek
        mov     dword [EBP-4], EAX
                                         ; #line frontend_syn_expr.qdi:551
        imul    EAX,  37
        mov     EDX,  6
        mov     dword [@@DATA+EAX+8877672], EDX
                                         ; #line frontend_syn_expr.qdi:552
        mov     ECX,  dword [EBP-8]
        mov     dword [@@DATA+EAX+8877689], ECX
                                         ; #line frontend_syn_expr.qdi:553
        mov     EBX,  dword [@@DATA+8877344]
        test    EBX,  EBX
        je      @12069
                                         ; #line frontend_syn_expr.qdi:554
        mov     EBX,  dword [EBP+8]
        mov     dword [@@DATA+EAX+8877677], EBX
@12069: 
  • Устранение повторного вычисления смещения. Еще одно лишнее умножение убрать не удаётся, так как у алгоритма не хватает регистров (сейчас в генерации кода используются только EAX, EBX, ECX, EDX). На 64-разорядной версии, где регистров доступно больше, избыточное умножение было бы устранено.
if (Node[P1].Value == Node[P2].Value)
&  T_Equal(Node[P1].pType, Node[P2].pType) then
	return true;
else
	return false;
end
        mov     EDX,  dword [EBP+8]
        imul    EDX,  37
        mov     EAX,  dword [@@DATA+EDX+8877677]
        mov     ECX,  dword [EBP+12]
        imul    ECX,  37
        mov     EDX,  dword [@@DATA+ECX+8877677]
        cmp     EAX,  EDX
        jne     @10997
                                         ; #line frontend_node.qdi:213
        mov     EAX,  dword [@@DATA+ECX+8877689]
        push    EAX
        mov     EBX,  dword [EBP+8]
        imul    EBX,  37
        mov     EAX,  dword [@@DATA+EBX+8877689]
        push    EAX
        call    @10572                   ; T_Equal
        test    EAX,  EAX
        jz      @10997
                                         ; #line frontend_node.qdi:214
        mov     EAX,  1
        pop     EBP
        ret     8
@10997: 
                                         ; #line frontend_node.qdi:216
        xor     EAX,  EAX
        pop     EBP
        ret     8
  • Пример удаления 4 лишних вычислений смещения:
                                          ; #line backend_asmcode_mul.qdi:69
         mov     EAX,  dword [EBP-4]
         mov     EBX,  dword [@@DaTa+12748504]
         lea     EBX,  [EBX*4+EBX]
         shl     EBX,  2
         mov     dword [@@DaTa+EBX+12747704], EAX
                                          ; #line backend_asmcode_mul.qdi:70
         mov     EAX,  dword [EBP+8]
-        mov     EBX,  dword [@@DaTa+12748504]
-        lea     EBX,  [EBX*4+EBX]
-        shl     EBX,  2
         mov     dword [@@DaTa+EBX+12747708], EAX
                                          ; #line backend_asmcode_mul.qdi:71
         mov     EAX,  dword [EBP+12]
-        mov     EBX,  dword [@@DaTa+12748504]
-        lea     EBX,  [EBX*4+EBX]
-        shl     EBX,  2
         mov     dword [@@DaTa+EBX+12747712], EAX
                                          ; #line backend_asmcode_mul.qdi:72
         xor     EAX,  EAX
-        mov     EBX,  dword [@@DaTa+12748504]
-        lea     EBX,  [EBX*4+EBX]
-        shl     EBX,  2
         mov     dword [@@DaTa+EBX+12747716], EAX
                                          ; #line backend_asmcode_mul.qdi:73
-        mov     EBX,  dword [@@DaTa+12748504]
-        lea     EBX,  [EBX*4+EBX]
-        shl     EBX,  2
         mov     dword [@@DaTa+EBX+12747720], EAX
  • В некоторых случаях регистр со смещением удачно переиспользуется после ветвлений в блоке switch. Для других видов управляющих конструкций отслеживание привязок регистров не реализовано.
wandrien ★★★
() автор топика

Улучшеная обработка синтаксических ошибок при парсинге исходного кода, и некоторые сообщения об ошибках сделаны более дружелюбными.

Данная некорректная программа при компиляции даёт следующие сообщения об ошибках:

samples/wrong_code.qd:9:9: ошибка: Пропущена точка с запятой
samples/wrong_code.qd:10:13: ошибка: Пропущена точка с запятой
samples/wrong_code.qd:11:9: ошибка: Пропущена точка с запятой
samples/wrong_code.qd:12:11: ошибка: Пропущена точка с запятой
samples/wrong_code.qd:14:1: ошибка: Пропущена точка с запятой
samples/wrong_code.qd:11:9: предупреждение: Недостижимый оператор
samples/wrong_code.qd:15:6: ошибка: Пропущена точка с запятой
samples/wrong_code.qd:15:14: ошибка: Избыточная точка с запятой
samples/wrong_code.qd:19:7: ошибка: Пропущена точка с запятой
samples/wrong_code.qd:20:8: ошибка: Пропущена точка с запятой
samples/wrong_code.qd:22:5: ошибка: Пропущена точка с запятой
samples/wrong_code.qd:23:8: ошибка: Пропущен разделитель 'then'
samples/wrong_code.qd:24:12: ошибка: Ошибочная метка конца блока: 'while' вместо 'if'
samples/wrong_code.qd:27:8: ошибка: Пропущен разделитель 'do'
samples/wrong_code.qd:28:9: ошибка: Ошибочная метка конца блока: 'if' вместо 'do' или 'while'
samples/wrong_code.qd:30:19: ошибка: Пропущен разделитель 'do'
samples/wrong_code.qd:30:19: ошибка: Обнаружен ошибочный разделитель 'then'
samples/wrong_code.qd:32:13: ошибка: Ошибочная метка конца блока: 'switch' вместо 'do' или 'while'
samples/wrong_code.qd:35:7: ошибка: Пропущен разделитель 'of'
samples/wrong_code.qd:35:13: ошибка: Повтор варианта
samples/wrong_code.qd:36:10: ошибка: Требуется константа
samples/wrong_code.qd:37:16: ошибка: Пропущен разделитель ':'
samples/wrong_code.qd:38:12: ошибка: Пропущен разделитель ':'
wandrien ★★★
() автор топика

Для циклов ключевое слово do изменено на loop, подобно тому как сделано в языках Ada и Cowgol

Сравнение кода:

Ada:

declare
   I : Integer := 1024;
begin
   while I > 0 loop
      Put_Line(Integer'Image(I));
      I := I / 2;
   end loop;
end;

Cowgol:

var n: uint16 := 1024;
while n > 0 loop
    print_i16(n);
    print_nl();
    n := n/2;
end loop;

Qod:

uint16 n = 1014;
while n > 0 loop
    put_uint16(n);
    put_nl();
    n = n / 2;
end:loop

Старый синтаксис while ... do ... end:do всё еще поддерживается.

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

Зачем ожидать какой-то ЧУДО ЯП?

Разработал архитектуру и синтаксис структур в которых объекты и
поля могут содержать свойства (любой сложности).

Скажу честно мне ЧУДО-ЮДО ЯП более не особо интересуют.
Того что мне нужно для создания алгоритмов в них никогда не будет.

anonymous
()

Занимаюсь добавлением атрибутов maybe_unreachable и ensure_unreachable.

Выглядеть это будет как-то так:

char @GetDefaultTarget()
	when qod.env.__LINUX__:
		return "--linux";
	attribute(maybe_unreachable)
	when qod.env.__WIN32__:
		return "--win32-c";
	attribute(ensure_unreachable) pass;
end

maybe_unreachable подавляет ворнинг о недостижимом коде.

А ensure_unreachable прерывает компиляцию, если код в указанной точке оказывается достижим.

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

Хотя конкретно в данном примере attribute(ensure_unreachable) излишний.

Достаточно просто оставить отсутствие return в конце функции, и в случае, если ни одна из веток не вычислилась в безусловный return, функция не пройдёт тайпчек:

qodc.qd:66:7: ошибка: Функция должна возвращать значение: GetDefaultTarget

Но ensure_unreachable полезен в случае функций, возвращающих void, а также в целях более ясного отображения намерений разработчика.

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

Достаточно просто оставить отсутствие return в конце функции, и в случае, если ни одна из веток не вычислилась в безусловный return, функция не пройдёт тайпчек

По этому поводу добавил в тесты пример корректной программы, которая соблюдает указанные условия: https://github.com/wandrien/qod/blob/a62079dfa142560c77ce82fed90ac7122125a5de/src/tests/return_0.qd

А то был только тест на ловлю отсутствия return.

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

Самой горячей точкой программы в настоящее время является функция, которая резолвит имя в пространстве имён. При компиляции компилятором самого себя, он обращается к этой функции 39 300 раз, проводя в ней более 19% от общего количества выполненных инструкций.

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

attribute(hot)
word FindInNamespace(char @Name; word pNamespace)
	when pNamespace >= nDict:
		StopInternal(__FILE__, __LINE__);
	word hash = str_hash(@Name);
	word P = Dict[pNamespace].pFirst;
	while P < nDICT do
		if Dict[P].Name.Hash != hash then /* optimized for better code layout */
			P = Dict[P].pNext;
		else
			if str_eq(@DictGetName(P), @Name) then
				return DictResolveAlias(P);
			end
			P = Dict[P].pNext;
		end
	end
	return nDICT;
end

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

        align   32
; ЦИКЛ ОТСЮДА ===>
@10499: 
        mov     EAX,  dword [EBP-8]
        cmp     EAX,  100000
        jae     @10500
                                         ; #line compiler_dict.qdi:199
        imul    EAX,  91
        mov     EAX,  dword [@@DATA+EAX+777192]
        cmp     EAX,  dword [EBP-4]
        je      @10503
                                         ; #line compiler_dict.qdi:200
        mov     EDX,  dword [EBP-8]
        imul    EDX,  91
        mov     EAX,  dword [@@DATA+EDX+777204]
        mov     dword [EBP-8], EAX
        jmp     @10499
; <=== ДОСЮДА
@10503: 
                                         ; #line compiler_dict.qdi:202
        push    dword [EBP+8]
        push    dword [EBP-8]
        call    @10464                   ; DictGetName
        push    EAX
        call    @10031                   ; str_eq
        test    EAX,  EAX
        jz      @10506
                                         ; #line compiler_dict.qdi:203
        push    dword [EBP-8]
        call    @10487                   ; DictResolveAlias
        leave
        ret     8
@10506: 
                                         ; #line compiler_dict.qdi:205
        mov     EDX,  dword [EBP-8]
        imul    EDX,  91
        mov     EAX,  dword [@@DATA+EDX+777204]
        mov     dword [EBP-8], EAX
        jmp     @10499
@10500: 
                                         ; #line compiler_dict.qdi:208
        mov     EAX,  100000
        leave
        ret     8

При этом проверка условия str_eq(@DictGetName(P), @Name) в теории может сфейлиться, а на практике, в профиле valgring соответствующий бранч не был выполнен ни разу. Потому что для хэша используется 32-битное число, и вероятность коллизиции, сами понимаете. Поэтому в рамках оптимизации производительности, этот случай не является телом цикла де факто.

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

Далее для принципиального скачка в скорости парсинга нужно переходить со списка на дерево или хэш-мапу. Но мне тааааак леееень пока этим заниматься.

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

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

Ну в общем, да. Сокращение кода до следующего не дало никакого существенного прироста в общей скорости работы:

        align   32
@10499: 
        mov     EAX,  dword [EBP-8]
        cmp     EAX,  100000
        jae     @10500
                                         ; #line compiler_dict.qdi:199
        imul    EAX,  92
        mov     EDX,  dword [@@DATA+EAX+777192]
        cmp     EDX,  dword [EBP-4]
        je      @10503
                                         ; #line compiler_dict.qdi:200
        mov     ECX,  dword [@@DATA+EAX+777204]
        mov     dword [EBP-8], ECX
        jmp     @10499
wandrien ★★★
() автор топика
Ответ на: комментарий от wandrien

Далее что тут можно сделать это поместить часть данных в регистры, чтобы получилось такое:

        mov     ESI,  dword [EBP-8]
        mov     EDI,  dword [EBP-4]
        mov     EBX,  100000

        align   32
@10499: 
        cmp     ESI,  EBX
        jae     @10500

        imul    EAX,  ESI, 92
        mov     EDX,  dword [@@DATA+EAX+777192]
        cmp     EDX,  EDI
        je      @10503

        mov     ESI,  dword [@@DATA+EAX+777204]
        jmp     @10499

Компилятор пока такое не умеет.

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

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

Что касается оптимизации кода, то далее в моих планах:

  • Добавить likely(), unlikely(), чтобы указывать компилятору, какие ветви являются горячими, а какие холодными.
if likely(x < y) then
    /* горячая ветвь */
    pass;
else
    /* холодная ветвь */
    pass;
end:if
  • Если likely(), unlikely() не указаны, то компилятор будет распознавать их автоматически. Если внутри ветви находится вызов функции, помеченной как attribute(cold), то такая ветвь является холодной.

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

Код наподобие такого:

int f1(int x, y, z) of
    when x == 0:
        panic(__FILE__, __LINE__, "invalid x");
    when y == 0:
        panic(__FILE__, __LINE__, "invalid x");
    when z == 0:
        panic(__FILE__, __LINE__, "invalid x");

    int a = f2(x, y, z);

    when a == 0:
        panic(__FILE__, __LINE__, "invalid a");

    return a;
end

Будет превращен во что-то типа такого:

int f1(int x, y, z) of
    when x == 0: goto panic_x;
    when y == 0: goto panic_y;
    when z == 0: goto panic_z;
    int a = f2(x, y, z);
    when a == 0: goto panic_a;
    return a;

label panic_x:
    panic(__FILE__, __LINE__, "invalid x");
label panic_y:
    panic(__FILE__, __LINE__, "invalid y");
label panic_z:
    panic(__FILE__, __LINE__, "invalid z");
label panic_a:
    panic(__FILE__, __LINE__, "invalid a");
end

(Так как компилятор знает, что panic() не возвращает управление, то никакого дополнительного кода для «возврата обратно» не генерируется.)

  • Также опционально холодные ветви могут быть оптимизированы по размеру кода, а не по скорости.
wandrien ★★★
() автор топика
Последнее исправление: wandrien (всего исправлений: 5)

Пример генерации кода для следующей функции:

attribute(hot)
bool str_eq(char @S1; char @S2) of
	when @S1 == @S2:
		return true;

	word i = 0;
	while S1[i] == S2[i] do
		if S1[i] != #0 then /* optimized for better code layout */
			inc i;
		else
			return true;
		end
	end

	return false;
end
        align   32

@10031:                                  ; ## str_eq ##
        push    EBP
        mov     EBP,  ESP
        sub     ESP,  4
                                         ; #line include/strings_cmp.qdi:19
                                         ; #line include/strings_cmp.qdi:22
        mov     EAX,  dword [EBP+8]
        cmp     EAX,  dword [EBP+12]
        jne     @10035
                                         ; #line include/strings_cmp.qdi:23
        mov     EAX,  1
        leave
        ret     8
@10035: 
                                         ; #line include/strings_cmp.qdi:25
        xor     EAX,  EAX
        mov     dword [EBP-4], EAX
                                         ; #line include/strings_cmp.qdi:26
        align   32
@10039: 
        mov     EAX,  dword [EBP-4]
        add     EAX,  dword [EBP+8]
        mov      AL,  byte  [EAX]
        mov     EDX,  dword [EBP-4]
        add     EDX,  dword [EBP+12]
        mov      DL,  byte  [EDX]
        cmp      AL,   DL
        jne     @10040
                                         ; #line include/strings_cmp.qdi:27
        test     AL,   AL
        je      @10043
                                         ; #line include/strings_cmp.qdi:28
        inc     dword [EBP-4]
        jmp     @10039
@10043: 
                                         ; #line include/strings_cmp.qdi:30
        mov     EAX,  1
        leave
        ret     8
@10040: 
                                         ; #line include/strings_cmp.qdi:34
        xor     EAX,  EAX
        leave
        ret     8

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

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

@firkax, что думаешь по этому поводу?

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

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

Конечно еще напрашивается увеличение указателя вместо счетчика.

Еще меня смущает выравнивание, мне кажется оно отрицательную оптимизацию вводит. Ты пробовал без него?

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 6)

Хрень какая-то произошла только что.

valgrind + qcachegrind прекрасно работали. Вдруг раз – и qcachegrind перестал отображать и ассемблерный исходник, и машинный код.

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

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

Еще меня смущает выравнивание, мне кажется оно отрицательную оптимизацию вводит. Ты пробовал без него?

Разница в пределах погрешности измерения. Вроде с выравниванием немного быстрее, но зуб не дам. Это надо закрыть все другие приложения, запустить штук по 50 каждого измерения и усреднить. Пока некогда, попозже гляну, что получится.

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

Что то они плохо распределяются, все действия через память постоянно проходят.

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

Делать полноценный IL и гонять на нём сложные алгоритмы оптимизации в мои планы не входит.

Но если кому-то хочется переизобрести LLVM в рамках собственного проекта, то можно взять https://c9x.me/compile/doc/il.html и написать на него оптимизирующий бэк ;)

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

Разница в пределах погрешности измерения.

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

Потому что это тупо разновидность peephole оптимизации.

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

В твоем цикле восстановления/сохранения счетчика вообще не будет. Это простая доработка. Пусть счетчик будет в регистре.

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

@firkax, что думаешь по этому поводу?

По какому именно?

Если это

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

То, очевидно, да. И для этого можно даже не проверять текущее состояние. Если речь про ПК последних 30 лет, то любой компилируемый язык подходит для практических целей в плане производительности, потому как требований к производительности генерируемого компилятором кода можно считать нет, за исключением числодробилок. Это, конечно, не значит, что надо обкладываться оверхедами итд, оверхеды всегда неприятны, но этот аспект не блокирует практическое использование. Основные источники лагов вовсе не в компиляторе а в плохом коде софта.

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

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

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

Кстати сказать, я планировал добавить сюда фронт для Си.

Вроде бы, самое муторное, что есть в Си, и чего не умеет мой кодогенератор — это goto про произвольной метке. В текущем виде мой вариант умеет только структурированные переходы.

Так что пока бэк не готов, чтобы к нему прикручивать сишный фронт.

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

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

Ну это естественный ход улучшения программы:

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

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

Ну и в итоге вот такой кусок исходника:

while P < nDICT do
  if Dict[P].Name.Hash != hash then
    P = Dict[P].pNext;
    continue;
...

Превращается во вполне осмысленный цикл:

@10499: 
        mov     EAX,  dword [EBP-8]
        cmp     EAX,  100000
        jae     @10500
                                         ; #line compiler_dict.qdi:199
        imul    EAX,  92
        mov     EDX,  dword [@@DATA+EAX+777192]
        cmp     EDX,  dword [EBP-4]
        je      @10503
                                         ; #line compiler_dict.qdi:200
        mov     ECX,  dword [@@DATA+EAX+777204]
        mov     dword [EBP-8], ECX
        jmp     @10499

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

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

Для сравнения тот же код с выключенной оптимизацией:

@10499: 
        mov     EAX,  dword [EBP-8]
        cmp     EAX,  100000
        jae     @10500
                                         ; #line compiler_dict.qdi:199
        mov     EAX,  dword [EBP-8]
        imul    EAX,  92
        mov     EAX,  dword [@@DATA+EAX+777192]
        cmp     EAX,  dword [EBP-4]
        je      @10503
                                         ; #line compiler_dict.qdi:200
        mov     EAX,  dword [EBP-8]
        imul    EAX,  92
        mov     EAX,  dword [@@DATA+EAX+777204]
        mov     dword [EBP-8], EAX
        jmp     @10499
wandrien ★★★
() автор топика

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

attribute(hot)
word FindInNamespace(char @Name; word pNamespace)
	when pNamespace >= nDict:
		StopInternal(__FILE__, __LINE__);
	word hash = str_hash(@Name);
	word P = Dict[pNamespace].pFirst;
	while P < nDICT do
		if Dict[P].Name.Hash != hash then /* optimized for better code layout */
			P = Dict[P].pNext;

			when P >= nDICT:
				exit;
			if Dict[P].Name.Hash != hash then
				P = Dict[P].pNext;
			else
				if str_eq(@DictGetName(P), @Name) then
					return DictResolveAlias(P);
				end
				P = Dict[P].pNext;
			end

		else
			if str_eq(@DictGetName(P), @Name) then
				return DictResolveAlias(P);
			end
			P = Dict[P].pNext;
		end
	end
	return nDICT;
end

В результате нашел две недоработки в генераторе кода.

Первая — тупая опечатка в функции проверки алиасинга: https://github.com/wandrien/qod/commit/6dcb10ec700f9e133975bc5cdc0362a38d814513

Вторая — неоптимальная генерация jmp-ов. Из вложенного if переход осуществляется не в начало цикла, а на промежуточный jmp.

Вот кстати про опечатку по ссылке. Никакой Раст от такого не защитит, конечно. Никаких вам обращений по нулевому указателю. Тупо вызов не той функции.

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

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

Но пока метки блоков я не реализовал.

attribute(hot)
word FindInNamespace(char @Name; word pNamespace)
	when pNamespace >= nDict:
		StopInternal(__FILE__, __LINE__);
	word hash = str_hash(@Name);
	word P = Dict[pNamespace].pFirst;
	label outer:
	forever loop
		label inner:
		forever loop
			when P >= nDICT:
				exit outer;
			when Dict[P].Name.Hash == hash:
				exit inner;
			P = Dict[P].pNext;
			when P >= nDICT:
				exit outer;
			when Dict[P].Name.Hash == hash:
				exit inner;
			P = Dict[P].pNext;
		end:inner

		if str_eq(@DictGetName(P), @Name) then
			return DictResolveAlias(P);
		end
		P = Dict[P].pNext;
	end:outer
	return nDICT;
end
wandrien ★★★
() автор топика
Последнее исправление: wandrien (всего исправлений: 1)
Ответ на: комментарий от wandrien

Вторая — неоптимальная генерация jmp-ов. Из вложенного if переход осуществляется не в начало цикла, а на промежуточный jmp.

Это проблему тоже решил.

Также этот фикс немного приблизил к реализации exit по labeled statements.

Размышляю вот о том, чтобы добавить в язык fallthrough:

switch x of
case 1,2:
    bla_bla_1();
    fallthrough;
case 3,4:
    bla_bla_2();
end:switch

А также о том, чтобы добавить его не только в switch, но в select:

select
case f1():
    bla_bla_1();
    fallthrough;
case f2():
    bla_bla_2();
end:select

Потому что алгоритмически для статического анализа программы и работы кодогенератора – однофигственно. Если в свитче может быть fallthrough, то и в select его добавление ничего не стоит.

Соображения могут быть чисто в плане дизайна ЯП: нафига такое надо. Но идея мне нравится.

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

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

Вторая — неоптимальная генерация jmp-ов. Из вложенного if переход осуществляется не в начало цикла, а на промежуточный jmp.

А на switch это повлияло каким нибудь образом, может код уже лучше производится чем у GCC/Clang? https://godbolt.org/z/a7vbfnKd9

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

Я не делал реализацию табличного switch.

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

  • Доделать сам язык.
  • Реализовать поддержку множества целевых платформ в генераторе кода.

Вообще твоя подъёбка «может код уже лучше производится чем у GCC/Clang?» глупо выглядит.

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

А реализация табличного switch сама по себе никуда не ведёт. Это будет просто одна изолированная функция или класс в генераторе кода, не связанная с остальной кодовой базой.

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

Вообще твоя подъёбка «может код уже лучше производится чем у GCC/Clang?» глупо выглядит.

Я лишь поинтересовался, у тебя какие то языковые комплексы неполноценности. У GCC/Clang на удивление плохая генерация для такой распространенной конструкции, что сразу выделяется, на фоне того как они оптимизируют и довольно сложный код. А если ты сделал сворачивание лишних jmp, то я предположил что это могло повлиять на jmp table и реально привести к более лучшему коду чем у GCC/Clang.

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

Если таблиц много и они в горячем пути, то повлияет. Или если через них реализуется полиморфизм (enum+union) вместо создания vtable из указателей на функции. Конечно намного выгоднее сделать распределение регистров если выбирать из текущих целей, я про то что это не надуманная оптимизация.

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

Я лишь поинтересовался, у тебя какие то языковые комплексы неполноценности. У GCC/Clang на удивление плохая генерация для такой распространенной конструкции, что сразу выделяется, на фоне того как они оптимизируют и довольно сложный код.

Просто до меня уже кто-то тут докапывался с этими jump-таблицами и достал. Не помню, кто это был.

Если таблиц много и они в горячем пути, то повлияет.

У меня 16-19 процентов времени программа проводит в функции поиска имени в пространстве имён. Потому что там используется линейный список вместо бинарного дерева или хэш-таблицы. Это самый горячий кусок программы сейчас.

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

Видимо, нужная какая-то другая программа, написанная на Qod, которая бы меня мотивировала заняться оптимизацией switch-а =)

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

сворачивание лишних jmp

Просто оно уже и раньше было, но оказалось недоделанным.

while a != b loop
   if bla_bla(a) then
      a = foo1(b);
      // здесь переход не на "после if", а как если бы стояло continue -- сразу на условие цикла
   else
      a = foo2(b);
      // здесь аналогично
   end:if
   // здесь не генерируется jmp обратно, потому что управление этой точки никогда не достигает
end:while

Вот такие кейсы работали. А если внутри был еще один if, то уже нет. Доделал рекурсивную передачу информации о целевой метке перехода.

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

Для сборки проекта сейчас используется bash-портянка на 600 строк:

Выглядит это так себе. Да и зависеть от bash, у которого бинаник в 10 раз больше, чем бинарник моего компилятора — так себе. Компилятор self-hosted, и сборочную систему тоже хотелось бы self-hosted.

Тут я вижу два пути.

1. Взять какой-нибудь готовый небольшой интепретатор встраиваемого языка. Например, lil. Это TCL-подобный ЯП, с single-file реализацией на 3500 строк сишного кода. Переписать его на Qod. И на этом скриптовом ЯП написать сборочные скрипты для проекта.

2. Для Qod дописать недостающие сейчас библиотеки. Например, для работы со строками, списками и т.п. И реализовать «сборочный скрипт» прямо на самом Qod. Точнее это уже будет не скрипт, а компилируемая программа.

В обоих случаях круг замкнётся. Не будет зависимости от bash, make и любых сторонних вещей, кроме ассемблера. Для проекта нужен будет только fasm и предыдуший собранный qodc.

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

Компилятор self-hosted, и сборочную систему тоже хотелось бы self-hosted

нужен будет предыдуший собранный qodc

Но.. зачем? Ну откуда эта тяга первым делом сделать поделку вещью в себе?

Опыт раста ничему не учит? Его в том числе за это критикуют. Бутстрапить раст – сплошной неприятный опыт.

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

Ну вот посмотри, что именно написано в bash-скрипте:

  • Компиляция цепочкой нескольких вариантов компилятора под две платформы и проверка, что повторная компиляция самого себя порождает побайтово идентичный результат при совпадении режимов компиляции.
  • Запуск тестов.
  • Компиляция примеров программ под две платформы.
  • Запуск valgrind, чтобы сделать профилирование.
wandrien ★★★
() автор топика
Ответ на: комментарий от wandrien

Что касается оптимизации кода, то далее в моих планах:

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

Пример компилируемой функции:

void DictAddToNamespace(word P; word pNamespace)
	when P >= nDict:
		StopInternal(__FILE__, __LINE__);
	when Dict[P].pNamespace != nDICT:
		StopInternal(__FILE__, __LINE__);
	when Dict[P].pNext != nDICT:
		StopInternal(__FILE__, __LINE__);
	when Dict[pNamespace].pFirst != nDICT & Dict[pNamespace].pLast == nDICT:
		StopInternal(__FILE__, __LINE__);
	when Dict[pNamespace].pFirst == nDICT & Dict[pNamespace].pLast != nDICT:
		StopInternal(__FILE__, __LINE__);
	when pNamespace >= nDict:
		StopInternal(__FILE__, __LINE__);

	Dict[P].pNamespace = pNamespace;
	if Dict[pNamespace].pLast < nDICT then
		Dict[Dict[pNamespace].pLast].pNext = P;
		Dict[pNamespace].pLast = P;
	else
		Dict[pNamespace].pFirst = P;
		Dict[pNamespace].pLast = P;
	end:if
end

Результат компиляции:

@10103:                                  ; ## DictAddToNamespace ##
        push    EBP
        mov     EBP,  ESP
                                         ; #line compiler_dict.qdi:280
        mov     EAX,  dword [EBP+8]
        cmp     EAX,  dword [@@DATA+9977192]
        jae     @10815
                                         ; #line compiler_dict.qdi:282
        imul    EAX,  92
        mov     EDX,  dword [@@DATA+EAX+777200]
        cmp     EDX,  100000
        jne     @10816
                                         ; #line compiler_dict.qdi:284
        mov     ECX,  dword [@@DATA+EAX+777204]
        cmp     ECX,  EDX
        jne     @10817
                                         ; #line compiler_dict.qdi:286
        mov     EBX,  dword [EBP+12]
        imul    EBX,  92
        mov     EAX,  dword [@@DATA+EBX+777208]
        cmp     EAX,  EDX
        je      @10818
        mov     EAX,  dword [@@DATA+EBX+777212]
        cmp     EAX,  EDX
        je      @10819
@10818: 
                                         ; #line compiler_dict.qdi:288
        mov     EAX,  dword [EBP+12]
        imul    EAX,  92
        mov     EDX,  dword [@@DATA+EAX+777208]
        cmp     EDX,  100000
        jne     @10820
        mov     ECX,  dword [@@DATA+EAX+777212]
        cmp     ECX,  EDX
        jne     @10821
@10820: 
                                         ; #line compiler_dict.qdi:290
        mov     EAX,  dword [EBP+12]
        cmp     EAX,  dword [@@DATA+9977192]
        jae     @10822
                                         ; #line compiler_dict.qdi:293
        mov     EDX,  dword [EBP+8]
        imul    EDX,  92
        mov     dword [@@DATA+EDX+777200], EAX
                                         ; #line compiler_dict.qdi:294
        imul    EAX,  92
        mov     ECX,  dword [@@DATA+EAX+777212]
        cmp     ECX,  100000
        jae     @10823
                                         ; #line compiler_dict.qdi:295
        imul    ECX,  92
        mov     EBX,  dword [EBP+8]
        mov     dword [@@DATA+ECX+777204], EBX
                                         ; #line compiler_dict.qdi:296
        mov     dword [@@DATA+EAX+777212], EBX
        jmp     @10824
@10823: 
                                         ; #line compiler_dict.qdi:298
        mov     EAX,  dword [EBP+12]
        imul    EAX,  92
        mov     EDX,  dword [EBP+8]
        mov     dword [@@DATA+EAX+777208], EDX
                                         ; #line compiler_dict.qdi:299
        mov     dword [@@DATA+EAX+777212], EDX
@10824: 
                                         ; #line qodc.qd:364
        pop     EBP
        ret     8
@10815: 
                                         ; #line compiler_dict.qdi:281
        push    dword 281
        push    dword @@ROLITERALS+9730
        call    @10071                   ; StopInternal
@10816: 
                                         ; #line compiler_dict.qdi:283
        push    dword 283
        push    dword @@ROLITERALS+9730
        call    @10071                   ; StopInternal
@10817: 
                                         ; #line compiler_dict.qdi:285
        push    dword 285
        push    dword @@ROLITERALS+9730
        call    @10071                   ; StopInternal
@10819: 
                                         ; #line compiler_dict.qdi:287
        push    dword 287
        push    dword @@ROLITERALS+9730
        call    @10071                   ; StopInternal
@10821: 
                                         ; #line compiler_dict.qdi:289
        push    dword 289
        push    dword @@ROLITERALS+9730
        call    @10071                   ; StopInternal
@10822: 
                                         ; #line compiler_dict.qdi:291
        push    dword 291
        push    dword @@ROLITERALS+9730
        call    @10071                   ; StopInternal
wandrien ★★★
() автор топика
Последнее исправление: wandrien (всего исправлений: 1)
Ответ на: комментарий от anonymous

Есть ЯП, которые любят за как бы это сказать «операторные скобки».
А есть ЯП, которые удобны для разработки алгоритмов.

Сделайте свой ЯП удобным для разработки алгоритмов и прикрутите к нему ABI.

Чего ныне нет, так хорошей кроссплатформенной экосистемы.
Библиотек полно и они безусловно нужны, но они обычно не кроссплатформенны.
Да и библиотеки ОС обычно «прибиты гвоздями» к ОС.
Это весьма плохо.
Работа с данными не должна быть «прибита гвоздями» к ОС.

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

все виды операторных скобок

возможно, вам не хватает по настоящему продвинутого универсального (но вместе с тем минималистично синтаксически совместимого с) препроцессора навроде gpp

вот кстати, чем-то напомнило HLA aka High Level Assemly от Randall Hyde «Art Of Assembly» и пурбасик со встроенным фасмом :))

HLA – довольно любопытный артефакт. синтаксис паскале/ада/cowgol/algol подобный, как у ТС. но «высокоуровневый». но всё же ассемблер.

из «высокоуровнего» кода выглядещего синтаксически как паскаль (но семантически ближе к С++, там даже исключения и объекты и шаблоны и STL местная имеется ;))) – но всё же ассемблер, в духе Algol-W или Euclid, Euler

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

=> генерируется «низкоуровневый» на обычном ассемблере: fasm, fasm-g, nasm, gas, свой кодогенератор hla-be, умеющий линковаться сразу в бинарники.

есть frame nodisplay/c naked и прочие calling convention прямо отдельной директивой. то есть, никто не заставляет cdecl, можно передавать сразу в регистрах.

что выгодно отличает HLA от например masm (PDP-11 masm с macro64) – так это довольно высокоуровневый макропроцессор, препроцессор.

например, есть рефлексия и CTFE функции.

вообще да, вот в PL1 например был довольно продвинутый препроцессор в смысле CTFE. в книге Эберхарда Штурма есть примеры, например, реализация REXX PARSE конструкции чисто на препроцессоре PL1 VisualAge под полуось. чисто во время CTFE, compile-time function execution.

то есть, можно сформировать любую строчку и «подать её к конпеляции» пользуясь всей стандартной библиотекой PL1 в препроцессоре.

в этом смысле, пример реализации из стандартной библиотеки HLA, принтф форматного вывода – это пример реализации примерно как std.fmt в новомодных стандартах С++ по образу реализованного в D2 из std.algorithm вместо буста – проверка шаблона насчёт корректности типов и количества аргументов во время конпеляции.

в HLA эта рефлексия и интроспекция сделана вручную, примерно как в C++: есть символьная таблица, есть подобный вариантному тип и далее эти jmp table реализуют через CTFE код макросами.

в целом, препроцессор с CTFE в HLA достаточно мощный, например, на нём же реализована объектная подсистема и исключения.

то есть, в некоторых ассемблерах сам по себе препроцессор достаточно мощный, я бы сказал мощнее чем в С/С++.

а так, с точки зрения синтаксического сахара – например, ассемблеры uasm/asmc (jwasm, переписанный на с++) – поддерживают сишный синтаксис (транслируется макросами в cdecl вызов, наподобие masm-овского invoke)

частично и C++ ABI с non-virtual methods, virtual methods с манглингом.

собственно, в примерах QtD привязок D2/D1 к Qt были сгенерированы манглируемые имена для линковки с С++. тот автор линковал Qt C++ вместе с D функциями через манглинг через CTFE через рефлексию, но во время компиляции. также он вызывал C++ ABI из форта.

аналогично можно вызываться и из ассемблера.

anonymous
()