Desmistificando o compilador Go: A jornada do func main() até o go run

Desmistificando o compilador Go: A jornada do func main() até o go run

Slides da minha apresentação na GopherCon Brasil - Setembro - 2019

Ceebf2f839c7b3e279da34be8a9017c3?s=128

Alex Sandro Garzão

September 27, 2019
Tweet

Transcript

  1. Desmistificando o compilador Go: A jornada do func main() até

    o go run Alex Sandro Garzão GopherConBR 2019
  2. Agenda (ou alinhando expectativas) O que é um compilador Compilador

    Go (Histórico, arquitetura, etapas de compilação, …) Hacking (Alegrias, tristezas, trechos de código, …) Considerações finais
  3. Não dá tempo para tudo….

  4. Quem sou • Engenheiro de Software na Zenvia • Minhas

    paixões (algumas) ◦ Meus filhos, livros, cinema, andar de bike ◦ Linguagens de programação, compiladores, máquinas virtuais ◦ Soluções simples para domínios complexos, algoritmos, processamento em tempo real ◦ Código bem feito :-) • Já atuei com “Toy compilers” ◦ HoloC, UbiC, Pascal para bytecode JVM, ST para ASM (80C51), …
  5. Vamos nos conhecer um pouco... Quem tem conhecimento sobre como

    um compilador funciona? Quem já atuou na implementação de um compilador (toy ou não)? Quem já olhou e/ou analisou o código de um compilador?
  6. O que é um compilador? “Um compilador é um programa

    que consegue ler um programa em uma linguagem (linguagem de origem) e traduzir para um programa equivalente em outra linguagem (linguagem destino)” [Aho, 2a edição] Compilador Linguagem de origem Linguagem de destino
  7. Arquitetura clássica de um compilador Frontend Código fonte Middleend Backend

    Representação intermediária Código de máquina Representação intermediária
  8. Arquitetura do GCC RTL RTL Optimizer Final Code Generation Assembly

    Back End GENERIC C C++ Java Fortran Front End GIMPLE Inter Procedural Optimizer SSA Optimizer Middle End GCCGO
  9. Arquitetura do LLVM LLVM X86 Backend LLVM PowerPC Backend LLVM

    ARM Backend X86 PowerPC ARM Back End Clang C/C++/ObjC Frontend C llvm-gcc Frontend Fortran GHC Frontend Haskell go-llvm Frontend Front End LLVM Optimizer LLVM IR LLVM IR Middle End
  10. Quem é o gc? • gc (minúsculo) é o compilador

    de go • Sem confusões com o GC (maiúsculo), que é o Garbage Collector • Pacote go/lexer não faz parte do gc ◦ go/lexer é usado por ferramentas como gofmt, golint, …
  11. Histórico do gc SETEMBRO 2007 JANEIRO 2008 METADE 2008 NOVEMBRO

    2009 AGOSTO 2015 Metas de uma nova linguagem Compilador escrito em C Compilador que gerava código C Versão pública do projeto ooo Porquê? Compilador escrito em Go (e asm) Como? Problemas? Só agora?
  12. O que o compilador do Go faz? RUNTIME • garbage

    collector • stack management • concurrency • ...
  13. Backend Middleend Arquitetura do gc / etapas de compilação Analisador

    léxico Stream de caracteres (Programa em Go) Stream de tokens Analisador sintático Árvore sintática abstrata anotada Gerador de código intermediário Otimizador SSA específica Gerador de código de máquina Código de máquina Representação intermediária (SSA genérica) Frontend
  14. Mas como isso acontece?

  15. Analisador léxico Analisador léxico Stream de caracteres (Programa em Go)

    Stream de tokens Analisador sintático Árvore sintática abstrata anotada Gerador de código intermediário Otimizador SSA específica Gerador de código de máquina Código de máquina Representação intermediária (SSA genérica) Frontend Middleend Backend
  16. Analisador léxico FUNC “func” IDENT “main” LPAREN “(” RPAREN “)”

    LBRACE “{” IDENT “y” ATTR “:=” INT “2” RBRACE “}” IDENT “x” ATTR “:=” INT “1” IDENT “fmt” DOT “.” IDENT “Println” LPAREN “(” RPAREN “)” IDENT “x” ADDOP “+” IDENT “y” Espaçamento? Tabulação? Salto de linha?
  17. Analisador sintático Analisador léxico Stream de caracteres (Programa em Go)

    Stream de tokens Analisador sintático Árvore sintática abstrata anotada Gerador de código intermediário Otimizador SSA específica Gerador de código de máquina Código de máquina Representação intermediária (SSA genérica) Frontend Middleend Backend
  18. Analisador sintático FuncDecl [main] CallExpr SelectorExpr Name [fmt] Name [Println]

    FUNC “func” IDENT “main” LPAREN “(” RPAREN “)” LBRACE “{” IDENT “y” ATTR “:=” INT “2” RBRACE “}” IDENT “x” ATTR “:=” INT “1” IDENT “fmt” DOT “.” IDENT “Println” LPAREN “(” RPAREN “)” IDENT “x” ADDOP “+” IDENT “y” VarDecl [:=] Var [y] IntLiteral [2] VarDecl [:=] Var [x] IntLiteral [1] AddOp [+] Var [x] Var [y] AST? Delimitadores?
  19. Gerador de código intermediário Analisador léxico Stream de caracteres (Programa

    em Go) Stream de tokens Analisador sintático Árvore sintática abstrata anotada Gerador de código intermediário Otimizador SSA específica Gerador de código de máquina Código de máquina Representação intermediária (SSA genérica) Frontend Middleend Backend
  20. Gerador de código intermediário FuncDecl [main] CallExpr SelectorExpr Name [fmt]

    Name [Println] VarDecl [:=] Var [y] IntLiteral [2] AddOp [+] Var [x] Var [y] VarDecl [:=] Var [x] IntLiteral [1] v5 (?) = Const64 <int> [2] (y[int]) v4 (?) = Const64 <int> [1] (x[int]) ... v7 (8) = Add64 <int> v4 v5 ... v41 (275) = StaticCall <mem> {fmt.Fprintln} [64] v40 ... Semântica?
  21. Otimizador Analisador léxico Stream de caracteres (Programa em Go) Stream

    de tokens Analisador sintático Árvore sintática abstrata anotada Gerador de código intermediário Otimizador SSA específica Gerador de código de máquina Código de máquina Representação intermediária (SSA genérica) Frontend Middleend Backend
  22. Otimizador v4 (?) = Const64 <int> [1] (x[int]) v5 (?)

    = Const64 <int> [2] (y[int]) v7 (8) = Add64 <int> v4 v5 v41 (275) = StaticCall <mem> {fmt.Fprintln} [64] v40 ... ... ... v7 (8) = Const64 <int> [3] v41 (275) = StaticCall <mem> {fmt.Fprintln} [64] v40 ... ...
  23. Gerador de código de máquina Analisador léxico Stream de caracteres

    (Programa em Go) Stream de tokens Analisador sintático Árvore sintática abstrata anotada Gerador de código intermediário Otimizador SSA específica Gerador de código de máquina Código de máquina Representação intermediária (SSA genérica) Frontend Middleend Backend
  24. Gerador de código de máquina v7 (8) = Const64 <int>

    [3] v41 (275) = StaticCall <mem> {fmt.Fprintln} [64] v40 ... ... # hello.go 00000 (5) TEXT "".main(SB), ABIInternal ... v8 00007 (+8) MOVQ $3, (SP) v9 00008 (8) CALL runtime.convT64(SB) ... # $GOROOT/src/fmt/print.go ... v41 00036 (275) CALL fmt.Fprintln(SB) b4 00037 (?) RET 00038 (?) END
  25. Falar é fácil…. show me the code :-) • Dois

    hacks feitos ◦ while ◦ if ternário (tem proposta para Go 2.0)
  26. Hacking (while)

  27. Hacking (while) - go run while.go

  28. Hacking 1 (while) - Novo token tokens.go

  29. Hacking 1 (while) - Novo statement parser.go Statement???

  30. Hacking 1 (while) - func whileStmt ForStmt ??? func whileStmt

    em parser.go
  31. Hacking 1 (while) - Define while tem condition Ajuste para

    parser entender que while tem “condition”
  32. Hacking 1 (while) - gc usa perfect hash scanner.go

  33. Hacking 1 (while) - Gerando o novo gc • Gerar

    os tokens novamente ◦ $ stringer -type token -linecomment tokens.go • Gerar os binários do compilador ◦ $ ./all.bash (gera binário, bibliotecas, executa testes) • $ ./bin/go run hacks/while.go
  34. Hacking 1 (while) - O que NÃO foi feito? •

    Não foi criado WhileStmt • Analisador sintático transforma o while em um ForStmt • As outras etapas acham que é um for...
  35. Hacking 2 (if ternário) - Expectativa...

  36. Qual a “treta”? • gc é LL(1) ◦ L: parser

    Left to Right ◦ L: derivação mais à esquerda ◦ (1): indica o look ahead (número de símbolos parser utiliza para tomar uma decisão) • “Ser” LL1 não é ruim, e para Go está certo... • Porém, eu queria transformar o if ternário em um “if” normal durante o parser
  37. Como eu queria resolver...

  38. LL(1) IDENT… SimpleStmt! ATTR… DEFINE! IfStmt não é compatível com

    SimpleStmt :-/ INT
  39. Expectativa vs realidade...

  40. Hacking 2 (if ternário) - Realidade :-( Porque assim funciona???

  41. Hacking 2 (if ternário) - go run ternary_if.go

  42. Hacking 2 (if ternário) - O que foi feito? •

    Sequência similar ao while… ◦ Novo token (let) ◦ Novo statement ◦ Gerar os tokens novamente ◦ Gerar a nova versão do compilador
  43. Hacking 2 (if ternário) - Novo statement parser.go

  44. Hacking 2 (if ternário) - Novo statement let <id> =

    <SimpleStmt> if <Cond> else <SimpleStmt>
  45. Resumindo…. • gc é um projeto MUITO legal! • No

    geral, bem legível • Boa documentação • Tem o “legado” da conversão direta de C para Go • Primeiro compilador “real” que tudo é óbvio :-D
  46. Coisas que (NÃO) vale a pena se preocupar • Foque

    em clean code, código organizado… • Programe para outros humanos entenderem, e não para máquinas ◦ Código, no geral, é escrito uma vez, mas lido milhares de vezes • Pode deixar que o gc lida MUITO bem com otimizações :-)
  47. Referências • Links interessantes sobre o gc ◦ Histórico ◦

    Repositório do go: gc está aqui ◦ Introduction to the go compiler ◦ Go contribution guide ◦ SSA rules • Porque reescreveram o gc em go e como • Adding a new statement: part 1 and part 2 • Go toolchain • Hacking go compiler internals • Para gerar SSA: GOSSAFUNC=main go tool compile hello.go (gera ssa.html) • Compiler explorer • Aho, 2nd editon
  48. Dúvidas???? Alex Sandro Garzão alexgarzao@gmail.com https://www.linkedin.com/in/alexgarzao/ https://github.com/alexgarzao https://twitter.com/alexgarzao