Mostrando entradas con la etiqueta compilador. Mostrar todas las entradas
Mostrando entradas con la etiqueta compilador. Mostrar todas las entradas

lunes, 6 de abril de 2026

LLVM: una herramienta para crear compiladores

LLVM (Low Level Virtual Machine) es una infraestructura de compiladores que permite transformar y optimizar código de distintos lenguajes de programación. No es un compilador único, sino un conjunto de herramientas que sirven para crear compiladores, analizadores y optimizadores de código.

En este blog ya hemos hablado, burdamente, de lo que conforma un lenguaje de programación. Sin embargo, sería importante recordar algunos detalles.

Un compilador es un programa que traduce el código que escribes (por ejemplo en C, Rust o Java) a un lenguaje que la computadora entiende directamente: el código máquina. Ejemplo, tú escribes una receta en español, el compilador la traduce al "idioma" de la computadora para que pueda cocinarla.

Un analizador de código es la parte del compilador que lee y entiende tu código fuente. Verifica que lo que escribiste tenga sentido (sintaxis correcta, variables bien usadas, etc.). Es como un profesor que revisa tu redacción y te dice si las frases están bien construidas antes de publicarlas.

Un optimizador de código es una herramienta que mejora el código para que sea más rápido o consuma menos recursos. Reorganiza instrucciones, elimina pasos innecesarios y aprovecha mejor el hardware. Llanamente, el optimizador busca el camino más corto y rápido para llegar a algo.

Estos tres se relacionan de esta manera:

  • Analizador: entiende tu código y lo valida. 
  • Compilador: traduce ese código a instrucciones que la máquina entiende. 
  • Optimizador: pule esas instrucciones para que se ejecuten de la mejor manera posible.

LLVM al ser un conjunto de bibliotecas y herramientas que permiten construir compiladores y optimizadores no está limitado a un lenguaje específico; se usa en C, C++, Rust, Swift, Julia, Haskell, entre otros. Está diseñado como bloques reutilizables (frontends, optimizadores, backends).

¿Para qué sirve LLVM?

  • Compilación: Convierte código fuente en código máquina optimizado. 
  • Optimización: Mejora el rendimiento del código en tiempo de compilación y ejecución. 
  • Portabilidad: Permite que un mismo lenguaje se ejecute en múltiples plataformas. 
  • Experimentación: Ideal para investigadores y desarrolladores que quieren crear nuevos lenguajes o probar técnicas de optimización.
  • Ejemplo real: El compilador Clang (para C/C++/Objective-C) está construido sobre LLVM.

¿Qué hace LLVM?

  1. Traduce código a un lenguaje intermedio (LLVM IR) que es más fácil de analizar y optimizar. 
  2. Optimiza ese código para que sea más rápido y eficiente. 
  3. Genera código máquina para distintas arquitecturas (x86, ARM, etc.), lo que permite que tu programa corra en diferentes dispositivos.

LLVM es como el motor universal que hace funcionar muchos lenguajes modernos (Rust, Swift, Julia, Haskell).

Como desarrollador, puedes usarlo para compilar, optimizar y experimentar con lenguajes modernos o incluso crear el tuyo propio.

Probando LLVM

Vamos a comenzar instalandolo:

Linux:

$ sudo apt update
$ sudo apt install llvm clang

Windows:

$ choco install llvm

Vamos a crear un sencillo programa que verifique si un número entero es mayor a 100.

mayor.c

#include <stdio.h>

int main() {
    printf("\t Determinar si un número es mayor a 100 \n");
int numero = 120; if (numero > 100) { printf("El número es mayor que 100\n"); } else { printf("El número es menor o igual a 100\n"); } return 0; }

Compilams con la herramienta ``clang``:

$ clang mayor.c -o mayor

Creará un ejecutable:

$ ./mayor

Salida:

El número es mayor que 100

Ahora usaremos LLVM para ver el código intermedio (LLVM IR):

$ clang -S -emit-llvm mayor.c -o mayor.ll

Esto genera un archivo ``mayor.ll`` con el Intermediate Representation (IR). En ese archivo verás instrucciones más “básicas” que LLVM entiende, por ejemplo:

define i32 @main() {
entry:
  %numero = alloca i32, align 4
  store i32 120, i32* %numero, align 4
  %0 = load i32, i32* %numero, align 4
  %cmp = icmp sgt i32 %0, 100
  br i1 %cmp, label %if.then, label %if.else
  ...
}

Optimizamos el código:

$ clang -S -emit-llvm mayor.c -o mayor.ll

Esto genera una versión optimizada del IR, eliminando redundancias y mejorando el rendimiento.

Enlaces:

https://llvm.org/
https://codemonkeyjunior.blogspot.com/2024/02/lo-que-conforma-un-lenguaje-de.html
https://clang.llvm.org/
https://es.wikipedia.org/wiki/LLVM
https://www.reddit.com/r/ProgrammingLanguages/comments/kl7n03/what_is_meant_by_llvm_and_a_language_frontend/
https://aosabook.org/en/index.html

lunes, 5 de febrero de 2024

Lo que conforma a un lenguaje de programación

 


De la Wikipedia:

Los lenguajes de programación están formados por un conjunto de símbolos (llamado alfabeto), reglas gramaticales (léxico/morfológicas y sintácticas) y semánticas, que en conjunto definen las estructuras válidas del lenguaje y su significado.

Es un sistema de notación para escribir programas de computadora; se describe por su sintaxis (forma) y semántica (significado), además de tener al menos una implementación en forma de compilador o intérprete, lo que permite ejecutar programas escritos en ese lenguaje.

La explicación simplista sería: es un lenguaje para hacer que la computadora obedezca nuestras instrucciones previamente creadas.

¿Pero qué diantres es lo que conforma a un lenguaje de programación?

Lo que conforma a un lenguaje de programación es:

  • Reglas sintácticas: Conjunto de reglas que definen las secuencias correctas de los elementos.
  • Reglas semánticas: Es el significado de las instrucciones en un lenguaje de programación y determina cómo se deben interpretar y ejecutar las acciones.
  • Un intérprete o compilador.

La sintaxis describe cómo se ve, mientras que la semántica describe lo que debe hacer. Para mayor información ver esto: https://gus.chat/blog/semantica-en-programacion-tipos-y-usos-mas-comunes/

Ejemplos de sintaxis:

def var nombreVar as type no-undo.

function nameFunction returns type(param1 as type, param2 as type, paramN as type):
   /*  do stuff */
end.

Como podemos ver, la sintaxis nos indica cómo debemos escribir las instrucciones, ya sea declaración de variables, operaciones aritméticas y/o lógicas y la definición de funciones o procedimientos. En este caso vemos la forma en que debemos definir variables y crear una función (para el ejemplo usamos OpenEdge ABL, en otros lenguajes la sintaxis cambia).

Ejemplo de semántica:

Si tomamos los ejemplos de arriba. En primer lugar vemos la declaración de variables. Una variable debe ser definida, tener un valor y ser implementada. En el caso de una función deberá tener un propósito definido, realizar alguna operación y ser invocada. La semántica nos dice el signififcado, el por qué de una instrucción.



Intérprete: es un traductor. Verificará las reglas sintácticas (como se escribe la instrucción) y semánticas (el significado de la instrucción) y traducirá a un lenguaje que la máquina (computadora) podrá ejecutar. Python y Perl usan intérpretes que ejecutan el código.


Compilador: es un conversor. No solo verificará las reglas sintácticas y semánticas sino que además creará un código ejecutable para que la computadorá lo ejecute. Relativamente más lento que un intérprete, pero más seguro. Esto porque no solo se conforma con interpretar sino en verificar el tipo de datos que se definen a la hora de escribir las instrucciones. C, C++ y Java usan compiladores que crean un ejecutable (ver la imagen de arriba).

Si te interesa crear un compilador podrías checar este sitio:

https://www.antlr.org/

Pepepepero ¿eso es todo?

No. Cualquier lenguaje de programación que se digne llamar así tendrá su propia sintaxis y semántica, así como paradigma (funcional, orientado a objetos, etc.). Como definir tipos de datos (en el caso de los lenguajes de tipado estático), estructuras de control (top-down, secuenciales e iterativas). Manejo de errores o excepciones. Aserciones. Estructuras de control (vectores, listas, nodos, mapas, árboles) . Manipulación de archivos. Acceso a bases de datos. Entre otras cosas más.

Enlaces:

https://www.antlr.org/

https://www.compilers.iecc.com/crenshaw/

domingo, 13 de marzo de 2022

Katas de programación en C [2da parte]

"Santos remolinos Batman, éste martini tiene un sabor que no logro identificar"

 

En el anterior post realizamos algunos ejercicios de programación en C. En ésta ocasión continuaremos con los ejercicios.

Ejemplo 1. Obtener el número binario de un número decimal mayor a cero.

binario.c

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<ctype.h>
#define MAX 100

int main(int argc, char *argv[]){
  char binario[MAX];
  int decimal;
  int residuo;
  char c;
  do{
    puts("Introduce numero decimal:");
    scanf("%d",&decimal);
  }while(decimal <= 0);
  printf("Decimal: %d\n",decimal);

  while(decimal >= 2){
    residuo = decimal % 2;
    decimal = (int) decimal/2;
    c = (char)residuo+'0';
    strcat(binario, &c);
  }
  c = (char)decimal+'0';
  strcat(binario, &c);
  char binarioFinal[MAX];
  int i, j,k;
  for(i=0; binario[i] != '\0'; i++ );
    j=i-1;
    for(k=0; k<=i; k++){
        binarioFinal[k]=binario[j];
        j--;
    }
    printf("Binario final:%s\n",binarioFinal);
  return 0;
}

En éste ejemplo podemos ver la descomposición de un número decimal a su forma binaria (0,1). Obtenemos el residuo de la división (%) y lo vamos almacenando en una cadena string, incluimos el último dígito decimal, para al final mostrar la cadena final (en reversa). Ej. Si el número introducido es 3 deberá mostrar su equivalente en binario que sería: 11.

Compilamos y creamos el ejecutable.

$ gcc binario.c -o ejecutable.exe

$ ./ejecutable.exe

Ejemplo 2. Crear un programa que tome dos números enteros mayores a cero que realice las seis operaciones básicas de la aritmética: suma, resta, multiplicación(producto) , división, potenciación y radicación.

matematicas.c

#include<stdio.h>
#include<stdlib.h>
#include<ctype.h>
#include<math.h>

int suma(int,int);
int resta(int,int);
int producto(int,int);
double division(int,int);
double potencia(double, int);
int radicacion(int);

int suma(int a, int b){
  return a+b;
}

int resta(int a, int b){
  return a-b;
}

int producto(int a, int b){
  return a*b;
}

double division(int a,int b){
  return (double) a/b;
}

double potencia(double b, int p){
  int i = 1;
  double pot = 1.0;
  while(i <= p){
    pot = pot * b;
    i++;
  }
  return pot;
}

int radicacion(int a){
  return sqrt((double) a);
}

int main(int argc, char *argv[]){
  int a = 0, b = 0, mySuma = 0, myResta = 0, myProd = 0;
  double myDiv = 0.0, myPot = 0.0;
  do{
    system("clear");
    puts("Introduce dos numeros enteros mayor a cero:");
    printf("a = %d y b = %d\n",a,b);
    scanf("%d %d",&a,&b);
  }while(a <= 0 || b <= 0);
  printf("Numeros introducidos: a=%d y b=%d\n",a,b);
  mySuma = suma(a,b);
  myResta = resta(a,b);
  myProd = producto(a,b);
  myDiv = division(a,b);
  myPot = potencia((double) a,b);
  printf("Suma = %d\n",mySuma);
  printf("Resta = %d\n",myResta);
  printf("Producto = %d\n",myProd);
  printf("Division = %.3lf\n",myDiv);
  printf("Potencia = %.0lf\n",myPot);
  printf("Raiz de  a = %d es: %d\n",a, radicacion(a));
  printf("Raiz de  b = %d es: %d\n",b, radicacion(b));
  return 0;
}

Creamos seis cabeceras y sus implementaciones (funciones) para realizar las operaciones aritméticas básicas. Obligamos a introducir dos número enteros positivos. Si algún número es cero el bucle do-while continuará hasta que los dos números sean mayores a cero.

Para poder compilar y crear el ejecutable (esto si usas GCC):

$ cc matematicas.c -lm -o ejecutable.exe 

Ejemplo 3. Crear un programa que tome tres parámetros, dos decimales y un operador (+, -, x, / , %) para realizar las operaciones de: suma, resta, producto, división y resto/residuo. 

operador.c

#include<stdio.h>
#include<ctype.h>
#include<curses.h>
#include<string.h>
#include<stdlib.h>

int operacion(int, int, char);

int operacion(int a, int b, char opc){
  int resultado = 0;
  if(opc == '+'){
    puts("\tSuma");
    resultado = a+b;
  }

  if(opc == '-'){
    puts("\tResta");
    resultado = a-b;
  }

  if(opc =='x'){
    puts("\tProducto");
    resultado = a*b;
  }

  if(opc == '/'){
    puts("\tDivision");
    if(b <= 0){
      resultado = a;
    }else{
      resultado = a/b;
    }
  }

  if(opc == '%'){
    puts("\tResiduo");
    resultado = a%b;
  }
  
  return resultado;
}

int main(int argc, char *argv[]){
  if(argc >= 3){
    int a = atoi(argv[1]);
    int b = atoi(argv[2]);
    char c = argv[3][0]; // tomar el primer caracter de la fila 3, columna 0
    printf("Entrada \t a = %d b = %d , operador: %c\n",a,b,c);
    printf("Resultado: %d\n",operacion(a,b,c));
  }else{
    puts("\tDebes introducir tres argumentos: num1 num2 operador\n");
  }
  return 0;
}

Compilar y ejecutar:

$ gcc operador.c -o ejecutable.exe
$ ./ejecutable.exe 2 3 +
    Suma
Resultado: 5

Enlaces:

http://codemonkeyjunior.blogspot.com/2021/08/vale-la-pena-aprender-c-y-o-c.html

domingo, 15 de agosto de 2021

¿Vale la pena aprender C y o C++?


En lenguaje C tuvo su época de gloria en en los años 70's. Con el advenimiento de lenguajes de programación como Visual Basic, Java, PHP y otros más es bueno recordarlo. C es un lenguaje que aún vale la pena darle un vistazo.

En este post usaremos el compilador GCC:
$ gcc --version
gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

holamundo.c
#include<stdio.h>
#define FIN 0

int main(int argc, char *argv[]){
  puts("Hola, mundo en C");
  return FIN;
}
Compilar:
$ gcc holamundo.c -o ejecutable
Ejecutar:
$ ./ejecutable

La evolución directa del lenguaje no fue ni Java o C#, fue C++. Si con C la curva de aprendizaje era alta, con C++ aumento de manera considerable. Podíamos hacer exactamente los mismo pero ahora tenia otras cosas más por aplicar como lo es: la programación orientada a objetos. Objetos, clases, herencia, polimorfismo era conceptos difíciles de aprender a quienes solo conocían los paradigmas funcional o estruturado. La primera diferencia es: la extensión de un programa en C++. Ya que se añade dos "pp" a la extensión clásica, quedando ".cpp" en vez de solo ".c". Otra diferencia es que introduce el concepto de manejo de excepciones, plantillas y ,como se mencionó antes, la programación orientada a objetos.
holamundo.cpp
#include<iostream>
using namespace std;

int main(int argc, char *argv[]){
  cout << "Hola, mundo en C++"<<endl;
  return 0;
}
En este caso usaremos el compilador G++:
$ g++ --version
g++ (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Compilamos:
$ g++ holamundo.cpp -o ejecutable
Ejecutamos:
$ ./ejecutable

Con C comprenderemos algunos conceptos como:

  • Punteros
  • Asignación dinámica de memoria
  • Tipos definidos y estructuras
Punteros y asignación dinámica de memoria
// Puntero, variable que almacena la memoria de otra variable
int entero = 33;
int *puntero_entero = NULL;

// Asignación dinámica de memoria
puntero_entero = (int *)malloc(1*sizeof(int));

*puntero_entero = &entero;
printf("entero:%d\n",entero);
printf("puntero:%p\n",puntero_entero);
Estructuras y tipos definidos
// Creamos un tipo definido
typedef struct Nodo Nodo;

// Creamos una estructura
struct Nodo{
  long item;
  int valor;
  Nodo *izq;
  Nodo *der;
};

Nodo *raiz = NULL;
// asignamos memoria
raiz = (Nodo *)malloc(sizeof(Nodo));

raiz->item=(long)(12000);
raiz->valor=123;
raiz->izq = raiz->der = NULL;
printf("%10ld : %10d\n", raiz->item, raiz->valor);

// ... 

Con C++ conoceremos:

  • Clases y objetos
  • Manejo de excepciones
  • Plantilas
Clases y objetos
#include<iostream>
using namespace std;

class X{
   public:
    X();
    virtual ~X();
};

X::X(){
  cout << "Objeto X creado"<<endl;
}

X::~X(){
  cout << "Objeto X destruido"<<endl;
}

int main(){
  X myObj;
  return 0;
}

Un pequeño consejo:

Para quienes quieran aprender a programar sería bueno que le dieran un vistazo al lenguaje C (en lugar de Python o cualquier otro "más sencillo").

Muchos juran y perjuran que Python es el mejor lenguaje de programación para principiantes, pero creo que no es así. Existen aspectos de la programación que en el lenguaje C te enseñará para después pasar a otros lenguajes. Si sabes lo que es un puntero y para qué se usa, si sabes lo que es la asignación dinámica de memoria y para qué se usa y si sabes para qué y cómo usar las estructuras de datos entonces ya estarás listo para pasar a Java, Python o cualquier otro lenguaje compilado o interpretado. 

Entendemos que es más fácil y entendible escribir un hola mundo de esta forma:

print("Hola, mundo en Python")

que en la forma de arriba. Pero cuando enfrentes proyectos hechos en lenguajes como Java, C++ y/o C# será más difícil entender la lógica y la complejidad del código.

Enlaces:
http://conclase.net/c

sábado, 20 de marzo de 2021

Regiones en C#

CSharp tiene características muy útiles para los desarrolladores .NET y en este post hablaremos de una de ellas, las regiones. La documentación oficial nos dice que las #region 
Permite especificar un bloque de código que puede expandir o contraer cuando usa la función de esquema del editor de código. En archivos de código más largos, es conveniente poder contraer u ocultar una o más regiones para que pueda concentrarse en la parte del archivo en la que está trabajando actualmente.
Un ejemplo del uso del bloque #region ... #endregion.
#region Tipos anonimos
var entero = 334;
var strNumero = "MX-34455";
var booleano = true;
var real = 123.9;
var flotante = 34.7f;
var caracter = '\u0432';
var listaEnteros = new List<int>();
#endregion

Las regiones forman parte de las directivas del pre-procesador de C#.  Otro ejemplo de estas directivas es #if ... #endif.

#if DEBUGEANDO
  Console.Title = "Debugeando el Code";
  Console.WriteLine("En modo DEBUG");
#endif

Algunos consideran que es muy útil el uso de estas directivas y otros más que es una perdida de tiempo. Que no son nada útiles. Cada quien puede dar su opinión. A mi parecer sino afecta el funcionamiento del código está bien.

 
Links:
https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/preprocessor-directives/preprocessor-region
https://geeks.ms/jorge/2018/07/25/el-uso-de-regions-en-c-apesta/

Jai un lenguaje de programación inspirado en C++

Hoy hablaremos de un nuevo lenguaje de programación llamado Jai . Se trata de un lenguaje de programación que está desarrolland...

Etiquetas

Archivo del blog