Você está na página 1de 289

Introdução à linguagem de

programação Assembly
Cap. 2: “Instructions: Language of the Computer”
Compilação e execução de código
Visão geral
O interface entre software e hardware

Aplicação
(linguagem de alto nível) Arquitetura de conjunto de instruções
ou
Instruction Set Architecture (ISA)

▪ Modelo abstrato de uma arquitetura de


Aplicação processador
(linguagem Assembly) ▪ Define o conjunto de instruções
disponíveis
▪ Estabelece o funcionamento lógico do
processador, e desta forma o interface
entre o software e o hardware
▪ Permite múltiplas realizações (ex:
Aplicação diferentes modelos de processadores
(linguagem máquina) da Intel), cada uma com desempenhos
diferentes.
3
Execução do programa

▪ Sistema operativo:
▫ Gestão das entradas e saídas

▫ Gestão da memória (RAM e disco), incluindo inicialização das


estruturas de dados necessárias para começar a executar o
programa

▫ Escalonamento da execução das tarefas e gestão dos


recursos partilhados

▪ Hardware:
▫ Processador, memória, controladores I/O

4
Instruction Set Architecture (ISA)

1. Define as instruções disponíveis


▫ Ex: ADD, SUB, MUL, DIV, AND, OR, XOR

2. Define os operandos
▫ Registos (ex: R0,…R31),
▫ Memória,
▫ Imediato (ao nível do ISA denomina-se por imediato quando um operando toma um valor constante)

3. Define a forma como se acede à memória e aos periféricos


▫ Instruções dedicadas de acesso à memória (load/store), ou diretamente como um operando de qualquer
instrução
▫ Barramento dedicado de acesso aos periféricos (instruções específicas) ou diretamente mapeados numa zona de
memória

4. Define a forma como se tratam exceções e interrupções


▫ Interrupção: ocorrência de um sinal externo ao processador (ex: o utilizador pressiona uma tecla do teclado)
▫ Exceção: ocorrência de um evento especial durante a execução de um programa (ex: divisão por zero)

5
Instruction Set Architecture (ISA)
RISC-V
Programação Assembly
Como aprender?

▪ O que preciso DECORAR sobre Assembly?


▪ Em princípio NADA!!! Mas é preciso COMPREENDER

7 7
Instruction Set Architecture (ISA)
RISC-V

1. No caso do RISC-V, a maioria das instruções tem 3 operandos

Instrução Register Transfer Language (RTL)

add a,b,c ab+c

sub a,b,c ab-c

Exemplo de código para calcular (a+b) - (c+d)

add x, a, b # soma a com b e coloca em x


add y, c, d # soma c com d e coloca em y
sub x, x, y # calcula o resultado final

Código Assembly Comentários

8
Instruction Set Architecture (ISA)
RISC-V

2. Por omissão os operandos devem ser registos

▫ O RISC-V contém
▫ 32 registos de inteiros (x0,x1,…,x31).
▫ Dependendo da implementação, os registos podem ser de 32 ou 64 bits
▫ O registo x0 vale sempre 0 (mesmo após uma escrita para este registo)

Assim, no exemplo anterior, admitindo um processador de 32 bits, e que os registos x12,…,x15


contêm os valores a,…,d, respetivamente,

add x10, x12, x13 # soma a com b e coloca em x


add x11, x14, x15 # soma c e d
sub x10, x10, x11 # calcula o resultado (a+b)-(c+d)

9 9
Instruction Set Architecture (ISA)
RISC-V

2. Por omissão os operandos devem ser


registos Registos de inteiros Registos de FP
▫ O RISC-V contém
x0 0
Vale
f0
▫ 32 registos de inteiros (x0,x1,…,x31). sempre 0
▫ 32 registos para virgula flutuante x1 f1
(f0,…,f31) – se a extensão de virgula x2 f2
flutuante (floating point) estiver ativa
▫ Dependendo da implementação, os x3 f3
registos podem ser de 32 ou 64 bits x4 f4
x5 f5
▫ O registo x0 vale sempre 0 (mesmo após uma
escrita para este registo) … …
x31 f31

10 10
Instruction Set Architecture (ISA)
RISC-V

3. O conjunto de instruções está dividido em base+extensões:

▫ Base:
▫ RV32I – Operações sobre inteiros (I), registos de 32 bits
▫ RV32E – Operações sobre inteiros (I), registos de 32 bits, versão reduzida para sistemas embebidos
▫ RV64I – Operações sobre inteiros (I), registos de 64 bits
▫ RV128I – Operações sobre inteiros (I), registos de 128 bits
▫ Extensões (não exaustivo):
▫ M – Operações de multiplicação e divisão sobre inteiros
▫ A – Operações Atómicas
▫ F – Operações de virgula flutuante, precisão simples (C float)
▫ D – Operações de virgula flutuante, precisão dupla (C double)
▫ C – Instruções comprimidas (16-bits)
▫ V – Operações vetoriais
▫ Conjunto típico
▫ RV32G “Geral”: RV32I + M + A + F + D
▫ RV64G “Geral”: RV64I + M + A + F + D

11 11
Instruction Set Architecture (ISA)
RISC-V

Tipo de instruções Mnemónicas


Aritméticas (INT) add, addi, sub, mul, mulh, mulhu, mulhsu, div, divu
Aritméticas (FP) fadd.s, fadd.d, fsub.s, fsub.d, fmul.s, fmul.d, fdiv.s, fdiv.d
Lógicas and, andi, or, ori, xor, xori
Deslocamento sll, slli, srl, srli, sra, srai
Acesso à memória lb, lbu, lh, lhu, lw, flw, fld
sb, sh, sw, fsw, fsd
Transferência lui, auipc
Comparação slt, slti, sltiu, sltu
Controlo de fluxo beq, bne, blt, bge, bltu, bgeu, jal, jalr

Nota: Não é necessário decorar as instruções. Para o laboratório e para o teste será disponibilizado uma folha de referência com o
conjunto de instruções.
12 12
Instruction Set Architecture (ISA)
RISC-V

Não é suposto decorar as instruções

Pode-se usar o reference card em


Todos os momentos de avaliação

13 13
Instruction Set Architecture (ISA)
RISC-V

Não é suposto decorar as instruções


Registos FP

Pode-se usar o reference card em


Todos os momentos de avaliação

Registos inteiros

Directives de Assembly

14 14
Instruction Set Architecture (ISA)
RISC-V

Não é suposto decorar as instruções

Pode-se usar o reference card em


Todos os momentos de avaliação

Codificação das instruções


(ver mais tarde)

15 15
Instruction Set Architecture (ISA)
RISC-V

Do not memorise the instructions

Use the reference card…

Instruções
Não é para decorar, é para consultar sempre que necessário

16 16
Instruction Set Architecture (ISA)
RISC-V

Do not memorise the instructions

Use the reference card…

Classe da instrução

17 17
Instruction Set Architecture (ISA)
RISC-V

Do not memorise the instructions

Use the reference card…

Mnemónica e operandos

18 18
Instruction Set Architecture (ISA)
RISC-V

Do not memorise the instructions

Use the reference card…

Nome da instrução

19 19
Instruction Set Architecture (ISA)
RISC-V

Do not memorise the instructions

Use the reference card…

Descrição em RTL
(Register Transfer Level)

20 20
Instruction Set Architecture (ISA)
RISC-V

Do not memorise the instructions

Use the reference card…

Campos da codificação
(conjugar com a tabela do formato)

21 21
Instruções do tipo
RV32IM

22
Instruções do tipo
RV32IM

Pseudo-instruções
Não são instruções de facto,
mas realizam-se à custa
de 1 ou mais instruções reais

Pode-se usar pseudo-instruções,


mas convém saber como se realiza
a mesma operação sem
pseudo-instruções

23
Instruções do tipo
RV32IM

Operações aritméticas,
lógicas e de deslocamento

24
Instruções do tipo
RV32IM

Multiplicação
e divisão

25
Instruções do tipo
RV32IM

Transferência

26
Instruções do tipo
RV32IM

lui xd,imm

Preenche os 20 bits mais


significativos com o imediato de 20
bits imm e coloca os 12 bits menos
significativos a zero

O carregamento de uma constante de 32


bits (ex: F314 2346h) pode ser realizado
da seguinte forma:
lui xa,0xf3142
ori xa,xa,0x346
31 12 11 0
Registo Xa
27
Instruções do tipo
RV32FD

S = Single D = Double
29 (C float) (C double)
Exemplo
Cálculo de um polinómio

▪ Considere o seguinte troço de código C:

#define a0 5
#define a1 7 Realize a operação:
#define a2 -5
𝑦 = 𝑎0 + 𝑎1 × 𝑥 − 𝑎2 × 𝑥 2
int x, y;

y = a0 + a1*x + a2*x*x; onde 𝑎0 , 𝑎1 e 𝑎2 são constantes.

▪ Escreva o código Assembly correspondente, assumindo que x5 e x6


guardam o valor de x e y, respetivamente.

30
Exemplo
Cálculo de um polinómio

▪ Escreva o código Assembly correspondente, assumindo que x5 e x6


guardam o valor de x e y, respetivamente.

# x5=x, x6=y
li x6,5 # y = a0

li x7,7 # a1=7
mul x7,x7,x5
add x6,x6,x7 # y = a0 + a1*x

li x7,-5 # a2=-5
mul x7,x7,x5
mul x7,x7,x5
add x6,x6,x7 # y = a0 + a1*x + a2*x*x

31
Exemplo
Cálculo de um polinómio

▪ Escreva o código Assembly correspondente, assumindo que x5 e x6


guardam o valor de x e y, respetivamente.

# x5=x, x6=y
li é uma pseudo-instrução.
li x6,5 # y = a0
Como em todos os casos o
imediato (constante) é
li x7,7 representável com menos de 12
mul x7,x7,x5 bits, poderiamos, em alternativa
add x6,x6,x7 # y = a0 + a1*x usar

addi xd,x0,imm
li x7,-5
mul x7,x7,x5 ou
mul x7,x7,x5
add x6,x6,x7 # y = a0 + a1*x + a2*x*x ori xd,x0,imm

32
Exemplo
Cálculo de um polinómio

▪ Escreva o código Assembly correspondente, assumindo que x5 e x6


guardam o valor de x e y, respetivamente.

Simplificação # x5=x, x6=y


de código li x6,5 # y = a0

li x7,7
mul x7,x7,x5
addi x6,x7,5 # y = a0 + a1*x

li x7,-5
mul x7,x7,x5
mul x7,x7,x5
add x6,x6,x7 # y = a0 + a1*x + a2*x*x

33
Exemplo
Cálculo de um polinómio

▪ Considere o seguinte troço de código C:

#define a0 5.0 Mesma operação, mas em virgula


#define a1 7.0 flutuante, precisão simples:
#define a2 -5.0
𝑦 = 𝑎0 + 𝑎1 × 𝑥 − 𝑎2 × 𝑥 2
float x, y;

y = a0 + a1*x + a2*x*x; onde 𝑎0 , 𝑎1 e 𝑎2 são constantes.

▪ Escreva o código Assembly correspondente, assumindo que f0 e f1 guardam


o valor de x e y, respetivamente.

34
Exemplo
Cálculo de um polinómio

▪ Escreva o código Assembly correspondente, assumindo que f0 e f1 guardam


o valor de x e y, respetivamente.
# f0=x, f1=y
li x5,5
fcvt.s.w f1,x5 # y = (float) a0 Conversão de inteiro
para floating point
li x5,7
fcvt.s.w f2,x5 # f2 = (float) a1
fmul.s f2,f0,f2 # f2 = ((float) a1)*x
fadd.s f1,f1,f2 # y = a0 + a1*x

li x5,-5
fcvt.s.w f2,x5 # f2 = (float) a2
Todas as instruções em FP têm o
fmul.s f2,f0,f2 # f2 = ((float) a2)*x
sufixo “.s”, que representa precisão
fmul.s f2,f0,f2 # f2 = ((float) a2)*x*x simples (float). Se fosse precisão
fadd.s f1,f1,f2 # y = a0 + a1*x + a2*x*x dupla (double) usar-se-ia o prefixo “.d”
35
Exemplo
Cálculo de um polinómio

▪ Escreva o código Assembly correspondente, assumindo que f0 e f1 guardam


o valor de x e y, respetivamente.
# f0=x, f1=y
li x5,5
fcvt.s.w f1,x5 # y = (float) a0

li x5,7
fcvt.s.w f2,x5 # f2 = (float) a1
fmul.s f2,f0,f2 # f2 = ((float) a1)*x
Podemos usar a operação
fadd.s f1,f1,f2 # y = a0 + a1*x
fused fmadd.s
li x5,-5
fcvt.s.w f2,x5 # f2 = (float) a2
fmul.s f2,f0,f2 # f2 = ((float) a2)*x
fmul.s f2,f0,f2 # f2 = ((float) a2)*x*x
fadd.s f1,f1,f2 # y = a0 + a1*x + a2*x*x
36
Exemplo
Cálculo de um polinómio

▪ Escreva o código Assembly correspondente, assumindo que f0 e f1 guardam


o valor de x e y, respetivamente.
# f0=x, f1=y
li x5,5
fcvt.s.w f1,x5 # y = (float) a0

li x5,7
fcvt.s.w f2,x5 # f2 = (float) a1
fmadd.s f1,f2,f0,f1 # y = a1*x + a0

li x5,-5
fcvt.s.w f2,x5 # f2 = (float) a2
fmul.s f2,f0,f2 # f2 = ((float) a2)*x
fmadd.s f1,f2,f0,f1 # y = (a2*x)*x + (a1*x + a0)
f2 f0 f1
37
Exemplo
Cálculo de um polinómio

▪ Considere o seguinte troço de código C:

#define a0 5.0
#define a1 7.0 Mas geralmente as variáveis estão
#define a2 -5.0 guardadas em memória e não em
registos!
float x, y;

y = a0 + a1*x + a2*x*x; Como aceder à memória?

38
Instruction Set Architecture (ISA)
RISC-V
→ Instruções de acesso à memória
Estrutura lógica do core

Memória
Core

Registos
Data
inteiros
(X0,…,X31) ALU Address
(Arithmetic and Dados
Registos Logic Unit)
FP Size
(F0,…,F31)
(byte/halfword/word/doubleword) Instruções

Existem dois bancos de registos: Existe uma única memória com dados e instruções:
• X0…X31 (inteiros) • Do ponto de vista lógico segue o Modelo de Von Neumann

• F0…F31 (floating point) • Veremos mais à frente que, do ponto de vista físico, segue
40 um modelo Harvard Modificado
RISC-V Instruction Set Architecture (ISA)
Definição de “load” e “store”

▪ Memória: Gama de endereços em RV32


FFFF FFFFh
Conjunto de “gavetas”, numeradas de
00…00h a FF…FFh (endereço) Registo de destino
xn – se registo de inteiros
fn – se registo FP
▪ Operações: YYY
▫ Load registo, endereço:
xpto1 YYY

Leitura do valor em memória, no Load


endereço indicado, e escrita num (carregar/ler da memória,
registo Registo de origem endereço xpto1)
xn – se registo de inteiros

▫ Store registo, endereço:


fn – se registo FP

Escrita do valor armazenado no XXX xpto2 XXX


registo em memória (no endereço
indicado) Store
(guardar na memória,
As operações de load/store são não destrutivas, i.e., realizam uma cópia do valor endereço xpto2)
41 que está na origem para o destino. O valor permanece inalterado na origem. 0000 0000h
RISC-V Instruction Set Architecture (ISA)
Endereços de acesso à memória

▪ A memória é endereçável ao byte (caso típico).


▫ Operandos com dimensões superiores a um byte ocupam várias posições na memória:

Tipo de Tipo C Tamanho do Espaço em memória


operando equivalente operando (i.e. número de endereços)

Byte (B) byte/char 1B 1

Half-word (H) short 2B 2

Word (W) int / long 4B 4

Double Word (X) long long 8B 8

SP FP float 4B 4

DP FP double 8B 8

42
RISC-V Instruction Set Architecture (ISA)
Dimensão das palavras na memória

Quando fazemos load ou store de uma


… … … …
000Fh Byte 15 000Fh Half-Word
palavra
000Fh com mais de um byte,
000Fh
000Eh Byte 14 000Eh 7 indicamos
000Eh
Wordapenas
3 o endereço do
000Eh
000Dh Byte 13 000Dh Half-Word primeiro byte (i.e., o endereço
000Dh
/ menor)
000Dh Double
SP FP 3
000Ch Byte 12 000Ch 6 000Ch 000Ch Word 1
000Bh Byte 11 000Bh Half-Word 000Bh 000Bh /
5 Word 2 DP FP 1
000Ah Byte 10 000Ah 000Ah 000Ah
/
0009h Byte 9 0009h Half-Word 0009h 0009h
SP FP 2
0008h Byte 8 0008h 4 0008h 0008h
0007h Byte 7 0007h Half-Word 0007h 0007h
3 Word 1
0006h Byte 6 0006h 0006h 0006h
/
0005h Byte 5 0005h Half-Word 0005h 0005h Double
SP FP 1
0004h Byte 4 0004h 2 0004h 0004h Word 0
0003h Byte 3 0003h Half-Word 0003h 0003h /
1 Word 0 DP FP 0
0002h Byte 2 0002h 0002h 0002h
/
0001h Byte 1 0001h Half-Word 0001h 0001h
SP FP 0
0000h Byte 0 0000h 0 0000h 0000h
43
RISC-V Instruction Set Architecture (ISA)
Instruções de acesso à memória (base)
A terminologia M[xpto] é usada
Instrução RTL para nos referirmos à posição de
memória com endereço xpto
lw xa, imm12 (xb) xa  M[xb + imm12] (load word)

sw xa, imm12 (xb) M[xb+imm12]  xa (store word)

Endereço na memória, soma do imediato de 12 bits (com sinal) com o valor no registo xb
Registo origem/destino

Por omissão os loads e stores são


indexados, i.e., correspondem ao valor
no registo, somado do index, em bytes

IMM12 → Constante (signed)


representável em 12 bits
44 44
RISC-V Instruction Set Architecture (ISA)
Instruções de acesso à memória (base)

Instrução RTL

lw xa, imm12 (xb) xa  M[xb + imm12] (load word)

sw xa, imm12 (xb) M[xb+imm12]  xa (store word)

Variantes:
Tipo (C) Dimensão Instruções
lb / lbu
char / byte 8 bits lb, lbu, sb
Quando carregado para um
registo (X0,…,X31), o valor lido
da memória é estendido:
• lbu: com zeros
• lb: com o sinal

imm12 → Constante ≥0 (signed)


representável em 12 bits
45 45
RISC-V Instruction Set Architecture (ISA)
Instruções de acesso à memória (base)

Instrução RTL

lw xa, imm12 (xb) xa  M[xb + imm12] (load word)

sw xa, imm12 (xb) M[xb+imm12]  xa (store word)

Variantes:
Tipo (C) Dimensão Instruções
Lx / LxU
char / byte 8 bits lb, lbu, sb
Quando carregado para um
short 16 bits lh, lhu, sh registo (X0,…,X31), o valor lido
da memória é estendido:
int / long 32 bits lw, lwu, sw • LxU: com zeros
• Lx: com o sinal
long long 64 bits ld, sd

float 32 bits flw, fsw

double 64 bits fld, fsd imm12 → Constante ≥0 (signed)


representável em 12 bits
46 46
RISC-V Instruction Set Architecture (ISA)
Instruções de acesso à memória (base)

Instrução RTL

lw xa, imm12 (xb) xa  M[xb + imm12] (load word)

sw xa, imm12 (xb) M[xb+imm12]  xa (store word)

Inicialização do endereço RTL


la xa, imm Pseudo-instrução que faz uso das instruções auipc
(load upper immediate) e addi

A instrução AUIPC será explicada


nas próximas aulas

imm12 → Constante ≥0 (signed)


representável em 12 bits
47 47
RISC-V Instruction Set Architecture (ISA)
Exemplo de acesso à memória

▪ A memória é endereçável ao byte (caso típico):


Endereço Conteúdo
la x1,0x2a # pseudo-instrução …
lb x2,3(x1) # x2 = FFFF FFBBh 2Eh CFh
2Dh BBh
lb x3,-3(x1) # x3 = 0000 0053h
2Ch 47h
lbu x4,3(x1) # x4 = 0000 00BBh 2Bh 23h
2Ah ABh
29h 14h
sb x4,-1(x1) # guarda os 8 bits menos significativos
28h 02h
# de X4 no endereço 29h da memória, 27h 53h
# i.e., M[29h] passa a conter BBh …

48
Lista1=100h ➔ {-1127,+3401,1457,-4832}
Lista2=200h ➔ {…}

Exemplo: …
Cópia de uma 10Fh

lista de valores 10Eh


10Dh
-4832

10Ch
Considere uma lista de 10Bh
palavras (32 bits), 10Ah
+1457
armazenada em memória. 109h
Escreva o código que 108h
copia os 4 elementos para 107h
106h
a zona de outra lista de +3401
105h
valores. 104h
103h
102h
-1127
101h
100h
49 …
Lista1=100h ➔ {-1127,+3401,1457,-4832}
Lista2=200h ➔ {…}

Exemplo: …
la x10,lista1
Cópia de uma 10Fh
la x11,lista2
lista de valores 10Eh
10Dh
-4832

10Ch
Considere uma lista de 10Bh
palavras (32 bits), 10Ah
+1457
armazenada em memória. 109h
Escreva o código que 108h
copia os 4 elementos para 107h
106h
a zona de outra lista de Inicialização dos registos x10 e x11 com o +3401
105h
valores. endereço das listas de valores. 104h
103h
102h
Na prática diz-se que x10 e x11 são ponteiros -1127
101h
para Lista1 e Lista2 (i.e., guardam o endereço
do primeiro elemento da lista) 100h
50 …
Lista1=100h ➔ {-1127,+3401,1457,-4832}
Lista2=200h ➔ {…}

Exemplo: …
la x10,lista1
Cópia de uma 10Fh
la x11,lista2
lista de valores 10Eh
10Dh
-4832

10Ch
Considere uma lista de lw x12,0(x10)
10Bh
palavras (32 bits), sw x12,0(x11) 10Ah
+1457
armazenada em memória. 109h
Escreva o código que lw x12,4(x10) 108h
copia os 4 elementos para sw x12,4(x11) 107h
106h
a zona de outra lista de +3401
105h
valores. lw x12,8(x10)
104h
sw x12,8(x11) 103h
102h
-1127
lw x12,12(x10) 101h
100h
sw x12,12(x11)
51 …
Informativo

▪ Determine o valor de cada byte 10Fh
em memória 10Eh
-4832
10Dh
10Ch
10Bh
10Ah
+1457
109h
108h
107h
106h
+3401
105h
104h
103h
102h
-1127
101h
100h
52 …
Informativo
Valores armazenados em cada posição de memória


▪ Determine o valor de cada byte 10Fh
em memória FF FF ED 20h
10Eh
-4832
10Dh
10Ch
10Bh
10Ah
00 00 05 B1h +1457
109h
108h
107h
106h
00 00 0D 49h +3401
105h
104h
103h
102h
FF FF FB 99h -1127
101h
100h
53 53 …
Informativo
Valores armazenados em cada posição de memória

… …
▪ Determine o valor de cada byte 10Fh 10Fh
em memória 10Eh
FF FF ED 20h
10Eh
-4832
10Dh 10Dh
10Ch 10Ch
10Bh 10Bh
10Ah 10Ah
00 00 05 B1h +1457
109h 109h
108h 108h
107h 107h
106h 106h
00 00 0D 49h +3401
105h 105h
104h 104h
103h 103h
102h 102h
FF FF FB 99h -1127
101h 101h
100h 100h
54 54 … …
Informativo
Valores armazenados em cada posição de memória

… …
▪ Determine o valor de cada byte 10Fh FFh 10Fh
em memória 10Eh FFh
FF FF ED 20h
10Eh
-4832
10Dh EDh 10Dh
10Ch 20h 10Ch
▪ A organização dos bytes em 10Bh 00h 10Bh

memória é denominada de 10Ah 00h


00 00 05 B1h
10Ah
+1457
109h 05h 109h
“Endianness”
▫ Neste caso corresponde a uma
108h
107h
B1h
00h
108h
107h
topologia “little-endian”. 106h 00h 106h
00 00 0D 49h +3401
105h 0Dh 105h
Nota 1: existem arquiteturas que são big-endian (i.e., 104h 49h 104h
os dados são armazenados ao contrário). Outras são 103h FFh 103h
bi-endian, o que significa que tanto podem ser little-
102h FFh 102h
ou big-endian. FF FF FB 99h -1127
101h FBh 101h
Nota2: Este conceito também se pode aplicar aos 100h 99h 100h
55 protocolos
55 de comunicação e ao formato de ficheiros … …
RISC-V Instruction Set Architecture (ISA)
Dimensão das palavras na memória

… … … …
000Fh Byte 15 000Fh Half-Word 000Fh 000Fh
7 Word 3
000Eh Byte 14 000Eh 000Eh 000Eh
/
000Dh Byte 13 000Dh Half-Word 000Dh 000Dh Double
SP FP 3
000Ch Byte 12 000Ch 6 000Ch 000Ch Word 1
000Bh Byte 11 000Bh Half-Word 000Bh 000Bh /
5 Word 2 DP FP 1
000Ah Byte 10 000Ah 000Ah 000Ah
/
0009h Byte 9 0009h Half-Word 0009h 0009h
SP FP 2
0008h Byte 8 0008h 4 0008h 0008h
0007h Byte 7 0007h Half-Word 0007h 0007h
3 Word 1
0006h Byte 6 0006h 0006h 0006h
/
0005h Byte 5 0005h Half-Word 0005h 0005h Double
SP FP 1
0004h Byte 4 0004h 2 0004h 0004h Word 0
0003h Byte 3 0003h Half-Word 0003h 0003h /
1 Word 0 DP FP 0
0002h Byte 2 0002h 0002h 0002h
/
0001h Byte 1 0001h Half-Word 0001h 0001h
SP FP 0
0000h Byte 0 0000h 0 0000h 0000h
56
RISC-V Instruction Set Architecture (ISA)
Dimensão das palavras na memória

Exemplos de acessos alinhados


… … … …
000Fh Byte 15 000Fh Half-Word 000Fh 000Fh
7 Word 3
000Eh Byte 14 000Eh 000Eh 000Eh
/
000Dh Byte 13 000Dh Half-Word 000Dh 000Dh Double
SP FP 3
000Ch Byte 12 000Ch 6 000Ch 000Ch Word 1
000Bh Byte 11 000Bh Half-Word 000Bh 000Bh /
5 Word 2 DP FP 1
000Ah Byte 10 000Ah 000Ah 000Ah
/
0009h Byte 9 0009h Half-Word 0009h 0009h
SP FP 2
0008h Byte 8 0008h 4 0008h 0008h
0007h Byte 7 0007h Half-Word 0007h 0007h
3 Word 1
0006h Byte 6 0006h 0006h 0006h
/
0005h Byte 5 0005h Half-Word 0005h 0005h Double
SP FP 1
0004h Byte 4 0004h 2 0004h 0004h Word 0
0003h Byte 3 0003h Half-Word 0003h 0003h /
1 Word 0 DP FP 0
0002h Byte 2 0002h 0002h 0002h
/
0001h Byte 1 0001h Half-Word 0001h 0001h
SP FP 0
0000h Byte 0 0000h 0 0000h 0000h
57
RISC-V Instruction Set Architecture (ISA)
Dimensão das palavras na memória

Exemplos de acessos *NÃO* alinhados


… … … …
000Fh Byte 15 000Fh Half-Word 000Fh 000Fh
7 Word 3
000Eh Byte 14 000Eh 000Eh 000Eh
/
000Dh Byte 13 000Dh Half-Word 000Dh 000Dh Double
SP FP 3
000Ch Byte 12 000Ch 6 000Ch 000Ch Word 1
000Bh Byte 11 000Bh Half-Word 000Bh 000Bh /
5 Word 2 DP FP 1
000Ah Byte 10 000Ah 000Ah 000Ah
/
0009h Byte 9 0009h Half-Word 0009h 0009h
SP FP 2
0008h Byte 8 0008h 4 0008h 0008h
0007h Byte 7 0007h Half-Word 0007h 0007h
3 Word 1
0006h Byte 6 0006h 0006h 0006h
/
0005h Byte 5 0005h Half-Word 0005h 0005h Double
SP FP 1
0004h Byte 4 0004h 2 0004h 0004h Word 0
0003h Byte 3 0003h Half-Word 0003h 0003h /
1 Word 0 DP FP 0
0002h Byte 2 0002h 0002h 0002h
/
0001h Byte 1 0001h Half-Word 0001h 0001h
SP FP 0
0000h Byte 0 0000h 0 0000h 0000h
58
RISC-V Instruction Set Architecture (ISA)
Alinhamento no acesso à memória

▪ A memória é endereçável ao byte (caso típico).


▫ Operandos com dimensões superiores a um byte ocupam várias posições na memória.
▫ O alinhamento dos dados em memória é garantido colocando os m bits menos significativos
do endereço a zero, onde m é calculado como:

𝑚 = log2 𝑑𝑖𝑚𝑒𝑛𝑠ã𝑜 𝑑𝑎 𝑣𝑎𝑟𝑖á𝑣𝑒𝑙Τ𝑑𝑖𝑚𝑒𝑛𝑠ã𝑜 𝑑𝑎 𝑝𝑎𝑙𝑎𝑣𝑟𝑎 𝑑𝑒 𝑚𝑒𝑚ó𝑟𝑖𝑎

Como a palavra de memória tem dimensão de 1B (tamanho de cada posição de memória),


obtém-se:

Byte ➔ log 2 1𝐵Τ1𝐵 = 0, i.e., não é necessário qualquer alinhamento


Half-word ➔ log 2 2𝐵Τ1𝐵 = 1, i.e., o último bit do endereço é sempre 0
Word ➔ log 2 4𝐵Τ1𝐵 = 2, i.e., os últimos dois bits do endereço são sempre 0
Double Word ➔ log 2 8𝐵Τ1𝐵 = 3, i.e., os últimos três bits do endereço são sempre 0
SP FP ➔ log 2 4𝐵Τ1𝐵 = 2, i.e., os últimos dois bits do endereço são sempre 0
DP FP ➔ log 2 8𝐵Τ1𝐵 = 3, i.e., os últimos três bits do endereço são sempre 0
59
RISC-V Instruction Set Architecture (ISA)
Alinhamento das palavras na memória

O primeiro byte de cada O primeiro byte de cada


O primeiro byte de cada
… … Word… é múltiplo de 4, Double… Word é múltiplo
Half-word é par, i.e., o
000Fh Byte 15 000Fh Half-Word i.e., os 2Word
000Fh bits menos de 8, i.e., os 3 bits
000Fh
000Eh Byte 14 bit menos significativo
000Eh 7 000Eh
3
000Eh
significativos
/ do menos significativos do
000Dh Byte 13 do endereço é zero.
000Dh Half-Word 000Dh 000Dh Double
endereçoSP são
FP 3zero. endereço são zero.
000Ch Byte 12 000Ch 6 000Ch 000Ch Word 1
000Bh Byte 11 000Bh Half-Word 000Bh 000Bh /
5 Word 2 DP FP 1
000Ah Byte 10 000Ah 000Ah 000Ah
/
0009h Byte 9 0009h Half-Word 0009h 0009h
SP FP 2
0008h Byte 8 0008h 4 0008h 0008h
0007h Byte 7 0007h Half-Word 0007h 0007h
3 Word 1
0006h Byte 6 0006h 0006h 0006h
/
0005h Byte 5 0005h Half-Word 0005h 0005h Double
SP FP 1
0004h Byte 4 0004h 2 0004h 0004h Word 0
0003h Byte 3 0003h Half-Word 0003h 0003h /
1 Word 0 DP FP 0
0002h Byte 2 0002h 0002h 0002h
/
0001h Byte 1 0001h Half-Word 0001h 0001h
SP FP 0
0000h Byte 0 0000h 0 0000h 0000h
60
Instruction Set Architecture (ISA)
RISC-V
→ Estrutura de um programa Assembly
RISC-V Instruction Set Architecture (ISA)
Diretivas de Assembler

▪ As directivas não são instruções!


▫ São comandos para o Assembler gerar o código binário

Directiva Descrição
.text Declara uma zona de código (instruções)
.data Declara uma zona de dados writable

Directiva Descrição
.zero N Declara um vetor de N bytes inicializados a 0
Declara um vetor de bytes (1B) sequenciais em memória com valores
.byte num1 [,num2, …]
num1, [num2, …]
.half num1 [,num2, …] Declara um vetor de half (2B) sequenciais em memória
.word num1 [,num2, …] Declara um vetor de words (4B) sequenciais em memória
.dword num1 [,num2, …] Declara um vetor de doublewords (8B) sequenciais em memória
.string “list of characters” Declara uma string em memória. Cada character (char) ocupa 1 B
62
Definem-se duas “zonas” no código Assembly: dados e
código.

Ripes # Declaração de variáveis


Simulador .data
var1: .word 153
Estrutura de um var2: .half -1227, 3443,213,0x14,13
programa em str1: .string “cadeia de caracteres”
Assembly

# Código
.text
la x1,var1
lw x2,0(x1)
loop: j loop

63
Definem-se duas “zonas” no código Assembly: dados e
código.
Valores em
Ripes # Declaração de variáveis hexadecimal
Simulador .data (nomenclatura
var1: .word 153 de C)
Estrutura de um var2: .half -1227, 3443,213,0x14,13
programa em str1: .string “cadeia de caracteres”
Assembly

# Código
.text
la x1,var1
lw x2,0(x1)
loop: j loop

64
Definem-se duas “zonas” no código Assembly: dados e
código.

Ripes # Declaração de variáveis


Simulador .data
var1: .word 153
Etiquetas (labels)
Estrutura de um var2: .half -1227, 3443,213,0x14,13
programa em str1: .string “cadeia de caracteres”
Assembly

# Código
.text
la x1,var1
lw x2,0(x1)
loop: j loop

65
Definem-se duas “zonas” no código Assembly: dados e
código.

Ripes # Declaração de variáveis


Simulador .data Diretivas de Assembly
var1: .word 153
Estrutura de um var2: .half -1227, 3443,213,0x14,13
programa em str1: .string “cadeia de caracteres”
Assembly

# Código
.text Instruções
la x1,var1
lw x2,0(x1)
loop: j loop

66
Definem-se duas “zonas” no código Assembly: dados e
código.

Ripes # Declaração de variáveis


Operandos
Simulador .data
var1: .word 153
Estrutura de um var2: .half -1227, 3443,213,0x14,13
programa em str1: .string “cadeia de caracteres”
Assembly

# Código
.text
la x1,var1
lw x2,0(x1)
loop: j loop

67
Instruction Set Architecture (ISA)
RISC-V
→ Controlo do fluxo de instruções
Revisão da estrutura lógica do core

Core Memória

Registos
inteiros
(X0,…,X31) ALU Data
(Arithmetic and
Registos Logic Unit) Dados
FP Address
(F0,…,F31)

Instruções
Registos especiais (ex: PC)

Existem dois bancos de registos: Existe uma única memória com dados e instruções:
• X0…X31 (inteiros) • Do ponto de vista lógico segue o Modelo de Von Neumann

• F0…F31 (floating point) • Veremos mais à frente que, do ponto de vista físico, segue
69 um modelo Harvard Modificado
Fluxo de instruções

la x10,lista1
▪ Exemplo da última aula (cópia dos
la x11,lista2
elementos de um vetor A para outro
vetor B)

Ordem de execução
lw x12,0(x10)

das instruções
sw x12,0(x11)

lw x12,4(x10)
sw x12,4(x11)

lw x12,8(x10)
sw x12,8(x11)

lw x12,12(x10)
sw x12,12(x11)
70
Fluxo de instruções

la x10,lista1
▪ Exemplo da última aula (cópia dos
la x11,lista2
elementos de um vetor A para outro
vetor B)

Ordem de execução
lw x12,0(x10)

das instruções
sw x12,0(x11)

São pseudo-instruções. De lw x12,4(x10)


acordo com o manual, seriam
substituídas pela combinação sw x12,4(x11)
auipc+addi. Contudo, para
simplificar, vamos trocar por: lw x12,8(x10)
sw x12,8(x11)
ori x10,x0,100h
ori x11,x0,200h
lw x12,12(x10)
sw x12,12(x11)
71
Fluxo de instruções
Execução de um troço de código
Memória

De acordo com o slide anterior,


o vetor de origem (V1) está no
endereço 100h, e o vetor de 200h Dados
destino/cópia (V2) está no
endereço 200h
100h

O programa começa sempre no endereço 0 Programa

(Quando fazem reset ao processador, estão a colocá-lo na


72 instrução que está no endereço 0) 0h
Fluxo de instruções
Endereço na
memória Instrução
▪ Como cada instrução ocupa 4B, e a
00h: ori x10,x0,100h
primeira está no endereço 0
04h: ori x11,x0,200h

08h: lw x12,0(x10)
0Ch: sw x12,0(x11)

10h: lw x12,4(x10)
14h: sw x12,4(x11)

18h: lw x12,8(x10)
1Ch: sw x12,8(x11)

20h: lw x12,12(x10)
73 24h: sw x12,12(x11)
Fluxo de instruções
Endereço na
memória Instrução
▪ Por omissão as instruções são
executadas em sequência. 00h: ori x10,x0,100h
04h: ori x11,x0,200h

▪ Para que o processador saiba em que 08h: lw x12,0(x10)

Ordem de execução
instrução se encontra, existe um registo 0Ch: sw x12,0(x11)

das instruções
que contém o endereço da instrução a
executar: 10h: lw x12,4(x10)
14h: sw x12,4(x11)
Program counter (PC)
18h: lw x12,8(x10)
▪ O nome mais adequado para este registo 1Ch: sw x12,8(x11)
seria Instruction Address Register, mas o
nome é histórico. 20h: lw x12,12(x10)
74 24h: sw x12,12(x11)
Fluxo de instruções
Execução de um troço de código

Xn
X0
X1 Banco de
X2 registos para
inteiros
……
X31

double
Data
Single
F0
F1
Banco de registos para

vírgula flutuante Program
F31

75
Fluxo de instruções
Execução de um troço de código

… …
X0 0 ?
PC 0000 0000h 0204h

?
0200h
X10
… …
X11 +3401
0104h
X12
-1127
… 0100h
… …
X31
sw x12,4(x11)
0014h
lw x12,4(x10)
0010h
F0
sw x12,0(x11)
F1 000Ch
Ponto inicial:
… lw x12,0(x10)
Quando se faz reset ao processador coloca-se o PC=0, 0008h
F31 obrigando a executar a instrução armazenada na ori x11,x0,200h
0004h
posição 0 PC
76 ori x10,x0,100h
0000h
Fluxo de instruções
Execução de um troço de código

… …
X0 0 ?
PC 0000 0004h 0204h

?
0200h
X10 0000 0100h
… …
X11 +3401
0104h
X12
-1127
… 0100h
… …
X31
sw x12,4(x11)
0014h
lw x12,4(x10)
0010h
F0
sw x12,0(x11)
F1 000Ch
Resultado da execução da primeira instrução:
… lw x12,0(x10)
Como cada instrução ocupa 4 bytes, o registo PC é 0008h
F31 incrementado por 4 de cada vez que uma instrução é PC ori x11,x0,200h
0004h
executada
77 ori x10,x0,100h
0000h
Fluxo de instruções
Execução de um troço de código

… …
X0 0 ?
PC 0000 0008h 0204h

?
0200h
X10 0000 0100h
… …
X11 0000 0200h +3401
0104h
X12
-1127
… 0100h
… …
X31
sw x12,4(x11)
0014h
lw x12,4(x10)
0010h
F0
sw x12,0(x11)
F1 000Ch
Resultado da execução da segunda instrução:
… PC lw x12,0(x10)
Na prática, sempre que uma instrução é executada, o 0008h
F31 PC tem necessariamente de ser alterado. ori x11,x0,200h
0004h

78 ori x10,x0,100h
0000h
Fluxo de instruções
Execução de um troço de código

… …
X0 0 ?
PC 0000 000Ch 0204h

?
0200h
X10 0000 0100h
… …
X11 0000 0200h +3401
0104h
X12 FFFF FB99h (-1127)
-1127
… 0100h
… …
X31
sw x12,4(x11)
0014h
lw x12,4(x10)
0010h
F0
PC sw x12,0(x11)
F1 000Ch
Na prática pode-se dizer que o PC é um ponteiro, que
aponta
… (guarda o endereço) para a próxima instrução lw x12,0(x10)
0008h
F31 a executar. ori x11,x0,200h
0004h

79 ori x10,x0,100h
0000h
Fluxo de instruções
Execução de um troço de código

… …
X0 0 ?
PC 0000 0010h 0204h

-1127
0200h
X10 0000 0100h
… …
X11 0000 0200h +3401
0104h
X12 FFFF FB99h
-1127
… 0100h
… …
X31
sw x12,4(x11)
0014h
PC lw x12,4(x10)
0010h
F0
sw x12,0(x11)
F1 000Ch
Na prática pode-se dizer que o PC é um ponteiro, que
aponta
… (guarda o endereço) para a próxima instrução lw x12,0(x10)
0008h
F31 a executar. ori x11,x0,200h
0004h

80 ori x10,x0,100h
0000h
Fluxo de instruções
Execução de um troço de código

… …
X0 0 ?
PC 0000 0014h 0204h

-1127
0200h
X10 0000 0100h
… …
X11 0000 0200h +3401
0104h
X12 0000 0D49h (3401)
-1127
… 0100h
… …
X31 PC sw x12,4(x11)
0014h
lw x12,4(x10)
0010h
F0
sw x12,0(x11)
F1 000Ch
Sem outras instruções o PC é incrementado de 4
após…a execução de cada instrução. Assim, as 0008h
lw x12,0(x10)

F31 instruções são sempre executadas em sequência, ori x11,x0,200h


sem possibilidades de interromper o fluxo de 0004h

81 instruções. 0000h
ori x10,x0,100h
RISC-V Instruction Set Architecture (ISA)
Controlo do fluxo de instruções

Nota: Como veremos mais à frente, na


Instrução RTL verdade na codificação da instrução
omitimos o bit menos significativo
j imm21 pc  pc + imm21
(pseudo-instrução)

A pseudo-instrução jump (“J”) codifica um salto de IMM21/4 instruções


(signed), relativamente à instrução J, ex:

add x1,x2,x3 add x1,x2,x3 add x1,x2,x3


mul x1,x1,x1 mul x1,x1,x1 mul x1,x1,x1
sub x4,x1,x2 sub x4,x1,x2 sub x4,x1,x2
div x5,x4,x1 div x5,x4,x1 div x5,x4,x1
j -12 # goto mul j 0 # goto j j 8 # skip ori
lw x2,4(x10) # infinite loop ori x0,x0,0
lw x2,4(x10)

82 82
RISC-V Instruction Set Architecture (ISA)
Controlo do fluxo de instruções

Nota: Como veremos mais à frente, na


Instrução RTL verdade na codificação da instrução
omitimos o bit menos significativo
j imm21 pc  pc + imm21
(pseudo-instrução)

Para simplificar a representação, o endereço de salto é apresentado em


Assembly através da referência a uma label:

add x1,x2,x3 add x1,x2,x3 add x1,x2,x3


goback: mul x1,x1,x1 mul x1,x1,x1 mul x1,x1,x1
sub x4,x1,x2 sub x4,x1,x2 sub x4,x1,x2
div x5,x4,x1 div x5,x4,x1 div x5,x4,x1
j goback loop: j loop j target
lw x2,4(x10) ori x0,x0,0
target: lw x2,4(x10)

83 83
RISC-V Instruction Set Architecture (ISA)
Controlo do fluxo de instruções

Nota: Como veremos mais à frente, na


Instrução RTL Este tipo de salto é denominado de saltoda
verdade na codificação relativo,
instruçãojá
que é relativo ao valor omitimos
do PC. o bit menos significativo
j imm21 pc  pc + imm21
Nota: Como veremos mais à frente, na
(pseudo-instrução) verdade na codificação da instrução
É também por vezes referido
omitimosque
o bit a instrução
menos tem
significativo
um modo de endereçamento implícito, já que a
Para simplificar a representação, o endereço de saltomanipulação
é apresentado
doem
registo PC é implícita pelo tipo de
Assembly através da referência a uma label: instrução.

add x1,x2,x3 add x1,x2,x3 add x1,x2,x3


goback: mul x1,x1,x1 mul x1,x1,x1 mul x1,x1,x1
sub x4,x1,x2 sub x4,x1,x2 sub x4,x1,x2
div x5,x4,x1 div x5,x4,x1 div x5,x4,x1
j goback loop: j loop j target
lw x2,4(x10) ori x0,x0,0
target: lw x2,4(x10)

84 84
Fluxo de instruções

▪ Por vezes, é necessário interromper o fluxo


natural de instruções.

Requer:
▪ Exemplo:
If cond then A) O teste da condição
if sequence
else
B) A execução de um
else sequence “salto” para o troço
correspondente, mudando
o fluxo (ordem) da
execução de instruções

85
Instruções de comparação e teste ARMv8
Exemplo para o ARMv8 (ISA diferente do estudado nas aulas)

Instrução RTL
Na generalidade dos
ADDS Ra,Rb,Rc Ra,Flags  Rb + Rc
processadores, existem
SUBS Ra,Rb,Rc Ra,Flags  Rb – Rc instruções que permitem
… alterar as flags do
processador, nomeadamente:
Z → o resultado é zero
N → o resultado é negativo
C → transporte à saída do
somador
V → Overflow
As flags são armazenadas num
registo de estado (RE)

86 86
Instruções de comparação e teste ARMv8
Exemplo para o ARMv8 (ISA diferente do estudado nas aulas)

Instrução RTL
Existem instruções de Na generalidade dos
ADDS Ra,Rb,Rc Ra,Flags  Rb + Rc
controlo de fluxo de processadores, existem
SUBS Ra,Rb,Rc Ra,Flags  Rb –instruções
Rc que permitem instruções que permitem
… saltar para outra zona do alterar as flags do
código, eventualmente processador, nomeadamente:
condicionadas ao estado das Z → o resultado é zero
flags
Instrução RTL N → o resultado é negativo
B IMM26 PC  PC + IMM26x4 C → transporte à saída do
B.cond IMM19 If cond=true then somador
PC  PC + IMM26x4
V → Overflow
Else
PC  PC + 4 As flags são armazenadas num
registo de estado (RE)

87 87
RISC-V Instruction Set Architecture (ISA)
Instruções de comparação

Instrução RTL
Poderiamos pensar em mais
slt xd,xa,xb xd  (xa<xb)?1:0
instruções de comparação,
slti xd,xa, imm12 xd  (xa<imm12)?1:0 mas como veremos mais à
sltu xd,xa,xb xd  (xa<xb)?1:0 (unsigned comparison) frente, estas são suficientes.
sltiu xd,xa, imm12 xd  (xa<uimm12)?1:0 (unsigned comparison)

88
RISC-V Instruction Set Architecture (ISA)
Instruções de comparação

Instrução RTL
Poderiamos pensar em mais
slt xd,xa,xb xd  (xa<xb)?1:0
instruções de comparação,
slti xd,xa, imm12 xd  (xa<imm12)?1:0 mas como veremos mais à
sltu xd,xa,xb xd  (xa<xb)?1:0 (unsigned comparison) frente, estas são suficientes.
sltiu xd,xa, imm12 xd  (xa<uimm12)?1:0 (unsigned comparison)

Instrução RTL
As operações de
feq.s xd,fa,fb feq.d xd,fa,fb xd  (fa==fb)?1:0
comparação de FP
flt.s xd,fa,fb flt.d xd,fa,fb xd  (fa<fb)?1:0 escrevem num
fle.s xd,fa,fb fle.d xd,fa,fb xd  (fa<=fb)?1:0 registo de inteiros!
fclass.s xd,fa fclass.d xd,fa xd  class (fa)

S = Single D = Double
89 89 (C float) (C double)
RISC-V Instruction Set Architecture (ISA)
Instrução fclass
Sign Exponent Fraction
Nota: A fração não pode ser zero, caso
Signaling NaN (SP FP) S 1111 1111 1xx xxxx xxxx xxxx xxxx xxxx
contrário representa +∞/−∞

O bit de sinal num resultado


Quiet NaN (SP FP) S 1111 1111 0xx xxxx xxxx xxxx xxxx xxxx
NaN geralmente é ignorado
pelas aplicações

Class FP type

0 −∞
Instrução RTL 1 Negative Normal
2 Negative Subnormal
feq.s xd,fa,fb feq.d xd,fa,fb xd  (fa==fb)?1:0
3 -0
flt.s xd,fa,fb flt.d xd,fa,fb xd  (fa<fb)?1:0 4 +0
fle.s xd,fa,fb fle.d xd,fa,fb xd  (fa<=fb)?1:0 5 Positive Subnormal
6 Positive Normal
fclass.s xd,fa fclass.d xd,fa xd  class (fa)
7 +∞
8 Signaling NaN
S = Single D = Double 9 Quiet NaN
90 90 (C float) (C double)
RISC-V Instruction Set Architecture (ISA)
Controlo do fluxo de instruções

Instrução RTL
beq xa,xb,imm13 if (xa==xb) pc  pc + imm13 else: pc  pc + 4
bne xa,xb,imm13 if (xa!=xb) pc  pc + imm13 else: pc  pc + 4
blt xa,xb,imm13 if (xa<xb) pc  pc + imm13 else: pc  pc + 4
bge xa,xb,imm13 if (xa>=xb) pc  pc + imm13 else: pc  pc + 4

91
RISC-V Instruction Set Architecture (ISA)
Controlo do fluxo de instruções

Instrução RTL
beq xa,xb,imm13 if (xa==xb) pc  pc + imm13 else: pc  pc + 4
bne xa,xb,imm13 if (xa!=xb) pc  pc + imm13 else: pc  pc + 4
blt xa,xb,imm13 if (xa<xb) pc  pc + imm13 else: pc  pc + 4
bge xa,xb,imm13 if (xa>=xb) pc  pc + imm13 else: pc  pc + 4

ori x10,x0,1 ori x10,x0,5


ori x11,x0,1 ori x11,x0,1
goback: addi x11,x11,1 goback: addi x11,x11,-1
beq x10,x11,goback bge x10,x11,goback 4x
… …
(sai com X11=6)

92
RISC-V Instruction Set Architecture (ISA)
Controlo do fluxo de instruções

Instrução RTL
beq xa,xb,imm13 if (xa==xb) pc  pc + imm13 else: pc  pc + 4
bne xa,xb,imm13 if (xa!=xb) pc  pc + imm13 else: pc  pc + 4
blt xa,xb,imm13 if (xa<xb) pc  pc + imm13 else: pc  pc + 4
bge xa,xb,imm13 if (xa>=xb) pc  pc + imm13 else: pc  pc + 4

Instrução RTL
bltu xa,xb,imm13 if (xa<xb) pc  pc + imm13 else: pc  pc + 4 Unsigned
comparison
bgeu xa,xb,imm13 if (xa>=xb) pc  pc + imm13 else: pc  pc + 4

93
RISC-V Instruction Set Architecture (ISA)
Controlo do fluxo de instruções

Instrução RTL
beq xa,xb,imm13 if (xa==xb) pc  pc + imm13 else: pc  pc + 4
bne xa,xb,imm13 if (xa!=xb) pc  pc + imm13 else: pc  pc + 4
blt xa,xb,imm13 if (xa<xb) pc  pc + imm13 else: pc  pc + 4
bge xa,xb,imm13 if (xa>=xb) pc  pc + imm13 else: pc  pc + 4

Instrução RTL
bltu xa,xb,imm13 if (xa<xb) pc  pc + imm13 else: pc  pc + 4 Unsigned
comparison
bgeu xa,xb,imm13 if (xa>=xb) pc  pc + imm13 else: pc  pc + 4

Instrução RTL
jal xd,imm21 xd pc+4 and pc  pc + imm21
jalr xd,xa,imm12 xd pc+4 and pc  xa + imm12 Salto absoluto

94 94
RISC-V Instruction Set Architecture (ISA)
Controlo do fluxo de instruções

Instrução RTL
beq xa,xb,imm13 if (xa==xb) pc  pc + imm13 else: pc  pc + 4
bne xa,xb,imm13 if (xa!=xb) pc  pc + imm13 else: pc  pc + 4
As instruções jal/jalr são usadas nos seguintes contextos:
blt xa,xb,imm13 if (xa<xb) pc  pc + imm13 else: pc  pc + 4
bge
• Salto absoluto
xa,xb,imm13 if (xa>=xb) pc  pc + imm13 else: pc  pc + 4
• Pseudo-instrução “j imm21” ➔ “jal x0,imm21”

Instrução Chamadas e retorno de funções
RTL (rotinas)
bltu • (explicado mais à frente) if (xa<xb)
xa,xb,imm13 pc  pc + imm13 else: pc  pc + 4 Unsigned
comparison
bgeu xa,xb,imm13 if (xa>=xb) pc  pc + imm13 else: pc  pc + 4

Instrução RTL
jal xd,imm21 xd pc+4 and pc  pc + imm21
jalr xd,xa,imm12 xd pc+4 and pc  xa + imm12 Salto absoluto

95 95
RISC-V Instruction Set Architecture (ISA)
Controlo do fluxo de instruções

Do ponto de vista de
Instrução RTL
desempenho, as instruções
beq xa,xb,imm13 if (xa==xb) pc  pc + imm13 else: pc  pc + 4 de salto (branch) são
bne xa,xb,imm13 if (xa!=xb) pc  pc + imm13 else: pc  pc + 4 indesejáveis e devem ser
evitadas (quando possível).
blt xa,xb,imm13 if (xa<xb) pc  pc + imm13 else: pc  pc + 4
bge xa,xb,imm13 if (xa>=xb) pc  pc + imm13 else: pc  pc + 4

Instrução RTL
bltu xa,xb,imm13 if (xa<xb) pc  pc + imm13 else: pc  pc + 4 Unsigned
comparison
bgeu xa,xb,imm13 if (xa>=xb) pc  pc + imm13 else: pc  pc + 4

Instrução RTL
jal xd,imm21 xd pc+4 and pc  pc + imm21
jalr xd,xa,imm12 xd pc+4 and pc  xa + imm12 Salto absoluto

96 96
Instruction Set Architecture (ISA)
RISC-V
→ Controlo de fluxo de instruções (Exemplos práticos)
RISC-V Instruction Set Architecture (ISA)
Mapear estruturas de código C

Código C fonte:
do_pre_if; do_pre_if;

if (cond) Na prática o código diz if (!cond) branch (goto) end_if;


do_a; que devemos saltar a secção do_a;
“do_a” se a condição for falsa
do_after_if; end_if:
do_after_if;

98 98
RISC-V Instruction Set Architecture (ISA)
Mapear estruturas de código C

Código C fonte:
do_pre_if; do_pre_if;

if (cond) Na prática o código diz if (!cond) branch (goto) else;


do_a; que devemos saltar a secção do_a;
else “do_a” se a condição for falsa Branch (goto) end_if;
do_b;
do_after_if; else: do_b;
end_if:
do_after_if;

99 99
.data
a: .word 0x406ccccd # float 3.7
b: .word 0x4019999a # float 2.4
Exemplo de c: .word 0

controlo do fluxo
de instruções Memória

if (a>b) …
c=b*b; c+3
c+2
else 0
c+1
c=a/b;
X12 c=b+8
b+3
b+2
2.4
b+1
X11 b=a+8
a+3
a+2
3.7
a+1
X10 a

100
.data
a: .word 0x406ccccd # float 3.7
b: .word 0x4019999a # float 2.4
Exemplo de c: .word 0

controlo do fluxo .text


de instruções # leitura das variáveis da memória Memória
la x10,a # x10 aponta para a
if (a>b) la x11,b # x11 aponta para b …
c=b*b; la x12,c # x12 aponta para c c+3
c+2
else 0
c+1
c=a/b;
X12 c=b+8
b+3
b+2
2.4
b+1
X11 b=a+8
a+3
a+2
3.7
a+1
X10 a

101
.data
a: .word 0x406ccccd # float 3.7
b: .word 0x4019999a # float 2.4
Exemplo de c: .word 0

controlo do fluxo .text


de instruções # leitura das variáveis da memória Memória
la x10,a # x10 aponta para a
if (a>b) la x11,b # x11 aponta para b …
c=b*b; la x12,c # x12 aponta para c c+3
c+2
else 0
c+1
c=a/b; flw f0,0(x10) # f0 = a
flw f1,0(x11) # f1 = b X12 c=b+8
b+3
b+2
2.4
b+1
X11 b=a+8
a+3
a+2
3.7
a+1
X10 a

102
.data
a: .word 0x406ccccd # float 3.7
b: .word 0x4019999a # float 2.4
Exemplo de c: .word 0

controlo do fluxo .text


de instruções # leitura das variáveis da memória Memória
la x10,a # x10 aponta para a
if (a>b) la x11,b # x11 aponta para b …
c=b*b; la x12,c # x12 aponta para c c+3
c+2
else 0
c+1
c=a/b; flw f0,0(x10) # f0 = a
flw f1,0(x11) # f1 = b X12 c=b+4
b+3
flt.s x13,f1,f0 # 1 if b<a
b+2
beq x13,x0,else 2.4
b+1

X11 b=a+4
j endif # jal x0,endif a+3
else: … a+2
3.7
endif: Salta se a a+1
condição X10 a
for falsa …
103
.data
a: .word 0x406ccccd # float 3.7
b: .word 0x4019999a # float 2.4
Exemplo de c: .word 0

controlo do fluxo .text


de instruções # leitura das variáveis da memória Memória
la x10,a # x10 aponta para a
if (a>b) la x11,b # x11 aponta para b …
c=b*b; la x12,c # x12 aponta para c c+3
c+2
else 0
c+1
c=a/b; flw f0,0(x10) # f0 = a
flw f1,0(x11) # f1 = b X12 c=b+4
b+3
flt.s x13,f1,f0 # 1 if b<a
b+2
beq x13,x0,else 2.4
b+1
fmul.s f2,f1,f1
X11 b=a+4
j endif # jal x0,endif a+3
else: fdiv.s f2,f0,f1 a+2
3.7
endif: fsw f2,0(x12) a+1
X10 a

104
Como ambos os lados alteram a variável c, pode-se mover uma das
opções para antes do loop.
Exemplo de
controlo do fluxo
de instruções
if (a>b) c=b*b
c=b*b;
Sim Não
else a>b
c=a/b; Sim Não
a>b
c=b*b c=a/b
c=a/b

105
.data
a: .word 0x406ccccd # float 3.7
b: .word 0x4019999a # float 2.4
Exemplo de c: .word 0

controlo do fluxo .text


de instruções # leitura das variáveis da memória
la x10,a # x10 aponta para a
if (a>b) la x11,b # x11 aponta para b
c=b*b; la x12,c # x12 aponta para c
else
c=a/b; flw f0,0(x10) # f0 = a
flw f1,0(x11) # f1 = b
fmul.s f2,f1,f1 # f2 = b*b
flt.s x13,f1,f0 # 1 if b<a
bne x13,x0,endif
fdiv.s f2,f0,f1
endif: fsw f2,0(x12)

106
RISC-V Instruction Set Architecture (ISA)
Mapear estruturas de código C

Código C fonte:
while (cond)
{ Na prática o código diz:
1) Sai se a condição for
iterate; falsa
2) Sempre que chegamos
} ao final do loop,
devemos voltar a testar
a condição
teste:
if (!cond) branch (goto) end_while;

iterate;
branch (goto) teste;

end_while:
107 107
RISC-V Instruction Set Architecture (ISA)
Mapear estruturas de código C

Código C fonte:
while (cond)
{ Na prática o código diz:
1) Sai se a condição for
iterate; falsa Alternativa
2) Sempre que chegamos
} ao final do loop,
devemos voltar a testar
a condição
teste:
if (!cond) branch (goto) end_while;
next:
iterate;
if (cond) branch (goto) next;

end_while:
108 108
.data
v: .word 1,5,7,3,4,8,10,3,5,7 Memória
m: .word 3
Exemplo de …
controlo do fluxo m
v+36
3
7
de instruções Declaração equivalente em C: v+32 5
v+28 3
Procurar a primeira int v[]={1,5,7,3,4,8,10,3,5,7}; v+24 10
int m=3 v+20 8
ocorrência de um número
v+16 4
m numa lista v[0],v[1],…:
v+12 3
v+ 8 7
i=0; v+ 4 5
while ( v[i]!= m ) v 1
i += 1; …

Nota: para simplificar a


representação, cada
quadrado representa 4B,
i.e., quatro posições de
memória

109
.data
v: .word 1,5,7,3,4,8,10,3,5,7 Memória
m: .word 3
Exemplo de …
controlo do fluxo .text
m
v+36
3
7
de instruções # leitura das variáveis da memória v+32 5
la x10,v v+28 3
Procurar a primeira la x11,m v+24 10
v+20 8
ocorrência de um número lw x11,0(x11) # x11 = m v+16 4
m numa lista v[0],v[1],…:
# Inicialização do loop v+12 3
or x12,x0,x0 # i=0 (x12) v+ 8 7
i=0; v+ 4 5
while ( v[i]!= m ) v 1
i += 1; …

Nota: para simplificar a


representação, cada
quadrado representa 4B,
i.e., quatro posições de
memória

110
.data
v: .word 1,5,7,3,4,8,10,3,5,7 Memória
m: .word 3
Exemplo de …
controlo do fluxo .text
m
v+36
3
7
de instruções # leitura das variáveis da memória v+32 5
la x10,v v+28 3
Procurar a primeira la x11,m v+24 10
v+20 8
ocorrência de um número lw x11,0(x11) # x11 = m v+16 4
m numa lista v[0],v[1],…:
# Inicialização do loop v+12 3
or x12,x0,x0 # i=0 v+ 8 7
i=0; v+ 4 5
while ( v[i]!= m ) # Teste de condição
v 1
i += 1; lw x13,0(x10) # load v[i] …
beq x13,x11,end
Sai se Nota: para simplificar a
...
forem representação, cada
end: iguais quadrado representa 4B,
i.e., quatro posições de
memória

111
.data
v: .word 1,5,7,3,4,8,10,3,5,7
m: .word 3 Memória

Exemplo de .text

controlo do fluxo # leitura das variáveis da memória
m
v+36
3
7
de instruções la x10,v v+32 5
la x11,m v+28 3
Procurar a primeira v+24 10
lw x11,0(x11) # x11 = m
v+20 8
ocorrência de um número # Inicialização do loop v+16 4
m numa lista v[0],v[1],…:
or x12,x0,x0 # i=0 v+12 3
# Teste de condição v+ 8 7
i=0; C:
Linguagem v+ 4 5
whileao tipo,
Indexação ( v[i]!= m )
i.e., v+i aponta while: sll x15,x12,2 # i*4
v 1
para o elemento
i += i 1; add x15,x10,x15 # v+i*4 …
Volta
lw x13,0(x15) # load v[i] ao
Assembly:
Indexação ao byte, para obtermos o beq x13,x11,end loop Nota: para simplificar a
representação, cada
endereço (ponteiro) do elemento # Loop quadrado representa 4B,
v+i, teremos de fazer i.e., quatro posições de
addi x12,x12,1
v + i*sizeof(elemento) memória
Neste caso os elementos são de 4B j while
112 end:
.data
v: .word 1,5,7,3,4,8,10,3,5,7 Memória
m: .word 3
Exemplo de …
controlo do fluxo .text m
v+36
3
7
de instruções # leitura das variáveis da memória
v+32 5
la x10,v # tmp = v v+28 3
Procurar a primeira la x11,m v+24 10
v+20 8
ocorrência de um número lw x11,0(x11)
v+16 4
m numa lista v[0],v[1],…: # Inicialização do loop
v+12 3
or x12,x0,x0 # i=0 v+ 8 7
i=0; v+ 4 5
# Teste de condição
while (
A alternativa v[i]!=
é alterar m )
o código v 1
i += 1; while: lw x13,0(x10) # load *tmp
para: …
i=0; beq x13,x11,end
tmp = v; Volta
# Loop ao Nota: para simplificar a
while ( *tmp != m ){
addi x12,x12,1 loop representação, cada
i += 1; quadrado representa 4B,
tmp++; addi x10,x10,4 # tmp++ i.e., quatro posições de
} memória
j while
113 end:
.data
v: .word 1,5,7,3,4,8,10,3,5,7
m: .word 3 Memória

Exemplo de .text …
controlo do fluxo # leitura das variáveis da memória m
v+36
3
7
de instruções la x10,v
v+32 5
la x11,m v+28 3
Procurar a primeira lw x11,0(x11) v+24 10
v+20 8
ocorrência de um número # Inicialização do loop
v+16 4
m numa lista v[0],v[1],…: or x12,x0,x0 # i=0
v+12 3
# Teste de condição v+ 8 7
i=0; v+ 4 5
lw x13,0(x10) # load v[i]
while ( v[i]!= m ) v 1
i += 1; beq x13,x11,end

# Loop
while: addi x12,x12,1 Nota: para simplificar a
addi x10,x10,4 Volta representação, cada
Podemos evitar ter de realizar 2 ao quadrado representa 4B,
branches seguidos, realizando lw x13,0(x10) loop i.e., quatro posições de
uma nova comparação no final do bne x13,x11,while memória
loop.
114 end:
RISC-V Instruction Set Architecture (ISA)
Mapear estruturas de código C

Código C fonte:
for (init ; cond ; increment){ Na prática o código diz:
1) antes do ciclo while,
iterate; realiza as inicializações
2) Sai se a condição for
} falsa
3) Sempre que chegamos
ao final do loop,
devemos executar a
operação de
incremento e voltar a
testar a condição

115 115
# ler o valor de m
la x13,m
lw x13,0(x13) # m (X13)

Exemplo de # for (K0=1,K1=1,i=2,…

controlo do fluxo ori x10,x0,1 # X10=K0=1


ori x11,x0,1 # X11=K1=1
de instruções ori x12,x0,2 # X12=i=2
# teste da condição
Calculo do número de
Fibonacci m (m>=2) forloop: bge x12,x13,fim # if (i>=m) end loop
# corpo do ciclo for
add x14,x10,x11
for (K0=1, K1=1, i=2; i<m; i++){ or x10,x11,x0
tmp=K1+K0;
or x11,x14,x0
K0=K1;
K1=tmp; # operação de incremento do ciclo
} addi x12,x12,1
j forloop
fim: …

116
# ler o valor de m
la x13,m
lw x13,0(x13) # m (X13)

Exemplo de # for (K0=1,K1=1,i=2,…

controlo do fluxo Inicializações do ciclo


ori x10,x0,1 # X10=K0=1
ori x11,x0,1 # X11=K1=1
de instruções ori x12,x0,2 # X12=i=2
# teste da condição
Calculo do número de
Fibonacci m (m>=2) forloop: bge x12,x13,fim # if (i>=m) end loop
# corpo do ciclo for
Inicializações do ciclo add x14,x10,x11
for (K0=1, K1=1, i=2; i<m; i++){ or x10,x11,x0
tmp=K1+K0;
or x11,x14,x0
K0=K1;
K1=tmp; # operação de incremento do ciclo
} addi x12,x12,1
j forloop
fim: …

117
# ler o valor de m
la x13,m
lw x13,0(x13) # m (X13)

Exemplo de # for (K0=1,K1=1,i=2,…

controlo do fluxo ori x10,x0,1 # X10=K0=1


ori x11,x0,1 # X11=K1=1
de instruções ori x12,x0,2 # X12=i=2
Teste de condição
(sai se a condição for falsa) # teste da condição
Calculo do número de
Fibonacci m (m>=2) forloop: bge x12,x13,fim # if (i>=m) end loop
# corpo do ciclo for
Teste de condição add x14,x10,x11
for (K0=1, K1=1, i=2; i<m; i++){ or x10,x11,x0
tmp=K1+K0;
or x11,x14,x0
K0=K1;
K1=tmp; # operação de incremento do ciclo
} addi x12,x12,1
j forloop
fim: …

118
# ler o valor de m
la x13,m
lw x13,0(x13) # m (X13)

Exemplo de # for (K0=1,K1=1,i=2,…

controlo do fluxo ori x10,x0,1 # X10=K0=1


ori x11,x0,1 # X11=K1=1
de instruções ori x12,x0,2 # X12=i=2
# teste da condição
Calculo do número de
Fibonacci m (m>=2) forloop: bge x12,x13,fim # if (i>=m) end loop
# corpo do ciclo for
add x14,x10,x11
for (K0=1, K1=1, i=2; i<m; i++){ Corpo do
ciclo for or x10,x11,x0
tmp=K1+K0;
Corpo do or x11,x14,x0
K0=K1; ciclo for
K1=tmp; # operação de incremento do ciclo
} addi x12,x12,1
j forloop
fim: …

119
# ler o valor de m
la x13,m
lw x13,0(x13) # m (X13)

Exemplo de # for (K0=1,K1=1,i=2,…

controlo do fluxo ori x10,x0,1 # X10=K0=1


ori x11,x0,1 # X11=K1=1
de instruções ori x12,x0,2 # X12=i=2
# teste da condição
Calculo do número de
Fibonacci m (m>=2) forloop: bge x12,x13,fim # if (i>=m) end loop
# corpo do ciclo for
Incremento add x14,x10,x11
for (K0=1, K1=1, i=2; i<m; i++){ or x10,x11,x0
tmp=K1+K0;
or x11,x14,x0
K0=K1;
K1=tmp; # operação de incremento do ciclo
} Incremento addi x12,x12,1
j forloop
fim: …

120
# ler o valor de m
la x13,m
lw x13,0(x13) # m (X13)

Exemplo de # for (K0=1,K1=1,i=2,…

controlo do fluxo ori x10,x0,1 # X10=K0=1


ori x11,x0,1 # X11=K1=1
de instruções ori x12,x0,2 # X12=i=2
# teste da condição
Calculo do número de
Fibonacci m (m>=2) forloop: bge x12,x13,fim # if (i>=m) end loop
# corpo do ciclo for
add x14,x10,x11
for (K0=1, K1=1, i=2; i<m; i++){ or x10,x11,x0
tmp=K1+K0;
or x11,x14,x0
K0=K1;
K1=tmp; # operação de incremento do ciclo
} addi x12,x12,1
Volta ao teste de condição j forloop
fim: …

121
# ler o valor de m
la x13,m
lw x13,0(x13) # m (X13)

Exemplo de # for (K0=1,K1=1,i=2,…

controlo do fluxo ori x10,x0,1 # X10=K0=1


ori x11,x0,1 # X11=K1=1
de instruções ori x12,x0,2 # X12=i=2
# teste da condição
Calculo do número de
Fibonacci m (m>=2) bge x12,x13,fim # if (i>=m) end loop
# corpo do ciclo for
Ciclo com
forloop: add x13,x10,x11 Menos
for (K0=1, K1=1, i=2; i<m; i++){
or x10,x11,x0 instruções
tmp=K1+K0;
K0=K1; or x11,x13,x0
K1=tmp; # operação de incremento do ciclo
Simplificação:
} Se a condição for verdadeira, addi x12,x12,1
voltamos ao loop blt x12,x13,forloop # if (i<m) forloop
fim: …

122
Instruction Set Architecture (ISA)
Chamadas a funções & introdução à pilha
Invocação e retorno da função

▪ A invocação de uma função é realizada pela instrução jal A utilização convencional do


registo x1 é para ser usado como
Código C: xpto() Código ASM: jal x1,xpto return address (ra), pelo que se
ou pode usar a sigla ra
Código ASM: jal ra,xpto
ou
Código ASM: call xpto (pseudo-instrução)

jal xa,offset xa  PC + 4 # guarda no ra o endereço da instrução seguinte…


PC  PC + offset # salta para offset
Invocação e retorno da função

▪ O retorno da função é realizada pela instrução jalr

Código C: return Código ASM: jalr x0,x1,0


ou
Código ASM: jalr x0,ra,0
ou
Código ASM: ret (pseudo-instrução)

jalr xa,xb,offset xa  PC + 4 # guarda no xa o endereço da instrução seguinte…


# neste caso colocamos x0, porque não
# precisamos do valor
PC  xb + offset # salta para xb + offset, neste caso, ra+0
Passagem de parametros

int avg(int a, int b){


return (a + b) / 2;
}

int main (){


int v0[]={1,2,3};
int v1[]={4,5,6};
int res[3];

res[0] = avg(v0[0],v1[0]);
res[1] = avg(v0[1],v1[1]);
res[2] = avg(v0[2],v1[2]);
}
126
Passagem de parametros

int avg(int a, int b){


return (a + b) / 2;
}

Passagem dos parâmetros


int main (){ pelo código Assembly que realiza:
Substituir
<preparar os parâmetros de entrada>
int v0[]={1,2,3};
<chamada à função> Por vezes o Código da função está num
<obter
into resultado>
v1[]={4,5,6}; ficheiro diferente da chamada à função.

int res[3]; Como saber como passer os parametros sem


conhecer o Código da função?

res[0] = avg(v0[0],v1[0]); SIMPLES:


• Convenciona-se qual a regra a usar e
res[1] = avg(v0[1],v1[1]);
segue-se sempre a mesmo regra!
res[2] = avg(v0[2],v1[2]);
}
127
Passagem de parametros

int avg(int a, int b){


return (a + b) / 2;
} Passagem dos parâmetros

Substituir pelo código Assembly que realiza: Regra por omissão: por registo
mv (){
int main x10,”v0[0]” # por registo • Registos X10-x17 para entrada
mv x11,”v1[0]” # por registo • Registos x10-x11 para retorno
int v0[]={1,2,3};
<chamada à função>
mvint”res[0]”,x10
v1[]={4,5,6}; # por registo Nota: tipicamente é o compilador que convenciona
quais os registos que devem ser usados como
int res[3]; entrada e saída. No caso do RISC-V a especificação
do ISA convenciona diretamente a utilização de
registos indicados em cima. Diferentes arquiteturas
(ARM, Intel, …) diferentes convenções.
res[0] = avg(v0[0],v1[0]);
res[1] = avg(v0[1],v1[1]); Alternativa: pela pilha (analisado mais tarde)
• Menos eficiente
res[2] = avg(v0[2],v1[2]); • Permite a passagem de mais parametros
}
128
int avg(int a, int b){
return (a + b) / 2;
}

Suporte para
int main (){
funções int v0[]={1,2,3};
int v1[]={4,5,6};
Exemplo:
int res[3];

Código C
res[0] = avg(v0[0],v1[0]);
res[1] = avg(v0[1],v1[1]);
res[2] = avg(v0[2],v1[2]);
}

129
.data
v0: .word 1,2,3
v1: .word 4,5,6
res: .word 0,0,0 # empty vector of 3 words

Suporte para .text

funções # leitura do endereço base (ponteiros)


la x5,v0
la x6,v1
Exemplo: la x7,res
# primeira chamada
lw X10,4(x5)
Correspondente em Coloca os argumentos lw x10,0(x5)
nos registos x10 e x11 lw X11,4(x6)
Assembly lw X11,0(x6)
jal avg
Chama a função jal ra,avg
sw X10,4(x7)
Nota: Em Assembly, Retira o resultado de x10 sw X10,0(x7)
geralmente usa-se a # outras chamadas
terminologia rotina (ou lw X10,8(x5)

subrotina) em vez de lw X11,8(x6)
# função
função. jal avg
avg: add x10,x10,x11
sw X10,8(x7)
srai X10,X10,1
jalr x0,ra,0
130
.data
v0: .word 1,2,3
v1: .word 4,5,6
res: .word 0,0,0 # empty vector of 3 words

Suporte para .text

funções # leitura do endereço base (ponteiros)


la x5,v0
la x6,v1
Exemplo: la x7,res
# primeira chamada
lw X10,4(x5)
Correspondente em lw x10,0(x5)
lw X11,4(x6)
Assembly lw X11,0(x6)
jal avg
jal ra,avg
sw X10,4(x7)
Nota: Em Assembly, sw X10,0(x7)
geralmente usa-se a # outras chamadas
terminologia rotina (ou lw X10,8(x5)
Opera sobre x10 e x11 e …
subrotina) em vez de lw X11,8(x6)
coloca o resultado em # função
função. jal avg
avg: x10 add x10,x10,x11
sw X10,8(x7)
srai X10,X10,1
Retorno da função jalr x0,ra,0
131 ra é o registo x1
.data
v0: .word 1,2,3
x0 0 v1: .word 4,5,6
res: .word 0,0,0
ra= x1
.text
Suporte para … …
# leitura do endereço
x5
funções PC la
la
x5,v0
x6,v1
x6
la x7,res
x7
Exemplo: # primeira chamada
… …
lw x10,0(x5)
Correspondente em x10 lw X11,0(x6)
Assembly x11 jal x1,avg
… … sw X10,0(x7)
Nota: Em Assembly, # outras chamadas
geralmente usa-se a x31

terminologia rotina (ou # função
subrotina) em vez de
avg: add x10,x10,x11
função. PC 0000 0000h
srai X10,X10,1
jalr x0,x1,0

132
Assumindo: .data
v0=400h
v0: .word 1,2,3
x0 0 v1: .word 4,5,6
res: .word 0,0,0
ra= x1
.text
Suporte para … …
Instrução # leitura do endereço
x5 400h executada
funções la x5,v0
x6 PC la x6,v1
la x7,res
x7
Exemplo: A pseudo-instrução la
# primeira chamada
… … traduz-se na sequencia
AUIPC+ADDI, i.e., em 2 lw x10,0(x5)
Correspondente em x10 instruções. Assim, temos lw X11,0(x6)
Assembly x11 de somar 8 ao PC jal x1,avg
… … sw X10,0(x7)
Nota: Em Assembly, # outras chamadas
geralmente usa-se a x31

terminologia rotina (ou # função
subrotina) em vez de
avg: add x10,x10,x11
função. PC 0000 0008h
srai X10,X10,1
jalr x0,x1,0
Após a execução da primeira instrução
133 ra = return address
Assumindo: .data
v0=400h
v0: .word 1,2,3
x0 0 v1: .word 4,5,6
res: .word 0,0,0
ra= x1
.text
Suporte para … …
# leitura do endereço
x5 400h
funções Instrução
executada
la
la
x5,v0
x6,v1
x6 40Ch
x7 PC la x7,res
Exemplo: # primeira chamada
… … A pseudo-instrução la
traduz-se na sequencia lw x10,0(x5)
Correspondente em x10 AUIPC+ADDI, i.e., em 2 lw X11,0(x6)
Assembly x11 instruções. Assim, temos jal x1,avg
de somar 8 ao PC sw X10,0(x7)
… …
Nota: Em Assembly, # outras chamadas
geralmente usa-se a x31

terminologia rotina (ou # função
subrotina) em vez de
avg: add x10,x10,x11
função. PC 0000 0010h
srai X10,X10,1
jalr x0,x1,0
Após a execução da segunda instrução
134 ra = return address
Assumindo: .data
v0=400h
v0: .word 1,2,3
x0 0 v1: .word 4,5,6
res: .word 0,0,0
ra= x1
.text
Suporte para … …
# leitura do endereço
x5 400h
funções Instrução
la
la
x5,v0
x6,v1
x6 40Ch
executada la x7,res
x7 418h
Exemplo: # primeira chamada
… …
PC lw x10,0(x5)
Correspondente em x10 lw X11,0(x6)
Assembly x11 A pseudo-instrução la jal x1,avg
traduz-se na sequencia sw X10,0(x7)
… … AUIPC+ADDI, i.e., em 2
Nota: Em Assembly, # outras chamadas
x31 instruções. Assim, temos
geralmente usa-se a de somar 8 ao PC …
terminologia rotina (ou # função
subrotina) em vez de
avg: add x10,x10,x11
função. PC 0000 0018h
srai X10,X10,1
jalr x0,x1,0
Após a execução da terceira instrução
135 ra = return address
Assumindo: .data
v0=400h
v0: .word 1,2,3
x0 0 v1: .word 4,5,6
res: .word 0,0,0
ra= x1
.text
Suporte para … …
# leitura do endereço
x5 400h
funções la
la
x5,v0
x6,v1
x6 40Ch
la x7,res
x7 418h
Exemplo: # primeira chamada
Instrução
… …
executada lw x10,0(x5)
Correspondente em x10 1 PC lw X11,0(x6)
Assembly x11 jal x1,avg
… … sw X10,0(x7)
Nota: Em Assembly, # outras chamadas
geralmente usa-se a x31

terminologia rotina (ou # função
subrotina) em vez de
avg: add x10,x10,x11
função. PC 0000 001Ch
srai X10,X10,1
jalr x0,x1,0

136 ra = return address


Assumindo: .data
v0=400h
v0: .word 1,2,3
x0 0 v1: .word 4,5,6
res: .word 0,0,0
ra= x1
.text
Suporte para … …
# leitura do endereço
x5 400h
funções la
la
x5,v0
x6,v1
x6 40Ch
la x7,res
x7 418h
Exemplo: # primeira chamada
… …
Instrução lw x10,0(x5)
Correspondente em x10 1 executada lw X11,0(x6)
Assembly x11 4 PC jal x1,avg
… … sw X10,0(x7)
Nota: Em Assembly, # outras chamadas
geralmente usa-se a x31

terminologia rotina (ou # função
subrotina) em vez de
avg: add x10,x10,x11
função. PC 0000 0020h
srai X10,X10,1
jalr x0,x1,0

137 ra = return address


Assumindo: .data
v0=400h
v0: .word 1,2,3
x0 0 v1: .word 4,5,6
res: .word 0,0,0
ra= x1 24h=&(sw)
.text
Suporte para … …
# leitura do endereço
x5 400h
funções la
la
x5,v0
x6,v1
x6 40Ch
la x7,res
x7 418h
Exemplo: # primeira chamada
… …
lw x10,0(x5)
Correspondente em x10 1
Instrução lw X11,0(x6)
Assembly x11 4 executada jal x1,avg
… … sw X10,0(x7)
Nota: Em Assembly, # outras chamadas
geralmente usa-se a x31

terminologia rotina (ou # função
subrotina) em vez de
PC avg: add x10,x10,x11
função. PC &(add)
srai X10,X10,1
jalr x0,x1,0

138 ra = return address


Assumindo: .data
v0=400h
v0: .word 1,2,3
x0 0 v1: .word 4,5,6
res: .word 0,0,0
ra= x1 24h
.text
Suporte para … …
# leitura do endereço
x5 400h
funções la
la
x5,v0
x6,v1
x6 40Ch
la x7,res
x7 418h
Exemplo: # primeira chamada
… …
lw x10,0(x5)
Correspondente em x10 5 lw X11,0(x6)
Assembly x11 4 jal x1,avg
… … sw X10,0(x7)
Nota: Em Assembly, # outras chamadas
geralmente usa-se a x31

terminologia rotina (ou # função
subrotina) em vez de Instrução
função. executada avg: add x10,x10,x11
PC &(sra)
PC srai X10,X10,1
jalr x0,x1,0

139 ra = return address


Assumindo: .data
v0=400h
v0: .word 1,2,3
x0 0 v1: .word 4,5,6
res: .word 0,0,0
ra= x1 24h
.text
Suporte para … …
# leitura do endereço
x5 400h
funções la
la
x5,v0
x6,v1
x6 40Ch
la x7,res
x7 418h
Exemplo: # primeira chamada
… …
lw x10,0(x5)
Correspondente em x10 2 lw X11,0(x6)
Assembly x11 4 jal x1,avg
… … sw X10,0(x7)
Nota: Em Assembly, # outras chamadas
geralmente usa-se a x31

terminologia rotina (ou # função
subrotina) em vez de
função. PC &(jalr) Instruçãoavg: add x10,x10,x11
executada srai X10,X10,1
PC jalr x0,x1,0

140 ra = return address


Assumindo: .data
v0=400h
v0: .word 1,2,3
x0 0 v1: .word 4,5,6
res: .word 0,0,0
ra= x1 24h
.text
Suporte para … …
# leitura do endereço
x5 400h
funções la
la
x5,v0
x6,v1
x6 40Ch
la x7,res
x7 418h
Exemplo: # primeira chamada
… …
lw x10,0(x5)
Correspondente em x10 2 lw X11,0(x6)
Assembly x11 4 jal x1,avg
… … PC sw X10,0(x7)
Nota: Em Assembly, # outras chamadas
geralmente usa-se a x31

terminologia rotina (ou # função
subrotina) em vez de
avg: add x10,x10,x11
função. PC 24h
Instrução srai X10,X10,1
executada jalr x0,x1,0

141 ra = return address


Passagem de parametros
Passagem por registo (método por omissão)

▪ Para realizar a chamada a uma função não é preciso conhecer os detalhes da função,
basta saber a convenção e o cabeçalho da função:

<int, double> = my_function( char, float, float, double, int, int )

142
Passagem de parametros
Passagem por registo (método por omissão)

▪ Para realizar a chamada a uma função não é preciso conhecer os detalhes da função,
basta saber a convenção e o cabeçalho da função:

<int, double> = my_function( char, float, float, double, int, int )


x10 f10 x10 f10 f11 f12 x11 x12

Da esquerda para a direita Da esquerda para a direita inicializar os


admitir que os resultados argumentos de entrada com os registos x10-x17
são colocados nos registos (inteiros) e f10-f11 (floating point)
x10-x11 (inteiros) e f10-f11
(floating point)

143
Considere a existência de uma função

Passagem de float my_fun (float x, float y, int n);

parametros por Que devolve

registo y=abs 𝑥/𝑦 𝑛

Realize o código que determina:

res = my_fun ( x , y , k )
Com
f1 = 3.5, f2=37.21, k=2

144
Considere a existência de uma função

Passagem de float my_fun (float x, float y, int n);

parametros por Que devolve

registo y=abs 𝑥/𝑦 𝑛

Realize o código que determina:

res = my_fun ( x , y , k )

Com
f1 = 3.5, f2=37.21, k=2

f10 f10 f11 x10

145
.data
x: .word 0x40600000 # 3.5 em floating point (float)
y: .word 0x4214d70a # 37.21 em floating point (float)
Passagem de K: .word 2

parametros por res: .zero 4 # reserva de 4B para o resultado

registo .text
la x10,x
res = my_fun ( x , y, k ) flw f10,0(x10) # inicialização do 1º argumento
la x10,y
x = 3.5, y=37.21, k=2
flw f11,0(x10) # inicialização do 2º argumento
la x10,K
lw x10,0(x10) # inicialização do 3º argumento

# chamada à função; o resultado ficará em f10


jal ra,abs_sin_power_n

la x10,res
fsw f10,0(x10)

146
Impacto da invocação de uma função

▪ Para realizar a chamada a uma função não é preciso conhecer os detalhes da função,
basta saber a convenção e o cabeçalho da função:

<int, double> = my_function( char, float, float, double, int, int )


x10 f10 x10 f10 f11 f12 x11 x12

▪ Mas a execução da função, implica necessáriamente a manipulação de diversos


registos…
▫ Como saber o impacto de uma chamada a uma função para o restante código?

147
Impacto da invocação de uma função
Convenção do compilador

▪ Para evitar a salvaguarda de registos desnecessários, e simplificar o código,


o compilador pode fazer convenções específicas

Admite-se que após a chamada a uma função, admite-se que os


seguintes registos foram modificados e têm um valor
desconhecido:
- Registos temporários
- Return Address (RA)
- Registos para passagem de argumentos (mesmo que não
148 148 sejam usados na chamada em particular)
Passagem de parametros por registo
Limitações

▪ Como realizar a passagem de parametros complexos?

▫ Ponteiros é fácil… um ponteiro é um endereço, e portanto um inteiro.

▫ Mas e listas, estruturas, objetos, etc?

▪ E como realizar a passagem de parametros se forem precisos mais do que 8 registos


de inteiros ou mais de 8 FP?

149
Pilha

▪ PILHA (stack):
▫ Espaço reservado na memória para guardar dados temporários;
▫ O acesso a este espeço de memória é feito segundo uma política do
tipo “Last-in-first-out (LIFO)”, i.e., o último elemento a entrar é o
primeiro a sair.

150
Pilha

▪ Existem duas operações conceptuais sobre a pilha:


▫ PUSH → colocar um valor na pilha
▫ POP → remover um valor da pilha

151
Pilha

▪ Existem duas operações conceptuais sobre a pilha:


▫ PUSH → colocar um valor na pilha
▫ POP → remover um valor da pilha

152
Pilha
Memória
FFFF FFFFh

▪ Geralmente a pilha é implementada no sentido


de endereços decrescentes
Stack
(Pilha)

▪ Existe um registo, Stack Pointer (SP), que


indica o limite inferior da pilha
▫ No RISC-V o SP (x2) indica o ultimo elemento ocupado
▫ Podemos usar a designação “sp” para nos referirmos ao registo “x2”
Data

Program
153 0000 0000h
Pilha no RISC-V
Operação de PUSH

FFFF FFFF FFFF FFFFh FFFF FFFF FFFF FFFFh


… …
SP → Ocupado Ocupado
SP → Valor de Xn

PUSH

Implementado como:

addi x2,x2,-4
sw xn,0(x2)

… …
0000 0000 0000 0000h 0000 0000 0000 0000h
154
Pilha no RISC-V
Operação de PUSH

FFFF FFFF FFFF FFFFh FFFF FFFF FFFF FFFFh


… …
Ocupado Ocupado
SP → Valor de Xn Valor de Xn
Valor de Xa
Valor de Xb
Múltiplos SP → Valor de Xc
PUSHs

Implementado como:

addi x2,x2,-12
sw xa,8(x2)
sw xb,4(x2)
sw xc,0(x2)
… …
0000 0000 0000 0000h 0000 0000 0000 0000h
155
Pilha no RISC-V
Operação de POP

FFFF FFFF FFFF FFFFh FFFF FFFF FFFF FFFFh


… …
Ocupado Ocupado
Valor de Xn Valor de Xn
Valor de Xa Valor de Xa
Valor de Xb SP → Valor de Xb
SP → Valor de Xc
POP

Implementado como:

lw xd,0(x2)
addi x2,x2,4

… …
0000 0000 0000 0000h 0000 0000 0000 0000h
156
Pilha no RISC-V
Operação de POP

FFFF FFFF FFFF FFFFh FFFF FFFF FFFF FFFFh


… …
Ocupado Ocupado
Valor de Xn SP → Valor de Xn
Valor de Xa
SP → Valor de Xb
Múltiplos
POPs

Implementado como:

lw xe,0(x2)
lw xf,4(x2)
addi x2,x2,8

… …
0000 0000 0000 0000h 0000 0000 0000 0000h
157
Informação
Operações sobre a pilha

▪ Alguns ISAs definem operações específicas para manipular a pilha, ex:

Instrução de PUSH: Instrução de POP:


PUSH X POP Y
Realiza, de uma só vez: Realiza, de uma só vez:
SP  SP – 8 Y  M[SP]
M[SP]  X SP  SP + 8

▪ Também é comum outras configurações para a pilha (stack), como por


exemplo o SP apontar para a primeira posição vazia (e não a última ocupada)

158
Passagem de parametros
Passagem por registo (método por omissão)

▪ Para realizar a chamada a uma função não é preciso conhecer os detalhes da função,
basta saber a convenção e o cabeçalho da função:

<int*, mystruct> = my_function( object , float*, float, struct, list of values )


x10 pilha pilha x10 f10 pilha pilha

Da esquerda para a direita Da esquerda para a direita inicializar os


admitir que os resultados argumentos de entrada com os registos x10-x17
são colocados nos registos (inteiros), f10-f11 (floating point), ou pilha no caso
x10-x11 (inteiros), f10-f11 de estruturas complexas ou caso o número de
(floating point), ou na pilha parametros exceda o número de registos
em caso de estruturas
complexas

Nota: em linguagem C uma lista de valores é sempre passada por referencia (i.e., por ponteiro),
159 mas há linguagens que suportam a passagem por valor
Passagem de parametros
Passagem por registo (método por omissão)

▪ Para realizar a chamada a uma função não é preciso conhecer os detalhes da função,
basta saber a convenção e o cabeçalho da função:

<int*, mystruct> = my_function( object , float*, float, struct, list of values )


x10 pilha pilha x10 f10 pilha pilha

▪ Parametros de entrada na pilha


Inicio Durante a execução do jal
1. Alocar espaço
SP → na pilha
Object
2. Colocar os Struct
parametros de SP → List of values
entrada pela
ordem indicada
na declaração
da função
160
Passagem de parametros
Passagem por registo (método por omissão)

▪ Para realizar a chamada a uma função não é preciso conhecer os detalhes da função,
basta saber a convenção e o cabeçalho da função:

<int*, mystruct> = my_function( object , float*, float, struct, list of values )


x10 pilha pilha x10 f10 pilha pilha

▪ Resultados (parametros de saída)


Inicio Durante a execução do jal
1. Retirar os SP →
SP → mystruct valores da pilha

2. Remover a
alocação de
espaço na pilha

161
Considere a existência de uma função

Passagem de struct time timediff (struct time t1, struct time t2, int mode);

parametros por com:

registo
struct time {
int hour;
int minute;
int sec;
int milisecond;
}
Realize o código que determina:

res = my_fun ( t1 , t2 , 0 )
Com
t1 = {23,4,25,125} , t2 = {12,5,14,671}

162
Considere a existência de uma função

Passagem de struct time timediff (struct time t1, struct time t2, int mode);

parametros por com:

registo
struct time {
int hour;
int minute;
int sec;
res = my_fun ( t1 , t2 , 0 ) int milisecond;
}
Realize o código que determina:

res = my_fun ( t1 , t2 , 0 )
Com
pilha pilha pilha x10
t1 = {23,4,25,125} , t2 = {12,5,14,671}

163
.data
t1: .word 23,4,25,125 # struct t1
t2: .word 12,5,14,671 # struct t2
Passagem de res: .zero 16 # output

parametros por .text


registo (…)
add sp,sp,-32 # 2(structs)*4(ints)*4(B/int)

res = my_fun ( t1 , t2 , 0 )

t1
(16B)

t2
(16B)
Reserva de espaço na pilha de acordo com os requisitos…
SP →
As duas estruturas (t1 e t2) são passadas pela pilha!

164
.data
t1: .word 23,4,25,125 # struct t1
t2: .word 12,5,14,671 # struct t2
Passagem de res: .zero 16 # output

parametros por .text


registo (…)
add sp,sp,-32 # 2(structs)*4(ints)*4(B/int)

res = my_fun ( t1 , t2 , 0 )

struct time {
hour int hour;
t1 minute int minute;
(16B) second int sec;
milisecond int milisecond;
}
Mantemos a ordem da declaração!
t2
(16B)
SP →

165
.data
t1: .word 23,4,25,125 # struct t1
t2: .word 12,5,14,671 # struct t2
Passagem de res: .zero 16 # output

parametros por .text


registo (…)
add sp,sp,-32 # 2(structs)*4(ints)*4(B/int)
la x10,t1
res = my_fun ( t1 , t2 , 0 )
lw x11,0(x10)
sw x11,28(sp) # hour
lw x11,4(x10)
hour
sw x11,24(sp) # minute
t1 minute
lw x11,8(x10)
(16B) second sw x11,20(sp) # second
milisecond
lw x11,12(x10)
sw x11,16(sp) # milisecond
t2 (…)
(16B)
SP →

166
.data
t1: .word 23,4,25,125 # struct t1
t2: .word 12,5,14,671 # struct t2
Passagem de res: .zero 16 # output
(copy t1 to stack)
parametros por .text
la x10,t1
lw x11,0(x10)
registo (…)
sw x11,28(sp)
add sp,sp,-32 # 2(structs)*4(ints)*4(B/int)
lw x11,4(x10)
res = my_fun ( t1 , t2 , 0 )
(copy t1 to stack)
sw x11,24(sp)
la x10,t2
lw x11,8(x10)
lw x11,0(x10)
sw x11,20(sp)
sw x11,12(sp) # hour
lw x11,4(x10)
lw x11,12(x10)
t1 sw x11,8(sp) # minute sw x11,16(sp)
(16B)
lw x11,8(x10)
sw x11,4(sp) # second
hour lw x11,12(x10)
t2 minute sw x11,0(sp) # milisecond
(16B) second (…)
SP → milisecond

167
.data
t1: .word 23,4,25,125 # struct t1
t2: .word 12,5,14,671 # struct t2
Passagem de res: .zero 16 # output
(copy t1 to stack)
parametros
(copy t2por
to stack)
.text
la x10,t1
lw x11,0(x10)
registo lw x11,0(x10)
la x10,t2
(…)
sw x11,28(sp)
add sp,sp,-32 # 2(structs)*4(ints)*4(B/int)
sw x11,12(sp) lw x11,4(x10)
res = my_fun ( t1lw, t2 , 0x11,4(x10)
)
(copy t1 to stack)
sw x11,24(sp)
(copy t2 to stack)
sw x11,8(sp) lw x11,8(x10)
mv x10,x0
lw x11,8(x10) sw x11,20(sp)
jal my_fun lw x11,12(x10)
sw x11,4(sp)
(…)
lw x11,12(x10) sw x11,16(sp)
sw x11,0(sp)
Estado da stack após
a chamada à função

hour
res minute
(16B) second
SP → milisecond

168
.data
t1: .word 23,4,25,125 # struct t1
t2: .word 12,5,14,671 # struct t2
Passagem de res: .zero 16 # output
(copy t1 to stack)
parametros
(copy t2por
to stack)
.text
la x10,t1
lw x11,0(x10)
registo lw x11,0(x10)
la x10,t2
(…)
sw x11,28(sp)
add sp,sp,-32 # 2(structs)*4(ints)*4(B/int)
sw x11,12(sp) lw x11,4(x10)
res = my_fun ( t1lw, t2 , 0x11,4(x10)
)
(copy t1 to stack)
sw x11,24(sp)
(copy t2 to stack)
sw x11,8(sp) lw x11,8(x10)
mv x10,x0
lw x11,8(x10) sw x11,20(sp)
jal my_fun lw x11,12(x10)
sw x11,4(sp)
lw x11,12(x10) la x10,res sw x11,16(sp)
sw x11,0(sp) lw x11,12(sp)
sw x11,0(x10) Estado da stack após
lw x11,8(sp) a chamada à função
sw x11,4(x10)
lw x11,4(sp) hour
sw x11,8(x10) res minute
lw x11,0(sp) (16B) second
sw x11,12(x10) SP → milisecond
addi sp,sp,16
169 (…)
Passagem de parametros pela pilha

▪ Em algumas arquiteturas a passagem de parametros é sempre realizada pela pilha


▫ Típico em arquiteturas com poucos registos de uso geral, ex: Intel x86

▪ Do ponto de vista da UC de arquitetura de computadores:

▫ Por omissão respeitamos a convenção do RISC-V


➔ passagem de parametros por registo, excepto estruturas complexas ou caso o número de parametros
INT / FP exceda os 8 registos disponíveis (x10-x17 / f10-f17)

▫ Pode ser pedido para realizar a passagem de parametros exclusivamente pela


pilha
➔ Ignoramos a convenção de compilador do RISC-V e passamos todos os parametros (entrada e saída)
pela pilha, i.e., não usamos nenhum registo para passagem de parametros.

170
Impacto da invocação de uma função
Convenção do compilador (revisão)!

▪ Para evitar a salvaguarda de registos desnecessários, e simplificar o código,


o compilador pode fazer convenções específicas

Admite-se que após a chamada a uma função, admite-se que os


seguintes registos foram modificados e têm um valor
desconhecido:
- Registos temporários
- Return Address (RA)
- Registos para passagem de argumentos (mesmo que não
171 171 sejam usados na chamada em particular)
Salvaguarda de registos

▪ Durante a chamada a uma função admite-se que todas as variáveis alocadas em


registos temporários são perdidos, ex:

lw x28,0(x10) # x28 guarda uma variável aux

(…)
#chamada a uma função
li x10,3
li x11,5
jal ra, max # os valores guardados em todos os registos
# temporários assumem-se perdidos! Não há garantia que os
# os valores ainda estejam nos registos 

172
Salvaguarda de registos

▪ Durante a chamada a uma função admite-se que todas as variáveis alocadas em


registos temporários são perdidos, ex:
Pode-se colocar a variável num registo do
lw x18,0(x10)
tipo saved...
# x28 guarda uma variável aux

(…) Mas estes podem estar todos ocupados!


#chamada a uma função
li x10,3
li x11,5
jal ra, max # os valores guardados em todos os registos
# temporários assumem-se perdidos! Não há garantia que os
# os valores ainda estejam nos registos 

173
Salvaguarda de registos

▪ Solução: salvaguardar os valores necessários na pilha

lw x28,0(x10) # x28 guarda uma variável aux

(…)
#salvaguarda de contexto
addi sp,sp,-8 # assumindo RV64 – registos de 64 bits
sd x28,0(sp)

#chamada à função
li x10,3
li x11,5
jal ra, max

#reposição de contexto
ld x28,0(sp)
addi sp,sp,8 # assumindo RV64 – registos de 64 bits
174
Instruction Set Architecture (ISA)
Escrita do código de uma função
Estrutura de uma função em Assembly

Estrutura do código da função:

1. Salvaguarda de contexto na pilha


2. Leitura dos operandos (argumentos) da pilha, quando necessário
3. Declaração de variáveis na pilha (variáveis locais), quando necessário
4. Corpo da função
5. Escrita do resultado na pilha, quando necessário
6. Reposição do contexto da pilha
return_type function_name(argumentos){
7. Retorno da função declaração de variáveis locais

Corpo da função

return x
}
176
Estrutura de uma função em Assembly

Estrutura do código da função:

1. Salvaguarda de contexto na pilha


2. Leitura dos operandos (argumentos) da pilha, quando necessário
3. Declaração de variáveis na pilha (variáveis locais), quando necessário
4. Corpo da função
5. Escrita do resultado na pilha, quando necessário
6. Reposição do contexto da pilha
Convenção do compilador (RISC-V) – método por omissão
7. Retorno da função
Na salvaguarda de contexto devemos guardar na pilha todos os
registos usados na função, mas apenas se forem do tipo saved

Passagem de parametros pela pilha


Na salvaguarda de contexto devemos guardar na pilha todos os
registos usados, independentemente do tipo dos mesmos

177
Estrutura de uma função em Assembly

Colocar na pilha (PUSH) os registos alterados pela função.


Estrutura do código da função: …
Ex: salvaguarda dos registos x20,x21,x22 ArgX
addi sp,sp,-24 ; 3 x 8B SP (before)→ ArgY
1. Salvaguarda de contexto sw x20,16(sp) X20 8B
2. Leitura dos operandos (argumentos) X21 8B
sw da pilhax21,8(sp)
SP (after)→ X22 8B
3. Declaração de variáveis na pilha (variáveis
sw locais), se necessário
x22,0(sp) …
4. Corpo da função
5. Escrita do resultado na pilha return_type function_name(argumentos){
6. Reposição de contexto Retirar da pilha (POP) os registos
declaração dealterados pela função
variáveis locais
7. Retorno da função Ex. reposição do contexto dos registos x20,x21,x22
lw Corpo da função
x22,0(sp)
lw x21,8(sp)
return x
lw } x20,16(sp)
addi sp,sp,24
178 Nota: Considerando uma implementação RV32. Com RV64 os registos são de 64 bits, pelo que vão ocupar 8B em memória.
Estrutura de uma função em Assembly

Estrutura do código da função:

1. Salvaguarda de contexto
2. Leitura dos operandos (argumentos) da pilha Retirar da pilha os argumentos da função
3. Declaração de variáveis na pilha (variáveis locais),Ex:
se necessário
colocar X e Y em registos
4. Corpo da função fld f0,24(sp) ; Y

5. Escrita do resultado na pilha lw X21,32(sp) ; X

6. Reposição de contexto …
Obs: Assumindo que X é do tipo int X 4B
7. Retorno da função Y 8B
e que Y é do tipo double, e que {X,Y}
X20 8B
fazem parte de uma estrutura, e portanto X21 8B
foram passados pela pilha SP→ X22 8B

179
Estrutura de uma função em Assembly

Estrutura do código da função:

1. Salvaguarda de contexto na pilha


2. Pornecessário
Leitura dos operandos (argumentos) da pilha, quando omissão as variáveis locais são declaradas diretamente em
registos, nunca chegando a ir a memória.
3. Declaração de variáveis na pilha (variáveis locais), quando necessário
Contudo, por falta de registos, ou caso o tipo da variável não seja
4. Corpo da função compatível com registos (ex: estruturas complexas, vetores, etc),
as variáveis locais são alocadas em stack!
5. Escrita do resultado na pilha, quando necessário
6. Reposição do contexto da pilha Nota: variáveis alocadas dentro de uma função com a keyword
return_type function_name(argumentos){
static não são locais, mas globais.
7. Retorno da função declaração de variáveis locais

Corpo da função

return x
}
180
Estrutura de uma função em Assembly

X 4B
Estrutura do código da função: Y 8B
X20 8B
X21 8B
1. Salvaguarda de contexto SP (before)→ X22 8B
int
2. Leitura dos operandos (argumentos) da pilha int
3. Declaração de variáveis na pilha (variáveis locais), se necessário SP (after)→ double
4. Corpo da função
5. Escrita do resultado na pilha
fun(…){ Reserva de espaço na pilha para as variáveis locais à
6. Reposição de contexto função (ex: 2 x int, 1x double):
int a,b;
7. Retorno da função addi sp,sp,-16 ; 2x4B + 1x8B
float f;
(…)
}

181 Observação: o compilador só declara as variáveis na pilha, se não existirem registos suficientes para armazenar estas mesmas variáveis.
Estrutura de uma função em Assembly

X 4B
Estrutura do código da função: Y 8B
X20 8B
Até ao topo são 52B X21 8B
1. Salvaguarda de contexto X22 8B
int
2. Leitura dos operandos (argumentos) da pilha int
3. Declaração de variáveis na pilha (variáveis locais), se necessário SP→ double
4. Corpo da função
5. Escrita do resultado na pilha A escrita do resultado na pilha (quando necessário)
ocupa o espaço dos argumentos de entrada...
6. Reposição de contexto Relembra-se que após a chamada à função, na pilha fica apenas o resultado.
7. Retorno da função Exemplo, retornar a estrutura {long long,double} no
topo da pilha:
sw x22,44(sp) # 52B (até ao topo)-8B = 44B
sw f12,36(sp) # 44B (até à anterior)-8B = 36B

182
Estrutura de uma função em Assembly

SP (after)→ X 4B
Estrutura do código da função: Y 8B
X20 8B
X21 8B
1. Salvaguarda de contexto X22 8B
int
2. Leitura dos operandos (argumentos) da pilha int
3. Declaração de variáveis na pilha (variáveis locais), se necessário SP (before)→ double
4. Corpo da função
5. Escrita do resultado na pilha Ajustar ainda o valor
return_type final da pilha, após a reposição de contexto
function_name(argumentos){
6. Reposição de contexto lw declaração de variáveis locais
x22,16(sp)
7. Retorno da função lw x21,20(sp)
Corpo da função
lw x20,24(sp)
addi return x # 16B (vars. temp.) + 24B (regs. salvaguardados)
sp,sp,36
} # + 12B relativos aos argumentos de entrada
# - 16B relativos aos argumentos de saida
183
Código de uma
função
Realize o código da função

double maxvalue(int N, double *v);

Que determina o máximo do vetor v


com dimensão N:

184
maxvalue:

Passo 1: Código da função


ble x10,x0,maxvalue_end # exit is N<=0

Código de uma fld f10,0(x11) # f10 <– current max

função maxvalue_next: Variáveis de entrada


addi x11,x11,8
Realize o código da função addi x10,x10,-1
beq x10,x0,maxvalue_end # exit if no more elements

double maxvalue(int N, double *v);


fld f11,8(x11)
fmax.d f10,f10,f11 # f10 <– max(current,next)
Que determina o máximo do vetor v
com dimensão N: j maxvalue_next
maxvalue_end:

f10 x10 x11

185
maxvalue:

Passo 2: verificar se usamos algum registo não temporário


ble x10,x0,maxvalue_end # exit is N<=0

Código de uma fld f10,0(x11) # f10 <– current max

função maxvalue_next:
addi x11,x11,8
Realize o código da função addi x10,x10,-1
beq x10,x0,maxvalue_end # exit if no more elements

double maxvalue(int N, double *v);


fld f11,8(x11)
fmax.d f10,f10,f11 # f10 <– max(current,next)
Que determina o máximo do vetor v
com dimensão N: j maxvalue_next
maxvalue_end:

f10 x10 x11 Todos os registos são temporários,


não é preciso salvaguardar nada na pilha

186
Terminar
maxvalue:
ble x10,x0,maxvalue_end # exit is N<=0
fld f10,0(x11) # f10 <– current max

Código de uma maxvalue_next:

função
addi x11,x11,8
addi x10,x10,-1
beq x10,x0,maxvalue_end # exit if no more elements
Realize o código da função
fld f11,8(x11)

double maxvalue(int N, double *v); fmax.d f10,f10,f11 # f10 <– max(current,next)


j maxvalue_next
maxvalue_end:
Que determina o máximo do vetor v
com dimensão N: jalr x0,ra,0

187
Código de uma
função
Realize o código da função

mystruct stats(int N, float *v);

Que determina o minimo, máximo


e média do vetor v com dimensão
N. Devolve uma estrutura:

struct vector_stats{
float min;
float avg;
float max
}

188
Passo 1: Código da função
maxvalue:

li x12,0x7f800000 # +inf in SP FP
fmv.s.w f10,x12 # f10 (min) = +inf
fsgnjn.s f11,f10,f10 # f11 (max) = -f10 = -inf

Código de uma fmv.s.w f12,x0


fcvt.s.w f14,x10
# f12 (sum) = 0
# f14 (N)
função
ble x10,x0,maxvalue_end # exit is N<=0
maxvalue_next:
Realize o código da função fld f13,0(x11)
fmin.s f10,f10,f13
pilha x10 x11 fmax.s f11,f11,f13 Variáveis de entrada
mystruct stats(int N, float *v);
fadd.s f12,f12,f13
addi x11,x11,4
Que determina o minimo, máximo addi x10,x10,-1
e média do vetor v com dimensão bne x10,0,maxvalue_next # loop if there are more elements
N. Devolve uma estrutura: fdiv.d f12,f12,f14 # compute average
maxvalue_end:
struct vector_stats{
float min;
float avg;
float max
}

189
maxvalue:

li x12,0x7f800000 # -inf in SP FP
fmv.s.w f10,x12 # f10 (min) = +inf
fsgnjn.s f11,f10,f10 # f11 (max) = -f10 = -inf

Código de uma fmv.s.w f12,x0


fcvt.s.w f14,x10
# f12 (sum) = 0
# f14 (N)
função
ble x10,x0,maxvalue_end # exit is N<=0
maxvalue_next:
Realize o código da função fld f13,0(x11)
fmin.s f10,f10,f13
pilha x10 x11 fmax.s f11,f11,f13
mystruct stats(int N, float *v);
fadd.s f12,f12,f13
addi x11,x11,4
Que determina o minimo, máximo addi x10,x10,-1
e média do vetor v com dimensão bne x10,0,maxvalue_next # loop if there are more elements
N. Devolve uma estrutura: fdiv.d f12,f12,f14 # compute average
maxvalue_end:
struct vector_stats{
float min;
float avg;
float max Passo 2: Verificar se é necessário salvaguardar algum registo…
}
Todos os registos usados são temporários, tudo ok!

190
Passo 3: Colocar o resultado na pilha
maxvalue:

li x12,0x7f800000 # -inf in SP FP
fmv.s.w f10,x12 # f10 (min) = +inf
fsgnjn.s f11,f10,f10 # f11 (max) = -f10 = -inf

Código de uma fmv.s.w f12,x0


fcvt.s.w f14,x10
# f12 (sum) = 0
# f14 (N)
função
ble x10,x0,maxvalue_end # exit is N<=0
maxvalue_next:
Realize o código da função fld f13,0(x11)
fmin.s f10,f10,f13
pilha x10 x11 fmax.s f11,f11,f13
mystruct stats(int N, float *v);
fadd.s f12,f12,f13
addi x11,x11,4
Que determina o minimo, máximo addi x10,x10,-1
e média do vetor v com dimensão bne x10,0,maxvalue_next # loop if there are more elements
N. Devolve uma estrutura: fdiv.d f12,f12,f14 # compute average
maxvalue_end:
struct vector_stats{ PILHA addi sp,sp,-12 # reserva de 12B na pilha para o resultado
float min; fsw f10,8(sp)
float avg; fsw f12,4(sp)
float max min
} fsw f11,0(sp)
avg
SP → max

191
Passo 4: retornar da função
maxvalue:
li x12,0x7f800000 # -inf in SP FP
fmv.s.w f10,x12 # f10 (min) = +inf
fsgnjn.s f11,f10,f10 # f11 (max) = -f10 = -inf
fmv.s.w f12,x0 # f12 (sum) = 0

Código de uma fcvt.s.w f14,x10 # f14 (N)

função ble x10,x0,maxvalue_end # exit is N<=0


maxvalue_next:
fld f13,0(x11)
Realize o código da função fmin.s f10,f10,f13
fmax.s f11,f11,f13
pilha x10 x11 fadd.s f12,f12,f13
mystruct stats(int N, float *v);
addi x11,x11,4
addi x10,x10,-1
Que determina o minimo, máximo bne x10,0,maxvalue_next # loop if there are more elements
e média do vetor v com dimensão fdiv.d f12,f12,f14 # compute average
N. Devolve uma estrutura: maxvalue_end:
addi sp,sp,-12 # reserva de 12B na pilha para o resultado
struct vector_stats{ fsw f10,8(sp)
float min; fsw f12,4(sp)
float avg; fsw f11,0(sp)
float max
} ret

192
Código de uma
função
Realize o código da função

pilha x10 pilha Mesmo exemplo, mas agora o array de


mystruct stats(int N, list of floats); dados é passado pela pilha!

PILHA À ENTRADA DA FUNÇÃO


Que determina o minimo, máximo e
média do vetor v com dimensão N.
Devolve uma estrutura: Value N

struct vector_stats{ Value 3
float min;
float avg; Value 2
float max SP → Value 1
}

193
maxvalue:
Passo 1: recuperar o passo 1 para o caso em que o vetor
é passado
li por registo # +inf in SP FP
x12,0x7f800000
fmv.s.w f10,x12 # f10 (min) = +inf
fsgnjn.s f11,f10,f10 # f11 (max) = -f10 = -inf

Código de uma fmv.s.w f12,x0


fcvt.s.w f14,x10
# f12 (sum) = 0
# f14 (N)
função
ble x10,x0,maxvalue_end # exit is N<=0
maxvalue_next:
Realize o código da função fld f13,0(x11)
fmin.s f10,f10,f13
pilha x10 pilha fmax.s f11,f11,f13 X11 nunca foi definido!!!!
mystruct stats(int N, list of floats);
fadd.s f12,f12,f13
addi x11,x11,4 1. Começamos por escrever o
Que determina o minimo, máximo e addi x10,x10,-1 Código admitindo que foi…
média do vetor v com dimensão N. bne x10,0,maxvalue_next # loop if there are more elements
Devolve uma estrutura: fdiv.d f12,f12,f14 # compute average
maxvalue_end:
struct vector_stats{
float min;
float avg;
float max
}

194
PILHA À ENTRADA DA FUNÇÃO

maxvalue:
Value N
li x12,0x7f800000 # +inf in SP FP …
fmv.s.w f10,x12 # f10 (min) = +inf Value 3
fsgnjn.s f11,f10,f10 # f11 (max) = -f10 = -inf Value 2
Código de uma fmv.s.w f12,x0
fcvt.s.w f14,x10
# f12 (sum) = 0
# f14 (N)
SP → Value 1

função
ble x10,x0,maxvalue_end # exit is N<=0
maxvalue_next:
Realize o código da função fld f13,0(x11)
fmin.s f10,f10,f13
pilha x10 pilha fmax.s f11,f11,f13 X11 nunca foi definido!!!!
mystruct stats(int N, list of floats);
fadd.s f12,f12,f13
addi x11,x11,4 1. Começamos por escrever o
Que determina o minimo, máximo e addi x10,x10,-1 Código admitindo que foi
média do vetor v com dimensão N. bne x10,0,maxvalue_next # loop if there are more elements
Devolve uma estrutura: fdiv.d f12,f12,f14 # compute average
maxvalue_end:
struct vector_stats{
float min;
float avg;
float max
}
Passo 2: inicializar os registos cujo valor está na pilha
Neste caso, o endereço do inicio do vetor é a primeira
posição do vetor na pilha
195
Completo
maxvalue_end:
mv x11,sp
li x12,0x7f800000 # +inf in SP FP
fmv.s.w f10,x12 # f10 (min) = +inf
fsgnjn.s f11,f10,f10 # f11 (max) = -f10 = -inf

Código de uma fmv.s.w f12,x0


fcvt.s.w f14,x10
# f12 (sum) = 0
# f14 (N)
função
ble x10,x0,maxvalue_end # exit is N<=0
maxvalue_next:
Realize o código da função fld f13,0(x11)
fmin.s f10,f10,f13
pilha x10 x11 fmax.s f11,f11,f13
mystruct stats(int N, float *v);
fadd.s f12,f12,f13
addi x11,x11,4
Que determina o minimo, máximo addi x10,x10,-1
e média do vetor v com dimensão bne x10,0,maxvalue_next # loop if there are more elements
N. Devolve uma estrutura: fdiv.d f12,f12,f14 # compute average
maxvalue_end:
struct vector_stats{ slli x10,x10,2 # v tem de ser removido e ocupa N*4B (N floats)
float min; addi x10,x10,-12 # mas depois precisamos de 12B para o resultado
float avg;
float max add sp,sp,x10 # ajusta a pilha com base no calculo anterior
} fsw f10,8(sp)
fsw f12,4(sp)
fsw f11,0(sp)
jalr x0,ra,0
196
int power(int x, int pow){
if (pow==0) return 1;
else return power(x,pow-1)*x;
}
Código de uma
função
Realize o código da função

int power(int x, int pow);

Que determina de forma recursiva


y=x^pow

197
# int power(int x, int pow){
# if (pow==0) return 1;
# else return power(x,pow-1)*x;
# }
Código de uma power:

função bne x11,x0,power_else


addi x10,x0,1 Passo 1: tradução direta do código
jalr x0,ra,0
Realize o código da função

x10 x10 x11 power_else:


int power(int x, int pow);
addi x11,x11,-1
jal power
Que determina de forma recursiva
mul x10,x10, x???
y=x^pow
jalr x0,ra,0

198
Passo 2: Verificar registos perdidos
# int power(int x, int pow){ (temporários) em chamadas a funções
# if (pow==0) return 1;
# É
else return power(x,pow-1)*x; preciso salvaguardar na pilha x10 e x1
# }
Código de uma power:

função bne x11,x0,power_else


addi x10,x0,1
jalr x0,ra,0
Realize o código da função

x10 x10 x11 power_else:


int power(int x, int pow);
addi x11,x11,-1
jal power
Que determina de forma recursiva
mul x10,x10, x??? O valor de x (x10) é perdido na
y=x^pow
jalr x0,ra,0 chamada à função!

O valor de x1 (return address) foi


perdido ao fazer jal

199
Salvaguarda dos registos
# int power(int x, int pow){
# if (pow==0) return 1;
# else return power(x,pow-1)*x;
# }
Código de uma power:

função bne x11,x0,power_else


addi x10,x0,1
jalr x0,ra,0
Realize o código da função
Por omissão o Ripes usa RV32, i.e., registos de 4B.
x10 x10 x11 power_else: Este exemplo apenas funciona se mudarmos o processador para RV64
int power(int x, int pow);
addi sp,sp,-16 # reservar 16B (ra + x10, admitindo RV64) na pilha
sd ra,8(sp) # salvaguardar x1 (ra)
Que determina de forma recursiva
sd x10,0(sp) # salvaguardar x10 (x) Nota: para x10 (x) bastavam 4B,
y=x^pow
addi x11,x11,-1 já que a variável é do tipo int
jal power
ld x11,0(sp) # restaurar x e colocar em x11
ld ra,8(sp) # restaurar ra
addi sp,sp,16 # libertar os 16B da pilha
mul x10,x10,x11
jalr x0,ra,0
200
Passo 3: Verificar utilização de registos saved
# int power(int x, int Nenhum, nada a fazer
pow){
# if (pow==0) return 1;
# Passo
else return power(x,pow-1)*x; 4: terminar
# }
Código de uma power:

função bne x11,x0,power_else


addi x10,x0,1
jalr x0,ra,0
Realize o código da função

x10 x10 x11 power_else:


int power(int x, int pow);
addi sp,sp,-16 # reservar 16B (ra + x10, admitindo RV64) na pilha
sd ra,8(sp) # salvaguardar x1 (ra)
Que determina de forma recursiva
sd x10,0(sp) # salvaguardar x10 (x) Nota: para x10 (x) bastavam 4B,
y=x^pow
addi x11,x11,-1 já que a variável é do tipo int
jal power
ld x11,0(sp) # restaurar x e colocar em x11
ld ra,8(sp) # restaurar ra
addi sp,sp,16 # libertar os 16B da pilha
mul x10,x10,x11
jalr x0,ra,0
201
int power(int x, int pow){
if (pow==0) return 1;
else return power(x,pow-1)*x;
}
Código de uma
função
Realize o código da função PILHA À ENTRADA DA FUNÇÃO PILHA À SAIDA DA FUNÇÃO

stack stack x SP → result


int power(int x, int pow);
stack SP → pow

Que determina de forma recursiva


y=x^pow

IGNORE AS CONVENÇÕES DE
UTILIZAÇÃO DE REGISTOS E FAÇA
TODA A PASSAGEM DE
PARAMETROS PELA PILHA!

202
Passo 1: tradução direta do Código,
# int power(int x, int pow){ admitindo que os parametros são
# if (pow==0) return 1; passados por registo
# else return power(x,pow-1)*x;
# }
Código de uma
função
power:
bne x11,x0,power_else
Realize o código da função
addi x10,x0,1
jalr x0,ra,0
int power(int x, int pow);

power_else:
Que determina de forma recursiva
addi x11,x11,-1
y=x^pow
jal power Alterar passagem de parametros que
mul x10,x10,x11 agora é feita pela pilha
IGNORE AS CONVENÇÕES DE
UTILIZAÇÃO DE REGISTOS E jalr x0,ra,0
FAÇA TODA A PASSAGEM DE
PARAMETROS PELA PILHA!

203
Passo 2: passagem de parametros
# int power(int x, int pow){ pela pilha
# if (pow==0) return 1;
# else return power(x,pow-1)*x;
# }
Código de uma power:
PILHA À ENTRADA DA PRÓXIMA
função bne x11,x0,power_else INVOCAÇÃO DA FUNÇÃO
addi x10,x0,1
jalr x0,ra,0 x 4B = int
Realize o código da função
SP → pow-1 4B = int

power_else:
int power(int x, int pow);
addi x11,x11,-1
addi sp,sp,-8
Que determina de forma recursiva
sw x10,4(sp)
y=x^pow
sw x11,0(sp) PILHA À SAÍDA DA PRÓXIMA
INVOCAÇÃO DA FUNÇÃO
jal power
IGNORE AS CONVENÇÕES DE
UTILIZAÇÃO DE REGISTOS E lw x11,0(sp)
SP → power(x,pow-1) 4B = int
FAÇA TODA A PASSAGEM DE addi sp,sp,4
PARAMETROS PELA PILHA!
mul x10,x10,x11
jalr x0,ra,0

204
Passo 2b: esquecemo-nos de
# int power(int x, int pow){ salvaguardar o registo ra
# if (pow==0) return 1;
Nota: o ra poderia ser salvaguardado logo no inicio da rotina.
# else return power(x,pow-1)*x; Em rotinas que têm várias chamadas a funções é preferível.
# } Neste exemplo, como só há uma chamada, esta solução evita
a salvaguarda no caso em que pow=0.
power:
Código de uma bne x11,x0,power_else
PILHA À ENTRADA DA PRÓXIMA
função addi
jalr
x10,x0,1
x0,ra,0
INVOCAÇÃO DA FUNÇÃO

ra 8B = registo RV64
Realize o código da função power_else:
addi x11,x11,-1 x 4B = int

addi sp,sp,-16 SP → pow-1 4B = int


int power(int x, int pow);
sd ra,8(sp)
sw x10,4(sp)
Que determina de forma recursiva sw x11,0(sp)
y=x^pow jal power
PILHA À SAÍDA DA PRÓXIMA
lw x11,0(sp)
INVOCAÇÃO DA FUNÇÃO
ld ra,4(sp)
IGNORE AS CONVENÇÕES DE
UTILIZAÇÃO DE REGISTOS E addi sp,sp,12
FAÇA TODA A PASSAGEM DE mul x10,x10,x11 ra 8B = registo RV64

PARAMETROS PELA PILHA! jalr x0,ra,0 SP → power(x,pow-1) 4B = int

205
Passo 3:
# int power(int x, int pow){ a) ler os argumentos de entrada da
# if (pow==0) return 1;
pilha e colocar em (x10,x11);
# else return power(x,pow-1)*x;
# } b) colocar o resultado (x10) na pilha
power:

Código de uma lw
lw
x10,4(sp)
x11,0(sp)
PILHA À ENTRADA DA FUNÇÃO

função bne x11,x0,power_else


x 4B = int
addi x10,x0,1
SP → pow 4B = int
j power_return
Realize o código da função
power_else:
addi x11,x11,-1
int power(int x, int pow);
addi sp,sp,-16
sd ra,8(sp)
Que determina de forma recursiva sw x10,4(sp)
sw x11,0(sp)
y=x^pow PILHA À SAIDA DA FUNÇÃO
jal power
lw x11,0(sp)
IGNORE AS CONVENÇÕES DE ld ra,4(sp) SP → result 4B = int
UTILIZAÇÃO DE REGISTOS E addi sp,sp,12
FAÇA TODA A PASSAGEM DE mul x10,x10,x11
PARAMETROS PELA PILHA! power_return:
sw x10,4(sp) # colocamos em cima de x
addi sp,sp,4 # removemos o argumento ‘pow’
jalr x0,ra,0
206
Passo 4: salvaguardar na pilha todos os registos
# int power(int x, int pow){
#
modificados pela função!
if (pow==0) return 1;
# else return power(x,pow-1)*x;
# }
power: Ajustar a leitura dos argumentos e a escrita do
addi sp,sp,-24 resultado na pilha
Código de uma
sd x10,16(sp)
sd x11,8(sp)
sd x12,0(sp) ESTADO DA PILHA
função lw
lw
x10,28(sp)
x11,24(sp)
bne x11,x0,power_else x 4B = int
addi x10,x0,1 pow 4B = int
Realize o código da função j power_return
x10 8B = registo RV64
power_else: x11 8B = registo RV64
int power(int x, int pow); addi x11,x11,-1
addi sp,sp,-16 SP → x12 8B = registo RV64
sd ra,8(sp)
sw x10,4(sp)
Que determina de forma recursiva sw x11,0(sp)
y=x^pow jal power
lw x11,0(sp)
ld ra,4(sp)
addi sp,sp,12
IGNORE AS CONVENÇÕES DE mul x10,x10,x11
UTILIZAÇÃO DE REGISTOS E power_return:
FAÇA TODA A PASSAGEM DE sw x10,28(sp)
PARAMETROS PELA PILHA! ld x10,16(sp)
ld x11,8(sp)
ld x12,0(sp)
addi sp,sp,28
jalr x0,ra,0
207
Por omissão o Ripes usa RV32, i.e., registos de 4B.
# int power(int x, int pow){
# if (pow==0) return 1; Este exemplo apenas funciona se mudarmos o
# else return power(x,pow-1)*x; processador para RV64
# }
power:
addi sp,sp,-24 # salvaguarda de contexto (x10,x11,x12)
sd x10,16(sp)

Código de uma sd
sd
x11,8(sp)
x12,0(sp)

função lw
lw
x10,28(sp)
x11,24(sp)
# leitura dos argumentos de entrada da pilha

bne x11,x0,power_else # verifica se pow==0


addi x10,x0,1 # return 1 se pow==0
j power_return
Realize o código da função
power_else:
addi x11,x11,-1 # else: coloca os parametros de entrada na pilha (e ra)
int power(int x, int pow); addi sp,sp,-16
sd ra,8(sp)
sw x10,4(sp)
Que determina de forma recursiva sw x11,0(sp)
jal power # call: power(x,pow-1)
y=x^pow lw x11,0(sp)
ld ra,4(sp) # retira o resultado da pilha
addi sp,sp,12
IGNORE AS CONVENÇÕES DE mul x10,x10,x11 # calcula power(x,pow-1) * x
UTILIZAÇÃO DE REGISTOS E
power_return:
FAÇA TODA A PASSAGEM DE
sw x10,28(sp) # guarda o resultado na pilha
PARAMETROS PELA PILHA! ld x10,16(sp) # reposição de contexto (x10,x11,x12)
ld x11,8(sp)
ld x12,0(sp)
addi sp,sp,28 # ajusta a pilha
208 jalr x0,ra,0 # return
Frame pointer

▪ A alocação dinâmica de variáveis locais na pilha pode complicar o código


➢ Sempre que uma nova variável local é declarada, o offset para os argumentos de entrada variam
➢ Complica a escrita e compreensão do código

Antes da chamada Após o JAL e Após a declaração Após a declaração


à função salvaguarda de de variáveis locais de MAIS variáveis
(i.e., antes do JAL) contexto locais
? ? ? ?
Argumentos da Argumentos da Argumentos da Argumentos da
função função função função
SP
Registos Registos Registos
salvaguardados salvaguardados salvaguardados
SP Variáveis Variáveis
locais locais
SP Variáveis extra
SP

209
Frame pointer

▪ A alocação dinâmica de variáveis locais na pilha pode complicar o código


➢ Sempre que uma nova variável local é declarada, o offset para os argumentos de entrada variam
➢ Complica a escrita e compreensão do código

▪ O FP (Frame Pointer) fornece uma base estável para cada variável


➢ Aponta para a primeira word da stack relativa à função.

Antes da chamada Após o JAL e Após a declaração Após a declaração


à função salvaguarda de de variáveis locais de MAIS variáveis
(i.e., antes do JAL) contexto locais
? ? ? ?
Argumentos da FP Argumentos da FP Argumentos da FP Argumentos da
função função função função
SP
Registos Registos Registos
salvaguardados salvaguardados salvaguardados
SP Variáveis Variáveis
locais locais
SP Variáveis extra
SP

210
Frame pointer

▪ A alocação dinâmica de variáveis locais na pilha pode complicar o código


➢ Sempre que uma nova variável local é declarada, o offset para os argumentos de entrada variam
➢ Complica a escrita e compreensão do código

Função mãe Função filha


▪ O FP (Frame Pointer) fornece uma baseFP
estável paraArgumentos
cada variável
➢ Aponta para a primeira word da stack (função
relativamãe)
à função.
Registos

Antes da chamada Após o JALFP e Variáveis locais


Após a declaração Após a declaração
(função filha) Argumentos da
à função salvaguarda de funçãode variáveis locais de MAIS variáveis
(i.e., antes do JAL) contexto Registos
locais
salvaguardados
? ? Variáveis ? ?
Argumentos da FP Se a função
Argumentos da FP locais Argumentos da FP Argumentos da
função filha utiliza função
o Variáveis extra função função
SP FP, terá deRegistos
o SP Registos Registos

SP salvaguardar
salvaguardados salvaguardados salvaguardados

à entrada e Variáveis Variáveis


locais locais
repor o valor SP Variáveis extra
à saída SP

211
Frame pointer

▪ A alocação dinâmica de variáveis locais na pilha pode complicar o código


➢ Sempre que uma nova variável local é declarada, o offset para os argumentos de entrada variam
➢ Complica a escrita e compreensão do código

▪ O FP (Frame Pointer) fornece uma base estável para cada variável O FP voltou à posição
➢ Aponta para a primeira word da stack relativa à função. correspondente à
função mãe
▪ Não esquecer: Após o JAL e Antes de sair da
➢ À saída da função, o SP deve apontar para
o ultimo byte de retorno da função salvaguarda de FP função
contexto
? ?
FP Retorno
Argumentos da
função SP
Registos
salvaguardados
O SP
SP aponta para
o resultado
da função

212
Código usando o frame pointer (opcional!!!)
power:
addi sp,sp,-32 # salvaguarda de contexto (x10,x11,x12,fp)
sd fp,24(sp) ESTADO DA PILHA
sd x10,16(sp)
sd x11,8(sp)
sd x12,0(sp) FP → x

Código de uma
addi fp,sp,36
pow
lw x10,0(fp) # leitura dos argumentos de entrada da pilha
lw x11,-4(fp) fp
função bne
addi
x11,x0,power_else # verifica se pow==0
x10,x0,1 # return 1 se pow==0
x10
j power_return x11
SP → x12
Realize o código da função power_else:
addi x11,x11,-1 # else: coloca os parametros de entrada na pilha (e ra)
addi sp,sp,-16
int power(int x, int pow); sd ra,8(sp)
sw x10,4(sp)
sw x11,0(sp)
jal power # call: power(x,pow-1)
Que determina de forma recursiva lw x11,0(sp)
y=x^pow ld ra,4(sp) # retira o resultado da pilha
addi sp,sp,12
mul x10,x10,x11 # calcula power(x,pow-1) * x

IGNORE AS CONVENÇÕES DE power_return:


UTILIZAÇÃO DE REGISTOS E sw x10,0(fp) # guarda o resultado na pilha
FAÇA TODA A PASSAGEM DE ld fp,24(sp)
Nota 1: O Ripes não reconhece o nome “fp”, pelo que é necessário
# reposição de contexto (x10,x11,x12,fp)
PARAMETROS PELA PILHA! ld x10,16(sp) usar x8 (ou substituir por outro registo, como por exemplo tp=x4)
ld x11,8(sp)
ld x12,0(sp) Nota 2: a utilização do fp é opcional; o corpo docente não irá
addi sp,sp,36 # ajusta
obrigar a pilha
à sua utilização, mas os alunos podem usá-lo se acharem o
jalr x0,ra,0 # return
procedimento mais fácil
213
int max2(int a, int b){
if (a>b) return a;
else return b;

Código de uma }

função int maxN(int v[], int N){


int i, max;
Exemplo:
for (i=1, max=v[0]; i<N; i++) max=max2(max,v[i]);
return max;
Código C
}

IGNORE AS CONVENÇÕES DE
UTILIZAÇÃO DE REGISTOS E int main (){
FAÇA TODA A PASSAGEM DE
PARAMETROS PELA PILHA! int vector[]={2,10,-27,4,13,-7};
int maximum;

maximum = maxN( vector , 6 );


}
214
int max2(int a, int b){
if (a>b) return a;
else return b;

Código de uma }

função int maxN(int v[], int N){


int i, max;
Exemplo:
for (i=1, max=v[0]; i<N; i++) max=max2(max,v[i]);
return max;
Código C
}

IGNORE AS CONVENÇÕES DE
UTILIZAÇÃO DE REGISTOS E int main (){
FAÇA TODA A PASSAGEM DE
PARAMETROS PELA PILHA! int vector[]={2,10,-27,4,13,-7};
int maximum;

maximum = maxN( vector , 6 );


}
215
1. Fazer o corpo da função, assumindo que a entrada e saída da função é
feita por registo (ex: X10 e X11 para entrada de a e b, X12 para a saída)

max2: (salvaguarda de contexto)

Código de uma (leitura dos argumentos da pilha)


função
bge x11,x10,max_else # (b>=a)?else:if
mv x12,x10 # resultado if
Exemplo: jal x0,max_fim
max_else: or X12,X11,X0 # resultado else
int max2(int a, int b){
if (a>b) return a; max_fim: (escrita do resultado na pilha)
else return b;
} (reposição de contexto)

IGNORE AS CONVENÇÕES DE ret


UTILIZAÇÃO DE REGISTOS E
FAÇA TODA A PASSAGEM DE
PARAMETROS PELA PILHA!

216
2. Escrever o código da salvaguarda de contexto e reposição de contexto
(todos os registos alterados pela função)

max2: addi sp,sp,-12 # salvaguarda de x10,x11,x12


sw x10,8(sp) # assumindo RV32, i.e.,
Código de uma sw x11,4(sp) # registos de 4B

função sw x12,0(sp)

(leitura dos argumentos da pilha)


Exemplo: bge x11,x10,max_else # (b>=a)?else:if
mv x12,x10 # resultado if
int max2(int a, int b){
if (a>b) return a; jal x0,max_fim
else return b; max_else: mv X12,X11 # resultado else
} max_fim: (escrita do resultado na pilha)

lw x12,0(sp) # Reposição de X10,X11,X12


lw x11,4(sp)
lw x10,8(sp)
addi sp,sp, 12
ret

217 NESTE EXEMPLO ESTAMOS A USAR RV32, i.e., REGISTOS DE 4B


3. Ler e escrever os argumentos da pilha (assumindo que os operandos são
colocados na pilha pela ordem indicada na declaração)

max2: addi sp,sp,-12 # salvaguarda de X10,X11,X12


sw x10,8(sp)
Código de uma sw x11,4(sp)
sw x12,0(sp)
função
lw x10,16(sp) # leitura dos operandos
int max2(int a, int b){ lw x11,12(sp) # X10=a e X11=b
if (a>b) return a;
else return b; bge x11,x10,max_else # (b>=a)?else:if
} mv x12,x10 # resultado if
Estado
jal x0,max_fim
da pilha
max_else: mv X12,X11 # resultado else

a 4B
b 4B max_fim: sw X12,16(sp) # Escrita do resultado(X12)
X10 4B lw x12,0(sp) # Reposição de X10,X11,X12
X11 4B
lw x11,4(sp)
SP → X12 4B
lw x10,8(sp)
… addi sp,sp, 12 #  REVER NO FINAL!!!!
ret
218
3. Ler e escrever os argumentos da pilha (assumindo que os operandos são
colocados na pilha pela ordem indicada na declaração)

max2: addi sp,sp,-12 # salvaguarda de X10,X11,X12


sw x10,8(sp)
Código de uma sw x11,4(sp)
sw x12,0(sp)
função
lw x10,16(sp) # leitura dos operandos
int max2(int a, int b){ lw x11,12(sp) # X10=a e X11=b
if (a>b) return a;
else return b; bge x11,x10,max_else # (b>=a)?else:if
} mv x12,x10 # resultado if
Estado
jal x0,max_fim
da pilha
max_else: mv X12,X11 # resultado else

SP → Result 4B
4B max_fim: sw X12,16(sp) # Escrita do resultado(X12)
4B lw x12,0(sp) # Reposição de X10,X11,X12
4B
lw x11,4(sp)
4B
lw x10,8(sp)
… addi sp,sp, 16 # mantem na pilha apenas o
ret # resultado
219
int max2(int a, int b){
if (a>b) return a;
else return b;

Código de uma }

função int maxN(int v[], int N){


int i, max;
Exemplo:
for (i=1, max=v[0]; i<N; i++) max=max2(max,v[i]);
return max;
Código C
}

IGNORE AS CONVENÇÕES DE
UTILIZAÇÃO DE REGISTOS E int main (){
FAÇA TODA A PASSAGEM DE
PARAMETROS PELA PILHA! int vector[]={2,10,-27,4,13,-7};
int maximum;

maximum = maxN( vector , 6 );


}
220
maxN 2 - salvaguarda de contexto

Código de uma (reserva de espaço para as variáveis locais i e max)


função
3 - leitura dos argumentos da pilha
Como neste caso específico o número de
int maxN(int v[], int N){
registos é suficiente, não precisamos de
1 - corpo da função guardar as variáveis temporárias na pilha
int i, max=v[0];
e podemos simplesmente mante-las em
3 - escrita do resultado na pilha registos.
for (i=1; i<N; i++)
max=max2(max,v[i]);
2 - reposição de contexto
return max;
ret
}

IGNORE AS CONVENÇÕES DE
UTILIZAÇÃO DE REGISTOS E FAÇA
TODA A PASSAGEM DE
PARAMETROS PELA PILHA!

221
1. Fazer o corpo da função, assumindo a entrada e saída por registo, p. ex.,
X10 e X11 para entrada de v e N, X12 para a variável i e X13 para max (saída)
maxN: (salvaguarda de contexto)
(leitura dos argumentos da pilha)

lw x13,0(x10) # X13 (max) = v[0]


Código de uma ori x12,x0,1 # X12 (i) = 1

função maxN_loop: bge x12,x11, maxN_sai # if i>=N, sai do for

addi x10,x10,4 # X10 aponta para v[i]


int maxN(int v[], int N){ lw x14,0(x10) # X14 = v[i]
# chamada a max2
int i, max=v[0];
addi sp,sp,-8 # reserva 2 x 4B (int)
for (i=1; i<N; i++) sw x13,4(sp) # coloca max na pilha
max=max2(max,v[i]); sw x14,0(sp) # coloca v[i] na pilha
jal x1,max2
return max;
lw x13,0(sp) # retira o res. da pilha
} addi sp,sp,4 # ajusta a pilha
# iterador do ciclo (i++)
IGNORE AS CONVENÇÕES DE addi x12,x12,1
UTILIZAÇÃO DE REGISTOS E FAÇA j maxN_loop
TODA A PASSAGEM DE
maxN_sai: (escrita do resultado na pilha)
PARAMETROS PELA PILHA!
(reposição de contexto)
ret
222
1. Fazer o corpo da função, assumindo a entrada e saída por registo, p. ex.,
X10 e X11 para entrada de v e N, X12 para a variável i e X13 para max (saída)
maxN: (salvaguarda de contexto)
(leitura dos argumentos da pilha)

lw x13,0(x10) # X13 (max) = v[0]


Código de uma ori x12,x0,1 # X12 (i) = 1

função maxN_loop: bge x12,x11, maxN_sai # if i>=N, sai do for

addi x10,x10,4 # X10 aponta para v[i]


int maxN(int v[], int N){ lw x14,0(x10) # X14 = v[i]
# chamada a max2
int i, max=v[0];
addi sp,sp,-8 # reserva 2 x 4B (int)
for (i=1; i<N; i++) sw x13,4(sp) # coloca max na pilha
max=max2(max,v[i]); sw x14,0(sp) # coloca v[i] na pilha
PROBLEMA: A chamada à jal x1,max2
return max; função max2 altera o x1, o lw x13,0(sp) # retira o res. da pilha
que obriga a salvaguardar o addi sp,sp,4 # ajusta a pilha
}
conteúdo deste registo no # iterador do ciclo (i++)
inicio da função maxN
addi x12,x12,1
j maxN_loop
IGNORE AS CONVENÇÕES DE
maxN_sai: (escrita do resultado na pilha)
UTILIZAÇÃO DE REGISTOS E FAÇA
TODA A PASSAGEM DE (reposição de contexto)
PARAMETROS PELA PILHA! ret
223
2. Salvaguarda e reposição de contexto

maxN: addi sp,sp,-24 # espaço para 6 pal. de 4B


sw x1,20(sp) # salvaguarda de X0-X4 + LR
sw x10,16(sp)

Código de uma
sw x11,12(sp)
sw x12,8(sp) …
função sw x13,4(sp) V 4B
sw x14,0(sp) Estado da pilha N 4B
int maxN(int v[], int N){ rax1 4B
(leitura dos argumentos da pilha)
(corpo da função – ver slide anterior)
x10 4B
int i, max=v[0]; x11 4B
(escrita do resultado na pilha)
for (i=1; i<N; i++)
x12 4B
max=max2(max,v[i]); x13 4B
lw x14,0(sp)
SP → x14 4B
lw x13,4(sp)
return max; …
lw x12,8(sp)
} lw x11,12(sp)
lw x10,16(sp)
IGNORE AS CONVENÇÕES DE lw x1,20(sp) # salvaguarda de X0-X4 + LR
UTILIZAÇÃO DE REGISTOS E FAÇA
addi sp,sp,28 # 6 registos de 4B + 4B (argumento N)
TODA A PASSAGEM DE
PARAMETROS PELA PILHA!
ret
224 NESTE EXEMPLO ESTAMOS A USAR RV32, i.e., REGISTOS DE 4B
3. Leitura dos argumentos e escrita do resultado
X10 e X11 para entrada de v e N, X12 para a variável i e X13 para max (saída)

Código de uma maxN (salvaguarda de contexto – ver slides anteriores)



função lw
lw
x10,28(sp)
x11,24(sp)
V 4B
Estado da pilha N 4B
int maxN(int v[], int N){ rax1 4B
(corpo da função – ver slides anteriores)
x10 4B
int i, max=v[0]; x11 4B
sw x13,28(sp)
for (i=1; i<N; i++)
x12 4B
max=max2(max,v[i]); x13 4B
(reposição de contexto – ver slides anteriores) SP → x14 4B
return max; …
ret
}

IGNORE AS CONVENÇÕES DE
UTILIZAÇÃO DE REGISTOS E FAÇA
TODA A PASSAGEM DE
PARAMETROS PELA PILHA!

225 NESTE EXEMPLO ESTAMOS A USAR RV32, i.e., REGISTOS DE 4B


int max2(int a, int b){
if (a>b) return a;
else return b;

Código de uma }

função int maxN(int v[], int N){


int i, max;
Exemplo:
for (i=1, max=v[0]; i<N; i++) max=max2(max,v[i]);
return max;
Código C
}

IGNORE AS CONVENÇÕES DE
UTILIZAÇÃO DE REGISTOS E int main (){
FAÇA TODA A PASSAGEM DE
PARAMETROS PELA PILHA! int vector[]={2,10,-27,4,13,-7};
int maximum;

maximum = maxN( vector , 6 );


}
226
.data
N: .word 6
vector: .word 2,10,-27,4,13,-7
Maximum: .word 0

Código de uma .text


função la X10,vector # inicialização das variáveis
la x11,N
lw x11,0(x11)
Exemplo:
addi sp,sp,-8 # chamada a maxN
int main(…)
sw x10,4(sp)
sw X11,0(sp)
De forma equivalente, também se podia jal x1,maxN
usar a designação “ra” para nos referirmos
ao x1, ou usar a pseudo-instrução: lw x11,0(sp) # retirar o resultado da pilha
call symbol  jal x1,symbol
addi sp,sp,4

lw x10,Maximum # Escrever o res. na mem


sw x11,0(x11)

227
.data
N: .word 6
vector: .word 2,10,-27,4,13,-7
Maximum: .word 0
Código de uma
função .text
la X10,vector # inicialização das variáveis
la x11,N
Exemplo: lw x11,0(x11)
E onde está a inicialização do SP?
int main(…)
Geralmente faz parte da competência do addi sp,sp,-12 # chamada a maxN
Sistema Operativo (SO) inicializar o SP sw x10,4(sp)
(mais detalhe nas próximas aulas). sw X11,0(sp)
jal x1,maxN
Contudo, se não existir SO (ex: no caso de
um microcontrolador), então deve-se lw x11,0(sp) # retirar o resultado da pilha
começar o programa com a inicialização do addi sp,sp,4
SP, ex:
lw x10,Maximum # Escrever o res. na mem
li SP,0x8000 sw x11,0(x11)
228
;===================================================== ;==========================================================
.data
N: .word 6 maxN: addi sp,sp,-24 # espaço para 6 pal. de 4B
vector: .word 2,10,-27,4,13,-7 sw x1,20(sp) # salvaguarda de X0-X4 + LR
Maximum: .word 0 sw x10,16(sp)
sw x11,12(sp)
.text sw x12,8(sp)
la X10,vector # inic. das variáveis sw x13,4(sp)
la x11,N sw x14,0(sp)

Código de uma lw
addi
sw
x11,0(x11)
sp,sp,-8
x10,4(sp)
# chamada a maxN
lw
lw
X10,28(sp)
X11,24(sp)

função
lw x13,0(x10) # X13 (max) = v[0]
sw X11,0(sp)
jal x1,maxN ori x12,x0,1 # X12 (i) = 1
lw x11,0(sp) # retirar o res. Da pilha
addi sp,sp,4 maxN_loop: bge x12,x11, maxN_sai # if i>=N, sai do for
lw x10,Maximum # Escrever o res. na mem addi x10,x10,4 # X10 aponta para v[i]
sw x11,0(x11) lw x14,0(x10) # X14 = v[i]

end: b end
; chamada a max2
;===================================================== addi sp,sp,-8 # reserva 2 x 4B (int)
max2: addi sp,sp,-12 # salvaguarda de X10-X12 sw x13,4(sp) # coloca max na pilha
sw x10,8(sp) sw x14,0(sp) # coloca v[i] na pilha
sw x11,4(sp) jal x1,max2
sw x12,0(sp) lw x13,0(sp) # retira o res. da pilha
addi sp,sp,4 # ajusta a pilha
lw x10,16(sp) # leitura dos operandos
lw x11,12(sp) # X10=a e X11=b
; iterador do ciclo (i++)
bge x11,x10,max_else # (b>=a)?else:if addi x12,x12,#1
mv x12,x10 # resultado if j maxN_loop
jal x0,max_fim maxN_sai: sw x13,28(sp)
max_else: mv X12,X11 # resultado else lw x14,0(sp)
lw x13,4(sp)
max_fim: sw X12,16(sp) # Escrita do resultado(X12) lw x12,8(sp)
lw x11,12(sp)
lw x12,0(sp) # Reposição de X10,X11,X12 lw x10,16(sp)
lw x11,4(sp) lw x1,20(sp) # salvaguarda de X0-X4 + LR
lw x10,8(sp) addi sp,sp,28 # 6 registos de 4B + 4B
addi sp,sp, 16 # mantem na pilha apenas o ret
ret # resultado
;=====================================================
229 ;======================================================
▪ Considere a implementação da função fatorial de forma
recursiva

▪ Considere que a passagem de parâmetros é sempre realizada


Exercicio pela pilha e que a implementação da função requer a
salvaguarda de 2 registos de uso geral (x18 e x19) em cada
int factorial(int n){
if (n==0)
chamada.
return 1;
else
return fatorial(n-1)*n;
➢ Considerando a invocação factorial(5), determine o
} estado da pilha quando esta atinge o valor máximo.

➢ Determine o valor máximo do parametro de entrada n, se


a sua pilha tiver uma dimensão máxima de 1KB.

230
▪ Considere a implementação da função fatorial de forma
recursiva

▪ Considere que a passagem de parâmetros é sempre realizada


Exercicio pela pilha e que a implementação da função requer a
salvaguarda de 2 registos de uso geral (x18 e x19) em cada
int factorial(int n){
if (n==0)
chamada.
return 1;
else
return fatorial(n-1)*n;
➢ Considerando a invocação factorial(5), determine o
} estado da pilha quando esta atinge o valor máximo.

SP 5 Após a primeira invocação (entrada na função fatorial)

231
▪ Considere a implementação da função fatorial de forma
recursiva

▪ Considere que a passagem de parâmetros é sempre realizada


Exercicio pela pilha e que a implementação da função requer a
salvaguarda de 2 registos de uso geral (x18 e x19) em cada
int factorial(int n){
if (n==0)
chamada.
return 1;
else
return fatorial(n-1)*n;
➢ Considerando a invocação factorial(5), determine o
} estado da pilha quando esta atinge o valor máximo.

5 • A função fatorial guarda x18 e x19.


x18
x19 • Guarda ainda o ra!
ra • Chama factorial(4)
SP 4

232
▪ Considere a implementação da função fatorial de forma
recursiva

▪ Considere que a passagem de parâmetros é sempre realizada


Exercicio pela pilha e que a implementação da função requer a
salvaguarda de 2 registos de uso geral (x18 e x19) em cada
int factorial(int n){
if (n==0)
chamada.
return 1;
else
return fatorial(n-1)*n;
➢ Considerando a invocação factorial(5), determine o
} estado da pilha quando esta atinge o valor máximo.
5
x18
x19
ra
4 • A função fatorial guarda x18 e x19.
x18
x19 • Guarda ainda o ra!
ra • Chama factorial(3)
SP 3

233
▪ Considere a implementação da função fatorial de forma
recursiva
5
x18
x19
ra
▪ Considere que a passagem de parâmetros é sempre realizada
4
Exercicio pela pilha e que a implementação da função requer a
X18
x19
salvaguarda de 2 registos de uso geral (x18 e x19) em cada
ra
int factorial(int n){ 3
if (n==0)
chamada.
x18
x19
return 1; ra
else
return fatorial(n-1)*n;
➢ Considerando a invocação factorial(5), determine o
2
x18
} estado da pilha quando esta atinge o valor máximo.
x19
ra
1
x18
x19
ra • A função fatorial guarda x18 e x19.
0
X18
• Guarda ainda o ra!
X19 • Chama factorial(2)…(1)…(0)
SP ra • Admitindo que x18, x19 e ra são sempre
preservados, independentemente do caminho
234
seguido no if.
▪ Considere a implementação da função fatorial de forma
recursiva
5
x18
x19
ra
▪ Considere que a passagem de parâmetros é sempre realizada
4
Exercicio pela pilha e que a implementação da função requer a
x18
x19
salvaguarda de 2 registos de uso geral (x18 e x19) em cada
ra
int factorial(int n){ 3
if (n==0)
chamada.
x18
x19
return 1; ra
else
return fatorial(n-1)*n;
➢ Considerando a invocação factorial(5), determine o
2
x18
} estado da pilha quando esta atinge o valor máximo.
x19
ra
1
x18
x19
ra Para um dado n, serão ocupados:
0
x18
• n x 4(palavras)x4B + 4x4B = 16 (n+1) B
x19
SP ra

235
▪ Considere a implementação da função fatorial de forma
recursiva

▪ Considere que a passagem de parâmetros é sempre realizada


Exercicio pela pilha e que a implementação da função requer a
salvaguarda de 2 registos de uso geral (x18 e x19) em cada
int factorial(int n){
if (n==0)
chamada.
return 1;
else
return fatorial(n-1)*n;
➢ Considerando a invocação factorial(5), determine o
} estado da pilha quando esta atinge o valor máximo.

➢ Determine o valor máximo do parametro de entrada n, se


a sua pilha tiver uma dimensão máxima de 1KB.

1024
Para um dado n, 16 𝑛 + 1 = 1024 ⇒ 𝑛 + 1 = = 64
serão ocupados: 16
⇒ 𝑛𝑀𝐴𝑋 = 64 − 1 = 63
• 16 (n+1)
236
Reserva de espaço na stack

▪ Para simplificar, a especificação do RISC-V admite que o SP tem de estar sempre alinhado a 5
x18
uma palavra de 16B
▫ Se precisarmos de alocar xB na pilha (para passagem de parâmetros, salvaguarda de
𝑥
x19
ra
contexto, ou para variáveis temporárias), alocamos y blocos de 16B, tal que 𝑦 = 16 . 4
x18
x19
▪ No caso da função fatorial:
ra
3

▫ Quando chamamos a função factorial(n) alocamos 16B para os argumentos de


x18
x19
entrada.

ra
Desperdiçamos 12B 2
x18


x19
Para salvaguarda de registos (3) alocamos mais 16B. Como em RV32 os registos são ra
1
de 4B, precisamos de 12B. Alocamos portanto mais um bloco, desperdiçando 4B
x18

▫ No total, vamos precisar de alocar 2 blocos por cada chamada a fatorial (um para
x19
ra
argumentos, outro para salvaguarda de contexto). 0
x18
▫ Para factorial(n), são realizadas n chamadas ➔ alocação de 𝑛 + 1 × 2 × 16B x19
ra
237
Registos
Convenção do compilador

▪ Para evitar a salvaguarda de registos desnecessários, e simplificar o código,


o compilador pode fazer convenções específicas

238 238
Registos
Convenção do compilador

▪ Para evitar a salvaguarda de registos desnecessários, e simplificar o código,


o compilador pode fazer convenções específicas

Sempre zero

239 239
Registos
Convenção do compilador

▪ Para evitar a salvaguarda de registos desnecessários, e simplificar o código,


o compilador pode fazer convenções específicas

Guarda o retorno da função

240 240
Registos
Convenção do compilador

▪ Para evitar a salvaguarda de registos desnecessários, e simplificar o código,


o compilador pode fazer convenções específicas

Ultima posição da stack ocupada

241 241
Registos
Convenção do compilador

▪ Para evitar a salvaguarda de registos desnecessários, e simplificar o código,


o compilador pode fazer convenções específicas

Usados para facilitar o endereçamento.

Podem usar livremente ou como registo temporário para


endereçamento.
242 242
Registos int M(…){
(…)
y=f(xpto);
Convenção do compilador (…)
return;

▪ Para evitar a salvaguarda de registos desnecessários, e simplificar o código,


o compilador pode fazer convenções específicas

Usar livremente como registos temporários.

Após uma chamada da função M (mãe) a uma função f (filha),


podem ter sido modificados. Assim, se a função M necessita
destes registos, têm de ser salvaguardados por esta antes de
243 243 chamar f (e repostos após a chamada a f).
Registos int M(…){
(…)
y=f(xpto);
Convenção do compilador (…)
return;

▪ Para evitar a salvaguarda de registos desnecessários, e simplificar o código,


o compilador pode fazer convenções específicas

Registos que contêm variáveis da função M. Têm de ser


preservados após uma chamada à função f. A salvaguarda é da
responsabilidade da função filha (f).

244 244
Registos int M(…){
(…)
y=f(xpto);
Convenção do compilador (…)
return;

▪ Para evitar a salvaguarda de registos desnecessários, e simplificar o código,


o compilador pode fazer convenções específicas

Registos que são usados para passagem de parâmetros para a


função filha, pelo que o seu conteúdo pode ser modificado
livremente pela função filha.

245 245
Registos int M(…){
(…)
y=f(xpto);
Convenção do compilador (…)
return;

▪ Para evitar a salvaguarda de registos desnecessários, e simplificar o código,


o compilador pode fazer convenções específicas

Responsável pela preservação do valor após chamada à função


filha:
- Caller: função Mãe (M)
- Callee: função filha (f)
246
int M(…){
Salvaguarda de registos (…)

A. Sem convenção de compilador:

A Passagem de parametros para a


função M é realizada pela pilha

➢ TODOS os registos usados por M


devem ser salvaguardados à entrada, e o
seu valor reposto à saída.

➢ Exceptuam-se os registos
-x0 (o valor é sempre 0)
-x2 (Stack Pointer)

247
int M(…){
Salvaguarda de registos (…)

A. Sem convenção de compilador: B. Com convenção de compilador:

A Passagem de parametros para a A passagem de parametros para a função M é realizada por registo
função M é realizada pela pilha (x10-x17 para entrada, x10-x11 para saída).
(Usa-se a pilha apenas se o número de parametros entrada for maior que 8 e de saída maior que 2.)

➢ TODOS os registos usados por M


devem ser salvaguardados à entrada, e o
➢ A função M pode usar à vontade todos os registos marcados como
seu valor reposto à saída.
temporários, ou como argumentos de E/S:
➢ Exceptuam-se os registos
temporários: x5-x7, x28-x31 Argumentos E/S: x10-x17
-x0 (o valor é sempre 0)
-x2 (Stack Pointer)
➢ A função M deve preservar todos os registos correspondentes a saved
variables (se os usar):

saved variables: x4, x8-x9, x18-x27

248
int M(…){
Salvaguarda de registos (…)

A. Sem convenção de compilador: B. Com convenção de compilador:


End. Retorno

A Passagem de parametros para a


Saved Variable A passagem de parametros para a função M é realizada por registo
função M é realizada pela pilha
Saved Variable (x10-x17 para entrada, x10-x11 para saída).
Temporários (Usa-se a pilha apenas se o número de parametros entrada for maior que 8 e de saída maior que 2.)
Saved Variable
➢ TODOS os registos usados por M
Saved Variable
devem ser salvaguardados à entrada, e o
Argumentos ➢ A função M pode usar à vontade todos os registos marcados como
seu valor reposto à saída.
Argumentos temporários, ou como argumentos de E/S:
Saved Variable
➢ Exceptuam-se
Temporários os registos
temporários: x5-x7, x28-x31 Argumentos E/S: x10-x17
-x0 (o valor é sempre 0)
-x2 (Stack Pointer)
Temporários ➢ A função M deve preservar todos os registos correspondentes a saved
Saved Variable variables (se os usar):
Argumentos
Argumentos saved variables: x4, x8-x9, x18-x27
Saved Variable
Temporários

249
int M(…){
Chamada a uma função K(…)

A. Sem convenção de compilador: B. Com convenção de compilador:

Na chamada a uma função K(…) Na chamada a uma função K(…) que segue a convenção do
que NÃO segue a convenção do compilador…
compilador…

Não é preciso cuidados extra. Se a função M (caller) necessita de um valor que se encontra num
registo temporário ou de argumento E/S, para garantir que este se
mantem inalterado, M deve salvaguardar esse valor antes de chamar
K (callee).

Relembre-se: se K segue a convenção do compilador, não há qualquer garantia


que após a chamada a K, os registos temporários ou correspondentes a
argumentos de E/S, não sejam alterados.

250
Instruction Set Architecture (ISA)
Codificação das instruções
Codificação das instruções

▪ Em geral as instruções são codificadas numa palavra de instrução,


correspondente à seguinte sequência de campos (fields):
▫ Opcode – Código da operação a realizar
▫ Operandos – identificação (número) dos registos fonte (source) e destino (destination), e/ou
valor dos operandos imediatos (constantes codificadas imediatamente na palavra de instrução)
▫ Outros – codifica outras opções das instruções

▪ Dependendo do ISA, o tamanho e ordem de cada um dos campos pode variar


▪ No RISC-V a palavra de instrução tem tamanho fixo (4B)

OPCODE OPERANDOS OUTROS

32 bits (4B)
252
Codificação das instruções
Formato das instruções

253
Codificação das instruções
Formato das instruções

254
Codificação das instruções
Formato das instruções

add
sub
funct3? funct7? …

lb
opcode
?
lh
funct3? lw

lui
255 …
Quick Reference
Guide
Disponível na página da UC!!!

Não é suposto “decorar” os


formatos e a codificação das
instruções. É apenas preciso
compreender.

O Quick Reference Guide


pode ser consultado em
todos os momentos de
avaliação.

256
Exemplo 1
Codificação da instrução:

addi x1,x1,1
(increment)

257
Resposta: imm | ra | funct3 | rd | opcode

Exemplo 1
Codificação da instrução:

addi x1,x1,1
(increment)

258
Resposta: imm | ra | 000 | rd | 0010011

Exemplo 1
Codificação da instrução:

addi x1,x1,1
(increment)

259
Resposta: 0000 0000 0001 0000 1000 0000 1001 0011 = 00108093h

Exemplo 1
Codificação da instrução:

addi x1,x1,1
(increment)

260
Exemplo 2
Codificação da instrução:

ret
(return from subrotine)

261
Exemplo 2
Codificação da instrução:

ret
(return from subrotine)

jalr x0, x1, 0

262
Exemplo 2
Codificação da instrução:

ret
(return from subrotine)

jalr x0, x1, 0

Resposta: 0000 0000 0000 | 00001 | 000 | 00000 | 1100111

0000 0000 0000 0000 1000 0000 0110 0111 = 00008067h

263
Exemplo 3
Codificação da instrução:

call my_function

264
Exemplo 3
Codificação da instrução:

call my_function
(return from subrotine)

auipc + jalr Para realizar a operação, é necessário colocar parte do


endereço num registo auxiliar xT, o qual será depois usado
como base para a instrução jalr.

Como o registo ra (x1) vai ser modificado, podemos usá-lo


como registo temporário.

265
Exemplo 3
Codificação da instrução:

call my_function
(return from subrotine)

auipc + jalr Para realizar a operação, é necessário colocar parte do


endereço num registo auxiliar xT, o qual será depois usado
1) auipc ra,imm31:12 como base para a instrução jalr.
2) jalr ra,ra,imm11:0
Como o registo ra (x1) vai ser modificado, podemos usá-lo
como registo temporário.

266
Exemplo 3
Codificação da instrução:

call my_function
(return from subrotine)

auipc + jalr

1) auipc ra,imm31:12
2) jalr ra,ra,imm11:0

Resposta: imm | 00001 | 0010111

Qual o valor de imm? Depende da distancia entre a instrução auipc e o


inicio da rotina. Vamos assumir um valor: 4A28h

267
Exemplo 3
Codificação da instrução:

call my_function
(return from subrotine)

auipc + jalr

1) auipc ra,imm31:12
2) jalr ra,ra,imm11:0

Parte alta: 4h Resposta: imm | 00001 | 0010111


Parte baixa: A28h
Qual o valor de imm? Depende da distancia entre a instrução auipc e o
Como a parte baixa (em 12 bits)
inicio da rotina. Vamos assumir um valor: 4A28h
corresponde a um valor
negativo, vamos escrever o
268
número de forma diferente
Exemplo 3
Codificação da instrução:

call my_function
(return from subrotine)

auipc + jalr

1) auipc ra,imm31:12
2) jalr ra,ra,imm11:0

Parte alta: 4h+1h=5h auipc ra,5: 0000 0000 0000 0000 0101 00001 0010111
Parte baixa: A28h-1000h=A28h

Na prática pode-se demonstrar


que a parte baixa fica sempre
igual.
269
Exemplo 3
Codificação da instrução:

call my_function
(return from subrotine)

auipc + jalr

1) auipc ra,imm31:12
2) jalr ra,ra,imm11:0

Parte alta: 4h+1h=5h auipc ra,5: 0000 0000 0000 0000 0101 00001 0010111
Parte baixa: A28h-1000h=A28h jalr ra,ra,0xa28: 1010 0010 1000 00001 000 00001 1100111

270
Exemplo 4
Codificação da instrução:

jal x1,0xa740

271
Exemplo 4
Codificação da instrução:

jal x1,0xa740

272
Exemplo 4
Codificação da instrução:

jal x1,0xa740

Cuidado com a ordem dos bits!!!!

Valor do imediato:
0..0 1010 0111 0100 0000 - A ordem dos bits deve ser a indicada na
Imm[ 20 | 10:1 | 11 | 19:12 ] tabela de formatos
- O bit menos significativo é omitido!

273
Exemplo 4
Codificação da instrução:

jal x1,0xa740

Cuidado com a ordem dos bits!!!!

Valor do imediato:
0..0 1010 0111 0100 0000 - A ordem dos bits deve ser a indicada na
19:12 11 10:1 Imm[ 20 | 10:1 | 11 | 19:12 ] tabela de formatos
- O bit menos significativo é omitido!

jal x1,0xa740: 0 111 0100 000 0 0000 1010 00001 1101111


20 10:1 11 19:12
274
Instruction Set Architecture (ISA)
RISC-V
→ Compilação de código
Passos para a geração do executável

O compilador traduz a sequência de comandos da linguagem


de alto nível (ex: C, Java, Python, etc.) numa sequência de
“instruções” Assembly

276
Passos para a geração do executável
Linker

Embora o compilador acabe por gerar o Assembly


internamente, geralmente realiza imediatamente a
codificação das instruções.

A compilação do programa pode ser realizada por módulos


(ex: múltiplos ficheiros de código). Os ficheiros objeto não
são executáveis, já que incluem meta-dados para o linker.

O trabalho do linker é construir um binário final a partir dos


vários módulos pre-compilados.

277
Passos para a geração do executável
Visão dos módulos

Modulo A
Legenda
Data
Instruções
Referências a dados
(variáveis) declaradas
Modulo B no módulo B
Data
Instruções

Referências a
Modulo C chamadas ao módulo C
Data
Instruções

278
Linker
Passo A: Alocação dos vários módulos em memória

Mapa de memória

Modulo A
Data
Instruções

Modulo B
Data
Instruções
Data
Data
Modulo C
Data
Data
Instruções Instruções
Instruções
Instruções
279
Linker
Passo B: Resolução das referências

Mapa de memória

Modulo A
Data
Instruções

Modulo B
Data
Instruções
Data
Data
Modulo C
Data
Data
Instruções Instruções
Instruções
Instruções
280
Executável

Indica a estrutura e campos do executável

Indica os segmentos que devem ser carregados em memória para execução do


programa, e ainda a localização deles no ficheiro

Instruções (programa)

Variáveis apenas de leitura (constantes).


Nota: um #define não é uma variável

Variáveis globais que devem ser inicializadas (podem haver múltiplas .data sections)

Indica os segmentos que que foram gerados pelo linker e a sua localização no ficheiro

281
Executável
Espaço de endereçamento virtual

O executável com o
programa deve ser
carregado em
Data
memória, seguindo os
endereços indicados Programa

0 Reservado (SO)
282
Passos para a geração do executável
Loader

1. Lê a estrutura do executável
2. Carrega as secções de instruções e dados
para memória
3. Copia os parâmetros de entrada para a stack
4. Inicializa alguns registos do processador (ex:
SP)
283 5. Salta para o início do programa (main)
Instruction Set Architecture (ISA)
RISC-V
→ Mapa de memória
Mapa de memória
Visão da aplicação

Divisão do Espaço de endereçamento


Rotinas do kernel (núcleo do sistema operativo (SO)) que só podem
1GB Kernel Space ser acedidas via chamadas ao sistema (system calls)

Stack Pilha do programa (inclui variáveis locais às funções)


(variáveis locais)

Memory Map
Mapeamentos a ficheiros ou a bibliotecas (libraries), p. ex.: /lib/libc.so
Segment

3GB
(User Reserva de espaço de memória alocado ao longo da execução do programa,
Space) Heap i.e., através de malloc’s

Data Variáveis globais ou variáveis declaradas dentro de funções com a keyword static
Programa Código (instruções) do programa
Reservado (SO) Código geral acrescentado pelo SO, p.ex., carregar o programa para memória e
285 0 Inicialização da stack
Mapa de memória
Bare metal, i.e., sem SO

Divisão do Espaço de endereçamento


Especialmente em microcontroladores,
diferentes partes do mapa de memória
podem corresponder a diferentes tipos
Endereços de memórias (ex: EPROM, Flash, DRAM,
sem SRAM, etc)
mapeamento
físico a uma
Diferentes sistemas podem ter
memória
diferentes mapeamentos em memória
física. Este mapa é baseado na
especificação do ARMv8

Memória RAM
instalada no O mapa de memória do ARMv8 tem 64
RAM Dados/instruções do programa bits de endereço permitindo até 16 hexa
sistema
(ex: 16GB) bytes de memória (equivalente a 224
Terabytes de memória), mas este
espaço pode ser limitado pela
Periféricos Mapeamento dos registos dos periféricos implementação física do processador.

0 BIOS Código geral de inicialização do sistema


286
Mapa de memória
Dois mapas de memória diferentes?

Visão da aplicação (processo de Linux) Mapa de memória física

Kernel Space

Stack
(variáveis locais)

Memory Map
4GB Segment

Heap RAM 16GB

Data
Periféricos
Programa

0 Reservado (SO) 0 BIOS


287
Mapa de memória
Dois mapas de memória diferentes?
Visão da aplicação (processo de Linux)

Kernel Space

Stack
(variáveis locais)

Memory Map
Segment

Heap

BSS
Data
Programa

0 Reservado (SO)
288
Mapa de memória
Divisão do mapa em páginas

Mapa de memória virtual Mapa de memória física

Kernel Space
Página D

Stack
Cada segmento é dividida
Página C
em páginas (ex: 4kB) e
mapeado em memória
Memory Map
Segment física RAM
Página D
Página C
Heap Página A RAM 16GB
Página B
Página A Página B
Data Cada página contém informação
de apenas um segmento Periféricos
Programa
Reservado (SO) BIOS
289 0
Mapa de memória
Divisão do mapa em páginas

Mapa de memória virtual Mapa de memória física


Existe uma entidade:
Kernel Space
Página D Memory Management Unit (MMU),

composta por elementos de SW e HW,


Stack
que realiza o mapeamento e indica o
Cada segmento é dividida
Página C endereço físico de cada página virtual
em páginas (ex: 4kB) e
mapeada em memória Este tópico é estudado no final do
Memory Map
Segment física RAM semestre.
Página D
Este processo é transparente para a
Página C aplicação. Assim, do ponto de vista da
Heap Página A aplicação, todas as referências
RAM são 16GB
Página B
realizadas a endereços virtuais.
Página A Página B
Data Cada página contem informação
de apenas um segmento Periféricos
Programa
Reservado (SO) BIOS
290 0

Você também pode gostar