Istruzioni di salto condizionati

Le istruzioni condizionali o anche salti condizionati permettono di variare il flusso del programma (variando anche il valore del Program Counter) al verificarsi di una condizione.

  • branch if equal
  • branch if not equal
    La traduzione di branch è letteralmente ramo. Le istruzioni hanno la seguente forma:
  • beq rs1, rs2, L1
  • bne rs1, rs2, L1
    Ovvero: vai al ramo con etichetta L1 se gli operandi nei registri rs1 e rs2 sono uguali o se non lo sono.

Sono presenti anche rami come:

  • branch if less than: blt rs1, rs2, L1
  • branch if greater than or equal: bgt rs1, rs2, L1
    Ovvero: vai al ramo con etichetta L1 se l'operando in rs1 è minore/maggiore o uguale a rs2.

Esistono anche operazioni di salto condizionato che confrontano i due registri rs1 e rs2 trattandoli come numeri senza segno:

  • bltu rs1, rs2, L1
  • bgeu rs1, rs2, L1

Grazie a queste istruzioni è possibile tradurre le istruzioni if-else dei linguaggi di programmazione ad alto livello.

if (i == j)
	f = g + h;
else 
	f = g - h;
if (i == j)
	f = g + h;
else 
	f = g - h;

Posti: f = x19, g = x20, h = x21, i = x22, j = x23

bne x22, x23, ELSE
add x19, x20, x21
bne x0, x0, ENDIF
ELSE: sub x19, x20, x21
ENDIF: 
bne x22, x23, ELSE
add x19, x20, x21
bne x0, x0, ENDIF
ELSE: sub x19, x20, x21
ENDIF: 
if (i == j)
	f = g + h;
else 
	f = g - h;

Posti: f = x19, g = x20, h = x21, i = x22, j = x23

bne x22, x23, ELSE
add x19, x20, x21
bne x0, x0, ENDIF
ELSE: sub x19, x20, x21
ENDIF: 

=== end-multi-column
Premesse:

  • le etichette possono avere qualsiasi nome: in questo caso c'è l'etichetta ELSE e l'etichetta ENDIF.
  • le etichette nel codice assembly si indicano con NOME_ETICHETTA seguite dai due punti (:).
  • non c'è una regola del modo in cui si scrivono i salti condizionati, in questo esempio la condizione che non si verfica: l'ELSE è scritto prima dell'IF, ma potrebbero essere scritti in qualsiasi modo, è stato scelto questo modo per convenienza.

L'istruzione bne x22, x23, ELSE: fa in modo che il PC salti all'etichetta ELSE se x22 e x23 non sono uguali. In caso contrario il PC semplicemente avanza di una istruzione, non saltando all'ELSE, eseguendo il codice del caso in cui x22 e x23 sono uguali, dopo il PC avanza, se non ci fosse l'istruzione bne x0, x0, ENDIF (sempre vera poiché 0 = 0 è si verifica sempre), si eseguirebbe l'etichetta di ELSE, che invece grazie a tale riga viene saltata.

Nota

Cosa vuol dire avanzare di una istruzione per il Program Counter? Una istruzione in RISC-V è di 32 bit, come abbiamo visto con i vari formati delle istruzioni (Immediato, Registro, Store), di conseguenza il salto di una istruzione equivale a 4 salti: bit. Ricordiamo che ogni cella della memoria (in cui si trova il PC) è di 8 bit.

Con lo stesso metodo si possono scrivere istruzioni per i cicli for e while.

Cicli for
Una possibile implementazione:

for(i = 0; i < 100; i++){
	...
}
for(i = 0; i < 100; i++){
	...
}

Posto i = x19

add x19, x0, x0
addi x20, x0, 100

FOR:
bge x19, x20 ENDFOR
...
addi x19, x19, 1
beq x0, x0, FOR

ENDFOR:
add x19, x0, x0
addi x20, x0, 100

FOR:
bge x19, x20 ENDFOR
...
addi x19, x19, 1
beq x0, x0, FOR

ENDFOR:
for(i = 0; i < 100; i++){
	...
}

Posto i = x19

add x19, x0, x0
addi x20, x0, 100

FOR:
bge x19, x20 ENDFOR
...
addi x19, x19, 1
beq x0, x0, FOR

ENDFOR:

=== end-multi-column
Cicli while
Una possibile implementazione:

long v[10], k, i;
while(v[i] == k){
...
i = i + 1;
}
long v[10], k, i;
while(v[i] == k){
...
i = i + 1;
}

Posti: i = x22, k = x24, v = x25

LOOP: 
slli x10, x22, 3 # Salva in x10 l'indirizzo della double word v[i]
add x10, x10, x25

ld x9, 0(x10)

bne x9, x24, ENDLOOP
...

addi x22, x22, 1
beq x0, x0, LOOP

ENDLOOP:
LOOP: 
slli x10, x22, 3 # Salva in x10 l'indirizzo della double word v[i]
add x10, x10, x25

ld x9, 0(x10)

bne x9, x24, ENDLOOP
...

addi x22, x22, 1
beq x0, x0, LOOP

ENDLOOP:
long v[10], k, i;
while(v[i] == k){
...
i = i + 1;
}

Posti: i = x22, k = x24, v = x25

LOOP: 
slli x10, x22, 3 # Salva in x10 l'indirizzo della double word v[i]
add x10, x10, x25

ld x9, 0(x10)

bne x9, x24, ENDLOOP
...

addi x22, x22, 1
beq x0, x0, LOOP

ENDLOOP:

=== end-multi-column
Istruzioni di salto condizionato generiche
L'istruzione slt, o anche slti (versione immediate) scrive 1 nel registro rd se il contenuto del registro rs1 < rs2 e scrive 0 altrimenti. Questa istruzione ci consente di effettuare dei salti generici.

if (i < j){
	k = 1;	
}else{
	k = 0;
}
if (i < j){
	k = 1;	
}else{
	k = 0;
}

Posti: i = x19, j = x20, k = x21.

slt x21, x19, x20.
slt x21, x19, x20.
if (i < j){
	k = 1;	
}else{
	k = 0;
}

Posti: i = x19, j = x20, k = x21.

slt x21, x19, x20.

=== end-multi-column
Si può ad esempio inserire dopo slt l'istruzione beq x21, x19, x20.

  • per il confronto su '>=' basta invertire la condizione (bne)
  • per il confronto su '>' basta scambiare gli operandi dell'istruzione slt
  • per il confronto su '<=' si inverte la condizione e si scambiano gli operandi.

Le istruzioni esaminate fino ad ora operano su interi con segno:

  • gli operandi (a 64 bit, nei registri) sono rappresentati in complemento a due: i valori vanno da a .
  • anche i byte-offset di ld e sd e il numero di istruzioni di cui saltare nella bne/beq sono interi con segno da a .
    É possibile utilizzare ance operandi senza segno, in tal caso i valori vanno da a . In questo caso invece di slt o slti si useranno sltu e sltiu: in questo caso un confronto del tipo restituirà 0 (falso), infatti senza tener conto del segno il negativo viene interpretato come un numero estremamente grande.

Salti condizionati e linguaggio macchina
Le istruzioni di salto condizionato utilizzano un formato diverso di istruzione, il formato di tipo SB. Questo formato può rappresentare indirizzi di salto da -4096 a 4094, in multipli di due. Ovvero risulta che gli indirizzi del PC saranno sempre pari (in particolare considerando solo istruzioni di 32 bit, saranno sempre multipli di 4), il motivo lo spiegheremo nell'argomentazione che riguarda i salti non condizionati.

Questo tipo di istruzioni utilizzano indirizzamento relativo al PC, ovvero sommano all'indirizzo del PC, l'offset necessario per raggiungere l'etichetta specificata nell'istruzione di branch.
Il campo immediato, contiene tale l'offset, in realtà contiene il numero di istruzioni che devono essere saltate per raggiungere l'etichetta specificata nel branch.

Vediamo un esempio in RISC-V e cerchiamo di capire come avanza il PC:

LOOP: 
slli x10, x22, 3
add x10, x10, x25
ld x9, 0(x10)
bne x9, x24, ENDLOOP
addi, x22, x22, 1
beq x0, x0, LOOP

ENDLOOP:
IndirizzoIstruzione
80000000000 00011 10110 001 01010 0010011
80040000000 11001 01010 000 01010 0110011
80080000000 00000 01010 011 01001 0000111
80120000000 11000 01001 001 01100 1100011
80160000000 00001 10110 000 10110 0010011
800201111111 00000 00000 000 01101 1100011

PC = 8000
L'architettura sa che se l'opcode è 0010011 (Esempio di decodifica di istruzioni in linguaggio macchina) allora si tratta di una istruzione del formato I. Dopo di che guarda il parametro func3 che è 001, che indica una operazione di tipo slli.

Il PC avanza, PC = 8004.
L'architettura sa che se l'opcode è 0110011 si tratta di una istruzione del formato R. Dopo di che guarda il parametro funcr che è 000, che indica una operazione di tipo add.

Il PC continua ad avanzare in questo modo, "guardando" opcode e func3.

Nel manuale di RISC-V si trovano le tabelle relative agli opcode e func3, un esempio per il formato R:
Pasted image 20230329165328.png

Il linguaggio assembler fornisce anche delle pseudoistruzioni.

A questo punto della trattazione aggiungiamo anche delle istruzioni aritmetiche particolari, diverse da quelle trattate fino precedentemente (vedi istruzioni aritmetiche). Questa nuova raccolta di istruzioni aritmetiche le tratteremo nel file: istruzioni aritmetiche 2.

NOTA: oltre ai salti condizionati, il RISC-V consente i salti non condizionati.