Decisões e repetições

Decisões e repetições

Por padrão, a CPU executa suas instruções na ordem em que aparecem, de cima para baixo. Por si só, essa ordem nunca pode fazer uma escolha ou repetir trabalho. Um branch é como um programa sai desse caminho reto: ele testa uma condição e, quando a condição é verdadeira, direciona a CPU para continuar em um lugar diferente em vez da próxima linha. Toda decisão e todo loop vêm desse único mecanismo.

Não existe if nem for. Você os constrói a partir de labels (posições nomeadas) e branches (salto para uma posição quando uma condição é verdadeira). Um label é um nome seguido de :.

Para entender como um branch é executado, você precisa conhecer o Program Counter (PC). O PC é um registrador especial, oculto do seu código, que contém o endereço de memória da instrução sendo executada. Uma instrução de branch escreve um novo valor no PC, então a CPU busca sua próxima instrução no label de destino em vez da linha de código seguinte.

A família de desvios

Todo branch compara dois registradores e salta para o label somente quando o teste é verdadeiro. Quando o teste falha, a execução continua na próxima linha. Seis testes formam o núcleo:

Os seis desvios reais
beq a, b, label # desvia se a == b
bne a, b, label # desvia se a != b
blt a, b, label # desvia se a < b (com sinal)
bge a, b, label # desvia se a >= b (com sinal)
bltu a, b, label # desvia se a < b (sem sinal)
bgeu a, b, label # desvia se a >= b (sem sinal)

e testam igualdade e ignoram o sinal. e formam o par de ordenação, menor que e maior ou igual, e comparam os registradores como números com sinal. e fazem as mesmas comparações, mas tratam os valores como sem sinal, o que serve para endereços ou contagens que nunca são negativos.

O que significam com sinal e sem sinal? Um registrador contém 32 bits, e os mesmos bits podem ser lidos de duas maneiras. Lidos como sem sinal, eles formam um número inteiro simples de 0 até cerca de 4 bilhões. Lidos como com sinal, o bit mais alto conta como um sinal de menos, então metade dos padrões de bits representa números negativos. Os bits em si não mudam. Só muda a forma como você os lê. É por isso que existem dois conjuntos de comparações: use as com sinal (, ) para números comuns que podem ser negativos, e as sem sinal (, ) para valores que nunca são negativos, como endereços de memória.

Não existe um "branch se maior" nem "branch se menor ou igual" diretos. Você os obtém trocando os dois registradores, e o assembler fornece e como pseudo-branches que fazem a troca para você, então se torna . Mais dois atalhos: e testam um registrador contra zero, e salta sem nenhuma condição.

Somar os números de 1 a 5 usando um laço de repetição
li t0, 0 # soma = 0
li t1, 1 # i = 1
li t2, 6 # limite = 6 (repete enquanto i < 6)
loop:
bge t1, t2, done # sai do laço se i >= 6
add t0, t0, t1 # soma += i
addi t1, t1, 1 # i++
j loop # volta para verificar de novo
done:
mv a0, t0 # imprime a soma (deve ser 15)
li a7, 1
ecall
li a7, 10
ecall

Trace isso. t0 contém a soma corrente (começa em 0), t1 contém o contador i (começa em 1), e t2 contém o limite 6. A cada passagem por loop: sai do loop quando i chega a 6. Caso contrário, adiciona i à soma, aumenta i em um, e salta de volta para testar novamente. O loop soma 1, 2, 3, 4 e 5, e done imprime o total, 15.

Loop com um branch
loop:
bge t1, t2, done
add t0, t0, t1
addi t1, t1, 1
j loop
i (t1)1
sum (t0)0
simnãot1 < 6?sum += t1t1 += 1concluído

bge compara t1 com t2 para decidir se o loop continua.

A figura abaixo marca a instrução que atualiza o contador e a que atualiza a soma corrente, destacando-as do resto do loop. avança o contador, e o que se segue adiciona o novo valor do contador à soma. O último quadro mostra o teste que encerra o loop. Quando t1 se torna 6, transfere o controle para done em vez de deixar o corpo rodar novamente. Esse teste é o que separa uma passagem que se repete da passagem que para.
💡
Avance pelo programa com Step em vez de Run. Conforme você avança, t1 aumenta e o program counter (pc) salta de volta para loop. Toda vez que pc retorna a loop, a condição do branch era falsa, então o loop roda novamente.
⚠
Se a condição de saída estiver errada, o loop nunca termina. O simulador interrompe um loop descontrolado, mas a lógica do branch é sua para acertar.