Linguagens de alto nível têm particularidades que, ainda que comumente entendidas como problemáticas, auxiliam em muito no entendimento do hardware. Por consequência, seu aprendizado tem muito valor. Recentemente, deparei-me com a necessidade de aprender Assembly, e é a respeito disso que busco instruir, mais para ensinar a mim mesmo que a um terceiro, mesmo porque estou longe de entender o suficiente do tópico para fazê-lo.

Antes de mais nada, é preciso compreender que a declaração de variáveis, ou mesmo operações matemáticas simples, como adição, multiplicação e potenciação, não são exatamente processos diretos em Assembly. Aqui, utilizamos uma notação que se apresenta como $ + letra particular + número. Isto se torna mais evidente com a apresentação de conceitos fundamentais, que ilustrarão a formatação. Para dar início à explicação, instruo-os, aqui, quanto aos registradores, tanto temporários quanto fixos. Registradores são, em analogia direta, similares a um RNA; são transportadores. Informações, em um computador, são armazenadas em um endereço de memória específico (e, por enquanto, não convém explorarmos esta noção, que, no entanto, contém um título intuitivo, e, por isso, cumpre não nos aprofundarmos, de momento, nela), mas, como revelei antes, o Assembly é incapaz de, nestes próprios endereços de memória, executar as operações matemáticas básicas, e, em verdade, qualquer procedimento. Para isso, portanto, é preciso, antes, copiar as informações armazenadas no endereços de memória e colá-las em registradores, caixinhas que servirão, estas, sim, aos procedimentos requeridos. Exemplos de registradores são, para variáveis que, noutra linguagem, seriam a, b, c, $s0, $s1, $s2, respectivamente. Como vêem, o Assembly utiliza $s para registradores padrões. Quanto aos temporários, àqueles que simplesmente servirão de placeholders em um eventual processo aritmético, a linguagem utiliza $t0, $t1, $t2, [...]; trata-se de $t, portanto.

Uma certa confusão pode surgir quanto à diferenciação entre estes dois tipos de registradores, mas um exemplo simples enterra qualquer dúvida de uma vez por todas. Se, por exemplo, em um exercício nos é dado a, alocado em $s0, e queremos fazer a + 1, faríamos addi $t0, $s0, 1. ¹ O registrador temporário, que vem primeiro, guarda o resultado desta operação, e não reporta a uma variável propriamente dita, mas, antes, é a entranha do processo aritmético. Para subtrair, naturalmente, utilizamos sub, que, tal como add, recebe três parâmetros separados por vírgula.

A partir deste ponto, conseguimos declarar variáveis, utilizar registradores temporários e fazer operações básicas. Surge o próximo desafio: vetores. Estes exigirão o uso de dois comandos novos, lw e sw, cuja formatação será exposta a seguir. Lê-se, respectivamente, "load word" e "save word". Estes fazem o papel de resgatar, como disse antes, as informações da memória para registradores de qualquer tipo. A formatação, em que se altera apenas lw <=> sw, se dá com lw $t0, [distância da base](registrador que contém o endereço inicial; base). Com "base", refiro-me ao endereço de memória original da informação; com "distância da base", reporto ao fato de um vetor nada mais ser que uma série de informações sequenciais. Neste sentido, V[10], por exemplo, se localiza no endereço V[0], base, + 10 espaços (como cada palavra, em Assembly, ocupa 4 bytes, o offset, a distância, seria 10 * 4, 40). V[10], no exemplo, seria carregado com lw $t0, 40($zero), porque, aqui, não foi explicitado o endereço-base de V[0]. Se, por exemplo, V[0] estivesse em $s6, aí o carregamento seria dado com lw $t0, 40($s6).

Sabendo, agora, pescar a informação em qualquer posição de um vetor, podemos fazer a conversão de códigos que, noutra linguagem, o utilizem, e, por óbvio, utilizá-los também. Surge um novo problema: condicionais. Digamos que, em mãos, tenhamos o código a seguir, onde se pressupõe que vetor V esteja associado ao registrador base $s6, e as variáveis a, b, c, d estejam associadas aos registradores $s0, $s1, $s2, $s3:

if(V[8] >= 12) { a = a + b + c; V[8] = V[8] + a; }else { a = a – b – c; V[8] = V[8] + a; }

Para convertê-lo em Assembly, é preciso estar ciente dos códigos de comparação dos quais a linguagem dispõe. Primeiro de tudo, vamos aplicar o que já sabemos e carregar em um registrador temporário o elemento na posição 8 do vetor V:

lw $t0, 32($s6)

Em seguida, faremos a comparação, e aqui entram dois conceitos, ou comandos, fundamentais: beq & bne (igualdade e não-igualdade, respectivamente). Se, por exemplo, dizemos bne $s0, $s1, 12, estamos dizendo, em português, "se $s1 e 12 NÃO forem iguais (bne), $s0 será 1; se forem iguais, $s0 será 0". Isto nos é útil porque, na ausência de comparações diretas, podemos retirar do valor de $s0 a comparação entre os dois valores utilizados como parâmetro. Claro, maior e menor se mantêm uma questão; para isso, temos slt (set on less than), que faz um a < b e retorna 1 ou 0 para caso seja menor e não seja menor respectivamente. Juntando ambos, conseguimos fazer indiretamente a comparação, sem uso de >, <, ≥ ou ≤. Para o caso do nosso código de exemplo, temos if(V[8] ≥ 12). Isto, claro, é análogo a V[8] NÃO ser menor que 12. Utilizaremos justamente esta identidade no bne. Estando V[8] em $t0, como definido na nossa primeira linha de código, utilizamos bne:

lw $t0, 32($s6) slt $t1, $t0, 12 bne $t1, $zero THEN:

Resolvido. Podemos, agora, só construir o resto do código com as noções básicas já ensinadas:

lw $t0, 32($s6) slt $t1, $t0, 12 bne $t1, $zero THEN sub $s0, $s0, $s1 sub $s0, $s0, $s2 add $t0, $t0, $s0 sw $t0, 32($s6) j END THEN: add $s0, $s0, $s1 add $s0, $s0, $s2 add $t0, $t0, $s0 sw $t0, 32($s6) END:

Pronto! Conseguimos reproduzir if's e else's perfeitamente em Assembly. Restam duas duas dificuldades parciais antes de termos em mãos um domínio completo sobre a primeira parte da linguagem: ainda não conseguimos fazer loops com while e nem utilizar funções, incluindo, aqui, o caráter recursivo destas.

Por fim, abordemos os loops. Eles substituem o "THEN/END" dos condicionais por "Loop/EndLoop", mas a lógica é idêntica. Vamos, por exemplo, converter o código a seguir para Assembly:

while(V[0] <= a){ V[0] = V[0] + b; }

Se tornará, em MIPS: lw $t0, 0($s6) Loop: slt $t1, $s0, $t0 bne $t1, $zero EndLoop add $t0, $t0, $s1 sw $t0, 0($s6) j Loop EndLoop:

Falaremos sobre as funções e sua recursividade noutra ocasião. Terminamos a parte I por aqui.

[1] Em Assembly, o "add" normal não aceita imediatos (números secos, como 1, 2, 3, ...), apenas registradores; como, no exemplo, eu queria adicionar à variável o número 1, utilizei "addi" (o "i" no fim do comando reporta a "immediate", mesmo, isto é, à capacidade de encerrar, neste comando, operações com números puros).

Go back.