** "Ressemble à JavaScript, le cerveau (contenu) est Ruby, (la stabilité est AC / DC)" ** Langage de script Kinx ). J'ai fait une bibliothèque pour la compilation JIT.
Je veux faire JIT. Cette fois, nous avons fait de SLJIT, qui est également utilisé dans Kinx --native, une bibliothèque pour le rendre plus facile à utiliser. Puisque SLJIT lui-même a peu de documents et est utilisé en le décodant à partir de la source, j'ai pensé à écrire comment utiliser SLJIT lui-même comme un mémorandum, mais cette fois il est réservé. Je pourrais le faire quelque part.
Cependant, bien sûr, il est plus facile à utiliser que d'utiliser SLJIT tel quel, donc je pense que c'est mieux. ** La langue hôte est également un script **, vous pouvez donc en profiter facilement.
Tout d'abord, je vais vous donner un exemple de ce à quoi ressemblera le programme. Il semble que divers détails continueront et n'atteindront pas ce point. .. ..
using Jit;
var c = new Jit.Compiler();
var entry1 = c.enter();
var jump0 = c.ge(Jit.S0, Jit.IMM(3));
c.ret(Jit.S0);
var l1 = c.label();
c.sub(Jit.R0, Jit.S0, Jit.IMM(2));
c.call(entry1);
c.mov(Jit.S1, Jit.R0);
c.sub(Jit.R0, Jit.S0, Jit.IMM(1));
c.call(entry1);
c.add(Jit.R0, Jit.R0, Jit.S1);
c.ret(Jit.R0);
jump0.setLabel(l1);
var code = c.generate();
for (var i = 1; i <= 42; ++i) {
var tmr = new SystemTimer();
var r = code.run(i);
System.println("[%8.3f] fib(%2d) = %d" % tmr.elapsed() % i % r);
}
Créez un objet Jit.Compiler
, créez une entrée de fonction avec ʻenter, et écrivez du code qui tripote divers registres à
ret. Ainsi, lorsque vous l'exécutez, il devient
generate ()et
run (). Vous pouvez également voir la liste d'assemblage en faisant
generate ()et
dump ()`.
Si vous voulez ignorer diverses choses, allez dans Sample! → L'exemple fait également des tests avec Ruby, Python et PyPy.
SLJIT
Qu'est-ce que SLJIT en premier lieu?
En un mot, ** Abstract Assembler ** est une bibliothèque qui résout le problème de l'assembleur selon lequel un style d'écriture peut prendre en charge plusieurs environnements, ce qui est différent pour chaque processeur et doit être recréé. Les plates-formes actuellement prises en charge sont les suivantes.
Cependant, veuillez noter que la version Kinx de la bibliothèque JIT introduite ici ne prend en charge que 64 bits, et nous n'avons confirmé (créé) que Windows x64 et x64 Linux.
Pour autant que je sache, je n'ai trouvé que les documents utiles suivants.
Ce sera utile.
Le référentiel GitHub est ci-dessous.
Jit
Maintenant, la bibliothèque JIT comme bibliothèque Kinx. C'est plus pratique que de l'utiliser comme C. Bien sûr, vous pouvez utiliser la bibliothèque C pour plus de contrôle, mais vous pouvez le faire.
using Jit
La bibliothèque Jit n'est pas intégrée, utilisez donc la directive using pour la charger explicitement.
using Jit;
L'objet Jit définit des méthodes pour les paramètres et les classes du compilateur.
Il existe trois types de paramètres Jit: valeur immédiate, registre et accès mémoire. Il est utilisé sous la forme suivante.
La valeur immédiate et l'accès à la mémoire sont utilisés dans les méthodes suivantes. Jit.VAR ()
est une méthode spéciale pour utiliser la zone de variable locale. Une zone de variable locale est automatiquement allouée dans la zone de pile et cette zone est utilisée.
Méthode | Remarques |
---|---|
Jit.IMM(v) |
Les entiers 64 bits et les nombres à virgule flottante sont écrits de la même manière. Correspondance avec le registre de destination de l'affectation. |
Jit.VAR(n) |
Zone variable locale. 1 variable fixée à 8 octets. |
Jit.MEM0(address) |
La valeur immédiate est attribuée comme adresse, mais elle ne peut pas être utilisée à partir du script car l'adresse réelle ne peut pas être spécifiée à partir du script pour le moment. |
Jit.MEM1(r1, offset) |
Le registre spécifié dans r1 est considéré comme une adresse et l'adresse mémoire de la position de décalage (en octets) est affichée. |
Jit.MEM2(r1, r2, shift) |
shift est 0 pour 1 octet, 1 pour 2 octets, 2 pour 4 octets, 3 pour 8 octets,r1 + r2 * (Octets indiqués par shift) Indique l'adresse mémoire de l'emplacement de. |
Les registres suivants peuvent être utilisés. Le nombre de registres utilisables dans une fonction est calculé automatiquement et change pour chaque fonction (plage séparée par ʻenter () `).
S'inscrire | Utilisation |
---|---|
Jit.R0 ~ Jit.R5 |
Registre à usage général. Utilisé temporairement. Il peut être supprimé après avoir appelé une autre fonction. |
Jit.S0 ~ Jit.S5 |
Registre à usage général. Garantissez qu'il ne sera pas détruit après avoir appelé une autre fonction. |
Jit.FR0 ~ Jit.FR5 |
Registre à virgule flottante. Utilisé temporairement. Il peut être supprimé après avoir appelé une autre fonction. |
Jit.FS0 ~ Jit.FS5 |
Registre à virgule flottante. Garantissez qu'il ne sera pas détruit après avoir appelé une autre fonction. |
Puisqu'il y a un maximum de 6 registres pour Floating Point au total pour «FR» / «FS», seul «FS0» peut être utilisé avec jusqu'à «FR4». Si vous utilisez jusqu'à «FR5», vous ne pouvez pas utiliser tous les «FS *». Veuillez noter qu'il ressemble à ce qui suit.
FR* S'inscrire |
FS* S'inscrire |
---|---|
(Indisponible) | FS0 , FS1 , FS2 , FS3 , FS4 , FS5 |
FR0 |
FS0 , FS1 , FS2 , FS3 , FS4 |
FR0 , FR1 |
FS0 , FS1 , FS2 , FS3 |
FR0 , FR1 , FR2 |
FS0 , FS1 , FS2 |
FR0 , FR1 , FR2 , FR3 |
FS0 , FS1 |
FR0 , FR1 , FR2 , FR3 , FR4 |
FS0 |
FR0 , FR1 , FR2 , FR3 , FR4 , FR5 |
(Indisponible) |
Pour créer une instruction Jit, créez un objet de compilateur Jit.
var c = new Jit.Compiler();
Le compilateur Jit a les méthodes suivantes.
Méthode du compilateur Jit | Valeur de retour | Aperçu |
---|---|---|
Jit.Compiler#label() |
label | Ajoutez une étiquette à l'emplacement actuel. |
Jit.Compiler#makeConst(reg, init) |
ConstTarget | Produit un code de définition temporaire pour définir la valeur immédiate après la génération du code. |
Jit.Compiler#localp(dst, offset) |
Sortez le code pour obtenir l'adresse réelle de la variable locale.dst Il est stocké dans le registre indiqué dans. offset est le numéro de la variable locale. |
|
Jit.Compiler#enter(argType) |
label | Créez une entrée de fonction. Le type d'argument peut être spécifié (facultatif). |
Jit.Compiler#fastEnter(reg) |
label | Créez une entrée de fonction. Cependant, aucun épilogue ou prologue supplémentaire n'est produit et l'adresse de retour est sortie.reg Enregistrer dans. |
Jit.Compiler#ret(val) |
Sortez le code de retour.val rends le.val Est un nombre à virgule flottanteFR0 Inscrivez-vous, sinonR0 Retourné au registre. |
|
Jit.Compiler#f2i(dst, op1) |
double int64_Sortez le code à convertir en t.dst Est un registre à usage général.op1 Est un registre à virgule flottante. |
|
Jit.Compiler#i2f(dst, op1) |
int64_Code de sortie qui convertit t en double.dst Est un registre à virgule flottante.op1 Est un registre à usage général. |
|
Jit.Compiler#mov(dst, op1) |
dst Àop1 Sortez le code à remplacer. La virgule flottante et les autres types sont automatiquement reconnus. |
|
Jit.Compiler#neg(dst, op1) |
op1 Le résultat de l'inversion de signe dedst Sortez le code dans lequel stocker. |
|
Jit.Compiler#clz(dst, op1) |
op1 Comptez le nombre de bits qui sont 0 à partir du début dedst Sortez le code dans lequel stocker. |
|
Jit.Compiler#add(dst, op1, op2) |
op1 Quandop2 Le résultat de l'ajoutdst Sortez le code dans lequel stocker. |
|
Jit.Compiler#sub(dst, op1, op2) |
op1 Quandop2 Le résultat de la soustractiondst Sortez le code dans lequel stocker. |
|
Jit.Compiler#mul(dst, op1, op2) |
op1 Quandop2 Le résultat de la multiplication pardst Sortez le code dans lequel stocker. |
|
Jit.Compiler#div(dst, op1, op2) |
Nombres à virgule flottante uniquement,op1 Quandop2 Le résultat de la divisiondst Sortez le code dans lequel stocker. |
|
Jit.Compiler#div() |
La valeur divisée par le registre à usage général comme non signéR0 Sortez le code à stocker dans le registre. |
|
Jit.Compiler#sdiv() |
La valeur divisée par le registre à usage général signéR0 Sortez le code à stocker dans le registre. |
|
Jit.Compiler#divmod() |
La valeur divisée par le registre à usage général comme non signéR0 Stocker dans un registre et laisser le resteR1 Sortez le code à stocker dans le registre. |
|
Jit.Compiler#sdivmod() |
La valeur divisée par le registre à usage général signéR0 Stocker dans un registre et laisser le resteR1 Sortez le code à stocker dans le registre. |
|
Jit.Compiler#not(dst, op1) |
op1 Le résultat de l'inversion de bits dedst Sortez le code dans lequel stocker. |
|
Jit.Compiler#and(dst, op1, op2) |
op1 Quandop2 Bit AND valeur avecdst Sortez le code dans lequel stocker. |
|
Jit.Compiler#or(dst, op1, op2) |
op1 Quandop2 Bit OR valeur dansdst Sortez le code dans lequel stocker. |
|
Jit.Compiler#xor(dst, op1, op2) |
op1 Quandop2 Bit XOR valeur dansdst Sortez le code dans lequel stocker. |
|
Jit.Compiler#shl(dst, op1, op2) |
op1 Àop2 La valeur décalée vers la gauche par le bitdst Sortez le code dans lequel stocker. |
|
Jit.Compiler#lshr(dst, op1, op2) |
op1 Àop2 La valeur est décalée logiquement vers la droite par le bitdst Sortez le code dans lequel stocker. |
|
Jit.Compiler#ashr(dst, op1, op2) |
op1 Àop2 Bits, valeur arithmétique décalée vers la droitedst Sortez le code dans lequel stocker. |
|
Jit.Compiler#call(label) |
JumpTarget | enter() Sortez le code qui effectue l'appel de la fonction définie. Renvoie un JumpTarget qui définit ultérieurement l'appelé.label Si est spécifié, il n'est pas nécessaire de le définir ultérieurement. |
Jit.Compiler#fastCall(label) |
JumpTarget | fastEnter() Sortez le code qui appelle la fonction définie dans. Renvoie un JumpTarget qui définit ultérieurement l'appelé. |
Jit.Compiler#jmp(label) |
JumpTarget | jmp Sortez la commande.label Si est spécifié, il n'est pas nécessaire de le définir ultérieurement. |
Jit.Compiler#ijmp(dst) |
JumpTarget | jmp Sortez la commande.dst Est un registre indiquant l'adresse ou une valeur immédiate. |
Jit.Compiler#eq(op1, op2) |
JumpTarget | op1 == op2 Sortez le code pour confirmer. Renvoie un JumpTarget qui spécifie la destination du saut si la condition est vraie. |
Jit.Compiler#neq(op1, op2) |
JumpTarget | op1 != op2 Sortez le code pour confirmer. Renvoie un JumpTarget qui spécifie la destination du saut si la condition est vraie. |
Jit.Compiler#lt(op1, op2) |
JumpTarget | Comme non signéop1 < op2 Sortez le code pour confirmer. Renvoie un JumpTarget qui spécifie la destination du saut si la condition est vraie. |
Jit.Compiler#le(op1, op2) |
JumpTarget | Comme non signéop1 <= op2 Sortez le code pour confirmer. Renvoie un JumpTarget qui spécifie la destination du saut si la condition est vraie. |
Jit.Compiler#gt(op1, op2) |
JumpTarget | Comme non signéop1 > op2 Sortez le code pour confirmer. Renvoie un JumpTarget qui spécifie la destination du saut si la condition est vraie. |
Jit.Compiler#ge(op1, op2) |
JumpTarget | Comme non signéop1 >= op2 Sortez le code pour confirmer. Renvoie un JumpTarget qui spécifie la destination du saut si la condition est vraie. |
Jit.Compiler#slt(op1, op2) |
JumpTarget | Attribuéop1 < op2 Sortez le code pour confirmer. Renvoie un JumpTarget qui spécifie la destination du saut si la condition est vraie. |
Jit.Compiler#sle(op1, op2) |
JumpTarget | Attribuéop1 <= op2 Sortez le code pour confirmer. Renvoie un JumpTarget qui spécifie la destination du saut si la condition est vraie. |
Jit.Compiler#sgt(op1, op2) |
JumpTarget | Attribuéop1 > op2 Sortez le code pour confirmer. Renvoie un JumpTarget qui spécifie la destination du saut si la condition est vraie. |
Jit.Compiler#sge(op1, op2) |
JumpTarget | Attribuéop1 >= op2 Sortez le code pour confirmer. Renvoie un JumpTarget qui spécifie la destination du saut si la condition est vraie. |
Jit.Compiler#generate() |
JitCode | Générez du code. |
Jit.Compiler#enter(argType)
L'entrée de la fonction est définie par la méthode ʻenter, mais si ʻargType
n'est pas spécifié, on considère que Jit.ArgType.SW_SW_SW
est spécifié. Jusqu'à 3 arguments (spécification) spécifient chaque type.
SW
... Signed Word (64bit)UW
... Unsigned Word (64bit)FP
... Floating Point (64bit)En fait, «SW» et «UW» sont les mêmes parce que les chaînes de bits des registres reçus sont les mêmes, mais cela peut faire une certaine différence dans le futur. Notez que «SW» peut être omis du dernier argument. Par conséquent, ce qui suit a la même signification.
Jit.ArgType.SW_SW_SW
Jit.ArgType.SW_SW
Jit.ArgType.SW
Les registres passés comme arguments sont fixes et sont les suivants.
Moule | 1er argument | 2ème argument | Troisième argument |
---|---|---|---|
Integer | Jit.R0 |
Jit.R1 |
Jit.R2 |
Double | Jit.FR0 |
Jit.FR1 |
Jit.FR2 |
Moule | 1er argument | 2ème argument | Troisième argument |
---|---|---|---|
Integer | Jit.S0 |
Jit.S1 |
Jit.S2 |
Double | Jit.FS0 |
Jit.FS1 |
Jit.FS2 |
Notez que le registre défini par l'appelant et le registre reçu par le récepteur sont différents.
ConstTarget
Définissez l'adresse de l'étiquette avec setLabel ()
.
Il est utilisé lorsque vous souhaitez stocker l'adresse d'étiquette en tant que valeur immédiate dans un registre ou une mémoire. Avez-vous de nombreuses opportunités de l'utiliser? Je pense qu'il peut être utilisé comme substitut à une table de saut, mais je n'ai pas préparé un bon mécanisme pour créer une table.
Au fait, vous pouvez définir la valeur immédiate avec setValue ()
, mais j'ai rendu possible l'utilisation de Jit.IMM (100)
ou même de nombres à virgule flottante tels que Jit.IMM (0,1)
. Il ne sert à rien de l’utiliser.
Un exemple de son utilisation pour une table de sauts sera décrit plus loin.
JumpTarget
Définissez la destination du saut ou l'adresse de l'appel de fonction avec setLabel ()
.
Par exemple, lors du branchement basé sur le résultat de la comparaison, cela devient comme suit.
var c = new Jit.Compiler();
//Point d'entrée de fonction.
c.enter();
//Valeur du registre S0>= 3
var jump0 = c.ge(Jit.S0, Jit.IMM(3));
... //Code lorsque la condition est fausse
var jump1 = c.jmp();
var label0 = c.label();
... //Code lorsque la condition est vraie
var label1 = c.label();
...
jump0.setLabel(label0);
jump1.setLabel(label1);
JitCode
Si le code est généré avec succès par la méthode generate ()
, un objet JitCode est renvoyé. Les méthodes de l'objet JitCode sont les suivantes. Notez que vous ne pouvez spécifier que jusqu'à 3 arguments (spécification). Puisqu'il s'agit d'un assembleur abstrait, il s'agit d'une spécification requise pour prendre en charge diverses architectures. Si nécessaire, il est nécessaire de sécuriser une zone de variable locale et de transmettre l'adresse de début de la zone de variable locale. Un échantillon sera décrit plus loin.
Méthode | Aperçu |
---|---|
JitCode#run(a1, a2, a3) |
Recevez la valeur de retour sous la forme Integer. |
JitCode#frun(a1, a2, a3) |
Recevez la valeur de retour comme Double. |
JitCode#dump() |
Sortez la liste d'assemblage générée. |
Écrivons maintenant une version récursive du code qui calcule la séquence de Fibonacci habituelle. C'est le même que celui présenté à l'origine comme échantillon.
var c = new Jit.Compiler();
var entry1 = c.enter();
var jump0 = c.ge(Jit.S0, Jit.IMM(3));
c.ret(Jit.S0);
var l1 = c.label();
c.sub(Jit.R0, Jit.S0, Jit.IMM(2));
c.call(entry1);
c.mov(Jit.S1, Jit.R0);
c.sub(Jit.R0, Jit.S0, Jit.IMM(1));
c.call(entry1);
c.add(Jit.R0, Jit.R0, Jit.S1);
c.ret(Jit.R0);
jump0.setLabel(l1);
var code = c.generate();
for (var i = 1; i <= 42; ++i) {
var tmr = new SystemTimer();
var r = code.run(i);
System.println("[%8.3f] fib(%2d) = %d" % tmr.elapsed() % i % r);
}
Le résultat est le suivant.
[ 0.000] fib( 1) = 1
[ 0.000] fib( 2) = 2
[ 0.000] fib( 3) = 3
[ 0.000] fib( 4) = 5
[ 0.000] fib( 5) = 8
[ 0.000] fib( 6) = 13
[ 0.000] fib( 7) = 21
[ 0.000] fib( 8) = 34
[ 0.000] fib( 9) = 55
[ 0.000] fib(10) = 89
[ 0.000] fib(11) = 144
[ 0.000] fib(12) = 233
[ 0.000] fib(13) = 377
[ 0.000] fib(14) = 610
[ 0.000] fib(15) = 987
[ 0.000] fib(16) = 1597
[ 0.000] fib(17) = 2584
[ 0.000] fib(18) = 4181
[ 0.000] fib(19) = 6765
[ 0.000] fib(20) = 10946
[ 0.000] fib(21) = 17711
[ 0.000] fib(22) = 28657
[ 0.000] fib(23) = 46368
[ 0.000] fib(24) = 75025
[ 0.000] fib(25) = 121393
[ 0.001] fib(26) = 196418
[ 0.001] fib(27) = 317811
[ 0.001] fib(28) = 514229
[ 0.002] fib(29) = 832040
[ 0.002] fib(30) = 1346269
[ 0.004] fib(31) = 2178309
[ 0.006] fib(32) = 3524578
[ 0.009] fib(33) = 5702887
[ 0.016] fib(34) = 9227465
[ 0.035] fib(35) = 14930352
[ 0.042] fib(36) = 24157817
[ 0.066] fib(37) = 39088169
[ 0.119] fib(38) = 63245986
[ 0.181] fib(39) = 102334155
[ 0.289] fib(40) = 165580141
[ 0.476] fib(41) = 267914296
[ 0.773] fib(42) = 433494437
En passant, j'ai mesuré le résultat de fib (42)
avec Ruby, Python, PyPy, PHP, HHVM, Kinx, Kinx (natif) et les ai comparés. Puisque la version de la bibliothèque JIT ne mesure que le temps de run ()
ci-dessus, tout, y compris l'interprétation de script et la génération de code JIT, est calculé équitablement par le temps utilisateur de l'ensemble du processus.
Il se présente comme suit lorsqu'il est organisé par ordre de vitesse. Après tout, il est remarquablement rapide lorsque le code natif est sorti directement par JIT. C'est une belle erreur de calcul que Kinx (natif) était plus rapide que PyPy. Combien coûte HHVM. Ruby est plus rapide dans le script. Je suis profondément ému quand je connais l'ère 1.8.
Langue | Numéro de version | Temps utilisateur |
---|---|---|
Kinx(Jit-Lib) | 0.10.0 | 0.828 |
HHVM | 3.21.0 | 2.227 |
Kinx(native) | 0.10.0 | 2.250 |
PyPy | 5.10.0 | 3.313 |
PHP | 7.2.24 | 11.422 |
Ruby | 2.5.1p57 | 14.877 |
Kinx | 0.10.0 | 27.478 |
Python | 2.7.15+ | 41.125 |
Cliquez ici pour la liste d'assemblage générée par la bibliothèque JIT précédente. C'est différent entre Windows et Linux, mais cette fois c'est Linux.
0: 53 push rbx
1: 41 57 push r15
3: 41 56 push r14
5: 48 8b df mov rbx, rdi
8: 4c 8b fe mov r15, rsi
b: 4c 8b f2 mov r14, rdx
e: 48 83 ec 10 sub rsp, 0x10
12: 48 83 fb 03 cmp rbx, 0x3
16: 73 0d jae 0x25
18: 48 89 d8 mov rax, rbx
1b: 48 83 c4 10 add rsp, 0x10
1f: 41 5e pop r14
21: 41 5f pop r15
23: 5b pop rbx
24: c3 ret
25: 48 8d 43 fe lea rax, [rbx-0x2]
29: 48 89 fa mov rdx, rdi
2c: 48 89 c7 mov rdi, rax
2f: e8 cc ff ff ff call 0x0
34: 49 89 c7 mov r15, rax
37: 48 8d 43 ff lea rax, [rbx-0x1]
3b: 48 89 fa mov rdx, rdi
3e: 48 89 c7 mov rdi, rax
41: e8 ba ff ff ff call 0x0
46: 49 03 c7 add rax, r15
49: 48 83 c4 10 add rsp, 0x10
4d: 41 5e pop r14
4f: 41 5f pop r15
51: 5b pop rbx
52: c3 ret
Comme exemple de Const, si vous osez l'écrire, cela ressemble à ceci. Je crée une table de saut pour les variables locales, donc je ne suis pas douée pour recréer la table à chaque fois. Il semble que cela sera résolu si vous préparez une interface séparée qui vous permet de créer uniquement une table et de passer l'adresse (peut-être).
var c = new Jit.Compiler();
c.enter();
c.mov(Jit.R1, Jit.IMM(-1));
var jump0 = c.slt(Jit.S0, Jit.IMM(0));
var jump1 = c.sgt(Jit.S0, Jit.IMM(3));
var const0 = c.makeConst(Jit.VAR(0));
var const1 = c.makeConst(Jit.VAR(1));
var const2 = c.makeConst(Jit.VAR(2));
var const3 = c.makeConst(Jit.VAR(3));
//L'adresse de la variable locale est acquise par l'offset du registre S0 (premier argument) et stockée dans le registre R0.
c.localp(Jit.R0, Jit.S0);
//Obtenez la valeur de la variable locale elle-même.
c.mov(Jit.R0, Jit.MEM1(Jit.R0));
//Sautez en considérant le contenu des variables locales comme des adresses.
c.ijmp(Jit.R0);
var l0 = c.label();
c.mov(Jit.R1, Jit.IMM(102));
c.ret(Jit.R1);
var l1 = c.label();
c.mov(Jit.R1, Jit.IMM(103));
c.ret(Jit.R1);
var l2 = c.label();
c.mov(Jit.R1, Jit.IMM(104));
c.ret(Jit.R1);
var l3 = c.label();
c.mov(Jit.R1, Jit.IMM(105));
var l4 = c.label();
c.ret(Jit.R1);
//L'adresse de saut est définie avant la génération du code.
jump0.setLabel(l4);
jump1.setLabel(l4);
var code = c.generate();
//La valeur const est définie après la génération du code.
const0.setLabel(l0);
const1.setLabel(l1);
const2.setLabel(l2);
const3.setLabel(l3);
for (var i = -1; i < 5; ++i) {
var r = code.run(i);
System.println(r);
}
résultat.
-1
102
103
104
105
-1
La sortie du code ressemble à ceci. J'ai essayé ceci sur la version Windows.
0: 53 push rbx
1: 56 push rsi
2: 57 push rdi
3: 48 8b d9 mov rbx, rcx
6: 48 8b f2 mov rsi, rdx
9: 49 8b f8 mov rdi, r8
c: 4c 8b 4c 24 b0 mov r9, [rsp-0x50]
11: 48 83 ec 50 sub rsp, 0x50
15: 48 c7 c2 ff ff ff ff mov rdx, 0xffffffffffffffff
1c: 48 83 fb 00 cmp rbx, 0x0
20: 0f 8c 94 00 00 00 jl 0xba
26: 48 83 fb 03 cmp rbx, 0x3
2a: 0f 8f 8a 00 00 00 jg 0xba
30: 49 b9 95 ff 57 61 89 01 00 00 mov r9, 0x1896157ff95
3a: 4c 89 4c 24 20 mov [rsp+0x20], r9
3f: 49 b9 a7 ff 57 61 89 01 00 00 mov r9, 0x1896157ffa7
49: 4c 89 4c 24 28 mov [rsp+0x28], r9
4e: 49 b9 b9 ff 57 61 89 01 00 00 mov r9, 0x1896157ffb9
58: 4c 89 4c 24 30 mov [rsp+0x30], r9
5d: 49 b9 cb ff 57 61 89 01 00 00 mov r9, 0x1896157ffcb
67: 4c 89 4c 24 38 mov [rsp+0x38], r9
6c: 48 8d 44 24 20 lea rax, [rsp+0x20]
71: 48 6b db 08 imul rbx, rbx, 0x8
75: 48 03 c3 add rax, rbx
78: 48 8b 00 mov rax, [rax]
7b: ff e0 jmp rax
7d: 48 c7 c2 66 00 00 00 mov rdx, 0x66
84: 48 89 d0 mov rax, rdx
87: 48 83 c4 50 add rsp, 0x50
8b: 5f pop rdi
8c: 5e pop rsi
8d: 5b pop rbx
8e: c3 ret
8f: 48 c7 c2 67 00 00 00 mov rdx, 0x67
96: 48 89 d0 mov rax, rdx
99: 48 83 c4 50 add rsp, 0x50
9d: 5f pop rdi
9e: 5e pop rsi
9f: 5b pop rbx
a0: c3 ret
a1: 48 c7 c2 68 00 00 00 mov rdx, 0x68
a8: 48 89 d0 mov rax, rdx
ab: 48 83 c4 50 add rsp, 0x50
af: 5f pop rdi
b0: 5e pop rsi
b1: 5b pop rbx
b2: c3 ret
b3: 48 c7 c2 69 00 00 00 mov rdx, 0x69
ba: 48 89 d0 mov rax, rdx
bd: 48 83 c4 50 add rsp, 0x50
c1: 5f pop rdi
c2: 5e pop rsi
c3: 5b pop rbx
c4: c3 ret
Le point est «jmp rax» à la ligne 7b. Si la table peut être définie de manière statique, elle fonctionnera comme une table de saut (il n'y a pas de moyen facile de le faire maintenant ...).
C'est un peu ennuyeux, mais si vous voulez passer 4 arguments ou plus, stockez la valeur dans la zone des variables locales et passez l'adresse (pointeur) comme argument. Dans l'exemple suivant, la fonction hook permettant de définir l'argument de la zone de variable locale est d'abord transmise. Au fait, puisque toutes les variables locales sont allouées sur 8 octets, notez que le décalage lors de l'accès direct avec Jit.MEM1 ()
etc. doit être un multiple de 8.
var c = new Jit.Compiler();
var entry1 = c.enter();
c.mov(Jit.VAR(0), Jit.S0);
c.mov(Jit.VAR(1), Jit.IMM(3));
c.mov(Jit.VAR(2), Jit.IMM(2));
c.mov(Jit.VAR(3), Jit.IMM(1));
c.localp(Jit.R0);
var call1 = c.call();
c.ret(Jit.R0);
var entry2 = c.enter();
c.mov(Jit.R1, Jit.S0);
c.mov(Jit.S0, Jit.MEM1(Jit.R1, 0));
var jump0 = c.ge(Jit.S0, Jit.MEM1(Jit.R1, 8));
c.ret(Jit.S0);
var l1 = c.label();
c.sub(Jit.R3, Jit.S0, Jit.MEM1(Jit.R1, 16));
c.mov(Jit.VAR(0), Jit.R3);
c.mov(Jit.VAR(1), Jit.IMM(3));
c.mov(Jit.VAR(2), Jit.IMM(2));
c.mov(Jit.VAR(3), Jit.IMM(1));
c.localp(Jit.R0);
c.call(entry2);
c.mov(Jit.S1, Jit.R0);
c.sub(Jit.R3, Jit.S0, Jit.MEM1(Jit.R1, 24));
c.mov(Jit.VAR(0), Jit.R3);
c.mov(Jit.VAR(1), Jit.IMM(3));
c.mov(Jit.VAR(2), Jit.IMM(2));
c.mov(Jit.VAR(3), Jit.IMM(1));
c.localp(Jit.R0);
c.call(entry2);
c.add(Jit.R0, Jit.R0, Jit.S1);
c.ret(Jit.R0);
jump0.setLabel(l1);
call1.setLabel(entry2);
var code = c.generate();
for (var i = 1; i <= 42; ++i) {
var tmr = new SystemTimer();
var r = code.run(i);
System.println("[%8.3f] fib(%2d) = %d" % tmr.elapsed() % i % r);
}
La sortie est la même qu'avant.
Je n'ai pas présenté Double, donc ça aussi. Allons-y aussi avec Fibonacci. Mais j'adore Fibonacci. Je ne l'ai pas remarqué. C'est une version à 0,1 étape.
var c = new Jit.Compiler();
var entry1 = c.enter(Jit.ArgType.FP);
c.mov(Jit.FR0, Jit.IMM(0.3));
var jump0 = c.ge(Jit.FS0, Jit.FR0);
c.ret(Jit.FS0);
var l1 = c.label();
c.mov(Jit.FR0, Jit.IMM(0.2));
c.sub(Jit.FR0, Jit.FS0, Jit.FR0);
c.call(entry1);
c.mov(Jit.FS1, Jit.FR0);
c.mov(Jit.FR0, Jit.IMM(0.1));
c.sub(Jit.FR0, Jit.FS0, Jit.FR0);
c.call(entry1);
c.add(Jit.FR0, Jit.FR0, Jit.FS1);
c.ret(Jit.FR0);
jump0.setLabel(l1);
var code = c.generate();
for (var i = 0.1; i < 3.5; i += 0.1) {
var tmr = new SystemTimer();
var r = code.frun(i);
System.println("[%8.3f] fib(%3.1f) = %.1f" % tmr.elapsed() % i % r);
}
Puisque la valeur immédiate du nombre à virgule flottante n'est pas rendue disponible dans la méthode de comparaison directe (cela devrait être fait), elle doit être temporairement stockée dans le registre et utilisée.
Vous pouvez recevoir une valeur Double en faisant frun ()
. Le résultat est le suivant.
[ 0.000] fib(0.1) = 0.1
[ 0.000] fib(0.2) = 0.2
[ 0.000] fib(0.3) = 0.3
[ 0.000] fib(0.4) = 0.5
[ 0.000] fib(0.5) = 0.8
[ 0.000] fib(0.6) = 1.3
[ 0.000] fib(0.7) = 2.1
[ 0.000] fib(0.8) = 3.4
[ 0.000] fib(0.9) = 5.5
[ 0.000] fib(1.0) = 8.9
[ 0.000] fib(1.1) = 14.4
[ 0.000] fib(1.2) = 23.3
[ 0.000] fib(1.3) = 37.7
[ 0.000] fib(1.4) = 61.0
[ 0.000] fib(1.5) = 98.7
[ 0.000] fib(1.6) = 159.7
[ 0.000] fib(1.7) = 258.4
[ 0.000] fib(1.8) = 418.1
[ 0.000] fib(1.9) = 676.5
[ 0.000] fib(2.0) = 1094.6
[ 0.000] fib(2.1) = 1771.1
[ 0.000] fib(2.2) = 2865.7
[ 0.000] fib(2.3) = 4636.8
[ 0.000] fib(2.4) = 7502.5
[ 0.000] fib(2.5) = 12139.3
[ 0.001] fib(2.6) = 19641.8
[ 0.001] fib(2.7) = 31781.1
[ 0.002] fib(2.8) = 51422.9
[ 0.003] fib(2.9) = 83204.0
[ 0.004] fib(3.0) = 134626.9
[ 0.006] fib(3.1) = 217830.9
[ 0.015] fib(3.2) = 352457.8
[ 0.020] fib(3.3) = 570288.7
[ 0.027] fib(3.4) = 922746.5
Le code de sortie est le suivant. Ceci est également la version Windows. Il existe une simple fonction de crochet pour passer d'abord un nombre à virgule flottante. SLJIT ne peut pas spécifier un nombre à virgule flottante dans l'argument au point d'entrée de la fonction, donc il est évité de cette manière.
Dans ce sens également, il est préférable d'utiliser celui-ci plutôt que d'utiliser directement SLJIT. Parce que la taille requise est automatiquement calculée dans la zone de variable locale, et le nombre requis de codes de stockage temporaire pour les registres non destructifs est également calculé automatiquement.
0: 53 push rbx
1: 56 push rsi
2: 57 push rdi
3: 48 8b d9 mov rbx, rcx
6: 48 8b f2 mov rsi, rdx
9: 49 8b f8 mov rdi, r8
c: 4c 8b 4c 24 d0 mov r9, [rsp-0x30]
11: 48 83 ec 30 sub rsp, 0x30
15: 0f 29 74 24 20 movaps [rsp+0x20], xmm6
1a: f2 0f 10 03 movsd xmm0, qword [rbx]
1e: 48 89 f2 mov rdx, rsi
21: 49 89 f8 mov r8, rdi
24: 48 89 c1 mov rcx, rax
27: e8 0d 00 00 00 call 0x39
2c: 0f 28 74 24 20 movaps xmm6, [rsp+0x20]
31: 48 83 c4 30 add rsp, 0x30
35: 5f pop rdi
36: 5e pop rsi
37: 5b pop rbx
38: c3 ret
39: 53 push rbx
3a: 56 push rsi
3b: 57 push rdi
3c: 48 8b d9 mov rbx, rcx
3f: 48 8b f2 mov rsi, rdx
42: 49 8b f8 mov rdi, r8
45: 4c 8b 4c 24 b0 mov r9, [rsp-0x50]
4a: 48 83 ec 50 sub rsp, 0x50
4e: 0f 29 74 24 20 movaps [rsp+0x20], xmm6
53: f2 0f 11 6c 24 38 movsd [rsp+0x38], xmm5
59: f2 0f 10 f0 movsd xmm6, xmm0
5d: 49 b9 33 33 33 33 33 33 d3 3f mov r9, 0x3fd3333333333333
67: 4c 89 4c 24 40 mov [rsp+0x40], r9
6c: f2 0f 10 44 24 40 movsd xmm0, qword [rsp+0x40]
72: 66 0f 2e f0 ucomisd xmm6, xmm0
76: 73 17 jae 0x8f
78: f2 0f 10 c6 movsd xmm0, xmm6
7c: f2 0f 10 6c 24 38 movsd xmm5, qword [rsp+0x38]
82: 0f 28 74 24 20 movaps xmm6, [rsp+0x20]
87: 48 83 c4 50 add rsp, 0x50
8b: 5f pop rdi
8c: 5e pop rsi
8d: 5b pop rbx
8e: c3 ret
8f: 49 b9 9a 99 99 99 99 99 c9 3f mov r9, 0x3fc999999999999a
99: 4c 89 4c 24 40 mov [rsp+0x40], r9
9e: f2 0f 10 44 24 40 movsd xmm0, qword [rsp+0x40]
a4: f2 0f 10 e6 movsd xmm4, xmm6
a8: f2 0f 5c e0 subsd xmm4, xmm0
ac: f2 0f 11 e0 movsd xmm0, xmm4
b0: 48 89 c1 mov rcx, rax
b3: e8 81 ff ff ff call 0x39
b8: f2 0f 10 e8 movsd xmm5, xmm0
bc: 49 b9 9a 99 99 99 99 99 b9 3f mov r9, 0x3fb999999999999a
c6: 4c 89 4c 24 40 mov [rsp+0x40], r9
cb: f2 0f 10 44 24 40 movsd xmm0, qword [rsp+0x40]
d1: f2 0f 10 e6 movsd xmm4, xmm6
d5: f2 0f 5c e0 subsd xmm4, xmm0
d9: f2 0f 11 e0 movsd xmm0, xmm4
dd: 48 89 c1 mov rcx, rax
e0: e8 54 ff ff ff call 0x39
e5: f2 0f 58 c5 addsd xmm0, xmm5
e9: f2 0f 10 6c 24 38 movsd xmm5, qword [rsp+0x38]
ef: 0f 28 74 24 20 movaps xmm6, [rsp+0x20]
f4: 48 83 c4 50 add rsp, 0x50
f8: 5f pop rdi
f9: 5e pop rsi
fa: 5b pop rbx
fb: c3 ret
JIT est intéressant. Si vous l'implémentez et le combinez avec un combinateur d'analyseur, vous pouvez créer un petit système de traitement du langage avec JIT. Peut-être que vous pouvez viser un tel chemin.
Il y a peut-être deux utilisations possibles:
à plus.