College Online
0%

Controle de Fluxo e Memória

Módulo 2 · Aula 3 ~20 min de leitura Nível: Intermediário

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:

Assembly MIPS
# 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:

Assembly MIPS
# 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:

Assembly MIPS
# ═══ 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:

Assembly MIPS
# 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
i
Convenção de chamada Argumentos vão em $a0-$a3 (até 4). Resultado retorna em $v0-$v1. Se a função chama outra função, ela deve salvar $ra na pilha antes — senão o retorno se perde.

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.

Assembly MIPS
# 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):

Assembly MIPS
# 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
i
Leaf vs Non-leaf functions Uma leaf function (que não chama outras funções) não precisa salvar $ra na pilha. Uma non-leaf function (como fatorial recursivo) deve sempre salvar $ra, senão o 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:

Segmentos de Memória MIPS
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).

Mapeamento: Controle de Fluxo → harness.os
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

  1. 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).
  2. Escreva uma função MIPS soma_array que 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.
  3. 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.
  4. 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

Verifique seu entendimento

Por que uma função recursiva em MIPS precisa salvar $ra na pilha antes de chamar jal?

  • Porque $ra é um registrador temporário que pode ser sobrescrito por operações aritméticas
  • Porque jal sobrescreve $ra com o novo endereço de retorno, perdendo o retorno anterior
  • Porque a pilha é mais rápida que os registradores para armazenar endereços
  • Porque o hardware MIPS exige que $ra seja salvo antes de qualquer jump