Os analisadores GCC e Clang são realmente escritos à mão?

89

Parece que o GCC e o LLVM-Clang estão usando analisadores descendentes recursivos escritos à mão , e não gerados por máquina, baseados em Bison-Flex, análise de baixo para cima.

Alguém aqui poderia confirmar se este é o caso? E em caso afirmativo, por que os principais frameworks de compiladores usam analisadores escritos à mão?

Atualização : blog interessante sobre este tópico aqui

JCLL
fonte
27
Quase todos os compiladores convencionais usam analisadores escritos à mão. Qual é o problema disso?
SK-logic
2
você tem que fazer isso (semi-) manualmente se precisar de desempenho.
Gene Bushuyev
15
E não apenas desempenho - melhores mensagens de erro, capacidade de recuperação, etc.
SK-lógico
E quanto ao MS VisualStudio? embora não seja de código aberto, alguém da MS poderia verificar se também está usando um analisador descendente recursivo escrito à mão?
OrenIshShalom 01 de
3
@GeneBushuyev, do wiki do GCC: "... Embora os tempos tenham mostrado um aumento de 1,5% , os principais benefícios são a facilitação de melhorias futuras ..." esse aumento parece bastante marginal ...
OrenIshShalom 01 de

Respostas:

76

Sim:

  • O GCC usou um analisador yacc (bison) uma vez, mas foi substituído por um analisador descendente recursivo escrito à mão em algum ponto da série 3.x: consulte http://gcc.gnu.org/wiki/New_C_Parser para links para envios de patches relevantes.

  • O Clang também usa um analisador descendente recursivo escrito à mão: consulte a seção "Um único analisador unificado para C, Objective C, C ++ e Objective C ++" próximo ao final de http://clang.llvm.org/features.html .

Matthew Slattery
fonte
3
Isso significa que ObjC, C e C ++ tem gramáticas LL (k)?
Lindemann
47
Não: mesmo C, o mais simples dos três, tem uma gramática ambígua. Por exemplo, foo * bar;poderia analisar como uma expressão de multiplicação (com o resultado não usado) ou como uma declaração de uma variável barcom o tipo ponteiro-para- foo. Qual é o correto depende se um typedeffor fooestá no escopo no momento, o que não é algo que pode ser determinado com qualquer olhar à frente. Mas isso significa apenas que o analisador descendente recursivo precisa de algum maquinário extra feio adicionado para lidar com isso.
Matthew Slattery
9
Posso confirmar a partir de evidências empíricas, que C ++ 11, C e Objective C têm gramáticas livres de contexto que um analisador GLR pode manipular.
Ira Baxter
2
Com relação à sensibilidade ao contexto, esta resposta não afirma: que a análise dessas linguagens é provavelmente Turing-completa.
Ioannis Filippidis
104

Existe um teorema popular que diz que C é difícil de analisar e C ++ essencialmente impossível.

Não é verdade.

O que é verdade é que C e C ++ são muito difíceis de analisar usando analisadores LALR (1) sem hackear o mecanismo de análise e confundir os dados da tabela de símbolos. Na verdade, o GCC costumava analisá-los, usando YACC e hackery adicional como esse, e sim, era feio. Agora o GCC usa analisadores escritos à mão, mas ainda com o hackery da tabela de símbolos. O pessoal do Clang nunca tentou usar geradores de analisador automatizado; AFAIK, o analisador Clang sempre foi descendente recursivo codificado manualmente.

O que é verdade, é que C e C ++ são relativamente fáceis de analisar com analisadores mais fortes gerados automaticamente, por exemplo, analisadores GLR , e você não precisa de nenhum hacks. O analisador Elsa C ++ é um exemplo disso. Nosso front-end C ++ é outro (assim como todos os nossos front-ends de "compilador", GLR é uma tecnologia de análise bastante maravilhosa).

Nosso front-end C ++ não é tão rápido quanto o do GCC e certamente mais lento que o Elsa; colocamos pouca energia em ajustá-lo cuidadosamente porque temos outros problemas mais urgentes (embora ele tenha sido usado em milhões de linhas de código C ++). Elsa é provavelmente mais lento que o GCC simplesmente porque é mais geral. Dadas as velocidades do processador hoje em dia, essas diferenças podem não importar muito na prática.

Mas os "compiladores reais" que são amplamente distribuídos hoje têm suas raízes em compiladores de 10 ou 20 anos atrás ou mais. As ineficiências importavam muito mais, e ninguém tinha ouvido falar dos analisadores GLR, então as pessoas faziam o que sabiam fazer. O Clang é certamente mais recente, mas os teoremas populares mantêm seu "poder de persuasão" por muito tempo.

Você não precisa mais fazer assim. Você pode usar o GLR e outros analisadores como front-ends, com uma melhoria na manutenção do compilador.

O que é verdade é que obter uma gramática que corresponda ao comportamento do compilador de sua vizinhança amigável é difícil. Embora praticamente todos os compiladores C ++ implementem (a maioria) do padrão original, eles também tendem a ter muitas extensões de canto escuro, por exemplo, especificações de DLL em compiladores MS, etc. Se você tiver um motor de análise forte, pode gastar seu tempo tentando obter a gramática final para corresponder à realidade, em vez de tentar dobrar sua gramática para corresponder às limitações de seu gerador de analisador.

EDITAR novembro de 2012: desde que escrevemos esta resposta, melhoramos nosso front-end C ++ para lidar com C ++ 11 completo, incluindo ANSI, GNU e dialetos variantes MS. Embora houvesse muitas coisas extras, não precisamos mudar nosso mecanismo de análise; acabamos de revisar as regras gramaticais. Tivemos que mudar a análise semântica; C ++ 11 é semanticamente muito complicado e este trabalho atrapalha o esforço para fazer o analisador funcionar.

EDITAR fevereiro de 2015: ... agora lida com C ++ 14 completo. (Consulte obter AST legível por humanos a partir do código c ++ para análises GLR de um simples trecho de código e a infame "análise mais irritante" do C ++).

EDITAR abril de 2017: agora lida com (rascunho) C ++ 17.

Ira Baxter
fonte
6
PostScript: Assim como obter a gramática para corresponder ao que os fornecedores realmente fazem é mais difícil, obter resolução de nome e tipo para corresponder à interpretação do manual do C ++ 11 de diferentes fornecedores é ainda mais difícil, porque a única evidência que você tem são programas que compilam ligeiramente diferentemente, se você puder encontrá-los. Já passamos disso em agosto de 2013 para o C ++ 11 propriamente dito, mas me desespero um pouco no comitê C ++, que parece determinado a produzir um padrão ainda maior (e por experiência, mais confuso) na forma de C ++ 1y.
Ira Baxter
5
Eu realmente gostaria de saber: como você lida com essa foo * bar;ambigüidade?
Martin
14
@Martin: nosso analisador analisa as duas maneiras, produzindo uma árvore contendo "nós de ambigüidade" especiais cujos filhos são os analisadores alternativos; os filhos compartilham o máximo de seus filhos, então acabamos com um DAG em vez de uma árvore. Após a conclusão da análise, executamos um avaliador de gramática de atributos (AGE) sobre o DAG (nome extravagante para "andar na árvore e fazer coisas" se você não souber) que calcula os tipos de todos os identificadores declarados. ...
Ira Baxter
12
... Os filhos ambíguos não podem ser consistentes com o tipo; a IDADE ao descobrir uma criança ambígua que não pode ser digitada de maneira sensata simplesmente a apaga. O que resta são as crianças bem tipadas; assim, determinamos qual análise de "foo bar;" está correto. Esse truque funciona para todos os tipos de ambigüidades malucas encontradas nas gramáticas reais que construímos para os dialetos reais do C ++ 11 e * separa completamente a análise de análise semântica de nomes. Essa separação limpa significa muito menos trabalho de engenharia a fazer (sem emaranhados para depurar). Consulte stackoverflow.com/a/1004737/120163 para obter mais informações.
Ira Baxter
3
@TimCas: Na verdade, estou com você reclamando da estupidez aparente de projetar sintaxe de linguagem (e semântica) que é tão complicada que é tão difícil de acertar (sim, a linguagem C ++ sofre muito aqui). Eu gostaria que os comitês de design de linguagem projetassem a sintaxe para que tecnologias de análise mais simples funcionassem e definissem explicitamente a semântica da linguagem e a verificassem com algumas ferramentas de análise semântica. Infelizmente, o mundo não parece ser assim. Portanto, entendo que você constrói o que precisa construir da melhor maneira possível e segue com a vida, apesar da estranheza.
Ira Baxter
31

O analisador de Clang é um analisador descendente recursivo escrito à mão, assim como vários outros front-ends comerciais e de código aberto C e C ++.

O Clang usa um analisador descendente recursivo por vários motivos:

  • Desempenho : um analisador escrito à mão nos permite escrever um analisador rápido, otimizando os caminhos ativos conforme necessário, e estamos sempre no controle desse desempenho. Ter um analisador rápido permitiu que o Clang fosse usado em outras ferramentas de desenvolvimento onde analisadores "reais" normalmente não são usados, por exemplo, realce de sintaxe e conclusão de código em um IDE.
  • Diagnóstico e recuperação de erro : como você tem controle total com um analisador descendente recursivo escrito à mão, é fácil adicionar casos especiais que detectam problemas comuns e fornecem excelentes diagnósticos e recuperação de erros (por exemplo, consulte http: //clang.llvm .org / features.html # expressivediags ) Com analisadores gerados automaticamente, você está limitado aos recursos do gerador.
  • Simplicidade : analisadores descendentes recursivos são fáceis de escrever, entender e depurar. Você não precisa ser um especialista em análise ou aprender uma nova ferramenta para estender / melhorar o analisador (o que é especialmente importante para um projeto de código aberto), mas você ainda pode obter ótimos resultados.

No geral, para um compilador C ++, simplesmente não importa muito: a parte de análise de C ++ não é trivial, mas ainda é uma das partes mais fáceis, então vale a pena mantê-la simples. A análise semântica - particularmente pesquisa de nome, inicialização, resolução de sobrecarga e instanciação de modelo - é muito mais complicada do que a análise. Se você quiser uma prova, verifique a distribuição de código e commits no componente "Sema" do Clang (para análise semântica) versus seu componente "Parse" (para análise).

Doug
fonte
4
Sim, a análise semântica é muito mais difícil. Temos cerca de 4.000 linhas de regras gramaticais que compreendem nossa gramática C ++ 11 e cerca de 180.000 linhas de código de gramática de atributos para as "análises semânticas" das listas de dúvidas acima, com outras 100.000 linhas de código de suporte. A análise realmente não é o problema, embora seja difícil o suficiente se você começar com o pé errado.
Ira Baxter de
1
Não estou tão certo de que analisadores escritos à mão sejam necessariamente melhores para relatar / recuperar erros. Parece que as pessoas colocaram mais energia nesses analisadores do que no aprimoramento dos analisadores produzidos por geradores de analisadores automáticos na prática. Parece haver pesquisas muito boas sobre o assunto; este artigo em particular realmente chamou minha atenção: MG Burke, 1983, Um método prático para diagnóstico e recuperação de erros sintáticos LR e LL, tese de doutorado, Departamento de Ciência da Computação, Universidade de Nova York, Ver archive.org/details/practicalmethodf00burk
Ira Baxter
1
... continuando este trem de pensamento: se você está disposto a modificar / estender / personalizar seu analisador feito à mão para verificar se há casos especiais para um melhor diagnóstico, então você deve estar disposto a fazer investimentos iguais em diagnósticos melhores de um analisador gerado mecanicamente. Para qualquer análise especial que você possa codificar para a manual, você pode codificar uma verificação para a mecânica também (e para analisadores (G) LR, você pode fazer isso como verificações semânticas nas reduções). Na medida em que parece pouco apetitoso, alguém está apenas sendo preguiçoso, mas isso não é uma acusação aos analisadores gerados mecanicamente IMHO.
Ira Baxter
8

O analisador do gcc é escrito à mão. . Eu suspeito o mesmo para clang. Provavelmente por alguns motivos:

  • Desempenho : algo que você otimizou manualmente para sua tarefa específica quase sempre terá um desempenho melhor do que uma solução geral. A abstração geralmente tem um impacto no desempenho
  • Tempo : pelo menos no caso do GCC, o GCC é anterior a muitas ferramentas de desenvolvedor gratuitas (lançado em 1987). Não havia nenhuma versão gratuita do yacc, etc. na época, o que eu imagino que seria uma prioridade para o pessoal da FSF.

Este provavelmente não é um caso de síndrome do "não foi inventado aqui", mas mais do tipo "não havia nada otimizado especificamente para o que precisávamos, então escrevemos o nosso próprio".

Rafe Kettler
fonte
15
Nenhuma versão gratuita do yacc em 1987? Eu acho que havia versões gratuitas quando o yacc foi distribuído pela primeira vez no Unix nos anos 70. E IIRC (outro pôster parece o mesmo), GCC costumava ter um analisador baseado em YACC. Ouvi dizer que a desculpa para mudá-lo era para obter um relatório de erros melhor.
Ira Baxter
7
Eu gostaria de acrescentar que geralmente é mais fácil gerar boas mensagens de erro de um analisador manuscrito.
Dietrich Epp de
1
Seu ponto sobre o tempo é impreciso. O GCC costumava ter um analisador baseado em YACC, mas ele foi substituído por um analisador descendente recursivo escrito à mão, mais tarde.
Tommy Andersen de
7

Respostas estranhas aí!

Gramáticas C / C ++ não são independentes de contexto. Eles são sensíveis ao contexto por causa da barra Foo *; ambiguidade. Temos que construir uma lista de typedefs para saber se Foo é um tipo ou não.

Ira Baxter: Não vejo sentido em sua coisa de GLR. Por que construir uma árvore de análise que contém ambigüidades. Analisar significa resolver ambigüidades, construir a árvore sintática. Você resolve essas ambigüidades em uma segunda passagem, então isso não é menos feio. Pra mim é bem mais feio ...

Yacc é um gerador de analisador sintático LR (1) (ou LALR (1)), mas pode ser facilmente modificado para ser sensível ao contexto. E não há nada de feio nisso. Yacc / Bison foi criado para ajudar na análise da linguagem C, então provavelmente não é a ferramenta mais feia para gerar um analisador C ...

Até o GCC 3.x, o analisador C é gerado pelo yacc / bison, com a tabela typedefs construída durante a análise. Com a construção de tabelas typedefs "em análise", a gramática C torna-se localmente livre de contexto e, além disso, "localmente LR (1)".

Agora, no Gcc 4.x, é um analisador descendente recursivo. É exatamente o mesmo analisador do Gcc 3.x, ainda é LR (1) e tem as mesmas regras gramaticais. A diferença é que o analisador yacc foi reescrito à mão, o deslocamento / redução agora estão ocultos na pilha de chamadas e não há "state454: if (nextsym == '(') goto state398" como em gcc 3.x yacc's analisador, por isso é mais fácil corrigir, lidar com erros e imprimir mensagens mais agradáveis, e para realizar algumas das próximas etapas de compilação durante a análise. Ao preço de muito menos código "fácil de ler" para um noob gcc.

Por que eles mudaram de yacc para descida recursiva? Porque é muito necessário evitar o yacc para analisar C ++, e porque o GCC sonha em ser um compilador multilíngue, ou seja, compartilhar o máximo de código entre as diferentes linguagens que ele pode compilar. É por isso que o C ++ e o analisador C são escritos da mesma maneira.

C ++ é mais difícil de analisar do que C porque não é LR (1) "localmente" como C, nem mesmo LR (k). Veja func<4 > 2>qual é uma função de modelo instanciada com 4> 2, ou seja func<4 > 2> , deve ser lida como func<1>. Definitivamente, não é LR (1). Agora considere func<4 > 2 > 1 > 3 > 3 > 8 > 9 > 8 > 7 > 8>,. É aqui que uma descida recursiva pode facilmente resolver a ambiguidade, ao preço de mais algumas chamadas de função (parse_template_parameter é a função de analisador ambígua. Se parse_template_parameter (17tokens) falhou, tente novamente parse_template_parameter (15tokens), parse_template_parameter (13tokens) ... até funciona).

Não sei por que não seria possível adicionar em sub gramáticas recursivas yacc / bison, talvez esta seja a próxima etapa no desenvolvimento do analisador gcc / GNU?

reúne
fonte
9
"para mim, é bem mais feio". O que posso dizer é que a engenharia de um analisador de qualidade de produção usando GLR e resolução de ambigüidade de atraso é prática com uma equipe muito pequena. Todas as outras soluções que vi envolveram anos de ranger de dentes em público durante os backflips e hacks necessários para fazer funcionar com LR, descida recursiva, o que quiser. Você pode postular muitas outras novas tecnologias de análise, mas até onde eu posso dizer, isso é apenas mais ranger de dentes neste momento. As ideias são baratas; execução é cara.
Ira Baxter
@IraBaxter: Ratos! citeseerx.ist.psu.edu/viewdoc/…
Fizz
@Fizz: Artigo interessante sobre a análise de Fortress, uma linguagem de programação científica complexa. Eles disseram várias coisas importantes: a) geradores de analisadores clássicos (LL (k), LALR (1)) não conseguem lidar com gramáticas difíceis, b) eles tentaram GLR, tiveram problemas com escala, mas os desenvolvedores eram inexperientes, então não o fizeram complete [isso não é culpa do GLR] ec) eles usaram um analisador Packrat de retrocesso (transacional) e se esforçaram bastante, incluindo trabalho para produzir mensagens de erro melhores. Com relação ao exemplo de análise "{| x || x ← mySet, 3 | x}", acredito que o GLR faria isso muito bem e não precisa de espaços.
Ira Baxter
0

Parece que o GCC e o LLVM-Clang estão usando analisadores descendentes recursivos escritos à mão, e não gerados por máquina, baseados em Bison-Flex, análise de baixo para cima.

Bison, em particular, não acho que possa lidar com a gramática sem analisar algumas coisas de forma ambígua e fazer uma segunda passagem depois.

Eu sei que o Happy de Haskell permite analisadores monádicos (ou seja, dependentes de estado) que podem resolver o problema específico com a sintaxe C, mas não conheço nenhum gerador de analisador C que permita uma mônada de estado fornecida pelo usuário.

Em teoria, a recuperação de erros seria um ponto a favor de um analisador manuscrito, mas minha experiência com o GCC / Clang mostra que as mensagens de erro não são particularmente boas.

Quanto ao desempenho - algumas das afirmações parecem infundadas. Gerar uma grande máquina de estado usando um gerador de analisador deve resultar em algo que é O(n)e eu duvido que a análise seja o gargalo de muitas ferramentas.

Vanessa McHale
fonte
3
Essa questão já tem uma resposta de altíssima qualidade, o que você está tentando acrescentar?
tod