Redescobrindo Assembly

Como variáveis locais são suportadas em Assembly

O conceito de “variáveis locais”, como estamos habituados em linguagens de programação de mais alto nível, não tem equivalência direta em assembly.

IMPORTANTE: Todos os códigos em assembly compartilhados nessa série omitem verificações e, para fins de simplicidade, não estão otimizados.

Na prática, o desafio é determinar, dentre as alternativas disponíveis, a forma mais eficiente para armazenar e recuperar, de alguma forma, dados na memória tornando-os acessíveis para processamento pelo código. Escolhas infelizes geralmente implicam em performance mais pobre.

#include <iostream>

int summarize(const int value)
{
	auto result = 0;
	for (auto i = 1; i <= value; i++)
	{
		result += i;
	}
	return result;
}

int main() {
	std::cout << "summarize(10) = " << summarize(10) << std::endl;
}

O mais eficiente costuma ser utilizar os registradores do processador. Embora eles tenham pouca capacidade, estão montados fisicamente junto ao processador o que torna sua velocidade de acesso imbatível.

Os compiladores geralmente utilizam os registradores para, por exemplo, armazenar valores de contadores e acumuladores em loops.

	.model flat,c
        .code

summarize_ proc

	push ebp
	mov ebp, esp
	push ebx

	xor eax, eax    ; result = 0;
	mov ebx, 1
	mov ecx,[ebp+8] ; ecx = value

	jmp check_for_is_complete

for_body:
        add eax, ebx
	inc ebx

check_for_is_complete:
	cmp ebx, ecx
	jg finish
	jmp for_body

finish:
	pop ebx
	pop ebp
	ret

summarize_ endp

	end 

Importante que lembremos que os registradores que utilizamos em nossas funções são os mesmos disponíveis para todo o código. Dependendo da convenção adotada, alguns registradores podem ser voláteis ou não-voláteis. Registradores não-voláteis devem ter seu valor restaurado sempre antes da função retornar (como ebx e ebp no exemplo). Registradores voláteis podem ter seus valores modificados livremente.

Outra alternativa comum é alocar espaço na stack  para acomodar o valor das variáveis.

#include <iostream>

int summarize(const int a, const int b)
{
	const auto max = (a > b) ? a : b;
	const auto min = (a < b) ? a : b;
	return (max * (max + 1) - (min - 1) * min) / 2;
}

int main() {
	std::cout << "summarize(10) = " << summarize(100, 1) << std::endl;
}

Quando utilizamos a stack para armazenar os valores de variáveis locais, é comum “reservar” espaço logo no início da função (trecho de código conhecido como prólogo) e garantir que o espaço alocado seja liberado no final da função (trecho de código conhecido como epílogo).

A prática comum é colocar os valores das variáveis locais logo após o registrador ebp. Dessa forma, parâmetros para as funções são acessados com deslocamentos positivos e variáveis são acessadas com deslocamentos negativos.

	.model flat,c
        .code

summarize_ proc

	push ebp
	mov ebp, esp
	sub esp, 8		; allocating space on the stack for two integers
	
	mov eax,[ebp+8]		; eax = 'a'
        mov ecx,[ebp+12]	; ecx = 'b'

	cmp eax, ecx
	jle aIsMax
	mov [ebp - 8], eax	; max = a
	jmp resume_1
aIsMax:
	mov [ebp - 8], ecx	; max = b

resume_1:
	cmp eax, ecx
	jge aIsMin
	mov [ebp - 4], eax	; min = a
	jmp resume_2
aIsMin:
	mov [ebp - 4], ecx	; min = b

resume_2: 
	mov eax, [ebp - 8]  ; eax = max
	add eax, 1
	imul eax, [ebp - 8]

	mov ecx, [ebp - 4]  ; ecx = min
	sub ecx, 1
	imul ecx, [ebp - 4]

	sub eax, ecx
	cdq
	sar eax, 1

	mov esp, ebp  ; releasing local storage space
	pop ebp
	ret

summarize_ endp

	end 

Obviamente, nada disso é relevante quando estamos escrevendo código em Java, C++ ou C#. Por sorte, os compiladores fazem um ótimo trabalho escondendo esses “detalhes” do nosso dia a dia.

Objetos complexos, armazenados na heap, tem apenas seus endereço armazenado localmente (também em registradores ou na stack).

Em Resumo
  • O fato

    O conceito de "variáveis locais", como estamos habituados em linguagens de programação de mais alto nível, não tem equivalência direta em assembly. As duas abordagens mais comuns são usar os registradores do processador ou a stack para acomodar dados. Os compiladores são muito eficientes em estabelecer a abordagem ideal para cada cenário.

Mais posts da série Redescobrindo Assembly

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *