[RUBY]


Ce thème Il s'agit d'un article dont l'auteur lira l'implémentation de CRuby. C'est la troisième fois. Je suis occupé avec le travail aujourd'hui (je suis arrivé chez moi à 23h30 ...) et mon poste a été retardé. La dernière fois était relativement lourde, et je vais aller dans un endroit où le contenu peut être allégé. Alors, lisons le rb_bug qui est utilisé pour cracher les journaux d'erreurs que nous voyons souvent. Le but est de lire "Comment obtenir le nombre de lignes dans le fichier source lorsqu'une erreur est générée?"

rb_bug

rb_bug est défini dans error.c.

void
rb_bug(const char *fmt, ...)
{
    const char *file = NULL;
    int line = 0;

    if (GET_THREAD()) {
	file = rb_sourcefile();
	line = rb_sourceline();
    }

    report_bug(file, line, fmt, NULL);

    die();
}

C'est un code très simple. J'ai lu GET_THREAD () la dernière fois, mais c'était pour obtenir l'objet thread actuel.

  1. Acquérir l'objet thread actuel
  2. Récupérez le fichier source en cours d'exécution et son nombre de lignes,
  3. Sortie avec la fonction report_bug
  4. Terminer de force le système de traitement (die ())

Vous pouvez le lire comme s'il s'agissait du code.

Il y a deux questions principales.

  1. Comment rb_sourcefile () et rb_sourceline () obtiennent le nombre de lignes exécutées? ―― Pourquoi l'objet thread qui a été GET_THREAD () n'est-il pas passé en argument?
  2. En quoi report_bug est-il différent de fprintf (stderr, ...

Ce dernier n'a rien à voir avec cet objectif, je vais donc le lire bientôt et le mettre en attente.

Lisons le premier.

rb_sourcefile () et rb_sourceline ()

Les définitions de rb_sourcefile () et rb_sourceline () se trouvaient dans vm.c.

const char *
rb_sourcefile(void)
{
    rb_thread_t *th = GET_THREAD();
    rb_control_frame_t *cfp = rb_vm_get_ruby_level_next_cfp(th, th->cfp);

    if (cfp) {
	return RSTRING_PTR(cfp->iseq->location.path);
    }
    else {
	return 0;
    }
}

int
rb_sourceline(void)
{
    rb_thread_t *th = GET_THREAD();
    rb_control_frame_t *cfp = rb_vm_get_ruby_level_next_cfp(th, th->cfp);

    if (cfp) {
	return rb_vm_get_sourceline(cfp);
    }
    else {
	return 0;
    }
}

Pour le moment, ce qui est notable, c'est que les deux sont GET_THREAD () au début. Il semble que la raison pour laquelle je n'ai pas réussi GET_THREAD () était parce que j'ai fini par le réacquérir à l'intérieur. Était-ce un jugement selon lequel ce n'est pas du bon sens de rb_sourcefile () pour autre chose que le thread actuel?

Et encore une fois, les deux par rb_vm_get_ruby_level_next_cfp () Je reçois le pointeur de rb_control_frame_t comme cfp.

Et si cfp existe, il semble obtenir le nom de fichier réel et le nombre de lignes de cette structure. A partir du nom rb_control_frame_t, nous pouvons en quelque sorte comprendre que cfp est une structure avec des informations de contexte d'exécution. Cette construction d'informations contextuelles ne sera pas comprise sans la lecture de l'ensemble de la VM Pour le moment, je peux imaginer que le contexte d'exécution contient des informations permettant de calculer le fichier d'exécution et la ligne d'exécution, alors sautez-le sans le lire.

En fait, le côté rb_sourcefile () semble lire ʻiseq-> location.path dans la structure et le renvoyer comme une chaîne Ruby. Par conséquent, j'ai trouvé que ʻiseq-> location.path est conservé en tant que fichier en cours d'exécution dans la VM, et en acquérant cela, le message d'erreur peut afficher le chemin de fichier approprié.

Donc, le reste est rb_vm_get_sourceline () qui est utilisé pour extraire les informations de ligne de cfp du côté rb_sourceline () ...!

rb_vm_get_sourceline()

Cette définition existe dans vm_backtrace.c.

inline static int
calc_lineno(const rb_iseq_t *iseq, const VALUE *pc)
{
    return rb_iseq_line_no(iseq, pc - iseq->iseq_encoded);
}

int
rb_vm_get_sourceline(const rb_control_frame_t *cfp)
{
    int lineno = 0;
    const rb_iseq_t *iseq = cfp->iseq;

    if (RUBY_VM_NORMAL_ISEQ_P(iseq)) {
	lineno = calc_lineno(cfp->iseq, cfp->pc);
    }
    return lineno;
}

RUBY_VM_NORMAL_ISEQ_P est dans vm_core.h, Eh bien, je l'ai sauté car il peut y avoir des conditions d'échec d'acquisition.

calc_lineno () soustrait ʻiseq-> iseq_encoded du pointeur de cfp-> pc`.

Tout d'abord, quand vous dites ʻiseq dans la source de la VM, cela signifie probablement la séquence d'instructions. Et quand vous regardez comment utiliser ʻiseq_encoded

#define REG_PC  (REG_CFP->pc)
#define GET_PC_COUNT()     (REG_PC - GET_ISEQ()->iseq_encoded)

J'ai aussi trouvé quelque chose comme.

En soustrayant ʻiseq-> iseq_encoded du pointeur de pc, il devient PC_COUNT. pc est la position d'exécution réelle dans l'espace mémoire, et ʻiseq-> iseq_encoded peut être considéré comme le pointeur de départ du tableau contenant les instructions.

Et il volera de plus en plus profondément, Cette fonction est également différée à rb_iseq_line_no (). Il y a une définition dans iseq.c.

unsigned int
rb_iseq_line_no(const rb_iseq_t *iseq, size_t pos)
{
    if (pos == 0) {
	return find_line_no(iseq, pos);
    }
    else {
	return find_line_no(iseq, pos - 1);
    }
}

Si pos est 0, c'est comme ça, sinon find_line_no () pour l'instruction à la position précédente. Cela tente probablement de trouver la ligne où l'erreur s'est réellement produite en déplaçant la position d'exécution d'un endroit.

Alors find_line_no () est comme suit.

static unsigned int
find_line_no(const rb_iseq_t *iseq, size_t pos)
{
    struct iseq_line_info_entry *entry = get_line_info(iseq, pos);
    if (entry) {
	return entry->line_no;
    }
    else {
	return 0;
    }
}

Si ʻiseq_line_info_entryest trouvé, on peut lire que leline_no écrit ici est la ligne d'exécution. La définition de ʻiseq_line_info_entry était la suivante.

struct iseq_line_info_entry {
    unsigned int position;
    unsigned int line_no;
};

C'est une forme simple de «position» et de «line_no». N'est-ce pas une structure qui exprime à quelle ligne correspond une certaine position d'instruction? C'est considéré.

Alors get_line_info () est ...

static struct iseq_line_info_entry *
get_line_info(const rb_iseq_t *iseq, size_t pos)
{
<Omission>
  :
    return &table[i-1];
}

Je suis enfin arrivé. Cela semble être la véritable identité. En regardant d'en haut ...

<Omission>
  :
    size_t i = 0, size = iseq->line_info_size;
    struct iseq_line_info_entry *table = iseq->line_info_table;
  :
<Omission>

Tout d'abord, nous extrayons line_info_table de ʻiseq vers table. Puisque cette table est un pointeur vers ʻiseq_line_info_entry, Vous pouvez voir qu'il s'agit d'un tableau qui répertorie la correspondance entre le mot de commande et le nombre de lignes dans le fichier d'origine du mot de commande.

Continuez à lire get_line_info ().

<Omission>
  :
    const int debug = 0;

    if (debug) {
	printf("size: %"PRIdSIZE"\n", size);
	printf("table[%"PRIdSIZE"]: position: %d, line: %d, pos: %"PRIdSIZE"\n",
	       i, table[i].position, table[i].line_no, pos);
    }
  :
<Omission>

Utilitaire de débogage …… Il semble qu'il affiche la taille de la table. Je vais sauter.

<Omission>
  :
    if (size == 0) {
	return 0;
    }
    else if (size == 1) {
	return &table[0];
    }
    else <Ce qui suit est omis>

Il y avait un gros si, mais les deux premières conditions. Si la taille de table est 0, 0 (= NULL) est retourné, et s'il est 1, le pointeur vers le premier élément est retourné tel quel. (Au fait, ce 0 ne devrait-il pas être changé en une constante NULL?)

Le corps principal est ailleurs.

<Omission>
  :
	for (i=1; i<size; i++) {
	    if (debug) printf("table[%"PRIdSIZE"]: position: %d, line: %d, pos: %"PRIdSIZE"\n",
			      i, table[i].position, table[i].line_no, pos);

	    if (table[i].position == pos) {
		return &table[i];
	    }
	    if (table[i].position > pos) {
		return &table[i-1];
	    }
	}
    }
    return &table[i-1];
}   // get_line_info()Jusque là

Il parcourt la «table» pour trouver une position où la valeur de «position» dépasse la «pos» donnée dans l'argument et renvoie un pointeur vers cette position. En d'autres termes, c'est une recherche linéaire.

Cependant, si vous allez à une position qui dépasse la position dans l'ordre depuis l'avant, elle y sera renvoyée, donc «position» devrait augmenter de façon monotone. Cela signifie que ʻiseq_line_info_entry indique apparemment que la positione instruction de ʻiseq est après line_pos. Aussi, je pense que vous pouvez lire que «table» est un tableau trié par «position».

Hmm ···? L'endroit est-il le plus utilisé pour le débogage? Dans ce cas, la bonne réponse est d'utiliser un algorithme simple.

De plus, s'il ne peut pas être trouvé jusqu'à la fin, il semble en renvoyer un avant ʻi` en dehors de la clause else. À ce stade, «i» doit correspondre à «size», ce qui signifie qu'il renvoie le dernier élément.

Vous savez maintenant comment obtenir la ligne d'exécution.

Résumé

Voici comment rb_bug (), qui est utilisé pour générer le message d'erreur, obtient le fichier et la ligne en cours d'exécution:

--Fichier d'exécution

au fait

Le tableau semblait trié, mais pourquoi ne pas utiliser la recherche binaire, etc.? Quand j'y ai pensé, les commentaires suivants sont restés dans la source.

/* TODO: search algorithm is brute force.
         this should be binary search or so. */

Selon les commentaires, il vaut mieux passer à un plus efficace. Compte tenu de l'emplacement, je pense qu'il serait préférable de le garder aussi simple que maintenant. Comme c'est un gros problème, je pense que je vais essayer Pull Request plus tard.

Autre

Cette fois, j'ai senti qu'il y avait beaucoup de spéculations. Cependant, je pense que j'ai acquis pas un peu de connaissances parmi eux. En lisant lentement, j'augmenterai progressivement mes connaissances et élargirai le champ de compréhension.

Recommended Posts