Thiago Cantero

Tecnologia e Entretenimento

AlgoritmoGolanglinguagem de programação

Como as Linguagens se Compartam? Parte II

Olá, mundo!
Tenho um pouco distante das postagens, eu sei. No entanto retorno após longo hiato, devidos aos trabalhos e estudos.

Como falamos no Post anterior, sobre como as linguagens se comportam, falaremos sobre compiladores.

Introdução

Compiladores são responsáveis pela tradução literal das instruções que fazemos nas linguagens de programação para a compreensão, e por fim, a execução pela máquina.

Em linguagens consagradas como C#, por exemplo, temos a arquitetura .NET nela temos a CLR (Common Language Runtime), um ambiente que executa o código, parecido como a Máquina Virtual Java assim o faz, otimizando a execução dos códigos escritos em C#, F#, VB.NET. Já a linguagem Java se beneficia do maduro e estável compilador que possui hotspot, ou seja, o partes de execução do código em que estejam em execução são otimizadas pelo JIT, performando em tempo de execução.

CLR
Diagrama do Common Language Runtime .Net – disponível em: https://pt.wikipedia.org/wiki/Common_Language_Runtime#/media/Ficheiro:CLR_diag.svg

Em linguagens interpretadas, como Python, PHP, Ruby, Javascript – caso ainda tenha dúvidas sobre as diferenças entre linguagens interpretadas e compiladas, clique aqui – há melhorias substanciais utilizando o melhor dos mundos, ou seja, incorporando um compilador JIT, como foi feito a partir da versão 8 do PHP, assim como o Numba para Python.

Mas afinal, o que são compiladores?!

“If you don’t know how compilers work, then you don’t know how computers work” – Steve Yegge

Os compiladores exercem a função essencial da computação, se não fossem por eles não teríamos qualquer interatividade com a máquina, até mesmo não seria viável os interpretadores como Basic, pois não teríamos sequer uma abstração mais profunda com o Hardware, logo estamos falando sobre a fundamentação, a Pedra Angular da Computação.

Sequência de ações de um compilador

Para que possamos compreender melhor o funcionamento interno de um compilador vejamos o diagrama abaixo, na sequência, de maneira pormenorizada, será explanada cada etapa que o compilador executa.

 

Analisador Léxico

O Analisador Léxico tem a função de realizar o agrupamento de caracteres que estão dispostos em um código fonte, separando-as as palavras em Tokens, o que seriam equivalentes a uma sequência minimalista que possui significado para a linguagem, o qual o código foi escrito.

Exemplo de função em Golang:

package main

import "fmt"

func main (){
   fmt.Println("Olá, Mundo!)
}

No código acima o Analisador Léxico separa cada palavra reservada em Tokens, na linha 1 ele entende, pela convenção adotada do Golang que package define nosso pacote, na sequência main.

Na linha 2, a palavra reservada import atua para importar bibliotecas da própria linguagem, como extensões criadas por outros membros da comunidade, neste caso, importamos a biblioteca fmt, que realiza a formatação de I/O.

Uma observação importante, linhas em branco, espaços, nesta etapa são ignorados, apenas as palavras reservadas e as definições atribuídas à uma variável, por exemplo, serão devidamente transformadas e analisadas.

Continuando na linha 5, teremos nossa func que irá definir nossa função, seguida por main(), a qual é o nome de nossa função. As chaves { } delimitam a estrutura de execução, atribuições de nossa função.

Na linha 7, temos a chamada de nossa biblioteca de formatação, seguido do objeto que faz a impressão da expressão “Olá, Mundo!” fmt.Println(“Olá, Mundo!”).

Analisador Sintático

Com os Tokens devidamente agrupados, a função do Analisador Sintático é aglutinar em expressões para verificá-las se elas são válidas pela linguagem, no nosso exemplo a linguagem Go. É como uma análise gramatical de uma oração, de nossa Língua Portuguesa. Nesta fase, utiliza-se a estrutura de dados da Ávore de Abstração de Sintaxe (AST – Abstract Syntax Tree), dispondo toda a extensão do código de maneira hierarquizada, condizente com as respectivas funções de comando exercidadas pelos Tokens no contexto de expressão literal.

Veja como ficará a Árvore de nosso código, transformada em JSON:

{
  "Comments": [],
  "Decls": [
    {
      "Loc": {
        "End": {
          "Filename": "main.go",
          "Offset": 26,
          "Line": 3,
          "Column": 13
        },
        "Start": {
          "Filename": "main.go",
          "Offset": 14,
          "Line": 3,
          "Column": 1
        }
      },
      "Lparen": 0,
      "Rparen": 0,
      "Specs": [
        {
          "Loc": {
            "End": {
              "Filename": "main.go",
              "Offset": 26,
              "Line": 3,
              "Column": 13
            },
            "Start": {
              "Filename": "main.go",
              "Offset": 21,
              "Line": 3,
              "Column": 8
            }
          },
          "Path": {
            "Kind": "STRING",
            "Loc": {
              "End": {
                "Filename": "main.go",
                "Offset": 26,
                "Line": 3,
                "Column": 13
              },
              "Start": {
                "Filename": "main.go",
                "Offset": 21,
                "Line": 3,
                "Column": 8
              }
            },
            "Value": "\"fmt\"",
            "_type": "BasicLit"
          },
          "_type": "ImportSpec"
        }
      ],
      "Tok": "import",
      "_type": "GenDecl"
    },
    {
      "Body": {
        "Lbrace": 40,
        "List": [
          {
            "Loc": {
              "End": {
                "Filename": "main.go",
                "Offset": 69,
                "Line": 6,
                "Column": 29
              },
              "Start": {
                "Filename": "main.go",
                "Offset": 43,
                "Line": 6,
                "Column": 3
              }
            },
            "X": {
              "Args": [
                {
                  "Kind": "STRING",
                  "Loc": {
                    "End": {
                      "Filename": "main.go",
                      "Offset": 68,
                      "Line": 6,
                      "Column": 28
                    },
                    "Start": {
                      "Filename": "main.go",
                      "Offset": 55,
                      "Line": 6,
                      "Column": 15
                    }
                  },
                  "Value": "\"Olá, Mundo\"",
                  "_type": "BasicLit"
                }
              ],
              "Ellipsis": 0,
              "Fun": {
                "Loc": {
                  "End": {
                    "Filename": "main.go",
                    "Offset": 54,
                    "Line": 6,
                    "Column": 14
                  },
                  "Start": {
                    "Filename": "main.go",
                    "Offset": 43,
                    "Line": 6,
                    "Column": 3
                  }
                },
                "Sel": {
                  "Loc": {
                    "End": {
                      "Filename": "main.go",
                      "Offset": 54,
                      "Line": 6,
                      "Column": 14
                    },
                    "Start": {
                      "Filename": "main.go",
                      "Offset": 47,
                      "Line": 6,
                      "Column": 7
                    }
                  },
                  "Name": "Println",
                  "_type": "Ident"
                },
                "X": {
                  "Loc": {
                    "End": {
                      "Filename": "main.go",
                      "Offset": 46,
                      "Line": 6,
                      "Column": 6
                    },
                    "Start": {
                      "Filename": "main.go",
                      "Offset": 43,
                      "Line": 6,
                      "Column": 3
                    }
                  },
                  "Name": "fmt",
                  "_type": "Ident"
                },
                "_type": "SelectorExpr"
              },
              "Loc": {
                "End": {
                  "Filename": "main.go",
                  "Offset": 69,
                  "Line": 6,
                  "Column": 29
                },
                "Start": {
                  "Filename": "main.go",
                  "Offset": 43,
                  "Line": 6,
                  "Column": 3
                }
              },
              "Lparen": 55,
              "Rparen": 69,
              "_type": "CallExpr"
            },
            "_type": "ExprStmt"
          }
        ],
        "Loc": {
          "End": {
            "Filename": "main.go",
            "Offset": 71,
            "Line": 7,
            "Column": 2
          },
          "Start": {
            "Filename": "main.go",
            "Offset": 39,
            "Line": 5,
            "Column": 12
          }
        },
        "Rbrace": 71,
        "_type": "BlockStmt"
      },
      "Loc": {
        "End": {
          "Filename": "main.go",
          "Offset": 71,
          "Line": 7,
          "Column": 2
        },
        "Start": {
          "Filename": "main.go",
          "Offset": 28,
          "Line": 5,
          "Column": 1
        }
      },
      "Name": {
        "Loc": {
          "End": {
            "Filename": "main.go",
            "Offset": 37,
            "Line": 5,
            "Column": 10
          },
          "Start": {
            "Filename": "main.go",
            "Offset": 33,
            "Line": 5,
            "Column": 6
          }
        },
        "Name": "main",
        "_type": "Ident"
      },
      "Type": {
        "Func": 29,
        "Loc": {
          "End": {
            "Filename": "main.go",
            "Offset": 39,
            "Line": 5,
            "Column": 12
          },
          "Start": {
            "Filename": "main.go",
            "Offset": 28,
            "Line": 5,
            "Column": 1
          }
        },
        "Params": {
          "Closing": 39,
          "List": [],
          "Loc": {
            "End": {
              "Filename": "main.go",
              "Offset": 39,
              "Line": 5,
              "Column": 12
            },
            "Start": {
              "Filename": "main.go",
              "Offset": 37,
              "Line": 5,
              "Column": 10
            }
          },
          "Opening": 38,
          "_type": "FieldList"
        },
        "_type": "FuncType"
      },
      "_type": "FuncDecl"
    }
  ],
  "Imports": [
    {
      "Loc": {
        "End": {
          "Filename": "main.go",
          "Offset": 26,
          "Line": 3,
          "Column": 13
        },
        "Start": {
          "Filename": "main.go",
          "Offset": 21,
          "Line": 3,
          "Column": 8
        }
      },
      "Path": {
        "Kind": "STRING",
        "Loc": {
          "End": {
            "Filename": "main.go",
            "Offset": 26,
            "Line": 3,
            "Column": 13
          },
          "Start": {
            "Filename": "main.go",
            "Offset": 21,
            "Line": 3,
            "Column": 8
          }
        },
        "Value": "\"fmt\"",
        "_type": "BasicLit"
      },
      "_type": "ImportSpec"
    }
  ],
  "Loc": {
    "End": {
      "Filename": "main.go",
      "Offset": 71,
      "Line": 7,
      "Column": 2
    },
    "Start": {
      "Filename": "main.go",
      "Offset": 0,
      "Line": 1,
      "Column": 1
    }
  },
  "Name": {
    "Loc": {
      "End": {
        "Filename": "main.go",
        "Offset": 12,
        "Line": 1,
        "Column": 13
      },
      "Start": {
        "Filename": "main.go",
        "Offset": 8,
        "Line": 1,
        "Column": 9
      }
    },
    "Name": "main",
    "_type": "Ident"
  },
  "Package": 1,
  "Unresolved": [
    {
      "Loc": {
        "End": {
          "Filename": "main.go",
          "Offset": 46,
          "Line": 6,
          "Column": 6
        },
        "Start": {
          "Filename": "main.go",
          "Offset": 43,
          "Line": 6,
          "Column": 3
        }
      },
      "Name": "fmt",
      "_type": "Ident"
    }
  ],
  "_type": "File"
}

Como podemos ver, a estrutra verifica a contextualização do código a cada detalhe, separando cada item, como por exemplo o tipo de dado de saída, que no nosso exemplo será STRING, a importação da biblioteca, tudo de uma maneira bem mais assertiva e pragmática do que a etapa anterior.

Analisador Semântico

Aqui ele verifica se as regras gramaticais estão de acordo, como por exemplo se a variável foi declarada, qual o tipo agregada para ela, e etc.

package main

import "fmt"

func somar(x int, y int) {
    resultado := x + y
    fmt.Println("A soma de X + Y é: ", resultado)
}

func main() {
    somar(1, 1)
}

Neste exemplo, a função somar() recebe as variáveis x e y, declaradas como inteiro, a variável resultado tem o short assign  com a funçao aritmética de soma entre as respectivas variáveis. Na sequência imprime a variável resultado.

Na função main() é feito callback da função somar atribuindo os valores 1 e 1, que resultará em dois.

É tarefa do Analisador de Semântica a inferência de tipo das variáveis, verificar se uma variável está em escopo, se ela é compatível com demais identificadores e operadores.

Gerador de Código Intermediário

Nesta etapa, o compilador transforma nossas instruções com um código mais compreensível para a linguagem de máquina.

main_main_pc0:
        TEXT    main.main(SB), NOFRAME|ABIInternal, $40-0
        MOVL    TLS, CX
        PCDATA  $0, $-2
        MOVL    (CX)(TLS*2), CX
        PCDATA  $0, $-1
        CMPL    SP, 8(CX)
        PCDATA  $0, $-2
        JLS     main_main_pc109
        PCDATA  $0, $-1
        SUBL    $40, SP
        FUNCDATA        $0, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)
        FUNCDATA        $1, gclocals·EaPwxsZ75yY1hHMVZLmk6g==(SB)
        FUNCDATA        $2, main.main.stkobj(SB)
        MOVL    $0, main..autotmp_8+32(SP)
        MOVL    $0, main..autotmp_8+36(SP)
        LEAL    type:string(SB), AX
        MOVL    AX, main..autotmp_8+32(SP)
        LEAL    main..stmp_0(SB), AX
        MOVL    AX, main..autotmp_8+36(SP)
        MOVL    os.Stdout(SB), AX
        NOP
        LEAL    go:itab.*os.File,io.Writer(SB), CX
        MOVL    CX, (SP)
        MOVL    AX, 4(SP)
        LEAL    main..autotmp_8+32(SP), AX
        MOVL    AX, 8(SP)
        MOVL    $1, 12(SP)
        MOVL    $1, 16(SP)
        PCDATA  $1, $0
        CALL    fmt.Fprintln(SB)
        ADDL    $40, SP
        RET
main_main_pc109:
        NOP
        PCDATA  $1, $-1
        PCDATA  $0, $-2
        CALL    runtime.morestack_noctxt(SB)
        PCDATA  $0, $-1
        JMP     main_main_pc0

Acima vemos a otimização feita nesta etapa para que o código execute de maneira mais eficiente.

Otimização do Código

Nesta etapa, que antecede a final, ela otimiza o código para que seja executado com o melhor desempenho possível.

main_main_pc0:
        TEXT    main.main(SB), NOFRAME|ABIInternal, $92-0
        MOVL    TLS, CX
        PCDATA  $0, $-2
        MOVL    (CX)(TLS*2), CX
        PCDATA  $0, $-1
        CMPL    SP, 8(CX)
        PCDATA  $0, $-2
        JLS     main_main_pc268
        PCDATA  $0, $-1
        SUBL    $92, SP
        FUNCDATA        $0, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)
        FUNCDATA        $1, gclocals·E5oFiNjeiS1rQQ8jVbtNeA==(SB)
        FUNCDATA        $2, main.main.stkobj(SB)
        MOVL    $0, main..autotmp_8+64(SP)
        MOVL    $0, main..autotmp_8+68(SP)
        LEAL    main..autotmp_8+64(SP), AX
        MOVL    AX, main..autotmp_7+72(SP)
        TESTB   AX, (AX)
        LEAL    type:string(SB), CX
        MOVL    CX, (AX)
        MOVL    runtime.writeBarrier(SB), CX
        PCDATA  $0, $-2
        TESTL   CX, CX
        JEQ     main_main_pc71
        JMP     main_main_pc73
main_main_pc71:
        JMP     main_main_pc85
main_main_pc73:
        MOVL    4(AX), CX
        CALL    runtime.gcWriteBarrier1(SB)
        MOVL    CX, (DI)
        JMP     main_main_pc85
main_main_pc85:
        LEAL    main..stmp_0(SB), CX
        MOVL    CX, 4(AX)
        PCDATA  $0, $-1
        TESTB   AX, (AX)
        JMP     main_main_pc98
main_main_pc98:
        MOVL    AX, fmt.a+52(SP)
        MOVL    $1, fmt.a+56(SP)
        MOVL    $1, fmt.a+60(SP)
        MOVL    $0, fmt.n+32(SP)
        MOVL    $0, fmt.err+44(SP)
        MOVL    $0, fmt.err+48(SP)
        NOP
        MOVL    $0, main..autotmp_6+76(SP)
        MOVL    $0, main..autotmp_6+80(SP)
        MOVL    fmt.a+52(SP), AX
        MOVL    fmt.a+56(SP), CX
        MOVL    fmt.a+60(SP), DX
        MOVL    os.Stdout(SB), BX
        LEAL    go:itab.*os.File,io.Writer(SB), BP
        MOVL    BP, (SP)
        MOVL    BX, 4(SP)
        MOVL    AX, 8(SP)
        MOVL    CX, 12(SP)
        MOVL    DX, 16(SP)
        PCDATA  $1, $0
        CALL    fmt.Fprintln(SB)
        MOVL    24(SP), AX
        MOVL    20(SP), CX
        MOVL    28(SP), DX
        MOVL    CX, main..autotmp_5+36(SP)
        MOVL    AX, main..autotmp_6+76(SP)
        MOVL    DX, main..autotmp_6+80(SP)
        MOVL    main..autotmp_5+36(SP), CX
        MOVL    CX, main..autotmp_3+40(SP)
        MOVL    AX, main..autotmp_4+84(SP)
        MOVL    DX, main..autotmp_4+88(SP)
        MOVL    main..autotmp_3+40(SP), CX
        MOVL    CX, fmt.n+32(SP)
        MOVL    AX, fmt.err+44(SP)
        MOVL    DX, fmt.err+48(SP)
        JMP     main_main_pc264
main_main_pc264:
        ADDL    $92, SP
        RET
main_main_pc268:
        NOP
        PCDATA  $1, $-1
        PCDATA  $0, $-2
        CALL    runtime.morestack_noctxt(SB)
        PCDATA  $0, $-1
        JMP     main_main_pc0

E por fim é gerado o executável, podendo no caso da Golang ser opcional para cada S.O, aqui compilei para Linux, mas caso queira compilar para Windows ou Mac, só utilizar as flags de seus respectivos S.O, que irá perfazer toda a etapa que foi descrita acima.

O papel dos compiladores na informática é de suma importância, pois dependendo do cenário, exige-se que o código seja executado em tempo de execução, uma manipulação de hardware precisa, como foi até citado neste artigo anterior, sobre a Missão Apollo 11.

 

Como sempre deixo aqui as referências, e sempre lembro de que se aprofundem na pesquisa, aqui foi apenas uma superficialidade do funcionamento dos compiladores, no entanto há muito mais para se explorar.

Até a próxima! ; )

Referência

AHO, Alfred V.; SETHI, Ravi; ULLMAN, Jeffrey D. , Compiladores : princípios, técnicas e
ferramentas. Rio de Janeiro: Guanabara Koogan, 1995.

O cérebro é como um músculo. Quando pensamos bem, nos sentimos bem.