análise de tempo de execução utilizando llvm · figura 17: código fonte do programa que realiza...

104
UNIVERSIDADE FEDERAL DE SANTA CATARINA DEPARTAMENTO DE INFORMÁTICA E ESTATÍSTICA CURSO DE CIÊNCIAS DA COMPUTAÇÃO Leonardo Maccari Rufino Análise de Tempo de Execução Utilizando LLVM Florianópolis - SC 2008

Upload: nguyenkiet

Post on 02-Dec-2018

214 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

UNIVERSIDADE FEDERAL DE SANTA CATARINA

DEPARTAMENTO DE INFORMÁTICA E ESTATÍSTICA

CURSO DE CIÊNCIAS DA COMPUTAÇÃO

Leonardo Maccari Rufino

Análise de Tempo de Execução

Utilizando LLVM

Florianópolis - SC

2008

Page 2: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

2

Leonardo Maccari Rufino

Análise de Tempo de Execução

Utilizando LLVM

Trabalho de conclusão de curso

apresentado como parte dos

requisitos para obtenção do grau de

bacharel em Ciências da Computação

pela Universidade Federal de Santa

Catarina.

Orientador

Olinto José Varela Furtado

Florianópolis - SC

Page 3: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

3

Leonardo Maccari Rufino

Análise de Tempo de Execução

Utilizando LLVM

Trabalho de conclusão de curso

apresentado como parte dos

requisitos para obtenção do grau de

bacharel em Ciências da Computação

pela Universidade Federal de Santa

Catarina.

___________________________

Olinto José Varela Furtado

Orientador

Banca Examinadora:

José Eduardo de Lucca

Luiz Cláudio Villar dos Santos

Ricardo Azambuja Silveira

Florianópolis - SC

Page 4: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

4

RESUMO

Os sistemas embarcados dominam o mercado de diversas áreas comerciais hoje em dia.

Muitos desses sistemas podem também ser classificados como sistemas de tempo real, os

quais podem se dividir em críticos e brandos. Os sistemas de tempo real crítico necessitam de

uma validação quanto a sua correta implementação. Essa validação pode ser feita através de

técnicas estáticas ou dinâmicas. Nesse trabalho, será explicado como realizar essa análise,

dando ênfase à análise estática, explicando cada uma de suas etapas que são: análise do fluxo

de controle, análise de baixo nível e por fim o cálculo. Será feito também uma comparação

entre essas duas técnicas de análise. Também será comentado sobre a infraestrutura de

compilação LLVM, a qual está muito ativa no momento, descrevendo seus objetivos,

arquitetura e sua representação intermediária, a qual representa um dos fatores chaves que o

diferencia dos demais sistemas. Esse framework foi utilizado como base para a

implementação da ferramenta llflow, a qual será apresentada nesse trabalho. Para finalizar,

realizar-se-ão testes, correções e inclusões de novas funcionalidades em cima da ferramenta

llflow, alvo desse trabalho.

Palavras-Chave: Análise de Tempo de Execução, WCET, Tempo de Execução do Pior Caso,

Sistemas de Tempo Real, LLVM, llflow.

Page 5: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

5

ABSTRACT

The embedded systems dominate the commercial market today. Many of these systems can

also be classified as real-time systems, which can be split into hard and soft. The critical real-

time systems require a validation about its correct implementation. This validation can be

done through static or dynamic techniques. In this work, it will be explained how to perform

this analysis, emphasizing the static analysis, explaining each of its steps that are: control-

flow analysis, low level analysis and finally the calculation. It will be made a comparison

between these two analysis techniques. It will also be commented on the LLVM compilation

infrastructure, which is very active at the time, describing their goals, architecture and its

intermediate representation, which represents one of the key factors that differentiates from

other systems. This framework was used as a basis for the implementation of the llflow tool,

which will be presented in the work. Finally, it will be testing, corrections and additions of

new features in the llflow tool, target of this work.

Keywords: Execution Time Analysis, WCET, Worst Case Execution Time, Real-Time

Systems, LLVM, llflow.

Page 6: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

6

LISTA DE ILUSTRAÇÕES

Figura 1: Exemplo de curvas do WCET e BCET para análise estática e dinâmica ................. 14 Figura 2: Estrutura da análise estática do WCET ..................................................................... 16 Figura 3: Etapas da análise do fluxo de controle ...................................................................... 17 Figura 4: Arquitetura do sistema LLVM .................................................................................. 24 Figura 5: Código fonte em linguagem C, exemplificando o LLVM IR ................................... 29 Figura 6: Código na representação LVIS, exemplificando o LLVM IR .................................. 29 Figura 7: Arquitetura da ferramenta llflow............................................................................... 34 Figura 8: Arquivo de configuração da ferramenta llflow ......................................................... 36 Figura 9: Código fonte do programa que realiza a busca binária ............................................. 40 Figura 10: Geração dos arquivos texto e binário LLVM.......................................................... 41 Figura 11: Código na representação LVIS, sem otimização (busca binária) ........................... 42 Figura 12: Código na representação LVIS, com otimização (busca binária) ........................... 43 Figura 13: Arquivo de configuração (busca binária) ................................................................ 44 Figura 14: Chamada à ferramenta llflow .................................................................................. 44 Figura 15: Resultado da análise, sem otimização (busca binária) ............................................ 45 Figura 16: Resultado da análise, com otimização (busca binária) ........................................... 46 Figura 17: Código fonte do programa que realiza a raiz quadrada .......................................... 50 Figura 18: Código na representação LVIS, sem otimização (raiz quadrada) ........................... 52 Figura 19: Arquivo de configuração (raiz quadrada) ............................................................... 52 Figura 20: Resultado da análise, sem otimização (raiz quadrada) ........................................... 53 Figura 21: Código fonte do programa que executa a sequência de fibonacci .......................... 55 Figura 22: Código na representação LVIS, com otimização (fibonacci) ................................. 56 Figura 23: Arquivo de configuração (fibonacci) ...................................................................... 56 Figura 24: Resultado da análise, com otimização (fibonacci) .................................................. 57 Figura 25: Código fonte do programa com loops aninhados dependentes ............................... 62 Figura 26: Código na representação LVIS, sem otimização (janne complex) ......................... 64 Figura 27: Arquivo de configuração (janne complex).............................................................. 64 Figura 28: Resultado da análise da ferramenta llflow original (janne complex)...................... 65 Figura 29: Resultado da análise da ferramenta llflow modificada (janne complex) ................ 66 Figura 30: Código fonte do programa insertion sort ................................................................ 69 Figura 31: Código na representação LVIS, com otimização (insertion sort) ........................... 70 Figura 32: Arquivo de configuração (insertion sort) ................................................................ 70 Figura 33: Resultado da análise, com otimização (insertion sort) ............................................ 71 Figura 34: Código fonte do programa de busca em array multidimensional ........................... 74 Figura 35: Código na representação LVIS, sem otimização (busca array multidimensional) . 76 Figura 36: Arquivo de configuração (busca array multidimensional) ...................................... 77 Figura 37: Resultado da análise, sem otimização (busca array multidimensional) .................. 78 Figura 38: Código fonte do programa de teste de código morto .............................................. 80 Figura 39: Código na representação LVIS, com otimização (teste de código morto) .............. 80 Figura 40: Arquivo de configuração (teste de código morto) .................................................. 81 Figura 41: Resultado da análise, com otimização (teste de código morto) .............................. 81 Figura 42: Código fonte do programa cover ............................................................................ 82 Figura 43: Código na representação LVIS, sem otimização (cover) ........................................ 84 Figura 44: Arquivo de configuração (cover) ............................................................................ 85 Figura 45: Resultado da análise, sem otimização (cover) ........................................................ 86

Page 7: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

7

LISTA DE ABREVIATURAS E SIGLAS

BCET Best Case Execution Time

CFG Control-Flow Graph

CP Constraint Programming

ILP Integer Linear Programming

IPET Implicit Path Enumeration Technique

IR Intermediate Representation

LLVM Low Level Virtual Machine

LVIS LLVM Virtual Instruction Set

SSA Static Single Assignment

WCET Worst Case Execution Time

Page 8: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

8

SUMÁRIO

1 INTRODUÇÃO .................................................................................................................. 9 1.1 Motivação ...................................................................................................................... 11 1.2 Objetivos ........................................................................................................................ 11 1.2.1 Objetivo Geral ............................................................................................................... 11 1.2.2 Objetivos Específicos ..................................................................................................... 12 2 ANÁLISE DO TEMPO DE EXECUÇÃO DO PIOR CASO ........................................... 13 2.1 Análise Dinâmica .......................................................................................................... 14 2.2 Análise Estática ............................................................................................................. 15 2.2.1 Análise do Fluxo de Controle ........................................................................................ 16 2.2.2 Análise de Baixo Nível ................................................................................................... 18 2.2.3 Cálculo .......................................................................................................................... 19 2.2.4 Problemas ...................................................................................................................... 20 2.3 Análise Estática Vs. Análise Dinâmica ......................................................................... 21 3 LLVM................................................................................................................................ 23 3.1 Arquitetura ..................................................................................................................... 23 3.1.1 Tempo de Compilação ................................................................................................... 24 3.1.2 Tempo de Ligação ......................................................................................................... 25 3.1.3 Tempo de Execução ....................................................................................................... 26 3.1.4 Tempo Inativo ................................................................................................................ 26 3.2 LLVM IR ....................................................................................................................... 27 3.2.1 Representação Textual, Binária e Em Memória ........................................................... 29 3.2.2 Sistema de Tipo .............................................................................................................. 30 3.2.3 Alocação Explícita de Memória .................................................................................... 30 4 LLFLOW ........................................................................................................................... 32 4.1 Características ................................................................................................................ 32 4.2 Arquitetura ..................................................................................................................... 33 4.3 Entradas ......................................................................................................................... 34 4.3.1 Arquivo de Configuração .............................................................................................. 35 4.4 Saídas ............................................................................................................................. 36 5 VALIDANDO A FERRAMENTA LLFLOW .................................................................. 38 5.1 Busca Binária ................................................................................................................. 38 5.2 Raiz Quadrada ............................................................................................................... 48 5.3 Fibonacci ....................................................................................................................... 54 6 INCREMENTANDO A FERRAMENTA LLFLOW - PARTE 1 .................................... 61 6.1 Janne Complex .............................................................................................................. 62 6.2 Insertion Sort ................................................................................................................. 68 6.3 Busca em Array Multidimensional ................................................................................ 72 7 INCREMENTANDO A FERRAMENTA LLFLOW - PARTE 2 .................................... 79 7.1 Teste de Código Morto .................................................................................................. 79 7.2 Cover ............................................................................................................................. 82 8 CONCLUSÃO .................................................................................................................. 87 8.1 Considerações Finais ..................................................................................................... 87 8.2 Trabalhos Futuros .......................................................................................................... 88 REFERÊNCIAS ....................................................................................................................... 89 APÊNDICE A - ARTIGO ........................................................................................................ 91

Page 9: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

9

1 INTRODUÇÃO

Com o passar dos tempos, o computador tornou-se uma importante ferramenta na vida

da população de qualquer ponto do planeta. Cada dia mais os computadores invadem as casas

das pessoas, muitas vezes sem que sejam percebidos. É o caso dos sistemas embarcados (ou

também chamados de sistemas embutidos, embedded systems) que são, segundo

(ENGBLOM, J, 2002), “um computador que não se parece com um computador”, ou também,

melhor explicado em (MACHADO, A., 2008), “construídos com propósitos específicos e pré-

definidos e, em função disto, possuírem características que favorecem o uso para este

propósito e dificultam o uso para outros fins”. Sistemas embarcados estão localizados em

dispositivos que tenham algum processamento feito por um microprocessador encapsulado.

Exemplos estão por toda parte, como em brinquedos, eletrodomésticos, aparelhos celulares,

automóveis, aviões e mais uma diversidade de produtos.

Sistemas embarcados muitas vezes podem também ser classificados como estando no

grupo dos sistemas de tempo real (real-time systems). Estes representam os sistemas

computacionais que possuem uma característica marcante em comum, a qual diz que para que

o sistema seja considerado correto, além de apresentar as funcionalidades esperadas, também

deve responder dentro de um tempo estabelecido aos estímulos que recebem, ou seja, deve-se

garantir que as ações sejam executadas dentro de um intervalo de tempo pré-determinado.

Os sistemas de tempo real podem ser divididos em duas classes, distinguíveis por seus

requisitos temporais e de confiabilidade, que são os sistemas de tempo real brando (soft real-

time systems) e sistemas de tempo real crítico (hard real-time systems). O primeiro

caracteriza-se por possuir um prazo de resposta mais flexível em relação ao outro, ou seja, em

sistemas brandos, o descumprimento ocasional de um prazo de tempo pode ser aceito sem

grandes problemas. Estes possuem requisitos de segurança não-críticos. Já os sistemas de

tempo real crítico, são caracterizados por possuírem um prazo de resposta estrito, ou seja, seu

comportamento deve ser previsível até mesmo quando se está executando em sobrecarga.

Estes possuem requisitos de segurança críticos, sendo que se o sistema chegar a falhar ou

mesmo não responder dentro do tempo pré-estabelecido, problemas mais graves poderão

ocorrer. Um exemplo de um sistema de tempo real brando seria o de um aparelho de som e,

Page 10: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

10

do outro lado, um exemplo de um sistema crítico seria o controle do sistema de “air bag” de

carros.

Com a finalidade de assegurar a correta execução de sistemas de tempo real com

relação ao tempo de execução, algumas técnicas chamadas de análise de tempo de execução

surgiram. As análises podem ser realizadas dinâmica ou estaticamente, como também de uma

maneira híbrida. A análise dinâmica diz respeito à medição do tempo envolvendo a execução

do programa sobre determinadas entradas e observando o seu comportamento. Este tipo de

análise não é tão eficiente, pois não garante a análise do tempo de execução do pior e do

melhor caso (WCET e BCET respectivamente). Isto porque existe uma grande dificuldade em

se obter os dados de entrada que provoquem estes casos extremos. Já a análise de tempo de

execução estática, garante que esses tempos sejam calculados corretamente. Isto é realizado

através do cálculo do tempo de execução a partir da análise do código do programa.

A análise estática está dividida em duas fases, a análise de alto nível e a de baixo

nível. A análise de alto nível diz respeito ao fluxo de execução do programa dependente

apenas das características do programa analisado. Já a análise de baixo nível referencia-se à

simulação do comportamento de tempo do processador levando em conta detalhes mais

profundos como o uso de caches ou pipelines por exemplo.

Este trabalho está situado exatamente nesta área contextualizada até aqui, análise de

tempo de execução estática de alto nível. Para tal feito, será utilizada uma ferramenta em

ascensão no momento chamada LLVM (Low Level Virtual Machine). LLVM é um

framework que possui diversas funcionalidades como, por exemplo, o desenvolvimento de

compiladores, profundas otimizações de código, como também a análise do fluxo de

execução. Uma característica marcante desta infra-estrutura, que possibilita o uso para todas

estas áreas citadas, é o fato dela possuir uma representação intermediária própria de código

chamada LVIS (LLVM Virtual Instruction Set). Esta representação é muito interessante, pois

se trata de uma codificação de baixo nível, porém possui algumas características de alto nível

como, por exemplo, ser “tipada” (suas variáveis são declaradas sendo de determinado tipo).

Page 11: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

11

1.1 Motivação

Segundo (MACHADO, A., 2008), “98% dos processadores vendidos são utilizados

em sistemas embarcados”. Com este elevado consumo deste tipo de produto, foi necessária

uma dedicação por parte dos desenvolvedores para garantir a qualidade do produto. Produtos

que executam o proposto de maneira satisfatória levam vantagens sobre os demais,

principalmente quando se trata de sistemas de tempo real crítico. Nestes, um fator importante

é a confiabilidade, ou seja, devem ser extremamente previsíveis quanto as suas ações.

Quando se diz que existe uma grande preocupação de que os sistemas de tempo real

críticos sejam executados dentro de um período de tempo pré-determinado, está-se afirmando

que o tempo calculado para o WCET é satisfatório. Para garantir a confiabilidade desses

sistemas críticos, foi criada a análise de tempo de execução estática, a qual através de cálculos

obtém este valor.

Então, neste cenário de aumento do consumo de sistemas embarcados de tempo real

crítico e com o objetivo de garantir sua correta execução, encaixa-se este trabalho referente à

análise de tempo de execução estática.

1.2 Objetivos

1.2.1 Objetivo Geral

A proposta deste trabalho é fazer um estudo a fundo do tema análise de tempo de

execução estática de alto nível e em paralelo, também, da infra-estrutura LLVM com o

objetivo de obter um conhecimento aprofundado de ambos. Também, serão realizados

verificações e incrementos em uma ferramenta chamada llflow, a qual realiza o cálculo do

WCET e baseia-se no framework LLVM.

Page 12: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

12

1.2.2 Objetivos Específicos

Estudar as técnicas mais utilizadas atualmente de análise de tempo de execução

estática de alto nível;

Estudar o framework LLVM, porém dando enfoque à parte que está associada com o

tema do trabalho;

Realizar verificações e incrementos de novas funcionalidades à ferramenta llflow,

apresentada neste trabalho;

Page 13: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

13

2 ANÁLISE DO TEMPO DE EXECUÇÃO DO PIOR CASO

O propósito da análise do WCET é prover uma informação a priori sobre o pior tempo

de execução possível de um pedaço de código antes de usá-lo em um sistema, conforme

mencionado em (ENGBLOM, J. ERMEDAHLT, A, 2000). Sendo assim, o domínio

tradicional do cálculo do WCET está situado nos sistemas de tempo real crítico, para que haja

uma garantia satisfatória do comportamento do sistema em todas as circunstâncias.

Para o cálculo da estimativa do WCET, algumas suposições são assumidas

previamente conforme (ENGBLOM, J, 2002):

Um programa específico executa isoladamente;

Execução em um determinado CPU e clock;

Compilado com um determinado compilador;

Sem interferência de atividades de fundo (background), como acesso direto a memória

(DMA) e refresh da DRAM;

Sem troca de contextos (preempções) pelo escalonador.

Como já mencionado, o cálculo do tempo de execução do pior caso pode ser realizado

através da análise dinâmica e estática. Essas serão comentadas a seguir, enfatizando a técnica

estática.

Um sistema de tempo real é composto de várias tarefas (tasks) onde cada uma delas

realiza uma funcionalidade específica. Dependendo dos dados de entrada ou de diferentes

comportamentos do ambiente, uma tarefa, tipicamente, apresenta uma variação em seu tempo

de execução. Na figura 1, é mostrado o conjunto de todos os tempos de execução das tarefas

através da curva superior, tempos de execução possíveis. O limite esquerdo representa o

menor tempo de execução, BCET, enquanto que o limite direito, o maior tempo de execução,

WCET. Encontrar esses valores extremos exatos, na maioria das vezes, é muito difícil devido

ao imenso número de caminhos de execuções possíveis, tornando inviável a exploração

exaustiva de todos eles. Na análise de tempo dinâmica, ocorre a medição do tempo de

execução da tarefa inteira para um subconjunto das possíveis execuções, obtendo assim, o

tempo de execução mínimo e máximo observados. Esses resultados, em geral, superestimam o

BCET e subestimam o WCET, sendo desta forma, valores inseguros para sistemas de tempo

Page 14: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

14

real crítico. Esse método de análise é representado pela curva inferior na figura 1, tempos de

execução medidos, ressaltando os valores mínimo e máximo observados. Segundo

(WILHELM, R. et al, 2008), limites no tempo de execução de uma tarefa podem ser

computados por métodos que consideram todos os possíveis tempos de execução, que são

todas as possíveis execuções de uma tarefa, como é o caso da análise estática. Esses métodos

usam abstrações da tarefa para fazer a análise de tempo tornar-se viável. Porém, abstrações

causam a perda de informações, então o limite do WCET computado normalmente

superestima o WCET exato enquanto que subestima o BCET. O limite do WCET representa o

pior caso garantido pelo método ou ferramenta utilizado. Esse método é ilustrado na figura 1

através dos limites de tempo inferior e superior originados pela predição do tempo.

Figura 1: Exemplo de curvas do WCET e BCET para análise estática e dinâmica comparadas

com o valor real, adaptado de (WILHELM, R. et al, 2008).

2.1 Análise Dinâmica

Para obter resultados através da técnica de análise dinâmica, são realizadas medições

da tarefa, ou de suas partes, através da execução em um dado hardware ou um simulador, para

algum conjunto de entradas (“inputs”). Para isso, conforme comentado em (WILHELM, R. et

al, 2008), são utilizadas normalmente uma entre duas abordagens. A primeira chamada de

Page 15: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

15

end-to-end, realiza a medição do programa inteiro de uma só vez, enquanto a outra mede o

tempo de execução de segmentos do código, tipicamente de blocos básicos do CFG. Esses

tempos de execução medidos são então combinados e analisados, normalmente por alguma

forma de cálculo de limite, para produzir estimativas do WCET ou BCET. Todas as

abordagens seguem uma metodologia em comum, como citado em (ENGBLOM, J, 2002):

Determinar a entrada de pior caso;

Executar e medir;

Adicionar uma margem segura.

Em (WILHELM, R. et al, 2008), além da entrada referente ao primeiro passo anterior,

cita-se também a necessidade de determinar o estado inicial que conduzirá à execução do

caminho do pior caso. Com esses valores em mãos, utilizando esta técnica, o problema da

análise seria facilmente resolvido. Porém alguns problemas são notados, como a dificuldade

ou impossibilidade de encontrar o valor da entrada e do estado inicial que resultarão no tempo

de execução de pior caso. Além disso, outro problema seria o fato desta técnica nunca

superestimar o valor do WCET, geralmente subestimando-o.

Desta forma, esta técnica pode ser útil para aplicações que não requerem garantias do

tempo de pior caso encontrado, sendo assim, preferivelmente utilizada pra sistemas de tempo

real não crítico, ou seja, brando. Como dito em (WILHELM, R. et al, 2008), a análise

dinâmica pode dar ao desenvolvedor uma percepção sobre o tempo de execução nos casos

comuns e também a porcentagem das ocorrências do pior caso. Garantias de que o limite

obtido é um valor seguro podem ser conseguidas somente quando a arquitetura utilizada é

simples. Além disso, esta técnica também pode ser utilizada para prover validação para

abordagens de análise estática.

2.2 Análise Estática

Neste tipo de análise, não é necessária a presença do código de execução para o

hardware real ou um simulador. Como dito em (WILHELM, R. et al, 2008), é preferivelmente

pego o código por si só, talvez junto de algumas anotações, utilizando-o para analisar o

Page 16: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

16

conjunto de caminhos do fluxo de controle possível para a tarefa, posteriormente combinando

o fluxo de controle com alguns modelos abstratos da arquitetura do hardware, e assim,

obtendo o limite superior para essa junção. Com a análise estática, existe uma garantia de que

os resultados obtidos para o WCET sejam seguros (safe), além da tentativa de ser o mais

próximo possível do valor real (tight), sendo desta forma, valores utilizáveis.

O cálculo da estimativa do tempo de pior caso utilizando esta técnica é obtido através

de três passos como ilustrado na figura 2. Primeiramente é realizada a análise do fluxo do

programa que determina o comportamento dinâmico do programa, sem considerar o tempo

para cada unidade atômica do fluxo. Após esta etapa é feita a análise de baixo nível que

obtém o tempo de execução para partes do programa no hardware, dada a arquitetura e

características do sistema alvo. Para finalizar, é efetuado o cálculo combinando os resultados

obtidos da análise do fluxo e de baixo nível para dar a estimativa do WCET.

Figura 2: Estrutura da análise estática do WCET, adaptado de (ENGBLOM, J, 2002).

2.2.1 Análise do Fluxo de Controle

Page 17: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

17

Nesta primeira fase da análise estática, análise do fluxo de controle (também chamada

somente de análise do fluxo), é determinado o comportamento dinâmico do programa, ou

seja, tem como propósito coletar informações sobre os caminhos de execuções possíveis. Para

isso, são necessárias algumas informações como o número de iterações de loops,

profundidade das recursões, dependências de dados de entrada, caminhos inviáveis (infeasible

paths), instâncias de funções, etc. Essas informações podem ser fornecidas por anotações

manuais ou pela análise do fluxo automática.

Segundo (WILHELM, R. et al, 2008), há algumas abordagens para análise do fluxo

automática. Alguns dos métodos são gerais, enquanto outros são especializados para certos

tipos de construções de código. Os métodos também diferem no tipo de código que analisam,

isto é, código fonte, intermediário ou código de máquina.

A figura 3 ilustra os detalhes da análise do fluxo que é dividida em três partes. Inicia-

se pela extração de informações do fluxo, o qual deriva informações sobre o comportamento

do programa, seguindo pela representação do fluxo do programa, que armazena as

informações obtidas, e por último a preparação para o cálculo que busca como utilizar a

informação obtida nos passos anteriores para o cálculo do WCET.

Figura 3: Etapas da análise do fluxo de controle, adaptado de (ENGBLOM, J, 2002).

Page 18: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

18

2.2.2 Análise de Baixo Nível

Esta fase da análise estática visa determinar o tempo de execução para partes do

programa considerando os efeitos do hardware alvo. Aqui se trabalha com o arquivo

executável já ligado, o qual representa o programa real, pois somente ele contém todas as

informações necessárias para esta etapa.

Esta fase da análise é baseada no modelo abstrato do processador, o subsistema de

memória, os barramentos e os periféricos, que são conservativos com respeito ao

comportamento de tempo do hardware concreto, significando que o modelo nunca prediz um

tempo de execução menor do que aquele que pode ser observado no processador real. Porém,

a obtenção deste modelo de processador abstrato, que simule o original fielmente, é uma

tarefa muitas vezes complexa dependendo da classe do processador usado. Processadores

mais complexos são mais difíceis de modelar e analisar devido às caches, pipelines e até

mesmo pela quantidade de bits da arquitetura.

Conforme (WILHELM, R. et al, 2008), um típico processador contém muitos

componentes que tornam o tempo de execução dependente do contexto, tais como memória

cache, pipelines e predição de desvios. O tempo de execução de uma instrução individual,

como um acesso a memória depende do histórico de execução. Para encontrar um limite do

tempo de execução preciso para uma dada tarefa, é necessário analisar qual o estado de

ocupação desses componentes do processador para todos os caminhos que conduzem para a

instrução da tarefa analisada no momento. Diferentes estados em que as instruções podem ser

executadas podem conduzir a variações amplas no tempo de execução.

A análise de baixo nível possui dois assuntos principais que são a análise da cache e a

análise do pipeline. Na análise do pipeline a interação é feita somente com instruções

vizinhas, por isso é chamada de análise local, enquanto na análise da cache a interação é

realizada com o programa inteiro, ou seja, global. O comportamento da cache pode afetar o

pipeline, pois na análise do pipeline usam-se os acertos (hits) e erros (misses) da cache. Por

isso, o resultado da análise da cache serve como entrada para a análise do pipeline.

Hoje em dia, como citado em (WILHELM, R. et al, 2008), também se utiliza o termo

análise do comportamento do processador como sinônimo para a expressão análise de baixo

nível.

Page 19: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

19

2.2.3 Cálculo

O objetivo desta fase é encontrar um valor que representa uma estimativa para o

WCET. Há algumas abordagens que são utilizadas para a realização desta fase de cálculo. As

três mais comentadas na literatura, sendo assim as principais, segundo (WILHELM, R. et al,

2008), são chamadas de:

Baseadas em estrutura (structure-based);

Baseadas em caminhos (path-based);

Técnica de enumeração de caminhos implícitos (IPET).

Na técnica baseada em estrutura, ou também chamada de baseada em árvore (tree-

based) em (ENGBLOM, J, 2002), é realizada uma travessia bottom-up da árvore de sintaxe da

tarefa para a realização do cálculo do limite superior. Utiliza-se de constantes de tempo para

os nodos, sendo que os nodos folhas possuem um tempo definitivo, enquanto que há regras

para o cálculo dos nodos internos. Assim, conjuntos de nodos são unidos em nodos únicos,

simultaneamente derivando um tempo para esses novos nodos, reduzindo a árvore de baixo

para cima seguindo as regras, até restar somente um único nodo, o qual conterá o tempo do

pior caso para àquelas instruções analisadas. Conforme dito em (ENGBLOM, J, 2002), este

método é simples e eficiente, porém não pode tratar caminhos inviáveis (infeasible paths).

Além disso, segundo (WILHELM, R. et al, 2008), nem todo fluxo de controle pode ser

expresso através da árvore de sintaxe, como também, essa abordagem assume uma

correspondência muito direta entre a estrutura do arquivo fonte e o programa alvo não

facilmente admitindo otimizações de código. Adicionalmente, outro problema seria que não é

possível adicionar informações adicionais do fluxo como pode ser feito no caso do IPET.

No método baseado em caminho, o objetivo é encontrar o maior caminho global da

tarefa percorrendo o grafo, o qual é previamente criado e contém todos os caminhos de

execuções possíveis representados explicitamente. O valor final encontrado para este caminho

será o WCET para este grafo. Segundo (ENGBLOM, J, 2002), esta técnica é eficiente se

implementada corretamente e ainda pode tratar algumas informações do fluxo como, por

exemplo, o limite de loops. Mas como dito em (WILHELM, R. et al, 2008), a abordagem

baseada em caminho é natural dentro de uma iteração de loop único, mas tem problemas com

fluxos de informações estendendo entre níveis de loops aninhados. Também, o número de

Page 20: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

20

caminhos é exponencial com relação ao número de pontos de desvios, possivelmente

requerendo métodos de busca heurísticos.

No IPET, o fluxo do programa e o tempo de execução atômico são representados

usando restrições (constraints) algébricas e/ou lógicas. Nesta técnica, existem nodos e arestas,

sendo que ambos possuem uma variável contadora de execução (xentity) a qual registra a

quantidade máxima que aquela entidade é executada, informação esta obtida da análise do

fluxo. E também, os nodos, que representam os blocos básicos da tarefa, contêm uma variável

de informação de tempo (tentity) que informa o tempo que aquela entidade leva para ser

executada no hardware alvo, dado dependente da análise de baixo nível. Para obter o valor do

WCET, é feito um cálculo que corresponde a max (xentity * tentity), ou seja, é a maximização

do somatório da multiplicação das duas variáveis de cada entidade. Esta abordagem é muito

poderosa e complexa, podendo tratar muitos fluxos complexos. Porém, segundo (ENGBLOM,

J. ERMEDAHLT, A, 2000), esta técnica não encontrará o caminho de execução do pior caso

explicitamente, mas sim, só dará o contador do pior caso em cada nodo. Assim, não há

informações sobre a ordem de execução precisa. E ainda, segundo (WILHELM, R. et al,

2008), o cálculo de limites baseados na técnica IPET, utiliza-se de técnicas de Programação

Linear Inteira (ILP) ou Programação por Restrições (CP), assim, tendo uma complexidade

potencialmente exponencial com relação ao tamanho da tarefa.

2.2.4 Problemas

A abordagem estática de análise, apesar de ser mais exata e segura que as demais,

ainda assim apresenta dificuldades em sua resolução, principalmente levando em

consideração modernos processadores, os quais tornam a análise do WCET um problema

completamente complexo.

Como comentado em (FAUSTER, J.; KIRNER, R.; PUSCHNER, P, 2003), três

problemas fundamentais existem atualmente dificultando a análise do WCET:

Primeiro, a análise do WCET necessita de exato conhecimento sobre os possíveis

caminhos de execução através do código analisado. Derivar essa informação

automaticamente não é, entretanto, possível no caso geral. Isso é devido ao fato de que

o fluxo de controle de um programa tipicamente depende dos dados de entrada do

Page 21: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

21

programa e um valor limite para o WCET, dessa forma, não pode ser previsto

puramente através da análise do código;

O segundo problema é obter modelos corretos e exatos sobre o comportamento do

tempo de processadores modernos. Esses processadores tipicamente usam

características como caches e/ou pipelines para aumentar seu desempenho de pico. Os

efeitos dessas características de hardware interferem uma na outra e são dessa forma

difíceis de predizer. Ainda pior, o comportamento do processador geralmente é

escassamente documentado. Esses fatos tomados juntos tornam difícil, se não

impossível, para as ferramentas de análise do WCET construir um correto e exato

modelo de hardware do processador alvo;

O terceiro maior problema é a complexidade da análise do WCET. Além dos

problemas em identificar os caminhos de execução possíveis e obter dados de tempo

do hardware detalhados, a complexidade da análise do WCET por si só é um

problema. O número de caminhos que devem ser analisados para calcular um limite

preciso do WCET cresce exponencialmente com o número de desvios (branches)

consecutivos. Enumeração do caminho completo, dessa maneira, torna-se inviável,

exceto para programas tendo um fluxo de controle muito simples. Para superar esses

problemas, técnicas de análise aproximativas são usadas. Essas aproximações causam

superestimação e consequentemente conduzem a um projeto de sistema com utilização

diminuída dos recursos de hardware.

Ainda em (FAUSTER, J.; KIRNER, R.; PUSCHNER, P, 2003), é apresentada uma solução

em nível de programação que seria o uso de um novo paradigma de engenharia de software

feito para o desenvolvimento de software de tempo real, chamado de programação orientada

ao WCET. A fundamental motivação desse novo paradigma é reduzir o número de instruções

do programa com o fluxo de controle dependente de dados de entrada.

2.3 Análise Estática Vs. Análise Dinâmica

Page 22: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

22

Para simplificar as diferenças entre os métodos estáticos e dinâmicos, serão

comparados os pontos de discordância entre ambos, conforme citados em (WILHELM, R. et

al, 2008).

Primeiramente, métodos estáticos computam limites do tempo de execução utilizando

análise do fluxo de controle e cálculo de limites para cobrir todos os caminhos de execução

possíveis. Eles usam abstrações para cobrir todas as possíveis dependências de contexto do

comportamento do processador. O preço que eles pagam para obter resultados seguros é a

necessidade de modelos específicos do comportamento do processador e, possivelmente,

resultados imprecisos tal como superestimar o limite do WCET. Em favor dos métodos

estáticos está o fato de que a análise pode ser realizada sem executar o programa a ser

analisado, o que frequentemente necessita de complexos equipamentos para simular o

hardware e os periféricos do sistema alvo.

Métodos dinâmicos substituem a análise do comportamento do processador pela

medição. Portanto, a menos que todos os caminhos de execução possíveis sejam medidos ou o

processador seja simples o suficiente para permitir que cada medição seja iniciada no estado

inicial de pior caso, algumas mudanças no tempo de execução dependente de contexto podem

ser perdidas e o método, devido a isso, é taxado de inseguro. Para o passo do cálculo da

estimativa, esses métodos podem utilizar análise do fluxo de controle para incluir todos os

possíveis caminhos de execução, ou eles podem simplesmente usar os caminhos de execução

observados, como por exemplo, o número observado de iterações de loop, o qual novamente

faz do método inseguro. As vantagens alegadas para esse método são que ele é mais simples

para aplicar a novos processadores alvos, porque eles não necessitam do modelo do

comportamento do processador e que eles produzem estimativas do WCET e BCET que são

mais precisas, perto dos valores exatos, do que os limites para métodos estáticos,

especialmente para processadores e aplicações complexos.

Page 23: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

23

3 LLVM

O LLVM, Low Level Virtual Machine, é uma infraestrutura de compilação, o qual,

segundo (LATTNER, C, 2006), provê componentes modulares e reusáveis para construção de

compiladores, assim, reduzindo o tempo e custo para construir um compilador particular. Esta

infraestrutura possui uma representação intermediária (LLVM IR) bem definida para

programas, além de muitas bibliotecas (componentes) com interfaces limpas e ferramentas

construídas pelas próprias bibliotecas. LLVM provê componentes independentes de

linguagem e máquina alvo, permitindo que códigos de diferentes linguagens possam ser

ligados e otimizados juntos.

LLVM é baseado na representação SSA que, conforme (LATTNER, C.; ADVE, V,

2009), provê segurança de tipo, operações de baixo nível, flexibilidade e a capacidade de

representar todas as linguagens de alto nível limpamente. A forma SSA é uma representação

intermediária na qual cada variável é atribuída exatamente uma vez. Para maiores detalhes

conferir (WIKIPÉDIA. A ENCICLOPÉDIA LIVRE, 2009d).

3.1 Arquitetura

Conforme comentado em (LATTNER, C.; ADVE, V, 2004a), o objetivo do

framework LLVM é permitir sofisticadas transformações em tempo de compilação, ligação,

instalação, execução e durante o tempo inativo, operando na representação LLVM de um

programa em todos os estágios. Porém, para ser posto em prática, o mesmo deve ser

transparente com relação ao desenvolvedor de aplicações e usuários finais. Também, deve ser

eficiente o suficiente para ser usado com aplicações do mundo real.

Page 24: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

24

Figura 4: Arquitetura do sistema LLVM, adaptado de (LATTNER, C.; ADVE, V, 2004a).

A figura 4 apresenta um diagrama com uma visão geral da arquitetura de alto nível do

framework LLVM. Segundo (LATTNER, C, 2002a), compiladores tradicionais dividem o

processo de compilação em dois passos: compilar e ligar. Essa separação em duas fases provê

benefícios da compilação isolada, como a necessidade de recompilar somente as unidades

modificadas (embora a aplicação inteira deva ainda ser religada). Um compilador tradicional

compila o código fonte para um arquivo objeto (extensão .o) contendo código de máquina,

enquanto que o linker combina esses mesmos arquivos juntamente com bibliotecas para

formar um programa executável. O linker, além de concatenar os arquivos objetos, também

resolve referências de símbolos.

A abordagem utilizada pelo LLVM retém essas duas fases (compilar e ligar), porém

possui algumas diferenças referentes aos compiladores tradicionais. Essas peculiaridades do

LLVM serão descritas nas subseções seguintes, comentando cada uma das fases da arquitetura

do framework LLVM, como também algumas das otimizações feitas em cada uma dessas

etapas. As fases referidas são:

Tempo de Compilação;

Tempo de Ligação;

Tempo de Execução;

Tempo Inativo.

3.1.1 Tempo de Compilação

Nesta primeira fase da arquitetura LLVM encontram-se os front-ends, os quais são

compiladores estáticos que possuem como responsabilidade principal traduzir programas

escritos em uma determinada linguagem fonte para a representação intermediária (LVIS). O

sistema LLVM suporta front-ends de múltiplas linguagens fontes. Conforme (LATTNER, C,

2002a), além da tarefa primária, ainda nesta fase, cada compilador realiza tantas otimizações

Page 25: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

25

estáticas específicas de linguagem quanto possível em cada unidade de tradução para reduzir a

quantidade de trabalho requerida ao otimizador de tempo de ligação. Por fim, uma terceira

funcionalidade para os front-ends seria a invocação de passos LLVM para otimizações global

e interprocedural em um nível mais restrito, que seria o próprio módulo.

Otimizações LLVM são fáceis de serem utilizadas por front-ends, pois essas são

construídas em bibliotecas, sendo modulares e compartilhadas. Assim, compiladores estáticos

podem escolher por utilizar algumas ou até mesmo todas as otimizações disponíveis na

infraestrutura LLVM para aumentar suas potencialidades de geração de código.

3.1.2 Tempo de Ligação

Conforme dito, os front-ends emitem código na representação intermediária LLVM,

os quais são unidos pelo linker LLVM. Esta fase do processo de compilação é a primeira onde

se encontra disponível a maioria do programa para análise e transformação. Dessa forma,

nessa etapa podem ser realizadas otimizações interprocedurais agressivas no programa inteiro.

Essas otimizações operam na representação LLVM diretamente, tomando proveito das

informações semânticas que a mesma contém. As otimizações interprocedurais em LLVM

referem-se a transformações tais como:

Análise de ponteiro sensível ao contexto (Data Structure Analysis);

Construção do grafo de chamadas;

Análise Mod/Ref;

Transformação interprocedural como inlining;

Eliminação de global morta;

Eliminação de argumento morto;

Eliminação de tipo morto;

Propagação de constante;

Eliminação de checagem de limites de array;

Reordenamento de campos de estrutura simples;

Automatic Pool Allocation.

Após as otimizações, as quais são opcionais, serem realizadas, um gerador de código

apropriado para uma determinada arquitetura alvo é selecionado para traduzir o código

Page 26: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

26

LLVM para o código nativo da plataforma corrente. Segundo (LATTNER, C, 2002a), caso o

usuário decida usar otimizações pós ligação, uma cópia do bytecode LLVM comprimido é

incluída no executável. Alternativamente, pode-se utilizar um Just-In-Time Execution Engine

o qual invoca o gerador de código apropriado em runtime, traduzindo uma função em tempo

de execução ao invés de gerar código em tempo de ligação.

3.1.3 Tempo de Execução

No momento em que um programa está executando, as regiões mais frequentemente

executados são identificadas, localizando, por exemplo, regiões de loops mais comumente

acessadas. Ao detectar uma região como essa, em tempo de execução, uma biblioteca de

instrumentação runtime instrumenta o código nativo executando a identificar caminhos

executados frequentemente dentro daquela região. Uma vez que os caminhos são

identificados, o código LLVM original é duplicado e otimizações LLVM são realizadas na

sua cópia. Logo após serem realizadas, o código nativo é regenerado, inserindo desvios entre

o código original e o novo código nativo otimizado.

Segundo (LATTNER, C.; ADVE, V, 2004a), essa estratégia é poderosa, pois ela

combina as seguintes três características:

Gerador de código nativo pode ser realizado a frente do tempo usando sofisticados

algoritmos para gerar código de alto desempenho;

O gerador de código nativo e o otimizador em tempo de execução podem trabalhar

juntos desde que eles são, ambos, parte do framework LLVM, permitindo que o

otimizador em tempo de execução explore o suporte do gerador de código;

O otimizador em tempo de execução pode usar informações de alto nível da

representação intermediária LLVM para realizar sofisticas otimizações.

3.1.4 Tempo Inativo

Como dito em (LATTNER, C, 2002a), alguns tipos de aplicações não são

particularmente receptivas às otimizações em tempo de execução, por conta disso, o

Page 27: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

27

otimizador runtime não pode permitir gastar uma quantidade de tempo significativa

melhorando um pedaço de código, embora ele pode provavelmente detectar os caminhos mais

frequentemente executados pelo programa.

A fim de suportar esses tipos de aplicações e como a representação LLVM é

preservada permanentemente, segundo (LATTNER, C.; ADVE, V, 2004a), LLVM permite

otimização offline transparente de aplicações durante o tempo inativo em um sistema de

usuário final. Tal otimizador é simplesmente uma versão modificada do otimizador

interprocedural de tempo de ligação, mas com uma maior ênfase em otimizações dirigidas por

profile e específica de alvo. Esse tipo de otimização offline permite ser muito mais agressivo

do que o otimizador runtime.

3.2 LLVM IR

Low Level Virtual Machine Intermediate Representation representa um conjunto

virtual de instruções (LVIS) utilizado pelo LLVM. Essa representação de código é um dos

fatores chaves que diferencia LLVM de outros sistemas. Segundo (LATTNER, C.; ADVE, V,

2004a), a representação é designada para prover informação de alto nível sobre programas, o

que é necessário para suportar sofisticadas análises e transformações, enquanto sendo de

baixo nível o suficiente para representar programas arbitrários e para permitir extensiva

otimização nos compiladores estáticos.

Esse conjunto de instruções captura as operações de processadores comuns, mas evita

restrições específicas de máquina tal como registradores físicos, pipelines e convenções de

chamadas de baixo nível. Conforme (LATTNER, C, 2006) e (LATTNER, C.; ADVE, V,

2009), a representação intermediária possui algumas características marcantes como:

Objetiva ser leve, de baixo nível e ao mesmo tempo expressiva;

Deve ser independente de linguagem alvo, incluindo mistura de linguagens fontes

dentro do mesmo arquivo LLVM e permitindo análise e otimização entre linguagens;

Valores escalares são sempre representados na forma SSA, nunca em memória;

IR é inteiramente “tipada” e seus tipos são rigorosamente checados para consistência;

Page 28: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

28

Possui acessos à struct /array explícitos;

IR é facilmente extensível com funções intrínsecas;

Provê um mecanismo para implementar tratamento de exceções;

Como provê informação de tipo, hospeda uma larga variedade de otimizações e

análises.

Completando as características acima com informações de (LATTNER, C.; ADVE, V,

2004a) e (LATTNER, C.; ADVE, V, 2004b), as instruções da representação intermediária

são, na sua maioria, formadas por três endereços de código (three address code), um destino e

dois fontes, como em processadores RISC. Além disso, por usufruir da forma SSA, possui um

conjunto de registradores virtual infinito com informação de tipo, podendo manter valores de

tipos primitivos. Para finalizar, programas transferem valores entre registradores e memória

unicamente via instruções load e store, sendo que, até mesmo essas operações, possuem

ponteiros com referência a tipos.

A estrutura do programa LLVM é simples. Tudo começa com módulos, que nada mais

são do que unidades de compilação, análise e otimização, os quais possuem funções e

variáveis globais. Já as funções contêm seus itens típicos, como argumentos, tipo de retorno,

entre outros, além de blocos básicos, os quais formam o CFG da função. Os blocos básicos,

por sua vez, contêm uma lista de instruções que devem terminar com uma instrução de fluxo

de controle, também chamada de instrução terminadoura, tais como desvios, instruções de

retorno ou chamadas à função. Por fim, as instruções são formadas por um opcode e um vetor

de operandos, sendo que todos os elementos do vetor possuem um tipo associado. Como

resultado da instrução, é produzido um valor que também possui um tipo específico.

Para exemplificar o que foi dito até aqui sobre a representação intermediária LLVM,

as figura 5 e figura 6 mostram códigos, sendo que o primeiro escrito na linguagem de

programação C e, a partir dele, gera-se o código na representação LVIS visualizado na figura

posterior.

Page 29: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

29

Figura 5: Código fonte em linguagem C, exemplificando o LLVM IR, adaptado de

(MACHADO, A., 2008).

Figura 6: Código na representação LVIS, exemplificando o LLVM IR, adaptado de

(MACHADO, A., 2008).

3.2.1 Representação Textual, Binária e Em Memória

A representação de código LLVM é designada para ser usada em três diferentes

formatos, os quais são isomórficos, ou seja, equivalentes. São eles:

Como uma representação intermediária em memória, para o compilador trabalhar;

Page 30: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

30

Como uma representação binária comprimida em arquivo, apropriado para

carregamento rápido por um compilador Just-In-Time;

Como uma representação em texto, semelhante à linguagem assembly, legível ao

humano.

Isso, segundo (LATTNER, C.; ADVE, V, 2009), permite que o LLVM provenha uma

poderosa representação intermediária para eficientes transformações e análises do compilador,

enquanto provê um meio natural para depurar e visualizar as transformações.

3.2.2 Sistema de Tipo

O sistema de tipo LLVM é uma das características mais importantes da IR. Segundo

(LATTNER, C.; ADVE, V, 2009), sendo “tipada”, habilita um número de otimizações a

serem realizadas na representação intermediária diretamente, sem ter a necessidade de realizar

análise extra antes da transformação. Esse sistema de tipo inclui tipos primitivos independente

de linguagem fonte como, por exemplo, void, integer, floating point (single e double), entre

outros. LLVM também possui, além dos primitivos, tipos derivados, como pointer, array,

structure, vector, etc.

Conforme (LATTNER, C.; ADVE, V, 2004a), cada registrador na forma SSA e cada

objeto de memória explícito têm um tipo associado, sendo que todas as operações obedecem a

regras de tipos estritas. Essa informação de tipo é usada em associação com o opcode da

instrução para determinar a semântica exata de uma instrução. Isso porque a maioria dos

opcodes em LLVM é sobrecarregado, sendo assim, uma instrução como add, a qual realiza

uma soma, pode operar em operandos de qualquer tipo inteiro ou ponto flutuante.

Para finalizar, um forte sistema de tipo torna fácil a leitura do código gerado e permite

novas análises e transformações que não são viáveis na representação de código three address

code normal.

3.2.3 Alocação Explícita de Memória

Page 31: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

31

O LVIS é único na maneira de tratar áreas de memória. Todos os objetos endereçáveis

(variáveis locais alocadas na pilha, variáveis globais, funções e memória alocada

dinamicamente) são explicitamente alocados. Para isso, duas instruções de alocação de

memória “tipada” são providas, além de uma adicional para liberação da memória

previamente alocada. A primeira delas, malloc, aloca um ou mais elementos de um

determinado tipo na heap, retornando um ponteiro, com o tipo especificado, para a nova área

de memória. Já para liberar esta área alocada por malloc, a instrução free é utilizada. Outra

instrução utilizada para alocação é a alloc. Essa é similar a primeira, porém ela aloca memória

na pilha (stack frame) da função corrente ao invés da heap. Para essa última, não é necessária

qualquer instrução de liberação de memória, pois ela será automaticamente liberada ao sair de

escopo. Nenhum dado residente na pilha poderá ser alocado sem que seja utilizada a instrução

alloc explicitamente, tornando LLVM um framework com alocação explícita de memória.

Segundo (LATTNER, C, 2002a), variáveis globais e funções (chamadas coletivamente

de valores globais) declaram regiões de memória alocadas estaticamente que são acessadas

através do endereço do objeto e não do efetivo objeto. Isso gera um modelo de memória

unificado no qual todas as operações de memória, incluindo instruções de chamada, ocorrem

através de ponteiros “tipados”. Essa representação também simplifica a análise de acesso à

memória, pois não ocorrem acessos a memória implicitamente.

Page 32: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

32

4 LLFLOW

Para o desenvolvimento deste trabalho, foi utilizada como base uma ferramenta de

análise de tempo de execução de alto nível conhecida como llflow. A mesma foi desenvolvida

como trabalho de conclusão de curso por um aluno da Universidade Federal de Santa Catarina

(UFSC) e consiste na validação da infraestrutura LLVM como plataforma para análise do

WCET. Neste capítulo serão descritas algumas características e o funcionamento dessa

ferramenta. Descrições mais aprofundadas podem ser encontradas em (MACHADO, A.,

2008).

4.1 Características

Uma primeira característica da ferramenta llflow é que, como dito, ela realiza apenas a

análise de alto nível, não realizando a análise de baixo nível, a qual leva em conta o

comportamento do hardware alvo. Então, para tornar viável o trabalho, é necessário

considerar que cada instrução executada pelo processador leva uma unidade de tempo para ser

finalizada e, consequentemente, assume-se que o maior tempo de execução (WCET) é obtido

pelo caminho com o maior número de instruções (MACHADO, A., 2008).

Outra peculiaridade dessa ferramenta, talvez a mais importante de todas, é a utilização

do LLVM como sustentação para sua implementação. Como comentado em (MACHADO,

A., 2008), a riqueza de informações da representação intermediária LLVM e das análises já

disponíveis para esta plataforma fornecem ao usuário um conjunto de informações valiosas

sobre o código e, também, parte dos algoritmos necessários para o cálculo do tempo de

execução são simplificados.

Page 33: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

33

4.2 Arquitetura

Para a utilização da ferramenta llflow, deve-se primeiramente entender como ela

funciona e sobre o que ela atua. Então, primeiramente, deve-se ter o código fonte do programa

a ser analisado em alguma linguagem de programação a qual possua um compilador que

transforme o código para o conjunto de instruções LLVM. Podem existir vários arquivos

fontes como também somente um.

Seguindo, é utilizado um front-end, o qual compila o código fonte para a

representação intermediária LLVM. Um exemplo de front-end é o llvm-gcc. Ele é uma versão

do gcc que compila programas C/ObjC em objetos nativos, bitcode LLVM (binário), ou em

linguagem assembly LLVM (texto) (LLVM TEAM, 2009). Tanto durante a compilação, com

o próprio front-end, como após, com a ferramenta de otimização (opt), podem-se realizar

algumas otimizações no código gerado, o qual, posteriormente, deverá ser ligado, com a

ferramenta llvm-link, caso possua vários módulos.

Após todas essas etapas, o código binário na representação LLVM é inserido na

ferramenta llflow para a análise do código e geração de informações relacionadas ao fluxo de

controle. Ainda em (MACHADO, A., 2008), é citada uma ferramenta llvm-wcet, a qual

necessitaria da descrição da plataforma alvo para a realização da análise de baixo nível. Essa

ferramenta dependente do hardware de destino seria a responsável não só pelo cálculo de

tempo, como pela geração de código objeto final, com instruções nativas. Porém essa

ferramenta ainda não foi desenvolvida. Todo esse processo é mostrado na figura 7.

Page 34: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

34

Figura 7: Arquitetura da ferramenta llflow, obtida de (MACHADO, A., 2008).

4.3 Entradas

Para que a ferramenta llflow faça a análise, necessita-se que sejam fornecidos à

mesma dois arquivos essenciais, a saber:

Código binário na representação LLVM;

Arquivo de configuração com algumas informações sobre as funções existentes no

código para análise.

Page 35: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

35

Na subseção seguinte será explicado um pouco mais sobre o arquivo de configuração

utilizado pela ferramenta llflow.

4.3.1 Arquivo de Configuração

Para direcionar a análise, é necessário um arquivo de configuração com algumas

informações úteis, tais como (MACHADO, A., 2008):

Faixas de valores que podem ser retornados por chamadas de funções externas

(funções cujo código não está disponível para análise);

Faixas de valores que podem ser retornados em parâmetros passados por referência às

funções externas;

Funções do programa que devem ser analisadas (pontos de entrada, caso apenas uma

parte do programa seja tarefa de tempo real ou quando o ponto de entrada não for a

função main), com informações sobre as faixas de valores que podem ser aceitos como

parâmetros de entrada.

A figura 8 apresenta um exemplo de um arquivo de configuração, o qual possui duas

funções externas e uma função de tempo real a ser analisada. As funções externas possuem o

formato:

nomeFunção (parâmetro1, parâmetro2, ...) = valorRetorno;

Enquanto que as funções de tempo real são formadas por:

nomeFunção (parâmetro1, parâmetro2, ...);

Onde, para os parâmetros, o valor in significa valores somente de entrada, informando que

seus valores não serão alterados pela função. Os demais valores para os parâmetros e retorno

deverão ser especificados, podendo assumir um simples valor ou um conjunto de valores,

como os demonstrados na figura 8 pelos colchetes ([ ]). Os valores entre [ ] significam os

valores mínimo e máximo que aquele parâmetro ou retorno pode assumir.

Page 36: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

36

Figura 8: Arquivo de configuração da ferramenta llflow, retirado de (MACHADO, A., 2008).

4.4 Saídas

Em posse do arquivo binário LLVM e do arquivo de configuração, a ferramenta tem

condições de gerar os resultados esperados, os quais, segundo (MACHADO, A., 2008), são:

Grafo do fluxo de controle estendido, com indicação dos escopos de análise;

Anotações em cada escopo, indicando:

– Número de execuções de cada loop ou recursão;

– Intervalos válidos para as variáveis envolvidas nas decisões do fluxo de controle;

Caminhos possíveis e impossíveis no programa.

Porém, observando os resultados a partir de uma análise de um programa qualquer,

percebe-se que duas das saídas citadas em (MACHADO, A., 2008) não são apresentadas, as

quais são: intervalos válidos para as variáveis envolvidas nas decisões do fluxo de controle e

caminhos possíveis e impossíveis no programa.

Referente à primeira, algo parecido com o citado é apresentado ao decorrer da análise,

onde o programa mostra, quando apropriado, as divisões sofridas pelas variáveis que estão na

forma de conjunto na execução de uma instrução de comparação. Porém, isto não representa

propriamente um resultado final, e sim valores de checagem ao decorrer da execução da

ferramenta de análise.

Com relação à segunda informação citada, mas não apresentada, a ferramenta exibe

somente o caminho do pior caso, sendo que não há nada indicando os caminhos impossíveis

Page 37: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

37

para o programa. Então, neste trabalho, foi desenvolvida esta nova funcionalidade, a qual é

apresentada no capítulo 7.

Page 38: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

38

5 VALIDANDO A FERRAMENTA LLFLOW

Este trabalho consiste de duas partes, a saber:

Teste e correção da ferramenta de análise do tempo de execução llflow;

Incremento de novas funcionalidades à ferramenta.

Explicando melhor cada parte do trabalho, na primeira será realizado o teste do

aplicativo llflow através de um benchmark próprio para análise do WCET (MÄLARDALEN

WCET RESEARCH GROUP, 2006). Então, serão pegos alguns dos programas desse

benchmark, os quais serão compilados com e/ou sem otimização, dependendo da necessidade

do caso, e serão inseridos na ferramenta llflow para que os resultados sejam exibidos. Caso

erros venham a ocorrer, alterações serão feitas no código fonte do programa llflow para que o

mesmo apresente o resultado esperado. Essa parte será apresentada neste capítulo.

Na segunda etapa, serão incrementadas novas características ao aplicativo llflow, a fim

de que ele se torne uma ferramenta mais robusta e confiável, aumentando a quantidade de

programas que poderão usufruí-lo para o cálculo do WCET. Essa etapa será apresentada neste

e nos próximos capítulos.

Nas próximas seções deste capítulo serão apresentados alguns dos códigos fonte

retirados do benchmark citado, os quais foram utilizados na ferramenta llflow para que as

etapas de verificação e incrementação fossem executadas.

5.1 Busca Binária

O primeiro programa selecionado a partir do benchmark (MÄLARDALEN WCET

RESEARCH GROUP, 2006), apresentado aqui, realiza uma busca binária a partir de um array

com quinze elementos onde cada posição representa uma estrutura (struct) que por fim possui

Page 39: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

39

dois inteiros, a chave e o valor. A função analisada, bynary_search, recebe um parâmetro que

representa o valor de uma chave, a qual será procurada dentro do array, retornando o valor

correspondente ou -1 em caso de não existir. A figura 9 contém o código fonte desse

programa.

Page 40: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

40

Figura 9: Código fonte do programa que realiza a busca binária, adaptado de

(MÄLARDALEN WCET RESEARCH GROUP, 2006).

Por se tratar do primeiro programa apresentado, serão apresentadas as etapas, passo a

passo, para a realização da análise para que não haja dúvidas com relação ao processo de

análise.

De início, deve-se compilar o código fonte da figura 9 para a representação LVIS. A

realização desse processo de compilação é feita utilizando o front-end llvm-gcc. Essa

ferramenta possui a opção de passagem de parâmetros para a escolha de um código gerado

com ou sem otimização, como também a opção de geração de código binário LLVM ou em

linguagem assembly LLVM (texto). A figura 10 demonstra a geração dos arquivos texto e

binário LLVM, primeiro sem (-O0) e, por seguinte, com (-O3) otimização.

Page 41: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

41

Figura 10: Geração dos arquivos texto e binário LLVM.

A figura 11 e a figura 12 apresentam os códigos em linguagem assembly LLVM

gerados a partir da compilação, sendo a primeira sem otimização e a segunda com.

Page 42: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

42

Figura 11: Código na representação LVIS, sem otimização (busca binária).

Page 43: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

43

Figura 12: Código na representação LVIS, com otimização (busca binária).

Page 44: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

44

A figura 13 mostra o arquivo de configuração que será passado como parâmetro

juntamente com o arquivo binário do programa que realiza a busca binária na execução da

ferramenta llflow.

Figura 13: Arquivo de configuração (busca binária).

Finalmente, a figura 14 demonstra a chamada à ferramenta llflow, passando como

parâmetros o programa a ser analisado e o arquivo de configuração.

Figura 14: Chamada à ferramenta llflow.

Na execução do código relativo à busca binária pelo llflow, vários problemas foram

encontrados os quais inviabilizaram a análise do programa. Os defeitos encontrados foram:

Não era possível a criação de uma estrutura dentro de um array;

Page 45: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

45

A instrução getelementptr não estava funcionando corretamente. Essa instrução é

usada para obter o endereço de um elemento de uma estrutura de dados agregada

(LATTNER, C.; ADVE, V, 2009);

Operações de comparação com números negativos estavam com problemas;

A instrução de deslocamento para direita (shift right) apresentava resultado errado;

E por fim, a instrução phi não estava implementada. Essa instrução é usada para

implementar o nodo φ no grafo SSA representando a função (LATTNER, C.; ADVE,

V, 2009).

Então, após a correção desses problemas (instruções getelementptr, de comparação e

shift right) e a inclusão de novas funcionalidades (instrução phi e criação de estrutura dentro

de array), tornou-se possível a análise do programa, o qual gerou os resultados mostrados na

figura 15 e na figura 16, sendo a primeira para o código sem otimização e a segunda com.

Figura 15: Resultado da análise, sem otimização (busca binária).

Page 46: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

46

Figura 16: Resultado da análise, com otimização (busca binária).

Juntamente com os resultados apresentados, a ferramenta llflow informa o maior

caminho percorrido para os valores passados como parâmetros, de acordo com o número de

instruções executadas. Para a figura 15, obteve-se o resultado:

Comprimento do caminho: 103 instruções.

START, entry, bb5, bb, bb2, bb3, bb5, bb, bb2, bb4, bb5, bb, bb1, bb5, bb6, return

Enquanto que para a figura 16, o resultado foi o seguinte:

Comprimento do caminho: 57 instruções.

Page 47: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

47

START, entry, bb5.outer, bb5, bb, bb2, bb3, bb5, bb, bb2, bb4, bb5.outer, bb5, bb,

bb1, bb5, bb6

Para verificar a corretude da ferramenta llflow, serão realizadas duas verificações. A

primeira verifica o valor da variável count apresentado pela ferramenta llflow. Já a segunda,

analisa o número de instruções executadas, informado como “comprimento do caminho” pela

ferramenta, conforme apresentado logo acima.

Para a primeira análise, a seguir é demonstrada a análise do fluxo de controle do

código fonte escrito em C, que representa exatamente o código LVIS sem otimização, para a

comparação com o resultado obtido pela ferramenta:

1º Iteração => Entradas: low = 0, up = 14 -- Saídas: low = 0, up = 6;

2º Iteração => Entradas: low = 0, up = 6 -- Saídas: low = 4, up = 6;

3º Iteração => Entradas: low = 4, up = 6 -- Saídas: low = 4, up = 3;

Retornado valor 250.

Analisando o cálculo realizado e verificando primeiramente o código na representação

LVIS sem otimização, percebe-se claramente que a o bloco básico que realiza a contagem é o

de nome bb5, o qual realiza a instrução while (low <= up). A resposta obtida pela figura 15

nos diz que a análise do bloco básico bb5 executou 4 (quatro) vezes, o que é realmente

correto, pois pela análise feita acima, ocorreram 3 (três) iterações do loop, e de acordo com o

código na representação LVIS, essa instrução de comparação é executada mais uma vez para

por fim sair do loop principal, totalizando o número informado pela análise.

Já para o código com otimização, percebe-se que a legibilidade do mesmo não é tão

grande quanto ao código sem otimização, porém, em compensação, ele possui um tamanho

bem reduzido comparando-os. Então, fazendo o mesmo tipo de análise que foi realizado para

o cálculo sem otimização, percebe-se que os resultados apresentados pela ferramenta são

válidos.

Para a realização da análise do número de instruções executadas, é utilizado o

interpretador fornecido pelo LLVM, chamado lli, o qual executa programas no formato

binário LLVM. Então, para executar os códigos apresentados nas representações LVIS

referentes à busca binária, deve-se adicionar a função main que representa o ponto de entrada

para o código. Logo, foram adicionadas ao código, as seguintes linhas:

define i32 @main() nounwind readonly {

entry:

call i32 @binary_search(i32 9) nounwind

Page 48: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

48

ret i32 1

}

Assim, executando o interpretador LLVM, primeiramente para o código sem

otimização, encontra-se como resultado a seguinte informação:

105 interpreter – Number of dynamic instructions executed

Este número obtido deve ser subtraído de 2 (duas) instruções, referentes à função main

adicionada. Então, conferindo com o valor obtido pela ferramenta, 103, percebemos que

representam o mesmo valor. Provando a corretude da ferramenta llflow para esta caso.

Agora, executando o interpretador para o código com otimização, encontra-se o

seguinte resultado:

45 interpreter – Number of dynamic instructions executed

Diminuindo 2 (duas) instruções, obtêm-se 43 instruções, porém o resultado obtido pela

ferramenta llflow foi 57 instruções. Entretanto, o interpretador lli não considera as instruções

phi em sua contagem, logo, deve-se diminuir essas instruções do resultado obtido pela

ferramenta llflow. Essas instruções estão presentes nos blocos básicos bb5.outer e bb5, sendo

que o primeiro possui 3 (três), enquanto que o segundo possui 2 (duas) instruções phi. De

acordo com o caminho demonstrado como resultado pela ferramenta llflow, o bloco básico

bb5.outer é executado 2 (duas) vezes e o bb5, 4 (quatro) vezes. Então, fazendo os cálculos,

obtêm-se:

3 * 2 + 2 * 4 = 14

Subtraindo as instruções phi do total de 57 instruções, resulta em 43 instruções, exatamente o

resultado dado pelo interpretador LLVM, comprovando que a ferramenta llflow retornou o

valor correto.

5.2 Raiz Quadrada

O segundo programa retirado do benchmark (MÄLARDALEN WCET RESEARCH

GROUP, 2006) realiza a operação raiz quadrada de um número qualquer passado como

parâmetro. A figura 17 mostra o código fonte desse programa.

Page 49: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

49

Page 50: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

50

Figura 17: Código fonte do programa que realiza a raiz quadrada, adaptado de

(MÄLARDALEN WCET RESEARCH GROUP, 2006).

A figura 18 apresenta o código em linguagem assembly LLVM, sem otimização,

gerado a partir da compilação.

Page 51: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

51

Page 52: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

52

Figura 18: Código na representação LVIS, sem otimização (raiz quadrada).

A figura 19 mostra o arquivo de configuração para a ferramenta llflow.

Figura 19: Arquivo de configuração (raiz quadrada).

Ao deparar-se com o código relativo à raiz quadrada, percebe-se que nele encontram-

se muitas operações sobre ponto flutuante. Mas, a ferramenta llflow não trabalha com esse

tipo de valor, somente com valores inteiros. Logo, precisou-se incluir a opção de usar valores

ponto flutuante, tanto no código fonte como no arquivo de configuração. E, necessitou-se,

também, a implementação de instruções de comparação para ponto flutuante.

Page 53: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

53

Com essa nova funcionalidade, foi executada a ferramenta llflow para o código que

executa a raiz quadrada, produzindo o resultado presente na figura 20.

Figura 20: Resultado da análise, sem otimização (raiz quadrada).

Completando o resultado obtido, conforme apresentado pela ferramenta, o maior

caminho encontrado foi:

Comprimento do caminho: 505 instruções.

START, entry, bb1, bb8, bb2, bb3, entry, bb, bb2, return, bb5, bb7, bb8, bb2, bb3,

entry, bb, bb2, return, bb5, bb7, bb8, bb2, bb3, entry, bb, bb2, return, bb5, bb7, bb8,

bb2, bb3, entry, bb, bb2, return, bb5, bb7, bb8, bb2, bb3, entry, bb, bb2, return, bb4,

bb5, bb7, bb8, bb2, bb6, bb7, bb8, bb2, bb6, bb7, bb8, bb2, bb6, bb7, bb8, bb2, bb6,

bb7, bb8, bb2, bb6, bb7, bb8, bb2, bb6, bb7, bb8, bb2, bb6, bb7, bb8, bb2, bb6, bb7,

Page 54: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

54

bb8, bb2, bb6, bb7, bb8, bb2, bb6, bb7, bb8, bb2, bb6, bb7, bb8, bb2, bb6, bb7, bb8,

bb2, bb6, bb7, bb8, bb2, bb6, bb7, bb8, bb9, return

Fazer a análise referente ao valor da variável count, para esse código, não é necessário,

pois o loop que a função possui sempre executará 20 (vinte) vezes. Logo, para a função

my_sqrt, o bloco básico bb8, o qual realiza a comparação i < 20 (na realidade a instrução feita

é i <= 19, porém, como a variável i representa um valor inteiro, as operações realizam a

mesma comparação), é executado pelo número de vezes informado pela análise.

Já para a análise referente ao comprimento do caminho, é executado o código na

representação LVIS pelo interpretador LLVM, o qual resulta a informação:

506 interpreter – Number of dynamic instructions executed

Seguindo a mesma metodologia do exemplo anterior, diminuindo 2 (duas) instruções

referentes à função main, obtêm-se 504 instruções, diferentemente da ferramenta llflow, a

qual alcançou o valor 505. Assim, mostra-se que o valor obtido através da análise representa

um valor para o WCET seguro (safe), pois reflete um valor maior do que o real, e ao mesmo

tempo bem próximo a ele (tight), consistindo em um valor utilizável.

5.3 Fibonacci

O próximo programa selecionado para realização de teste foi o famoso fibonacci. Seu

código foi extraído do benchmark (MÄLARDALEN WCET RESEARCH GROUP, 2006) e o

mesmo calcula o valor do número de fibonacci para a posição passada como parâmetro. Para

maiores informações com relação à sequência de fibonacci, consultar (WIKIPÉDIA. A

ENCICLOPÉDIA LIVRE, 2009c). Esse código possui muitas chamadas recursivas, o que

leva a um grande processamento. A figura 21 apresenta o código fonte desse programa.

Page 55: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

55

Figura 21: Código fonte do programa que executa a sequência de fibonacci, adaptado de

(MÄLARDALEN WCET RESEARCH GROUP, 2006).

A figura 22 apresenta o código em linguagem assembly LLVM, com otimização

máxima (-O3), gerado a partir da compilação.

Page 56: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

56

Figura 22: Código na representação LVIS, com otimização (fibonacci).

A figura 23 mostra o arquivo de configuração para a ferramenta llflow.

Figura 23: Arquivo de configuração (fibonacci).

Esse código, ao ser executado pelo analisador com o arquivo de configuração da

figura 23, apresentava problemas. A ferramenta llflow nunca finalizava a analise devido ao

grande número de caminhos que deveriam ser criados. Então, para viabilizar a análise foram

necessárias algumas correções e implementações, como por exemplo:

A instrução switch que não havia sido implementada;

Page 57: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

57

Havia problemas ao utilizar a variável i passada como parâmetro, onde em certos

momentos ela possuía um valor incorreto;

A instrução phi não implementada, também, teve que ser adicionada para esse código;

A instrução de chamada call apresentava problemas;

E quanto à utilização de um conjunto de valores no arquivo de configuração ([0, 10]),

surgiram vários problemas, principalmente relativo à divisão de contexto, que são

ocasionadas por instruções de comparação e switch.

Com todos os problemas solucionados, pode-se executar o código com a ferramenta de

análise de tempo de execução e obteve-se o resultado apresentado na figura 24.

Figura 24: Resultado da análise, com otimização (fibonacci).

Page 58: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

58

O maior caminho percorrido, com o parâmetro da função assumindo o valor

especificado, é:

Comprimento do caminho: 594 instruções.

START, entry, bb3.i6, entry, bb3.i6, entry, bb3.i6, entry, bb3.i6, entry, fib.exit8,

fib.exit, fib.exit.i, fib.exit8, bb3.i, entry, bb4, entry, bb4, fib.exit, bb3.i.i, entry,

fib.exit8, fib.exit, entry, bb4, fib.exit.i, fib.exit8, bb3.i, entry, bb3.i6, entry, bb4,

fib.exit.i, fib.exit8, fib.exit, entry, fib.exit8, fib.exit, fib.exit, bb3.i.i, entry, bb3.i6,

entry, fib.exit8, fib.exit, fib.exit.i, fib.exit8, bb3.i, entry, bb4, entry, bb4, fib.exit,

entry, bb3.i6, entry, bb4, fib.exit.i, fib.exit8, fib.exit, fib.exit.i, fib.exit8, bb3.i, entry,

bb3.i6, entry, bb3.i6, entry, bb4, fib.exit.i, fib.exit8, fib.exit, bb3.i.i, entry, bb4, entry,

bb4, fib.exit.i, fib.exit8, bb3.i, entry, fib.exit8, fib.exit, entry, bb4, fib.exit, entry,

bb3.i6, entry, fib.exit8, fib.exit, fib.exit.i, fib.exit8, bb3.i, entry, bb4, entry, bb4,

fib.exit, fib.exit, bb3.i.i, entry, bb3.i6, entry, bb3.i6, entry, fib.exit8, fib.exit, fib.exit.i,

fib.exit8, bb3.i, entry, bb4, entry, bb4, fib.exit, bb3.i.i, entry, fib.exit8, fib.exit, entry,

bb4, fib.exit.i, fib.exit8, bb3.i, entry, bb3.i6, entry, bb4, fib.exit.i, fib.exit8, fib.exit,

entry, fib.exit8, fib.exit, fib.exit, entry, bb3.i6, entry, bb3.i6, entry, bb4, fib.exit.i,

fib.exit8, fib.exit, bb3.i.i, entry, bb4, entry, bb4, fib.exit.i, fib.exit8, bb3.i, entry,

fib.exit8, fib.exit, entry, bb4, fib.exit, fib.exit.i, fib.exit8, bb3.i, entry, bb3.i6, entry,

bb3.i6, entry, bb3.i6, entry, bb4, fib.exit.i, fib.exit8, fib.exit, bb3.i.i, entry, bb4, entry,

bb4, fib.exit.i, fib.exit8, bb3.i, entry, fib.exit8, fib.exit, entry, bb4, fib.exit, bb3.i.i,

entry, bb3.i6, entry, bb4, fib.exit.i, fib.exit8, fib.exit, entry, fib.exit8, fib.exit, fib.exit.i,

fib.exit8, bb3.i, entry, bb3.i6, entry, fib.exit8, fib.exit, fib.exit.i, fib.exit8, bb3.i, entry,

bb4, entry, bb4, fib.exit, entry, bb3.i6, entry, bb4, fib.exit.i, fib.exit8, fib.exit, fib.exit,

entry, bb3.i6, entry, bb3.i6, entry, fib.exit8, fib.exit, fib.exit.i, fib.exit8, bb3.i, entry,

bb4, entry, bb4, fib.exit, bb3.i.i, entry, fib.exit8, fib.exit, entry, bb4, fib.exit.i, fib.exit8,

bb3.i, entry, bb3.i6, entry, bb4, fib.exit.i, fib.exit8, fib.exit, entry, fib.exit8, fib.exit,

fib.exit, fib.exit

De acordo com o arquivo de configuração, o valor passado como parâmetro para a

função fib a ser analisada representa um conjunto, sendo que o valor mínimo é 0 (zero) e o

máximo 10 (dez). Para a função fibonacci, nota-se claramente, que quanto maior o valor do

parâmetro, maior será o caminho percorrido pela função para que o resultado seja obtido.

Logo, a resposta final cedida pela ferramenta llflow deverá ser o mesmo valor obtido para o

parâmetro de número 10 (dez), pois já conhecemos a priori que este caso representa o maior

caminho possível. É importante salientar que a ferramenta llflow realiza o cálculo do pior

Page 59: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

59

caminho utilizando a definição de conjuntos, então, realizar-se-á o cálculo para todos os

valores entre 0 (zero) e 10 (dez).

A seguir, será realizado o cálculo do código da função presente na figura 22 de acordo

com as informações já mencionadas acima. A contagem dá-se pelo número de vezes que o

bloco básico entry é chamado:

i = 0 => Número Iterações = 1;

i = 1 => Número Iterações = 1;

i = 2 => Número Iterações = 1;

i = 3 => Número Iterações = (1 + Nº Iterações i = 1) = 2;

i = 4 => Número Iterações = (1 + Nº Iterações i = 2 + Nº Iterações i = 1 + Nº

Iterações i = 0) = 4;

i = 5 => Número Iterações = (1 + Nº Iterações i = 3 + Nº Iterações i = 1 + Nº

Iterações i = 0 + Nº Iterações i = 2 + Nº Iterações i = 1) = 7;

i = 6 => Número Iterações = (1 + Nº Iterações i = 4 + Nº Iterações i = 2 + Nº

Iterações i = 1 + Nº Iterações i = 3 + Nº Iterações i = 2) = 10;

i = 7 => Número Iterações = (1 + Nº Iterações i = 5 + Nº Iterações i = 3 + Nº

Iterações i = 2 + Nº Iterações i = 4 + Nº Iterações i = 3) = 17;

i = 8 => Número Iterações = (1 + Nº Iterações i = 6 + Nº Iterações i = 4 + Nº

Iterações i = 3 + Nº Iterações i = 5 + Nº Iterações i = 4) = 28;

i = 9 => Número Iterações = (1 + Nº Iterações i = 7 + Nº Iterações i = 5 + Nº

Iterações i = 4 + Nº Iterações i = 6 + Nº Iterações i = 5) = 46;

i = 10 => Número Iterações = (1 + Nº Iterações i = 8 + Nº Iterações i = 6 + Nº

Iterações i = 5 + Nº Iterações i = 7 + Nº Iterações i = 6) = 73;

Como informado pela análise apresentada, quando o valor da variável i assume 10

(dez), o bloco básico de nome entry é chamado 73 vezes ao total. Porém, para a ferramenta

llflow, como a função já se inicia na própria função entry, a primeira vez que ela é executada

não é adicionada ao valor total. Logo, para todos os valores obtidos no cálculo acima, o

analisador assumirá que o valor da variável count será sempre uma unidade menor em relação

ao demonstrado. Então, prova-se que a ferramenta apresentou o valor count esperado para a

função fibonacci com os valores de entrada sendo um intervalo.

Agora, para realizar a análise do comprimento do caminho, executa-se o interpretador

lli para todos os valores possíveis como parâmetro, de 0 (zero) a 10 (dez), e obtêm-se:

i = 0 => 4 interpreter - Number of dynamic instructions executed

Page 60: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

60

i = 1 => 4 interpreter - Number of dynamic instructions executed

i = 2 => 6 interpreter - Number of dynamic instructions executed

i = 3 => 13 interpreter - Number of dynamic instructions executed

i = 4 => 25 interpreter - Number of dynamic instructions executed

i = 5 => 44 interpreter - Number of dynamic instructions executed

i = 6 => 67 interpreter - Number of dynamic instructions executed

i = 7 => 114 interpreter - Number of dynamic instructions executed

i = 8 => 187 interpreter - Number of dynamic instructions executed

i = 9 => 307 interpreter - Number of dynamic instructions executed

i = 10 => 492 interpreter - Number of dynamic instructions executed

Assim, o maior valor, representado pelo parâmetro 10 (dez), é 492. Subtraindo as duas

instruções da função main, é obtido o valor 490. Mas a ferramenta llflow retornou o número

594 como resultado, porém, nesta contagem estão incluídas as instruções phi, as quais não

estão presentes no cálculo realizado pelo interpretador.

Realizando o cálculo da quantidade de instruções phi para o caminho apresentado,

encontra-se 104 instruções, as quais devem ser diminuídas do total de 594, resultando em 490,

exatamente o valor encontrado pelo interpretador, provando assim, mais uma vez, a eficiência

da ferramenta llflow.

Page 61: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

61

6 INCREMENTANDO A FERRAMENTA LLFLOW - PARTE 1

Além das adições e modificações comentadas no capítulo 5, incrementou-se ainda

uma nova funcionalidade que merece destaque e, por isso, será apresentada neste capítulo.

Essa característica foi desenvolvida devido ao problema que a ferramenta llflow possuía em

lidar com análise de loops aninhados.

No trabalho de (MACHADO, A., 2008), cita-se este problema como uma limitação do

protótipo e são sugeridos dois possíveis contornos para tal, que seriam:

Ao invés de anotar os escopos com números de iterações, anotar cada bloco básico, e

enumerar todos os caminhos no programa, mostrando como resultado o maior

caminho, e não o grafo de fluxo de controle;

Realizar anotações simbólicas, ou seja, deixar o número de iterações do loop interno

como uma função da iteração do loop externo. Esta abordagem tornaria mais

complexa a análise final para identificação do caminho no grafo que representa o pior

caso para o cálculo de tempo.

Porém, neste trabalho, realizou-se outra solução que se assemelha com a segunda

opção. O cálculo do número de iterações ainda continua a cargo dos escopos, porém a

contagem para os loops internos é realizada para cada iteração do loop externo

separadamente, resultando em um valor final para cada iteração.

A seguir, serão demonstrados os efeitos dessa nova solução para alguns exemplos

retirados do benchmark (MÄLARDALEN WCET RESEARCH GROUP, 2006), seguindo a

mesma metodologia apresentada no capítulo 5.

Page 62: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

62

6.1 Janne Complex

Este código, chamado Janne Complex, apresenta dois loops, onde o número máximo

de iterações do loop interno depende da iteração atual do loop externo. A figura 25 apresenta

o código fonte desse programa.

Figura 25: Código fonte do programa com loops aninhados dependentes, adaptado de

(MÄLARDALEN WCET RESEARCH GROUP, 2006).

Page 63: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

63

A figura 26 apresenta o código em linguagem assembly LLVM, sem otimização,

gerado a partir da compilação do código fonte.

Page 64: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

64

Figura 26: Código na representação LVIS, sem otimização (janne complex).

A figura 27 mostra o arquivo de configuração para a ferramenta llflow.

Figura 27: Arquivo de configuração (janne complex).

Por fim, para este exemplo, serão apresentadas duas imagens, a figura 28 possui o

grafo gerado pela ferramenta llflow original, desenvolvida por (MACHADO, A., 2008),

enquanto que a figura 29 representa o fluxo de controle modificado neste trabalho.

Page 65: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

65

Figura 28: Resultado da análise da ferramenta llflow original, sem otimização (janne

complex).

Page 66: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

66

Figura 29: Resultado da análise da ferramenta llflow modificada, sem otimização (janne

complex).

Page 67: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

67

Como demonstrado pelas figuras, o código original conduzia a erros no cálculo, pois,

informava o número total de iterações do loop interno. Porém, com as modificações

executadas, o loop interno tornou-se dependente do externo, sendo que para cada iteração do

loop externo, haverá uma contagem separada para o interno.

Abaixo, será apresentada a contagem dos loops realizada para comparação com o

valor obtido:

1º Iteração Loop Externo => 0 Iteração Loop Interno

2º Iteração Loop Externo => 9 Iterações Loop Interno

3º Iteração Loop Externo => 1 Iteração Loop Interno

4º Iteração Loop Externo => 0 Iteração Loop Interno

5º Iteração Loop Externo => 1 Iteração Loop Interno

6º Iteração Loop Externo => 0 Iteração Loop Interno

7º Iteração Loop Externo => 1 Iteração Loop Interno

8º Iteração Loop Externo => 0 Iteração Loop Interno

9º Iteração Loop Externo => 0 Iteração Loop Interno

Logo, nota-se pelo cálculo que o loop externo é executado 9 (nove) vezes, enquanto

que para cada uma dessas execuções, o número de iterações do loop interno é variável. Vale

ressaltar que, para o código LVIS, os blocos básicos que executam as contagens sempre são

executados uma vez a mais, pois, antes de finalizar os loops, deve-se fazer a comparação uma

última vez.

Assim, o resultado apresentado na figura 29 está correto, pois informa que o loop

externo executa 10 (dez) vezes, enquanto que o loop interno resulta em 1, 10, 2, 1, 2, 1, 2, 1,

1, 0, informando que na 1º iteração do loop externo, o loop interno é executado 1 (uma) única

vez, na 2º iteração do externo, 10 (dez) vezes são executadas o interno e assim

sucessivamente.

Page 68: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

68

6.2 Insertion Sort

Este programa, selecionado para análise de loops aninhados, realiza uma operação em

arrays chamada insertion sort, o qual, segundo (WIKIPÉDIA. A ENCICLOPÉDIA LIVRE,

2009b), é um simples algoritmo de ordenação, eficiente quando aplicado a um pequeno

número de elementos. Em termos gerais, ele percorre um vetor de elementos da esquerda para

a direita e à medida que avança vai deixando os elementos mais à esquerda ordenados. Seu

código também foi extraído do benchmark (MÄLARDALEN WCET RESEARCH GROUP,

2006). A figura 30 apresenta o código fonte do programa comentado.

Page 69: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

69

Figura 30: Código fonte do programa insertion sort, adaptado de (MÄLARDALEN WCET

RESEARCH GROUP, 2006).

A figura 31 apresenta o código em linguagem assembly LLVM, com otimização

máxima, gerado a partir da compilação.

Page 70: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

70

Figura 31: Código na representação LVIS, com otimização (insertion sort).

Já a figura 32 apresenta o arquivo de configuração para a ferramenta llflow.

Figura 32: Arquivo de configuração (insertion sort).

Esse código, ao ser executado pelo analisador com o arquivo de configuração

apresentado, gerou o resultado da figura 33.

Conforme demonstrado no resultado, nota-se que para ordenar um array com 11

(onze) números inteiros, totalmente invertido com relação ao objetivo final, são necessárias 9

(nove) iterações do loop externo e para cada loop interno o número de iterações é

incrementado em 1 (um), começando com uma única iteração.

Page 71: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

71

Figura 33: Resultado da análise, com otimização (insertion sort).

Page 72: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

72

6.3 Busca em Array Multidimensional

O último exemplo apresentado neste trabalho representa um programa, também

selecionado do benchmark (MÄLARDALEN WCET RESEARCH GROUP, 2006), o qual

realiza uma busca em um array de 4 (quatro) dimensões. O código fonte está demonstrado na

figura 34.

Page 73: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

73

Page 74: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

74

Figura 34: Código fonte do programa de busca em array multidimensional, adaptado de

(MÄLARDALEN WCET RESEARCH GROUP, 2006).

Page 75: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

75

A figura 35 apresenta o código em linguagem assembly LLVM, sem otimização,

gerado a partir da compilação, sendo que o código relativo à inicialização dos arrays (keys e

answer) foram suprimidos devido aos seus imensos tamanhos.

Page 76: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

76

Figura 35: Código na representação LVIS, sem otimização (busca array multidimensional).

Page 77: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

77

A figura 36 mostra o arquivo de configuração para a ferramenta llflow.

Figura 36: Arquivo de configuração (busca array multidimensional).

Com esse código, percebeu-se que o analisador não aceitava array com múltiplas

dimensões. Logo, foi necessário modificar a ferramenta llflow para que fosse possível a

criação de arrays com qualquer número de dimensões.

Arrumado isso, executando o programa no analisador de tempo de execução obteve-se

um resultado mais complexo do que os demais exemplos, o qual é apresentado na figura 37.

Page 78: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

78

Figura 37: Resultado da análise, sem otimização (busca array multidimensional).

Page 79: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

79

7 INCREMENTANDO A FERRAMENTA LLFLOW - PARTE 2

Neste capítulo será apresentada uma nova funcionalidade que talvez seja a mais

importante realizada neste trabalho. Ela refere-se à determinação dos blocos básicos

inalcançáveis, ou também chamados de mortos, significando que nunca serão atingidos por

qualquer caminho que a função possa seguir para os valores passados como parâmetros.

Isto é importante, pois se pode eliminar este pedaço de código sem causar mudanças

no resultado final obtido, conseguindo assim, uma redução no tamanho do código, item

importante quando há limitações de memória.

Outro fator que cabe ressaltar é que, com relação ao cálculo do WCET, este pedaço de

código considerado inalcançável pode ser eliminado da contagem do resultado final,

resultando assim em um valor mais próximo do real, tornando um cálculo mais preciso. Essa

característica será demonstrada através dos códigos exemplos apresentados a seguir.

7.1 Teste de Código Morto

Este código foi desenvolvido com a finalidade, única e exclusiva, de realizar testes na

ferramenta llflow com relação à nova funcionalidade introduzida, a qual representa a

descoberta de código inalcançável. A figura 38 apresenta o código fonte desse programa,

enquanto que a figura 39 demonstra o código em linguagem assembly LLVM, com

otimização, gerado a partir da compilação do código fonte. Por fim, a figura 40 representa o

arquivo de configuração para a ferramenta llflow.

Page 80: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

80

Figura 38: Código fonte do programa de teste de código morto.

Figura 39: Código na representação LVIS, com otimização (teste de código morto).

Page 81: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

81

Figura 40: Arquivo de configuração (teste de código morto).

Analisando o código, percebe-se que a primeira instrução da função, if ((x > y) && (y

> z)), compara as variáveis passadas como parâmetro. Logo abaixo, há outra instrução de

comparação, if (x > z), a qual se trata de uma informação sempre verdadeira, pois o if mais

externo já possui esta comparação subentendida. Então, sendo uma tautologia, o código

referente ao else desta instrução no código em C e situado no bloco básico bb4 na LLVM IR

nunca é executado. Portando, conforme implementado, ao final da análise, além do resultado

apresentado na figura 41, são apresentadas as seguintes informações:

Comprimento do caminho: 8 instruções.

START, entry, bb, bb3

BBs inviáveis: bb4

Vale ressaltar que vários outros testes foram executados, sendo que os arquivos de

configurações informavam valores diferentes para as parâmetros presentes no código. Todos

os testes mostraram-se corretos, informando no resultado final o bloco básico bb4 como sendo

inalcançável.

Figura 41: Resultado da análise, com otimização (teste de código morto).

Page 82: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

82

7.2 Cover

Este código, retirado de (MÄLARDALEN WCET RESEARCH GROUP, 2006), tem

como objetivo testar vários caminhos, sendo que existe um loop for com várias instruções

switch case. A figura 42 apresenta o código fonte, a figura 43 o código em linguagem

assembly LLVM e, finalmente, a figura 44 apresenta o arquivo de configuração utilizado na

análise.

Figura 42: Código fonte do programa cover, adaptado de (MÄLARDALEN WCET

RESEARCH GROUP, 2006).

Page 83: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

83

Page 84: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

84

Figura 43: Código na representação LVIS, sem otimização (cover).

Page 85: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

85

Figura 44: Arquivo de configuração (cover).

De acordo com o código fonte e os valores passados como parâmetro, percebe-se que

a instrução for irá iterar por no máximo 3 (três) vezes, executando assim as instruções

presentes nas três primeiras cláusulas case do switch. Desse modo, as instruções case

restantes nunca serão executados, sendo assim, são consideradas blocos básicos mortos.

Para este código, obteve-se como resultado o grafo apresentado na figura 45 e,

também, as seguintes informações:

Comprimento do caminho: 64 instruções.

START, entry, bb13, bb, bb1, bb12, bb13, bb, bb2, bb12, bb13, bb, bb3, bb12, bb13,

bb14, return

BBs inviáveis: bb4, bb5, bb6, bb7, bb8, bb9, bb10, bb11

Lembrando que este resultado foi obtido de acordo com o arquivo de configuração da

figura 44. Outro teste foi realizado, sendo que o arquivo de configuração informava o

intervalo [0, 11] à ferramenta. Para este, encontraram-se os seguintes resultados, informando

que todos os blocos básicos são alcançáveis:

Comprimento do caminho: 176 instruções.

START, entry, bb13, bb, bb1, bb12, bb13, bb, bb2, bb12, bb13, bb, bb3, bb12, bb13,

bb, bb4, bb12, bb13, bb, bb5, bb12, bb13, bb, bb6, bb12, bb13, bb, bb7, bb12, bb13,

bb, bb8, bb12, bb13, bb, bb9, bb12, bb13, bb, bb10, bb12, bb13, bb, bb11, bb12, bb13,

bb14, return

BBs inviáveis:

Page 86: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

86

Figura 45: Resultado da análise, sem otimização (cover).

Page 87: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

87

8 CONCLUSÃO

8.1 Considerações Finais

Os sistemas embarcados, mais especificamente os de tempo real crítico, representam

uma parcela significativa dos aplicativos atuais e, devido à sua natureza, merecem uma

atenção especial quanto a sua validação. A ferramenta llflow foi desenvolvida com a

finalidade de, para esses sistemas, obter seu fluxo de controle para que posteriormente, na

realização da análise de baixo nível, possa-se garantir o tempo de execução do pior caso

(WCET), tornando o sistema confiável e útil.

Essa ferramenta, llflow, utiliza-se do LLVM, uma infraestrutura de compilação muito

ativa no momento, a qual provê uma série de facilidades e benefícios, tornando seu uso uma

decisão extremamente oportuna. O analisador proposto e implementado por (MACHADO, A.,

2008) possui uma estrutura excelente em relação ao seu código, seu uso é extremamente

simples e seus resultados fáceis de entender. Porém, não possuía a robustez necessária para a

utilização em diversos programas, limitando-se a somente alguns mais simples.

Com as modificações feitas nesse trabalho na ferramenta llflow, sua implementação

aderiu novas funcionalidades e apresentou uma melhoria na questão da correção relativa às

características já existentes. Isso a tornou mais robusta e completa, deixando-a menos

susceptível a erros.

Logo, este trabalho foi de grande valia, pois serviu para a realização de um estudo

aprofundado sobre a área em que o situa, a saber, análise de tempo de execução e a

infraestrutura LLVM, como também para o conhecimento da ferramenta llflow,

proporcionando a análise de seu código fonte e contribuindo com sua implementação.

Page 88: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

88

8.2 Trabalhos Futuros

A partir desse trabalho, nota-se claramente, observando a figura 7, que visando à

completude do analisador do tempo de execução para o pior caso, necessita-se da

implementação de uma ferramenta, cujo nome proposto por (MACHADO, A., 2008) em seu

trabalho foi llvm-wcet. Essa nova ferramenta fará a análise de baixo nível, tendo como

entrada o resultado da análise do fluxo de controle executada pela ferramenta llflow, como

também a descrição do hardware alvo, sendo que esta deve simular o comportamento da

plataforma fielmente.

Outras propostas, também citadas por (MACHADO, A., 2008), seriam usar mais

eficientemente as informações de análise providas pela infraestrutura LLVM e, também,

implementar as anotações de resultados da análise por bloco básico ao invés de anotações por

escopo, como são feitas atualmente.

Ainda como um possível trabalho futuro, envolvendo prioritariamente a área de

programação gráfica, poderia ser realizada a implementação de um ambiente gráfico para a

ferramenta llflow, que atualmente só funciona em modo texto. Juntamente a isso, seria

interessante também, que ao final da análise fosse gerado automaticamente um arquivo

semelhante aos apresentados nesse trabalho para demonstrar os resultados da análise (por

exemplo a figura 15), pois o resultado se dá na forma de um arquivo texto com os nodos e

arestas, tendo o usuário que desenvolver o arquivo em formato gráfico em um editor de

preferência se desejado.

Page 89: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

89

REFERÊNCIAS

CANTU, E. M. (2008). Geração de Código para a Máquina Virtual LLVM a partir de

Programas Escritos na Linguagem de Programação JAVA (Tradutor Java – LLVM).

Trabalho de conclusão do curso de Sistemas de Informação, Universidade Federal de Santa

Catarina, Florianópolis, Santa Catarina, Brasil.

ENGBLOM, J. (2002). Worst-case Execution Time Analysis.

ENGBLOM, J. ERMEDAHLT, A. (2000). Modeling Complex Flows for Worst-case

Execution Time Analysis.

FAUSTER, J.; KIRNER, R.; PUSCHNER, P. (2003). Intelligent Editor for Writing Worst-

Case-Execution-Time-Oriented Programs.

FERDINAND, C.; HECKMANN, R. (2008). Worst-Case Execution Time - A Tool

Provider’s Perspective.

GÓES, J. A. (2000). Estimação de Tempo de Execução de Programas a partir de Arquivos-

Objeto. Trabalho de conclusão no programa de pós-graduação do curso de Engenharia

Elétrica e Informática Industrial, Centro Federal de Educação Tecnológica do Paraná, Paraná,

Curitiba, Brasil.

GUSTAFSSON, J.; ERMEDAHL, A.; LISPER, B. (2006). Algorithms for Infeasible Path

Calculation.

LATTNER, C. (2006). Introduction to the LLVM Compiler Infrastructure.

LATTNER, C. (2002a). LLVM: An Infrastructure for Multi-Stage Optimization.

LATTNER, C. (2002b). Macroscopic Data Structure Analysis and Optimization.

LATTNER, C.; ADVE, V. (2009). LLVM Language Reference Manual. Acesso em 28 de

Fevereiro de 2009, disponível em http://www.llvm.org/docs/LangRef.html

LATTNER, C.; ADVE, V. (2004a). LLVM: A Compilation Framework for Lifelong Program

Analysis & Transformation.

LATTNER, C.; ADVE, V. (2004b). The LLVM Compiler Framework and Infrastructure.

LLVM TEAM. (2009). LLVM C Front-End. Acesso em 4 de Agosto de 2009, disponível em

http://llvm.org/cmds/llvmgcc.html

LLVM TEAM. (2002). The LLVM Compiler Infrastructure Project. Acesso em 28 de

Fevereiro de 2009, disponível em http://www.llvm.org

Page 90: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

90

MACHADO, A. (2008). Análise de Tempo de Execução em Alto Nível para Sistemas de

Tempo Real Utilizando-se o Framework LLVM. Trabalho de conclusão do curso de Sistemas

de Informação, Universidade Federal de Santa Catarina, Florianópolis, Santa Catarina, Brasil.

MÄLARDALEN WCET RESEARCH GROUP. (2006). WCET Project. Acesso em 29 de

Abril de 2009, disponível em http://www.mrtc.mdh.se/projects/wcet/benchmarks.html

OLIVEIRA, B. C.; SANTOS, M. M.; DESCHAMPS, F. (2006). Cálculo do Tempo de

Execução de Códigos no Pior Caso (WCET) em Aplicações de Tempo Real: Um Estudo de

Caso.

WIKIPÉDIA. A ENCICLOPÉDIA LIVRE. (2009a). Embedded System. Acesso em 28 de

Fevereiro de 2009, disponível em http://en.wikipedia.org/wiki/Embedded_system

WIKIPÉDIA. A ENCICLOPÉDIA LIVRE. (2009b). Insertion Sort. Acesso em 16 de Agosto

de 2009, disponível em http://pt.wikipedia.org/wiki/Insertion_sort

WIKIPÉDIA. A ENCICLOPÉDIA LIVRE. (2009c). Número de Fibonacci. Acesso em 7 de

Agosto de 2009, disponível em http://pt.wikipedia.org/wiki/Número_de_Fibonacci

WIKIPÉDIA. A ENCICLOPÉDIA LIVRE. (2009d). Static Single Assignment Form. Acesso

em 4 de Abril de 2009, disponível em

http://en.wikipedia.org/wiki/Static_single_assignment_form

WILHELM, R. et al. (2008). The Worst-Case Execution Time Problem - Overview of

Methods and Survey of Tools.

Page 91: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

91

APÊNDICE A - ARTIGO

Page 92: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

92

Análise de Tempo de Execução Utilizando LLVM

LEONARDO MACCARI RUFINO

UFSC - Universidade Federal de Santa Catarina

INE - Departamento de Informática e Estatística

Florianópolis (SC), Brasil

[email protected]

Resumo: Os sistemas embarcados dominam o mercado de diversas áreas comerciais hoje em dia.

Muitos desses sistemas podem também ser classificados como sistemas de tempo real, os quais

podem se dividir em críticos e brandos. Os sistemas de tempo real crítico necessitam de uma

validação quanto a sua correta implementação. Essa validação pode ser feita através de técnicas

estáticas ou dinâmicas. Nesse trabalho, será explicado como realizar essa análise, dando ênfase à

análise estática, explicando cada uma de suas etapas que são: análise do fluxo de controle, análise

de baixo nível e por fim o cálculo. Também será comentado sobre a infraestrutura de compilação

LLVM, a qual está muito ativa no momento, descrevendo seus objetivos e sua representação

intermediária, a qual representa um dos fatores chaves que o diferencia dos demais sistemas. Esse

framework foi utilizado como base para a implementação da ferramenta llflow, a qual será

apresentada nesse trabalho. Para finalizar, realizar-se-ão testes, correções e inclusões de novas

funcionalidades na ferramenta llflow.

Palavras-Chave: Análise de Tempo de Execução, WCET, Tempo de Execução do Pior Caso,

Sistemas de Tempo Real, LLVM, llflow.

1 Introdução

Com o passar dos tempos, o computador

tornou-se uma importante ferramenta na vida da

população de qualquer ponto do planeta. Cada dia

mais os computadores invadem as casas das

pessoas, muitas vezes sem que sejam percebidos. É

o caso dos sistemas embarcados (ou também

chamados de sistemas embutidos, embedded

systems) que são, segundo (ENGBLOM, J, 2002),

“um computador que não se parece com um

computador”, ou também, melhor explicado em

(MACHADO, A., 2008), “construídos com

propósitos específicos e pré-definidos e, em função

disto, possuírem características que favorecem o

uso para este propósito e dificultam o uso para

outros fins”. Sistemas embarcados estão localizados

em dispositivos que tenham algum processamento

feito por um microprocessador encapsulado.

Exemplos estão por toda parte, como em

brinquedos, eletrodomésticos, aparelhos celulares,

automóveis, aviões e mais uma diversidade de

produtos.

Sistemas embarcados muitas vezes podem

também ser classificados como estando no grupo

dos sistemas de tempo real (real-time systems).

Estes representam os sistemas computacionais que

possuem uma característica marcante em comum, a

qual diz que para que o sistema seja considerado

correto, além de apresentar as funcionalidades

esperadas, também deve responder dentro de um

tempo estabelecido aos estímulos que recebem, ou

seja, deve-se garantir que as ações sejam

executadas dentro de um intervalo de tempo pré-

determinado.

Os sistemas de tempo real podem ser

divididos em duas classes, distinguíveis por seus

requisitos temporais e de confiabilidade, que são os

sistemas de tempo real brando (soft real-time

systems) e sistemas de tempo real crítico (hard real-

time systems). O primeiro caracteriza-se por

possuir um prazo de resposta mais flexível em

relação ao outro, ou seja, em sistemas brandos, o

descumprimento ocasional de um prazo de tempo

pode ser aceito sem grandes problemas. Estes

possuem requisitos de segurança não-críticos. Já os

sistemas de tempo real crítico, são caracterizados

por possuírem um prazo de resposta estrito, ou seja,

seu comportamento deve ser previsível até mesmo

quando se está executando em sobrecarga. Estes

possuem requisitos de segurança críticos, sendo que

se o sistema chegar a falhar ou mesmo não

responder dentro do tempo pré-estabelecido,

problemas mais graves poderão ocorrer. Um

exemplo de um sistema de tempo real brando seria

o de um aparelho de som e, do outro lado, um

exemplo de um sistema crítico seria o controle do

sistema de “air bag” de carros.

Com a finalidade de assegurar a correta

execução de sistemas de tempo real com relação ao

tempo de execução, algumas técnicas chamadas de

análise de tempo de execução são utilizadas, as

Page 93: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

2

quais serão apresentadas a seguir. E, também, para

a realização deste trabalho, será utilizada uma

ferramenta em ascensão no momento chamada

LLVM (Low Level Virtual Machine), apresentada

adiante.

2 Análise do Tempo de Execução do Pior Caso

O propósito da análise do WCET é prover

uma informação a priori sobre o pior tempo de

execução possível de um pedaço de código antes de

usá-lo em um sistema, conforme mencionado em

(ENGBLOM, J. ERMEDAHLT, A, 2000). Sendo

assim, o domínio tradicional do cálculo do WCET

está situado nos sistemas de tempo real crítico, para

que haja uma garantia satisfatória do

comportamento do sistema em todas as

circunstâncias.

O cálculo do tempo de execução do pior

caso pode ser realizado através da análise dinâmica

e estática. Essas serão comentadas a seguir,

enfatizando a técnica estática.

2.1 Análise Dinâmica

Para obter resultados através da técnica de

análise dinâmica, são realizadas medições da tarefa,

ou de suas partes, através da execução em um dado

hardware ou um simulador, para algum conjunto de

entradas. Como citado em (ENGBLOM, J, 2002) e

(WILHELM, R. et al, 2008), existe uma

metodologia para este tipo de análise, que seria:

Determinar a entrada e o estado inicial do

pior caso;

Executar e medir;

Adicionar uma margem segura.

Porém alguns problemas são notados,

como a dificuldade ou impossibilidade de encontrar

o valor da entrada e do estado inicial que resultarão

no tempo de execução de pior caso. Além disso,

outro problema seria o fato desta técnica nunca

superestimar o valor do WCET, geralmente

subestimando-o.

Desta forma, esta técnica pode ser útil para

aplicações que não requerem garantias do tempo de

pior caso encontrado, sendo assim, preferivelmente

utilizada pra sistemas de tempo real não crítico, ou

seja, brando. Como dito em (WILHELM, R. et al,

2008), a análise dinâmica pode dar ao

desenvolvedor uma percepção sobre o tempo de

execução nos casos comuns e também a

porcentagem das ocorrências do pior caso.

Garantias de que o limite obtido é um valor seguro

podem ser conseguidas somente quando a

arquitetura utilizada é simples. Além disso, esta

técnica também pode ser utilizada para prover

validação para abordagens de análise estática.

2.2 Análise Estática

Neste tipo de análise não é necessária a

presença do código de execução para o hardware

real ou um simulador. Como dito em (WILHELM,

R. et al, 2008), é preferivelmente pego o código por

si só, talvez junto de algumas anotações, utilizando-

o para analisar o conjunto de caminhos do fluxo de

controle possível para a tarefa, posteriormente

combinando o fluxo de controle com alguns

modelos abstratos da arquitetura do hardware, e

assim, obtendo o limite superior para essa junção.

Com a análise estática, existe uma garantia de que

os resultados obtidos para o WCET sejam seguros

(safe), além da tentativa de ser o mais próximo

possível do valor real (tight), sendo desta forma,

valores utilizáveis.

O cálculo da estimativa de tempo do pior

caso utilizando esta técnica é obtido através de três

passos como ilustrado na figura 2: análise do fluxo

de controle, análise de baixo nível e cálculo.

Figura 46: Estrutura da análise estática do WCET,

adaptado de (ENGBLOM, J, 2002).

2.2.1 Análise do Fluxo de Controle

Nesta primeira fase da análise estática é

determinado o comportamento dinâmico do

programa, ou seja, tem como propósito coletar

informações sobre os caminhos de execuções

possíveis. Para isso, são necessárias algumas

informações como o número de iterações de loops,

profundidade das recursões, dependências de dados

de entrada, caminhos inviáveis (infeasible paths),

etc. Essas informações podem ser fornecidas por

anotações manuais ou pela análise do fluxo

automática.

Esta etapa da análise pode ser dividida em

três partes. Iniciando pela extração de informações

do fluxo, a qual deriva informações sobre o

comportamento do programa, seguindo pela

representação do fluxo do programa, que armazena

as informações obtidas, e finalizando com a

preparação para o cálculo que busca como utilizar a

informação obtida nos passos anteriores para o

cálculo do WCET.

Page 94: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

3

2.2.2 Análise de Baixo Nível

Esta fase da análise estática visa

determinar o tempo de execução para partes do

programa considerando os efeitos do hardware

alvo.

Esta fase da análise é baseada no modelo

abstrato do processador, o subsistema de memória,

os barramentos e os periféricos, que são

conservativos com respeito ao comportamento de

tempo do hardware concreto, significando que o

modelo nunca prediz um tempo de execução menor

do que aquele que pode ser observado no

processador real. Porém, a obtenção deste modelo

de processador abstrato, que simule o original

fielmente, é uma tarefa muitas vezes complexa

dependendo da classe do processador usado.

Processadores mais complexos são mais difíceis de

modelar e analisar devido às caches, pipelines e até

mesmo pela quantidade de bits da arquitetura.

A análise de baixo nível possui dois

assuntos principais que são a análise da cache e a

análise do pipeline.

2.2.3 Cálculo

O objetivo desta fase é encontrar um valor

que represente uma estimativa para o WCET.

Há algumas abordagens que são utilizadas

para a realização desta fase de cálculo. As três mais

comentadas na literatura, sendo assim as principais,

segundo (WILHELM, R. et al, 2008), são chamadas

de:

Baseadas em estrutura (structure-based);

Baseadas em caminhos (path-based);

Técnica de enumeração de caminhos

implícitos (IPET).

Maiores detalhes podem ser encontrados

em (RUFINO, L. M., 2009).

3 LLVM

O LLVM, Low Level Virtual Machine, é

uma infraestrutura de compilação, o qual, segundo

(LATTNER, C, 2006), provê componentes

modulares e reusáveis para construção de

compiladores, assim, reduzindo o tempo e custo

para construir um compilador particular. Esta

infraestrutura possui uma representação

intermediária (LLVM IR) bem definida para

programas, além de muitas bibliotecas

(componentes) com interfaces limpas e ferramentas

construídas pelas próprias bibliotecas. LLVM provê

componentes independentes de linguagem e

máquina alvo, permitindo que códigos de diferentes

linguagens possam ser ligados e otimizados juntos.

Conforme comentado em (LATTNER, C.;

ADVE, V, 2004a), o objetivo do framework LLVM

é permitir sofisticadas transformações em tempo de

compilação, ligação, instalação, execução e durante

o tempo inativo, operando na representação LLVM

de um programa em todos os estágios. Porém, para

ser posto em prática, o mesmo deve ser transparente

com relação ao desenvolvedor de aplicações e

usuários finais. Também, deve ser eficiente o

suficiente para ser usado com aplicações do mundo

real.

LLVM é baseado na representação SSA

que, conforme (LATTNER, C.; ADVE, V, 2009),

provê segurança de tipo, operações de baixo nível,

flexibilidade e a capacidade de representar todas as

linguagens de alto nível limpamente. A forma SSA

é uma representação intermediária na qual cada

variável é atribuída exatamente uma vez. Para

maiores detalhes conferir (WIKIPÉDIA. A

ENCICLOPÉDIA LIVRE, 2009d).

3.1 LLVM IR

Low Level Virtual Machine Intermediate

Representation representa um conjunto virtual de

instruções (LVIS) utilizado pelo LLVM. Essa

representação de código é um dos fatores chaves

que diferencia LLVM de outros sistemas. Segundo

(LATTNER, C.; ADVE, V, 2004a), a representação

é designada para prover informação de alto nível

sobre programas, o que é necessário para suportar

sofisticadas análises e transformações, enquanto

sendo de baixo nível o suficiente para representar

programas arbitrários e para permitir extensiva

otimização nos compiladores estáticos.

Esse conjunto de instruções captura as

operações de processadores comuns, mas evita

restrições específicas de máquina, tal como

registradores físicos, pipelines e convenções de

chamadas de baixo nível. Conforme (LATTNER,

C, 2006), (LATTNER, C.; ADVE, V, 2009),

(LATTNER, C.; ADVE, V, 2004a) e (LATTNER,

C.; ADVE, V, 2004b), a representação

intermediária possui algumas características

marcantes como:

Objetiva ser leve, de baixo nível e ao

mesmo tempo expressiva;

Deve ser independente de linguagem alvo;

Valores escalares são sempre

representados na forma SSA, nunca em

memória;

IR é inteiramente “tipada” e seus tipos são

rigorosamente checados para consistência;

Possui acessos à struct /array explícitos;

IR é facilmente extensível com funções

intrínsecas;

Provê um mecanismo para implementar

tratamento de exceções;

Hospeda uma larga variedade de

otimizações e análises;

As instruções são, na sua maioria,

formadas por três endereços de código

Page 95: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

4

(three address code), um destino e dois

fontes, como em processadores RISC.

Por usufruir da forma SSA, possui um

conjunto de registradores virtual infinito

com informação de tipo.

Para finalizar, programas transferem

valores entre registradores e memória

unicamente via instruções load e store,

sendo que, até mesmo essas operações,

possuem ponteiros com referência a tipos.

4 LLFLOW

A ferramenta de análise de tempo de

execução de alto nível conhecida como llflow foi

desenvolvida como trabalho de conclusão de curso

por um aluno da Universidade Federal de Santa

Catarina (UFSC) e consiste na validação da

infraestrutura LLVM como plataforma para análise

do WCET. Neste capítulo serão descritas algumas

características e o funcionamento dessa ferramenta.

Descrições mais aprofundadas podem ser

encontradas em (MACHADO, A., 2008).

4.1 Características

Uma primeira característica da ferramenta

llflow é que, como dito, ela realiza apenas a análise

de alto nível, não realizando a análise de baixo

nível, a qual leva em conta o comportamento do

hardware alvo. Então, para tornar viável o trabalho,

é necessário considerar que cada instrução

executada pelo processador leva uma unidade de

tempo para ser finalizada e, consequentemente,

assume-se que o maior tempo de execução (WCET)

é obtido pelo caminho com o maior número de

instruções (MACHADO, A., 2008).

Outra peculiaridade dessa ferramenta,

talvez a mais importante de todas, é a utilização do

LLVM como sustentação para sua implementação.

Como comentado em (MACHADO, A., 2008), a

riqueza de informações da representação

intermediária LLVM e das análises já disponíveis

para esta plataforma fornecem ao usuário um

conjunto de informações valiosas sobre o código e,

também, parte dos algoritmos necessários para o

cálculo do tempo de execução são simplificados.

4.2 Funcionamento

Para a utilização da ferramenta llflow,

deve-se primeiramente entender como ela funciona

e sobre o que ela atua. Então, primeiramente, deve-

se ter o código fonte do programa a ser analisado

em alguma linguagem de programação a qual

possua um compilador que transforme o código

para o conjunto de instruções LLVM. Podem existir

vários arquivos fontes como também somente um.

Seguindo, é utilizado um front-end, o qual

compila o código fonte para a representação

intermediária LLVM. Um exemplo de front-end é o

llvm-gcc, o qual é uma versão do gcc que compila

programas C/ObjC em objetos nativos, bitcode

LLVM (binário), ou em linguagem assembly

LLVM (texto) (LLVM TEAM, 2009). Tanto

durante a compilação, com o próprio front-end,

como após, com a ferramenta de otimização (opt),

podem-se realizar algumas otimizações no código

gerado, o qual, posteriormente, deverá ser ligado,

com a ferramenta llvm-link, caso haja vários

módulos.

Após todas essas etapas, o código binário

na representação LLVM é inserido na ferramenta

llflow para a análise do código e geração de

informações relacionadas ao fluxo de controle.

Ainda em (MACHADO, A., 2008), é citada uma

ferramenta llvm-wcet, a qual necessitaria da

descrição da plataforma alvo para a realização da

análise de baixo nível. Essa ferramenta dependente

do hardware de destino seria a responsável não só

pelo cálculo de tempo, como pela geração do

código objeto final, com instruções nativas. Porém

essa ferramenta ainda não foi desenvolvida. Todo

esse processo é mostrado na figura 7.

Figura 47: Arquitetura da ferramenta llflow, obtido de

(MACHADO, A., 2008).

4.3 Entradas

Para que a ferramenta llflow faça a análise,

necessita-se que sejam fornecidos dois arquivos

essenciais, a saber:

Código binário na representação LLVM;

Arquivo de configuração com algumas

informações sobre as funções existentes no

código para análise.

Essas informações úteis presentes no

arquivo de configuração são (MACHADO, A.,

2008):

Faixas de valores que podem ser

retornados por chamadas de funções

externas (funções cujo código não está

disponível para análise);

Page 96: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

5

Faixas de valores que podem ser

retornados em parâmetros passados por

referência às funções externas;

Funções do programa que devem ser

analisadas (pontos de entrada, caso apenas

uma parte do programa for tarefa de tempo

real ou quando o ponto de entrada não for

a função main), com informações sobre as

faixas de valores que podem ser aceitos

como parâmetros de entrada.

4.4 Saídas

Em posse do arquivo binário LLVM e do

arquivo de configuração, a ferramenta tem

condições de gerar os resultados esperados, os

quais são:

Grafo do fluxo de controle estendido, com

indicação dos escopos de análise;

Anotações em cada escopo, indicando o

número de execuções de cada loop ou

recursão;

5 Validando a Ferramenta LLFLOW

Este trabalho consiste de duas partes, a

saber:

Teste e correção da ferramenta de análise

do tempo de execução llflow;

Incremento de novas funcionalidades à

ferramenta.

Explicando melhor cada parte do trabalho,

na primeira será realizado o teste do aplicativo

llflow através de um benchmark próprio para

análise do WCET (MÄLARDALEN WCET

RESEARCH GROUP, 2006). Então, será pego um

programa desse benchmark, o qual será compilado

e inserido na ferramenta llflow para que os

resultados sejam exibidos. Caso erros venham a

ocorrer, alterações serão feitas no código fonte do

programa llflow para que o mesmo apresente o

resultado esperado. Essa parte será apresentada

neste capítulo.

Na segunda etapa, serão incrementadas

novas características ao aplicativo llflow, a fim de

que ele se torne uma ferramenta mais robusta e

confiável, aumentando a quantidade de programas

que poderão usufruí-lo para o cálculo do WCET.

Essa etapa será apresentada neste e nos próximos

capítulos.

Na próxima seção deste capítulo será

apresentado o código fonte retirado do benchmark

citado, o qual foi utilizado na ferramenta llflow

para que as etapas de verificação e incrementação

fossem executadas.

5.1 Fibonacci

Este programa, selecionado para

realização de teste, é o famoso fibonacci. Seu

código foi extraído do benchmark

(MÄLARDALEN WCET RESEARCH GROUP,

2006) e o mesmo calcula o valor do número de

fibonacci para a posição passada como parâmetro.

Para maiores informações com relação à sequência

de fibonacci, consultar (WIKIPÉDIA. A

ENCICLOPÉDIA LIVRE, 2009c). Este código

possui muitas chamadas recursivas, o que leva a um

grande processamento. A figura 48 apresenta o

código fonte desse programa, a figura 22 apresenta o

código em linguagem assembly LLVM, com

otimização máxima (-O3), gerado a partir da

compilação e, por fim, a figura 23 mostra o arquivo

de configuração que será passado como parâmetro

juntamente com o arquivo binário do programa

fibonacci na execução da ferramenta llflow.

Figura 48: Código fonte do programa que executa a

sequência de fibonacci, adaptado de (MÄLARDALEN

WCET RESEARCH GROUP, 2006).

Page 97: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

6

Figura 49: Código na representação LVIS, com

otimização (fibonacci).

Figura 50: Arquivo de configuração (fibonacci).

Esse código, ao ser executado pelo

analisador, com o arquivo de configuração

apresentado, resultava em problemas. Então, para

viabilizar a análise foi necessário algumas

correções e implementações, como por exemplo:

A instrução switch que não havia sido

implementada;

A instrução phi não implementada,

também, teve que ser adicionada;

A instrução de chamada call apresentava

problemas;

Havia problemas ao utilizar a variável i

passada como parâmetro, onde em certos

momentos ela possuía um valor incorreto;

Quanto à utilização de um conjunto de

valores no arquivo de configuração ([0,

10]), surgiram vários problemas,

principalmente relativo à divisão de

contexto, que são ocasionadas por

instruções de comparação.

Com todos os problemas solucionados,

pode-se executar o código com a ferramenta de

análise de tempo de execução e obteve-se o

resultado apresentado na figura 24.

Figura 51: Resultado da análise, com otimização

(fibonacci).

O maior caminho percorrido, com o

parâmetro da função assumindo o valor

especificado, é:

Page 98: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

7

Comprimento do caminho: 594 instruções.

START, entry, bb3.i6, entry, bb3.i6, entry,

bb3.i6, entry, bb3.i6, entry, fib.exit8,

fib.exit, fib.exit.i, fib.exit8, bb3.i, entry,

bb4, entry, bb4, fib.exit, bb3.i.i, entry,

fib.exit8, fib.exit, entry, bb4, fib.exit.i,

fib.exit8, bb3.i, entry, bb3.i6, entry, bb4,

fib.exit.i, fib.exit8, fib.exit, entry, fib.exit8,

fib.exit, fib.exit, bb3.i.i, entry, bb3.i6,

entry, fib.exit8, fib.exit, fib.exit.i, fib.exit8,

bb3.i, entry, bb4, entry, bb4, fib.exit,

entry, bb3.i6, entry, bb4, fib.exit.i,

fib.exit8, fib.exit, fib.exit.i, fib.exit8, bb3.i,

entry, bb3.i6, entry, bb3.i6, entry, bb4,

fib.exit.i, fib.exit8, fib.exit, bb3.i.i, entry,

bb4, entry, bb4, fib.exit.i, fib.exit8, bb3.i,

entry, fib.exit8, fib.exit, entry, bb4,

fib.exit, entry, bb3.i6, entry, fib.exit8,

fib.exit, fib.exit.i, fib.exit8, bb3.i, entry,

bb4, entry, bb4, fib.exit, fib.exit, bb3.i.i,

entry, bb3.i6, entry, bb3.i6, entry,

fib.exit8, fib.exit, fib.exit.i, fib.exit8, bb3.i,

entry, bb4, entry, bb4, fib.exit, bb3.i.i,

entry, fib.exit8, fib.exit, entry, bb4,

fib.exit.i, fib.exit8, bb3.i, entry, bb3.i6,

entry, bb4, fib.exit.i, fib.exit8, fib.exit,

entry, fib.exit8, fib.exit, fib.exit, entry,

bb3.i6, entry, bb3.i6, entry, bb4, fib.exit.i,

fib.exit8, fib.exit, bb3.i.i, entry, bb4, entry,

bb4, fib.exit.i, fib.exit8, bb3.i, entry,

fib.exit8, fib.exit, entry, bb4, fib.exit,

fib.exit.i, fib.exit8, bb3.i, entry, bb3.i6,

entry, bb3.i6, entry, bb3.i6, entry, bb4,

fib.exit.i, fib.exit8, fib.exit, bb3.i.i, entry,

bb4, entry, bb4, fib.exit.i, fib.exit8, bb3.i,

entry, fib.exit8, fib.exit, entry, bb4,

fib.exit, bb3.i.i, entry, bb3.i6, entry, bb4,

fib.exit.i, fib.exit8, fib.exit, entry, fib.exit8,

fib.exit, fib.exit.i, fib.exit8, bb3.i, entry,

bb3.i6, entry, fib.exit8, fib.exit, fib.exit.i,

fib.exit8, bb3.i, entry, bb4, entry, bb4,

fib.exit, entry, bb3.i6, entry, bb4, fib.exit.i,

fib.exit8, fib.exit, fib.exit, entry, bb3.i6,

entry, bb3.i6, entry, fib.exit8, fib.exit,

fib.exit.i, fib.exit8, bb3.i, entry, bb4, entry,

bb4, fib.exit, bb3.i.i, entry, fib.exit8,

fib.exit, entry, bb4, fib.exit.i, fib.exit8,

bb3.i, entry, bb3.i6, entry, bb4, fib.exit.i,

fib.exit8, fib.exit, entry, fib.exit8, fib.exit,

fib.exit, fib.exit

De acordo com o arquivo de configuração,

o valor passado como parâmetro para a função fib a

ser analisada representa um conjunto, sendo que o

valor mínimo é 0 (zero) e o máximo 10 (dez). Para

a função fibonacci, nota-se claramente, que quanto

maior o valor do parâmetro, maior será o caminho

percorrido pela função para que o resultado seja

obtido. Logo, a resposta final cedida pela

ferramenta llflow deverá ser o mesmo valor obtido

para o parâmetro de número 10 (dez), pois já

conhecemos a priori que este caso representa o

maior caminho possível. É importante salientar que

a ferramenta llflow realiza o cálculo do pior

caminho utilizando a definição de conjuntos, então,

realizar-se-á o cálculo para todos os valores entre 0

(zero) e 10 (dez).

A seguir, será apresentado o cálculo do

número de chamadas recursiva do código presente

na figura 22 de acordo com as informações já

mencionadas acima. A contagem dá-se pelo número

de vezes que o bloco básico entry é chamado:

i = 0 => 1;

i = 1 => 1;

i = 2 => 1;

i = 3 => 2;

i = 4 => 4;

i = 5 => 7;

i = 6 => 10;

i = 7 => 17;

i = 8 => 28;

i = 9 => 46;

i = 10 => 73;

Como informado pela análise apresentada,

quando o valor da variável i assume 10 (dez), o

bloco básico de nome entry é chamado 73 vezes ao

total. Porém, para a ferramenta llflow, como a

função já se inicia na própria função entry, a

primeira vez que ela é executada não é adicionada

ao valor total. Logo, para todos os valores obtidos

no cálculo acima, o analisador assumirá que o valor

da variável count será sempre uma unidade menor

em relação ao demonstrado. Então, prova-se que a

ferramenta apresentou o valor count esperado para

a função fibonacci com os valores de entrada sendo

um conjunto.

Para a realização da análise do número de

instruções executadas, é utilizado o interpretador

fornecido pelo LLVM, chamado lli, o qual executa

programas no formato binário LLVM. Então, para

executar os códigos apresentados nas

representações LVIS referentes à busca binária,

deve-se adicionar a função main que representa o

ponto de entrada para o código. Logo, foram

adicionadas ao código, as seguintes linhas:

define i32 @main() nounwind readonly {

entry:

call i32 @fib(i32 10) nounwind

ret i32 1

}

Assim, executando o interpretador LLVM

para todos os valores possíveis como parâmetro, de

0 (zero) a 10 (dez), obtêm-se para o número de

instruções executadas:

i = 0 => 4

i = 1 => 4

i = 2 => 6

i = 3 => 13

Page 99: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

8

i = 4 => 25

i = 5 => 44

i = 6 => 67

i = 7 => 114

i = 8 => 187

i = 9 => 307

i = 10 => 492

Então, o maior valor, representado pelo

parâmetro 10 (dez), é 492. Subtraindo as duas

instruções da função main, é obtido o valor 490.

Mas a ferramenta llflow retornou o número 594

como resultado, porém, nesta contagem estão

incluídas as instruções phi, as quais não estão

presentes no cálculo realizado pelo interpretador.

Realizando o cálculo da quantidade de

instruções phi para o caminho apresentado,

encontra-se 104 instruções, as quais devem ser

diminuídas do total de 594, resultando em 490,

exatamente o valor encontrado pelo interpretador,

provando assim, a eficiência da ferramenta llflow.

6 Incrementando a Ferramenta LLFLOW - Parte 1

Além das adições e modificações já

comentadas, incrementou-se ainda uma nova

funcionalidade que merece destaque e, por isso,

será apresentada neste capítulo. Essa característica

foi desenvolvida devido ao problema que a

ferramenta llflow possuía em lidar com análise de

loops aninhados.

No trabalho de (MACHADO, A., 2008),

cita-se este problema como uma limitação do

protótipo e sugere dois possíveis contornos para tal,

que seriam:

Ao invés de anotar os escopos com

números de iterações, anotar cada bloco

básico, e enumerar todos os caminhos no

programa, mostrando como resultado o

maior caminho, e não o grafo de fluxo de

controle;

Realizar anotações simbólicas, ou seja,

deixar o número de iterações do loop

interno como uma função da iteração do

loop externo. Esta abordagem tornaria

mais complexa a análise final para

identificação do caminho no grafo que

representa o pior caso para o cálculo de

tempo.

Porém, neste trabalho, realizou-se outra

solução que se assemelha com a segunda opção. O

cálculo do número de iterações ainda continua a

cargo dos escopos, porém a contagem para os loops

internos são realizadas para cada iteração do loop

externo separadamente, resultando em um valor

final para cada iteração.

A seguir, serão demonstrados os efeitos

dessa nova solução para alguns exemplos retirados

do benchmark (MÄLARDALEN WCET

RESEARCH GROUP, 2006), seguindo a mesma

metodologia apresentada no capítulo anterior.

6.1 Insertion Sort

Este programa, selecionado para análise de

loops aninhados, realiza uma operação em arrays

chamada insertion sort, o qual, segundo

(WIKIPÉDIA. A ENCICLOPÉDIA LIVRE,

2009b), é um simples algoritmo de ordenação,

eficiente quando aplicado a um pequeno número de

elementos. Em termos gerais, ele percorre um vetor

de elementos da esquerda para a direita e à medida

que avança vai deixando os elementos mais à

esquerda ordenados. Seu código foi extraído do

benchmark (MÄLARDALEN WCET RESEARCH

GROUP, 2006). A figura 30 apresenta o código

fonte do programa comentado, a figura 31

apresenta o código em linguagem assembly LLVM,

com otimização máxima, gerado a partir da

compilação e, por fim, a figura 32 apresenta o

arquivo de configuração para a ferramenta llflow.

Figura 52: Código fonte do programa insertion sort,

adaptado de (MÄLARDALEN WCET RESEARCH

GROUP, 2006).

Page 100: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

9

Figura 53: Código na representação LVIS, com

otimização (insertion sort).

Figura 54: Arquivo de configuração (insertion sort).

Esse código, ao ser executado pelo

analisador com o arquivo de configuração

apresentado, gerou o resultado da figura 33.

Conforme demonstrado no resultado, nota-

se que para ordenar um array com 11 (onze)

números inteiros, totalmente invertido com relação

ao objetivo final, são necessárias 9 (nove) iterações

do loop externo e para cada loop interno o número

de iterações é incrementado em 1 (um), começando

com uma única iteração.

A versão original da ferramenta llflow

conduzia a erros na análise, pois retornava como

resultado um único valor para a variável count do

loop interno, bloco básico bb1, a saber: 45

(quarenta e cinco). Com as modificações feitas na

ferramenta, os valores referem-se a cada iteração do

loop externo individualmente, sendo mais claro o

cálculo da análise.

Figura 55: Resultado da análise, com otimização

(insertion sort).

7 Incrementando a Ferramenta LLFLOW - Parte 2

Neste capítulo será apresentada uma nova

funcionalidade que talvez seja a mais importante

realizada neste trabalho. Ela refere-se à

determinação dos blocos básicos inalcançáveis, ou

também chamados de mortos, significando que

nunca serão atingidos por qualquer caminho que a

Page 101: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

10

função possa seguir para os valores passados como

parâmetros.

Isto é importante, pois se pode eliminar

este pedaço de código sem causar mudanças no

resultado final obtido, conseguindo assim, uma

redução no tamanho do código, item importante

quando há limitações de memória.

Outro fator que cabe ressaltar é que, com

relação ao cálculo do WCET, este pedaço de código

considerado inalcançável pode ser eliminado da

contagem do resultado final, resultando assim em

um valor mais próximo do real, tornando um

cálculo mais preciso. Essa característica será

demonstrada através do código exemplo

apresentado a seguir.

7.1 Cover

Este código, retirado de (MÄLARDALEN

WCET RESEARCH GROUP, 2006), tem como

objetivo testar vários caminhos, sendo que existe

um loop for com várias instruções switch case. A

figura 56 apresenta o código fonte, a figura 57 o

código em linguagem assembly LLVM e,

finalmente, a figura 58 apresenta o arquivo de

configuração utilizado na análise.

Figura 56: Código fonte do programa cover, adaptado de

(MÄLARDALEN WCET RESEARCH GROUP, 2006).

Page 102: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

11

Figura 57: Código na representação LVIS, sem

otimização (cover).

Figura 58: Arquivo de configuração (cover).

De acordo com o código fonte e os valores

passados como parâmetro, percebe-se que a

instrução for irá iterar por no máximo 3 (três)

vezes, executando assim as instruções presentes nas

três primeiras cláusulas case do switch. Desse

modo, as instruções case restantes nunca serão

executados, sendo assim, são consideradas blocos

básicos mortos.

Para este código, obteve-se como resultado

o grafo apresentado na figura 59 e, também, as

seguintes informações:

Comprimento do caminho: 64 instruções.

START, entry, bb13, bb, bb1, bb12, bb13,

bb, bb2, bb12, bb13, bb, bb3, bb12, bb13,

bb14, return

BBs inviáveis: bb4, bb5, bb6, bb7, bb8,

bb9, bb10, bb11

Figura 59: Resultado da análise, sem otimização (cover).

8 Conclusão

Os sistemas embarcados, mais

especificamente os de tempo real crítico,

Page 103: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

12

representam uma parcela significativa dos

aplicativos atuais e, devido à sua natureza,

merecem uma atenção especial quanto a sua

validação. A ferramenta llflow foi desenvolvida

com a finalidade de, para esses sistemas, obter seu

fluxo de controle para que posteriormente, na

realização da análise de baixo nível, possa-se

garantir o tempo de execução do pior caso

(WCET), tornando o sistema confiável e útil.

Essa ferramenta, llflow, utiliza-se do

LLVM, uma infraestrutura de compilação muito

ativa no momento, o qual provê uma série de

facilidades e benefícios, tornando seu uso uma

decisão extremamente oportuna. O analisador

proposto e implementado por (MACHADO, A.,

2008) possui uma estrutura excelente em relação ao

seu código, seu uso é extremamente simples e seus

resultados fáceis de entender. Porém, não possuía a

robustez necessária para a utilização em diversos

programas, limitando-se a somente alguns mais

simples.

Com as modificações feitas nesse trabalho

na ferramenta llflow, sua implementação aderiu

novas funcionalidades e apresentou uma melhoria

na questão da correção relativa às características já

existentes. Isso a tornou mais robusta e completa,

deixando-a menos susceptível a erros.

9 Referências

CANTU, E. M. (2008). Geração de Código

para a Máquina Virtual LLVM a partir de

Programas Escritos na Linguagem de

Programação JAVA (Tradutor Java – LLVM).

Trabalho de conclusão do curso de Sistemas de

Informação, Universidade Federal de Santa

Catarina, Florianópolis, Santa Catarina, Brasil.

ENGBLOM, J. (2002). Worst-case Execution

Time Analysis.

ENGBLOM, J. ERMEDAHLT, A. (2000).

Modeling Complex Flows for Worst-case

Execution Time Analysis.

FAUSTER, J.; KIRNER, R.; PUSCHNER, P.

(2003). Intelligent Editor for Writing Worst-

Case-Execution-Time-Oriented Programs.

FERDINAND, C.; HECKMANN, R. (2008).

Worst-Case Execution Time - A Tool

Provider’s Perspective.

GÓES, J. A. (2000). Estimação de Tempo de

Execução de Programas a partir de Arquivos-

Objeto. Trabalho de conclusão no programa de

pós-graduação do curso de Engenharia Elétrica

e Informática Industrial, Centro Federal de

Educação Tecnológica do Paraná, Paraná,

Curitiba, Brasil.

GUSTAFSSON, J.; ERMEDAHL, A.; LISPER,

B. (2006). Algorithms for Infeasible Path

Calculation.

LATTNER, C. (2006). Introduction to the

LLVM Compiler Infrastructure.

LATTNER, C. (2002a). LLVM: An

Infrastructure for Multi-Stage Optimization.

LATTNER, C. (2002b). Macroscopic Data

Structure Analysis and Optimization.

LATTNER, C.; ADVE, V. (2009). LLVM

Language Reference Manual. Acesso em 28 de

Fevereiro de 2009, disponível em

http://www.llvm.org/docs/LangRef.html

LATTNER, C.; ADVE, V. (2004a). LLVM: A

Compilation Framework for Lifelong Program

Analysis & Transformation.

LATTNER, C.; ADVE, V. (2004b). The LLVM

Compiler Framework and Infrastructure.

LLVM TEAM. (2009). LLVM C Front-End.

Acesso em 4 de Agosto de 2009, disponível em

http://llvm.org/cmds/llvmgcc.html

LLVM TEAM. (2002). The LLVM Compiler

Infrastructure Project. Acesso em 28 de

Fevereiro de 2009, disponível em

http://www.llvm.org

MACHADO, A. (2008). Análise de Tempo de

Execução em Alto Nível para Sistemas de

Tempo Real Utilizando-se o Framework LLVM.

Trabalho de conclusão do curso de Sistemas de

Informação, Universidade Federal de Santa

Catarina, Florianópolis, Santa Catarina, Brasil.

MÄLARDALEN WCET RESEARCH GROUP.

(2006). WCET Project. Acesso em 29 de Abril

de 2009, disponível em

http://www.mrtc.mdh.se/projects/wcet/benchma

rks.html

OLIVEIRA, B. C.; SANTOS, M. M.;

DESCHAMPS, F. (2006). Cálculo do Tempo de

Execução de Códigos no Pior Caso (WCET) em

Aplicações de Tempo Real: Um Estudo de

Caso.

RUFINO, L. M. (2009). Análise de Tempo de

Execução Utilizando LLVM. Trabalho de

conclusão do curso de Ciências da Computação,

Universidade Federal de Santa Catarina,

Florianópolis, Santa Catarina, Brasil.

WIKIPÉDIA. A ENCICLOPÉDIA LIVRE.

(2009a). Embedded System. Acesso em 28 de

Fevereiro de 2009, disponível em

http://en.wikipedia.org/wiki/Embedded_system

WIKIPÉDIA. A ENCICLOPÉDIA LIVRE.

(2009b). Insertion Sort. Acesso em 16 de

Page 104: Análise de Tempo de Execução Utilizando LLVM · Figura 17: Código fonte do programa que realiza a raiz quadrada ..... 50 Figura 18: Código na representação ... 25 3.1.3 Tempo

13

Agosto de 2009, disponível em

http://pt.wikipedia.org/wiki/Insertion_sort

WIKIPÉDIA. A ENCICLOPÉDIA LIVRE.

(2009c). Número de Fibonacci. Acesso em 7 de

Agosto de 2009, disponível em

http://pt.wikipedia.org/wiki/Número_de_Fibona

cci

WIKIPÉDIA. A ENCICLOPÉDIA LIVRE.

(2009d). Static Single Assignment Form. Acesso

em 4 de Abril de 2009, disponível em

http://en.wikipedia.org/wiki/Static_single_assig

nment_form

WILHELM, R. et al. (2008). The Worst-Case

Execution Time Problem - Overview of

Methods and Survey of Tools.