[couverture de Linux Magazine 98]

La machine Parrot, deuxième partie

Article publié dans Linux Magazine 98, octobre 2007.

Copyright © 2007 Christian Aperghis-Tramoni

[+ del.icio.us] [+ Developers Zone] [+ Bookmarks.fr] [Digg this] [+ My Yahoo!]

Chapeau

Le premier article nous a permis de montrer combien il était facile de développer en assembleur Parrot. Nous allons dans cette seconde partie aborder les structures de données et l'accès aux fichiers. Plusieurs programmes nous permettrons de mettre en application les sujets traités.

Les ruptures de séquence

Les ruptures inconditionnelles

Avant d'aborder le problème des ruptures de séquence en assembleur, je ne saurais que recommander la lecture de l'article de Philippe Bruhat et Jean Forget consacré à l'histoire de l'instruction goto[goto].

Lorsqu'on programme en langage d'assemblage, la rupture dans le séquencement d'une suite d'instructions est inévitable. Cette affirmation n'interdit pas de tenter d'en minimiser le nombre et la portée afin de conserver au programme une certaine lisibilité.

Les opérations de rupture de séquence, qu'elles soient conditionnelles ou inconditionnelles, font référence à une adresse symbolique, toujours représentée par une étiquette.

Voici un exemple à ne surtout pas suivre, mais qui permet d'illustrer le fonctionnement de l'instruction de rupture inconditionnelle de séquence branch.

  coruscant:~/Langages/asmparrot chris$ cat rupt.pasm
   branch   E1
  E2:
   print    "Et puis par la.\n"
   branch   FIN
  E1:
   print    "Je passe par ici.\n"
   branch   E2
  FIN:
   print    "Et enfin, je termine.\n"
   end
  coruscant:~/Langages/asmparrot chris$ parrot rupt.pasm
  Je passe par ici.
  Et puis par la.
  Et enfin, je termine.
  coruscant:~/Langages/asmparrot chris$

Les ruptures conditionnelles

Il existe deux familles de rupture conditionnelle de séquence. Comme son nom l'indique, la réalisation du saut sera soumise à une condition. Dans le cas de l'instruction de code opération if, il est conditionné au test (vrai ou faux) du contenu d'un registre. Une évaluation à vrai (non undef) contraindra le programme à réaliser le saut demandé, alors qu'avec un résultat de type faux (undef), le programme continuera à s'exécuter en séquence. À noter qu'il existe aussi une instruction unless qui, au lieu de tester la condition à vrai, la teste à faux. En voici l'illustration sur un exemple :

  coruscant:~/Langages/asmparrot chris$ cat exo.pasm
   getstdin   P0
   getstdout  P1
   print      P1, "Quel est le diviseur ?\n"
   readline   S1, P0
   set        I1, S1
   print      P1, "Quel est le dividende ?\n"
   readline   S1, P0
   set        I2, S1
   mod        I3, I2, I1
   if         I3, RESTE
   print      P1, I1
   print      P1, " est un diviseur entier de "
   print      P1, I2 
   branch     FIN
  RESTE:
   print      P1, I1
   print      P1, " divise "
   print      P1, I2
   print      P1, " avec un reste egal a "
   print      P1, I3
  FIN:
   print      P1, ".\n"
   end
  coruscant:~/Langages/asmparrot chris$ parrot exo.pasm
  Quel est le diviseur ?
  5
  Quel est le dividende ?
  12
  5 divise 12 avec un reste egal a 2.
  coruscant:~/Langages/asmparrot chris$ parrot exo.pasm
  Quel est le diviseur ?
  5
  Quel est le dividende ?
  25
  5 est un diviseur entier de 25.
  coruscant:~/Langages/asmparrot chris$

Lorsque le contenu doit être testée relativement à une valeur de référence, on dispose de six opérations logiques pour effectuer cette comparaison. Ce dont :

Le programme suivant permet de calculer les n premiers éléments de la suite de Fibonacci, le nombre de valeurs à calculer sera lu sur le clavier.

  coruscant:~/Langages/asmparrot chris$ cat fibo.pasm
   getstdin   P0
   getstdout  P1
   print      P1, "Combien de valeurs doit-on calculer ?\n"
   readline   S1, P0
   print      P1, "----------\n"
   set        I1, 1
   set        I2, S1
   set        I3, 1
   set        I4, 1
  BOUCLE:
   gt         I1, I2, FIN
   set        I5, I4
   add        I4, I3, I4
   set        I3, I5
   print      P1, "Fibonnaci("
   set        S1, I1
   concat     S0, "  ", S1
   substr     S1, S0, -2
   print      P1, S1
   print      P1, ") = "
   set        S1, I3
   concat     S0, "  ", S1
   substr     S1, S0, -2
   print      P1, S1
   print      P1, "\n"
   inc        I1
   branch     BOUCLE
  FIN:
   end
  coruscant:~/Langages/asmparrot chris$ parrot fibo.pasm
  Combien de valeurs doit-on calculer ?
  10
  ----------
  Fibonnaci( 1) =  1
  Fibonnaci( 2) =  2
  Fibonnaci( 3) =  3
  Fibonnaci( 4) =  5
  Fibonnaci( 5) =  8
  Fibonnaci( 6) = 13
  Fibonnaci( 7) = 21
  Fibonnaci( 8) = 34
  Fibonnaci( 9) = 55
  Fibonnaci(10) = 89
  coruscant:~/Langages/asmparrot chris$

C'est l'utilisation judicieuse des opérations conditionnelles et inconditionnelles qui va permettre de réaliser toutes les boucles ou tests dont nous avons l'habitude de nous servir lorsque nous programmons dans un langage de haut niveau. La boucle for qui, en Perl, s'écrit :

    for ($i=debut; $i<fin; $i++) {
        # corps de la boucle.
    }

Sera traduite en assembleur par la séquence d'instructions :

  set I0, debut
  FOR:
  # corps de la boucle
    inc I0
    lt I0, fin, FOR
  # suite du programme

De la même manière, l'écriture en Perl de la boucle while :

    while (variable cond valeur) {
        # corps de la boucle.
        # modification de la variable.
    }

Se présentera en Parrot sous la forme :

  WHILE: 
    set     I1, valeur_depart
    cond    I1, valeur, FIN
  # corps de la boucle
  # modification de I1
    branch  WHILE
  FIN:
    # suite du programme

Terminons sur l'instruction conditionnelle :

    if (VAL1 == VAL2) {
        # bloc vrai.
    } else {
        # bloc faux
    }

Dont la traduction en assembleur Parrot donne :

    set     I0, VAL1
    set     I1, VAL2
    ne      I0, I1, FAUX
  # bloc vrai
    branch  FIN_IF
  FAUX:
  # bloc faux
  FIN_IF:
  # suite du programme

Les agrégats

La liste statique

C'est l'utilisation des PMC appropriés qui va permettre la manipulation des structures (listes, hash). L'ensemble des PMC disponibles sont stockés dans le répertoire include/parrot/pmc.h et nous avons vu qu'on pouvait en obtenir la liste au moyen d'un programme. La documentation détaillée est disponible sur le site Parrot [pmc]. Ici, nous limiterons la présentation aux structures de base standard les plus souvent utilisées, listes statiques, listes dynamiques et hash. À terme, on devrait disposer d'une vaste palette de PMC permettant la gestion de plusieurs types d'agrégats couvrant plusieurs langages de programmation.

Commençons par un exemple qui met en évidence l'utilisation d'un agrégat statique, une table au sens classique du terme dont la longueur doit être spécifiée avant de pouvoir l'utiliser. Ce programme va créer une structure indexée et va stocker dans chaque emplacement i de l'agrégat la somme des i premiers nombres entiers.

  coruscant:~/Langages/asmparrot chris$ cat agregat1.pasm
      getstdin  P0
      getstdout P1
  # Declaration de la table.
      new       P3, .Array
      print     P1, "Nombre d'elements a calculer ?\n"
      readline  S1, P0
      set       I2, S1
  # Declaration de la longueur de la table. 
      set       P3, I2
      set       I0, 0
      set       I1, 0
  BOUCLE:
      eq        I0, I2, IMPRIME
      add       I1, I1, I0
      set       P3[I0], I1
      inc       I0
      branch    BOUCLE
  IMPRIME:
      set       I0, 0
      print     P1, "Longueur de la table : "
  # Recuperation de la longueur de la table.
      set       I3, P3
      print     P1, I2
      print     P1, "\n"
  CONT:
      eq        I0, I2, FIN
      print     P1, "Table("
      set       S1, I0
      concat    S0, "  ", S1
      substr    S1, S0, -2
      print     P1, S1
      print     P1, ") = "
      set       S1, P3[I0]
      concat    S0, "  ", S1
      substr    S1, S0, -2
      print     P1, S1
      print     P1, "\n"
      inc       I0
      branch    CONT
  FIN:
      end
  coruscant:~/Langages/asmparrot chris$ parrot agregat1.pasm
  Nombre d'elements a calculer ?
  12
  Longueur de la table : 12
  Table( 0) =  0
  Table( 1) =  1
  Table( 2) =  3
  Table( 3) =  6
  Table( 4) = 10
  Table( 5) = 15
  Table( 6) = 21
  Table( 7) = 28
  Table( 8) = 36
  Table( 9) = 45
  Table(10) = 55
  Table(11) = 66
  coruscant:~/Langages/asmparrot chris$

L'agrégat étant statique, ses bornes sont testées avant affectation. Une erreur dans l'indexation de la structure ainsi définie déclenchera une exception et le message d'erreur "Array index out of bounds!" sera imprimé.

La liste dynamique

Le même programme peut aussi être réalisé en utilisant une structure dynamique, une liste au sens Perl du terme, par exemple un agrégat de type .ResizableIntegerArray. Le programme est identique, il suffit de changer la déclaration du PMC de

  new   P3, .Array

en

  new   P3, .ResizableIntegerArray

Puis, si on le désire, de supprimer la ligne déclarant la longueur de la structure :

  set   P3, I2

Cette opération n'est pas indispensable, la gestion dynamique de l'agrégat n'imposant pas son dimensionnement préalable. Il est néanmoins possible d'utiliser cette possibilité. Si par exemple il est nécessaire de disposer à un instant donné d'une liste vide, il suffit de lui affecter la longueur zéro.

  set  P3, 0

Nous allons illustrer le comportement d'une telle structure, en écrivant le programme qui calcule les lignes successives du triangle de Pascal, programme particulièrement adapté à la gestion dynamique d'une liste. Rappelons très brièvement la méthode de calcul pour n lignes. Dans un dialecte classique, on réserverait une matrice n*n dans laquelle seuls un peu plus de la moitié des emplacements (n+1)/2n seraient utilisés. Un élément p[i,j] se calcule par rapport aux deux éléments de la ligne précédente p[i-1,j] + p[i-1,j-1]. Cet algorithme peut être écrit d'une manière élégante en Perl en n'utilisant une seule liste dont la longueur augmentera de 1 à chaque itération dans laquelle la ligne concernée sera générée, et un doublet dans lequel seront mémorisées les valeurs servant au calcul. En Perl, cet algorithme se programme comme suit :

    $l = @t;

    for ($j=1; $j<=$l; $j++) {
        @tp = ($tp[1], $t[$j]);
        $t[$j] = $tp[0] + $tp[1];
    }

La réécriture en Parrot va utiliser les deux types de structures, un PMC statique .Array pour mémoriser le doublet de valeurs et un PMC dynamique .ResizableIntegerArray pour générer les itérations successives.

  coruscant:~/Langages/asmparrot chris$ cat pascal.pasm
    # Le triangle de Pascal
       getstdin       P0
       getstdout      P1
    # Nombre de lignes a générer dans I0.
       set            I0, 10
    # P3, vecteur à deux éléments.
       new            P3, .Array
       set            P3, 2
    # P4, ligne courante du triangle.
       new            P4, .ResizableIntegerArray
       set            P4[0], ""
       set            P4[1], 1
    ITERATION:
    # Longueur courante du vecteur dans I2.
       set            I2, P4
       dec            I0
       lt             I0, 0, FIN
       inc            I1
    # Impression du vecteur.
       set            I31, 1
    CONTINUE:
    # Formatage des résultats sur 4 caractères.
       set            S1, P4[I31]
       concat         S1, "    ", S1
       substr         S1, S1, -4
       print          P1, S1
       inc            I31
       lt             I31, I2, CONTINUE
       print          P1, "\n"
    # Calculer la ligne suivante à partir de la ligne courante.
    # I31 : Indice d'exploration de vecteur.
       set            I31, 1
    CALCUL:
       set            I30, P3[1]
       set            P3[0], I30
       set            I30, P4[I31]
       set            P3[1], I30
       set            I30, P3[0]
       set            I29, P3[1]
       add            I30, I30, I29
       set            P4[I31], I30
    # Progression dans le vecteur.
       inc            I31
       le             I31, I2, CALCUL
       branch         ITERATION
    FIN:
       end
  coruscant:~/Langages/asmparrot chris$ parrot pascal.pasm
   1
   1   1
   1   2   1
   1   3   3   1
   1   4   6   4   1
   1   5  10  10   5   1
   1   6  15  20  15   6   1
   1   7  21  35  35  21   7   1
   1   8  28  56  70  56  28   8   1
   1   9  36  84 126 126  84  36   9   1
  coruscant:~/Langages/asmparrot chris$

Ce programme peut, à volonté, être modifié par exemple pour demander combien d'itérations doivent être calculées ou, plus intéressant, n'imprimer que celle correspondant à un rang donné. De plus, le formatage de la sortie sur 4 caractères, 3 chiffres plus une espace, qui ne permet pas la présentation des résultats au-delà de 12 lignes, peut lui aussi évoluer. Une modification facile à mettre en oeuvre consisterait à indiquer son numéro devant chacune des lignes en remarquant qu'il correspond au nombre d'éléments de la structure moins 1, le premier emplacement étant à Undef pour permettre de démarrer l'algorithme. La taille est récupérée dans un registre entier au moyen de l'instruction :

  set Ix, P4

Il ne reste plus qu'à lui retrancher un et à l'imprimer.

Le hash

Terminons ce chapitre sur les agrégats avec une structure de type hash, au sens Perl du terme, c'est à dire une structure de donnée dans laquelle chaque valeur est accessible par l'intermédiaire d'une clé. La déclaration de la structure se fait par l'intermédiaire du PMC .Hash.

  new Px, .Hash

L'écriture d'une valeur, explicite ou en provenance d'un registre (Rz), dans le hash défini par le PMC Px, à l'emplacement spécifié par une clé Sy qui peut elle aussi être un littéral ou une référence à un registre se fait classiquement au moyen de l'opération de chargement set.

  set Px[Sy], Rz

L'instruction exists permet de tester l'existence d'une clé d'entrée.

  exists Ix, Px["cle"]

Le registre entier concerné (Ix) sera positionné à vrai si la clé spécifiée existe dans le cas contraire il sera positionné à faux. Cette instruction teste l'existence de l'entrée, mais elle ne teste pas la valeur qui lui est associée. Le résultat du test sera vrai lorsque l'entrée relative à la clé a été définie, même si la valeur qui lui correspond est undef. Pour vérifier que la donnée correspondante est différente de undef, on dispose de l'instruction defined.

  defined Ix, Px["cle"]

Le registre Ix contiendra alors faux si Px["cle"] est undef, vrai dans le cas contraire.

Pour pouvoir travailler efficacement sur un hash, il est indispensable de disposer d'une méthode adaptée permettant la récupération de la liste des clés d'entrée. Pour concrétiser cette opération il est nécessaire de faire référence à un itérateur spécifique iterator.pasm dont le code se trouve stocké dans le répertoire /usr/local/lib/parrot/include et qui sera inclus dans le programme au moyen de l'instruction :

  .include "iterator.pasm"

Une requête à l'itérateur permet de créer dans un PMC Px, une liste qui va contenir l'ensemble des clés d'accès au hash défini par le PMC Py.

  new   Px, .Iterator, Py

Cet itérateur propose plusieurs méthodes d'accès : ITERATE_FROM_START, ITERATE_FROM_START_KEYS, ITERATE_GET_NEXT, ITERATE_GET_PREV, ITERATE_FROM_END.

Donnant le choix entre plusieurs méthodes différentes pour explorer la liste de clés ainsi construite. Dans le cas qui nous intéresse, nous choisissons la méthode .ITERATE_FROM_START qui, comme son nom l'indique, explore la liste en commençant par le début.

  set   P2, .ITERATE_FROM_START

Reprenons l'ensemble de ces opérations dans un programme.

  coruscant:~/Langages/asmparrot chris$ cat jardin.pasm
   .include "iterator.pasm"
   getstdin   P0
   getstdout  P1
  # Déclaration du hash.
   new        P3, .Hash
   print      P1, "Etat des plantations de mon jardin.\n"
  # Lecture des informations et construction du hash.
  JARDIN:
   print      P1, "Nom de la fleur ?\n"
   readline   S5, P0
   chopn      S5, 1
   eq         S5, "", TERMINE
   print      P1, "Nombre d'unites ?\n"
   readline   S1, P0
   chopn      S1, 1
   set        P3[S5], S1
   branch     JARDIN
  TERMINE:
   print      P1, "Statistiques du jardin.\n"
   print      P1, "-----------------------\n\n"
  # Récupération de la liste des fleurs (les clés du hash).
   new        P2, .Iterator, P3
   set        P2, .ITERATE_FROM_START
   set        I1, P2
   print      P1, "Le jardin comporte "
   print      P1, I1
   print      P1, " essences florales differentes.\n"
   print      P1, "Ce sont :\n"
  # Impression des résultats.
  BOUCLE:
   unless     P2, FIN
   shift      S1, P2
   print      P1, S1
   print      P1, " au nombre de "
   set        S2, P3[S1]
   print      P1, S2
   print      P1, "\n"
   branch     BOUCLE
  FIN:
   end 
  coruscant:~/Langages/asmparrot chris$ parrot jardin.pasm
  Etat des plantations de mon jardin.
  Nom de la fleur ?
  Tulipes
  Nombre d'unites ?
  250
  Nom de la fleur ?
  Iris
  Nombre d'unites ?
  500
  Nom de la fleur ?
  Jonquilles
  Nombre d'unites ?
  350
  Nom de la fleur ?

  Statistiques du jardin.
  -----------------------

  Le jardin comporte 3 essences florales differentes.
  Ce sont :
  Tulipes au nombre de 250
  Iris au nombre de 500
  Jonquilles au nombre de 350
  coruscant:~/Langages/asmparrot chris$

Clonage des agrégats.

L'instruction clone permet comme nous l'avons vu de cloner un agrégat, c'est-à-dire de dupliquer l'agrégat considéré en en créant une nouvelle instance.

   new P1, .ResizableIntegerArray
  # Affectation de valeurs aux éléments de P1.
   set P1[ ], ...
  # Duplication de P1.
   clone P2, P1
  # Affectation de valeurs aux éléments de P2.
   set P2[ ], ...
  # Impression des éléments de P1.
   set S1, P1[ ]
   print S1
  # Impression des éléments de P2.
   set S1, P2[ ]
   print S1

Les instructions split et join

Ces deux instructions Parrot se comportent comme les fonctions Perl du même nom.

Découpage d'une chaîne

split permet le découper d'une chaîne pour générer un agrégat. Dans l'état actuel du développement, l'implémentation n'autorise qu'une chaîne de caractères comme critère pour déterminer l'emplacement de la scission. Dans sa version définitive, c'est une expression régulière qui la remplacera.

  coruscant:~/Langages/asmparrot chris$ cat decoup.pasm
   new    P0, .Array
   set    P0, 2
   set    S0, "ab..cd"
   split  P0, "..", S0          # Création de l'agregat.
   set    S0, P0[0]
   print  S0
   print  "\n"
   set    S0, P0[1]
   print  S0
   print  "\n"
   end
  coruscant:~/Langages/asmparrot chris$ parrot decoup.pasm
  ab
  cd
  coruscant:~/Langages/asmparrot chris$

Union des éléments d'une liste

join va réunir l'ensemble des éléments d'un agrégat en une chaîne unique, le second argument permet de spécifier quel seront les caractères qui devront être utilisés pour effectuer cette liaison.

# FIXME nbc # normalement on réserve l'usage de second à des cas # oú il n'y pas de troisième.

  coruscant:~/Langages/asmparrot chris$ cat union.pasm
   new    P0, .Array
   set    P0, 3
   set    P0[0], "Zero"
   set    P0[1], "Un"
   set    P0[2], "Deux"
   join   S0, ", ", P0
   print  S0
   print  "\n"
   end
  coruscant:~/Langages/asmparrot chris$ parrot union.pasm
  Zero, Un, Deux
  coruscant:~/Langages/asmparrot chris$

Les sous-programmes

Branchement relatif

Une référence destination d'un sous-programme implique la finalisation de deux actions. Concrétiser la rupture de séquence pour aller exécuter l'ensemble d'instructions représentatives de la fonction et mémoriser l'adresse courante pour pouvoir, une fois la portion de code terminé, revenir à l'emplacement d'où s'est effectué l'appel, ce retour sera effectif au moment où sera rencontrée l'instruction ret.

La première manière d'aller exécuter un sous-programme est d'y faire référence en adressage relatif. C'est l'instruction bsr (branch to subroutine) qui permet de réaliser cette action.

  bsr   _nom

L'adresse du sous-programme est calculée relativement à l'emplacement de l'instruction qui y fait référence.

  coruscant:~/Langages/asmparrot chris$ cat sprel.pasm
  # Branchement en relatif.
   print    "On est dans le programme principal.\n"
   print    "On se branche au sous programme.\n"
   bsr      _sp
   print    "Retour au programme principal.\n"
   end
  _sp:
   print    "On est dans le sous programme.\n"
   ret
  coruscant:~/Langages/asmparrot chris$ parrot sprel.pasm
  On est dans le programme principal.
  On se branche au sous programme.
  On est dans le sous programme.
  Retour au programme principal.
  coruscant:~/Langages/asmparrot chris$

Branchement absolu

Le saut à un sous-programme peut s'effectuer en faisant directement référence à son adresse. Une instruction spécifique permet de la connaître et de la sauvegarder dans un registre entier set_adr, et, dans ces conditions, c'est l'instruction jsr (jump to subroutine) qui sera utilisée pour réaliser l'opération.

  set_adr  Ix, _nom   # mémorisation de l'adresse dans un registre entier
  jsr      Ix         # saut à l'adresse contenue dans I0.

Reprenons le programme ci dessus en effectuant un saut absolu.

  coruscant:~/Langages/asmparrot chris$ cat sabs.pasm
  # Saut en absolu.
   print    "On est dans le programme principal.\n"
   print    "On se branche au sous programme.\n"
   set_addr I1, _sp
   jsr      I1
   print    "Retour au programme principal.\n"
   end
  _sp:
   print    "---> On est dans le sous programme. <---\n"
   ret
  coruscant:~/Langages/asmparrot chris$ parrot sabs.pasm
  On est dans le programme principal.
  On se branche au sous programme.
  ---> On est dans le sous programme. <---
  Retour au programme principal.
  coruscant:~/Langages/asmparrot chris$

Personnellement, et toujours dans le but de clarifier la présentation, j'utilise des lettres minuscules pour référencer mes sous-programmes et leur nom commence toujours par le caractère blanc souligné (_). Il est ainsi possible de les distinguer immédiatement des étiquettes correspondant à des adresses symboliques et qui sont représentées en majuscules.

Utilisation des PMC pour gérer les sous-programmes

Gestion dynamique de l'adresse de retour

Une fois de plus, les capacités offertes par les PMC vont nous permettre d'aller un peu plus loin dans la construction de sous-programmes. Dans un premier temps, créer un sous-programme peut se faire en déclarant un PMC.

  .const   .Sub P0 = "_sp"

Le PMC P0 va alors permettre de faire référence au sous-programme qui vient d'être défini par son nom.

Dans ces conditions, l'adresse de retour sera générée dynamiquement dans le registre P1 au moment de l'appel qui doit se faire par l'intermédiaire de l'instruction invokecc.

  coruscant:~/Langages/asmparrot chris$ cat sp.pasm
   print    "On est dans le programme principal.\n"
   .const   .Sub P0 = "_sp"
   print    "On se branche au sous programme.\n"
   invokecc P0
   print    "On est de retour au programme principal.\n"
   end
.pcc_sub _sp:
   print    "---> On est dans le sous programme. <---\n"
   returncc
  coruscant:~/Langages/asmparrot chris$ parrot sp.pasm
  On est dans le programme principal.
  On se branche au sous programme.
  ---> On est dans le sous programme. <---
  On est de retour au programme principal.
  coruscant:~/Langages/asmparrot chris$

Les sous-programmes récursifs

Pour autoriser la récursivité lors de la conception d'un sous-programme, il faut réaliser en Parrot ce qui est fait en Perl par l'intermédiaire des variables privées (my). Lorsqu'une variable est déclarée my sa visibilité est réduite au bloc qui la contient. De plus, toute entrée dans le bloc en question aura pour effet d'en créer une nouvelle instance alors que la sortie du bloc détruira la dernière instance créée. Une des solutions pour réaliser cette fonctionnalité est de se servir de la pile banalisée pour sauvegarder les variables au moment où on rentre dans le sous-programme et de les restaurer en le quittant. En nous basant sur le classique programme des Tours de Hanoi [Hanoi] dont l'écriture en Perl se présente comme suit :

    #!/usr/bin/perl
    # Les tours de Hanoi.
    print "Combien de disques ?\n";
    $x = <STDIN>;
    hanoi($x, "a", "b", "c");

    sub hanoi {
        my ($n, $x, $y, $z) = @_;
        return if $n == 0;
        hanoi($n-1, $x, $z, $y);
        print "De $x vers $z\n";
        hanoi($n-1, $y, $x, $z);
    }

Il est possible de réaliser, sans trop de difficulté, la même chose en Parrot en suivant le modèle défini par le programme que nous venons de voir.

  coruscant:~/Langages/asmparrot chris$ cat hanoi.pasm
  # I0 : Nombre de disques
  # S1 : Nom de la première tour (a).
  # S2 : Nom de la seconde tour (b).
  # S3 : Nom de la troisième tour (c).
   getstdin   P0
   getstdout  P1
   print      P1, "Combien de disques ?\n"
   readline   S0, P0
   set        I0, S0
   set        S1, "a"
   set        S2, "b"
   set        S3, "c"
   bsr        _hanoi
   end 
  # I0, S1, S2 et S3 sont privées.
  _hanoi:
  # Sauvegarde des variables privées dans la pile banalisée.
   save      I0
   save      S1
   save      S2
   save      S3
   eq        I0, 0, OUT
   dec       I0
   exchange  S2, S3
   bsr       _hanoi
   print     P1, "De "
   print     P1, S1
   print     P1, " vers "
   print     P1, S2
   print     P1, "\n"
   exchange  S1, S3
   exchange  S2, S3
   bsr       _hanoi
  OUT:
  # Restauration des variables privees.
   restore   S3
   restore   S2
   restore   S1
   restore   I0
   ret
  coruscant:~/Langages/asmparrot chris$ parrot hanoi.pasm
  Combien de disques ?
  3
  De a vers c
  De a vers b
  De c vers b
  De a vers c
  De b vers a
  De b vers c
  De a vers c
  coruscant:~/Langages/asmparrot chris$

Au moment où on entre dans le sous-programme, les quatre instructions :

  save  I0
  save  S1
  save  S2
  save  S3

permettent de sauvegarder dans la pile banalisée la valeur des variables concernées par la récursion r avant de lancer la récursion r + 1. Et lorsqu'on sort du sous-programme, à la fin de la récursion r + 1, les quatre instructions

  restore S3
  restore S2
  restore S1
  restore I0

effectuent l'opération inverse, à savoir rendre aux variables concernées la valeur qu'elles avaient lors de la récursion r.

Le passage d'arguments

Dans le programme que nous venons de voir, le passage d'arguments était géré par le concepteur. C'était à lui à décider par quel registre était transmise une donnée particulière destinée à la fonction. Parrot propose quatre instructions permettant de s'affranchir de cette contrainte. Le programme appelant va pouvoir au moyen de l'instruction set_args spécifier la liste des arguments à transmettre au sous-programme, lequel pourra les récupérer par get_params.

  set_args      "(desc0, desc1, .... , descn)", ARG0, ARG1, .... , ARGn
  get_params    "(desc0, desc1, .... , descn)", ARG0, ARG1, .... , ARGn

Les descripteurs, un par valeur à transmettre, sont des entiers qui vont permettre de contrôler le type de l'argument correspondant (valeur explicite ou référence à un registre). Si la valeur est mise à 0, c'est l'assembleur qui se charge du calcul en fonction du paramètre correspondant.

  coruscant:~/Langages/asmparrot chris$ cat params.pasm
    .const   .Sub P0 = "_sp"
     print    "Appel du sous programme.\n"
     set I0, 100
  # Premier argument passe par un registre.
  # Second et troisième argument explicite.
     set_args "(0,0,0)", I0, 200, "Bonjour"
     invokecc P0
     print    "Retour au programme principal.\n"
     end
  .pcc_sub _sp:
     print    "---> On est dans le sous programme. <---\n"
  # Le premier argument est récupéré dans I10.
  # Le second argument est récupéré dans I20.
  # Le troisième argument est récupéré dans S10
     get_params "(0,0,0)", I10, I20, S10
     print "Premiere valeur : "
     print I10
     print "\nSeconde valeur : "
     print I20
     print "\nTroisieme valeur : "
     print S10
     print "\n"
     returncc
  coruscant:~/Langages/asmparrot chris$ parrot params.pasm
  Appel du sous programme.
  ---> On est dans le sous programme. <---
  Premiere valeur : 100
  Seconde valeur : 200
  Troisieme valeur : Bonjour
  Retour au programme principal.
  coruscant:~/Langages/asmparrot chris$

C'est de la même manière que la fonction retournera au programme appelant les résultats qui auront été calculés.

  set_returns   "(desc0, desc1, .... , descn)", ARG0, ARG1, .... , ARGn
  get_results   "(desc0, desc1, .... , descn)", ARG0, ARG1, .... , ARGn

Les modules Parrot Byte Code

Déclaration d'un sous-programme global

Nous avons dit qu'un sous-programme dont le nom est préfixé par la déclaration .pcc_sub est stocké dans un cache global, son adresse pourra donc être retrouvée au moyen de l'instruction find_global. Cette option alliée à l'instruction load_bytecode qui permet d'aller chercher des lignes de code sur un fichier externe, donne la possibilité de créer des modules et d'y faire appel au moment voulu.

  coruscant:~/Langages/asmparrot chris$ cat mess.pasm
  # mess.pasm
  # Sous-programme catalogue.
    .pcc_sub    _sub:
    print       "---------->  On est dans le sous programme.\n"
    returncc
  coruscant:~/Langages/asmparrot chris$ cat main.pasm
  # main.pasm
  # Programme principal qui va faire référence au sous programme
  # mess.pasm préalablement archive dans un fichier.
    _main:
    print         "***** On entre dans main.\n"
    load_bytecode "mess.pasm"
    find_global   P0, "_sub"
    invokecc      P0
    print         "De retour dans main.\n"       
    end
  coruscant:~/Langages/asmparrot chris$ parrot main.pasm
  ***** On entre dans main.
  ---------->  On est dans le sous programme.
  De retour dans main.
  coruscant:~/Langages/asmparrot chris$

Création de modules en byte code

Nous avons vu au moment de la présentation du système qu'il était possible de stocker les programmes opérationnels sous forme de modules précomptés. C'est l'option -–o qui permet de le faire. Application à un petit programme qui permet de calculer les 13 premières lignes de l'énumération de Conway [look and say]. Tout d'abord on créée le module, celui-ci va être stocké dans un fichier devant impérativement comporter l'extension .pbc (Parrot byte code). Puis, le programme obtenu est soumis à l'interpréteur.

# FIXME nbc # je ne suis pas sur de comprendre le "précomptés" ci-dessus ?

  coruscant:~/Langages/asmparrot chris$ ls
  enum.pasm
  coruscant:~/Langages/asmparrot chris$ cat enum.pasm
   getstdout  P0
   set        S0, "1"
   print      P0, S0
   print      P0, "\n"
   set        I1, 13
  CALCUL:
   set        I0, 0
   set        S4, ""
   substr     S1, S0, 0, 1
  EXPLORE:
   inc        I0
   substr     S0, S0, 1
   eq         S0, "", FINI
   substr     S2, S0, 0, 1
   eq         S1, S2, EXPLORE
   bsr        _construire
   set        S1, S2
   set        I0, 0
   branch     EXPLORE
  FINI:
   bsr       CONSTRUIRE
   print      P0, S4
   print      P0, "\n"
   set        S0, S4
   dec        I1
   ne         I1, 0, CALCUL
   end 
  _construire:
   set        S3, I0
   concat     S4, S3
   concat     S4, S1
   ret
  coruscant:~/Langages/asmparrot chris$ parrot -o enum.pbc enum.pasm

  coruscant:~/Langages/asmparrot chris$ ls
  enum.pasm enum.pbc
  coruscant:~/Langages/asmparrot chris$ parrot enum.pbc
  1
  11
  21
  1211
  111221
  312211
  13112221
  1113213211
  31131211131221
  13211311123113112211
  11131221133112132113212221
  3113112221232112111312211312113211
  1321132132111213122112311311222113111221131221
  11131221131211131231121113112221121321132132211331222113112211
  coruscant:~/Langages/asmparrot chris$

Création de sous-programmes précompilés

Dans le même ordre d'idées, on peut utiliser cette fonctionnalité pour créer des modules externes précompilés qui seront sollicités au moment voulu.

  coruscant:~/Langages/asmparrot chris$ ls
  main.pasm mess.pasm
  coruscant:~/Langages/asmparrot chris$ cat mess.pasm
  # mess.pasm
  # Sous-programme catalogue.
    .pcc_sub    _sub:
    print       "---------->  On est dans sub.\n\n"
    returncc
  coruscant:~/Langages/asmparrot chris$ parrot –o mess.pbc mess.pasm

  coruscant:~/Langages/asmparrot chris$ ls
  main.pasm mess.pasm mess.pbc
  coruscant:~/Langages/asmparrot chris$ cat main.pasm
  # main.pasm
  # Programme principal qui va faire référence au sous-programme
  # mess.pasm préalablement précompilé dans un fichier.
  _main:
    print       "***** On entre dans main.\n"
  load_bytecode "mess.pbc"
  find_global   P0, "_sub"
  invokecc      P0
  print         "De retour dans main.\n"       
  end
  coruscant:~/Langages/asmparrot chris$ parrot main.pasm
  ***** On entre dans main.
  ---------->  On est dans sub.
  De retour dans main.
  coruscant:~/Langages/asmparrot chris$

Just another Perl Hacker

Application à un programme Parrot qui affichera ce célèbre mantra. Il se compose de quatre fichiers sous-programme J.pasm, A.pasm, P.pasm, H.pasm et d'un programme principal japh.pasm.

  coruscant:~/Langages/asmparrot chris$ cat J.pasm
      .pcc_sub _sub:
      concat  S2, "Just "
      returncc
  coruscant:~/Langages/asmparrot chris$ cat A.pasm
      .pcc_sub _sub:
      concat  S2, "another "
      returncc
  coruscant:~/Langages/asmparrot chris$ cat P.pasm
      .pcc_sub _sub:
      concat  S2, "Perl "
      returncc
  coruscant:~/Langages/asmparrot chris$ cat H.pasm
      .pcc_sub _sub:
      concat  S2, "hacker."
      returncc
  coruscant:~/Langages/asmparrot chris$ cat japh.pasm
  _main:
      set             I0, 0
  B:
      substr          S0, "JAPH", I0, 1 
      concat          S1, S0, ".pasm"
      load_bytecode   S1
      find_global     P0, "_sub"
      invokecc        P0
      inc             I0
      lt              I0, 4, B
      print           S2
      end
  coruscant:~/Langages/asmparrot chris$ parrot japh.pasm
  Just another Perl hacker.
  coruscant:~/Langages/asmparrot chris$

Les fichiers

Ouverture d'un fichier

La gestion des descripteurs de fichiers ressemble fortement à celle de Perl. L'ouverture d'un fichier consiste à affecter le descripteur gestionnaire par l'intermédiaire d'un PMC puis à utiliser les fonctions d'entrée sortie qui feront référence au PMC en question.

L'ouverture d'un fichier se réalise au moyen de l'instruction :

  open  Px, "nom.txt", "sens"

Comme en Perl, le sens sera :

Écriture dans un fichier

L'utilisation d'un PMC pour visualiser les informations lues va nous permettre de modifier le programme afin de créer un nouveau fichier, et ce sans toucher le corps du programme. Il suffit de changer l'affectation du PMC de :

  getstdout P1

en :

  open  P1, "nouveau.txt, ">"

Pour que les informations soient dirigées sur le fichier ainsi ouvert

  coruscant:~/Langages/asmparrot chris$ ls
  dial.txt file.pasm
  coruscant:~/Langages/asmparrot chris$ cat file.pasm
      open      P3, "dial.txt", "<"
      open      P1, "nouveau.txt", ">"
  LIRE:
      readline S0, P3
      unless    S0, FIN
      print     P1, S0
      branch    LIRE
  FIN:
      end
  coruscant:~/Langages/asmparrot chris$ parrot file.pasm

  coruscant:~/Langages/asmparrot chris$ ls
  dial.txt file.pasm nouveau.txt
  coruscant:~/Langages/asmparrot chris$ cat nouveau.txt
  What about the name of the new language?

   GvR:    Well, that was my idea. We went over lots of possible names:
          Chimera, Pylon, Perth, before finally coming up with Parrot.
          We had a few basic ideas: we wanted it to begin with "P";
          it had to be something that wouldn't sound stupid on the end
          of  /usr/bin/.

   LW:    We also wanted the name of an animal, to represent the combination
          of the camel and the python. It also helps with the book covers...

   GvR:   Eventually, I came up with Parrot after thinking about Monty 
          Python's finest hour, the Parrot sketch.

   LW:    It just sounded right - dynamic, colourful, exotic. I love it!
  coruscant:~/Langages/asmparrot chris$

Assombrissement en Parrot.

Pourquoi ne pas reprendre l'idée de l'assombrissement de $A++ [$A++] pour la retravailler en Parrot ? Il suffirait d'en changer le nom de $A++ en inc I0. Le but restant toujours le même, incrémenter le registre I0 sans que cela se sache. Ecrit de manière traditionnelle, le programme se présente sous une forme particulièrement évidente.

  coruscant:~/Langages/asmparrot chris$ cat inc.pasm
      getstdout   P1
      set         I1, 10
      print       P1, I1
  # Début séquence de base
      inc         I1
  # Fin séquence
      print       P1, I1
      print       P1, "\n"
      end
  coruscant:~/Langages/asmparrot chris$ parrot inc.pasm
  11
  coruscant:~/Langages/asmparrot chris$

Il pourrait être obscurci sous la forme :

  coruscant:~/Langages/asmparrot chris$ cat inc.pasm
      getstdout   P1
      set         I1, 10
      print       P1, I1
  # Début séquence inc I1
      set         I2, 1
  CALC:
      bxor        I1, I2
      band        I3, P2, I1
      if          I3, FINI
      shl         I2, 1
      branch      CALC
  FINI:
  # Fin séquence inc I1
      print       P1, I1
      print       P1, "\n"
      end
  coruscant:~/Langages/asmparrot chris$ parrot inc.pasm
  11
  coruscant:~/Langages/asmparrot chris$

Pour terminer

L'assembleur n'a jamais eu l'audience qu'il méritait, force est de reconnaître que les dialectes qui étaient proposés n'avaient rien de particulièrement attirant. Souvent, l'apprentissage de la programmation en "langage machine" comme on avait l'habitude de dire se résumait à une longue présentation de la structure de la plate-forme suivie d'un interminable catalogue des formats d'instructions et des modes d'adressage. Cette approche n'avait rien de vraiment passionnant et surtout, imposait un éternel recommencement. La machine virtuelle qui a été présentée, aborde les choses sous un aspect totalement nouveau. Si l'ombre de Perl plane sur la manière qu'elle adopte pour représenter les choses, et, par là même, en simplifie l'utilisation, il est impossible de nier le fait qu'il s'agisse réellement d'un langage d'assemblage.

Références

[goto] goto Perl, par Philippe Bruhat et Jean Forget, in GNU/Linux Magazine France n°72, mai 2005, http://articles.mongueurs.net/magazines/linuxmag72.html

[pmc] http://www.parrotcode.org/docs/pmc

[Hanoï] http://articles.mongueurs.net/magazines/perles/perles-hanoi.html

[look and say] http://fr.wikipedia.org/wiki/Suite_de_Conway

[$A++] http://paris.mongueurs.net/aplusplus.html

[IE7, par Dean Edwards] [Validation du HTML] [Validation du CSS]