Nous connaissons la convention d'appel selon laquelle "les six premiers arguments entiers ou pointeurs sont passés dans les registres RDI, RSI, RDX, RCX (R10 dans l'interface du noyau Linux [17]: 124), R8 et R9" pour le code c / c ++ dans la plate-forme Linux basée sur l'article suivant. https://en.wikipedia.org/wiki/X86_calling_conventions#x86-64_calling_conventions

Cependant, quelle est la convention d'appel pour le code Java sur la plate-forme Linux (supposons que la JVM soit hotspot)? Ce qui suit est un exemple, qu'est-ce que les registres stockent les quatre paramètres?

protected void caller( ) {
callee(1,"123", 123,1)
}

protected void callee(int a,String b, Integer c,Object d) {

}
5
YuFeng Shen 17 janv. 2017 à 12:39

2 réponses

Meilleure réponse

Il n'est pas précisé comment JVM appelle les méthodes Java en interne. Diverses implémentations JVM peuvent suivre différentes conventions d'appel. Voici comment cela fonctionne dans HotSpot JVM sur Linux x64 .

  • Une méthode Java peut s'exécuter dans l'interpréteur ou elle peut être compilée JIT.
  • Le code interprété et compilé utilise des conventions d'appel différentes.

1. Saisie de la méthode d'interprétation

Chaque méthode Java a un point d'entrée dans l'interpréteur. Cette entrée est utilisée pour passer d'une méthode interprétée à une autre méthode interprétée.

  • Tous les arguments sont passés sur pile, de bas en haut.
  • rbx contient un pointeur vers la structure Method* - métadonnées internes d'un méthode appelée.
  • r13 contient sender_sp - pointeur de pile d'une méthode appelante. Il peut différer de rsp + 8 si l'adaptateur c2i est utilisé (voir ci-dessous).

Plus de détails sur les entrées d'interpréteur dans le code source HotSpot: templateInterpreter_x86_64.cpp.

2. Entrée compilée

Une méthode compilée a son propre point d'entrée. Le code compilé appelle les méthodes compilées via cette entrée.

  • Jusqu'à 6 premiers arguments entiers sont passés dans les registres: rsi, rdx, rcx, r8, r9, rdi. Les méthodes non statiques reçoivent la référence this comme premier argument dans rsi.
  • Jusqu'à 8 arguments en virgule flottante sont passés dans les registres xmm0 ... xmm7.
  • Tous les autres arguments sont passés sur la pile de haut en bas.

Cette convention est bien illustrée dans assembler_x86.hpp:

    |-------------------------------------------------------|
    | c_rarg0   c_rarg1  c_rarg2 c_rarg3 c_rarg4 c_rarg5    |
    |-------------------------------------------------------|
    | rcx       rdx      r8      r9      rdi*    rsi*       | windows (* not a c_rarg)
    | rdi       rsi      rdx     rcx     r8      r9         | solaris/linux
    |-------------------------------------------------------|
    | j_rarg5   j_rarg0  j_rarg1 j_rarg2 j_rarg3 j_rarg4    |
    |-------------------------------------------------------|

Vous remarquerez peut-être que la convention d'appel Java ressemble à la convention d'appel C mais décalée d'un argument vers la droite. Ceci est fait intentionnellement pour éviter un brassage supplémentaire des registres lors de l'appel des méthodes JNI (vous savez, les méthodes JNI ont un argument supplémentaire JNIEnv* ajouté aux paramètres de méthode).

3. Adaptateurs

Les méthodes Java peuvent avoir deux autres points d'entrée: les adaptateurs c2i et i2c. Ces adaptateurs sont des morceaux de code généré dynamiquement qui convertissent la convention d'appel compilée en disposition d'interpréteur et vice versa. Les points d'entrée с2i et i2c sont utilisés pour appeler respectivement la méthode interprétée à partir du code compilé et la méthode compilée à partir du code interprété.


P.S. La manière dont la JVM appelle les méthodes en interne n'a généralement pas d'importance, car ce ne sont que des détails d'implémentation opaques pour l'utilisateur final. De plus, ces détails peuvent changer même lors d'une mise à jour mineure du JDK. Cependant, je connais au moins un cas où la connaissance de la convention d'appel Java peut sembler utile - lors de l'analyse des vidages sur incident JVM.

11
apangin 17 janv. 2017 à 20:57

Il n’existe pas de règles spécifiques, car l’appelant et l’appelant sont tous deux sous le contrôle de la machine virtuelle Java.

Surtout lorsque vous considérez le cas où les deux méthodes ont été compilées en code natif, car cela est généralement déclenché lorsque ce chemin de code s'avère être un hotspot. Dans ce cas, il est très probable que le code de la méthode invoquée soit incorporé dans le code de l'appelant, permettant une transformation de code ultérieure qui le transformera en quelque chose qui ne ressemble presque jamais au code source que vous avez écrit. Au lieu de faire référence à des variables de paramètre, la version intégrée de la méthode appelée peut faire référence aux valeurs ou aux constantes à partir desquelles les arguments d'appel ont été dérivés à l'origine. (Cela s'applique particulièrement à votre exemple où tous les arguments sont des valeurs constantes)

Voir Formulaire d'affectation statique unique, Numérotation des valeurs globales et Constante conditionnelle éparse propagation pour plus de détails. L’affectation de variables aux registres n’a lieu qu’après toutes ces optimisations de niveau supérieur sur les variables restantes. Elle ne s’applique donc pas aux paramètres d’un schéma fixe, si leurs variables existent toujours.

Dans le cas où l'appel n'a pas été inséré, il existe plusieurs scénarios différents, chacun ayant probablement sa propre convention d'appel:

  • L'exécution interprétée de l'appelant entre l'exécution interprétée de l'appelé
  • L'exécution interprétée de l'appelant entre un appelé déjà compilé
  • Code natif n'ayant pas incorporé l'appelé entre l'exécution interprétée de l'appelé
  • Le code natif n'ayant pas incorporé l'appelé entre un appelé déjà compilé
0
Holger 17 janv. 2017 à 18:16