micropython dans KhiCAS
Posté le 01/02/2022 14:22
J'ai avance sur le portage de MicroPython dans KhiCAS pour graph 90, ca compile, mais j'ai un crash lors de l'initialisation de MicroPython, lie a l'allocation memoire. Ca plante au 1er appel d'allocation dans la fonction mp_init (py/runtime.c) sur une demande d'allocation de 24 bits, lorsque le gc est actif, le pointeur renvoye est nul. Si je desactive le garbage collector, ca ne marche pas non plus, ca plante un peu plus tard a la 5eme allocation memoire (avec kmalloc de gint, mais c'est pas kmalloc la cause, car si on desactive les arena en ram en ne conservant que le malloc systeme ca plante tout autant).
C'est probablement lie a une mauvaise configuration. Mon mpconfigport.h est
https://www-fourier.univ-grenoble-alpes.fr/~parisse/tmp/mpconfigport.h, j'ai beau le comparer avec celui de CasioPython je ne vois rien qui explique le crash (
https://github.com/Zezombye/casiopy/blob/master/ports/minimal/mpconfigport.h). La compilation se fait avec des commandes du type
sh3eb-elf-gcc -DNUMWORKS -DMICROPY_LIB -I. -I.. -I../py -Ibuild -Wall -ansi -std=gnu99 -DFFCONF_H=\"lib/oofatfs/ffconf.h\" -Os -g -DMODULE_ULAB_ENABLED=1 -mb -m4a-nofpu -mhitachi -fdata-sections -ffunction-sections -fno-strict-aliasing -fno-exceptions -c -MD -o build/./main.o main.c
(le flag NUMWORKS sert a contourner un bug de sprintf dans un module additionnel, ca n'a rien a voir avec le code de micropython)
Le source complet (giac+mp) est la
https://www-fourier.univ-grenoble-alpes.fr/~parisse/tmp/giac2.tgz (se compile avec ./mklib dans micropython-1.12/fxcg et make dans giac2)
L'addin en 2 parties
https://www-fourier.univ-grenoble-alpes.fr/~parisse/tmp/khicas.g3a et
https://www-fourier.univ-grenoble-alpes.fr/~parisse/tmp/khicas.ac2
Pour lancer MicroPython depuis le shell de KhiCAS, faire shift-PRGM puis 8 pour mettre python( en ligne de commande et valider. Le crash apparait avec le print du malloc fautif que j'ai rajoute dans py/malloc.c, on devine aussi le msg d'erreur de micropython (FATAL uncaught NLR ...) genere suite au pointeur nul (non catche par un nlr_push).
Citer : Posté le 01/02/2022 15:01 | #
Il y a un truc vraiment bizarre, si j'ajoute dans py/gc.c a la fin de la fonciton gc_alloc un printf("gc6\n"); juste avant de renvoyer le pointeur (return ret_ptr;) alors ca ne crashe plus a l'initialisation, et ensuite l'interpreteur a l'air de fonctionner. Je suis completement ahuri. Je ne vais quand meme pas laisser ce genre de code, ca ralentirait trop!
Ajouté le 01/02/2022 à 17:43 :
Pour le moment je contourne en executant un affichage uniquement au 1er appel de gc_alloc. Mais ca ne fait que cacher la poussiere sous le tapis, il doit y avoir une raison plus profonde a comprendre...
Ajouté le 01/02/2022 à 19:42 :
Le mystere s'epaissit. Certaines commandes de Xcas crashent uniquement quand l'addin est compile avec la librairie micropython. Le code source est identique, sans mp ca marche, avec mp ca crashe. Et pour ajouter au mystere, ces fonctions de Xcas affichent des messages que j'ai ajoutes dans gc_alloc, donc dans le code du garbage collector de MicroPython, alors qu'elles n'y font jamais appel (et que MicroPython n'a pas ete active).
Du coup, j'ai bien peur de ne pas pouvoir integrer MicroPython...
Citer : Posté le 01/02/2022 20:16 | #
Je réponds rapidement, je n'ai pas encore pris le temps de tester... x_x
Le fait qu'ajouter/enlever le printf déplace le problème suggère que c'est typiquement un problème de bas niveau : buffer overflow, dépassement de pile, variables qui se chevauchent, data races et autres perturbations asynchrones sont les premiers trucs auxquels je pense dans ces situations (j'ai déjà eu droit à tous).
Le fait que compiler ou pas MicroPython dans l'add-in change le comportement d'autres fonctions, et surtout le fait que les messages se mélangent, ne font que le confirmer. Ma suggestion serait de délimiter l'effet que linker avec MicroPython a, du genre :
Un seul truc louche dans la config :
MicroPython n'a aucun compilateur natif compatible avec la calculatrice, cette macro ne devrait pas être définie.
Citer : Posté le 01/02/2022 20:42 | #
Je crois que j'ai une piste, qui serait que la commande de l'OS de lecture d'un fichier ne marche pas correctement si la taille depasse 2Mo, est-ce que ca te semble possible? Il semble que les commandes Xcas qui ne marchent plus quand MicroPython est inclus sont situees a la fin de la partie de l'addin charge en RAM, au-dela de l'adresse 0x8c400000.
Citer : Posté le 01/02/2022 20:48 | #
Ça paraît un peu fourbe mais ce serait pas le pire qu'on ait vu. Peut-être aussi que la limite joue ailleurs durant le processus de chargement.
Citer : Posté le 01/02/2022 21:22 | #
Fausse piste. J'ai remis la taille des 2 morceaux de l'addin sous les 2M, mais ca plante toujours.
Il n'y a pas de fonction d'initialisation de la lib micropython implicite, il faut les appeler explicitement. Le seul appel que je fais au demarrage de Xcas c'est pour initialiser une variable MicroPython qui marque la pile au demarrage.
Je vais regarder pour la RAM vs la stack mais j'ai reserve pas mal de place pour la stack ( ram (rwx) : o = 0x08101000, l = 432k /* skip 4k at start */)
Tous les fichiers de mp sont linkes dans l'addin. Je n'ai pas d'adresse du message affiche (c'est un printf("gc3\n")), mais en fait c'est l'affichage du message qui est aussi typique d'un appel a printf depuis gc_alloc (depuis Xcas les messages sont affiches en fonte plus grande, dans la console). Et je ne vois pas comment gc_alloc peut etre appele par Xcas, c'est comme si les appels a malloc dans Xcas etaient remplaces par gc_alloc mais je ne vois pas comment c'est possible (dans le source de MP il y a bien des undef malloc, mais ces sources ne sont pas lu depuis Xcas). En plus c'est un message de memoire full qui s'affiche meme si j'initialise le tas MP.
Ajouté le 02/02/2022 à 06:51 :
J'ai une nouvelle piste: c'est l'initialisation des data. Si ca se fait sur un fichier objet charge en ram ca va logiquement bugguer, car c'est fait avant que main soit appele et donc avant que main ait charge la partie ram de l'addin.
Donc il faudrait charger toutes les parties data en rom dans prizm.ld, ca te parait juste?
Citer : Posté le 02/02/2022 08:51 | #
Si le linker script est bien fait normalement tu n'as pas ce problème. La raison pour laquelle la section .data est initialisée à part dans l'add-in principal c'est que l'accès que l'OS te fournit au fichier d'add-in (par 00300000) est en lecture seule, donc tu peux pas l'utiliser pour les données. D'où le fait que dans le linker script on a >ram AT>rom, ce qui signifie qu'on stocke initialement la section dans la ROM (ie. dans le g3a) mais qu'ensuite on promet de la charger dans la RAM là où elle pourra être modifiée.
Comme toute la RAM dans la section r8c2 peut être modifiée, tu n'a pas ce problème ; tu peux charger les données où tu veux, y compris avec le code. Actuellement tu les mets avec le reste de la section .data, ce qui est correct aussi. Les variables globales des fichiers z*.o sont donc initialisées dès le démarrage de l'add-in, avant le chargement du second fichier.
Tu peux vérifier tout ça en affichant depuis le code de l'add-in principal une variable globale stockée dans un fichier z*.o initialisée à une valeur non triviale.
(J'ai essayé de le compiler mais j'y arrive pas encore, j'ai pas le bon système de compilation sous la main - ma libc se mélange avec celle de libfxcg.)
Citer : Posté le 02/02/2022 13:45 | #
En enlevant le printf de gc.c j'obtiens un message
FATAL uncaught NLR 0x08167d74
L'adresse se situe dans une zone allouee pour mp_state_ctx puisque sh3eb-elf-objdump -C -t khicas.elf | sort > dump_t me renvoie
...
08167d50 g O .bss 00000004 mp_verbose_flag
08167d54 g O .bss 00000001 cout
08167d58 g O .bss 00000004 signgam
08167d5c g O .bss 00000238 mp_state_ctx
08167f94 g O .bss 00000004 mp_showbc_code_start
08167f98 g O .bss 00000004 mp_showbc_const_table
08167f9c g O .bss 00000001 ctrl_c
08167f9d g O .bss 00000001 interrupted
08167fa0 g O .bss 00000004 errno
08167fa4 g O .bss 00000004 _strtoks_
...
Ajouté le 02/02/2022 à 14:18 :
Je suis sur une autre piste qui me semble plus prometteuse: un conflit entre libtommath et libmicropy, toutes les 2 ont une fonction mp_init.
Si je linke -lmicropy avant -ltommath il n'y a pas d'avertissement. Si je linke tommath avant micropy j'ai une erreur.
./libmicropy.a(runtime.o): In function `mp_init':
/home/parisse/casio/micropython-1.12/fxcg/../py/runtime.c:59: multiple definition of `_mp_init'
/home/parisse/casiolocal/lib/libtommath.a(bn_mp_init.o):bn_mp_init.c:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status
Ajouté le 02/02/2022 à 14:20 :
Voila qui expliquerait pourquoi micropython est appele mysterieusement.
Reste a trouver un moyen d'eviter le conflit, sachant que mp_ est le prefixe utilise a la fois par micropython et tommath (mp=multi precision)...
Ajouté le 02/02/2022 à 14:41 :
Je viens de renommer mp_init de micropython en mp_init_py et ca a l'air de fonctionner!
Citer : Posté le 02/02/2022 14:56 | #
Ok, bien vu cette histoire. C'était donc bien des mélanges au link, d'un genre que j'admets ne pas avoir vu du tout venir.
Maintenant la question c'est comment l'éviter dans le futur. Je ne pense pas que tu puisses renommer automatiquement les fonctions. Par contre ce que tu peux faire c'est extraire pour chaque archive la liste des symboles, et avoir un script qui les énumère et t'avertit en cas de doublon :
Le sed commence par se débarasser des lignes indiquant les noms des fichiers de l'archive (/:$/ d), ensuite il élimine tous les symboles locaux (lettre minuscule) et les références qui ne sont pas des déclarations (lettre U) (/[^ ]+ [a-zU]/ d), et enfin il ne garde que le nom du symbole au début de la ligne (s/^([^ ]+).*$/\1/).
Tu peux faire ça pour tes deux archives et comparer l'intersection. Ça devrait aider à repérer ce problème.
Citer : Posté le 02/02/2022 15:53 | #
Je viens de le faire, je n'ai pas vu d'autre conflit.
Il reste encore des bugs, mais ce sont des bugs classiques, ouf!
Citer : Posté le 02/02/2022 16:04 | #
Excellent ! Bon courage pour la suite de l'intégration
Citer : Posté le 03/02/2022 07:50 | #
J'ai plutot une bonne nouvelle pour une fois, c'est que OFF/ON a l'air de preserver les 3Mo de RAM. Du coup je vais serieusement envisager d'en reserver une partie pour kmalloc ou/et le heap Python. Ca pourrait d'ailleurs aussi servir pour la version light d'addin en 1 seul morceau, mais il faudrait pouvoir verifier qu'on est sur une fxcg50 ou graph 90. Tu as du code pour ca?
Sinon, j'ai fait un essai de compilation avec -flto, mais ca a l'air incompatible avec le decoupage en 2 addins, tu as une idee de la raison ? Ou bien je m'y suis mal pris...
Citer : Posté le 03/02/2022 08:55 | #
Mais bien sûr x3
__asm__("mov r15, %0" : "=r"(stack));
if (stack < 0x8c000000) {
/* Prizm ou émulateur Graph 90+E */
}
else {
/* Graph 90+E */
}
En profitant de l'adresse de la RAM qui est différente.
Les fois où j'ai essayé -flto je me suis retrouvé avec des add-ins qui ne marchent pas, il ne linkait pas du tout assez de choses. J'admets ne pas être très à l'aise avec la fonctionnalité pour l'instant.
Citer : Posté le 03/02/2022 10:22 | #
Du coup je fais
#ifdef GINT_MALLOC
/* À appeler une seule fois au début de l'exécution */
kmalloc_init();
/* Ajouter une arène sur la RAM inutilisée */
static_ram.name = "_uram";
static_ram.is_default = 1; // 0 for system malloc first, 1 for ram
#ifdef VAR_HEAP
static_ram.start = malloc_heap;
static_ram.end = malloc_heap+sizeof(malloc_heap);
#else
//void *malloc_start = &sextra;
//void *malloc_end = &eextra;
//int malloc_size = malloc_end - malloc_start;
static_ram.start = &sextra;
static_ram.end = &eextra;
#endif
kmalloc_init_arena(&static_ram, true);
kmalloc_add_arena(&static_ram);
uint32_t stack;
__asm__("mov r15, %0" : "=r"(stack));
if (stack < 0x8c000000) {
/* Prizm ou émulateur Graph 90+E */
}
else {
/* Graph 90+E */
ram3M,name="_3M";
ram3M.is_default=1;
ram3M.start=0x8c200000;
ram3M.end=ram3M.start+3*1024*1024;
kmalloc_init_arena(&ram3M, true);
kmalloc_add_arena(&ram3M);
}
#endif // GINT_MALLOC
pour l'addin seul, pour celui en 2 parties il faudra que je reserve moins de place et plus loin. Sauf si on peut aller au-dela des 3M!
Citer : Posté le 03/02/2022 10:41 | #
Yup, ça a l'air tout bon. Bien sûr static_ram et ram3M doivent être globales. Comme tu l'as vu du coup tu ne peux plus utiliser cette mémoire pour charger ta section r8c2 avec du code supplémentaire.
Il y a 6 Mo pour être exact mais il y a des données à la fin, et même si pour l'instant on n'a pas trouvé de cas où elles sont utilisées et où on ne peut pas les modifier, je range ça dans la catégorie "instable". Tant qu'à faire si tu peux garder la détection des zéros plutôt que la constante 3 Mo je suggère de le faire, on n'est pas à l'abri d'une mauvaise surprise avec une future version de l'OS
Si tu tiens à tenter le coup, je conseille de protéger ce cas d'usage par une option off par défaut et/ou une détection de la version d'OS.
Citer : Posté le 03/02/2022 11:26 | #
D'ailleurs, as-tu une idee de comment le reste de la RAM est utilisee? Les addins sont-ils aussi recopies dans une section de la RAM ou bien la flash est-elle executable?
Citer : Posté le 03/02/2022 12:03 | #
Le "reste" de la RAM c'est vague mais si tu veux dire l'autre moitié des 6 Mo alors on ne sait pas encore. Pour les 2 premiers Mo à 0x8c000000, y'a toutes les données du noyau et de l'OS, variables globales, la mémoire principale, et tout ce qui est utile à l'add-in : bloc utilisateur, pile, tas principalement.
Les add-ins ne sont pas copiés en RAM, il y a un mapping exécutable vers la ROM à travers le MMU. De façon générale la ROM est exécutable aussi (il faut bien que l'OS tourne !) mais la mémoire de stockage en pratique non puisque les fichiers qu'on met dedans sont fragmentés.
Citer : Posté le 03/02/2022 12:45 | #
Du coup l'OS doit detecter les fichiers d'extension g3a pour ne pas les fragmenter?
Citer : Posté le 03/02/2022 13:08 | #
Non, les fichiers g3a sont bel et bien fragmentés. Mais ensuite il y a un mapping de P0 via le MMU. Si tu n'es pas familier avec la mémoire virtuelle, en gros la zone 00300000 ne contient pas vraiment de mémoire ; elle est découpée en pages de 4 kio qui pointent chacune vers une adresse en ROM. Le MMU est le périphérique via lequel l'OS déclare quelles pages existent et où elles mènent dans la ROM. Chaque fois que tu fais un accès mémoire dans cette région, une translation d'adresse se produit dans le MMU pour déterminer la cible et ainsi accéder à la ROM.
La clé de ce système (pour nous ; il a bien d'autres propriétés désirables par ailleurs) c'est que les pages sont contrôlées individuellement, donc l'OS peut faire pointer la première page vers le premier fragment du fichier g3a, la seconde page vers le second, et ainsi de suite, même si le fichier est complètement en morceaux dans la ROM.
Ça veut aussi dire que seul le CPU peut lire le code et certaines données ; les périphériques comme le DMA qui n'ont pas accès au MMU en sont incapables.
Citer : Posté le 03/02/2022 18:38 | #
Du coup, est-ce que ce serait possible de programmer nous-memes le MMU (dans un addin classique supporte par Casio) pour lancer des addins de taille plus grande que 2M mais cette fois sans utiliser la RAM? Ou bien il y a une taille limite? C'est quand meme dommage d'utiliser de la RAM si on peut l'economiser.
L'autre mystere pour moi, c'est pourquoi Casio n'utilise pas lui-meme les capacites RAM de sa machine (et communique dessus). Est-ce juste parce que les 6Mo additionnels ne sont pas disponibles partout ?
D'aileurs les 6Mo sont-ils disponibles sur les fxcg50 ? J'ai beaucoup plus de telechargements de KhiCAS en anglais qu'en francais (308 contre 90 pour ce mois de janvier), ce serait dommage de les priver des ameliorations en cours.
Citer : Posté le 03/02/2022 18:53 | #
Ce n'est pas possible dans un add-in classique ; pour faire ce genre de manœuvres il faut contrôler les interruptions. Et de toute façon même quand on le peut on évite parce qu'en cas de plantage avec un MMU modifié on peut bricker une calto (AHelper a eu ce problème avec une Prizm à l'époque où il voulait implémenter des libs dynamiques).
La Graph 90+E est juste un successeur à la Prizm, qui n'avait que 2 Mo de RAM. Elle a beau avoir une puce RAM de 8 Mo, tout ce qu'on peut en déduire c'est que le R&D se sent à l'étroit dans 2 Mo et apprécie de pré-équiper le nouveau modèle avec une puce plus grosse pour qu'il soit compatible avec les futures mises à jour dont ils anticipent qu'elles utiliseront cet espace supplémentaire.
La même chose s'est produite avec la série monochrome ; elles ont eu des puces de 512 ko de RAM tout en n'en utilisant que 256 ko pendant 10 ans, et puis la Graph 35+E II est arrivée avec MicroPython et soudain la mémoire en plus est devenue nécessaire.
D'où le fait que j'insiste autant sur la stabilité de la manœuvre : cette RAM est probablement gratuite pour l'instante mais sera certainement utilisée plus tard, et on ne veut pas que quelqu'un dans 5 ans avec un OS plus lourd bricke quelque chose en utilisant un binaire qui traîne sur le forum.
Oui la fx-CG 50 a tout autant de mémoire, pour autant que je le sache matériellement elle est identique à la Graph 90+E.