Todos los compiladores que
generan código para la plataforma .NET no generan código máquina para CPUs x86
ni para ningún otro tipo de CPU concreta, sino que generan código escrito en el
lenguaje intermedio conocido como Microsoft Intermediate Lenguage (MSIL) El CLR
da a las aplicaciones las sensación de que se están ejecutando sobre una
máquina virtual, y precisamente MSIL es el código máquina de esa máquina
virtual. Es decir, MSIL es el único código que es capaz de interpretar el CLR,
y por tanto cuando se dice que un compilador genera código para la plataforma .NET
lo que se está diciendo es que genera MSIL.
MSIL ha sido creado por Microsoft
tras consultar a numerosos especialistas en la escritura de compiladores y
lenguajes tanto del mundo académico como empresarial. Es un lenguaje de un
nivel de abstracción mucho más alto que el de la mayoría de los códigos máquina
de las CPUs existentes, e incluye instrucciones que permiten trabajar
directamente con objetos (crearlos, destruirlos, inicializarlos, llamar a
métodos virtuales, etc.), tablas y excepciones (lanzarlas, capturarlas y
tratarlas)
Ya se comentó que el compilador
de C# compila directamente el código fuente a MSIL, que Microsoft ha
desarrollado nuevas versiones de sus lenguajes Visual Basic (Visual Basic.NET)
y C++ (C++ con extensiones gestionadas) cuyos compiladores generan MSIL, y que
ha desarrollado un intérprete de JScript (JScript.NET) que genera código MSIL.
Pues bien, también hay numerosos terceros que han anunciado estar realizando
versiones para la plataforma .NET de otros lenguajes como APL, CAML, Cobol,
Eiffel, Fortran, Haskell, Java, Mercury, ML, Mondrian, Oberon, Oz, Pascal,
Perl, Python, RPG, Scheme y Smalltalk.
La principal ventaja del MSIL es
que facilita la ejecución multiplataforma y la integración entre lenguajes al
ser independiente de la CPU y proporcionar un formato común para el código
máquina generado por todos los compiladores que generen código para .NET. Sin
embargo, dado que las CPUs no pueden ejecutar directamente MSIL, antes de
ejecutarlo habrá que convertirlo al código nativo de la CPU sobre la que se
vaya a ejecutar. De esto se encarga un componente del CLR conocido como
compilador JIT (Just-In-Time) o jitter que va convirtiendo dinámicamente el
código MSIL a ejecutar en código nativo según sea necesario. Este jitter se distribuye
en tres versiones:
- jitter
normal: Es el que se suele usar por defecto, y sólo compila el código
MSIL a código nativo a medida que va siendo necesario, pues así se ahorra
tiempo y memoria al evitarse tener que compilar innecesariamente código
que nunca se ejecute. Para conseguir esto, el cargador de clases del CLR
sustituye inicialmente las llamadas a métodos de las nuevas clases que
vaya cargando por llamadas a funciones auxiliares (stubs) que se encarguen
de compilar el verdadero código del método. Una vez compilado, la llamada
al stub es sustituida por una llamada directa al código ya compilado, con
lo que posteriores llamadas al mismo no necesitarán compilación.
- jitter
económico: Funciona de forma similar al jitter normal solo que no
realiza ninguna optimización de código al compilar sino que traduce cada
instrucción MSIL por su equivalente en el código máquina sobre la que se
ejecute. Esta especialmente pensado para ser usado en dispositivos
empotrados que dispongan de poca potencia de CPU y poca memoria, pues
aunque genere código más ineficiente es menor el tiempo y memoria que
necesita para compilar. Es más, para ahorrar memoria este jitter puede
descargar código ya compilado que lleve cierto tiempo sin ejecutarse y
sustituirlo de nuevo por el stub apropiado. Por estas razones, este es el
jitter usado por defecto en Windows CE,
sistema operativo que se suele incluir en los dispositivos
empotrados antes mencionados.
Otra utilidad
del jitter económico es que facilita la adaptación de la plataforma .NET a
nuevos sistemas porque es mucho más sencillo de implementar que el normal. De
este modo, gracias a él es posible desarrollar rápidamente una versión del CLR
que pueda ejecutar aplicaciones gestionadas aunque sea de una forma poco
eficiente, y una vez desarrollada es posible centrarse en desarrollar el jitter
normal para optimizar la ejecución de las mismas.
- prejitter: Se
distribuye como una aplicación en línea de comandos llamada ngen.exe mediante la que es posible compilar
completamente cualquier ejecutable o librería (cualquier ensamblado en
general, aunque este concepto se verá más adelante) que contenga código
gestionado y convertirlo a código nativo, de modo que posteriores
ejecuciones del mismo se harán usando esta versión ya compilada y no se
perderá tiempo en hacer la compilación dinámica.
La actuación de un jitter durante
la ejecución de una aplicación gestionada puede dar la sensación de hacer que
ésta se ejecute más lentamente debido a que ha de invertirse tiempo en las
compilaciones dinámicas. Esto es cierto, pero hay que tener en cuenta que es
una solución mucho más eficiente que la usada en otras plataformas como Java,
ya que en .NET cada código no es interpretado cada vez que se ejecuta sino que
sólo es compilado la primera vez que se llama al método al que pertenece. Es
más, el hecho de que la compilación se realice dinámicamente permite que el
jitter tenga acceso a mucha más información sobre la máquina en que se
ejecutará la aplicación del que tendría cualquier compilador tradicional, con
lo que puede optimizar el código para ella generado (por ejemplo, usando las
instrucciones especiales del Pentium III si la máquina las admite, usando
registros extra, incluyendo código inline,
etc.) Además, como el recolector de basura de .NET mantiene siempre compactada
la memoria dinámica las reservas de memoria se harán más rápido, sobre todo en
aplicaciones que no agoten la memoria y, por tanto, no necesiten de una
recolección de basura. Por estas razones, los ingenieros de Microsoft piensan
que futuras versiones de sus jitters podrán incluso conseguir que el código
gestionado se ejecute más rápido que el no gestionado.