En el post anterior
, vimos cómo el compilador transforma SSA optimizado en bytes de código máquina y los empaqueta en archivos objeto. Cada archivo .o contiene el código compilado de un paquete—con instrucciones de máquina, definiciones de símbolos y relocalizaciones que marcan direcciones que necesitan ser corregidas.
Pero tu programa no es solo un paquete. Incluso un simple “hello world” importa fmt, que a su vez importa io, os, reflect y docenas de otros paquetes. Cada paquete se compila por separado en su propio archivo objeto. Ninguno de estos archivos puede ejecutarse por sí solo.
En el artículo anterior
, exploramos cómo el compiler transforma IR en SSA—una representación donde cada variable se asigna exactamente una vez. Vimos cómo el compiler construye SSA usando Values y Blocks, luego ejecuta más de 30 pases de optimización. Observamos cómo el pase de lowering convierte operaciones genéricas en instrucciones específicas de arquitectura como AMD64ADDQ y ARM64ADD.
Ahora estamos en la recta final. El compiler ha optimizado SSA con operaciones específicas de arquitectura. Lo único que queda es convertir esas operaciones en bytes de código máquina.
En el artículo anterior
, exploramos el IR—el formato de trabajo del compilador donde ocurren la desvirtualización, el inlining y el análisis de escape. El IR optimiza tu código a alto nivel, tomando decisiones inteligentes sobre qué funciones hacer inline y dónde deben vivir las variables, el heap o el stack.
Pero el IR todavía se parece mucho a tu código fuente. Tiene variables que pueden asignarse múltiples veces, flujo de control complejo con bucles y condicionales, y operaciones que se mapean directamente a la sintaxis de Go.
En los artículos anteriores
, hemos explorado cómo el compilador de Go procesa tu código: el scanner lo divide en tokens, el parser construye un Abstract Syntax Tree, el comprobador de tipos lo valida todo, y el formato Unified IR
serializa el AST con tipos en una representación binaria compacta.
Ahora llegamos a un punto crítico de transformación. El compilador toma ese Unified IR—ya sea recién serializado desde tu código o cargado desde un archivo de caché—y lo deserializa directamente en nodos IR. Aquí es donde tu código fuente se convierte realmente en el formato de trabajo del compilador.
En el artículo anterior
, exploramos cómo el comprobador de tipos del compilador de Go analiza tu código. Vimos cómo resuelve identificadores, verifica la compatibilidad de tipos y asegura que tu programa sea semánticamente correcto.
Ahora que tenemos un AST completamente verificado en tipos, el siguiente paso lógico sería generar la Representación Intermedia (IR) del compilador—la forma que utiliza para optimización y generación de código. Pero aquí hay algo interesante: el compilador de Go no transforma inmediatamente el AST en IR. En su lugar, toma lo que podría parecer un desvío—serializa el AST verificado en tipos en un formato binario, luego lo deserializa de vuelta a nodos IR.
En los artículos anteriores
, exploramos el scanner—que convierte código fuente en tokens—y el parser
—que toma esos tokens y construye un Árbol de Sintaxis Abstracta.
En artículos futuros, cubriré la Representación Intermedia (IR)—cómo el compilador transforma el AST en una forma intermedia más low level. Pero antes de que podamos llegar ahí, necesitamos hablar sobre dos pasos intermedios cruciales: verificación de tipos (este artículo) y el Unified IR (que cubriré en un artículo separado pronto).
En el artículo anterior del blog
, exploramos el scanner—el componente que convierte tu código fuente de un flujo de caracteres en un flujo de tokens.
Ahora estamos listos para el siguiente paso: el parser.
Aquí está el desafío que resuelve el parser: ahora mismo, tenemos una lista plana de tokens sin relaciones entre ellos. El scanner nos dio package, main, {, fmt, ., Println… pero no tiene idea de que Println pertenece al paquete fmt, o que toda la declaración fmt.Println("Hello world") vive dentro de la función main.
Esta es parte de una serie donde te guiaré a través del compilador completo de Go, cubriendo cada fase desde código fuente hasta ejecutable. Si alguna vez te preguntaste qué sucede cuando ejecutas go build, estás en el lugar correcto.
Nota: Este artículo está basado en Go 1.25.3. El funcionamiento interno del compilador pueden cambiar en versiones futuras, pero los conceptos centrales probablemente permanecerán iguales.
Voy a usar el ejemplo más simple posible para guiarnos a través del proceso—un programa clásico “hola mundo”:
Este sitio web utiliza cookies para analizar el tráfico y mejorar tu experiencia.
Política de privacidad