SilverStone, ou la pierre philosophale de Silverlight
Comme vous avez pu le constater, l'activité s'est quelque peu réduite sur mon blog ces derniers temps. Malheureusement, il n'est pas toujours facile de jongler entre vie professionnelle, vie extra-professionnelle et vie de famille... 
Je tenais donc à vous faire partager mes réflexions sur un nouveau projet Silverlight sur lequel je compte travailler pendant mon temps libre : "SilverStone". Je pense qu'il est prometteur et surtout qu'il ouvrira de nouveaux horizons à Silverlight.
Tout d'abord, un petit historique s'impose. Il faut savoir qu'avant de m'intéresser à cette technologie Microsoft, je travaillais essentiellement sur des émulateurs et des portages de jeux vidéos avec le language Java. C'est en juillet de l'année dernière, que j'ai découvert une vidéo impressionnante de Quake en Flash (http://www.youtube.com/watch?v=0hX-Uh3oTcE). Il s'agissait en fait d'une preview d'un projet de recherche d'Abobe, Adobe Alchemy. Cette démonstration technique a d'ailleurs fortement inspiré mon portage de Quake en Silverlight.
Adobe Alchemy (http://labs.adobe.com/technologies/alchemy) est un outil qui se présente sous la forme d'un environnement de compilation pour Flash 10. Il permet de convertir n'importe quelle application écrite en language C en du bytecode Flash (AVM2). Le bytecode Flash généré s'exécute ensuite dynamiquement sur la machine virtuelle de Flash. Alchemy utilise en fait en interne le célèbre projet open-source LLVM (http://www.llvm.org/). LLVM permet de transformer différents languages de haut niveau comme le C ou le Fortran dans un unique language générique de bas niveau, le bytecode LLVM. Il est possible d'appliquer au code LLVM des optimisations très performantes, voire meilleures que celles que proposerait GCC. Pour arriver au résultat final, Adobe a développé un backend particulier qui transforme ce code LLVM en bytecode Flash.
Quake a été porté avec cette technique, et plus récemment on a vu fleurir d'autres projets comme une librairie Ogg Vorbis ou d'autres jeux comme Doom, Heretic ou Hexen (http://www.newgrounds.com/portal/view/470460). Bluffant...
Pourrions-nous appliquer cette même chaîne de compilation à Silverlight ? En théorie oui. Partant du code générique LLVM, on pourrait générer du bytecode MSIL pour Silverlight. La tâche s'annonce malgré tout ardue...
Depuis l'année dernière, j'ai donc exploré plusieurs pistes pour porter des programmes écrits en language C (comme Quake) en C# et Silverlight.
Portage "manuel"
Pour arriver à porter le jeu Quake en Silverlight, j'ai choisi un portage classique. J'ai repris chaque module du programme au fur et à mesure et je les ai converti en classes C#. Pour faciliter la lecture et le débogage du portage, j'ai choisi de conserver le maximum du code originel.
Pour garder la hiérarchie de fichiers sources (.h et .c), j'ai utilisé un seul namespace commun et des classes partielles pour les modules. L'ensemble des variables et des fonctions sont devenues statiques et publiques pour la plupart.
/*
Copyright (C) 1996-1997 Id Software, Inc.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
// client.h
namespace quake
{
public partial class client
La majeure partie du code a été recopié tel quel, il suffisait parfois de remplacer certaines syntaxes purement C par du code C# plus classique (pas de -> par exemple).
Les premières difficultés sont apparues avec le portage des fonctions de la librairie standard du C. J'ai donc créé un wrapper en language C# et Silverlight pour gérer les entrées/sorties, certaines opérations mémoires, etc.
public static void fseek(FILE file, int position, int seek)
{
file.stream.Seek(position, SeekOrigin.Begin);
}
public static void fclose(FILE file)
{
file.stream.Close();
file.stream = null;
file = null;
}
Plus difficile a été la conversion des opérations sur les pointeurs. Silverlight tournant dans une sandbox, pas question d'écrire du code unsafe et donc d'utiliser des pointeurs. A cet effet, j'ai défini des classes très utiles pour simuler les pointeurs sur des buffers mémoire.
#region ByteBuffer
public class ByteBuffer
{
public byte[] buffer;
public int ofs;
public ByteBuffer(byte[] buffer)
: this(buffer, 0)
{ }
public ByteBuffer(ByteBuffer buf)
: this(buf.buffer, buf.ofs)
{ }
public ByteBuffer(ByteBuffer buf, int ofs)
: this(buf.buffer, buf.ofs + ofs)
{ }
public ByteBuffer(byte[] buffer, int ofs)
{
this.buffer = buffer;
this.ofs = ofs;
}
public byte this[int index]
{
get { return buffer[ofs + index]; }
set { buffer[ofs + index] = value; }
}
public static ByteBuffer operator +(ByteBuffer obj, int ofs)
{
return new ByteBuffer(obj.buffer, obj.ofs + ofs);
}
public void Add(int ofs)
{
this.ofs += ofs;
}
public void Sub(int ofs)
{
this.ofs -= ofs;
}
public static bool operator >=(ByteBuffer obj1, ByteBuffer obj2)
{
return obj1.ofs >= obj2.ofs;
}
public static bool operator <=(ByteBuffer obj1, ByteBuffer obj2)
{
return obj1.ofs <= obj2.ofs;
}
}
#endregion
Au final, avec cette technique, longue et fastidieuse, j'ai pu porter la plupart du code source de Quake et y apporter certaines modifications. Les plus gros problèmes rencontrés sont les bugs liés au portage. Quand on passe d'un code C avec des structures et des pointeurs à du code C# avec des classes et des références, il est très facile de se tromper et d'avoir des résulats très inattendus (dans mon cas, le décor qui s'autodétruisait au fur et à mesure...). A ce jour, je n'ai toujours pas identifié certains bugs qui empêchent de faire fonctionner le moteur de jeu proprement dit.
Portage "assisté"
Parallèlement au développement de Quake en Silverlight et fort d'une demande massive des internautes pour jouer à Quake 2 en Silverlight, j'ai décidé d'expérimenter une autre technique. Le code source de Quake 2 est mieux structuré et me semblait plus adapté à une conversion automatique.
J'ai donc développé un programme en Java (sic) qui parse les fichiers .h et quelques .c et les convertit en fichiers sources C#. Ce générateur de code source conserve les commentaires, met en forme le code C#, convertit les #define en constantes, convertit les types C et les typedefs en types C#, etc.
Plus récemment, j'ai été rejoint par un développeur anglais qui a écrit une version en WPF d'un convertisseur similaire. Au final, c'est un outil qui facilite le portage et permet de diminuer le travail répétif du portage "manuel".
Bien entendu, le résultat ne peut être parfait. Il faudrait pouvoir interpréter le code C pour générer du code C# très précis. Par exemple, en fonction du contexte, un char* peut être considéré comme une chaîne de caractères à convertir en String ou bien comme un buffer à convertir en byte[].
Avec cette technique, la plupart des fichiers d'entête de Quake 2 ont été portés en C#. Cela représente une bonne base de départ pour un portage "manuel".
Emulation du code natif
Si la conversion de haut niveau ne permet pas un résultat direct, pourquoi ne pas tenter une approche bas niveau ?
Prenons la version de Quake publiée sous MsDos en 1996. Il s'agit d'un programme écrit en language C avec des parties optimisées spécifiques en assembleur. Quake fonctionne en mode protégé à l'aide d'un DOS extender (CWSDPMI.EXE).
Autrement dit, si on considère un logiciel écrit en C#/Silverlight qui émulerait les fonctions de base du couple PC/MsDos et pourrait interpréter du code Intel 80x386 et le mode protégé, il serait possible d'émuler Quake dans cette machine virtuelle.
Je me suis donc attelé à la réalisation d'un tel émulateur et je suis arrivé assez loin dans son développement :
- émulation du processeur 80x86 et 80x386.
- émulation du co-processeur mathématique 80x387 (pour la 3D et les calculs flottants).
- émulation basique des interruptions systèmes (vidéo, MsDos, clavier).
- gestion de fichiers.
- émulation des modes graphiques et textes de la carte vidéo (CGA, VGA).
- support du clavier.
Plusieurs applications, démos et vieux jeux vidéo pour PC (Pacman, Zaxxon) fonctionnent déjà très bien sur cet émulateur. Malheureusement, il reste beaucoup de corrections de bugs mais aussi encore beaucoup de fonctionnalités à émuler (le mode protégé par exemple) avant d'arriver à faire tourner un programme comme Quake.
Se pose aussi la question des performances. Pour émuler le code machine, j'utilise un interpréteur. C'est à dire que chaque opcode est interprété par du code C# équivalent. Pour un fonctionnement beaucoup plus performant, il faudrait pouvoir compiler dynamiquement du code C# à partir du code machine d'origine. Dans tous les cas, les performances ne vaudront jamais celles du portage "manuel" ou "assisté".
"SilverStone"
Comme on l'a vu précédemment, une conversion de haut niveau reste très complexe à mettre en oeuvre et ne peut pas substituer un portage "manuel" dans son intégralité. En revanche, une conversion de bas niveau offre une liberté incroyable mais demande des efforts de développement considérables et pas de garantie de performances.
Ce qu'il faudrait en fait, c'est un peu des deux : une conversion de haut niveau avec certaines parties de bas niveau. C'est l'idée maîtresse de SilverStone. Pour transformer le plomb en or, ici le language C en language C#, on pourrait utiliser un outil de conversion qui reposerait sur une machine virtuelle dédiée.
Plus précisément, d'un côté, on a tout ce qui peut se porter facilement d'un language à l'autre : les modules, les commentaires, les constantes, les types de base, etc. De l'autre côté, on a tout ce qui est spécifique au language C, les pointeurs, les structures complexes, les unions, les fonctions de la librairie standard.
L'outil de conversion parse le code source C initial et le convertit en language C#. Tout ce qui concerne les pointeurs et autres subtilités du language C est émulé par une gestion de bas niveau, concrètement une gestion mémoire.
Voici l'architecture de SilverStone telle que je la conçois :
- Un préprocesseur de code source C qui déroule les macros et en option les #define en constantes.
- Un parseur de code source C qui construit un arbre syntaxique complet.
- Un générateur de code source C# qui utilise l'arbre syntaxique précédent. Tout ce qui est spécifique au language C est converti pour être utilisé avec la machine virtuelle : les pointeurs deviennent des entiers, les accès mémoires deviennent des méthodes readmem et writemem, les structures et les tableaux sont alloués et tous les offsets (&x.a[2][3]) et les tailles (sizeof(struct vector_3d)) sont calculés et remplacés dans le code, etc.
- Une machine virtuelle en language C# destiné à faire fonctionner des programmes convertis en Silverlight. Il y a une implémentation de la libraire C standard, la gestion mémoire (heap et stack, les fonctions readmem et writemem) pour stocker les pointeurs, les structures et les données et bien sûr le noyau Silverlight (affichage, synchronisation des threads).
- Un host qui permet d'émuler ou de simuler les fonctions de base de l'environnement sur lequel a été développé le programme en C : un shell, une gestion de fichiers pour Linux ou MsDos, une interface graphique pour Windows, etc.
Il sera donc possible en théorie de convertir de manière automatique et sans intervention manuelle ou presque (l'utilisation de makefiles pour compiler les modules qui vont ensemble) n'importe quel programme en C, pourvu qu'il respecte le language C standard (pas de code _asm inline par exemple) et qu'il n'utilise pas des fonctions systèmes non supportés.
L'idée de pouvoir convertir ainsi des applications, des librairies, des jeux écrits à l'origine en language C en C#/Silverlight me paraît assez séduisante... De plus rien n'empêchera d'utiliser ces programmes avec d'autres programmes écrits en Silverlight mais aussi de les modifier à loisir une fois convertis en C#.
Bien entendu, je vous tiendrai informés sur le blog de toutes les avancées techniques sur ce projet ainsi que des résultats obtenus. A bientôt pour un portage 100% automatique de Quake en Silverlight 
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :