Какая-то странная логика у операций деления и умножения. Они почему-то работают с разными размерами и возвращают значение в фиксированные регистры. Как эффективно выделять регистры - непонятно. Посмотрел, что выдаёт GCC на выражение ((a+b+c)/(b-d))/((c-a)/(d*4-a)):
t = dword ptr -8
a = dword ptr 8
b = dword ptr 0Ch
c = dword ptr 10h
d = dword ptr 14h
push ebp
mov ebp, esp
push ebx
sub esp, 4
mov eax, [ebp+b] ; EAX := b
mov edx, [ebp+a] ; EDX := a
add edx, eax ; INC(EDX, EAX)
mov eax, [ebp+c] ; EAX := c
add eax, edx ; INC(EAX) + EDX
mov edx, [ebp+d] ; EDX := d
mov ecx, [ebp+b] ; ECX := b
mov ebx, ecx ; EBX := ECX
sub ebx, edx ; DEC(EBX, EDX)
mov [ebp+t], ebx ; t := EBX
idiv [ebp+t] ; EAX := EDX:EAX DIV [ebp+t]; EDX := EDX:EAX MOD [ebp+t];
mov ecx, eax ; ECX := EAX
mov eax, [ebp+a] ; EAX := a
mov edx, [ebp+c] ; EDX := c
mov ebx, edx ; EBX := EDX
sub ebx, eax ; DEC(EBX, EAX)
mov eax, ebx ; EAX := EBX
mov edx, [ebp+d] ; EDX := d
shl edx, 2 ; EDX := EDX*4
mov ebx, edx ; EBX := EDX
sub ebx, [ebp+a] ; DEC(EBX, a)
mov [ebp+t], ebx ; t := EBX
cdq ; EDX:EAX := EAX
idiv [ebp+t] ; EAX := EDX:EAX DIV [ebp+t]; EDX := EDX:EAX MOD [ebp+t];
mov [ebp+t], eax ; t := EAX
mov eax, ecx ; EAX := ECX
cdq ; EDX:EAX := EAX
idiv [ebp+t] ; EAX := EDX:EAX DIV [ebp+t]; EDX := EDX:EAX MOD [ebp+t];
add esp, 4
pop ebx
pop ebp
retn
Выходит он просто временную переменную заводит. Регистр нельзя?
Я предполагал такую архитектуру компилятора: сначала компилятор в лоб в один проход генерирует модуль с процедурами в формате стековой машины. Кроме процедур компилятор генерирует таблицу символов и метаинформацию. Потом этот модуль даётся оптимизатору. Он выдаёт эквивалентный модуль в том же формате, но оптимизированный. Оптимизатор платформо-независим и прозрачно интегрируется в цепочку компиляции и может быть убран или заменён при необходимости. Пока оптимизатора не будет. Затем оптимизированный модуль даётся "рекомпилятору", который конвертирует процедуры из стековой машины в машинный код i386.
Рекомпилятор поддерживает стек регистров примерно так:
stack machine program:
1 2 3 4 5 6 7 + + + + + +
x86 code:
mov eax, 1 ; eax
mov ebx, 2 ; eax, ebx
mov ecx, 3 ; eax, ebx, ecx
mov edx, 4 ; eax, ebx, ecx, edx
push eax
mov eax, 5 ; s[0], ebx, ecx, edx, eax
push ebx
mov ebx, 6 ; s[0], s[1], ecx, edx, eax, ebx
add ebx, 7 ; s[0], s[1], ecx, edx, eax, ebx
add eax, ebx ; s[0], s[1], ecx, edx, eax
add edx, eax ; s[0], s[1], ecx, edx
add ecx, edx ; s[0], s[1], ecx
pop ebx ; s[0], ebx, ecx
add ebx, ecx ; s[0], ebx
pop eax ; eax, ebx
add eax, ebx ; eax
Понятно, что исходная программа легко оптимизируется перестановкой и перегруппировкой слагаемых, но она специально составлена для демонстрации переполнения регистрового стека.
Но операции умножения и деления ломают этот механизм.
Также рекомпилятор должен уметь распознавать режимы адресации, используемые в x86:
stack machine
len& 1 - 2 DIV 4 * a& + ^ x ->
C:
x = a[(len-1)/2];
direct recompilation:
mov eax, [ebp+len] ; len len&
dec eax ; len len& 1 -
shr eax, 1 ; len len& 1 - 2 DIV
shl eax, 2 ; len len& 1 - 2 DIV 4 *
mov ebx, [ebp+a] ; len len& 1 - 2 DIV 4 * a&
add eax, ebx ; len len& 1 - 2 DIV 4 * a& +
mov eax, [eax] ; len len& 1 - 2 DIV 4 * a& + ^
mov [ebp+x], eax ; ~ len len& 1 - 2 DIV 4 * a& + ^ x ->
addressing modes detection:
mov eax, [ebp+len] ; len len&
dec eax ; len len& 1 -
shr eax, 1 ; len len& 1 - 2 DIV
mov ebx, [ebp+a] ; len len& 1 - 2 DIV &a
mov eax, [eax*4+ebx] ; len len& 1 - 2 DIV 4 * a& + ^