[JAVA] Bibliothèque de compilateur Kinx Library-JIT

Bibliothèque de compilateur Kinx Library-JIT

introduction

** "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.

Comment est-ce?

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 ()etrun (). Vous pouvez également voir la liste d'assemblage en faisant generate ()etdump ()`.

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.

Document explicatif officiel

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;

Objet Jit

L'objet Jit définit des méthodes pour les paramètres et les classes du compilateur.

Méthode pour les paramètres Jit

Il existe trois types de paramètres Jit: valeur immédiate, registre et accès mémoire. Il est utilisé sous la forme suivante.

Valeur immédiate, accès mémoire

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.
S'inscrire

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.R0Jit.R5 Registre à usage général. Utilisé temporairement. Il peut être supprimé après avoir appelé une autre fonction.
Jit.S0Jit.S5 Registre à usage général. Garantissez qu'il ne sera pas détruit après avoir appelé une autre fonction.
Jit.FR0Jit.FR5 Registre à virgule flottante. Utilisé temporairement. Il peut être supprimé après avoir appelé une autre fonction.
Jit.FS0Jit.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)

Compilateur Jit

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.dstIl 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.regEnregistrer dans.
Jit.Compiler#ret(val) Sortez le code de retour.valrends le.valEst un nombre à virgule flottanteFR0Inscrivez-vous, sinonR0Retourné au registre.
Jit.Compiler#f2i(dst, op1) double int64_Sortez le code à convertir en t.dstEst un registre à usage général.op1Est un registre à virgule flottante.
Jit.Compiler#i2f(dst, op1) int64_Code de sortie qui convertit t en double.dstEst un registre à virgule flottante.op1Est un registre à usage général.
Jit.Compiler#mov(dst, op1) dstÀop1Sortez le code à remplacer. La virgule flottante et les autres types sont automatiquement reconnus.
Jit.Compiler#neg(dst, op1) op1Le résultat de l'inversion de signe dedstSortez le code dans lequel stocker.
Jit.Compiler#clz(dst, op1) op1Comptez le nombre de bits qui sont 0 à partir du début dedstSortez le code dans lequel stocker.
Jit.Compiler#add(dst, op1, op2) op1Quandop2Le résultat de l'ajoutdstSortez le code dans lequel stocker.
Jit.Compiler#sub(dst, op1, op2) op1Quandop2Le résultat de la soustractiondstSortez le code dans lequel stocker.
Jit.Compiler#mul(dst, op1, op2) op1Quandop2Le résultat de la multiplication pardstSortez le code dans lequel stocker.
Jit.Compiler#div(dst, op1, op2) Nombres à virgule flottante uniquement,op1Quandop2Le résultat de la divisiondstSortez le code dans lequel stocker.
Jit.Compiler#div() La valeur divisée par le registre à usage général comme non signéR0Sortez le code à stocker dans le registre.
Jit.Compiler#sdiv() La valeur divisée par le registre à usage général signéR0Sortez le code à stocker dans le registre.
Jit.Compiler#divmod() La valeur divisée par le registre à usage général comme non signéR0Stocker dans un registre et laisser le resteR1Sortez le code à stocker dans le registre.
Jit.Compiler#sdivmod() La valeur divisée par le registre à usage général signéR0Stocker dans un registre et laisser le resteR1Sortez le code à stocker dans le registre.
Jit.Compiler#not(dst, op1) op1Le résultat de l'inversion de bits dedstSortez le code dans lequel stocker.
Jit.Compiler#and(dst, op1, op2) op1Quandop2Bit AND valeur avecdstSortez le code dans lequel stocker.
Jit.Compiler#or(dst, op1, op2) op1Quandop2Bit OR valeur dansdstSortez le code dans lequel stocker.
Jit.Compiler#xor(dst, op1, op2) op1Quandop2Bit XOR valeur dansdstSortez le code dans lequel stocker.
Jit.Compiler#shl(dst, op1, op2) op1Àop2La valeur décalée vers la gauche par le bitdstSortez le code dans lequel stocker.
Jit.Compiler#lshr(dst, op1, op2) op1Àop2La valeur est décalée logiquement vers la droite par le bitdstSortez le code dans lequel stocker.
Jit.Compiler#ashr(dst, op1, op2) op1Àop2Bits, valeur arithmétique décalée vers la droitedstSortez 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é.labelSi 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 jmpSortez la commande.labelSi est spécifié, il n'est pas nécessaire de le définir ultérieurement.
Jit.Compiler#ijmp(dst) JumpTarget jmpSortez la commande.dstEst un registre indiquant l'adresse ou une valeur immédiate.
Jit.Compiler#eq(op1, op2) JumpTarget op1 == op2Sortez 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 != op2Sortez 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 < op2Sortez 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 <= op2Sortez 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 > op2Sortez 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 >= op2Sortez 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 < op2Sortez 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 <= op2Sortez 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 > op2Sortez 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 >= op2Sortez 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.

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.

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.

échantillon

Séquence de Fibonacci (version récursive)

É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

Exemple de const

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 ...).

Exemple de 4 arguments ou plus

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.

Double argument et valeur de retour

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

en conclusion

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:

  1. Lors de la création d'une bibliothèque Kinx, faites-la JIT dans la plage de calcul numérique, etc., et accélérez-la.
  2. Héberge DSL (langage spécifique au domaine) et langage oléore et l'utilise pour la sortie backend.

à plus.

Recommended Posts

Bibliothèque de compilateur Kinx Library-JIT
Bibliothèque de compilateur Kinx Library-JIT (édition supplémentaire)
Bibliothèque Kinx - REPL
Bibliothèque Kinx --Obtenir
Bibliothèque Kinx --DateTime
Bibliothèque Kinx - Processus