Por que a representação intermediária do LLVM (LLVM IR) é semelhante a montagem e não a árvore?
Como alternativa, por que as implementações de linguagem têm como alvo o LLVM IR em vez do AST do clang?
Não estou tentando fazer duas perguntas diferentes de uma só vez, se assim parecer. Para mim, parece que os programadores de clientes e bibliotecas chegaram ao consenso de que a API do LLVM, nada mais e nada menos, é obviamente um bom design de software e minha pergunta é "por quê?".
A razão pela qual pergunto é que parece que o LLVM poderia fornecer mais funcionalidade aos frontends se o IR fosse do tipo AST, porque as ferramentas baseadas em AST do clang poderiam ser usadas para qualquer frontend. Como alternativa, os idiomas direcionados ao LLVM IR poderiam obter mais funcionalidade se direcionados ao AST do clang.
O Clang possui classes e funções para criar e trabalhar com ASTs e é o único projeto de front-end fortemente vinculado ao projeto LLVM. Por que a funcionalidade AST do clang é externa ao LLVM?
No topo da minha cabeça, eu sei que Rust (rustc), D (ldc) e Haskell (GHC) podem usar o LLVM como back-end, mas eles não usam o Clang AST (tanto quanto eu sei, eu poderia estar errado). Não conheço todos os detalhes internos desses compiladores, mas pelo menos Rust e D certamente parecem que poderiam ser compilados no AST do clang. Talvez Haskell também pudesse, mas tenho muito menos certeza disso.
Isso é devido a razões históricas (o LLVM originalmente é uma "máquina virtual de baixo nível" e o clang vem mais tarde)? Isso ocorre porque outros front-end desejam ter o máximo de controle possível sobre o que alimentam para o LLVM? Existem razões fundamentais para que o AST de clang seja inadequado para idiomas "não-C-like"?
Não pretendo que esta pergunta seja um exercício de leitura da mente. Eu só quero que seja útil para aqueles que têm curiosidade, mas ainda não são fluentes no design de compiladores. Como os projetos LLVM e clang são desenvolvidos em público, espero que alguém familiarizado com o desenvolvimento desses projetos possa responder ou que a resposta seja óbvia o suficiente para alguns nerds de compilação que eles se sintam confiantes o suficiente para responder.
Para antecipar algumas respostas óbvias, mas insatisfatórias:
Sim, ter um IR tipo montagem dá mais controle para quem cria o IR (talvez X lang tenha uma melhor base de código e formato AST do que clang), mas se essa é a única resposta, a pergunta passa a ser "por que o LLVM só tem um assembly- como IR em vez de um IR de árvore de alto nível e um IR de montagem de nível inferior? ".
Sim, não é tão difícil analisar uma linguagem de programação em um AST (pelo menos em comparação com as outras etapas de compilação). Mesmo assim, por que usar ASTs separados? Se nada mais, usar o mesmo AST permite que você use ferramentas que operam em ASTs (mesmo coisas simples, como impressoras AST).
Sim, concordo plenamente que ser mais modular é uma coisa boa, mas se esse é o único motivo, por que outras implementações de linguagem tendem a direcionar o LLVM IR em vez do AST do clang?
Essas pressões podem ser errôneas ou negligenciar os detalhes; portanto, fique à vontade para dar essas respostas se você tiver mais detalhes ou se minhas suposições estiverem erradas.
Para quem deseja responder a uma pergunta mais definitiva: quais são as vantagens e desvantagens de um IR tipo assembléia versus um IR tipo árvore?
fonte
Respostas:
Há várias perguntas inter-relacionadas aqui, tentarei separá-las da melhor maneira possível.
Por que outros idiomas se baseiam no LLVM IR e não clonam no AST?
Isso ocorre simplesmente porque o clang é um front end C / C ++ e o AST produzido é fortemente acoplado ao C / C ++. Outra linguagem poderia usá-lo, mas precisaria de semântica quase idêntica a algum subconjunto de C / C ++, o que é muito limitante. Como você ressalta, a análise de um AST é bastante simples, portanto, é improvável que restringir suas escolhas semânticas valha a pequena economia.
No entanto, se você estiver escrevendo ferramentas para C / C ++, por exemplo, analisadores estáticos, reutilizar o AST faz muito sentido, pois é muito mais fácil trabalhar com o AST do que o texto bruto, se você estiver trabalhando com C / C ++ .
Por que o LLVM IR está do jeito que está?
O LLVM IR foi escolhido como uma forma apropriada para escrever otimizações do compilador. Como tal, sua principal característica é que está no formato SSA . É um IR de nível bastante baixo, para que seja aplicável a uma ampla variedade de idiomas, por exemplo, não digita memória, pois isso varia muito entre os idiomas.
Agora, acontece que as otimizações de compilação de escrita são uma tarefa bastante especializada e geralmente são ortogonais ao design de recursos de linguagem. No entanto, ter uma linguagem compilada executada rapidamente é um requisito bastante geral. Além disso, a conversão de LLVM IR para ASM é bastante mecânica e geralmente não é interessante para designers de idiomas.
Portanto, a redução de um idioma para o LLVM IR fornece a um designer de idiomas muitos "itens gratuitos" que são muito úteis na prática, deixando-os concentrados no próprio idioma.
Um RI diferente seria útil (OK, não solicitado, mas meio implícito)?
Absolutamente! ASTs são muito bons para certas transformações na estrutura do programa, mas são muito difíceis de usar se você deseja transformar o fluxo do programa. Um formulário SSA geralmente é melhor. No entanto, o LLVM IR é de nível muito baixo, perdendo muito da estrutura de alto nível (de propósito, por isso é mais aplicável em geral). Ter um IR entre o AST e o IR de baixo nível pode ser benéfico aqui. Rust e Swift adotam essa abordagem e têm um alto nível de IR entre os dois.
fonte