Quais os “hazards” presentes no código abaixo?
LW R2, 0(R1)
ADD R3, R2, R4
SW R3, 0(R1)
LW R2, 0(R1)
ADD R3, R2, R4
SW R3, 0(R1)
Passo 1
O enunciado dá três instruções em sequência (em linguagem estilo MIPS):
A pergunta é: quais são os “hazards” (dependências/conflitos) presentes nesse trecho de código?
Passo 2
Antes de identificar, vamos definir de forma curta e clara os tipos de hazards que podem aparecer:
- RAW (read after write): uma instrução lê um registrador que foi escrito por uma instrução anterior — dependência verdadeira de dados. Ex.: instr1 escreve
, instr2 lê . - WAR (write after read): uma instrução escreve um registrador que foi lido por uma instrução anterior — anti-dependência.
- WAW (write after write): duas instruções escrevem no mesmo registrador — dependência de saída.
- Structural hazard: duas fases querem usar o mesmo recurso (por ex., memória) ao mesmo tempo.
- Memory-order hazard: acessos à memória (loads/stores) ao mesmo endereço podem gerar conflitos de ordenação que mudam o resultado do programa se reordenados.
Agora com essas definições, vamos olhar as instruções.
Passo 3
Para raciocinar com segurança, vamos assumir o pipeline clássico de 5 estágios (MIPS):
Importante saber quando um resultado fica disponível:
- Para uma instrução ALU (ADD): o resultado é calculado em EX (no final de EX) e normalmente pode ser encaminhado (forwarded) para a EX de instruções seguintes imediatamente.
- Para uma instrução de load (LW): os dados da memória ficam disponíveis somente no final do estágio MEM; o write-back para o registrador ocorre em WB. Por isso há o famoso “load-use” problem: a instrução seguinte que usa o registrador carregado pode precisar do dado antes dele estar disponível.
Passo 4
Aqui temos claramente uma dependência RAW: a segunda instrução (ADD) lê
que foi escrito pela primeira (LW). É exatamente o caso clássico “load-use”.
Explicando com tempo de pipeline (esquemático simplificado):
- A instrução
faz o acesso à memória no estágio MEM; o dado só chega no fim de MEM. - A instrução seguinte
, sem qualquer atraso, exigiria já no início do seu estágio EX.
Na linha do tempo isso dá problema porque o EX da ADD ocorre no mesmo ciclo em que o MEM da LW está sendo executado — o dado do load só fica disponível ao final desse ciclo, tarde demais para a EX da ADD. Logo: - Esse é um RAW do tipo load-use que, na pipeline clássica, requer inserir uma bolha (stall) de 1 ciclo se só houver forwarding normal: não há como encaminhar o valor do LW a tempo para a EX da instrução imediatamente seguinte.
Passo 5
Resumo prático: entre o
e a primeira instrução que usa o registrador carregado é necessário, normalmente, inserir
ciclo de bolha (um nop) se o hardware só tem o encaminhamento usual. Alternativas:
- Se houver forwarding especial de dados vindo do final de MEM para o início de EX da instrução seguinte (alguns microarquitetos projetam isso), poderia mitigar, mas o caso típico é: load → use imediato = 1 stall.
Uma solução habitual no código do compilador / do programador é inserir uma instrução independente entre o LW e o ADD para preencher essa bolha.
Passo 6
Aqui a segunda dependência é também RAW: a instrução
precisa do conteúdo de
(valor a armazenar) e esse
é produzido pelo
.
Agora o comportamento prático:
- ADD calcula
no final do estágio EX. Muitas implementações de pipeline suportam encaminhamento (forwarding) do resultado da ALU para usos imediatos da instrução seguinte, de modo que a instrução seguinte que precisa desse valor em EX pode recebê‑lo sem stall. - Para um store, o dado a ser escrito na memória normalmente percorre o pipeline até o estágio MEM, onde a escrita ocorre. Mesmo quando o store lê o registrador em ID, o hardware pode encaminhar o valor da ADD para o estágio apropriado (MEM) do store — portanto, esse RAW entre ADD e SW é, em geral, resolvível por forwarding e não exige um stall em arquiteturas com forwarding completo.
Conclusão prática para este par: RAW em
, mas geralmente não precisa de stall se houver forwarding; caso contrário, exigiria stall(s).
Passo 7
Note que tanto o
quanto o
usam a mesma base/offset
— ou seja, ambos acessam a mesma posição de memória. Isso implica:
- Há um potencial de hazard de ordem de memória (memory-order hazard): o store posterior escreve na mesma posição que o load anterior leu. Na ordem do programa o load vem antes do store, então a semântica correta exige que a leitura ocorra antes da escrita (se o pipeline reordenar ou buffers permitirem que o store seja efetuado cedo, isso altera o resultado).
- Em implementações simples que mantêm a ordem de memória do programa e não executam stores precocemente, não surge um problema extra além do que já tratamos. Mas arquiteturas com store buffers, write buffering ou reordenação têm que garantir que não vão escrever antes de a leitura precedente ter sido completada.
Além disso, se o processador usar uma única memória para instrução e dados (modelo Von Neumann simples) e não tiver portas separadas, poderia haver um structural hazard se uma instrução precisasse fazer fetch enquanto outra faz access à memória de dados no mesmo ciclo. Em implementações MIPS clássicas isso é evitado por memórias separadas (I/D).
Passo 8
-
sobre : RAW load-use — normalmente requer ciclo de stall (ou inserir uma instrução independente entre LW e ADD). Forwarding normal não resolve esse caso a tempo. -
sobre : RAW — geralmente resolvido por forwarding (sem stall) em pipelines com encaminhamento completo. - Acesso à mesma posição
: potencial hazard de ordem de memória; exige que a implementação preserve a ordem (ou que sejam tomadas precauções se houver store buffering). Não é um RAW/WAW/WAR de registrador, é uma dependência de memória que pode exigir cuidados de microarquitetura.
Resposta
RAW entre
e
sobre
— load-use hazard (exige normalmente
ciclo de stall).
RAW entre e
sobre
— dependência resolvível por forwarding (normalmente sem stall).
Acesso à mesma posição (load e store) — potencial hazard de ordenação de memória (precisa garantir que o load ocorra antes do store).
RAW entre
Acesso à mesma posição