Skip to content

Latest commit

 

History

5 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 

Repository files navigation

Лабораторная работа 7. Преобразование и анализ кода с использованием Clang и LLVM

Цель работы: Познакомиться с инструментами Clang и LLVM, научиться собирать AST и IR-промежуточное представление кода на C/C++, а также извлекать базовую информацию о программе (например, список функций). Задачи:

  1. Установить Clang и LLVM;
  2. Скомпилировать простой C-файл с использованием clang и получить его: абстрактное синтаксическое дерево (AST), промежуточное представление LLVM IR;
  3. Использовать opt для применения базовой комплексной оптимизации (например, О2);
  4. Построить граф потока управления (CFG) для оптимизированной программы;
  5. Проанализировать результат, сделать выводы и ответить на контрольные вопросы.

Ответы на контрольные вопросы по LLVM и Clang


1. Что такое Clang, и какова его роль в процессе компиляции программ?

Clang — это компилятор для языков C, C++, Objective-C и других, работающий как фронтенд для LLVM. Он:

  • Выполняет лексический, синтаксический и семантический анализ исходного кода;
  • Создаёт абстрактное синтаксическое дерево (AST);
  • Генерирует промежуточное представление (LLVM IR).

Таким образом, Clang отвечает за первую стадию компиляции: от исходного текста до промежуточного представления.


2. Что представляет собой LLVM и как он используется в современных компиляторах?

LLVM — это модульная инфраструктура для компиляторов. Она включает:

  • Промежуточное представление (LLVM IR);
  • Инструменты для оптимизации кода;
  • Бэкенд для генерации машинного кода под различные архитектуры (x86, ARM и др.).

Современные компиляторы используют LLVM как основу для:

  • Преобразования IR в оптимизированный код;
  • Генерации платформозависимого исполняемого файла.

3. Чем отличается абстрактное синтаксическое дерево (AST) от промежуточного представления LLVM IR?

Характеристика AST LLVM IR
Уровень абстракции Высокий (близко к исходному коду) Низкий (близко к машинному коду)
Назначение Анализ структуры и семантики программы Оптимизация и генерация машинного кода
Представление Структура данных на основе синтаксиса Линейная последовательность инструкций

AST используется для понимания структуры программы.
LLVM IR — для анализа и трансформаций кода.


4. Для чего необходимо промежуточное представление (IR) в процессе компиляции?

LLVM IR нужен для:

  • Универсальности: один IR поддерживает разные языки и архитектуры;
  • Оптимизации: IR позволяет применять мощные трансформации кода;
  • Гибкости: IR служит связующим звеном между фронтендом и бэкендом компилятора;
  • Портируемости: код можно легко адаптировать под разные платформы.

5. Что делает инструкция alloca в LLVM IR, и зачем она используется в функциях?

Инструкция alloca (от allocate) выделяет память в стеке текущей функции. Она используется для создания локальных переменных.

Пример:

%ptr = alloca i32

Эта команда резервирует место для одного целого числа на стеке. Она важна для управления памятью на уровне IR — особенно в функциях, где нужно хранить временные данные или параметры.


6. Зачем нужна оптимизация кода в компиляторе, и какие основные цели она преследует?

Оптимизация кода — это процесс преобразования программы для улучшения её характеристик без изменения поведения. Основные цели:

  • Увеличение производительности (уменьшение времени выполнения);
  • Снижение потребления памяти;
  • Уменьшение размера исполняемого файла;
  • Улучшение энергоэффективности (важно для мобильных устройств).

Компилятор с оптимизациями может автоматически заменять неэффективные конструкции более быстрыми и компактными.


7. Что такое SSA-форма и почему она важна при оптимизации программ?

SSA (Static Single Assignment) — это форма представления кода, в которой каждая переменная присваивается только один раз. Все повторные присваивания создают новые версии переменной.

Преимущества SSA:

  • Упрощает анализ зависимостей;
  • Облегчает оптимизации, такие как удаление мёртвого кода и устранение общего подвыражения;
  • Позволяет точнее отслеживать значения переменных.

LLVM IR использует SSA по умолчанию.


8. Что такое граф потока управления (CFG) и как он помогает анализировать поведение программы?

CFG (Control Flow Graph) — это граф, где:

  • Вершины — базовые блоки (последовательности инструкций без ветвлений);
  • Рёбра — возможные переходы между блоками (например, через br или switch).

CFG помогает:

  • Понять структуру выполнения программы;
  • Определять достижимость кода;
  • Выполнять оптимизации, такие как свёртывание циклов и предсказание ветвлений;
  • Анализировать потоки данных и зависимости.

9. Как устроено представление арифметических операций в LLVM IR (например, умножение, сложение)?

LLVM IR использует отдельные инструкции для каждой арифметической операции. Примеры:

%sum = add i32 %a, %b       ; сложение
%prod = mul i32 %a, %b      ; умножение
%diff = sub i32 %a, %b      ; вычитание
%quot = sdiv i32 %a, %b     ; целочисленное деление

Каждая операция:

  • Явно указывает тип операндов (i32, float и т.д.);

  • Является выражением в SSA-форме (результат сохраняется в %имя).


10. Почему функции в LLVM IR обычно представляют собой отдельные единицы анализа и оптимизации?

Функции — это самодостаточные блоки кода с чёткими границами. Это позволяет:

  • Проводить локальные оптимизации (например, устранение мёртвого кода);

  • Анализировать зависимости и эффекты внутри функции;

  • Выполнять инлайнинг, если функция маленькая;

  • Упрощать повторное использование и линковку модулей.

  • Разделение по функциям делает анализ более масштабируемым и параллелизуемым.

11. Что происходит с функцией в LLVM IR, если она вызывается один раз и очень короткая?

LLVM может применить инлайнинг — заменить вызов функции её телом. Преимущества:

  • Уменьшение накладных расходов на вызов функции;

  • Дополнительные возможности для локальных оптимизаций;

  • Улучшение производительности.

  • Короткие функции, вызываемые один раз, почти всегда инлайнится во время оптимизации.

12. Какие преимущества даёт использование IR и CFG для автоматических оптимизаций по сравнению с анализом исходного текста на C?

Преимущества использования IR и CFG:

  • Унифицированность: один IR подходит для разных языков;

  • Формализованная структура: упрощает статический анализ;

  • Типовая строгость и SSA: позволяют безопасно выполнять глубокие трансформации;

  • Явные зависимости и переходы: CFG помогает выявлять критические пути и упрощает анализ потоков выполнения;

  • Избавление от синтаксической сложности C: не нужно учитывать макросы, препроцессор и особенности синтаксиса.

  • IR и CFG создают среду, более удобную для автоматических и агрессивных оптимизаций, чем исходный текст программы.

Ход работы

1. Установка и подготовка среды

Работа выполнялась в среде Ubuntu 22.04. Установлены следующие инструменты:

  • clang — компилятор языка C/C++.
  • llvm — инструменты анализа и оптимизации кода.
  • opt — инструмент для работы с LLVM IR и применения оптимизаций.
  • Graphviz — инструмент для визуализации кода.

Команда установки:

sudo apt install clang llvm graphviz

image

2. Исходный код

Пример программы main.c:

#include <stdio.h>
int add(int a, int b) {
    return a + b;
}

int main() {
    return add(3, 4);
}

3. Получение AST

Команда для генерации AST:

clang -Xclang -ast-dump -fsyntax-only main.c

image

4. Генерация LLVM IR

Команда для генерации LLVM IR:

clang -S -emit-llvm main.c -o main.ll

image

5. Оптимизация IR

Команда для генерации неоптимизированного IR:

clang -O0 -S -emit-llvm main.c -o main_O0.ll

Особенности IR до оптимизации:

  • Все переменные (a, b) размещены в памяти через alloca.
  • Множество операций load и store.
  • Функция add вызывается как отдельная функция. image

Команда для генерации оптимизированного IR с уровнем -O2:

clang -O2 -S -emit-llvm main.c -o main_O2.ll

Оптимизация -O2 включает более 30 различных оптимизаций, таких как:

  • -inline: встраивание небольших функций (встраивает add в main).
  • -constprop: подстановка констант: если аргументы функции известны (например, add(3, 4)), результат (7) вычисляется на этапе компиляции;
  • -mem2reg: перевод переменных из памяти в регистры (SSA).
  • -instcombine: объединение и упрощение инструкций.
  • -simplifycfg: оптимизация структуры блоков.
  • -reassociate, -gvn, -sroa, -dce и другие.

Особенности IR после оптимизации:

  • Функция square исчезла (встроена через -inline и вычислена через -constprop).
  • Переменные, alloca, store, load удалены (-mem2reg, -dce).. image

Команда для сравнения IR до и после оптимизации:

diff main_O0.ll main_O2.ll

image

Изменения после оптимизации:

  • Переменные типа alloca удалены.
  • Код переведён в SSA-форму.
  • Улучшена читаемость и упрощён поток управления.

6. Граф потока управления программы

Команда для генерации оптимизированного LLVM IR:

clang -O2 -S -emit-llvm main.c -o main.ll

Команда для генерации .dot-файлов CFG:

opt -passes=dot-cfg -disable-output main.ll

image

Вывод:

find . -name "*.dot"
./.main.dot
./.square.dot

Создаются DOT-файлы:

  • .main.dot — для функции main.
  • .square.dot — для функции square (если не была удалена оптимизацией).

Команды для преобразования .dot в .png:

dot -Tpng .main.dot -o cfg_main.png
dot -Tpng .add.dot -o cfg_add.png

Команды для просмотра CFG:

xdg-open cfg_main.png
xdg-open cfg_add.png

cfg_main

cfg_add

Выводы

  • С помощью Clang можно получить полную структуру AST, IR и CFG.
  • LLVM предоставляет гибкие инструменты анализа и оптимизации.
  • Промежуточное представление (IR) удобно для написания компиляторных трансформаций.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages