Criar um Compilador Funcional com a Estrutura LLVM, Parte 2

Utilizar o clang para pré-processar o código de C/C++

A infraestrutura de compilador LLVM fornece uma forma eficiente de otimizar seus aplicativos independentemente da linguagem de programação utilizada. Saiba como instrumentar o código no LLVM utilizando a API do clang para pré-processar o código de C/C++ neste segundo artigo de uma série de duas partes.

Arpan Sen, Author, Independente

Arpan Sen é engenheiro líder que trabalha no desenvolvimento de software no segmento de mercado da automação de design eletrônico. Ele trabalhou em diversos tipos de UNIX, incluindo Solaris, SunOS, HP-UX e IRIX, além de Linux e Microsoft Windows, por muitos anos. Possui um grande interesse por técnicas de otimização do desempenho de software, teoria de gráfico e computação paralela. Arpan possui pós-doutorado em sistemas de software.



16/Mai/2014

Outros Artigos nesta Série

Veja mais artigos na série Create a working compiler with the LLVM framework .

O primeiro artigo desta série explorou a representação intermediária (IR) do LLVM. Você criou um programa de teste "Hello World"; aprendeu algumas nuances do LLVM, como conversão de tipos; e finalizou criando o mesmo programa utilizando as Interfaces de Programação de Aplicativos(API) do LLVM. No processo, você também aprendeu sobre as ferramentas do LLVM, como llc e lli e descobriu como utilizar llvm-gcc para emitir uma IR do LLVM. Esta segunda parte conclusiva da série explora algumas outras coisas interessantes que podem ser feitas com o LLVM. Especificamente, ela aborda o código de instrumentação—isto é, a inclusão de informações ao executável final que é gerado. Ela também explora um pouco do clang, um front-end para o suporte do LLVM de C, C++ e Objective-C. A API do clang é utilizada para pré-processar e gerar uma Árvore de Sintaxe Abstrata (AST) para o código C/C++ .

Passes do LLVM

O LLVM é conhecido pelos recursos de otimização que fornece. Otimizações são implementadas como passes (para detalhes de nível superior sobre passes do LLVM, consulte Recursos). É preciso observar que o LLVM fornece a habilidade de criar passes de utilitário com um código mínimo. Por exemplo, se não quiser que seus nomes da função comecem com "hello", é possível ter um passe de utilitário que faça isso.

Entendendo a ferramenta opt do LLVM

Na man page de opt, o "comando opt é o otimizador e analisador modular do LLVM". Quando o código do passe customizado estiver pronto, compile-o em uma biblioteca compartilhada e carregue-o utilizando opt. Se sua instalação do LLVM tiver sido executada com êxito, opt já deve estar disponível em seu sistema. Um comando opt aceita os formatos de IR (a extensão .ll) e de código de bits (a extensão .bc) do LLVM e pode gerar a saída como qualquer um dos dois. Segue como utilizar opt para carregar sua biblioteca compartilhada customizada:

tintin# opt –load=mycustom_pass.so –help –S

Observe também que a execução de opt –help a partir da linha de comandos gera uma lista de tarefas de passes executadas pelo LLVM. Usar a opção load com help gera uma mensagem de ajuda que inclui informações sobre seu passe customizado.

Criando um passe customizado do LLVM

Passes do LLVM são declarados no arquivo Pass.h que, no meu sistema, está instalado em /usr/include/llvm. Esse arquivo define a interface para o passe de um indivíduo como parte da classe Pass . Tipos de passe individuais, cada um derivado de Pass, também são declarados nesse arquivo. Tipos de passe incluem:

  • Classe BasicBlockPass . Utilizada para implementar otimizações locais, sendo normalmente operadas em um bloco ou instrução básica por vez
  • Classe FunctionPass . Utilizada para otimizações globais, uma função por vez
  • Classe ModulePass . Utilizada para executar qualquer otimização interprocessual não estruturada

Como você pretende criar um passe que deve se opor a qualquer nome da função que comece com "Hello", é obrigatório criar seu próprio passe decorrente de FunctionPass. Copie o código na Listagem 1 a partir de Pass.h.

Listagem 1. Substituindo a classe runOnFunction pela FunctionPass
Class FunctionPass : public Pass { 
  /// explicit FunctionPass(char &pid) : Pass(PT_Function, pid) {}
  /// runOnFunction - Virtual method overridden by subclasses to do the
  /// per-function processing of the pass.
  ///
  virtual bool runOnFunction(Function &F) = 0;
  /// …
};

Da mesma forma, a classe BasicBlockPass declara um runOnBasicBlock e a classe ModulePass declara um método runOnModule puramente virtual. A classe child precisa fornecer uma definição para o método virtual.

Voltando para o método runOnFunction da Listagem 1, é possível ver que a entrada é um objeto do tipo Function. Aprofunde-se no arquivo /usr/include/llvm/Function.h para facilmente ver que a classe Function é a que o LLVM utiliza para conter a funcionalidade de uma função C/C++ . Function , por sua vez, é derivada da classe Value definida em Value.h e suporta um método getName . A Listagem 2 mostra o código.

Listagem 2. Criando um passe customizado do LLVM
#include "llvm/Pass.h"
#include "llvm/Function.h"
class TestClass : public llvm::FunctionPass {
public:
virtual bool runOnFunction(llvm::Function &F)
  {
    if (F.getName().startswith("hello"))
    {
      std::cout << "Function name starts with hello\n";
    }
    return false;
  }
};

O código na Listagem 2 omite dois detalhes importantes:

  • O construtor FunctionPass precisa de um char, que é utilizado internamente pelo LLVM. O LLVM utiliza o endereço do char, portanto, não importa o que é utilizado para inicializar.
  • É necessária uma forma para o sistema do LLVM entender que a classe criada é um novo passe. É nessa parte que o modelo RegisterPass do LLVM entra em ação. O modelo RegisterPass é declarado no arquivo de cabeçalho PassSupport.h; esse arquivo é incluído no Pass.h, portanto, não são necessários cabeçalhos adicionais.

A Listagem 3 mostra o código completo.

Listagem 3. Registrando o passe Function do LLVM
class TestClass : public llvm::FunctionPass
{
public:
  TestClass() : llvm::FunctionPass(TestClass::ID) { }
  virtual bool runOnFunction(llvm::Function &F) {
    if (F.getName().startswith("hello")) {
      std::cout << "Function name starts with hello\n";
    }
    return false;
  }
  static char ID; // could be a global too
};
char TestClass::ID = 'a';
static llvm::RegisterPass<TestClass> global_("test_llvm", "test llvm", false, false);

O parâmetro template no modelo RegisterPass é o nome do passe a ser utilizado na linha de comandos com opt. É isso. Agora, basta criar uma biblioteca compartilhada a partir do código na Listagem 3e, em seguida, executar opt para carregar a biblioteca, seguido pelo nome do comando registrado, utilizando RegisterPass—neste caso, test_llvm—e, finalmente, um arquivo de código de bits no qual seu passe customizado será executado juntamente com os demais passes. As etapas estão descritas na Listagem 4.

Listagem 4. Executando o passe customizado
bash$ g++ -c pass.cpp -I/usr/local/include `llvm-config --cxxflags`
bash$ g++ -shared -o pass.so pass.o -L/usr/local/lib `llvm-config --ldflags -libs`
bash$ opt -load=./pass.so –test_llvm < test.bc

Agora veja o outro lado da moeda—o front-end para o back end do LLVM: clang.


Apresentando o clang

Observações antes de começar

O clang é um trabalho em andamento e, como é óbvio com qualquer projeto dessa escala, a documentação normalmente está atrás da base do código. Assim, é melhor verificar a lista de emails do desenvolvedor (consulte Recursos para obter um link). Se quiser desenvolver e instalar origens de clang, siga as instruções do Guia de Inicialização do Clang (consulte Recursos). Observe que é preciso emitir o comando make install após a conclusão do build para instalação em pastas padrão do sistema. O restante deste artigo pressupõe que os cabeçalhos e bibliotecas do clang residem nas pastas do sistema semelhantes a /usr/local/include e /usr/local/lib, respectivamente.

O LLVM possui seu próprio front-end—uma ferramenta denominada (de forma bastante apropriada) clang. Clang é um compilador C/C++/Objective-C eficiente, com velocidades de compilação comparáveis a ou melhores que as das ferramentas GNU Compiler Collection (GCC) (consulte Recursos para obter um link para mais informações). Mais importante ainda, o clang possui uma base de código vulnerável a hackers, facilitando extensões customizadas. De forma bastante parecida com a qual a API de back end do LLVM foi utilizada para seu plug-in customizado na Parte 1, neste artigo, a API é utilizada para o front-end do LLVM e alguns pequenos aplicativos são desenvolvidos para pré-processamento e análise.

Classes clang comuns

É preciso se familiarizar com algumas das classes clang mais comuns:

  • CompilerInstance
  • Preprocessor
  • FileManager
  • SourceManager
  • DiagnosticsEngine
  • LangOptions
  • TargetInfo
  • ASTConsumer
  • Sema
  • ParseAST é talvez o método clang mais importante.

Mais sobre o método ParseAST logo mais.

Para todos os fins práticos, considere CompilerInstance o compilador adequado. Ele fornece interfaces e gerencia o acesso ao AST, pré-processa as origens de entrada e mantém as informações de destino. Aplicativos típicos precisam criar um objeto CompilerInstance para fazer qualquer coisa útil. A Listagem 5 fornece uma ideia do arquivo de cabeçalho CompilerInstance.h.

Listagem 5. A classe CompilerInstance
class CompilerInstance : public ModuleLoader {
  /// The options used in this compiler instance.
  llvm::IntrusiveRefCntPtr<CompilerInvocation> Invocation;
  /// The diagnostics engine instance.
  llvm::IntrusiveRefCntPtr<DiagnosticsEngine> Diagnostics;
  /// The target being compiled for.
  llvm::IntrusiveRefCntPtr<TargetInfo> Target;
  /// The file manager.
  llvm::IntrusiveRefCntPtr<FileManager> FileMgr;
  /// The source manager.
  llvm::IntrusiveRefCntPtr<SourceManager> SourceMgr;
  /// The preprocessor.
  llvm::IntrusiveRefCntPtr<Preprocessor> PP;
  /// The AST context.
  llvm::IntrusiveRefCntPtr<ASTContext> Context;
  /// The AST consumer.
  OwningPtr<ASTConsumer> Consumer;
 /// \brief The semantic analysis object.
  OwningPtr<Sema> TheSema;
 //… the list continues
};

Pré-processando um arquivo C

Há pelo menos duas maneiras de criar um objeto preprocessor no clang:

  • Exemplifique um objeto Preprocessor de forma direta
  • Utilize a classe CompilerInstance para criar um objeto Preprocessor

Vamos começar com a última abordagem.

Classes helper e utility necessárias para o pré-processamento

Apenas o Preprocessor não ajudará muito: São necessárias as classes FileManager e SourceManager para a leitura de arquivos e o controle de locais de origem para o diagnóstico. A classe FileManager implementa um suporte para consulta do sistema de arquivos, armazenamento em cache do sistema de arquivos e procura do diretório. Consulte a classe FileEntry , que define a abstração do clang para um arquivo de origem. A Listagem 6 fornece um trecho do arquivo de cabeçalho FileManager.h.

Listagem 6. A classe FileManager do clang
class FileManager : public llvm::RefCountedBase<FileManager> {
  FileSystemOptions FileSystemOpts;
   /// \brief The virtual directories that we have allocated.  For each
  /// virtual file (e.g. foo/bar/baz.cpp), we add all of its parent
  /// directories (foo/ and foo/bar/) here.
  SmallVector<DirectoryEntry*, 4> VirtualDirectoryEntries;
  /// \brief The virtual files that we have allocated.
  SmallVector<FileEntry*, 4> VirtualFileEntries;
 /// NextFileUID - Each FileEntry we create is assigned a unique ID #.
  unsigned NextFileUID;
  // Statistics.
  unsigned NumDirLookups, NumFileLookups;
  unsigned NumDirCacheMisses, NumFileCacheMisses;
 // …
  // Caching.
  OwningPtr<FileSystemStatCache> StatCache;

A classe SourceManager é normalmente consultada em busca de objetos SourceLocation . A partir do arquivo de cabeçalho SourceManager.h, informações sobre os objetos SourceLocation são fornecidas na Listagem 7.

Listagem 7. Entendendo o SourceLocation
/// There are three different types of locations in a file: a spelling
/// location, an expansion location, and a presumed location.
///
/// Given an example of:
/// #define min(x, y) x < y ? x : y
///
/// and then later on a use of min:
/// #line 17
/// return min(a, b);
///
/// The expansion location is the line in the source code where the macro
/// was expanded (the return statement), the spelling location is the
/// location in the source where the macro was originally defined,
/// and the presumed location is where the line directive states that
/// the line is 17, or any other line.

Claramente, o SourceManager depende do FileManager em segundo plano; de fato, o construtor da classe SourceManager aceita uma classe FileManager como o argumento de entrada. Finalmente, é preciso manter um controle dos erros que podem ser encontrados ao processar a origem e descrevê-los. Isso pode ser feito utilizando a classe DiagnosticsEngine . Assim como no Preprocessor, há duas opções:

  • Criar todos os objetos necessários sozinho
  • Deixar que o CompilerInstance faça todo o trabalho

Vamos escolher a segunda opção. A Listagem 8 mostra o código para o Preprocessor; todo o resto já foi explicado.

Listagem 8. Criando um preprocessor com a API do clang
using namespace clang;
int main()
{
    CompilerInstance ci;
    ci.createDiagnostics(0,NULL); // create DiagnosticsEngine
    ci.createFileManager();  // create FileManager
    ci.createSourceManager(ci.getFileManager()); // create SourceManager
    ci.createPreprocessor();  // create Preprocessor
    const FileEntry *pFile = ci.getFileManager().getFile("hello.c");
    ci.getSourceManager().createMainFileID(pFile);
    ci.getPreprocessor().EnterMainSourceFile();
    ci.getDiagnosticClient().BeginSourceFile(ci.getLangOpts(), &ci.getPreprocessor());
    Token tok;
    do {
        ci.getPreprocessor().Lex(tok);
        if( ci.getDiagnostics().hasErrorOccurred())
            break;
        ci.getPreprocessor().DumpToken(tok);
        std::cerr << std::endl;
    } while ( tok.isNot(clang::tok::eof));
    ci.getDiagnosticClient().EndSourceFile();
}

A Listagem 8 utiliza a classe CompilerInstance para criar o DiagnosticsEngine em série (a chamada de método ci.createDiagnostics ) e FileManager (ci.createFileManager e ci.CreateSourceManager). Quando for feita a associação de arquivos utilizando o FileEntry, continue processando cada token no arquivo de origem até chegar ao final do arquivo (EOF). O método DumpToken do preprocessor descarrega o token na tela.

Para compilar e executar o código na Listagem 8, utilize o makefile fornecido na Listagem 9, com os ajustes necessários para suas pastas de instalação do clang e do LLVM. A ideia é utilizar a ferramenta llvm-config para fornecer quaisquer caminhos e bibliotecas de inclusão necessários do LLVM: Não se deve nunca tentar vinculá-los à mão em uma linha de comandos de g++.

Listagem 9. Makefile para desenvolver o código do preprocessor
CXX := g++
RTTIFLAG := -fno-rtti
CXXFLAGS := $(shell llvm-config --cxxflags) $(RTTIFLAG)
LLVMLDFLAGS := $(shell llvm-config --ldflags --libs)
DDD := $(shell echo $(LLVMLDFLAGS))
SOURCES = main.cpp
OBJECTS = $(SOURCES:.cpp=.o)
EXES = $(OBJECTS:.o=)
CLANGLIBS = \
    -L /usr/local/lib \
    -lclangFrontend \
    -lclangParse \
    -lclangSema \
    -lclangAnalysis \
    -lclangAST \
    -lclangLex \
    -lclangBasic \
    -lclangDriver \
    -lclangSerialization \
    -lLLVMMC \
    -lLLVMSupport \
all: $(OBJECTS) $(EXES)
%: %.o
        $(CXX) -o $@ $< $(CLANGLIBS) $(LLVMLDFLAGS)

Após a compilação e a execução do código acima, é preciso obter a saída da Listagem 10.

Listagem 10. Falha ao executar a codificação na Listagem 7
Assertion failed: (Target && "Compiler instance has no target!"), 
   function getTarget, file 
   /Users/Arpan/llvm/tools/clang/lib/Frontend/../..
   /include/clang/Frontend/CompilerInstance.h,
   line 294.
Abort trap: 6

O que aconteceu aqui é que uma última parte das configurações do CompilerInstance foi esquecida: a plataforma de destino para a qual o código deveria ser compilado. É nessa parte que as classes TargetInfo e TargetOptions entram em ação. Segundo o cabeçalho do clang TargetInfo.h, a classe TargetInfo armazena as informações necessárias sobre as informações de destino para a geração de códigos e deve ser criada antes da sucessão da compilação ou do pré-processamento. Como esperado, o TargetInfo parece ter informações sobre as larguras, alinhamentos, etc. de flutuação e de número inteiro. A Listagem 11 fornece um trecho do arquivo de cabeçalho TargetInfo.h.

Listagem 11. A classe TargetInfo do clang
class TargetInfo : public llvm::RefCountedBase<TargetInfo> {
  llvm::Triple Triple;
protected:
  bool BigEndian;
  unsigned char PointerWidth, PointerAlign;
  unsigned char IntWidth, IntAlign;
  unsigned char HalfWidth, HalfAlign;
  unsigned char FloatWidth, FloatAlign;
  unsigned char DoubleWidth, DoubleAlign;
  unsigned char LongDoubleWidth, LongDoubleAlign;
  // …

A classe TargetInfo aceita dois argumentos para sua inicialização: DiagnosticsEngine e TargetOptions. O último deve ter a sequência Triple configurada no valor adequado para a plataforma atual. É aqui que o LLVM é bastante útil. A Listagem 12 mostra a inclusão à Listagem 9 para fazer com que o preprocessor funcione.

Listagem 12. Configurando as opções de destino para o compilador
int main()
{
    CompilerInstance ci;
    ci.createDiagnostics(0,NULL);
    // create TargetOptions
    TargetOptions to;
    to.Triple = llvm::sys::getDefaultTargetTriple();
    // create TargetInfo
    TargetInfo *pti = TargetInfo::CreateTargetInfo(ci.getDiagnostics(), to);
    ci.setTarget(pti);
    // rest of the code same as in Listing 9…
    ci.createFileManager();
    // …

E é isso. Execute esse código e veja qual saída é obtida para o teste hello.c simples:

#include <stdio.h>
int main() {  printf("hello world!\n"); }

A Listagem 13 mostra a saída parcial do preprocessor.

Listagem 13. Saída (parcial) do preprocessor
typedef 'typedef'
struct 'struct'
identifier '__va_list_tag'
l_brace '{'
unsigned 'unsigned'
identifier 'gp_offset'
semi ';'
unsigned 'unsigned'
identifier 'fp_offset'
semi ';'
void 'void'
star '*'
identifier 'overflow_arg_area'
semi ';'
void 'void'
star '*'
identifier 'reg_save_area'
semi ';'
r_brace '}'
identifier '__va_list_tag'
semi ';'

identifier '__va_list_tag'
identifier '__builtin_va_list'
l_square '['
numeric_constant '1'
r_square ']'
semi ';'

Criando um objeto Preprocessor

Uma das vantagens das bibliotecas do clang é que é possível alcançar o mesmo resultado de diversas maneiras. Nesta seção, um objeto Preprocessor é criado, mas sem fazer uma solicitação direta ao CompilerInstance. A partir do arquivo de cabeçalho Preprocessor.h, a Listagem 14 mostra o construtor para o Preprocessor.

Listagem 14. Construindo um objeto Preprocessor
Preprocessor(DiagnosticsEngine &diags, LangOptions &opts,
               const TargetInfo *target,
               SourceManager &SM, HeaderSearch &Headers,
               ModuleLoader &TheModuleLoader,
               IdentifierInfoLookup *IILookup = 0,
               bool OwnsHeaderSearch = false,
               bool DelayInitialization = false);

Levando em consideração o construtor, fica claro que é preciso criar seis objetos diferentes antes de iniciar esse monstro. Você já conhece DiagnosticsEngine, TargetInfoe SourceManager. CompilerInstance é derivado do ModuleLoader. Portanto, é preciso criar dois novos objetos—uma para o LangOptions e outro para o HeaderSearch. A classe LangOptions permite a compilação de uma variedade de dialetos C/C++ , incluindo C99, C11e C++0x. Consulte os cabeçalhos LangOptions.h e LangOptions.def para mais informações. Por fim, a classe HeaderSearch armazena um std::vector de diretórios para procura, entre outras coisas. A Listagem 15 mostra o código para o Preprocessor.

Listagem 15. Preprocessor criado
using namespace clang;
int main()  {
    DiagnosticOptions diagnosticOptions;
    TextDiagnosticPrinter *printer = 
      new TextDiagnosticPrinter(llvm::outs(), diagnosticOptions);
    llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs> diagIDs;
    DiagnosticsEngine diagnostics(diagIDs, printer);
    LangOptions langOpts;
    clang::TargetOptions to;
    to.Triple = llvm::sys::getDefaultTargetTriple();
    TargetInfo *pti = TargetInfo::CreateTargetInfo(diagnostics, to);
    FileSystemOptions fsopts;
    FileManager fileManager(fsopts);
    SourceManager sourceManager(diagnostics, fileManager);
    HeaderSearch headerSearch(fileManager, diagnostics, langOpts, pti);
    CompilerInstance ci;
    Preprocessor preprocessor(diagnostics, langOpts, pti,
      sourceManager, headerSearch, ci);
    const FileEntry *pFile = fileManager.getFile("test.c");
    sourceManager.createMainFileID(pFile);
    preprocessor.EnterMainSourceFile();
    printer->BeginSourceFile(langOpts, &preprocessor);
    // … similar to Listing 8 here on
}

É preciso observar algumas coisas sobre o código na Listagem 15:

  • O HeaderSearch não foi inicializado para apontar para nenhum diretório. Isso é preciso ser feito.
  • A API do clang requer que o TextDiagnosticPrinter seja alocado no heap. A alocação na pilha causa uma falha.
  • Não foi possível se livrar do CompilerInstance. Como o CompilerInstance está sendo utilizado de qualquer maneira, por que se preocupar em criá-lo, exceto para se sentir mais confortável com a API do clang?

Opção de linguagem: C++

Até agora, você trabalhou com o código de teste C : Que tal um pouco de C++? No código da Listagem 15, inclua langOpts.CPlusPlus = 1;e teste-o com o código de teste na Listagem 16.

Listagem 16. Código de teste C++ para o preprocessor
template <typename T, int n>
struct s { 
  T array[n];
};
int main() {
  s<int, 20> var;
}

A Listagem 17 mostra a saída parcial do seu programa.

Listagem 17. Saída parcial do preprocessor do código na Listagem 16
identifier 'template'
less '<'
identifier 'typename'
identifier 'T'
comma ','
int 'int'
identifier 'n'
greater '>'
struct 'struct'
identifier 's'
l_brace '{'
identifier 'T'
identifier 'array'
l_square '['
identifier 'n'
r_square ']'
semi ';'
r_brace '}'
semi ';'
int 'int'
identifier 'main'
l_paren '('
r_paren ')'

Criando uma árvore de análise

O método ParseAST definido no clang/Parse/ParseAST.h é um dos mais importantes fornecidos pelo clang. Segue uma declaração de rotina copiada do ParseAST.h:

void ParseAST(Preprocessor &pp, ASTConsumer *C,
       ASTContext &Ctx, bool PrintStats = false,
       TranslationUnitKind TUKind = TU_Complete,
       CodeCompleteConsumer *CompletionConsumer = 0);

ASTConsumer fornece uma interface abstrata da qual derivar. É isso que deve ser feito, pois diferentes clientes podem descartar ou processar o AST de diversas maneiras. O código do seu cliente será derivado do ASTConsumer. A classe ASTContext armazena—entre outras coisas—informações sobre declarações de tipo. Segue a forma mais fácil de tentar: Imprima uma lista de variáveis globais no seu código utilizando a API ASTConsumer do clang. Muitas empresas de tecnologia possuem regras rigorosas com relação ao uso da variável global no código C++ e esse pode ser o ponto de início da criação de sua ferramenta lint customizada. O código para seu consumidor customizado é fornecido na Listagem 18.

Listagem 18. Uma classe de consumidor AST customizado
class CustomASTConsumer : public ASTConsumer {
public:
 CustomASTConsumer () :  ASTConsumer() { }
    virtual ~ CustomASTConsumer () { }
    virtual bool HandleTopLevelDecl(DeclGroupRef decls)
    {
        clang::DeclGroupRef::iterator it;
        for( it = decls.begin(); it != decls.end(); it++)
        {
            clang::VarDecl *vd = llvm::dyn_cast<clang::VarDecl>(*it);
            if(vd)
               std::cout << vd->getDeclName().getAsString() << std::endl;;
        }
        return true;
    }
};

O método HandleTopLevelDecl (originalmente fornecido no ASTConsumer) está sendo substituído por sua própria versão. O clang está passando a lista de globais; é feita uma iteração e os nomes de variáveis são impressos. Retirada do ASTConsumer.h, a Listagem 19 mostra vários outros método que o código do consumidor deve substituir.

Listagem 19. Outros métodos que podem ser substituídos no código do cliente
/// HandleInterestingDecl - Handle the specified interesting declaration. This
/// is called by the AST reader when deserializing things that might interest
/// the consumer. The default implementation forwards to HandleTopLevelDecl.
virtual void HandleInterestingDecl(DeclGroupRef D);

/// HandleTranslationUnit - This method is called when the ASTs for entire
/// translation unit have been parsed.
virtual void HandleTranslationUnit(ASTContext &Ctx) {}

/// HandleTagDeclDefinition - This callback is invoked each time a TagDecl
/// (e.g. struct, union, enum, class) is completed.  This allows the client to
/// hack on the type, which can occur at any point in the file (because these
/// can be defined in declspecs).
virtual void HandleTagDeclDefinition(TagDecl *D) {}

/// Note that at this point it does not have a body, its body is
  /// instantiated at the end of the translation unit and passed to
  /// HandleTopLevelDecl.
  virtual void HandleCXXImplicitFunctionInstantiation(FunctionDecl *D) {}

Por fim, a Listagem 20 mostra o código do cliente real utilizando a classe do consumidor AST customizado desenvolvida.

Listagem 20. Código do cliente utilizando um consumidor AST customizado
int main() { 
    CompilerInstance ci;
    ci.createDiagnostics(0,NULL);
    TargetOptions to;
    to.Triple = llvm::sys::getDefaultTargetTriple();
    TargetInfo *tin = TargetInfo::CreateTargetInfo(ci.getDiagnostics(), to);
    ci.setTarget(tin);
    ci.createFileManager();
    ci.createSourceManager(ci.getFileManager());
    ci.createPreprocessor();
    ci.createASTContext();
    CustomASTConsumer *astConsumer = new CustomASTConsumer ();
    ci.setASTConsumer(astConsumer);
    const FileEntry *file = ci.getFileManager().getFile("hello.c");
    ci.getSourceManager().createMainFileID(file);
    ci.getDiagnosticClient().BeginSourceFile(
       ci.getLangOpts(), &ci.getPreprocessor());
    clang::ParseAST(ci.getPreprocessor(), astConsumer, ci.getASTContext());
    ci.getDiagnosticClient().EndSourceFile();
    return 0;
}

Conclusão

Outros Artigos nesta Série

Veja mais artigos na série Create a working compiler with the LLVM framework .

Esta série de duas partes abordou diversos assuntos: Explorou a IR do LLVM, ofereceu maneiras de gerar IR por meio da criação e de APIs do LLVM, mostrou como criar um plug-in customizável para o back end do LLVM e explicou o front-end do LLVM e seu rico conjunto de cabeçalhos. Você também aprendeu como utilizar esse front-end para o pré-processamento e consumo do AST. A criação de um compilador e a sua extensão, especialmente para linguagens complexas, como C++, parecia um bicho de sete cabeças no início da história da computação, mas com o LLVM, a vida se tornou mais simples. Ainda é necessário trabalhar na documentação do LLVM e clang, mas até que isso não seja resolvido, sugiro um copo de cerveja e VIM/doxygen para navegar nos cabeçalhos. Divirta-se!

Recursos

Aprender

Obter produtos e tecnologias

  • Visite o site do projeto LLVM e faça o download da versão mais recente.
  • Encontre detalhes sobre o clang no site do LLVM.
  • Avalie produtos IBM da maneira que for melhor para você: faça download da versão de teste de um produto, avalie um produto online, use-o em um ambiente de nuvem ou passe algumas horas na SOA Sandbox aprendendo a implementar Arquitetura Orientada a Serviços de modo eficiente.

Discutir

Comentários

developerWorks: Conecte-se

Los campos obligatorios están marcados con un asterisco (*).


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


A primeira vez que você entrar no developerWorks, um perfil é criado para você. Informações no seu perfil (seu nome, país / região, e nome da empresa) é apresentado ao público e vai acompanhar qualquer conteúdo que você postar, a menos que você opte por esconder o nome da empresa. Você pode atualizar sua conta IBM a qualquer momento.

Todas as informações enviadas são seguras.

Elija su nombre para mostrar



Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Los campos obligatorios están marcados con un asterisco (*).

(Escolha um nome de exibição de 3 - 31 caracteres.)

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


Todas as informações enviadas são seguras.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Software livre, Linux
ArticleID=823947
ArticleTitle=Criar um Compilador Funcional com a Estrutura LLVM, Parte 2
publish-date=05162014