[Tutorial de back-end] Ve a los principios del compilador de Golang

El directorio es el siguiente:

  • Conoce ir a construir

  • Principios del compilador

  • Análisis léxico

  • Análisis gramatical

  • Análisis semántico

  • Generación de código intermedio

  • Optimización de código

  • Generación de código de máquina

  • Resumen

Conoce ir a construir

Cuando Qiaoxia go buildtiempo, se escribe archivos de código fuente en realidad experimentó lo que las cosas? Finalmente se convirtió en un archivo ejecutable.

Este comando compilará el código go. ¡Echemos un vistazo al proceso de compilación de go today!

En primer lugar, conozcamos la clasificación del archivo fuente del código de go

  • Archivo fuente de comando: en resumen, el archivo que contiene la función principal, generalmente un archivo por proyecto, y nunca he pensado en un proyecto que requiera dos archivos fuente de comando

  • archivo de fuente de prueba: que escribimos unidad de código de prueba es a _test.gofinales

  • Archivos de código fuente de la biblioteca: los archivos de código fuente de la biblioteca sin las características anteriores, como muchos paquetes de terceros que utilizamos pertenecen a esta parte

go buildEste comando se utiliza para compilar uno de los archivos de origen de mando , y depende de los archivos de origen de la biblioteca . La siguiente tabla es un resumen de algunas opciones de uso común.

Opcional Explicación
-una Reconstruya todos los archivos fuente de comandos y los archivos fuente de la biblioteca, incluso los últimos
-norte Imprima todos los comandos involucrados en la compilación, pero no se ejecutarán, lo cual es muy conveniente para nosotros para aprender
-carrera Permite la detección de condiciones de carrera, las plataformas compatibles son limitadas
-X Imprima el nombre utilizado durante la compilación, la diferencia entre él y -n es que no solo imprime sino que también se ejecuta

A continuación, use un programa hello world para demostrar las opciones de comando anteriores.

imagen

Si el código para llevar a cabo lo anterior go build-nnos fijamos en la salida:

imagen

Analizar todo el proceso de ejecución.

imagen

Esta parte es el núcleo de la compilación compile, buildidy linkel archivo ejecutable será compilado por los tres comandos a.out.

Entonces mvordenó el traslado a.out a la carpeta actual, y cambiar el archivo de proyecto con el mismo nombre (donde también se puede especificar el nombre de su preferencia).

Más adelante en el artículo, estamos hablando principalmente es decir compile, buildid、 linkestos tres comandos que intervienen en el proceso de compilación.

Principios del compilador

Esta es la ruta del código fuente del compilador go: https://github.com/golang/go/tree/master/src/cmd/compile

imagen

Como puede ver en la imagen de arriba, todo el compilador se puede dividir en: compilación front-end y compilación back-end; ahora veamos qué hace el compilador en cada etapa. Comencemos con el frente.

Análisis léxico

El análisis léxico es simplemente traducir el código fuente en el que escribimos Token. ¿Qué significa esto?

Para entender la Golangtraducción del código fuente para el Tokenproceso, nos fijamos en una pieza de un solo código traducido para una misma situación.

imagen

Figura lugares importantes que se han anotado, pero todavía hay un par de palabras para decir lo que nos fijamos en el código Imagínese, si desea que nuestro propio para alcanzar esta "traducción" y cómo el programa debe reconocer Tokenque?

Primero, clasifiquemos primero los tipos de tokens de Go: nombres de variables, literales, operadores, separadores y palabras clave. Necesitamos dividir un montón de código fuente de acuerdo con las reglas, que en realidad es la segmentación de palabras. Mirando el código de ejemplo anterior, podemos formular una regla de la siguiente manera:

  1. Identifica el espacio, si es un espacio, puedes dividir una palabra;

  2. Encuentro (, )'<', '>', etc. Estos operadores especiales cuando un número de palabras;

  3. Encuentro "o participio de medida literal numérico.

Por simple análisis anterior, podemos ver el código fuente, de hecho, a su vez, Tokenen realidad no es muy complicado, puede escribir su propio código para alcanzarlos. Por supuesto, hay muchos analizador léxico más común de lograr a través de la manera regular, al igual que el Golanguso temprano es lex, en una versión posterior antes de cambiar a la utilización por recorrer para lograr su propia cuenta.

Análisis gramatical

Tras el análisis léxico, obtenemos que la Tokensecuencia, que servirá como la entrada del analizador. Luego, después del proceso de generación de ASTla estructura como de salida.

El llamado análisis de sintaxis es para Tokenser convertido a un programa reconocido por la estructura gramatical, pero ASTes esta una representación abstracta de gramática. Hay dos formas de construir este árbol.

  1. Este enfoque de arriba hacia abajo en primer lugar construir el nodo raíz, y luego iniciar la exploración Token, la cara STRINGo de otros tipos saben que esto está haciendo el tipo indicado, funcsignifica que la función se declara. Simplemente siga escaneando hasta el final del programa.

  2. El enfoque ascendente es lo opuesto al enfoque anterior: primero construye el subárbol y luego lo ensambla en un árbol completo.

ir lenguaje de análisis utilizando el enfoque de abajo hacia arriba para construir AST, aquí vamos vistazo a la lengua por Tokenla estructura de árbol se parece.

imagen

He marcado todas las partes interesantes en el texto. Usted encontrará que cada ASTnodo del árbol se asocia con una Tokenubicación física correspondiente.

Después de construir el árbol, podemos ver que los diferentes tipos están representados por las estructuras correspondientes. Si hay errores gramaticales o léxicos aquí, no se resolverán. Porque hasta ahora, todo es procesamiento de cadenas.

Análisis semántico

Etapa dentro de la relación de sintaxis compilador después de que el análisis se llama análisis semántico , e ir en esta etapa se llama la verificación de tipos , pero miré a seguir sus propios documentos, de hecho, no tienen mucha diferencia, seguimos la especificación de la corriente principal para escribir este Proceso.

Entonces, ¿qué hace exactamente el análisis semántico (verificación de tipo)?

AST Después de la generación, el análisis semántico lo usará como entrada, y algunas operaciones relacionadas también se reescribirán directamente en este árbol.

El primero se Golangmenciona en la comprobación de la documentación tipo, así como la inferencia de tipos, para ver el tipo de partidos, si la conversión implícita (ir sin conversión implícita). Como dice el siguiente texto:

El AST luego se verifica por tipo. Los primeros pasos son la resolución de nombres y la inferencia de tipos, que determinan qué objeto pertenece a qué identificador y qué tipo tiene cada expresión. La verificación de tipo incluye ciertas verificaciones adicionales, como "declarada y no utilizada", así como determinar si una función termina o no.

La idea principal es: después de que se genera el AST, la verificación de tipos (es decir, el análisis semántico del que estamos hablando aquí), el primer paso es realizar la verificación de nombres y la inferencia de tipos, firmar el identificador al que pertenece cada objeto y de qué tipo tiene cada expresión. La verificación de tipo también tiene que hacer otras verificaciones, como "declarar no utilizado" y determinar si la función se cancela.

Ciertas transformaciones también se realizan en el AST. Algunos nodos se refinan en función de la información de tipo, como las adiciones de cadenas que se dividen del tipo de nodo de adición aritmética. Algunos otros ejemplos son la eliminación de código muerto, la función de llamada en línea y el análisis de escape.

Este párrafo dice: AST también se convertirá, y algunos nodos se simplificarán de acuerdo con la información de tipo, como dividir la suma de cadenas del tipo de nodo de suma aritmética. Otros ejemplos son la eliminación del código muerto, la inclusión de llamadas a funciones y el análisis de escape.

Los dos párrafos anteriores son de compilación de golang: https://github.com/golang/go/tree/master/src/cmd/compile

Una cosa más que decir aquí, a menudo necesitamos prohibir la inclusión en línea al depurar código, que en realidad es esta etapa de operación.

1. `# Prohibir la restricción durante la compilación` 

2.` ir a construir -gcflags '-N -l'' 4.` 

-N Prohibir la compilación y optimización` 5.` 

-l Prohibir la inserción, la desactivación de la inserción también puede ser en cierta medida Reducir el tamaño del programa ejecutable


Después del análisis semántico, se puede demostrar que nuestra estructura de código y gramática no son un problema. Entonces, el extremo frontal del compilador es principalmente analizar la estructura AST correcta que el extremo posterior del compilador puede manejar.

A continuación, veamos qué debe hacer el backend del compilador.

La máquina solo puede entender el binario y ejecutarlo, por lo que la tarea del backend del compilador es simplemente cómo traducir AST al código de la máquina.

Generación de código intermedio

Ahora que se ha obtenido el AST, el binario requerido para la operación de la máquina. ¿Por qué no traducir directamente a binario? De hecho, hasta ahora técnicamente, no hay ningún problema en absoluto.

Sin embargo, tenemos una variedad de sistemas operativos con diferentes tipos de CPU, cada uno de los cuales puede tener un número diferente de bits; las instrucciones que los registros pueden usar también son diferentes, como conjuntos de instrucciones complejos y conjuntos de instrucciones reducidos; Antes de la compatibilidad, también necesitamos reemplazar algunas funciones de bajo nivel. Por ejemplo, usamos make para inicializar el segmento. En este momento, será reemplazado por: makeslice64o de acuerdo con el tipo pasado makeslice. Por supuesto, el reemplazo de funciones como el dolor, el canal, etc. también será reemplazado durante el proceso de generación de código intermedio. Esta parte de la operación de reemplazo se puede ver aquí

Otro valor del código intermedio es mejorar la reutilización de la compilación de back-end. Por ejemplo, hemos definido cómo debería ser un conjunto de código intermedio, por lo que la generación de código de máquina de back-end es relativamente fija. Cada idioma solo necesita completar su propio trabajo de compilación front-end. Es por eso que ahora puede ver la velocidad de desarrollar un nuevo lenguaje. La compilación es en su mayoría reutilizable.

Y para el próximo trabajo de optimización, la existencia de código intermedio tiene una importancia extraordinaria. Debido a que hay tantas plataformas, si hay un código intermedio, podemos poner algunas optimizaciones comunes aquí.

El código intermedio es también una variedad de formatos, tales como Golangel uso del código intermedio es las características de la SSA (IR), esta forma de código intermedio, el más característico es las variables más importantes siempre se definen antes de usar variables, y cada variable sólo Asignar una vez.

Optimización de código

En la documentación de compilación de go, no encontré un paso independiente para optimizar el código. Sin embargo, de acuerdo con nuestro análisis anterior, podemos ver que el proceso de optimización del código se encuentra en cada etapa del compilador. Todos harán algo dentro de su poder.

En general, además de reemplazar los ineficientes con códigos eficientes, también tenemos los siguientes tratamientos:

  • Paralelismo, aproveche al máximo las características de las computadoras multinúcleo actuales

  • Pipeline, cpu a veces puede procesar instrucciones b al mismo tiempo al procesar una instrucción

  • La selección de instrucciones, para que la CPU complete ciertas operaciones, las instrucciones deben usarse, pero la eficiencia de las diferentes instrucciones es muy diferente, y la optimización de las instrucciones se realizará aquí

  • Usando registros y caché, todos sabemos que la CPU toma el más rápido del registro, y el segundo del caché. Aquí será totalmente utilizado

Generación de código de máquina

El código intermedio optimizado se convertirá primero en código ensamblador (Plan9) en esta etapa, y el lenguaje ensamblador es solo una representación de texto del código de máquina, y la máquina no puede ejecutarlo realmente. Entonces, en esta etapa, se llamará al ensamblador, y el ensamblador llamará al código correspondiente para generar el código de máquina de destino de acuerdo con la arquitectura que establecimos al realizar la compilación.

Lo más interesante aquí es que Golangsiempre digo que mi ensamblador es multiplataforma. De hecho, también escribió código multipunto para traducir el código de máquina final. Porque él, en nuestro conjunto en el momento de la entrada GOARCH=xxxpara inicializar el procesamiento de parámetros, y, finalmente, llamar a un método específico para la preparación de la arquitectura correspondiente a generar código máquina. Este tipo de lógica de la capa superior es consistente y la lógica de la capa inferior es inconsistente. Es muy común y vale la pena aprenderlo. Echemos un breve vistazo a este proceso.

Primero mira la función de entrada cmd/compile/main.go:main()

1. `var archInits = map [string] func (* gc.Arch) {` 

2. `" 386 ": x86.Init,` 

3. `" amd64 ": amd64.Init,` 

4. `" amd64p32 ": amd64.Init, `5.` 

" arm ": arm.Init,` 

6.` "arm64": arm64.Init, `7.` 

" mips ": mips.Init,` 

8.` "mipsle": mips. Init, ` 

9.`" mips64 ": mips64.Init,` 

10.` "mips64le": mips64.Init, `11.` 

" ppc64 ": ppc64.Init,` 

12.` "ppc64le": ppc64.Init, ` 

13.`" s390x ": s390x.Init,` 

14.` "wasm": wasm.Init, `15.` 

}` 17.` 

func main () {` 18.` 

// Según los parámetros del mapa anterior Seleccione el procesamiento de la arquitectura correspondiente` 

19. `archInit, ok: = archInits [objabi.GOARCH] `20.` 

if! Ok {` 21.` 

...... ` 

22.`} `

23. `// 

Pasa la correspondencia de la arquitectura de CPU correspondiente al interior` 24.` gc.Main (archInit)` 25.` 

} `

Entonces cmd/internal/obj/plist.goel procesamiento de método correspondiente a la llamada arquitectura

1. `func Flushplist (ctxt * Link, plist * Plist, newprog ProgAlloc, myimportpath string) (` 

2. `... ...` 

3. `for _, s: = range text {` 

4. `mkfwd ( s) ` 

5.` linkpatch (ctxt, s, newprog)` 6.` 

// El método de arquitectura correspondiente realiza su propia traducción de código de 

máquina` 7. `ctxt.Arch.Preprocess (ctxt, s, newprog)` 

8. ` ctxt.Arch.Assemble (ctxt, s, newprog) ` 

10.` linkpcln (ctxt, s) 

` 11.` ctxt.populateDWARF (plist.Curfn, s, myimportpath) `12.` 

}` 13.` 

} ``

Después de todo el proceso, puede ver que hay mucho trabajo por hacer en el back-end del compilador. Debe comprender la arquitectura de un determinado conjunto de instrucciones y CPU para traducir el código de la máquina correctamente. Al mismo tiempo, no puede ser justo: la eficiencia de un lenguaje es alta o baja, lo que también depende en gran medida de la optimización del backend del compilador. En particular, cuando estamos a punto de entrar en la era de la IA, nacen cada vez más fabricantes de chips. Calculo que la demanda de talentos en esta área será cada vez más vigorosa en el futuro.

Resumen

Resuma algunas ganancias al aprender esta parte del antiguo conocimiento del compilador:

  1. Sepa que toda la compilación consta de varias etapas y de lo que hace cada etapa, pero algunos detalles de la implementación más profunda de cada etapa no se conocen ni se pretende que sepan;

  2. Incluso si se trata de un compilador tan complejo, las cosas de muy bajo nivel se pueden descomponer para que cada etapa sea independiente y se vuelva reutilizable, lo que tiene cierta importancia para mí en el desarrollo de aplicaciones;

  3. La estratificación es dividir las responsabilidades, pero algunas cosas deben hacerse a nivel mundial, como la optimización, de hecho, se realizará en cada etapa; también es de cierta importancia de referencia para nuestro sistema de diseño;

  4. Aprendido Golangmuchas maneras de exposición externa es en realidad azúcar sintáctico (tales como: marca, painc etc.), el compilador me va a ayudar a traducir, pensé que era el principio del nivel de código, hazlo en tiempo de ejecución, similar al modelo de fábrica, ahora Mirarte a ti mismo es realmente ingenuo;

  5. Hice algunos preparativos básicos para la próxima preparación para aprender el mecanismo operativo de Go y la compilación de Plan9.

Recomendación de servicio

Publicado 0 artículos originales · me gusta 0 · visitas 358

Supongo que te gusta

Origin blog.csdn.net/weixin_47143210/article/details/105612027
Recomendado
Clasificación