Controle de Fluxo e Memória
Vídeo da aula estará disponível em breve
Instruções de Desvio Condicional
Sem desvios, um processador executaria instruções sequencialmente para sempre. Desvios condicionais (branches) permitem que o programa tome decisões — o equivalente hardware do if/else em linguagens de alto nível.
MIPS oferece duas instruções de branch:
# beq: Branch if Equal (desvia se $rs == $rt)
beq $s1, $s2, Label # Se $s1 == $s2, pula para Label
# Senão, continua na próxima instrução
# bne: Branch if Not Equal (desvia se $rs != $rt)
bne $s1, $s2, Label # Se $s1 != $s2, pula para Label
# Como funciona o endereçamento:
# beq é I-type: | opcode | rs | rt | offset (16 bits) |
# O offset é relativo ao PC+4, em WORDS (não bytes):
# Endereço alvo = (PC + 4) + (offset × 4)
# Alcance: ±2^15 words = ±128 KB a partir da instrução atual
Para comparações mais complexas (menor que, maior que), combinamos slt com beq/bne:
# Implementar "if ($s1 < $s2) goto Label"
slt $t0, $s1, $s2 # $t0 = 1 se $s1 < $s2
bne $t0, $zero, Label # Se $t0 != 0 (ou seja, $s1 < $s2), desvia
# Pseudo-instrução "blt" faz isso automaticamente:
blt $s1, $s2, Label # O assembler gera slt + bne
# Outras pseudo-instruções de comparação:
bge $s1, $s2, Label # Branch if Greater or Equal
bgt $s1, $s2, Label # Branch if Greater Than
ble $s1, $s2, Label # Branch if Less or Equal
Traduzindo if/else e Loops
Vejamos como estruturas de controle de alto nível se traduzem para Assembly:
# ═══ IF / ELSE ═══
# C: if (i == j) f = g + h; else f = g - h;
# Mapeamento: f=$s0, g=$s1, h=$s2, i=$s3, j=$s4
bne $s3, $s4, Else # Se i != j, vai para Else
add $s0, $s1, $s2 # f = g + h (caso if)
j Exit # Pula o else
Else: sub $s0, $s1, $s2 # f = g - h (caso else)
Exit: # Continua aqui
# ═══ WHILE LOOP ═══
# C: while (save[i] == k) i += 1;
# Mapeamento: i=$s3, k=$s5, save base=$s6
Loop: sll $t1, $s3, 2 # $t1 = i * 4 (offset em bytes)
add $t1, $t1, $s6 # $t1 = endereço de save[i]
lw $t0, 0($t1) # $t0 = save[i]
bne $t0, $s5, Exit # Se save[i] != k, sai do loop
addi $s3, $s3, 1 # i += 1
j Loop # Volta ao início do loop
Exit:
# ═══ FOR LOOP ═══
# C: for (i = 0; i < 10; i++) sum += A[i];
# Mapeamento: sum=$s0, i=$s1, A base=$s2
add $s1, $zero, $zero # i = 0
addi $t2, $zero, 10 # $t2 = 10 (limite)
For: slt $t0, $s1, $t2 # $t0 = (i < 10)?
beq $t0, $zero, Done # Se i >= 10, sai
sll $t1, $s1, 2 # $t1 = i * 4
add $t1, $t1, $s2 # $t1 = &A[i]
lw $t3, 0($t1) # $t3 = A[i]
add $s0, $s0, $t3 # sum += A[i]
addi $s1, $s1, 1 # i++
j For # Volta
Done:
Jump e Chamada de Subrotinas
Saltos incondicionais e chamadas de função são essenciais para modularizar código:
# j: Jump incondicional (J-type)
j Label # PC = endereço de Label
# Sempre desvia, sem condição
# jal: Jump and Link (chamada de função)
jal FuncLabel # $ra = PC + 4 (salva endereço de retorno)
# PC = endereço de FuncLabel
# jr: Jump Register (retorno de função)
jr $ra # PC = $ra (volta para quem chamou)
# ═══ Exemplo completo: chamada de função ═══
# C: int result = dobrar(5);
# int dobrar(int x) { return x * 2; }
main:
addi $a0, $zero, 5 # Argumento: x = 5
jal dobrar # Chama dobrar($a0), salva retorno em $ra
move $s0, $v0 # result = valor retornado
# ... continua main
dobrar:
sll $v0, $a0, 1 # $v0 = $a0 * 2 (= x * 2)
jr $ra # Retorna para quem chamou
Load/Store e Endereçamento de Memória
MIPS é uma arquitetura load/store: a única forma de acessar a memória é com instruções dedicadas. Operações aritméticas não podem ler da memória diretamente.
# lw: Load Word (carrega 4 bytes da memória para registrador)
lw $t0, 0($s1) # $t0 = Memória[$s1 + 0]
lw $t0, 4($s1) # $t0 = Memória[$s1 + 4]
lw $t0, -8($s1) # $t0 = Memória[$s1 - 8]
# sw: Store Word (armazena 4 bytes do registrador na memória)
sw $t0, 12($s1) # Memória[$s1 + 12] = $t0
# lb / sb: Load/Store Byte (1 byte, com extensão de sinal)
lb $t0, 0($s1) # $t0 = byte em Memória[$s1], estendido com sinal
sb $t0, 0($s1) # Memória[$s1] = byte inferior de $t0
# lbu: Load Byte Unsigned (sem extensão de sinal)
lbu $t0, 0($s1) # $t0 = byte em Memória[$s1], zero-extended
# lh / sh: Load/Store Halfword (2 bytes)
lh $t0, 0($s1) # Carrega 2 bytes com extensão de sinal
sh $t0, 0($s1) # Armazena 2 bytes
# Modo de endereçamento: Base + Offset
# Endereço efetivo = Registrador_base + Constante_offset
# Formato I-type: | opcode | rs(base) | rt(dado) | offset(16 bits) |
Stack Frame e Convenção de Chamada
Quando uma função precisa salvar registradores (porque chama outra função, ou porque usa mais registradores do que os disponíveis), ela usa a pilha (stack):
# Layout da memória MIPS:
# 0x7FFFFFFF ┌─────────────────┐ ← Endereço mais alto
# │ Stack │ ← Cresce para BAIXO ($sp)
# │ ↓ │
# │ │
# │ ↑ │
# │ Heap │ ← Cresce para CIMA
# ├─────────────────┤
# │ Dados estáticos│ ← $gp aponta aqui
# ├─────────────────┤
# │ Texto │ ← Código do programa
# 0x00400000 └─────────────────┘ ← PC começa aqui
# ═══ Função que chama outra função (non-leaf) ═══
# int fatorial(int n) {
# if (n < 1) return 1;
# return n * fatorial(n - 1);
# }
fatorial:
# Prólogo: alocar espaço na pilha e salvar registradores
addi $sp, $sp, -8 # Alocar 8 bytes (2 words) na pilha
sw $ra, 4($sp) # Salvar endereço de retorno
sw $a0, 0($sp) # Salvar argumento n
# Corpo: if (n < 1) return 1
slti $t0, $a0, 1 # $t0 = (n < 1)?
beq $t0, $zero, Recurse # Se n >= 1, recursão
# Caso base: return 1
addi $v0, $zero, 1 # $v0 = 1
addi $sp, $sp, 8 # Desalocar pilha
jr $ra # Retorna
Recurse:
addi $a0, $a0, -1 # n = n - 1
jal fatorial # Chama fatorial(n-1)
# Restaurar n e $ra
lw $a0, 0($sp) # Restaura n original
lw $ra, 4($sp) # Restaura endereço de retorno
addi $sp, $sp, 8 # Desalocar pilha
# $v0 = n * fatorial(n-1)
mult $a0, $v0
mflo $v0
jr $ra # Retorna
jal interno sobrescreve o endereço de retorno original.
Mapa de Memória do MIPS
O espaço de endereçamento de 32 bits do MIPS (4 GB) é dividido em segmentos:
Endereço Segmento Conteúdo
──────────────── ──────────────── ──────────────────────────────
0x7FFF FFFC Stack Variáveis locais, $ra, $fp
↓ (cresce p/ baixo) salvos. $sp aponta o topo.
0x1000 8000 Dados dinâmicos Heap: malloc/new. Cresce p/ cima.
0x1000 0000 Dados estáticos Variáveis globais. $gp = 0x10008000.
0x0040 0000 Texto Código do programa (.text).
0x0000 0000 Reservado Usado pelo SO.
Restrições de alinhamento:
- Word (lw/sw): endereço múltiplo de 4
- Halfword (lh/sh): endereço múltiplo de 2
- Byte (lb/sb): qualquer endereço
Violação de alinhamento causa exceção de hardware!
No harness.os
Controle de fluxo em assembly é idêntico ao controle de workflows no harness.os: decisões condicionais (beq/bne = rules), saltos (j = goto step), chamadas de subrotina (jal = delegate to agent).
Instrução MIPS Equivalente no harness.os
────────────────────────────── ────────────────────────────────────
beq/bne (branch condicional) Regra condicional: "SE testes passaram
→ deploy; SENÃO → fix"
j (jump incondicional) goto step: pular para um passo
específico do workflow
jal (jump and link) Delegação: chamar um agente filho
(salvando contexto de retorno)
jr $ra (return) Retorno do agente: devolver resultado
+ resumo para o agente pai
Stack ($sp) Context stack: cada delegação empilha
um nível de contexto; retornos
desempilham
Fatorial recursivo Agente que delega para si mesmo:
cada camada com seu contexto na
pilha, até o caso base
Homework
- Traduza para MIPS:
if (a > b) max = a; else max = b;onde a=$s0, b=$s1, max=$s2. Use apenas instruções reais (slt + bne/beq, não pseudo-instruções). - Escreva uma função MIPS
soma_arrayque recebe em $a0 o endereço base de um array e em $a1 o número de elementos, e retorna a soma em $v0. Use um loop com lw para percorrer o array. - Desenhe o estado da pilha (stack) durante a execução de
fatorial(3). Mostre os valores de $sp, $ra e $a0 salvos em cada nível de recursão. - No harness.os, quando um agente orquestrador delega para 3 agentes em paralelo, qual é o equivalente em termos de chamadas de subrotina? Compare com threads vs chamadas sequenciais.
Resumo
- beq e bne são os desvios condicionais nativos do MIPS; comparações complexas usam slt + branch
- if/else, while e for se traduzem em combinações de branch + jump + labels
- jal salva o endereço de retorno em $ra; jr $ra retorna da função
- Load (lw, lb) e Store (sw, sb) são as únicas formas de acessar memória em MIPS
- Endereçamento base+offset: endereço efetivo = registrador + constante de 16 bits
- A pilha cresce para baixo ($sp diminui); funções non-leaf devem salvar $ra na pilha
Verifique seu entendimento
Por que uma função recursiva em MIPS precisa salvar $ra na pilha antes de chamar jal?