- Aprender
- Computação e TI
- Ciência da computação
- Organização e arquitetura de computadores
- Linguagem de montagem RISC-V
Linguagem de montagem RISC-V e conjuntos de instruções
IntermediárioAula 2 de 6 do curso Curso de Organização e arquitetura de computadores
O que você vai aprender
- Explicar o que é um conjunto de instruções, ler linguagem de montagem RISC-V e usar registradores, memória e constantes como operandos de instruções.
- Explicar o que é uma arquitetura do conjunto de instruçõesDescrever o conjunto de instruções como o contrato entre software e hardware, a ideia de programa armazenado e a relação entre linguagem de montagem, código de máquina e o conjunto básico RISC-V.
- Usar registradores para calcular expressõesIdentificar os 32 registradores de inteiros RISC-V e suas funções convencionais e traduzir expressões aritméticas em sequências de instruções com registradores.
- Carregar e armazenar dados na memóriaUsar endereços de bytes, tamanhos de dados, alinhamento e ordem de bytes para mover palavras, meias palavras e bytes entre a memória e os registradores, incluindo elementos de vetores.
- Trabalhar com constantes e imediatosUsar operandos imediatos para constantes pequenas e construir constantes e endereços completos de 32 bits com lui, auipc, addi e as pseudoinstruções li e la.
- Explicar o que é uma arquitetura do conjunto de instruções
- Usar instruções aritméticas, lógicas, de deslocamento e de comparação e traduzir decisões e laços em C para desvios e saltos.
- Usar instruções aritméticas, lógicas e de deslocamentoCalcular com adição, subtração, lógica bit a bit, deslocamentos e comparação menor que, incluindo estouro, interpretação com e sem sinal, máscaras e multiplicação ou divisão por potências de 2.
- Realizar desvios e saltos com instruções condicionais e incondicionaisUsar instruções de comparação e desvio, condições com e sem sinal, saltos e suas pseudoinstruções para mudar qual instrução será executada em seguida.
- Traduzir decisões e laços em C para linguagem de montagemTransformar instruções if-else, condições compostas, laços while e for e instruções switch em desvios e acompanhar o código resultante com valores reais.
- Usar instruções aritméticas, lógicas e de deslocamento
- Converter entre instruções de montagem e código de máquina de 32 bits usando os seis formatos de instrução RISC-V e explicar as razões do projeto desses formatos.
- Codificar instruções com registradores, com imediatos e de armazenamentoOrganizar os campos de instruções dos tipos R, I e S e converter instruções como add, sub, addi, deslocamentos, lw e sw em binário e hexadecimal.
- Codificar desvios, saltos e imediatos superiores e decodificar código de máquinaCodificar instruções dos tipos B, J e U com deslocamentos relativos ao pc e imediatos superiores e decodificar qualquer palavra RV32I de 32 bits de volta para linguagem de montagem.
- Codificar instruções com registradores, com imediatos e de armazenamento
- Seguir a convenção de chamada RISC-V para passar argumentos, retornar resultados e preservar registradores, usar quadros de pilha para procedimentos aninhados e recursivos e traduzir ponteiros, vetores e cadeias de caracteres em C.
- Chamar procedimentos e retornar deles usando a convenção de chamadaPassar argumentos e resultados em registradores, chamar com jal e retornar com ret, e decidir quais registradores o chamador ou o chamado deve preservar.
- Construir quadros de pilha para procedimentos aninhados e recursivosAlocar e liberar quadros de pilha, salvar o endereço de retorno e registradores em chamadas aninhadas e recursivas e localizar a pilha na organização de um programa na memória.
- Traduzir ponteiros, vetores e cadeias de caracteres em C para linguagem de montagemTransformar desreferenciamentos de ponteiros, aritmética de ponteiros, campos de estruturas, parâmetros de vetores e cadeias de caracteres terminadas em nulo em cargas, armazenamentos e cálculos de endereços.
- Chamar procedimentos e retornar deles usando a convenção de chamada
- Explicar como um compilador, um montador, um ligador e um carregador transformam código-fonte C em um programa em execução na memória, incluindo arquivos objeto, relocação e ligação dinâmica.
- Explicar como compiladores e montadores produzem arquivos objetoDescrever a hierarquia de tradução, o que compiladores e montadores fazem, diretivas do montador e símbolos, e as partes de um arquivo objeto.
- Explicar como ligadores e carregadores criam e iniciam um executávelDescrever como um ligador posiciona módulos e ajusta endereços, comparar ligação estática e dinâmica e explicar como um carregador e chamadas de sistema iniciam e encerram um programa.
- Explicar como compiladores e montadores produzem arquivos objeto
- Contrastar RISC-V com conjuntos de instruções complexos como x86 e avaliar compromissos de projeto do conjunto de instruções quanto a desempenho, tamanho do código e compatibilidade.
- Contrastar características dos projetos RISC e CISCDescrever as características que definem conjuntos de instruções RISC e CISC e comparar RISC-V com x86 quanto a operandos, codificação, modos de endereçamento, registradores e códigos de condição.
- Avaliar compromissos de projeto do conjunto de instruções quanto a desempenho, tamanho do código e compatibilidadeUsar a equação de desempenho para comparar projetos, explicar como processadores modernos tornam menos nítida a distinção entre RISC e CISC e avaliar equívocos comuns sobre conjuntos de instruções.
- Contrastar características dos projetos RISC e CISC
Guia de estudo de Linguagem de montagem RISC-V
1Instruções, registradores e memória
Um conjunto de instruções conecta um programa a um processador. Comece lendo o destino e as origens de uma instrução RISC-V e depois acompanhe os valores pelos registradores e pela memória. Endereços de bytes explicam como vetores e estruturas são acessados; campos de imediato explicam por que algumas constantes precisam de mais de uma instrução. Distinguir valores, endereços e conteúdo da memória facilita acompanhar cada linha de montagem.
1.1 O conjunto de instruções como contrato de software
Um processador executa instruções representadas por bits. Uma arquitetura do conjunto de instruções (ISA) dá a esses bits um significado compartilhado, para que o software possa solicitar as mesmas operações em implementações diferentes.
O que a ISA define
A ISA especifica instruções, registradores visíveis, tamanhos de dados, comportamento da memória e codificações de instruções. Uma instrução como add a0, a1, a2 deve produzir o resultado definido em todo processador que a suporte. Os circuitos internos e o escalonamento pertencem à microarquitetura. Dois chips podem diferir muito em velocidade e consumo de potência enquanto implementam a mesma ISA.
A compatibilidade binária também exige que o ambiente de software seja compatível. Um programa que chama um serviço do sistema operacional ou uma biblioteca depende de mais do que os significados das instruções. Convenções de registradores e organizações de dados fazem parte da interface binária de aplicação, ou ABI.
Do código legível aos bits
Para C, um compilador seleciona instruções de montagem; um montador as codifica como código de máquina. A linguagem de montagem é texto com nomes de instruções, registradores, comentários e rótulos. O código de máquina é a representação binária que o processador busca. Um rótulo como Start: nomeia o endereço de uma instrução; ele não é uma instrução executada em tempo de execução. Um comentário começa com #.
Start:
add a0, a1, a2 # a0 receives a1 + a2
O primeiro registrador é o destino. Se a1 contém 14 e a2 contém 5, a instrução escreve 19 em a0; ela mantém as origens inalteradas. Expressões mais complexas precisam de várias operações. Somar quatro valores de registradores exige três adições, com resultados intermediários mantidos em registradores.
A maioria dos nomes de instruções corresponde a uma instrução de máquina. Uma pseudoinstrução é uma abreviação do montador: mv solicita uma cópia entre registradores e li solicita uma constante. Uma pseudoinstrução pode se expandir em uma ou várias instruções reais, portanto contar linhas de montagem nem sempre fornece o tamanho do código.
Instruções armazenadas na memória
Segundo o conceito de programa armazenado, instruções e dados são ambos representados como números na memória. O contador de programa, pc, identifica a instrução a ser buscada. Para uma sequência de instruções de 32 bits, a execução comum o avança em 4 bytes. Desvios e saltos escolhem um endereço seguinte diferente. O processador executa a codificação, não a escrita em linguagem de montagem que a produziu originalmente.
Bases e extensões RISC-V
RISC-V é um padrão aberto de ISA, livre de royalties, mantido pela RISC-V International. RV32I fornece 40 instruções básicas de inteiros e registradores de inteiros de 32 bits; RV64I usa registradores de inteiros de 64 bits. Ambos fornecem 32 registradores de inteiros. Extensões opcionais adicionam recursos: M fornece multiplicação e divisão de inteiros, A operações atômicas, F e D ponto flutuante, C instruções comprimidas e V vetores. Os exemplos usam RV32I e usam M onde mul aparece.
A linguagem de montagem RISC-V é específica de RISC-V. Um programa C pode ser recompilado para x86 ou ARM, mas seu código de máquina RISC-V não pode ser executado diretamente nessas ISAs. Um simulador pode executar instruções RISC-V em um hospedeiro com uma ISA diferente e mostrar as mudanças nos registradores e na memória uma etapa por vez.
Principais conclusões
- A ISA define o comportamento visível; a microarquitetura o implementa.
- O texto em linguagem de montagem é codificado por um montador; pseudoinstruções podem se expandir.
- Com instruções de 32 bits, a execução sequencial avança o pc em 4 bytes.
- RISC-V combina uma ISA básica com extensões opcionais.
1.2 Registradores e resultados de expressões
Registradores armazenam os valores que uma instrução pode usar imediatamente. Em RV32I, cada registrador de inteiros contém 32 bits, e um nome de registrador identifica um local de armazenamento, não uma variável C ou um tipo de dado.
Nomes e funções dos registradores
Há 32 registradores de inteiros, de x0 a x31. O contador de programa é separado. Cinco bits identificam um registrador de inteiros porque há 32 padrões possíveis de 5 bits. Um banco de registradores pequeno também limita o custo de acesso e o espaço que os campos de registradores ocupam nas instruções.
| Registradores | Nomes da ABI | Finalidade convencional |
|---|---|---|
| x0 | zero | Sempre é lido como 0 |
| x1, x2 | ra, sp | Endereço de retorno e ponteiro de pilha |
| x3, x4 | gp, tp | Ponteiros global e de thread |
| x5–x7, x28–x31 | t0–t6 | Valores temporários |
| x8–x9, x18–x27 | s0–s11 | Valores preservados durante chamadas |
| x10–x17 | a0–a7 | Argumentos; a0–a1 também retornam resultados |
O registrador s0 também pode servir como fp, o ponteiro do quadro. Escritas em zero são descartadas, portanto ele pode fornecer um operando zero sem antes carregar uma constante. Por exemplo, add a0, a1, zero copia a1.
Esses nomes expressam um acordo de software. Em particular, o hardware não preserva automaticamente um registrador s. Um procedimento que o altera deve providenciar a restauração de seu valor de entrada. A ABI padrão também reserva gp e tp para suas funções no ambiente; eles não são registradores temporários comuns.
Divida uma expressão em operações
Traduza f = (g + h) - (i + j); com g, h, i, j em s1–s4 e o resultado em s0:
add t0, s1, s2
add t1, s3, s4
sub s0, t0, t1
Com os valores 9, 4, 6 e 1, a primeira instrução deixa 13 em t0. A segunda deixa 7 em t1. A subtração escreve 6 em s0. Cada temporário mantém um resultado até que a próxima instrução precise dele.
A ordem dos dois operandos de origem importa na subtração: sub s0, t1, t0 produziria −6. O destino é sempre o primeiro, portanto sub t0, s0, t1 sobrescreveria um temporário e leria o valor antigo de s0. Ao acompanhar a execução, anote o destino após cada linha; não atualize os dois registradores de origem.
Os bits adquirem significado pelo uso
Um registrador não tem uma marca de com ou sem sinal. O padrão 0xFFFFFFFF pode significar −1 com sinal ou 4.294.967.295 sem sinal. Uma comparação com sinal e uma comparação sem sinal podem, portanto, discordar sobre os mesmos bits armazenados. Endereços também são padrões de bits em registradores; uma carga interpreta sua base como endereço.
Mantenha os valores disponíveis pelo tempo necessário
Um compilador atribui valores usados com frequência a registradores. Se precisa de mais valores ativos do que os registradores podem comportar, ele armazena alguns na memória, um processo chamado derramamento de registradores, e os carrega novamente depois. Um derramamento tem um custo real de armazenamento e carga.
Chamadas criam outro limite. Um procedimento chamado pode sobrescrever t0–t6 e a0–a7. Mantenha um valor necessário após uma chamada em um registrador s devidamente preservado ou salve-o na pilha antes da chamada. Escolher t0 apenas porque estava livre não faz seu valor ser preservado.
Principais conclusões
- RV32I tem 32 registradores de inteiros de 32 bits; pc é separado.
- Escritas em zero são descartadas, e leituras sempre produzem 0.
- A aritmética com registradores escreve no primeiro operando e lê os operandos seguintes.
- Resultados temporários devem permanecer disponíveis até seu último uso.
- Registradores preservados pelo chamador podem mudar durante uma chamada de procedimento.
1.3 Mover valores entre a memória e os registradores
Endereços de memória identificam bytes. Cargas trazem dados para registradores, e armazenamentos copiam bits de registradores de volta para a memória. Em uma arquitetura de carga e armazenamento, operações aritméticas não leem seus operandos diretamente da memória.
Tamanhos e deslocamentos em bytes
Um byte ocupa um endereço, uma meia palavra ocupa dois e uma palavra de 32 bits ocupa quatro. RV32 tem um espaço de endereçamento de 32 bits de até 4 GiB. Na ABI C ILP32, char usa 1 byte, short 2, e int, long e ponteiros 4.
As instruções lw e sw transferem palavras. As cargas de meias palavras são lh e lhu, e as cargas de bytes são lb e lbu. Armazenamentos usam sh e sb para os tamanhos menores. Um armazenamento copia os bits inferiores de sua origem; sb escreve apenas os 8 bits inferiores.
Um endereço é um registrador base mais um deslocamento em bytes de 12 bits com sinal:
lw t0, 32(s3)
sw t0, 48(s3)
A carga lê a palavra no endereço em s3 mais 32. Ela não soma 32 ao valor carregado e não altera s3. O armazenamento escreve esse valor na base mais 48. O intervalo de deslocamento vai de −2048 a 2047.
Traduza um acesso a vetor
Para um vetor int cuja base está em s3, o elemento A[i] começa na base mais 4 vezes o índice. Para implementar A[12] = h + A[8];, com h em s1:
lw t0, 32(s3) # A[8]
add t0, s1, t0
sw t0, 48(s3) # A[12]
Se A[8] é 80 e h é 3, A[12] passa a ser 83. Um deslocamento de 8 selecionaria A[2], pois o endereço conta bytes. Para um índice variável em s4, forme primeiro o endereço:
slli t1, s4, 2
add t1, s3, t1
lw t0, 0(t1)
A multiplicação segue o tamanho do elemento, não o nome do processador. Um ponteiro ou long em RV64 LP64 ocupa 8 bytes e usa ld/sd; um int em RV64 ainda ocupa 4.
Faça a extensão de uma carga pequena
Um registrador deve conter 32 bits mesmo quando uma carga lê apenas 8. lb copia o bit de sinal do byte para cada bit superior; lbu preenche os bits superiores com zeros. Assim, o byte 0xF0 torna-se 0xFFFFFFF0 (−16) com lb, mas 240 com lbu. A mesma distinção se aplica a lh e lhu. Bytes sem sinal, incluindo bytes de cadeias de caracteres, usam lbu.
Leia a ordem de bytes
Na memória little-endian, o byte menos significativo ocupa o menor endereço. Armazenar 0x12345678 em 0x1000 coloca 0x78, 0x56, 0x34, 0x12 nos endereços de 0x1000 a 0x1003. Uma carga de byte do primeiro endereço lê 0x78. A ordem big-endian coloca 0x12 primeiro; sistemas RISC-V podem definir qualquer uma das ordens, enquanto os sistemas de ensino usuais são little-endian.
Um endereço naturalmente alinhado é múltiplo do tamanho do acesso: 4 para uma palavra e 2 para uma meia palavra. Acessos naturalmente alinhados têm suporte da arquitetura. Um acesso desalinhado pode funcionar, levar mais tempo ou gerar uma exceção, dependendo do ambiente de execução. Coloque palavras em endereços múltiplos de 4 bytes e avance 4 para alcançar a próxima palavra.
Principais conclusões
- Deslocamentos de carga e armazenamento contam bytes, não elementos de vetores.
- Calcule um endereço com índice variável antes de executar a carga ou o armazenamento.
- Cargas com sinal fazem extensão de sinal; cargas sem sinal fazem extensão com zeros.
- A memória little-endian coloca o byte menos significativo primeiro.
- O alinhamento natural segue o tamanho do acesso.
- RV64 LP64 aumenta a largura de ponteiros e valores long, enquanto int permanece com 4 bytes.
1.4 Ajustar constantes aos campos de instrução
Um imediato é uma constante codificada dentro de uma instrução. Ele permite que uma operação comum, como incrementar um contador, evite uma carga separada.
Constantes pequenas
addi t0, t0, 1 soma 1 a t0. Seu imediato é um número de 12 bits com sinal em complemento de dois, de −2048 a 2047. O processador faz sua extensão de sinal para 32 bits antes da adição. Subtraia uma constante pequena com um imediato negativo, como em addi t0, t0, -5; RV32I não tem instrução de subtração com imediato.
Cargas, jalr e instruções aritméticas e lógicas comuns com imediato também usam imediatos de 12 bits com sinal. Em particular, andi, ori e xori não fazem extensão com zeros de suas constantes. Já instruções de deslocamento com imediato codificam uma quantidade de deslocamento RV32 de 0 a 31.
Abreviações úteis do montador incluem:
| Texto | Instrução básica ou expansão |
|---|---|
mv rd, rs | addi rd, rs, 0 |
nop | addi x0, x0, 0 |
not rd, rs | xori rd, rs, -1 |
neg rd, rs | sub rd, x0, rs |
li rd, c, para um c pequeno | addi rd, x0, c |
A constante −1 fica com todos os bits iguais a um quando sofre extensão de sinal, por isso xori com −1 inverte todos os bits.
Construa os 32 bits
lui coloca um campo de 20 bits nos bits 31–12 e zera os 12 bits inferiores. Para 0x12345678:
lui t0, 0x12345
addi t0, t0, 0x678
O valor intermediário é 0x12345000; somar 1.656 fornece a parte inferior. Uma parte inferior com o bit 11 ativado exige mais cuidado. Seu padrão de 12 bits representa uma parcela negativa, portanto aumente a parte superior antes de somá-la.
Para 0x00001800, o padrão inferior 0x800 significa −2048. Use:
lui t0, 0x2
addi t0, t0, -2048
O primeiro resultado é 0x2000. Subtrair 0x800 produz 0x1800. Usar o campo superior 0x1 produziria 0x800, um bloco de 4096 a menos.
Para qualquer constante de 32 bits, leia seus 12 bits inferiores como um valor com sinal. Subtraia esse valor com sinal da constante desejada, divida por 4096 e mantenha os 20 bits inferiores para lui. Para 0xDEADBEEF, a parte inferior com sinal é −273, portanto use o campo superior 0xDEADC e depois addi com −273. Montadores fazem essa divisão para li.
Constantes, endereços e conteúdo
li t0, 100 coloca o número 100 em um registrador. lw t0, 100(zero) lê o conteúdo da memória no endereço 100. Um endereço pode ser carregado como valor: la t0, Item solicita ao montador o endereço nomeado por Item.
auipc soma um imediato superior deslocado 12 bits à esquerda ao endereço dessa instrução. Combinado com uma operação com imediato ou uma carga, ele fornece acesso relativo ao pc em cerca de ±2 GiB. Uma la local geralmente usa auipc mais addi; outras expansões permitem o acesso a bibliotecas compartilhadas.
Uma constante como 5.000 não pode ser operando de addi. Carregue-a em um segundo registrador com li e depois use add. Um valor maior não recebe um imediato mais largo apenas por ser escrito em hexadecimal.
Principais conclusões
- Um imediato de 12 bits com sinal varia de −2048 a 2047.
- lui fornece os 20 bits superiores com 12 zeros inferiores.
- Se a parte inferior de 12 bits for negativa, compense no campo superior.
- li carrega um valor, la forma um endereço e lw lê a memória.
- auipc constrói um endereço relativo a seu próprio pc.
2Aritmética e fluxo de controle
Quando os valores estão nos registradores, as instruções calculam resultados e decidem o que será executado em seguida. A lógica bit a bit e os deslocamentos operam sobre representações; comparações com e sem sinal dão significados diferentes a esses padrões. Desvios e saltos transformam decisões e laços em C em caminhos explícitos. Acompanhar um caminho significa registrar tanto os valores atuais quanto o endereço da próxima instrução, incluindo o teste final do laço que falha.
2.1 Calcular com bits e valores com sinal
Uma instrução aritmética lê bits de registradores e escreve um novo padrão de bits. Entender esse padrão exige conhecer tanto a operação quanto a interpretação pretendida, com ou sem sinal.
Adição, subtração e estouro
Em RV32I, add, sub e addi mantêm os 32 bits inferiores do resultado. Somar 1 a 0x7FFFFFFF produz 0x80000000, que representa −2.147.483.648 como inteiro com sinal. A instrução não gera uma exceção de estouro.
Para adição sem sinal, o software pode detectar a passagem pelo limite:
add t0, a0, a1
bltu t0, a0, Overflow
Uma soma sem sinal que ultrapassou o limite é menor que uma das entradas. Esse teste sem sinal não é a regra para estouro com sinal, em que os sinais das entradas e do resultado importam.
A extensão M fornece mul, mulh, mulhu, div, divu, rem e remu. mul mantém os bits inferiores do produto; as instruções que produzem a parte superior do produto distinguem entradas com e sem sinal. A divisão por zero retorna um quociente com todos os bits iguais a um e o dividendo original como resto, sem uma exceção de divisão.
Selecione, ative e inverta bits
and, or e xor operam independentemente em cada bit. AND com 1 mantém um bit; AND com 0 o zera. OR com 1 ativa um bit, e XOR com 1 o inverte.
srli t0, a0, 4
andi t0, t0, 0xF
Com a0 = 0xABCD, deslocar à direita produz 0xABC. Aplicar a máscara 0xF deixa 0xC, os bits 7–4 originais. O deslocamento coloca o campo nas posições inferiores; a máscara remove tudo acima dele. Para ativar o bit 4 sem alterar os demais, use ori a0, a0, 0x10. xori com −1 inverte todos os 32 bits.
Escolha um deslocamento pela regra de preenchimento
Um deslocamento à esquerda, sll ou slli, preenche as posições inferiores com zeros. Um deslocamento lógico à direita, srl ou srli, preenche as posições superiores com zeros. Um deslocamento aritmético à direita, sra ou srai, copia o bit de sinal para as posições superiores. Deslocamentos com registrador em RV32 usam apenas os 5 bits inferiores do registrador que contém a quantidade de posições.
Deslocar 13 em 3 posições à esquerda produz 104. Isso multiplica por 8 porque nenhum bit necessário é perdido. Já deslocamentos à esquerda que descartam bits significativos mantêm apenas o resultado que cabe no registrador.
Para valores sem sinal, o deslocamento à direita divide por uma potência de 2 e arredonda para baixo. Para valores com sinal, o deslocamento aritmético à direita arredonda em direção ao infinito negativo: deslocar −7 em 1 posição à direita produz −4. A divisão com sinal em C trunca em direção a zero, produzindo −3 para -7 / 2. Um compilador precisa de uma correção antes de substituir essa divisão com sinal por um deslocamento. Um deslocamento lógico do mesmo padrão negativo preenche com zeros e produz um resultado positivo grande.
Compare para produzir um valor
slt escreve 1 quando sua primeira origem com sinal é menor e 0 caso contrário. sltu usa interpretação sem sinal. Com 0xFFFFFFFF e 1 como entradas, slt escreve 1, enquanto sltu escreve 0. As versões com imediato são slti e sltiu; esta última ainda faz extensão de sinal de seu imediato antes da comparação sem sinal.
AND bit a bit é diferente do && de C. Um resultado bit a bit pode conter muitos bits ativados; AND lógico produz 0 ou 1 e pode deixar de avaliar a segunda expressão.
Principais conclusões
- A aritmética RV32 mantém os 32 bits inferiores e não gera armadilha em caso de estouro.
- AND seleciona bits, OR os ativa e XOR os inverte.
- O deslocamento aritmético à direita preserva o sinal; o lógico preenche com zeros.
- srai e a divisão com sinal em C arredondam de forma diferente valores negativos que não são múltiplos do divisor.
- slt e sltu interpretam os mesmos padrões de registradores de formas diferentes.
- A multiplicação e a divisão da extensão M são separadas do RV32I.
2.2 Escolher a próxima instrução
Um desvio condicional compara dois registradores e altera o contador de programa quando seu teste é satisfeito. Quando não é, a execução continua com a próxima instrução.
Escolha a comparação
| Instrução | Condição de desvio |
|---|---|
beq, bne | Igual, diferente |
blt, bge | Menor que com sinal, maior ou igual com sinal |
bltu, bgeu | Menor que sem sinal, maior ou igual sem sinal |
RISC-V combina comparação e desvio em uma instrução. Ele não tem indicadores de condição de inteiros a serem definidos antes. Para testar em relação a zero, use zero como uma das origens. beqz a0, L expande-se em beq a0, zero, L.
As formas maior que e menor ou igual são abreviações do montador com as origens trocadas. bgt a0, a1, L expande-se em blt a1, a0, L. Trocar a ordem das origens importa tanto quanto selecionar o mnemônico correto.
Fluxo de controle com e sem sinal
Se a0 contém 0xFFFFFFFF e a1 contém 1, blt a0, a1, L realiza o desvio porque −1 com sinal é menor que 1. bltu não desvia, pois o primeiro padrão é um valor grande sem sinal. Use comparações coerentes com o significado dos dados, incluindo comparação sem sinal para endereços.
Uma verificação de limites sem sinal pode rejeitar índices negativos com sinal, assim como índices iguais ou superiores a um comprimento pequeno não negativo:
bgeu a0, a1, OutOfBounds
Para um comprimento de 20, um índice de −1 aparece como 4.294.967.295 sem sinal e causa o desvio. Um índice de 19 não causa; um índice de 20 causa. O comprimento deve representar o comprimento real não negativo do vetor.
Saltos e endereços de retorno
jal ra, f escreve o endereço da próxima instrução, pc + 4, em ra e depois salta para f. Um j L simples expande-se em jal zero, L; seu valor de enlace é descartado. Isso é adequado para um salto de volta em um laço, mas não estabelece um endereço de retorno para uma chamada.
jalr rd, offset(rs1) soma um deslocamento de 12 bits com sinal a um valor de registrador, zera o bit 0 do destino e escreve seu próprio pc + 4 em rd. ret expande-se em jalr zero, 0(ra), retornando ao endereço salvo sem manter um novo enlace.
Desvios condicionais e jal armazenam deslocamentos relativos aos endereços das próprias instruções. Seus alcances são de cerca de ±4 KiB e ±1 MiB, respectivamente. jalr usa um destino baseado em registrador e pode alcançar todo o espaço de endereçamento. Uma transferência condicional distante pode inverter o teste para pular um salto mais longo:
bne a0, a1, Skip
jal zero, Far
Skip:
Aqui Far está dentro do alcance de jal. Se houver igualdade, o desvio curto prossegue sequencialmente para o salto; caso contrário, ele o pula.
Reconheça um bloco básico
Um bloco básico tem uma entrada em seu início e nenhuma transferência de controle antes de seu fim. Dentro dele, as instruções são executadas em sequência. Destinos de desvios iniciam novos blocos; desvios e saltos os encerram. Isso permite analisar quais atribuições sempre são executadas antes de um determinado teste ou saída.
Principais conclusões
- Escolha desvios com ou sem sinal de acordo com o significado dos operandos.
- Desvios RISC-V comparam registradores diretamente, sem indicadores de condição.
- jal salva um enlace; j o descarta.
- jalr usa um registrador mais um imediato e zera o bit 0 do destino.
- Um deslocamento condicional é relativo à própria instrução de desvio.
2.3 Transformar o fluxo de controle em C em desvios
A linguagem de montagem torna explícito cada caminho em uma decisão ou laço. Rótulos marcam destinos, desvios condicionais escolhem caminhos e saltos simples impedem a continuação sequencial indesejada.
Um if-else precisa de um caminho de saída
Traduza if (i == j) f = g + h; else f = g - h;, com f, g, h, i, j em s0–s4:
bne s3, s4, Else
add s0, s1, s2
j Exit
Else:
sub s0, s1, s2
Exit:
O primeiro desvio testa o oposto da condição em C para que, em caso de igualdade, a execução prossiga para a adição. O salto após a adição impede a execução da subtração. Com g = 10 e h = 3, índices iguais produzem 13; índices diferentes produzem 7. Remover j Exit faz a subtração ser executada também após a adição.
Para a < b && b < c, desvie para a parte else assim que qualquer comparação falhar. Para a < b || b < c, desvie para a parte then assim que qualquer uma for bem-sucedida. Isso implementa a avaliação de curto-circuito, incluindo pular uma segunda expressão cujo valor é desnecessário.
Um laço while testa antes de seu corpo
Para while (save[i] == k) i++;, mantenha i em s3, k em s5 e a base do vetor em s6:
Loop:
slli t1, s3, 2
add t1, t1, s6
lw t0, 0(t1)
bne t0, s5, Exit
addi s3, s3, 1
j Loop
Exit:
Cada passagem forma o endereço do elemento atual, carrega-o e verifica-o. Com o vetor {7, 7, 7, 2, 7}, índice inicial 0 e k = 7, o laço termina com índice 3. O teste que falha não incrementa o índice. O vetor deve conter um elemento de parada dentro de seu intervalo válido.
Some um vetor com contagem de elementos
Para uma contagem não negativa de elementos em a1 e a base do vetor em a0:
li t1, 0
li t0, 0
Loop:
bge t0, a1, Done
slli t2, t0, 2
add t2, a0, t2
lw t3, 0(t2)
add t1, t1, t3
addi t0, t0, 1
j Loop
Done:
mv a0, t1
ret
O teste no início trata um comprimento zero sem uma carga. Com quatro elementos {5, -2, 10, 7}, a soma acumulada é 5, 3, 13 e depois 20. A sequência executa 33 instruções: 2 de preparação, 7 para cada uma das quatro iterações bem-sucedidas, um teste que falha e 2 instruções de conclusão.
Uma versão com ponteiro forma um endereço final uma vez e avança o endereço atual em 4. Para um intervalo válido de vetor cujo endereço final não ultrapassa o limite do espaço de endereçamento, ela pode usar comparações de endereços sem sinal:
li t1, 0
slli t0, a1, 2
add t0, a0, t0
bgeu a0, t0, Done
Loop:
lw t3, 0(a0)
add t1, t1, t3
addi a0, a0, 4
bltu a0, t0, Loop
Done:
mv a0, t1
ret
São 4 instruções de preparação, 4 por iteração e 2 de conclusão, totalizando 22 para quatro elementos. O desvio inicial pula o corpo para um intervalo vazio; o ponteiro avança um elemento após cada carga.
Decisões com vários caminhos
Um switch pode usar comparações repetidas ou uma tabela de saltos quando os valores dos casos são próximos. Uma seleção por tabela verifica o intervalo do caso, multiplica um índice pelo tamanho de 4 bytes da entrada, carrega o endereço do caso e salta por ele com jalr zero, 0(t0). A verificação de intervalo deve preceder a carga da tabela para que um caso inválido não selecione um endereço sem relação com ele.
Principais conclusões
- Desviar para contornar a parte then geralmente usa a condição oposta.
- Salte para depois da parte else ao concluir a parte then.
- Condições compostas devem preservar a avaliação de curto-circuito.
- Verifique um laço com contagem antes de ler seu primeiro elemento.
- Laços com ponteiros podem evitar multiplicações repetidas de índices.
- Uma tabela de saltos exige uma verificação de intervalo antes da seleção.
3Campos de instrução e código de máquina
Nomes em linguagem de montagem tornam-se campos fixos dentro de instruções de máquina. Parta da operação para seu formato e depois atribua registradores e imediatos às suas posições de bits. Cargas, armazenamentos, desvios e saltos usam organizações diferentes porque transportam informações diferentes. Codificar e decodificar nos dois sentidos revela erros de deslocamento, sinal e ordem de campos que a linguagem de montagem legível pode esconder.
3.1 Codificar operações com registradores, cargas e armazenamentos
Uma instrução de máquina é uma palavra de 32 bits dividida em campos. Codificar significa escolher a organização correta, atribuir cada operando a um campo e escrever cada campo com sua largura completa.
Posições fixas simplificam a decodificação
Os 7 bits inferiores, 6–0, contêm o código de operação. Quando presentes, rd ocupa os bits 11–7, funct3 os bits 14–12, rs1 os bits 19–15 e rs2 os bits 24–20. Campos de registrador de cinco bits identificam x0 a x31. Campos de função distinguem operações que compartilham um código de operação.
RV32I usa os formatos R, I, S, B, U e J. Eles acomodam operandos diferentes nos mesmos 32 bits, mantendo estáveis as posições dos registradores. Para imediatos com sinal, o bit 31 contém o sinal.
Tipo R: duas origens e um destino
Do bit 31 ao bit 0, a organização R é:
funct7 | rs2 | rs1 | funct3 | rd | opcode
7 5 5 3 5 7 bits
Para add s0, a0, a1, rd = 8, rs1 = 10, rs2 = 11. ADD usa o código de operação 0110011, funct3 = 000, funct7 = 0000000:
0000000 | 01011 | 01010 | 000 | 01000 | 0110011
0000 0000 1011 0101 0000 0100 0011 0011
A palavra hexadecimal é 0x00B50433. SUB altera apenas funct7 para 0100000, produzindo 0x40B50433 para sub s0, a0, a1. Os campos de origem aparecem em ordem diferente dos operandos de montagem, portanto não basta concatenar os nomes de registradores escritos.
Tipo I: uma origem e um imediato
A organização I é imm[11:0] | rs1 | funct3 | rd | opcode. addi usa o código de operação 0010011 e funct3 = 000. Em addi sp, sp, -16, ambos os campos de registrador contêm 00010. O imediato de 12 bits é 111111110000 (0xFF0), produzindo 0xFF010113.
Uma carga também usa o tipo I, com a base do endereço como rs1 e o registrador carregado como rd. lw t0, 32(s3) tem imediato 32, base x19, destino x5, código de operação 0000011 e funct3 = 010, produzindo 0x0209A283.
Operações de deslocamento com imediato usam os 5 bits inferiores da região de imediato como quantidade de deslocamento. Os bits superiores de controle distinguem os deslocamentos: srli t0, t1, 3 é 0x00335293, enquanto srai t0, t1, 3 é 0x40335293. O bit 30 seleciona a regra de preenchimento aritmético.
Tipo S: armazene um valor de origem
Um armazenamento não tem registrador de destino. A organização S usa esse espaço para parte de seu imediato:
imm[11:5] | rs2 | rs1 | funct3 | imm[4:0] | opcode
Para sw t0, 48(s3), rs2 = 5 fornece o valor armazenado e rs1 = 19 fornece a base. O imediato de 12 bits 48 divide-se em 0000001 e 10000. Com funct3 = 010 e código de operação 0100011, o resultado é 0x0259A823. Não há rd a ser preenchido com t0.
Depois de unir os campos, agrupe a cadeia completa de 32 bits em oito grupos de 4 bits e converta cada um em um dígito hexadecimal. Depois, decodifique o resultado: confirme o código de operação, os números de registradores e o imediato com sinal. Completar um campo de registrador até 5 bits é essencial; omitir um zero inicial altera as posições dos campos posteriores.
Principais conclusões
- Encontre o formato a partir da operação antes de atribuir campos aos operandos.
- Campos de registradores têm 5 bits e ocupam posições fixas.
- O tipo R usa funct7 para distinguir add de sub.
- O tipo I mantém um imediato de 12 bits reunido.
- O tipo S divide o deslocamento e tem rs2, mas não rd.
- Verifique uma palavra codificada decodificando-a novamente em operandos.
3.2 Recompor deslocamentos e decodificar palavras de máquina
As codificações de desvios e saltos economizam espaço ao omitir um bit de deslocamento que é sempre zero. Seus campos ainda representam distâncias em bytes, e o sinal deve ser reconstruído antes de calcular o destino.
Deslocamentos de desvio do tipo B
Calcule o deslocamento como o endereço de destino menos o endereço do próprio desvio. Uma bne em 0x1010 com destino em 0x1000 usa −16 bytes, não −20 nem −4 instruções.
O tipo B usa o código de operação 1100011. Seu deslocamento com sinal tem 13 bits, incluindo um bit 0 igual a zero que não é armazenado. O intervalo vai de −4096 a +4094 bytes, em passos de 2. A disposição é:
| Bits do deslocamento | Bits da instrução |
|---|---|
| imm[12] | 31 |
| imm[10:5] | 30–25 |
| imm[4:1] | 11–8 |
| imm[11] | 7 |
Os campos de origem permanecem em suas posições conhecidas. Os valores de funct3 são 000 para beq, 001 para bne, 100 para blt, 101 para bge, 110 para bltu e 111 para bgeu.
Para bne t0, t1, -16, o deslocamento de 13 bits é 1 1111 1111 0000. Suas partes distribuídas são 1, 111111, 1000 e 1 na ordem da tabela. Com rs1 = 5 e rs2 = 6, a instrução é 0xFE6298E3. Substituir o deslocamento por +12 produz 0x00629663.
Os deslocamentos permitem passos de 2 bytes porque instruções comprimidas podem ter 2 bytes de comprimento. Um destino de desvio realizado ainda deve satisfazer o alinhamento de instrução ativo: RV32I sem instruções comprimidas exige alinhamento de 4 bytes.
Tipos J e U
jal usa o código de operação 1101111. Seu deslocamento em bytes de 21 bits com sinal omite o bit 0 e coloca os bits na ordem imm[20|10:1|11|19:12] ao longo dos bits 31–12 da instrução. O intervalo vai de −1.048.576 a +1.048.574 bytes. jal ra, 192 torna-se 0x0C0000EF.
O tipo U coloca seu valor superior de 20 bits diretamente nos bits 31–12 da instrução, seguidos de rd e do código de operação. lui usa 0110111; auipc usa 0010111. Assim, lui t0, 0x12345 é 0x123452B7.
Decodifique primeiro pelo código de operação
Para 0x00A12423, os 7 bits inferiores são 0100011: um armazenamento. funct3 = 010 seleciona sw. Os campos de origem fornecem a base x2 (sp) e o valor x10 (a0). Os dois fragmentos do imediato unem-se para formar 8, portanto a instrução de montagem é sw a0, 8(sp).
Para um desvio, una as partes distribuídas na ordem dos bits do deslocamento, acrescente o zero implícito e faça a extensão de sinal do resultado completo de 13 bits. Tratar os 12 bits armazenados como um imediato comum do tipo I perde tanto a disposição correta quanto a escala. A posição fixa do bit de sinal e a reutilização de outras posições de campos reduzem o trabalho do decodificador, embora a organização escrita seja menos conveniente para leitura manual.
A relocação preserva relações
Se um desvio e seu destino se movem pela mesma distância, subtrair seus novos endereços fornece o deslocamento antigo. Uma referência a outra seção pode mudar quando as seções são organizadas separadamente. Um ligador pode, portanto, preservar um desvio relativo interno enquanto ajusta uma referência a um símbolo externo.
Principais conclusões
- Deslocamentos de desvios e de jal contam bytes a partir do endereço da instrução.
- Os tipos B e J omitem o bit 0, que é zero.
- Recomponha um deslocamento antes de interpretar seu sinal.
- O código de operação determina como os bits restantes são divididos.
- Mover uma instrução e seu destino juntos preserva sua distância relativa.
4Chamadas de procedimentos e armazenamento na pilha
Uma chamada precisa fornecer argumentos, preservar estados úteis e retornar ao lugar correto. Convenções de registradores permitem que procedimentos separados cooperem; quadros de pilha mantêm separados os estados de chamadas aninhadas e recursivas. Os mesmos cálculos de endereço usados para operandos em memória também implementam ponteiros, campos de estruturas e cadeias de caracteres. Acompanhe um valor durante uma chamada com o mesmo cuidado com que o acompanha em uma instrução aritmética.
4.1 Passar argumentos e preservar valores de registradores
Uma chamada de procedimento transfere controle e dados entre trechos de código escritos independentemente. A convenção de chamada informa a ambas as partes onde ficam argumentos e resultados e quais valores de registradores devem ser preservados.
A sequência de chamada e retorno
O chamador coloca os primeiros oito argumentos inteiros em a0–a7; argumentos adicionais vão para a pilha. O procedimento retorna um resultado inteiro em a0, usando também a1 para um resultado de 64 bits em RV32.
jal ra, f armazena seu próprio endereço mais 4 em ra e depois salta para f. jal f é sua forma abreviada. A pseudoinstrução call f normalmente se expande em auipc mais jalr para maior alcance; o ligador pode reduzi-la a uma jal. Em ambos os casos, o endereço de retorno vem após a sequência de chamada efetiva.
ret é jalr zero, 0(ra). Ele salta pelo endereço salvo, descartando um novo enlace. Um j f simples também descarta seu enlace, portanto não estabelece uma chamada e um retorno comuns.
Decida quem salva um valor
Os registradores preservados pelo chamador incluem t0–t6, a0–a7 e ra. O chamado pode alterá-los. Se o chamador precisa de um de seus valores atuais mais tarde, ele salva esse valor antes da chamada.
Os registradores preservados pelo chamado incluem s0–s11 e sp. Um chamado que altera um registrador s deve salvar o valor recebido e restaurá-lo antes de retornar. Restaurar sp devolve o chamador a seu próprio quadro. O processador não impõe essas obrigações; módulos compilados cooperam porque seu software segue a ABI.
Suponha que um chamador tenha um contador de laço em t0 e invoque um procedimento auxiliar. O auxiliar pode usar t0 legitimamente para seu próprio cálculo. Mover o contador para s1 pode preservá-lo durante chamadas a auxiliares, mas o procedimento que o contém deve então preservar o valor de s1 que recebeu de seu próprio chamador.
Um procedimento folha pode não precisar de quadro
Um procedimento folha não chama nenhum outro procedimento. Esta função calcula (g + h) - (i + j) a partir de argumentos em a0–a3:
leaf:
add t0, a0, a1
add t1, a2, a3
sub a0, t0, t1
ret
Ela usa apenas registradores de trabalho preservados pelo chamador e mantém ra inalterado. Para as entradas 9, 4, 6 e 1, retorna 6. Ela não precisa de quadro de pilha.
Se o cálculo usa s0, ele deve preservar esse registrador:
addi sp, sp, -16
sw s0, 12(sp)
add t0, a0, a1
add t1, a2, a3
sub s0, t0, t1
mv a0, s0
lw s0, 12(sp)
addi sp, sp, 16
ret
O resultado é movido para a0 antes de o antigo s0 ser restaurado. Se o s0 recebido era 1234, ele ainda é 1234 depois. O quadro alocado tem 16 bytes para preservar o alinhamento padrão da pilha, embora a palavra salva use apenas 4.
Uma chamada pode ser lida como seis ações: colocar argumentos, transferir controle, obter armazenamento, realizar a tarefa, colocar o resultado e retornar. Em cada limite, distinga o valor retornado dos valores anteriores que a convenção exige que o procedimento preserve.
Principais conclusões
- Argumentos inteiros começam em a0–a7, e o primeiro resultado fica em a0.
- ra aponta para depois da instrução de chamada efetiva ou da sequência de chamada expandida.
- Chamadores protegem valores ativos de registradores preservados pelo chamador; chamados restauram os registradores preservados que alteram.
- Um procedimento folha que usa apenas registradores preservados pelo chamador pode não precisar de quadro.
- Mova o resultado antes de restaurar um registrador preservado que o continha.
4.2 Dar a cada chamada seu próprio quadro de pilha
Chamadas aninhadas precisam de um lugar para manter os dados da chamada externa. A pilha fornece armazenamento na ordem último a entrar, primeiro a sair: a chamada mais recente termina primeiro e libera seu próprio quadro.
Aloque abaixo do ponteiro de pilha antigo
A pilha cresce em direção a endereços menores. addi sp, sp, -16 reserva 16 bytes; addi sp, sp, 16 libera-os. A ABI padrão mantém sp como múltiplo de 16 durante toda a execução. Um quadro pode conter registradores salvos, o endereço de retorno, valores derramados, objetos locais e espaço para argumentos de saída na pilha.
Um padrão comum para um procedimento que não é folha é:
addi sp, sp, -16
sw ra, 12(sp)
sw s0, 8(sp)
# body, including calls
lw s0, 8(sp)
lw ra, 12(sp)
addi sp, sp, 16
ret
Os deslocamentos referem-se ao novo sp. Se ele é 0x2000, o ra salvo ocupa de 0x200C a 0x200F. O epílogo correspondente restaura os valores enquanto o quadro ainda existe e depois o libera. Todo caminho de retorno deve desfazer a alocação.
Um ponteiro do quadro, convencionalmente s0, pode fornecer deslocamentos fixos para objetos locais quando sp muda dentro de um procedimento. Seu valor recebido deve ser preservado como em qualquer outro uso de s0.
A recursão mantém cópias separadas
O fatorial precisa do n original após calcular fact(n - 1). Ele também precisa de seu próprio endereço de retorno após a jal recursiva sobrescrever ra:
fact:
addi sp, sp, -16
sw ra, 12(sp)
sw a0, 8(sp)
li t0, 2
blt a0, t0, Base
addi a0, a0, -1
jal ra, fact
lw t1, 8(sp)
mul a0, t1, a0
j Done
Base:
li a0, 1
Done:
lw ra, 12(sp)
addi sp, sp, 16
ret
A mul da extensão M multiplica o resultado interno retornado pelo n original. Para fact(3), os quadros de 3, 2 e 1 coexistem. A chamada do caso base retorna 1; a seguinte retorna 2; a chamada externa retorna 6. Com sp = 0x7FFFFF00 inicialmente, três quadros reduzem-no em 48 bytes até 0x7FFFFED0. Os retornos liberam-nos em ordem inversa.
O teste do caso base trata os valores não negativos 0 e 1. Para esse resultado de 32 bits, o fatorial é exato até 12!: 479.001.600. Os bits inferiores do produto deixam de representar o resultado completo em 13!, que é 6.227.020.800.
Localize a pilha na memória do programa
Uma organização típica tem código em texto, dados globais em dados estáticos, objetos alocados dinamicamente no heap e quadros de chamadas na pilha. O heap frequentemente cresce para cima e a pilha para baixo. A disposição real pertence ao ambiente de execução.
Um ponteiro para um objeto local na pilha torna-se inválido quando o tempo de vida desse objeto termina no retorno. Restaurar sp não apaga os bytes antigos, mas chamadas posteriores podem reutilizá-los. Da mesma forma, alterar sp e continuar usando os deslocamentos antigos de salvamento pode ler os bytes do quadro errado.
Principais conclusões
- Subtraia de sp para alocar; some a mesma quantidade para liberar.
- A ABI padrão exige alinhamento da pilha de 16 bytes.
- Uma chamada aninhada sobrescreve ra, portanto preserve o endereço de retorno externo.
- Cada ativação recursiva tem argumentos salvos e estado de retorno próprios.
- Todos os caminhos de saída devem restaurar o quadro e os registradores necessários.
- Um objeto local não sobrevive a seu quadro de pilha.
4.3 Usar endereços para vetores, estruturas e cadeias de caracteres
Um ponteiro é um valor de endereço. A linguagem de montagem implementa um desreferenciamento usando esse endereço como base de carga ou armazenamento e implementa a aritmética de ponteiros somando uma distância em bytes.
Siga o ponteiro e acesse o valor
Com int *p em a0, lw t0, 0(a0) lê *p, enquanto sw t0, 0(a0) escreve nele. O registrador contém o endereço; a memória nesse endereço contém o inteiro. Se uma variável ponteiro estiver armazenada na memória, primeiro carregue esse valor de ponteiro e depois use-o como base para uma segunda carga.
Obter o endereço de um objeto local em C exige armazenamento endereçável, normalmente uma posição na pilha. Um registrador de hardware não tem endereço de memória que possa ser passado como &x.
A aritmética de ponteiros em C multiplica o deslocamento automaticamente. Para RV32 ILP32, incrementar int *p soma 4 bytes, enquanto incrementar char *p soma 1. A linguagem de montagem não tem um tipo de ponteiro que forneça esse fator:
addi a0, a0, 4 # next int
addi a1, a1, 1 # next char
Um argumento de vetor é seu endereço base, não uma cópia de seus elementos. Passe a contagem de elementos separadamente quando um procedimento precisar dela; o ponteiro sozinho não codifica o comprimento do vetor.
Campos de estruturas têm deslocamentos em bytes
Para struct item { int id; int score; };, id ocupa os bytes 0–3 e score os bytes 4–7. Com o endereço do objeto em a0, lw t0, 4(a0) lê score. Em geral, os deslocamentos dos campos seguem as regras de organização e preenchimento de alinhamento da ABI. Não deduza o deslocamento de cada campo apenas contando declarações no código-fonte.
Para um vetor dessas estruturas de 8 bytes, o próximo elemento começa 8 bytes depois. Dentro de cada elemento, score permanece a 4 bytes do início desse elemento. O passo entre elementos e o deslocamento do campo são partes separadas do cálculo do endereço.
Copie uma cadeia de caracteres terminada em nulo
Uma cadeia de bytes em C termina com um byte zero. Para copiá-la, leia e armazene um byte antes de decidir se a cópia terminou:
Loop:
lbu t0, 0(a1)
sb t0, 0(a0)
addi a0, a0, 1
addi a1, a1, 1
bnez t0, Loop
ret
Aqui a1 é a origem e a0 o destino. O destino deve ter espaço suficiente. lbu lê um byte sem sinal; sb armazena apenas seus 8 bits inferiores. Ambos os ponteiros avançam 1, inclusive na iteração final. O desvio testa o byte copiado, portanto o zero terminador também é armazenado.
Copiar “RISC” exige cinco iterações para quatro letras e o byte zero. Com cinco instruções por iteração mais ret, são executadas 26 instruções. Uma cadeia vazia ainda precisa de uma iteração para copiar seu terminador. Uma carga de palavra leria quatro bytes de uma vez e não implementaria esse algoritmo que opera um byte por vez.
UTF-8 pode codificar um caractere em vários bytes. Esse laço copia esses bytes sem alterá-los; ele não conta os caracteres exibidos. Laços de vetores baseados em ponteiros podem, da mesma forma, avançar pelo passo entre elementos sem recalcular um índice multiplicado a cada vez. Compiladores otimizadores podem fazer essa transformação mesmo quando o programa C usa índices de vetor comuns.
Principais conclusões
- O desreferenciamento usa o endereço contido no ponteiro.
- A aritmética de ponteiros em linguagem de montagem deve incluir o tamanho do elemento.
- Parâmetros de vetor precisam de um comprimento separado quando o procedimento usa uma contagem.
- O passo entre estruturas e o deslocamento de campo correspondem a partes diferentes de um endereço.
- Um laço de cópia de cadeia de caracteres deve armazenar o byte zero terminador.
5Do código-fonte à execução
Um programa passa por ferramentas que conhecem aspectos diferentes de sua forma final. Um compilador escolhe instruções, um montador as codifica, um ligador une módulos e um carregador prepara a execução. Símbolos e relocações transportam referências não concluídas entre as etapas. Acompanhar uma chamada desde o código-fonte até seu endereço final torna concretas as funções dos arquivos objeto, das bibliotecas compartilhadas e do código de inicialização.
5.1 Traduzir o código-fonte em um objeto relocável
Um arquivo-fonte transforma-se em instruções executáveis por várias etapas de tradução. Cada etapa tem uma responsabilidade diferente, e os arquivos intermediários mantêm informações necessárias às etapas posteriores.
O compilador escolhe instruções
Para C, o caminho usual é código-fonte, linguagem de montagem, arquivo objeto, executável e depois um processo em execução. Um compilador analisa o código-fonte, otimiza-o, aloca registradores e seleciona instruções para uma ISA de destino. Um programa de compilação pode executar várias etapas em um comando.
gcc -S program.c para após produzir a linguagem de montagem. gcc -c program.c produz um arquivo objeto sem ligação. O compilador de destino determina qual ISA essa linguagem de montagem usa; um programa RISC-V precisa de um conjunto de ferramentas direcionado a RISC-V.
Em -O0, valores frequentemente passam por posições na pilha e a linguagem de montagem acompanha de perto as instruções individuais do código-fonte. Em -O2, alocação de registradores, trabalho eliminado e laços reestruturados podem tornar essa correspondência menos evidente. Uma instrução C não tem uma única tradução fixa para linguagem de montagem.
O montador codifica e registra
O montador transforma mnemônicos e nomes de registradores em bits, expande pseudoinstruções e resolve rótulos que consegue localizar. Um desvio para a frente refere-se a um rótulo encontrado mais adiante. Um montador pode primeiro coletar posições e depois codificar referências, ou registrar locais a preencher quando o destino for conhecido.
Diretivas informam ao montador como organizar a saída; o processador não as executa:
.data
.align 2
count:
.word 5
message:
.string "hi"
.text
.globl main
main:
# instructions follow
.word 5 emite um valor de dados de 4 bytes. .string "hi" emite os bytes dos caracteres e um zero terminador. No montador GNU RISC-V, .align 2 alinha o próximo item a um endereço múltiplo de 4 bytes. .globl torna um símbolo visível a outros arquivos objeto. Rótulos nomeiam posições; eles não reservam bytes por si só.
O que um arquivo objeto mantém
Um objeto relocável geralmente contém um cabeçalho, código em seções de texto, dados estáticos, uma tabela de símbolos e informações de relocação. A tabela de símbolos distingue nomes definidos aqui de referências definidas em outro lugar. Informações opcionais de depuração conectam instruções a linhas do código-fonte; -g solicita essas informações.
Formatos comuns de objeto incluem ELF, Mach-O e PE/COFF. O formato organiza código de máquina e metadados; ele é distinto da ISA que define os bits de cada instrução.
Para call printf, o montador pode gerar uma sequência auipc/jalr sem conhecer o endereço final da função. Ele registra um símbolo indefinido e entradas de relocação para que o ligador possa concluir a referência. A distância de um desvio local já pode ser conhecida quando seu destino se move com a mesma seção, embora alterações posteriores na organização, como a redução de instruções, possam exigir ajustes.
Outros caminhos de execução
Um interpretador realiza operações por meio de outro programa em execução. Um compilador em tempo de execução gera código de máquina durante a execução, frequentemente após identificar código usado com frequência. Java normalmente usa bytecode seguido de interpretação e compilação JIT; Python normalmente usa um interpretador. Um JIT troca trabalho de compilação durante a execução por uma execução repetida mais rápida.
Um arquivo objeto é, portanto, mais que uma lista bruta de instruções, mas ainda não é um processo completo em execução. Símbolos não resolvidos e a organização final ainda precisam da atenção do ligador e do carregador.
Principais conclusões
- O compilador seleciona e otimiza instruções para uma ISA de destino.
- O montador codifica instruções e expande pseudoinstruções.
- Diretivas organizam a saída; rótulos nomeiam posições.
- Símbolos e relocações preservam referências que etapas posteriores devem resolver.
- -S para na linguagem de montagem, enquanto -c para em um arquivo objeto.
- Interpretadores e compiladores JIT fornecem caminhos diferentes para a execução.
5.2 Ligar módulos e iniciar um processo
Um ligador constrói um arquivo executável a partir de módulos objeto e bibliotecas. Um carregador usa esse arquivo para criar um processo em execução. Saber a qual etapa pertence um endereço ajuda a explicar tanto a relocação quanto erros de inicialização.
Organize módulos e ajuste referências
O ligador coloca seções de entrada no espaço de endereçamento do executável, resolve definições de símbolos e usa entradas de relocação para ajustar referências. Um nome que nenhum módulo ligado define produz um erro de “referência indefinida”.
Suponha que o texto comece em 0x00010000 e os dados em 0x10000000. O objeto A contribui com 0x100 bytes de texto e 0x20 bytes de dados; B vem imediatamente em seguida. O texto de B começa em 0x00010100 e seus dados em 0x10000020.
Uma jal em A no deslocamento de texto 0x40 tem o endereço 0x00010040. Se procB começa no início do texto de B, o deslocamento da chamada é 0xC0, ou 192 bytes, codificado com ra como 0x0C0000EF. Uma variável no deslocamento 0x10 dos dados de B termina em 0x10000030. Com gp = 0x10000800, seu deslocamento é −2000, que cabe em um deslocamento de carga de 12 bits com sinal. Esses são cálculos separados de texto e dados.
Escolha como o código de biblioteca é fornecido
A ligação estática copia o código objeto de biblioteca necessário para o executável. O programa carrega esse código, aumentando o tamanho do arquivo; aplicar uma correção na biblioteca exige ligá-lo novamente. Bibliotecas estáticas não eliminam a dependência do programa em relação à interface de seu sistema operacional.
A ligação dinâmica mantém o código de biblioteca em arquivos compartilhados, como .so, .dll ou .dylib. Páginas compatíveis de código de biblioteca podem ser compartilhadas por processos. Uma biblioteca atualizada compatível pode atender a programas sem ligar novamente cada executável, mas a versão necessária deve estar disponível quando for usada.
Com a resolução tardia de símbolos em ELF, a primeira chamada passa por uma rotina intermediária da tabela de ligação de procedimentos (PLT). O ligador dinâmico resolve o destino e o registra na tabela global de deslocamentos (GOT); chamadas posteriores usam esse endereço registrado. Outras configurações resolvem símbolos no momento do carregamento.
Código independente de posição alcança destinos locais usando endereçamento relativo, incluindo sequências auipc, e usa indireção para destinos externos. As próprias distâncias relativas de uma biblioteca permanecem válidas quando ela se move como uma unidade. A resolução de referências externas ainda pode exigir trabalho de relocação.
O carregador cria o estado de execução
O carregador lê metadados do executável, cria um espaço de endereçamento, mapeia ou copia código e dados, prepara argumentos e a pilha inicial, inicializa o estado necessário dos registradores e transfere o controle para o código de inicialização. O código de inicialização chama main; após seu retorno, o código de execução solicita o encerramento do processo. Sistemas modernos podem mapear páginas do arquivo e trazê-las para a memória conforme necessário.
ecall solicita um serviço do ambiente. No Linux para RISC-V, a7 contém o número da chamada de sistema e a0–a5 contêm argumentos. li a7, 93, li a0, 0, ecall solicita o encerramento com status 0; o serviço 64 é write. Os números de serviço do simulador vêm da interface do próprio simulador.
Uma biblioteca ausente durante a ligação causa uma falha de ligação. Uma biblioteca compartilhada necessária removida após a ligação pode, por sua vez, impedir o carregamento ou causar uma falha durante a resolução em tempo de execução. O executável pode ter sido construído com sucesso mesmo que agora não consiga iniciar.
Principais conclusões
- O ligador organiza um executável, resolve e ajusta referências a símbolos.
- O carregador mapeia esse executável e cria o estado inicial do processo.
- A ligação estática copia código de biblioteca; a dinâmica precisa de bibliotecas compartilhadas compatíveis.
- A resolução tardia de símbolos registra um destino resolvido para chamadas posteriores.
- A independência de posição preserva referências relativas locais quando há movimentação.
- Os números de serviço de ecall pertencem ao ambiente de execução.
6Compromissos no projeto de conjuntos de instruções
Conjuntos de instruções fazem escolhas diferentes sobre quanto trabalho uma instrução expressa. Compare RISC-V e x86 por seus operandos, codificações e cálculos de endereços e depois relacione essas escolhas a restrições reais. O desempenho exige considerar juntos a contagem de instruções, o CPI e o período de clock; a densidade de código e a compatibilidade de software respondem a perguntas diferentes. Uma avaliação útil de projeto identifica a carga de trabalho e a implementação comparadas.
6.1 Comparar instruções RISC-V e x86
RISC e CISC descrevem abordagens amplas de projeto de conjuntos de instruções. A comparação útil é como cada uma representa o trabalho: posições dos operandos, comprimento das instruções, cálculo de endereços e fluxo de controle.
Operações regulares ou instruções mais ricas
Projetos RISC favorecem operações simples, muitos registradores e acesso por carga e armazenamento. A abordagem desenvolveu-se com o trabalho no IBM 801 em meados dos anos 1970 e com Berkeley RISC e Stanford MIPS no início dos anos 1980. RISC-V, ARM AArch64, MIPS e Power seguem essa tradição geral.
Projetos CISC oferecem instruções de comprimento variável, endereçamento mais rico e operações que podem combinar várias etapas. x86 cresceu a partir do 8086, lançado em 1978; famílias CISC anteriores incluem VAX e Motorola 68000.
Na sintaxe Intel x86, add eax, [ebx+8] lê um valor de 32 bits da memória e o soma a eax. Uma operação RISC-V equivalente precisa de uma carga seguida de uma operação aritmética:
lw t0, 8(s1)
add a0, a0, t0
A instrução x86 combina um acesso à memória com adição. RISC-V apresenta-os como instruções separadas. x86 chama um valor de 32 bits de palavra dupla; sua palavra tem 16 bits. Uma palavra nos exemplos RV32 tem 32 bits.
Codificação e endereçamento
Uma instrução x86 tem de 1 a 15 bytes. Instruções básicas RISC-V ocupam 4 bytes, com formas comprimidas de 2 bytes disponíveis por meio de C. Posições regulares dos campos simplificam a decodificação. Comprimentos variáveis podem economizar espaço usando codificações curtas para operações comuns.
RISC-V usa operandos em registradores, imediatos, endereços de base mais deslocamento e endereços relativos ao pc. jalr pertence ao endereçamento com registrador base, enquanto desvios, jal e auipc usam o pc.
x86 também combina uma base, um índice escalonado e um deslocamento. mov eax, [ebx + esi*4 + 8] lê um elemento indexado de 4 bytes com um deslocamento adicional em uma instrução. RISC-V forma o índice escalonado e o endereço efetivo com instruções separadas antes da carga. O trabalho ainda precisa ocorrer internamente em qualquer um dos processadores.
Registradores e comparações
RISC-V tem 32 registradores de inteiros, incluindo seu registrador zero fixo. O x86-64 convencional tem 16 registradores de propósito geral; o x86 de 32 bits tem 8. Muitas instruções aritméticas x86 usam um destino que também é uma origem: add eax, ebx altera eax. A instrução add de três operandos do RISC-V pode manter ambos os valores de origem enquanto escreve em um terceiro registrador.
Operações aritméticas x86 e cmp definem indicadores de condição; um desvio posterior, como jl, testa esses indicadores. RISC-V usa blt para comparar e desviar diretamente. ARM AArch64 também usa indicadores, portanto a presença deles, por si só, não pode classificar uma ISA como CISC.
O que significa reduzido
RISC não significa que uma ISA moderna deva ter uma contagem total muito pequena de instruções. RV32I tem 40 instruções básicas, mas extensões opcionais adicionam muitas outras. A ideia central são operações e codificações regulares e relativamente simples. Codificações comprimidas também mostram que o comprimento fixo das instruções é uma tendência de projeto, não uma definição que toda instrução RISC deva satisfazer.
Principais conclusões
- RISC-V separa o acesso à memória da aritmética.
- x86 pode combinar operandos em memória e aritmética em uma instrução.
- O comprimento das instruções e os modos de endereçamento afetam a decodificação e a densidade de código.
- RISC-V compara dentro de um desvio; x86 geralmente usa indicadores de condição.
- A contagem de instruções, por si só, não define um projeto RISC.
6.2 Avaliar velocidade, tamanho do código e compatibilidade
Um conjunto de instruções molda o trabalho que um programa expressa, mas a velocidade de execução também depende do processador que o implementa. Compare o comportamento medido ou modelado do programa, não o número de operações prometido por uma instrução.
Combine os três fatores de desempenho
O tempo de CPU é igual à contagem de instruções multiplicada pela média de ciclos por instrução (CPI) e pelo período de clock. De forma equivalente, divida a contagem de instruções multiplicada pelo CPI pela frequência de clock. Uma contagem menor de instruções pode ser superada pelo efeito de um CPI maior ou de um clock mais lento.
A 2 GHz, cada ciclo leva 0,5 ns. Uma versão com 1,0 bilhão de instruções e CPI 2,0 usa 2,0 bilhões de ciclos e leva 1,0 s. Outra com 1,3 bilhão de instruções e CPI 1,2 usa 1,56 bilhão de ciclos e leva 0,78 s. A segunda executa 30% mais instruções, mas é cerca de 1,28 vez mais rápida. O CPI é uma média para esse programa nessa implementação, não uma propriedade universal de uma ISA.
Instruções complexas ainda precisam de trabalho interno
Muitos processadores x86 modernos traduzem a maioria das instruções em micro-operações internas mais simples. Elas podem então usar técnicas de execução semelhantes às dos núcleos RISC modernos. A ISA externa ainda importa para codificação, decodificação e compatibilidade, mesmo quando as operações internas parecem semelhantes.
Uma instrução complexa não promete uma contagem fixa de ciclos. Uma instrução de cópia de bloco e um laço simples de cópia podem alternar posições em desempenho entre processadores e cargas de trabalho. A versão do processador, o tamanho dos dados e o comportamento da memória importam; uma listagem com uma única instrução no código-fonte não pode determinar o resultado.
A densidade de código é uma medida separada
Sequências menores de código de máquina podem reduzir o armazenamento necessário e a pressão sobre a cache de instruções. Os comprimentos variáveis de CISC frequentemente ajudam na densidade. A extensão C do RISC-V fornece codificações de 16 bits para operações comuns junto com instruções de 32 bits. A especificação relata uso típico em 50–60% das instruções, produzindo código cerca de 25–30% menor.
Para 100 instruções com metade comprimida, 50 usam 2 bytes e 50 usam 4: 300 bytes em vez de 400. A contagem de instruções executadas permanece 100. Uma redução de tamanho não determina, por si só, uma aceleração, pois o tempo de execução depende de mais do que os bytes buscados.
Compatibilidade e ferramentas influenciam a escolha
Binários e bibliotecas x86 existentes dão valor considerável à compatibilidade. RISC-V mantém fixos os conjuntos básicos e adiciona extensões. Código RV32I pode ser executado em uma implementação que suporte RV32I quando a ABI e o ambiente de execução são compatíveis; usar M ou V acrescenta esses requisitos. RV32E é um conjunto básico diferente, com menos registradores, e não promete compatibilidade com RV32I.
ARM é amplamente usada em dispositivos móveis, assim como em laptops e servidores; x86 é amplamente usada em PCs e servidores; RISC-V atende a sistemas embarcados, aceleradores, pesquisa e ensino. Disponibilidade de software, licenciamento e qualidade de chips específicos importam ao escolher uma plataforma.
Linguagem de montagem escrita à mão pode ajudar em uma pequena rotina crítica ou dar acesso a uma instrução especial, mas vincula o código a uma ISA e aumenta o trabalho de manutenção. Compiladores otimizadores lidam bem com alocação de registradores e reestruturação de código. Use medições para justificar a substituição de código-fonte claro por linguagem de montagem.
Principais conclusões
- Compare contagem de instruções, CPI e período de clock em conjunto.
- Micro-operações não alteram a ISA visível ao software.
- Codificações comprimidas reduzem bytes sem necessariamente reduzir as instruções executadas.
- A compatibilidade binária também exige ABI e ambiente compatíveis.
- Meça um programa e uma implementação específicos antes de afirmar que uma ISA é mais rápida.
Exercícios resolvidos de Linguagem de montagem RISC-V
1Acompanhar operandos de uma expressão até a memória
15 passos
- 1IntroduçãoAcompanhe um valor de cada vez
Uma instrução de montagem nomeia tanto sua operação quanto seus operandos. Comece com um cálculo legível em registradores, depois traga um valor da memória e armazene um resultado atualizado. Por fim, construa uma constante grande demais para um único imediato. Em cada etapa, acompanhe exatamente o que muda: um registrador de destino, um grupo de bytes na memória ou o endereço da próxima instrução.
- 2ExplicaçãoLeia o contrato da instrução
A ISA define o que
add a0, a1, a2deve fazer: ler as duas origens e escrever sua soma no destino. A microarquitetura determina como o chip realiza essa solicitação. A linguagem de montagem dá nomes legíveis à solicitação; o montador produz seus bits codificados. Chips diferentes que implementam o mesmo contrato de instrução devem concordar quanto ao resultado visível. - 3DesafioConte as instruções após a montagem
Um montador expande uma
liem duas instruções de máquina, seguidas de umaadd. Com apenas codificações de 32 bits, quantos bytes de instruções essas duas linhas de montagem produzem?---reveal---
Há três instruções reais, cada uma com 4 bytes, totalizando 12 bytes. A contagem de linhas de texto é dois, mas a expansão determina o tamanho do código de máquina. Rótulos e comentários não adicionam instruções executadas.
- 4ExemploMantenha a soma intermediária
Calcule
r = (u + v) - wcomu = 18,v = 7,w = 6ema1,a2,a3:add t0, a1, a2 sub a0, t0, a3Após a primeira instrução,
t0contém 25. Após a segunda,a0contém 19. As três entradas permanecem disponíveis porque nenhuma instrução escreve em seus registradores.t0tem uma finalidade: preservar o primeiro resultado até que a subtração o leia. - 5DesafioMude o destino, mantenha as origens
As mesmas entradas agora executam
sub a3, t0, a3após a adição. O que muda em comparação com escrever o resultado ema0?---reveal---
O resultado numérico ainda é 19, mas substitui o valor antigo 6 em
a3. A instrução lê os valores de origem antes de escrever em seu destino.a0permanece inalterado, portanto um chamador que espera o resultado nele não receberia esse novo resultado. - 6ExplicaçãoTransforme um índice em endereço
Um vetor de inteiros começa no endereço em
s1. Cadaintde RV32 ILP32 ocupa 4 bytes, portanto o elemento 2 começa 8 bytes após a base e o elemento 5 começa 20 bytes depois dela. A cargalw t0, 8(s1)lê o valor no elemento 2; ela não coloca o endereço em si emt0. O registrador base permanece inalterado. - 7ExemploCarregue, calcule, armazene
Implemente
A[5] = A[2] + rcomr = 19ema0:lw t0, 8(s1) add t0, t0, a0 sw t0, 20(s1)Se
A[2]é 11, a carga fornece 11, a adição fornece 30 e o armazenamento escreve 30 emA[5]. Ler e escrever na memória exigem instruções separadas em torno da operação aritmética. O valor deA[2]permanece 11. - 8DesafioIdentifique a unidade errada em um deslocamento
Alguém altera a primeira linha para
lw t0, 2(s1)porque a expressão nomeiaA[2]. Qual é o erro?---reveal---
O deslocamento é de 2 bytes, não de dois elementos. O elemento 2 precisa de 8 bytes a partir da base. O endereço alterado também não está naturalmente alinhado para uma palavra quando a base está alinhada, portanto o acesso pode falhar ou exigir tratamento especial antes mesmo de fornecer dados úteis.
- 9ExemploLeia um byte de uma palavra armazenada
Armazene
0x7193D2A6em um endereço alinhado em uma máquina little-endian. Seus bytes em ordem crescente de endereços são0xA6,0xD2,0x93,0x71. Uma carga de byte no deslocamento 1 seleciona0xD2.lbuproduz 210, enquantolbproduz −46 ao estender o bit 7 para as posições superiores do registrador. O byte na memória é idêntico; a instrução de carga fornece a interpretação. - 10ExplicaçãoEncontre a parte inferior com sinal
Agora construa
0x00003D20em um registrador. Seus 12 bits inferiores são0xD20, que representam −736 como imediato de 12 bits com sinal. A etapa superior deve, portanto, começar um bloco de 4096 bytes acima do que o0x3visível sugere. Escolher0x4produz0x4000, do qual subtrair0x2E0alcança o valor desejado. - 11ExemploConstrua e verifique a constante
Use:
lui t1, 0x4 addi t1, t1, -736A primeira instrução escreve 16.384; a segunda subtrai 736, deixando 15.648, ou
0x3D20. A parte inferior é uma parcela com sinal, não apenas dígitos a serem colados no lugar.li t1, 0x3D20permite que o montador escolha a construção, mas sua expansão ainda ocupa instruções reais. - 12DesafioCorrija a divisão de uma constante
Uma divisão usa
lui t1, 0x3eaddi t1, t1, -736. Qual é a diferença entre seu resultado e0x3D20, e qual campo precisa de correção?---reveal---
Ela produz
0x2D20, exatamente 4096 a menos. Aumente o campo superior de0x3para0x4e mantenha a parcela inferior com sinal. Mudar a parcela para o valor positivo0xD20excederia o intervalo positivo com sinal deaddi. - 13DicaAcompanhe um registrador até seu último uso
Ao acompanhar uma sequência, escreva um valor ao lado do registrador que o contém no momento. Risque-o apenas quando esse registrador for sobrescrito. Uma chamada de procedimento posterior apresenta risco de sobrescrita para registradores temporários e de argumentos, mesmo que nenhuma atribuição explícita apareça ao lado da chamada. Salve um valor ainda necessário antes de cruzar esse limite.
- 14DesafioCombine as etapas de endereço e valor
Com a base do vetor em
s1, o elementoA[6]é 17.t1contém a constante corretamente construída0x3D20. O quelw t0, 24(s1); add t0, t0, t1; sw t0, 4(s1)altera?---reveal---
Ela lê
A[6], soma 17 a 15.648 e armazena 15.665 emA[1]. Os dois deslocamentos contam bytes, enquanto a constante é um valor aritmético. O elemento original no índice 6 e o endereço base ems1permanecem inalterados. - 15RecapitulaçãoAcompanhe a próxima sequência por conta própria
Para uma nova sequência de montagem, marque os registradores lidos e escritos, expanda quaisquer pseudoinstruções cujo tamanho importe e traduza cada deslocamento de memória em bytes. Verifique a extensão de cargas pequenas separadamente da ordem de bytes. Ao construir uma constante, confira o valor superior intermediário mais sua parcela inferior com sinal antes de usar o resultado como dado ou endereço.
2Acompanhar a aritmética em decisões e laços
14 passos
Salve esta aula para seguir cada Lição Interativa passo a passo.
- 1IntroduçãoTorne o caminho de execução visível
- 2ExemploMova o campo para a posição
- 3ExemploMantenha apenas a largura do campo
- 4DesafioPreveja um deslocamento com sinal
- 5ExplicaçãoCompare usando a interpretação correta
- 6ExemploDiferencie um salto de uma chamada
- 7DesafioTeste um padrão de duas maneiras
- 8ExemploDesenhe os dois caminhos de um if
- 9ExplicaçãoColoque o limite antes da carga
- 10ExemploAcompanhe três execuções do corpo
- 11DesafioConte todo o caminho executado
- 12ConexãoSubstitua a multiplicação repetida por um endereço que avança
- 13DesafioProteja uma carga com controle de curto-circuito
- 14RecapitulaçãoVerifique valores e caminhos em conjunto
3Codificar uma instrução e reconstruir um desvio
12 passos
Salve esta aula para seguir cada Lição Interativa passo a passo.
- 1IntroduçãoTraduza nos dois sentidos
- 2ExplicaçãoIdentifique o formato e os operandos
- 3ExemploEscreva os campos com largura completa
- 4ExemploConverta e decodifique a palavra
- 5DesafioLocalize a origem do valor de um armazenamento
- 6ExplicaçãoMeça a distância para trás
- 7ExemploDivida o imediato do desvio
- 8ExemploMonte o desvio completo
- 9DesafioRecupere o bit zero ausente
- 10ConexãoMantenha a distância relativa quando o código se move
- 11DesafioUse o código de operação para rejeitar uma decodificação falsa
- 12RecapitulaçãoVerifique uma palavra de máquina no sentido inverso
4Manter valores disponíveis durante chamadas aninhadas
15 passos
Salve esta aula para seguir cada Lição Interativa passo a passo.
- 1IntroduçãoProteja o que o chamador ainda precisa
- 2ExemploEspecifique o resultado do procedimento envoltório
- 3ExplicaçãoAplique as obrigações de preservação
- 4DesafioEscolha o estado que deve ser preservado durante a chamada
- 5ExemploAloque e preencha o quadro
- 6ExemploCombine resultados antes de liberar o armazenamento
- 7ExplicaçãoRepita o padrão recursivamente
- 8DesafioDesempilhe os argumentos salvos
- 9ExplicaçãoSepare um endereço de seu objeto
- 10ExemploEscolha operações de byte para uma cadeia de caracteres
- 11DesafioTermine nos endereços corretos
- 12DicaRestaure o valor salvo, não o novo resultado
- 13ConexãoO tempo de vida do objeto acompanha o quadro
- 14DesafioMantenha um ponteiro de destino durante uma chamada a um auxiliar
- 15RecapitulaçãoExamine uma chamada completa
5Acompanhar uma chamada do código-fonte à inicialização
11 passos
Salve esta aula para seguir cada Lição Interativa passo a passo.
- 1IntroduçãoPergunte o que cada ferramenta sabe
- 2ExplicaçãoCompile para uma ISA de destino
- 3ExemploDeixe a chamada externa inacabada
- 4DesafioDiferencie a emissão de dados da execução
- 5ExemploPosicione o módulo de destino
- 6ExemploAjuste a referência relativa
- 7ExplicaçãoEscolha quando fornecer o código de biblioteca
- 8DesafioLocalize a etapa da falha
- 9ConexãoCrie um processo antes de chamar main
- 10DesafioExplique por que um objeto não é o programa concluído
- 11RecapitulaçãoAcompanhe um novo erro até sua etapa
6Comparar projetos usando a medida correta
10 passos
Salve esta aula para seguir cada Lição Interativa passo a passo.
- 1IntroduçãoMantenha três medidas separadas
- 2ExemploCompare a operação de memória visível
- 3ExplicaçãoRelacione a codificação à escolha de projeto
- 4DesafioClassifique uma característica com cuidado
- 5ExemploCalcule o primeiro tempo de execução
- 6ExemploCompare uma contagem maior de instruções
- 7DesafioSepare a economia de bytes da aceleração
- 8ConexãoA compatibilidade vai além da ISA
- 9DesafioAvalie uma afirmação sobre otimização em linguagem de montagem
- 10RecapitulaçãoApresente as evidências da comparação
Questões de Linguagem de montagem RISC-V
Conjunto 1: Registradores, memória e constantes10 Questões de Prática
Questão 1 de 10
Mostrar respostaOcultar resposta
add, portanto ambas as implementações devem produzir esse resultado. Seus pipelines internos podem ser diferentes. A frequência de clock afeta o tempo, mas não redefine o significado da adição para um programa.Questão 2 de 10
sub a2, a0, a1, a0 contém 28 e a1 contém 9. Qual valor é escrito em a2?Mostrar respostaOcultar resposta
a2. Inverter as origens produziria −19, mas essa não é a ordem delas aqui.Questão 3 de 10
A[7]?Mostrar respostaOcultar resposta
Questão 4 de 10
sw armazena 0x86C4217D em 0x3000. Qual valor lbu t0, 2(s0) produz quando s0 = 0x3000?Mostrar respostaOcultar resposta
0x7D, 0x21, 0xC4 e 0x86 em endereços crescentes. O deslocamento 2 seleciona 0xC4, e lbu faz sua extensão com zeros para 0x000000C4. O resultado com extensão de sinal exigiria lb, não a carga de byte sem sinal usada aqui.Questão 5 de 10
lui e qual imediato com sinal de addi constroem 0x00002ABC em RV32?Mostrar respostaOcultar resposta
0xABC representa −1348. Começar de 0x3000 e subtrair 1348 alcança 0x2ABC, portanto o campo superior é 0x3. Tratar 2748 como operando positivo de addi falha porque ele excede o limite positivo de 12 bits com sinal.Questão 6 de 10
t3, chama um auxiliar que segue a convenção e depois precisa do valor original. O que o chamador deve providenciar?Mostrar respostaOcultar resposta
t3 é preservado pelo chamador, portanto um auxiliar que segue a convenção pode sobrescrevê-lo. O chamador deve salvar o valor ativo ou providenciar um local preservado equivalente antes da chamada. O auxiliar não tem obrigação de restaurar registradores temporários apenas porque seu chamador precisará deles.Questão 7 de 10
auipc t0, 0x2 é executada em 0x4000, seguida de addi t0, t0, -24. Qual é o valor final em t0?Mostrar respostaOcultar resposta
auipc soma 0x2000 ao seu próprio endereço, produzindo 0x6000. Subtrair 24 deixa 0x5FE8. Usar o endereço da instrução seguinte como base do pc aumentaria incorretamente o resultado em 4 bytes.Questão 8 de 10
0xB2 é carregado uma vez com lb e outra com lbu. Quais resultados decimais as duas cargas produzem, nessa ordem?Mostrar respostaOcultar resposta
0xB2 é 178 menos 256, ou −78. lb faz extensão de sinal desse valor, enquanto lbu produz 178 sem sinal. Tornar ambos os resultados positivos ignora a regra de extensão de sinal da carga com sinal.Questão 9 de 10
struct rec { int key; int value; }; em RV32 ILP32, sem preenchimento adicional. Sua base é 0x5000. Qual endereço é lido para A[3].value e qual byte com sinal lb retorna desse endereço se o campo contém 0x000000E9 na memória little-endian?Mostrar respostaOcultar resposta
0x501C. O primeiro byte nesse endereço é 0xE9, que lb estende com sinal para −23. Retornar 233 aplicaria a regra de extensão sem sinal.Questão 10 de 10
Em RV32, a sequência abaixo é executada com t1 = 0x7000; a palavra em 0x7030 é 6. Qual valor é armazenado em 0x7034?
lui t0, 0x4
addi t0, t0, -1984
lw t2, 48(t1)
add t2, t2, t0
sw t2, 52(t1)
Mostrar respostaOcultar resposta
0x4000 menos 0x7C0, ou 0x3840. A carga fornece 6, portanto a adição produz 0x3846, que o armazenamento final escreve. 0x3840 é apenas a constante construída e omite o operando carregado.Conjunto 2: Aritmética e caminhos em um programa10 Questões de Prática
Salve esta aula para ver todas as respostas e praticar cada conjunto.
Questão 1 de 10
add soma 1 a 0xFFFFFFFF?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 2 de 10
bgt a2, a3, Done?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 3 de 10
srli t0, a0, 8 e andi t0, t0, 0x1F, qual valor está em t0 se a0 = 0x0000D6B9?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 4 de 10
a0 = -11 com sinal, quais resultados srai t0, a0, 2 e a divisão inteira em C -11 / 4 produzem, respectivamente?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 5 de 10
a0 = 0xFFFFFFFE e a1 = 0x00000003. Qual das instruções blt a0, a1, L e bltu a0, a1, L realiza o desvio?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 6 de 10
if (x != y) r = 8; else r = 3; desvia para Else quando há igualdade. O que vem após a atribuição de 8 para que r permaneça 8 no caminho de desigualdade?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 7 de 10
while (A[i] == 6) i++; começa em i = 1 com A = {6, 6, 6, 4, 6}. Qual é o valor de i quando ele termina?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 8 de 10
Um procedimento folha executa o código abaixo em um vetor de inteiros de 4 bytes {2, -5, 9}, com sua base em a0 e a contagem 3 em a1. Cada instrução exibida é uma instrução de máquina. Qual resultado e qual contagem dinâmica de instruções ele produz?
addi t0, zero, 0
addi t1, zero, 0
L: bge t0, a1, D
slli t2, t0, 2
add t2, a0, t2
lw t3, 0(t2)
add t1, t1, t3
addi t0, t0, 1
jal zero, L
D: addi a0, t1, 0
jalr zero, 0(ra)
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 9 de 10
0x8000, jalr ra, 7(t0) é executada com t0 = 0x903A. Qual destino é usado, o que é colocado em ra e qual instrução pode retornar por esse enlace?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 10 de 10
p != 0 && *p == 9, com p em a0. Para o caminho then, qual ordem de fluxo de controle preserva a condição e evita desreferenciar um ponteiro nulo?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Conjunto 3: Campos de instrução e destinos relativos10 Questões de Prática
Salve esta aula para ver todas as respostas e praticar cada conjunto.
Questão 1 de 10
sw a2, 20(sp), qual registrador fornece o campo rs1?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 2 de 10
addi t2, s1, -36?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 3 de 10
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 4 de 10
0x308C tem como destino 0x3068. Qual deslocamento em bytes com sinal é codificado?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 5 de 10
jal em 0x1200 tem deslocamento com sinal +84 e destino ra. Quais são o endereço de destino e o endereço de retorno resultantes?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 6 de 10
addi t2, s1, -36? Aqui t2 = x7, s1 = x9, o código de operação ADDI é 0x13 e funct3 = 000.Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 7 de 10
0xFEC12623. O código de operação 0x23 seleciona um armazenamento e funct3 = 010 seleciona uma palavra. Qual instrução de montagem ela representa?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 8 de 10
bne a2, zero, Loop do tipo B está em 0x4018, com Loop em 0x400C. Usando o código de operação 0x63 e funct3 = 001, qual palavra codificada alcança Loop?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 9 de 10
0x6000, a palavra 0x000022B7 é seguida por 0xFE028293 em 0x6004. Qual valor permanece em t0? Elas são decodificadas como LUI do tipo U e ADDI do tipo I, respectivamente.Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 10 de 10
0x30 e 0x54. A seção move-se de 0x9000 para 0xB000 sem alterar sua organização interna. Quais são o deslocamento do desvio e o endereço de destino resultantes?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Conjunto 4: Chamadas, recursão e acesso por ponteiros10 Questões de Prática
Salve esta aula para ver todas as respostas e praticar cada conjunto.
Questão 1 de 10
s4. Qual valor s4 deve conter quando ele retorna?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 2 de 10
a0 para o próximo elemento quando ele contém um int *?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 3 de 10
sp = 0x8000, aloca 32 bytes e depois executa sw ra, 28(sp). Em qual endereço começa a palavra salva?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 4 de 10
a0, calcula em s1 e depois restaura o s1 recebido antes de retornar. O que ele deve fazer antes dessa restauração para retornar o inteiro calculado?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 5 de 10
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 6 de 10
ra e s2, cada um com uma palavra. Qual é o menor tamanho positivo de quadro que preserva o alinhamento exigido da pilha?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 7 de 10
n em a0 e deve retornar helper(n) + n. helper segue a ABI e pode sobrescrever todos os registradores preservados pelo chamador. O ra recebido pela função também deve ser preservado. Qual plano funciona com um quadro de 16 bytes?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 8 de 10
n e ra, retorna 1 para n < 2 e, caso contrário, retorna n * f(n - 1). Com n = 4 e sp = 0x9000 iniciais, quais são o resultado e o menor valor de sp?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 9 de 10
0xA000 contém o valor de ponteiro 0xB000. Em 0xB000 começa um vetor de inteiros de 4 bytes, com o elemento 4 igual a 27. Com a0 = 0xA000, qual sequência deixa esse elemento em a0?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 10 de 10
ra em 28(sp) e s0 em 24(sp) e depois chama um auxiliar. Sua saída primeiro soma 16 a sp, depois carrega de 24(sp) e 28(sp), soma mais 16 e retorna. Por que isso pode falhar mesmo que sp termine com seu valor original?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Conjunto 5: Construir, ligar e iniciar um programa10 Questões de Prática
Salve esta aula para ver todas as respostas e praticar cada conjunto.
Questão 1 de 10
gcc -c module.c produz sem fazer a ligação?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 2 de 10
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 3 de 10
main, mas chama mix, definida em um segundo objeto. O que permite ao ligador conectar essa chamada à definição?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 4 de 10
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 5 de 10
filter de uma biblioteca. O arquivo da biblioteca é então corrigido, mas o executável permanece intacto. Qual código o executável contém para filter?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 6 de 10
a7, 7 em a0 e depois executa ecall. Qual serviço é solicitado?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 7 de 10
0x20000 e dados em 0x30000000. O módulo P contribui com 0x180 bytes de texto e 0x30 bytes de dados; Q vem imediatamente em seguida. Uma chamada em P está no deslocamento de texto 0x70; seu destino em Q está no deslocamento de texto 0x20 de Q. A variável de Q está no deslocamento de dados 0x14 de Q. Quais são o deslocamento em bytes da chamada e o endereço da variável?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 8 de 10
0x1000 contém as seguintes escolhas de expansão: li t0, 5000 usa duas instruções, mv a0, t0 usa uma e call Helper usa um par de duas instruções sem redução. Helper retorna normalmente. Em qual endereço a execução continua após a chamada e quantos bytes de código precedem essa continuação?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 9 de 10
0x2C0 em uma biblioteca compartilhada. Um chamador local está no deslocamento 0x90; um ponteiro de função fora da biblioteca é armazenado em uma posição da GOT. A base da biblioteca muda de 0x40000000 para 0x50000000. Qual descrição trata corretamente ambas as referências?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 10 de 10
main. O que explica a falha e o que mudaria para o código dessa biblioteca incluído estaticamente?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Conjunto 6: Avaliar projetos e analisar linguagem de montagem10 Questões de Prática
Salve esta aula para ver todas as respostas e praticar cada conjunto.
Questão 1 de 10
add eax, [ebx+12], com eax correspondendo a a0 e ebx a s1?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 2 de 10
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 3 de 10
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 4 de 10
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 5 de 10
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 6 de 10
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 7 de 10
mul, espera a convenção de chamada ILP32 e invoca chamadas de sistema do Linux para RISC-V. Uma placa anuncia RV32I, usa ILP32 e executa um simulador com sua própria interface de serviços. Quais requisitos adicionais devem ser atendidos antes que esse binário possa ser executado como esperado?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 8 de 10
struct sample { int id; int reading; }; sem preenchimento adicional. Um ponteiro para o elemento 2 está em a0. O campo reading do próximo elemento é 31. Qual cálculo de endereço e carga recuperam esse campo sem alterar a0?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 9 de 10
Este procedimento folha deve retornar a soma de n inteiros, com n não negativo. Ele é chamado com n = 0 e um endereço que não deve ser lido. Qual correção mínima no fluxo de controle evita o acesso inválido preservando o comportamento para contagens positivas?
addi t1, zero, 0
L: lw t0, 0(a0)
add t1, t1, t0
addi a0, a0, 4
addi a1, a1, -1
bne a1, zero, L
D: addi a0, t1, 0
ret
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 10 de 10
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Travou em uma questão sua? Ajuda com Tarefas
Termos-Chave
A
Alinhamento natural
Alocação de registradores
Arquitetura de carga e armazenamento
Arquitetura do conjunto de instruções
Arquivo objeto relocável
-c para antes da combinação de bibliotecas e módulos necessária para um executável.Avaliação de curto-circuito
&& impede a avaliação do segundo operando em C.B
Bit implícito
Bloco básico
C
Campo de função
funct7.Campo de registrador
a0 como registrador número 10, escrito 01010 em binário.Carregador
Carregamento de endereço
la t0, Item, é necessária uma carga separada para ler o valor contido em Item.Chamada de sistema
ecall com 93 em a7 solicita o encerramento, usando a0 para o status.Ciclos por instrução
CISC
Código de operação
Código independente de posição
Comparação menor que
slt e sltu podem discordar quando o bit mais significativo de uma origem lhe dá valores diferentes com e sem sinal.Compatibilidade binária
Compilação em tempo de execução
Compilador
Conceito de programa armazenado
Contador de programa
Contagem dinâmica de instruções
Continuação sequencial
Convenção de chamada
a0 a a7.D
Densidade de código
Derramamento de registradores
Deslocamento aritmético à direita
Deslocamento de campo de estrutura
Deslocamento do desvio
Deslocamento lógico à direita
Desmontagem
Desreferenciamento
lw t0, 0(a0) lê um inteiro de 4 bytes pelo endereço atualmente contido em a0.Desvio condicional
bne t0, t1, Loop retorna a Loop apenas enquanto os dois padrões de registradores forem diferentes.Diretiva do montador
.word 5 emite um valor de 4 bytes, enquanto .text seleciona onde o código subsequente pertence.E
Endereçamento por byte
Endereçamento por deslocamento
lw t0, 20(s1), a palavra carregada começa 20 bytes além do endereço armazenado em s1.Endereçamento por índice escalonado
Endereço relativo ao PC
auipc pode alcançar dados locais mesmo quando todo o grupo de código e dados se move.Epílogo
sp mantém seus deslocamentos relativos ao quadro alocado.Estouro
Extensão com zeros
0xF0 com lbu produz o valor positivo 240 em um registrador de 32 bits.Extensão de sinal
0xF0 com lb produz 0xFFFFFFF0, a representação de 32 bits de −16.F
Formato de instrução
G
Grupo de quatro bits
I
Imediato
addi t0, t0, 1 é codificado na instrução como um imediato.Imediato superior
lui coloca acima de 12 zeros inferiores ao construir um valor de registrador RV32.addi seguinte pode fornecer uma parte inferior com sinal para completar uma constante de 32 bits.Indicadores de condição
Instrução comprimida
Interface binária de aplicação
L
Ligação dinâmica
Ligação estática
Ligador
Limite do laço
Linguagem de montagem
add a0, a1, a2 nomeia uma operação que tem uma codificação definida em código de máquina RISC-V.M
Máscara de bits
0xFF mantém o byte inferior enquanto zera todas as posições superiores do registrador.Micro-operação
Microarquitetura
Montador
call e pode deixar entradas de relocação para uma função definida em outro módulo.O
Ordem de bytes little-endian
0x12345678 coloca 0x78 no primeiro byte e 0x12 no quarto.P
Passo entre elementos
Ponteiro
Ponteiro de pilha
sp reserva um quadro alinhado em endereços menores na convenção RISC-V.Procedimento folha
Prólogo
ra antes de uma chamada aninhada protege o endereço necessário para retornar ao chamador externo.Pseudoinstrução
li depende do valor de seu operando constante.Q
Quadro de pilha
R
Recursão
Registrador de inteiros
Registrador preservado pelo chamado
s1 internamente se restaurar o s1 original do chamador antes de retornar.Registrador preservado pelo chamador
t2 precisa ser salvo antes de um auxiliar que pode usar esse registrador.Registrador zero
zero quando o programa não precisa salvar um endereço de retorno.Relocação relativa
Resolução tardia de símbolos
RISC
S
Salto com enlace
jal ra, Helper registra onde o chamador deve continuar quando Helper retornar.Salto indireto
jalr pode implementar uma chamada por ponteiro de função, um retorno ou uma seleção por tabela de saltos.T
Tabela de saltos
Tabela de símbolos
Tempo de vida do objeto
Terminador nulo
- 1Desempenho de computadores
- 2Linguagem de montagem RISC-V
- 3Aritmética computacional
- 4Caminho de dados e pipeline
- 5Hierarquia de memória e caches
- 6Processadores paralelos
Sobre esta aula
Crie seus próprios Materiais de Estudo
Transforme suas anotações, slides e documentos em Guias de Estudo, Questões de Prática, Flashcards e mais 6 ferramentas.