Linguagem de montagem RISC-V e conjuntos de instruções

Aprenda como o software se comunica com o hardware por meio de um conjunto de instruções. Usando RISC-V, leia e escreva linguagem de montagem com registradores, operandos em memória, constantes, operações aritméticas e lógicas e desvios, e traduza expressões, decisões, laços, ponteiros e cadeias de caracteres em C para instruções. Codifique instruções como código de máquina de 32 bits e decodifique-as novamente, construa chamadas de procedimentos com a pilha e acompanhe um programa pela compilação, montagem, ligação e carregamento. Por fim, compare RISC-V com conjuntos de instruções complexos como x86 e avalie o impacto do projeto do conjunto de instruções no desempenho.

IntermediárioAula 2 de 6 do curso Curso de Organização e arquitetura de computadores

O que você vai aprender

  1. 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ções
      Descrever 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ões
      Identificar 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ória
      Usar 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 imediatos
      Usar 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.
  2. 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 deslocamento
      Calcular 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 incondicionais
      Usar 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 montagem
      Transformar 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.
  3. 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 armazenamento
      Organizar 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áquina
      Codificar 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.
  4. 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 chamada
      Passar 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 recursivos
      Alocar 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 montagem
      Transformar 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.
  5. 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 objeto
      Descrever 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ável
      Descrever 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.
  6. 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 CISC
      Descrever 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 compatibilidade
      Usar 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.

Guia de estudo de Linguagem de montagem RISC-V

6 capítulos

1

Instruçõ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.

RegistradoresNomes da ABIFinalidade convencional
x0zeroSempre é lido como 0
x1, x2ra, spEndereço de retorno e ponteiro de pilha
x3, x4gp, tpPonteiros global e de thread
x5–x7, x28–x31t0–t6Valores temporários
x8–x9, x18–x27s0–s11Valores preservados durante chamadas
x10–x17a0–a7Argumentos; 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:

TextoInstrução básica ou expansão
mv rd, rsaddi rd, rs, 0
nopaddi x0, x0, 0
not rd, rsxori rd, rs, -1
neg rd, rssub rd, x0, rs
li rd, c, para um c pequenoaddi 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.
2

Aritmé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çãoCondição de desvio
beq, bneIgual, diferente
blt, bgeMenor que com sinal, maior ou igual com sinal
bltu, bgeuMenor 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.
3

Campos 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 deslocamentoBits 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.
4

Chamadas 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.
5

Do 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.
6

Compromissos 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

6 Lições Interativas

1

Acompanhar operandos de uma expressão até a memória

15 passos
Ler uma instrução, acompanhar valores temporários, atualizar um vetor e construir uma constante maior.
  1. 1
    Introduçã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.

  2. 2
    ExplicaçãoLeia o contrato da instrução

    A ISA define o que add a0, a1, a2 deve 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.

  3. 3
    DesafioConte as instruções após a montagem

    Um montador expande uma li em duas instruções de máquina, seguidas de uma add. 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.

  4. 4
    ExemploMantenha a soma intermediária

    Calcule r = (u + v) - w com u = 18, v = 7, w = 6 em a1, a2, a3:

    add t0, a1, a2
    sub a0, t0, a3
    

    Após a primeira instrução, t0 contém 25. Após a segunda, a0 contém 19. As três entradas permanecem disponíveis porque nenhuma instrução escreve em seus registradores. t0 tem uma finalidade: preservar o primeiro resultado até que a subtração o leia.

  5. 5
    DesafioMude o destino, mantenha as origens

    As mesmas entradas agora executam sub a3, t0, a3 após a adição. O que muda em comparação com escrever o resultado em a0?

    ---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. a0 permanece inalterado, portanto um chamador que espera o resultado nele não receberia esse novo resultado.

  6. 6
    ExplicaçãoTransforme um índice em endereço

    Um vetor de inteiros começa no endereço em s1. Cada int de 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 carga lw t0, 8(s1) lê o valor no elemento 2; ela não coloca o endereço em si em t0. O registrador base permanece inalterado.

  7. 7
    ExemploCarregue, calcule, armazene

    Implemente A[5] = A[2] + r com r = 19 em a0:

    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 em A[5]. Ler e escrever na memória exigem instruções separadas em torno da operação aritmética. O valor de A[2] permanece 11.

  8. 8
    DesafioIdentifique a unidade errada em um deslocamento

    Alguém altera a primeira linha para lw t0, 2(s1) porque a expressão nomeia A[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.

  9. 9
    ExemploLeia um byte de uma palavra armazenada

    Armazene 0x7193D2A6 em um endereço alinhado em uma máquina little-endian. Seus bytes em ordem crescente de endereços são 0xA6, 0xD2, 0x93, 0x71. Uma carga de byte no deslocamento 1 seleciona 0xD2. lbu produz 210, enquanto lb produz −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.

  10. 10
    ExplicaçãoEncontre a parte inferior com sinal

    Agora construa 0x00003D20 em um registrador. Seus 12 bits inferiores são 0xD20, 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 o 0x3 visível sugere. Escolher 0x4 produz 0x4000, do qual subtrair 0x2E0 alcança o valor desejado.

  11. 11
    ExemploConstrua e verifique a constante

    Use:

    lui t1, 0x4
    addi t1, t1, -736
    

    A 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, 0x3D20 permite que o montador escolha a construção, mas sua expansão ainda ocupa instruções reais.

  12. 12
    DesafioCorrija a divisão de uma constante

    Uma divisão usa lui t1, 0x3 e addi t1, t1, -736. Qual é a diferença entre seu resultado e 0x3D20, e qual campo precisa de correção?

    ---reveal---

    Ela produz 0x2D20, exatamente 4096 a menos. Aumente o campo superior de 0x3 para 0x4 e mantenha a parcela inferior com sinal. Mudar a parcela para o valor positivo 0xD20 excederia o intervalo positivo com sinal de addi.

  13. 13
    DicaAcompanhe 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.

  14. 14
    DesafioCombine as etapas de endereço e valor

    Com a base do vetor em s1, o elemento A[6] é 17. t1 contém a constante corretamente construída 0x3D20. O que lw 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 em A[1]. Os dois deslocamentos contam bytes, enquanto a constante é um valor aritmético. O elemento original no índice 6 e o endereço base em s1 permanecem inalterados.

  15. 15
    Recapitulaçã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.

2

Acompanhar a aritmética em decisões e laços

14 passos
Extrair um campo, escolher comparações com sinal e acompanhar todos os caminhos de um laço com contagem.

Salve esta aula para seguir cada Lição Interativa passo a passo.

  1. 1
    IntroduçãoTorne o caminho de execução visível
  2. 2
    ExemploMova o campo para a posição
  3. 3
    ExemploMantenha apenas a largura do campo
  4. 4
    DesafioPreveja um deslocamento com sinal
  5. 5
    ExplicaçãoCompare usando a interpretação correta
  6. 6
    ExemploDiferencie um salto de uma chamada
  7. 7
    DesafioTeste um padrão de duas maneiras
  8. 8
    ExemploDesenhe os dois caminhos de um if
  9. 9
    ExplicaçãoColoque o limite antes da carga
  10. 10
    ExemploAcompanhe três execuções do corpo
  11. 11
    DesafioConte todo o caminho executado
  12. 12
    ConexãoSubstitua a multiplicação repetida por um endereço que avança
  13. 13
    DesafioProteja uma carga com controle de curto-circuito
  14. 14
    RecapitulaçãoVerifique valores e caminhos em conjunto
3

Codificar uma instrução e reconstruir um desvio

12 passos
Colocar operandos em campos, verificar uma palavra codificada e reconstruir um deslocamento de desvio distribuído.

Salve esta aula para seguir cada Lição Interativa passo a passo.

  1. 1
    IntroduçãoTraduza nos dois sentidos
  2. 2
    ExplicaçãoIdentifique o formato e os operandos
  3. 3
    ExemploEscreva os campos com largura completa
  4. 4
    ExemploConverta e decodifique a palavra
  5. 5
    DesafioLocalize a origem do valor de um armazenamento
  6. 6
    ExplicaçãoMeça a distância para trás
  7. 7
    ExemploDivida o imediato do desvio
  8. 8
    ExemploMonte o desvio completo
  9. 9
    DesafioRecupere o bit zero ausente
  10. 10
    ConexãoMantenha a distância relativa quando o código se move
  11. 11
    DesafioUse o código de operação para rejeitar uma decodificação falsa
  12. 12
    RecapitulaçãoVerifique uma palavra de máquina no sentido inverso
4

Manter valores disponíveis durante chamadas aninhadas

15 passos
Construir um procedimento envoltório correto, acompanhar quadros recursivos e acompanhar uma cadeia de bytes pela memória.

Salve esta aula para seguir cada Lição Interativa passo a passo.

  1. 1
    IntroduçãoProteja o que o chamador ainda precisa
  2. 2
    ExemploEspecifique o resultado do procedimento envoltório
  3. 3
    ExplicaçãoAplique as obrigações de preservação
  4. 4
    DesafioEscolha o estado que deve ser preservado durante a chamada
  5. 5
    ExemploAloque e preencha o quadro
  6. 6
    ExemploCombine resultados antes de liberar o armazenamento
  7. 7
    ExplicaçãoRepita o padrão recursivamente
  8. 8
    DesafioDesempilhe os argumentos salvos
  9. 9
    ExplicaçãoSepare um endereço de seu objeto
  10. 10
    ExemploEscolha operações de byte para uma cadeia de caracteres
  11. 11
    DesafioTermine nos endereços corretos
  12. 12
    DicaRestaure o valor salvo, não o novo resultado
  13. 13
    ConexãoO tempo de vida do objeto acompanha o quadro
  14. 14
    DesafioMantenha um ponteiro de destino durante uma chamada a um auxiliar
  15. 15
    RecapitulaçãoExamine uma chamada completa
5

Acompanhar uma chamada do código-fonte à inicialização

11 passos
Acompanhar um símbolo pela compilação, montagem, ligação, resolução dinâmica e carregamento do processo.

Salve esta aula para seguir cada Lição Interativa passo a passo.

  1. 1
    IntroduçãoPergunte o que cada ferramenta sabe
  2. 2
    ExplicaçãoCompile para uma ISA de destino
  3. 3
    ExemploDeixe a chamada externa inacabada
  4. 4
    DesafioDiferencie a emissão de dados da execução
  5. 5
    ExemploPosicione o módulo de destino
  6. 6
    ExemploAjuste a referência relativa
  7. 7
    ExplicaçãoEscolha quando fornecer o código de biblioteca
  8. 8
    DesafioLocalize a etapa da falha
  9. 9
    ConexãoCrie um processo antes de chamar main
  10. 10
    DesafioExplique por que um objeto não é o programa concluído
  11. 11
    RecapitulaçãoAcompanhe um novo erro até sua etapa
6

Comparar projetos usando a medida correta

10 passos
Distinguir instruções visíveis, bytes codificados e tempo de execução ao avaliar uma escolha de ISA.

Salve esta aula para seguir cada Lição Interativa passo a passo.

  1. 1
    IntroduçãoMantenha três medidas separadas
  2. 2
    ExemploCompare a operação de memória visível
  3. 3
    ExplicaçãoRelacione a codificação à escolha de projeto
  4. 4
    DesafioClassifique uma característica com cuidado
  5. 5
    ExemploCalcule o primeiro tempo de execução
  6. 6
    ExemploCompare uma contagem maior de instruções
  7. 7
    DesafioSepare a economia de bytes da aceleração
  8. 8
    ConexãoA compatibilidade vai além da ISA
  9. 9
    DesafioAvalie uma afirmação sobre otimização em linguagem de montagem
  10. 10
    RecapitulaçãoApresente as evidências da comparação

Questões de Linguagem de montagem RISC-V

60 Questões de Prática em 6 conjuntos

Conjunto 1: Registradores, memória e constantes10 Questões de Prática

Questão 1 de 10

Dois processadores implementam RV32I usando pipelines internos diferentes. Qual especificação compartilhada determina o resultado de uma instrução add RV32I?
  1. A organização de processos do sistema operacional
  2. A arquitetura do conjunto de instruções
  3. A alocação de registradores do compilador
  4. A frequência de clock do processador
Mostrar respostaOcultar resposta
Resposta: B
A ISA define o efeito visível de 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

Antes de sub a2, a0, a1, a0 contém 28 e a1 contém 9. Qual valor é escrito em a2?
  1. 37
  2. −19
  3. 9
  4. 19
Mostrar respostaOcultar resposta
Resposta: D
O primeiro operando é o destino, e a subtração calcula o segundo operando menos o terceiro. A instrução, portanto, escreve 28 menos 9, ou 19, em a2. Inverter as origens produziria −19, mas essa não é a ordem delas aqui.

Questão 3 de 10

Um vetor RV32 ILP32 contém inteiros de 4 bytes. Quantos bytes após a base começa o elemento A[7]?
  1. 7 bytes
  2. 11 bytes
  3. 28 bytes
  4. 24 bytes
Mostrar respostaOcultar resposta
Resposta: C
Cada elemento anterior ocupa 4 bytes, portanto o índice 7 começa a 28 bytes da base. Usar 7 como deslocamento em bytes confundiria um índice de elemento com um deslocamento de endereço. O índice zero já está na base e não adiciona outro elemento à contagem.

Questão 4 de 10

Na memória little-endian, sw armazena 0x86C4217D em 0x3000. Qual valor lbu t0, 2(s0) produz quando s0 = 0x3000?
  1. 0x000000C4
  2. 0x00000021
  3. 0xFFFFFFC4
  4. 0x00000086
Mostrar respostaOcultar resposta
Resposta: A
Os bytes armazenados são 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

Qual campo superior de lui e qual imediato com sinal de addi constroem 0x00002ABC em RV32?
  1. 0x2 e +2748
  2. 0x2 e −1348
  3. 0x3 e −1348
  4. 0x3 e +2748
Mostrar respostaOcultar resposta
Resposta: C
O padrão inferior de 12 bits 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

Um chamador mantém um valor em t3, chama um auxiliar que segue a convenção e depois precisa do valor original. O que o chamador deve providenciar?
  1. Restaurar t3 apenas dentro do auxiliar
  2. Tratar t3 como registrador preservado pelo chamado
  3. Salvar t3 após o retorno do auxiliar
  4. Proteger o valor antes da chamada
Mostrar respostaOcultar resposta
Resposta: D
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

Em RV32, uma auipc t0, 0x2 é executada em 0x4000, seguida de addi t0, t0, -24. Qual é o valor final em t0?
  1. 0x00005FEC
  2. 0x00005FE8
  3. 0x00006018
  4. 0x00002000
Mostrar respostaOcultar resposta
Resposta: B
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

Um byte contendo 0xB2 é carregado uma vez com lb e outra com lbu. Quais resultados decimais as duas cargas produzem, nessa ordem?
  1. −78 e 178
  2. 178 e 178
  3. −78 e −78
  4. −50 e 178
Mostrar respostaOcultar resposta
Resposta: A
Como valor de 8 bits com sinal, 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

Um vetor de estruturas usa 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?
  1. 0x5018 e −23
  2. 0x501C e 233
  3. 0x5010 e −23
  4. 0x501C e −23
Mostrar respostaOcultar resposta
Resposta: D
Cada estrutura ocupa 8 bytes, portanto o elemento 3 começa a 24 bytes da base; seu segundo campo acrescenta 4, resultando em 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)
  1. 0x00003840
  2. 0x00003846
  3. 0x00004846
  4. 0x00004006
Mostrar respostaOcultar resposta
Resposta: B
A constante torna-se 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

Qual resultado hexadecimal de 32 bits o RV32I produz quando add soma 1 a 0xFFFFFFFF?
  1. 0xFFFFFFFF
  2. 0x80000000
  3. 0x00000000
  4. 0x7FFFFFFF
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 2 de 10

Qual instrução real implementa bgt a2, a3, Done?
  1. blt a2, a3, Done
  2. blt a3, a2, Done
  3. bge a3, a2, Done
  4. bge a2, a3, Done
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 3 de 10

Após srli t0, a0, 8 e andi t0, t0, 0x1F, qual valor está em t0 se a0 = 0x0000D6B9?
  1. 0x00000016
  2. 0x000000D6
  3. 0x00000019
  4. 0x00000006
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 4 de 10

Para a0 = -11 com sinal, quais resultados srai t0, a0, 2 e a divisão inteira em C -11 / 4 produzem, respectivamente?
  1. −3 e −2
  2. −2 e −3
  3. −3 e −3
  4. −2 e −2
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?
  1. Apenas bltu
  2. Ambos os desvios
  3. Nenhum dos desvios
  4. Apenas blt
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 6 de 10

Uma tradução de 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?
  1. Um desvio de volta para a comparação
  2. Um salto para depois da parte else
  3. Uma segunda atribuição de 3
  4. Uma recarga de x antes de Else
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 7 de 10

O laço 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?
  1. 2
  2. 4
  3. 3
  4. 5
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)
  1. 6 e 25
  2. 9 e 26
  3. 6 e 24
  4. 6 e 26
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 9 de 10

Em 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?
  1. 0x9041; 0x8004; jalr zero, 0(ra)
  2. 0x9040; 0x8004; jalr zero, 0(ra)
  3. 0x9040; 0x8000; jalr zero, 0(ra)
  4. 0x9040; 0x8004; jal zero, 0(ra)
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 10 de 10

A condição em C é 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?
  1. Rejeitar p nulo; carregar *p; rejeitar valor diferente de 9
  2. Carregar *p; rejeitar p nulo; comparar com 9
  3. Rejeitar p não nulo; carregar *p; rejeitar valor diferente de 9
  4. Rejeitar p nulo; comparar p com 9; carregar *p
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

Em sw a2, 20(sp), qual registrador fornece o campo rs1?
  1. a2 (x12)
  2. zero (x0)
  3. ra (x1)
  4. sp (x2)
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 2 de 10

Qual é o padrão de imediato de 12 bits para addi t2, s1, -36?
  1. 1111 1101 1100
  2. 0000 0010 0100
  3. 1111 1101 1011
  4. 1111 1110 1100
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 3 de 10

Qual mudança única converte uma ADD do tipo R em SUB com os mesmos registradores?
  1. Definir opcode como 0100011
  2. Definir funct3 como 100
  3. Definir funct7 como 0100000
  4. Definir rd como a primeira origem
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 4 de 10

Um desvio em 0x308C tem como destino 0x3068. Qual deslocamento em bytes com sinal é codificado?
  1. −40
  2. −36
  3. −9
  4. +36
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 5 de 10

Uma 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?
  1. 0x1258 e 0x1204
  2. 0x1254 e 0x1204
  3. 0x1254 e 0x1258
  4. 0x1350 e 0x1204
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 6 de 10

Usando campos do tipo I, qual palavra codifica addi t2, s1, -36? Aqui t2 = x7, s1 = x9, o código de operação ADDI é 0x13 e funct3 = 000.
  1. 0xFDC48393
  2. 0x02448393
  3. 0xFDC38493
  4. 0xFDC483B3
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 7 de 10

Decodifique 0xFEC12623. O código de operação 0x23 seleciona um armazenamento e funct3 = 010 seleciona uma palavra. Qual instrução de montagem ela representa?
  1. sw a2, -12(sp)
  2. lw a2, -20(sp)
  3. sw sp, -20(a2)
  4. sw a2, -20(sp)
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 8 de 10

Uma 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?
  1. 0xFE0618E3
  2. 0x00061663
  3. 0xFE061AE3
  4. 0xFE061CE3
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 9 de 10

No endereço 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.
  1. 0x00002020
  2. 0x00001FDC
  3. 0x00007FE0
  4. 0x00001FE0
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 10 de 10

Um desvio e seu destino começam na mesma seção, nos deslocamentos 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?
  1. +0x24 e 0x9054
  2. +0x2024 e 0xB054
  3. +0x24 e 0xB054
  4. +0x20 e 0xB054
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

Um procedimento RISC-V que segue a convenção altera s4. Qual valor s4 deve conter quando ele retorna?
  1. O valor de s4 recebido do chamador
  2. O primeiro resultado do procedimento
  3. O endereço após a chamada
  4. O endereço atual do topo da pilha
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 2 de 10

Em RV32 ILP32, qual instrução avança a0 para o próximo elemento quando ele contém um int *?
  1. addi a0, a0, 1
  2. addi a0, a0, 2
  3. addi a0, a0, 8
  4. addi a0, a0, 4
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 3 de 10

Um procedimento entra com sp = 0x8000, aloca 32 bytes e depois executa sw ra, 28(sp). Em qual endereço começa a palavra salva?
  1. 0x7FE0
  2. 0x801C
  3. 0x7FFC
  4. 0x7FF0
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 4 de 10

Um procedimento folha recebe um argumento em 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?
  1. Salvar o a0 do chamador em cada saída
  2. Mover o s1 calculado para a0
  3. Restaurar s1 e depois copiar s1 para a0
  4. Retornar com o valor calculado em s1
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 5 de 10

Um laço de cadeia de caracteres que opera um byte por vez armazena um byte, incrementa ambos os ponteiros e desvia de volta se esse byte não for zero. Quantas iterações copiam “logic”, incluindo seu terminador em C?
  1. 5
  2. 6
  3. 7
  4. 10
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 6 de 10

Um procedimento que não é folha e segue a ABI padrão deve salvar apenas ra e s2, cada um com uma palavra. Qual é o menor tamanho positivo de quadro que preserva o alinhamento exigido da pilha?
  1. 4 bytes
  2. 8 bytes
  3. 16 bytes
  4. 12 bytes
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 7 de 10

Uma função recebe 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?
  1. Salvar ra e n; chamar; recarregar n em t0; somar; restaurar ra e sp
  2. Salvar apenas ra; copiar n para t0; chamar; somar t0; restaurar ra e sp
  3. Salvar apenas n; chamar; recarregar n; somar; restaurar sp; retornar por ra
  4. Salvar ra e n; chamar; restaurar n em a0; somar a0 a si mesmo; restaurar ra
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 8 de 10

Uma função recursiva aloca um quadro de 16 bytes em cada entrada, salva 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?
  1. 24 e 0x8FD0
  2. 6 e 0x8FC0
  3. 24 e 0x8FB0
  4. 24 e 0x8FC0
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 9 de 10

Em RV32 ILP32, a memória em 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?
  1. lw a0, 16(a0); lw a0, 0(a0)
  2. lw t0, 0(a0); lw a0, 16(t0)
  3. addi t0, a0, 16; lw a0, 0(t0)
  4. lw t0, 0(a0); lw a0, 4(t0)
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 10 de 10

Um procedimento aloca 32 bytes, armazena 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?
  1. As cargas usam endereços 16 bytes acima do correto
  2. O auxiliar deve preservar cada temporário
  3. Um quadro não pode conter bytes não utilizados
  4. O ponteiro de pilha final está desalinhado
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

Qual arquivo gcc -c module.c produz sem fazer a ligação?
  1. Uma imagem executável de processo
  2. Uma listagem de código-fonte de montagem
  3. Um arquivo objeto relocável
  4. Uma cache de símbolos de biblioteca compartilhada
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 2 de 10

Qual componente mapeia o código e os dados de um executável em um novo processo e prepara sua pilha inicial?
  1. O compilador
  2. O carregador
  3. O montador
  4. O ligador estático
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 3 de 10

Um arquivo objeto define main, mas chama mix, definida em um segundo objeto. O que permite ao ligador conectar essa chamada à definição?
  1. A tabela de linhas de depuração e as posições no arquivo-fonte
  2. Os códigos de operação e uma convenção compartilhada de registradores
  3. A organização inicial da pilha e o ponto de entrada do executável
  4. As informações de símbolos e os registros de relocação da chamada
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 4 de 10

Um rótulo local é referenciado antes de sua definição, que aparece mais adiante na mesma seção de montagem. Como um montador pode concluir esse desvio?
  1. Registrar a referência e resolver sua posição depois
  2. Exigir que o carregador analise o rótulo no código-fonte
  3. Tratar o nome do rótulo como um inteiro imediato
  4. Emitir o desvio com deslocamento permanentemente zero
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 5 de 10

Um programa foi ligado estaticamente à implementação de 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?
  1. O código original copiado durante a ligação
  2. O código corrigido do arquivo na próxima chamada
  3. Uma versão recém-interpretada do código-fonte
  4. Uma rotina PLT que carrega código compartilhado substituto
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 6 de 10

Um programa RISC-V direcionado ao Linux coloca 93 em a7, 7 em a0 e depois executa ecall. Qual serviço é solicitado?
  1. Escrever 7 bytes em um fluxo padrão
  2. Retornar 93 ao chamador atual
  3. Encerrar o processo com status 7
  4. Alocar um quadro de pilha de 7 bytes
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 7 de 10

Um ligador coloca texto em 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?
  1. +0x1A0 e 0x30000044
  2. +0x130 e 0x30000194
  3. +0x12C e 0x30000044
  4. +0x130 e 0x30000044
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 8 de 10

Uma listagem RV32I em 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?
  1. 0x100C e 12 bytes
  2. 0x1014 e 20 bytes
  3. 0x1010 e 16 bytes
  4. 0x1018 e 24 bytes
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 9 de 10

Uma função de biblioteca começa no deslocamento 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?
  1. Somar 0x10000000 ao deslocamento local e manter cada entrada da GOT
  2. Manter o destino local em 0x400002C0; relocar apenas a instrução de chamada
  3. Subtrair 0x10000000 da distância local e limpar toda a GOT
  4. Manter a distância local +0x230; resolver o destino externo da GOT conforme necessário
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 10 de 10

Um executável foi ligado com sucesso a uma biblioteca compartilhada. Depois, essa biblioteca está ausente, e o carregamento do executável falha antes de main. O que explica a falha e o que mudaria para o código dessa biblioteca incluído estaticamente?
  1. O carregamento precisa do arquivo compartilhado; o código incluído já estaria no executável
  2. A ligação se repete ao iniciar; o código incluído ainda precisaria do arquivo original
  3. O objeto de entrada está ausente; o código incluído ainda precisaria de todos os objetos de entrada
  4. O arquivo não tem código de máquina; o código incluído dispensaria o carregador de processos
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

Qual par de instruções RISC-V realiza a operação de soma com a memória expressa pela instrução x86 add eax, [ebx+12], com eax correspondendo a a0 e ebx a s1?
  1. addi a0, a0, 12; add a0, a0, s1
  2. lw a0, 12(s1); add a0, a0, a0
  3. sw a0, 12(s1); add a0, a0, s1
  4. lw t0, 12(s1); add a0, a0, t0
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 2 de 10

Um processador tem uma ISA de carga e armazenamento e indicadores de condição. O que se pode concluir sobre a afirmação de que os indicadores o tornam CISC?
  1. Indicadores implicam aritmética de dois operandos, portanto a afirmação é válida.
  2. Indicadores podem aparecer em projetos RISC, portanto a afirmação é falsa.
  3. Uma ISA de carga e armazenamento não pode usar indicadores, portanto há uma contradição.
  4. Indicadores implicam aritmética combinada com memória, portanto a afirmação é válida.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 3 de 10

A versão P executa 0,8 bilhão de instruções com CPI 1,5 e 2 GHz. A versão Q executa 1,0 bilhão de instruções com CPI 1,0 e 2 GHz. Quais são os tempos de execução de P e Q?
  1. 0,60 s e 0,50 s
  2. 0,40 s e 0,50 s
  3. 0,60 s e 1,00 s
  4. 1,20 s e 1,00 s
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 4 de 10

Um programa tem 120 instruções de máquina. Se 72 usam codificações de 16 bits e o restante usa codificações de 32 bits, quantos bytes de código ele ocupa?
  1. 240 bytes
  2. 384 bytes
  3. 336 bytes
  4. 408 bytes
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 5 de 10

O projeto P executa 1,2 bilhão de instruções com CPI 1,1 e 2,2 GHz. O projeto Q executa 0,9 bilhão de instruções com CPI 1,8 e 2,7 GHz. Uma otimização proposta reduz a contagem de instruções de Q em 10%, mas aumenta seu CPI em 20%, mantendo sua frequência fixa. Como Q otimizado se compara a P?
  1. Q leva 0,648 s e é mais lento que P
  2. Q leva 0,54 s e é mais rápido que P
  3. Q leva 0,60 s e empata com P
  4. Q leva 0,72 s e é mais lento que P
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 6 de 10

Uma rotina de 200 instruções usa originalmente apenas codificações de 32 bits. Após 110 codificações se tornarem de 16 bits, a sequência de instruções e o tempo de execução medido permanecem inalterados. Quais são o novo tamanho e a conclusão justificada sobre desempenho?
  1. 580 bytes; execução 27,5% mais rápida
  2. 620 bytes; menos bytes com velocidade medida inalterada
  3. 400 bytes; metade das instruções executadas
  4. 580 bytes; menos bytes com velocidade medida inalterada
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 7 de 10

Um binário usa RV32I mais 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?
  1. Suporte a mul e a interface de serviços atual do simulador
  2. Suporte a mul e a interface de serviços esperada do Linux
  3. Apenas RV32I e a interface de serviços esperada do Linux
  4. Suporte a mul e apenas a convenção ILP32 compatível
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 8 de 10

Um vetor RV32 ILP32 contém 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?
  1. addi t0, a0, 4; lw t1, 4(t0)
  2. addi t0, a0, 8; lw t1, 0(t0)
  3. addi t0, a0, 8; lw t1, 4(t0)
  4. addi t0, a0, 12; lw t1, 4(t0)
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
  1. Usar uma carga de byte sem sinal no laço
  2. Inserir beq a1, zero, D antes de L
  3. Mudar o bne final para uma blt com sinal
  4. Inicializar t1 com o primeiro elemento do vetor
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 10 de 10

Uma rotina escrita à mão reduz as instruções executadas de 50 milhões para 40 milhões no mesmo processador de 2 GHz. O CPI médio sobe de 1,2 para 1,6. O que acontece com o tempo e o que isso mostra sobre a afirmação baseada na contagem de instruções?
  1. Cai de 30 ms para 24 ms; a redução da contagem determina o tempo
  2. Sobe de 60 ms para 64 ms; o CPI anula a redução da contagem
  3. Sobe de 30 ms para 32 ms; o CPI anula a redução da contagem
  4. Sobe de 24 ms para 32 ms; o CPI anula a redução da contagem
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Travou em uma questão sua? Ajuda com Tarefas

Termos-Chave

82 Termos-Chave, cada um com sua definição

A

Alinhamento natural
O alinhamento natural coloca um acesso à memória em um endereço divisível pelo tamanho do acesso, como um múltiplo de 4 para uma palavra.
Um compilador pode inserir preenchimento de alinhamento para que uma meia palavra ou palavra comece em um endereço adequadamente alinhado.
Alocação de registradores
Alocação de registradores é a atribuição de valores do programa pelo compilador aos registradores limitados disponíveis em cada ponto da execução.
Um laço otimizado pode manter seu índice e sua soma acumulada em registradores durante muitas iterações.
Arquitetura de carga e armazenamento
Uma arquitetura de carga e armazenamento usa instruções dedicadas de transferência de memória, enquanto operações aritméticas e lógicas atuam sobre valores de registradores e imediatos.
Para somar um elemento de vetor, RISC-V carrega-o em um registrador antes de emitir uma instrução aritmética.
Arquitetura do conjunto de instruções
Uma arquitetura do conjunto de instruções é o contrato que define as instruções, os registradores e o comportamento da máquina visíveis ao software.
Dois processadores podem implementar a mesma ISA com circuitos internos e velocidades de execução diferentes.
Arquivo objeto relocável
Um arquivo objeto relocável contém código de máquina, dados e metadados cujo posicionamento final e cujas referências externas podem ser concluídos depois.
Produzir um objeto com -c para antes da combinação de bibliotecas e módulos necessária para um executável.
Avaliação de curto-circuito
A avaliação de curto-circuito interrompe os testes de uma condição composta quando seu valor lógico geral já está determinado.
Um primeiro operando falso de && impede a avaliação do segundo operando em C.

B

Bit implícito
Um bit implícito é um valor reconstruído a partir de uma regra de codificação sem ocupar uma posição de bit armazenada própria.
O bit inferior de um deslocamento de desvio do tipo B é implicitamente zero porque a distância em bytes é par.
Bloco básico
Um bloco básico é uma sequência linear de instruções na qual se entra pelo início e da qual se sai apenas pelo fim durante o fluxo normal de controle.
Um rótulo que é destino de um desvio inicia um novo bloco que um compilador pode analisar separadamente.

C

Campo de função
Um campo de função refina um código de operação para selecionar uma operação específica, como adição ou subtração dentro da aritmética com registradores.
Os mesmos registradores e código de operação podem representar ADD ou SUB dependendo dos bits de funct7.
Campo de registrador
Um campo de registrador é uma parte de largura fixa da codificação de uma instrução que identifica pelo número um registrador de origem ou destino.
Um campo de 5 bits representa a0 como registrador número 10, escrito 01010 em binário.
Carregador
Um carregador prepara um executável como processo em execução, mapeando seu conteúdo e estabelecendo o estado inicial da memória e dos registradores.
A inicialização começa somente após a preparação do código, dos dados e da pilha inicial do programa.
Carregamento de endereço
Carregamento de endereço é uma operação do montador que forma o endereço de um rótulo como valor de registrador sem ler o objeto armazenado nesse endereço.
Após la t0, Item, é necessária uma carga separada para ler o valor contido em Item.
Chamada de sistema
Uma chamada de sistema é uma solicitação de um programa a um serviço do sistema operacional por meio da interface definida por seu ambiente de execução.
No Linux para RISC-V, ecall com 93 em a7 solicita o encerramento, usando a0 para o status.
Ciclos por instrução
Ciclos por instrução é o número médio de ciclos de clock usados por instrução executada para um programa e uma implementação específicos.
Um programa com mais instruções pode terminar antes se seu CPI for suficientemente menor.
CISC
CISC é uma abordagem de projeto de conjuntos de instruções que suporta operações e formas de endereçamento mais ricas, geralmente por meio de codificações de comprimento variável.
Uma instrução x86 pode combinar a leitura de um operando em memória com sua adição a um registrador.
Código de operação
Um código de operação é um campo de instrução que identifica um grupo de operações e ajuda a determinar a organização usada para decodificar seus outros bits.
Os 7 bits inferiores distinguem um armazenamento de palavra RV32I de uma instrução aritmética com registradores.
Código independente de posição
Código independente de posição usa referências relativas e indireção para que seu código possa ser executado corretamente em diferentes endereços de carregamento.
Mover uma biblioteca e seus destinos locais juntos preserva as distâncias usadas por suas referências locais.
Comparação menor que
Comparação menor que é uma operação que escreve 1 quando a primeira origem é menor que a segunda e escreve 0 caso contrário.
slt e sltu podem discordar quando o bit mais significativo de uma origem lhe dá valores diferentes com e sem sinal.
Compatibilidade binária
A compatibilidade binária permite que software existente em código de máquina seja executado sem recompilação quando os requisitos de ISA, ABI e ambiente são compatíveis.
Usar uma extensão opcional adiciona um requisito de hardware além do suporte à ISA básica.
Compilação em tempo de execução
A compilação em tempo de execução gera código de máquina durante a execução, frequentemente concentrando o esforço de compilação no código executado repetidamente.
Uma máquina virtual pode primeiro interpretar bytecode e depois compilar um método executado com frequência.
Compilador
Um compilador traduz um programa para outra representação, geralmente selecionando e otimizando instruções de máquina para uma ISA de destino.
Um compilador direcionado a RISC-V pode substituir cálculos repetidos de índice por um incremento de ponteiro.
Conceito de programa armazenado
O conceito de programa armazenado representa instruções como valores binários na memória, permitindo que um processador as busque e execute.
O contador de programa fornece o endereço de onde a próxima instrução codificada será buscada.
Contador de programa
O contador de programa é o registrador que contém o endereço da instrução que está sendo buscada ou executada na sequência de instruções.
Um desvio realizado substitui a progressão sequencial do pc pelo endereço calculado para seu destino.
Contagem dinâmica de instruções
Contagem dinâmica de instruções é o número de instruções efetivamente executadas ao longo do caminho de um programa, incluindo passagens repetidas por laços.
O teste final de um laço que falha contribui para a contagem de execução mesmo que seu corpo não seja executado novamente.
Continuação sequencial
Continuação sequencial é a execução que prossegue para a próxima instrução ou bloco de código porque nenhuma transferência de controle o pula.
Uma tradução de if-else precisa de um salto após a parte then para evitar a continuação sequencial na parte else.
Convenção de chamada
Uma convenção de chamada é o acordo que especifica locais de argumentos, locais de resultados e preservação de registradores durante chamadas de procedimentos.
Procedimentos RISC-V com inteiros passam seus primeiros argumentos em a0 a a7.

D

Densidade de código
Densidade de código descreve quão compactamente as instruções de máquina representam um programa, sendo geralmente comparada pelos bytes necessários para seu código.
Instruções comprimidas podem reduzir os bytes de código preservando o número de operações executadas.
Derramamento de registradores
Derramamento de registradores é armazenar na memória um valor ativo de registrador para que seu registrador possa ser reutilizado e depois carregar novamente o valor quando necessário.
Valores ativos adicionais podem acrescentar armazenamentos e cargas na pilha mesmo quando o C original não tem acesso explícito à memória.
Deslocamento aritmético à direita
Um deslocamento aritmético à direita move bits para posições inferiores e preenche as posições superiores com o bit de sinal original.
Deslocar −7 com sinal em 1 posição à direita produz −4 porque o resultado é arredondado em direção ao infinito negativo.
Deslocamento de campo de estrutura
Um deslocamento de campo de estrutura é a distância em bytes do início de uma estrutura até um membro, determinada por sua organização na ABI.
Uma carga soma o deslocamento do campo ao endereço do objeto atual para alcançar o membro desse objeto.
Deslocamento do desvio
Um deslocamento do desvio é a distância em bytes com sinal entre o endereço da própria instrução de desvio e o endereço de destino pretendido.
Um destino para trás produz um deslocamento negativo mesmo quando ambos os endereços são positivos.
Deslocamento lógico à direita
Um deslocamento lógico à direita move bits para posições inferiores e preenche as posições superiores liberadas com zeros, permitindo divisão sem sinal por potências de dois.
Um deslocamento lógico à direita de um padrão negativo com sinal pode produzir um resultado positivo grande.
Desmontagem
Desmontagem é a conversão de bits de instruções codificadas de volta em nomes de instruções e operandos usando as regras de decodificação da ISA.
Um desmontador identifica o código de operação antes de tratar outros bits como registradores ou fragmentos de imediato.
Desreferenciamento
Um desreferenciamento acessa o objeto identificado por um valor de ponteiro, sendo implementado por uma carga ou armazenamento apropriado na memória.
lw t0, 0(a0) lê um inteiro de 4 bytes pelo endereço atualmente contido em a0.
Desvio condicional
Um desvio condicional altera o endereço da próxima instrução apenas quando sua comparação é satisfeita; caso contrário, continua em sequência.
bne t0, t1, Loop retorna a Loop apenas enquanto os dois padrões de registradores forem diferentes.
Diretiva do montador
Uma diretiva do montador controla a emissão de dados, a seleção de seção, o alinhamento ou os metadados de símbolos sem ser executada pelo processador.
A diretiva .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 byte é uma organização de memória em que cada endereço identifica um byte, de modo que valores maiores ocupam endereços consecutivos.
Dois elementos adjacentes de vetor de 32 bits começam em endereços que diferem em 4, não em 1.
Endereçamento por deslocamento
O endereçamento por deslocamento forma um endereço efetivo de memória somando um deslocamento constante com sinal ao valor em um registrador base.
Em lw t0, 20(s1), a palavra carregada começa 20 bytes além do endereço armazenado em s1.
Endereçamento por índice escalonado
O endereçamento por índice escalonado combina uma base com um índice multiplicado por um passo, frequentemente somando um deslocamento para formar um endereço de memória.
Um operando em memória x86 pode expressar a base mais quatro vezes um índice mais 8 em uma instrução.
Endereço relativo ao PC
Um endereço relativo ao PC é formado pelo endereço da própria instrução mais um deslocamento codificado, mantendo uma posição relativa ao código.
Uma sequência auipc pode alcançar dados locais mesmo quando todo o grupo de código e dados se move.
Epílogo
Um epílogo é a sequência de saída que restaura o estado salvo, libera o quadro de pilha atual e retorna o controle ao chamador.
Restaurar registradores antes de aumentar sp mantém seus deslocamentos relativos ao quadro alocado.
Estouro
Estouro ocorre quando um resultado aritmético está fora do intervalo representável para a largura e a interpretação com ou sem sinal escolhidas.
A adição RV32 mantém os bits inferiores do resultado mesmo quando a soma com sinal não cabe em 32 bits.
Extensão com zeros
A extensão com zeros amplia um padrão de bits preenchendo novas posições superiores com zeros, preservando o valor interpretado como sem sinal.
Carregar o byte 0xF0 com lbu produz o valor positivo 240 em um registrador de 32 bits.
Extensão de sinal
A extensão de sinal amplia um padrão de bits com sinal copiando seu bit mais significativo para as novas posições superiores, preservando seu valor com sinal.
Carregar o byte 0xF0 com lb produz 0xFFFFFFF0, a representação de 32 bits de −16.

F

Formato de instrução
Um formato de instrução é a organização dos campos dentro de uma instrução codificada, que atribui posições de bits a operandos e controles.
O tipo S tem um imediato dividido onde o tipo R teria um campo de função e um registrador de destino.

G

Grupo de quatro bits
Um grupo de 4 bits corresponde a um dígito hexadecimal e facilita a conversão de palavras binárias de instrução para hexadecimal.
Uma instrução completa de 32 bits divide-se em oito grupos de quatro bits após a união de todos os seus campos.

I

Imediato
Um imediato é uma constante representada por bits dentro de uma instrução, disponível sem buscar primeiro a constante na memória.
O operando final em addi t0, t0, 1 é codificado na instrução como um imediato.
Imediato superior
Um imediato superior fornece um campo de 20 bits que lui coloca acima de 12 zeros inferiores ao construir um valor de registrador RV32.
Uma addi seguinte pode fornecer uma parte inferior com sinal para completar uma constante de 32 bits.
Indicadores de condição
Indicadores de condição são bits de estado que registram propriedades de um resultado ou comparação que instruções posteriores podem testar.
x86 pode comparar registradores e depois desviar com base em indicadores, enquanto desvios RISC-V comparam diretamente.
Instrução comprimida
Uma instrução comprimida é uma codificação mais curta de uma operação, como uma forma RISC-V C de 16 bits usada junto com instruções de 32 bits.
Se metade de 100 instruções usa 2 bytes, o tamanho total do código é 300 bytes em vez de 400.
Interface binária de aplicação
Uma interface binária de aplicação é o acordo de software sobre convenções de chamada, organização de dados e outras regras para a cooperação entre binários.
Uma ISA compatível não pode corrigir uma discordância sobre qual registrador contém o valor de retorno de uma função.

L

Ligação dinâmica
A ligação dinâmica conecta um programa a código separado de biblioteca compartilhada durante o carregamento ou a execução, exigindo que bibliotecas compatíveis estejam disponíveis.
Uma biblioteca compartilhada ausente pode impedir a inicialização de um executável ligado anteriormente.
Ligação estática
A ligação estática inclui o código objeto de biblioteca necessário no executável, tornando esse código incluído parte do próprio arquivo do programa.
Substituir um arquivo de biblioteca estática não atualiza o código já copiado para um executável mais antigo.
Ligador
Um ligador combina módulos objeto e bibliotecas, organiza o conteúdo executável, resolve símbolos e ajusta referências dependentes de endereços.
Um erro de referência indefinida significa que a ligação final não encontrou uma definição de símbolo necessária.
Limite do laço
Um limite do laço restringe as iterações permitidas pela comparação do laço, determinando quais índices ou endereços seu corpo pode acessar.
Um teste que encerra o laço quando o índice alcança o comprimento mantém o índice do vetor abaixo desse comprimento.
Linguagem de montagem
Linguagem de montagem é texto legível e específico de uma ISA que nomeia instruções e operandos antes que um montador os codifique em bits.
O texto 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
Uma máscara de bits é um padrão usado com uma operação bit a bit para selecionar, zerar, ativar ou inverter posições específicas em outro padrão.
Aplicar AND com 0xFF mantém o byte inferior enquanto zera todas as posições superiores do registrador.
Micro-operação
Uma micro-operação é uma operação interna em que um processador pode traduzir uma instrução da ISA para execução por seu núcleo.
Uma instrução x86 visível externamente pode exigir várias micro-operações internas mais simples.
Microarquitetura
Uma microarquitetura é o projeto interno de um processador para implementar as operações e o comportamento exigidos por seu conjunto de instruções.
Alterar o hardware de execução pode melhorar a velocidade de um chip preservando sua interface existente de código de máquina.
Montador
Um montador converte texto em linguagem de montagem em instruções e dados codificados enquanto registra símbolos e referências para resolução posterior.
Um montador expande call e pode deixar entradas de relocação para uma função definida em outro módulo.

O

Ordem de bytes little-endian
A ordem de bytes little-endian armazena o byte menos significativo de um valor de vários bytes em seu menor endereço de memória.
Um armazenamento de palavra de 0x12345678 coloca 0x78 no primeiro byte e 0x12 no quarto.

P

Passo entre elementos
Um passo entre elementos é a distância em bytes entre posições correspondentes em elementos consecutivos de um vetor ou de uma organização repetida.
Um vetor de estruturas com dois inteiros tem um passo de 8 bytes, enquanto seu segundo campo tem deslocamento 4.
Ponteiro
Um ponteiro é um valor que representa um endereço de objeto ou código, usado para localizar o que uma carga, armazenamento ou transferência indireta acessará.
Um registrador ponteiro fornece um endereço, enquanto uma carga recupera o valor armazenado nesse endereço.
Ponteiro de pilha
O ponteiro de pilha é o registrador que marca o limite atual do armazenamento alocado na pilha, movendo-se conforme quadros são obtidos ou liberados.
Subtrair 16 de sp reserva um quadro alinhado em endereços menores na convenção RISC-V.
Procedimento folha
Um procedimento folha é um procedimento que não faz chamadas a outros procedimentos, permitindo que mantenha intacto seu endereço de retorno recebido.
Um procedimento folha que usa apenas registradores de argumentos e temporários pode retornar sem alocar um quadro de pilha.
Prólogo
Um prólogo é a sequência de entrada que estabelece o quadro de um procedimento e salva o estado recebido dos registradores que ele deve preservar.
Salvar ra antes de uma chamada aninhada protege o endereço necessário para retornar ao chamador externo.
Pseudoinstrução
Uma pseudoinstrução é uma abreviação do montador que se expande em uma ou mais instruções efetivamente definidas pela ISA do processador.
O número de instruções de máquina produzidas por li depende do valor de seu operando constante.

Q

Quadro de pilha
Um quadro de pilha é a região alocada para o estado salvo, os objetos locais, os valores derramados e o espaço de argumentos na pilha de uma ativação de procedimento.
Três chamadas ativas podem conter três cópias distintas do argumento salvo da mesma função.

R

Recursão
Recursão ocorre quando um procedimento chama a si mesmo direta ou indiretamente, criando invocações ativas separadas com seus próprios estados.
O fatorial carrega novamente o argumento salvo de uma chamada externa após a chamada interna retornar seu resultado.
Registrador de inteiros
Um registrador de inteiros é um pequeno local de armazenamento do processador que contém um padrão de bits usado por operações com inteiros ou cálculos de endereços.
Em RV32I, cada um dos 32 registradores de inteiros contém 32 bits, seja usado para um valor ou um endereço.
Registrador preservado pelo chamado
Um registrador preservado pelo chamado deve manter seu valor recebido após uma chamada, exigindo restauração por um procedimento que o altere.
Uma função pode usar s1 internamente se restaurar o s1 original do chamador antes de retornar.
Registrador preservado pelo chamador
Um registrador preservado pelo chamador pode ser sobrescrito por um procedimento chamado, portanto seu chamador deve proteger qualquer valor atual necessário depois.
Um valor ativo em t2 precisa ser salvo antes de um auxiliar que pode usar esse registrador.
Registrador zero
O registrador zero é o registrador RISC-V x0, que sempre é lido como zero e descarta todo valor que uma instrução escreve nele.
Um salto pode escrever seu enlace em zero quando o programa não precisa salvar um endereço de retorno.
Relocação relativa
Relocação relativa é o ajuste de uma referência com base na distância entre uma posição de instrução ou de dados já posicionada e seu destino.
Mover apenas uma seção pode mudar o deslocamento de uma chamada mesmo quando os bytes da instrução começaram inalterados.
Resolução tardia de símbolos
A resolução tardia de símbolos adia a resolução de um procedimento de biblioteca compartilhada até sua primeira chamada e registra o resultado para chamadas posteriores.
A primeira chamada pode invocar o ligador dinâmico, enquanto chamadas subsequentes usam um endereço já armazenado.
RISC
RISC é uma abordagem de projeto de conjuntos de instruções que favorece codificações regulares, operações relativamente simples e instruções separadas de transferência de memória.
Adicionar extensões pode ampliar uma ISA RISC sem eliminar sua organização de carga e armazenamento.

S

Salto com enlace
Salto com enlace é uma transferência de controle que também escreve o endereço da instrução seguinte em um registrador de destino para uso posterior.
jal ra, Helper registra onde o chamador deve continuar quando Helper retornar.
Salto indireto
Um salto indireto obtém seu destino de um cálculo baseado em registrador, permitindo que um endereço armazenado escolha a próxima instrução.
jalr pode implementar uma chamada por ponteiro de função, um retorno ou uma seleção por tabela de saltos.

T

Tabela de saltos
Uma tabela de saltos é uma coleção indexada de endereços de código usada para transferir o controle eficientemente entre vários casos.
Uma seleção switch deve verificar seu intervalo de casos antes de ler um endereço de sua tabela de saltos.
Tabela de símbolos
Uma tabela de símbolos registra definições nomeadas e referências não resolvidas usadas para conectar instruções e dados entre módulos traduzidos.
Uma função pode estar indefinida em um objeto e definida em outro objeto fornecido ao ligador.
Tempo de vida do objeto
O tempo de vida de um objeto é o período durante o qual seu armazenamento representa um objeto válido que o programa tem permissão para acessar.
Retornar um ponteiro para um objeto local não mantém esse objeto válido após o retorno de sua função.
Terminador nulo
Um terminador nulo é o byte zero que marca o fim de uma cadeia de bytes em C, ocupando espaço após o último byte diferente de zero.
Um laço de cópia de cadeia de caracteres armazena o terminador para que o destino também tenha um fim reconhecível.

Sobre esta aula

Por StudeliRelatar um erro

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.

Começar