VII. Manipulation de la pile▲
Dans cette partie, nous allons étudier les instructions permettant de manipuler la pile. Comme nous le verrons, elles sont légèrement plus difficiles à appréhender que les précédentes.
Le code est disponible sur Github (tag et branche).
VII-A. Catégories des types▲
La correspondance entre les types utilisés par la JVM et les types Java est présentée dans le tableau ci-dessous.
Certaines instructions de la JVM telles que pop et swap opèrent sur la pile sans prendre en compte le type des valeurs manipulées, contrairement à toutes les instructions que nous avons vues jusqu'à présent, telles que iadd, dload ou lshr. Cependant, ces instructions ne peuvent être utilisées que sur des valeurs d'une certaine catégorie.
Il existe deux catégories permettant de distinguer les types nécessitant une entrée dans les variables locales ou la pile, de ceux en nécessitant deux.
Bien qu'une référence puisse être représentée sur 32 ou 64 bits en fonction de la JVM, elle sera toujours de catégorie 1, et par conséquent, prendra toujours une entrée dans les variables locales ou la pile.
Type Java |
Type JVM |
Catégorie |
byte |
int |
1 |
short |
int |
1 |
char |
int |
1 |
int |
int |
1 |
float |
float |
1 |
référence |
référence |
1 |
adresse de retour * |
adresse de retour * |
1 |
long |
long |
2 |
double |
double |
2 |
* utilisée par l'instruction ret.
VII-B. Représentation de la pile▲
La JVM étant basée sur le modèle de la pile, il est essentiel de connaître quel est l'impact des instructions. Pour représenter l'état avant/après l'exécution d'une instruction, nous allons reprendre le format utilisé par la JVMS et qui est le suivant :
…, valeur1, valeur2 → …, résultat, où les valeurs les plus à droite sont au sommet de la pile. valeur1 et valeur2 étant les deux valeurs utilisées pour le calcul et résultat le résultat.
Il est important de noter que dans cette représentation, le long et le double sont considérés comme une seule valeur. Par conséquent, lorsque nécessaire nous présenterons les différents cas d'utilisation d'une instruction en utilisant plusieurs formes.
VII-C. Opérations sur la pile▲
Il est parfois utile de manipuler les données au sommet de la pile sans avoir à faire intervenir les variables locales. C'est ce que permettent les instructions suivantes :
Hex |
Mnémonique |
Description |
0x57 |
pop |
Dépile le premier élément. |
0x58 |
pop2 |
Dépile les deux premiers éléments. |
0x59 |
dup |
Duplique le premier élément et l'empile. |
0x5a |
dup_x1 |
Duplique le premier élément et l'ajoute sous le deuxième. |
0x5b |
dup_x2 |
Duplique le premier élément et l'ajoute sous le troisième. |
0x5c |
dup2 |
Duplique les deux premiers éléments et les empile (en gardant l'ordre). |
0x5d |
dup2_x1 |
Duplique les deux premiers éléments et les ajoute sous le troisième (en gardant l'ordre) |
0x5e |
dup2_x2 |
Duplique les deux premiers éléments et les ajoute sous le quatrième élément (en gardant l'ordre). |
0x5f |
swap |
Échange les deux premiers éléments. |
Une chose importante à ne pas oublier est que les long et double prennent deux cases dans la pile et doivent être considérés comme deux éléments.
VII-C-1. SWAP▲
L'instruction swap permet d'inverser les deux premiers éléments au sommet de la pile quel que soit leur type à condition qu'il soit de catégorie 1.
Note : elle ne peut pas être utilisée avec des long et des double.
État de la pile avant → après exécution : …, valeur1, valeur2 → …, valeur2, valeur1, où valeur1 et valeur2 sont de catégorie 1.
Code PJB :
2.
3.
4.
5.
6.
7.
8.
.class org/isk/jvmhardcore/bytecode/partsix/Swap
.method swap()I
iconst_1
iconst_2
swap
ireturn
.methodend
.classend
Test unitaire :
2.
3.
4.
5.
public
void
swap
(
) {
final
int
result =
Swap.swap
(
);
Assert.assertEquals
(
1
, result);
}
VII-C-2. POP▲
L'instruction pop dépile l'élément au sommet de la pile.
Note : elle ne peut pas être utilisée avec des long et des double.
État de la pile avant → après exécution : …, valeur1 → …, où valeur1 est de catégorie 1.
Code PJB :
2.
3.
4.
5.
6.
7.
8.
.class org/isk/jvmhardcore/bytecode/partsix/Pop
.method pop()D
dconst_0
iconst_1
pop
dreturn
.methodend
.classend
Test unitaire :
2.
3.
4.
5.
public
void
pop
(
) {
final
double
result =
Pop.pop
(
);
Assert.assertEquals
(
0.0
, result, 0.0001
);
}
VII-C-3. POP2▲
L'instruction pop2 dépile les deux premiers éléments au sommet de la pile.
VII-C-3-a. POP2 - Forme 1▲
État de la pile avant → après exécution : …, valeur1, valeur2 → …, où valeur1 et valeur2 sont de catégorie 1.
Code PJB :
2.
3.
4.
5.
6.
7.
8.
9.
.class org/isk/jvmhardcore/bytecode/partsix/Pop2_Form1
.method pop2()I
iconst_0
iconst_1
iconst_1
pop2
ireturn
.methodend
.classend
Test unitaire :
2.
3.
4.
5.
public
void
pop2_form1
(
) {
final
int
result =
Pop2_Form1.pop2
(
);
Assert.assertEquals
(
0
, result);
}
VII-C-3-b. POP2 - Forme 2▲
État de la pile avant → après exécution : …, valeur1 → …, où valeur1 est de catégorie 2.
Code PJB :
2.
3.
4.
5.
6.
7.
8.
.class org/isk/jvmhardcore/bytecode/partsix/Pop2_Form2
.method pop2()I
iconst_2
dconst_1
pop2
ireturn
.methodend
.classend
Test unitaire :
2.
3.
4.
5.
6.
Test
public
void
pop2_form2
(
) {
final
int
result =
Pop2_Form2.pop2
(
);
Assert.assertEquals
(
2
, result);
}
VII-C-4. DUP▲
L'instruction dup duplique le premier élément et l'empile.
Note : elle ne peut pas être utilisée avec des long et des double.
État de la pile avant → après exécution : …, valeur1 → …, valeur1, valeur1, où valeur1 est de catégorie 1.
Code PJB :
2.
3.
4.
5.
6.
7.
8.
9.
.class org/isk/jvmhardcore/bytecode/partsix/Dup
.method dup()I
iconst_2
iconst_1
dup
pop2
ireturn
.methodend
.classend
Test unitaire :
2.
3.
4.
5.
public
void
dup
(
) {
final
int
result =
Dup.dup
(
);
Assert.assertEquals
(
2
, result);
}
VII-C-5. DUP_X1▲
L'instruction dup_x1 duplique le premier élément et l'ajoute sous le deuxième.
Note : elle ne peut pas être utilisée avec des long et des double.
État de la pile avant → après exécution : …, valeur1, valeur2 → …, valeur1, valeur2, valeur1, où valeur1 et valeur2 sont de catégorie 1.
Code PJB :
2.
3.
4.
5.
6.
7.
8.
9.
.class org/isk/jvmhardcore/bytecode/partsix/Dup_X1
.method dup_x1()I
iconst_2
iconst_1
dup_x1
pop2
ireturn
.methodend
.classend
Test unitaire :
2.
3.
4.
5.
public
void
dup_x1
(
) {
final
int
result =
Dup_X1.dup_x1
(
);
Assert.assertEquals
(
1
, result);
}
VII-C-6. DUP_X2▲
L'instruction dup_x2 duplique le premier élément et l'ajoute sous le troisième.
Note : elle ne peut pas être utilisée avec des long et des double.
VII-C-6-a. DUP_X2 - Forme 1▲
État de la pile avant → après exécution : …, valeur1, valeur2, valeur3 → …, valeur3, valeur1, valeur2, valeur3, où valeur1, valeur2 et valeur3 sont de catégorie 1.
Code PJB :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
.class org/isk/jvmhardcore/bytecode/partsix/Dup_X2_Form1
.method dup_x2()I
iconst_3
iconst_2
iconst_1
dup_x2
pop2
pop
ireturn
.methodend
.classend
Test unitaire :
2.
3.
4.
5.
public
void
dup_x2_form1
(
) {
final
int
result =
Dup_X2_Form1.dup_x2
(
);
Assert.assertEquals
(
1
, result);
}
VII-C-6-b. DUP_X2 - Forme 2▲
État de la pile avant → après exécution : …, valeur1, valeur2 → …, valeur2, valeur1, valeur2, où valeur1 est de catégorie 2 et valeur2 est de catégorie 1.
Code PJB :
2.
3.
4.
5.
6.
7.
8.
9.
10.
.class org/isk/jvmhardcore/bytecode/partsix/Dup_X2_Form2
.method dup_x2()I
dconst_1
iconst_2
dup_x2
pop
pop2
ireturn
.methodend
.classend
Test unitaire :
2.
3.
4.
5.
public
void
dup_x2_form2
(
) {
final
int
result =
Dup_X2_Form2.dup_x2
(
);
Assert.assertEquals
(
2
, result);
}
VII-C-7. DUP2▲
L'instruction dup2 duplique les deux premiers éléments et les empile (en gardant l'ordre).
VII-C-7-a. DUP2 - Forme 1▲
État de la pile avant → après exécution : …, valeur1, valeur2 → …, valeur1, valeur2, valeur1, valeur2, où valeur1, valeur2 et valeur3 sont de catégorie 1.
Code PJB :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
.class org/isk/jvmhardcore/bytecode/partsix/Dup2_Form1
.method dup2()I
iconst_2 # [empty] -> 2
iconst_1 # 2 -> 2, 1
dup2 # 2, 1 -> 2, 1, 2, 1
@--------- Start packing
bipush 8 # 2, 1, 2, 1 -> 2, 1, 2, 1, 8
ishl # 2, 1, 2, 1, 8 -> 2, 1, 2, 1 << 8
ior # 2, 1, 2, 1 << 8 -> 2, 1, 2 | 1 << 8
bipush 8 # 2, 1, 2 | 1 << 8 -> 2, 1, 2 | 1 << 8, 8
ishl # 2, 1, 2 | 1 << 8, 8 -> 2, 1, (2 | 1 << 8) << 8
ior # 2, 1, (2 | 1 << 8) << 8 -> 2, 1 | (2 | 1 << 8) << 8
bipush 8 # etc.
ishl
ior
ireturn # 0000 0001 0000 0010 0000 0001 0000 0010
.methodend
.classend
Nous souhaitons connaître l'état de la pile - les valeurs présentes et leur ordre - après l'exécution des trois premières instructions (celles se trouvant avant le commentaire Start packing) pour vérifier le fonctionnement de l'instruction dup2. Or, avec les instructions que nous avons étudiées jusqu'à présent, la seule solution est d'empaqueter les quatre valeurs présentes dans la pile (2, 1, 2, 1). En prenant le type int, l'empaquetage consistera à découper la valeur retournée en quatre blocs de 8 bits (). Le bloc le plus à gauche contiendra la valeur au sommet de la pile, le suivant la valeur juste au-dessous, et ainsi de suite jusqu'au bas la pile, pour obtenir la valeur 0x1020102. Pour ce faire, nous utilisons les instructions d'opération bit à bit que nous avons vues dans la partie précédente.
Test unitaire :
2.
3.
4.
5.
public
void
dup2_form1
(
) {
final
int
result =
Dup2_Form1.dup2
(
);
Assert.assertEquals
(
0x01020102
, result);
}
VII-C-7-b. DUP2 - Forme 2▲
État de la pile avant → après exécution : …, valeur1 → …, valeur1, valeur1, où valeur1 est de catégorie 2.
Code PJB :
2.
3.
4.
5.
6.
7.
8.
.class org/isk/jvmhardcore/bytecode/partsix/Dup2_Form2
.method dup2()D
dconst_1 # [empty] -> 1.0
dup2 # 1.0 -> 1.0, 1.0
dadd # 1.0, 1.0 -> 2.0
dreturn
.methodend
.classend
Test unitaire :
2.
3.
4.
5.
public
void
dup2_form2
(
) {
final
double
result =
Dup2_Form2.dup2
(
);
Assert.assertEquals
(
2.0
, result, 0.0001
);
}
VII-C-8. DUP2_X1▲
L'instruction dup2_x1 duplique les deux premiers éléments et les ajoute sous le troisième (en gardant l'ordre).
VII-C-8-a. DUP2_X1 - Forme 1▲
État de la pile avant → après exécution : …, valeur1, valeur2, valeur3 → …, valeur2, valeur3, valeur1, valeur2, valeur3, où valeur1, valeur2 et valeur3 sont de catégorie 1.
Code PJB :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
.class org/isk/jvmhardcore/bytecode/partsix/Dup2_X1_Form1
.method dup2_x1()I
iconst_3 # [empty] -> 3
iconst_2 # 3 -> 3, 2
iconst_1 # 3, 2 -> 3, 2, 1
dup2_x1 # 3, 2, 1 -> 2, 1, 3, 2, 1
@--------- Start packing
bipush 4
ishl
ior
bipush 4
ishl
ior
bipush 4
ishl
ior
bipush 4
ishl
ior
ireturn # 0001 0010 0011 0001 0010
.methodend
.classend
Test unitaire :
2.
3.
4.
5.
public
void
dup2_x1_form1
(
) {
final
int
result =
Dup2_X1_Form1.dup2_x1
(
);
Assert.assertEquals
(
0x12312
, result);
}
VII-C-8-b. DUP2_X1 - Forme 2▲
État de la pile avant → après exécution : …, valeur1, valeur2 → …, valeur2, valeur1, valeur2, où valeur1 est de catégorie 1 et valeur2 est de catégorie 2.
Code PJB :
2.
3.
4.
5.
6.
7.
8.
9.
10.
.class org/isk/jvmhardcore/bytecode/partsix/Dup2_X1_Form2
.method dup2_x1()D
iconst_3 # [empty] -> 3
dconst_1 # 3 -> 3, 1.0
dup2_x1 # 3, 1.0 -> 1.0, 3, 1.0
pop2 # 1.0, 3, 1.0 -> 1.0, 3
pop # 1.0, 3 -> 1.0
dreturn
.methodend
.classend
Test unitaire :
2.
3.
4.
5.
public
void
dup2_x1_form2
(
) {
final
double
result =
Dup2_X1_Form2.dup2_x1
(
);
Assert.assertEquals
(
1.0
, result, 0.0001
);
}
VII-C-9. DUP2_X2▲
L'instruction dup2_x2 duplique les deux premiers éléments et les ajoute sous le quatrième élément (en gardant l'ordre).
VII-C-9-a. DUP2_X2 - Forme 1▲
État de la pile avant → après exécution : …, valeur1, valeur2, valeur3, valeur4 → …, valeur3, valeur4, valeur1, valeur2, valeur3, valeur4, où toutes les valeurs sont de catégorie 1.
Code PJB :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
.class org/isk/jvmhardcore/bytecode/partsix/Dup2_X2_Form1
.method dup2_x2()I
iconst_4 # [empty] -> 4
iconst_3 # 4 -> 4, 3
iconst_2 # 4, 3 -> 4, 3, 2
iconst_1 # 4, 3, 2 -> 4, 3, 2, 1
dup2_x2 # 3, 2, 1 -> 2, 1, 3, 2, 1
@--------- Start packing
bipush 4
ishl
ior
bipush 4
ishl
ior
bipush 4
ishl
ior
bipush 4
ishl
ior
bipush 4
ishl
ior
ireturn # 0001 0010 0011 0100 0001 0010
.methodend
.classend
Test unitaire :
2.
3.
4.
5.
public
void
dup2_x2_form1
(
) {
final
int
result =
Dup2_X2_Form1.dup2_x2
(
);
Assert.assertEquals
(
0x123412
, result);
}
VII-C-9-b. DUP2_X2 - Forme 2▲
État de la pile avant → après exécution : …, valeur1, valeur2, valeur3 → …, valeur3, valeur1, valeur2, valeur3, où valeur1 et valeur2 sont de catégorie 1 et valeur3 est de catégorie 2.
Code PJB :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
.class org/isk/jvmhardcore/bytecode/partsix/Dup2_X2_Form2
.method dup2_x2()D
iconst_2 # [empty] -> 2
iconst_0 # 2 -> 2, 0
dconst_1 # 2, 0 -> 2, 0, 1.0
dup2_x2 # 2, 0, 1.0 -> 1.0, 2, 0, 1.0
pop2 # 1.0, 2, 0, 1.0 -> 1.0, 2, 0
pop2 # 1.0, 2, 0 -> 1.0
dreturn
.methodend
.classend
Test unitaire :
2.
3.
4.
5.
public
void
dup2_x2_form2
(
) {
final
double
result =
Dup2_X2_Form2.dup2_x2
(
);
Assert.assertEquals
(
1.0
, result, 0.0001
);
}
VII-C-9-c. DUP2_X2 - Forme 3▲
État de la pile avant → après exécution : …, valeur1, valeur2, valeur3 → …, valeur2, valeur3, valeur1, valeur2, valeur3, où valeur1 est de catégorie 2 et, valeur2 et valeur3 sont de catégorie 1.
Code PJB :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
.class org/isk/jvmhardcore/bytecode/partsix/Dup2_X2_Form3
.method dup2_x2()I
dconst_1 # [empty] -> 1.0
iconst_1 # 1.0 -> 1.0, 1
iconst_2 # 1.0, 1 -> 1.0, 1, 2
dup2_x2 # 1.0, 1, 2 -> 1, 2, 1.0, 1, 2
pop2 # 1, 2, 1.0, 1, 2 -> 1, 2, 1.0
pop2 # 1, 2, 1.0 -> 1, 2
@--------- Start packing
bipush 4
ishl
ior
ireturn # 0010 0001
.methodend
.classend
Test unitaire :
2.
3.
4.
5.
public
void
dup2_x2_form3
(
) {
final
int
result =
Dup2_X2_Form3.dup2_x2
(
);
Assert.assertEquals
(
0x21
, result);
}
VII-C-9-d. DUP2_X2 - Forme 4▲
État de la pile avant → après exécution : …, valeur1, valeur2 → …, valeur2, valeur1, valeur2, où valeur1 et valeur2 sont de catégorie 2.
Code PJB :
2.
3.
4.
5.
6.
7.
8.
9.
10.
.class org/isk/jvmhardcore/bytecode/partsix/Dup2_X2_Form4
.method dup2_x2()D
dconst_0
dconst_1
dup2_x2
pop2
pop2
dreturn
.methodend
.classend
Test unitaire :
2.
3.
4.
5.
public
void
dup2_x2_form4
(
) {
final
double
result =
Dup2_X2_Form4.dup2_x2
(
);
Assert.assertEquals
(
1.0
, result, 0.0001
);
}
VII-D. What's next ?▲
Au cours des trois parties précédentes et de celle-ci, nous avons vu 136 instructions. Néanmoins, l'objectif étant aussi de comprendre le contenu d'un fichier .class, nous allons mettre de côté les 69 restantes pendant quelques parties pour nous concentrer sur l'étude de différents éléments dont la connaissance est nécessaire à la construction d'un assembleur de bytecode.
La prochaine partie sera dédiée à la première partie de la création d'un analyseur syntaxique.