Этот пост посвящён замечательному инструменту, полезному для каждого, кто интересуется компиляторами или архитектурой компьютеров. Это Compiler Explorer, который я в дальнейшем будут называть CE.
CE — потрясающий инструмент. Если вы с ним не знакомы, то прервите чтение и перейдите на веб-сайт CE, где вы увидите примерно такой экран:
Предупреждение: вы забираетесь в «кроличью нору», на которую можете потратить несколько часов своего времени.

В основе CE лежит очень простая идея. Достаточно ввести исходный код в левую панель, и сайт мгновенно покажет вам на правой панели скомпилированный результат (обычно на языке ассемблера).
То есть теперь для просмотра результата работы компилятора достаточно открыть godbolt.org и скопировать туда блок кода.
Это само по себе удивительно, но у CE есть гораздо больше возможностей. Это инструмент, который должны знать все интересующиеся компиляторами и архитектурами компьютеров. В статье мы сможем лишь поверхностно рассмотреть функции CE. Вам стоит самим перейти на сайт CE и попробовать всё самостоятельно.
Однако нам стоит начать с предыстории CE.
CE хостится на сайте godbolt.org, потому что его первым автором был Мэтт Годболт, начавший этот проект в 2012 году. Цитата из поста Мэтта, посвящённого десятой годовщине CE:
Недавно Мэтт поделился историей CE в замечательном подкасте Microarch Дэна Магнума.
Стоит послушать интервью Мэтта целиком (ссылки на Spotify, YouTube и Apple Podcasts), так как в нём есть много интересных рассуждений об архитектуре, истории компьютеров и CE.
Сам Мэтт также провёл подкаст с Беном Рэди Two’s Complement, в том числе эпизод о будущем CE (ссылки на Spotify, YouTube и Apple Podcasts), в котором гораздо подробнее рассказывается о текущем статусе проекта.
CE — это опенсорсный проект, исходники которого выложены на GitHub, то есть при наличии времени и опыта вы можете хостить CE самостоятельно.
Большинство людей пользуется онлайн-версией CE, которая бесплатна и поддерживается благодаря пожертвованиям и спонсорам.
Для чего можно использовать CE? В этом году Мэтт Годболт выступил с докладом, описывающим различные способы применения CE и его новых функций
Давайте вкратце рассмотрим самые простые способы использования CE.
Мы можем сравнивать ассемблерный код для различных архитектур. В чём отличие кода для x86-64 от кода для ARM64 и RISC-V? Ответ можно узнать всего за несколько секунд. Вот x86-64 (все примеры созданы с помощью GCC 14.1):
А вот ARM64:
RISC-V (64-битный):
Код вполне в духе RISC. А как насчёт чего-то более CISC наподобие VAX?
Или простого 8-битного CPU наподобие 6502:
А если вас интересует Nvidia CUDA, то CE может показать вам код ptx.

CE — отличный инструмент для изучения языка ассемблера. Достаточно навести курсор на команду, после чего откроется всплывающее окно с описанием команды.

При нажатии правой клавишей мыши на команду открывается меню, в котором можно изучить более подробное описание.

Там, в свою очередь, можно найти ссылки на веб-сайт документации по набору команд x86 Феликса Клотье.
Код на Arm ведёт к документации для Arm. Однако меня немного расстроило отсутствие документации для языка ассемблера Vax!
Затем мы можем сравнить результат работы разных компиляторов. Вот сравнение gcc и clang. Логично, что код, сгенерированный для такой простой функции, почти одинаков.

Для компиляторов на основе LLVM CE может показывать LLVM Intermediate Representation (LLVM IR). Подробнее LLVM IR объясняется здесь:
То есть LLVM — это промежуточный этап перед генерацией ассемблера компилятором. LLVM IR показано справа внизу:

Также CE предоставляет доступ ко множеству инструментов, позволяющих лучше понять наш код и способ его исполнения. Например, мы можем добавить вывод анализатора машинного кода LLVM (llvm-mca), который позволяет нам подробнее изучить то, как машинный код работает на реальном CPU.
Окно llvm-mca показано слева внизу.

llvm-mca симулирует исполнение кода на реальной машине и предоставляет информацию об ожидаемой производительности.
В нашем простом примере llvm-mca сообщает нам, что можно ожидать выполнения 100 итераций за 105 тактов симулируемой машины.
Если добавить опцию -timeline , то мы получим диаграмму со временем исполнения команд при движении через CPU.
Цитата из документации llvm-mca:
Вот timeline нашего простого примера:
Если вас интересуют интерпретируемые языки, например, Python, то CE может отображать байт-код, сгенерированный интерпретатором Python или Ruby. Вот пример с Python.

Теперь CE даже предоставляет простую, но завершённую IDE, в том числе функциональность CMake и возможность запуска программ и просмотра результатов.

Ещё одна функция CE заключается в возможности увидеть, насколько умны современные компиляторы. Мэтт Годболт даже выступил со множеством докладов по этой теме. Вот доклад за 2017 год:
А вот более свежий:
А вот статья на ACM: Optimizations in C++ Compilers. Цитата из введения:
CE даже можно использовать как интересный ресурс, чтобы понять, с какими языками работают пользователи. Это можно сделать при помощи Grafana CE Dashboard.

C++ — это язык по умолчанию, так что, возможно, на него не стоит обращать внимание, а вот то, что Rust в четыре раза менее популярен, чем C, — это любопытно.
Есть также страница StatHat, на которой отображается рост использования CE на протяжении нескольких лет.

Сейчас пользователи выполняют примерно 18 миллионов компиляций в год.
Это приблизительно 1,5 миллиона в месяц, 50 тысяч в день, 2 тысяч в час, 30 в минуту или по одной каждые две секунды. Всё это работает на Amazon Web Services и стоит примерно $2500 ежемесячных затрат на хостинг (актуальное значение можно найти в Patreon CE).
Если вам всего этого недостаточно, то скажу, что сейчас Мэтт снимает потрясающую серию видео с YouTube-каналом Computerphile, в которой объясняет основы работы микропроцессоров. Первым в серии идёт видео «Что такое машинный код»:
Список остальных серий:
Надеюсь, это ещё не всё!

В архитектуре x86 команда MOV Тьюринг-полная. В качестве последнего примера приведу наш простой код, скомпилированный при помощи movfuscator, использующего только команды MOV.

CE — потрясающий инструмент. Если вы с ним не знакомы, то прервите чтение и перейдите на веб-сайт CE, где вы увидите примерно такой экран:
Предупреждение: вы забираетесь в «кроличью нору», на которую можете потратить несколько часов своего времени.

В основе CE лежит очень простая идея. Достаточно ввести исходный код в левую панель, и сайт мгновенно покажет вам на правой панели скомпилированный результат (обычно на языке ассемблера).
CE поддерживает 69 языков, более двух тысяч компиляторов и широкий спектр архитектур, включая x86, arm, risc-v, avr, mips, vax, tensa, 68k, PowerPC, SPARC и даже древний 6502.
То есть теперь для просмотра результата работы компилятора достаточно открыть godbolt.org и скопировать туда блок кода.
Это само по себе удивительно, но у CE есть гораздо больше возможностей. Это инструмент, который должны знать все интересующиеся компиляторами и архитектурами компьютеров. В статье мы сможем лишь поверхностно рассмотреть функции CE. Вам стоит самим перейти на сайт CE и попробовать всё самостоятельно.
История Compiler Explorer
Однако нам стоит начать с предыстории CE.
CE хостится на сайте godbolt.org, потому что его первым автором был Мэтт Годболт, начавший этот проект в 2012 году. Цитата из поста Мэтта, посвящённого десятой годовщине CE:
Десять лет назад я получил разрешение на перевод в open source небольшого инструмента под названием GCC Explorer. Я разработал его примерно за неделю своего свободного времени на node.js у своего работодателя DRW. Всё остальное, как говорится, стало достоянием истории.
Спустя несколько лет стало очевидно, что GCC Explorer — это уже нечто большее, чем просто GCC, и 30 апреля 2014 года он стал называться «Compiler Explorer».
Недавно Мэтт поделился историей CE в замечательном подкасте Microarch Дэна Магнума.
Стоит послушать интервью Мэтта целиком (ссылки на Spotify, YouTube и Apple Podcasts), так как в нём есть много интересных рассуждений об архитектуре, истории компьютеров и CE.
Мэтт Годболт присоединился к обсуждению первых микропроцессоров, работы в игровой отрасли, оптимизации производительности на современных CPU x86 и вычислительной инфраструктуры отрасли финансового трейдинга. Также мы обсудили работу Мэтта по переносу YouTube на первые мобильные телефоны и историю Compiler Explorer
Сам Мэтт также провёл подкаст с Беном Рэди Two’s Complement, в том числе эпизод о будущем CE (ссылки на Spotify, YouTube и Apple Podcasts), в котором гораздо подробнее рассказывается о текущем статусе проекта.
CE — это опенсорсный проект, исходники которого выложены на GitHub, то есть при наличии времени и опыта вы можете хостить CE самостоятельно.
Большинство людей пользуется онлайн-версией CE, которая бесплатна и поддерживается благодаря пожертвованиям и спонсорам.
Возможности Compiler Explorer
Для чего можно использовать CE? В этом году Мэтт Годболт выступил с докладом, описывающим различные способы применения CE и его новых функций
Давайте вкратце рассмотрим самые простые способы использования CE.
▍ Исследование архитектур
Мы можем сравнивать ассемблерный код для различных архитектур. В чём отличие кода для x86-64 от кода для ARM64 и RISC-V? Ответ можно узнать всего за несколько секунд. Вот x86-64 (все примеры созданы с помощью GCC 14.1):
square(int): push rbp mov rbp, rsp mov DWORD PTR [rbp-4], edi mov eax, DWORD PTR [rbp-4] imul eax, eax pop rbp ret
А вот ARM64:
square(int): sub sp, sp, #16 str w0, [sp, 12] ldr w0, [sp, 12] mul w0, w0, w0 add sp, sp, 16 ret
RISC-V (64-битный):
square(int): addi sp,sp,-32 sd ra,24(sp) sd s0,16(sp) addi s0,sp,32 mv a5,a0 sw a5,-20(s0) lw a5,-20(s0) mulw a5,a5,a5 sext.w a5,a5 mv a0,a5 ld ra,24(sp) ld s0,16(sp) addi sp,sp,32 jr ra
Код вполне в духе RISC. А как насчёт чего-то более CISC наподобие VAX?
square(int): .word 0 subl2 $4,%sp mull3 4(%ap),4(%ap),%r0 ret
Или простого 8-битного CPU наподобие 6502:
square(int): ; @square(int) pha clc lda __rc0 adc #254 sta __rc0 lda __rc1 adc #255 sta __rc1 pla clc ldy __rc0 sty __rc6 ldy __rc1 sty __rc7 sta (__rc6) ldy #1 txa sta (__rc6),y lda (__rc6) sta __rc4 lda (__rc6),y tax lda (__rc6) sta __rc2 lda (__rc6),y sta __rc3 lda __rc4 jsr __mulhi3 pha clc lda __rc0 adc #2 sta __rc0 lda __rc1 adc #0 sta __rc1 pla rts
А если вас интересует Nvidia CUDA, то CE может показать вам код ptx.

▍ Освоение языка ассемблера
CE — отличный инструмент для изучения языка ассемблера. Достаточно навести курсор на команду, после чего откроется всплывающее окно с описанием команды.

При нажатии правой клавишей мыши на команду открывается меню, в котором можно изучить более подробное описание.

Там, в свою очередь, можно найти ссылки на веб-сайт документации по набору команд x86 Феликса Клотье.
Код на Arm ведёт к документации для Arm. Однако меня немного расстроило отсутствие документации для языка ассемблера Vax!
▍ Сравнение компиляторов
Затем мы можем сравнить результат работы разных компиляторов. Вот сравнение gcc и clang. Логично, что код, сгенерированный для такой простой функции, почти одинаков.

▍ Промежуточное представление LLVM
Для компиляторов на основе LLVM CE может показывать LLVM Intermediate Representation (LLVM IR). Подробнее LLVM IR объясняется здесь:
Его флагманский продукт — это Clang, высококлассный компилятор C/C++/Objective-C. Clang реализует традиционную архитектуру компилятора: фронтенд, который парсит исходный код в AST и понижает его до промежуточного представления (IR), оптимизатор («мидл-енд»), преобразующий IR в бол��е качественное IR, и бэкенд, преобразующий, IR в машинный код для конкретной платформы.
То есть LLVM — это промежуточный этап перед генерацией ассемблера компилятором. LLVM IR показано справа внизу:

▍ Анализатор машинного кода LLVM
Также CE предоставляет доступ ко множеству инструментов, позволяющих лучше понять наш код и способ его исполнения. Например, мы можем добавить вывод анализатора машинного кода LLVM (llvm-mca), который позволяет нам подробнее изучить то, как машинный код работает на реальном CPU.
Окно llvm-mca показано слева внизу.

llvm-mca симулирует исполнение кода на реальной машине и предоставляет информацию об ожидаемой производительности.
В нашем простом примере llvm-mca сообщает нам, что можно ожидать выполнения 100 итераций за 105 тактов симулируемой машины.
Iterations: 100 Instructions: 300 Total Cycles: 105 Total uOps: 300 Dispatch Width: 6 uOps Per Cycle: 2.86 IPC: 2.86 Block RThroughput: 1.0
Если добавить опцию -timeline , то мы получим диаграмму со временем исполнения команд при движении через CPU.
Цитата из документации llvm-mca:
Режим timeline позволяет просмотреть подробный отчёт о переходах состояний каждой команды по конвейеру команд. Этот режим включается опцией командной строки-timeline. В процессе переходов команд по различным этапам конвейера их состояния отображаются в отчёте. Состояния представлены следующими символами:
- D: команда отправлена
- e: исполнение команды
- E: команда исполнена
- R: команда удалена
- =: команда уже отправлена, ожидает исполнения
- — : команда исполнена, ожидает удаления
Вот timeline нашего простого примера:
Timeline view: 01234 Index 0123456789 [0,0] DR . . . mov eax, edi [0,1] DeeeER . . imul eax, edi [0,2] DeeeeeER . . ret [1,0] D------R . . mov eax, edi [1,1] D=eeeE-R . . imul eax, edi [1,2] DeeeeeER . . ret [2,0] .D-----R . . mov eax, edi [2,1] .D=eeeER . . imul eax, edi [2,2] .DeeeeeER . . ret [3,0] .D------R . . mov eax, edi [3,1] .D==eeeER . . imul eax, edi [3,2] .DeeeeeER . . ret [4,0] . D-----R . . mov eax, edi [4,1] . D==eeeER. . imul eax, edi [4,2] . DeeeeeER. . ret [5,0] . D------R. . mov eax, edi [5,1] . D===eeeER . imul eax, edi [5,2] . DeeeeeE-R . ret [6,0] . D------R . mov eax, edi [6,1] . D===eeeER . imul eax, edi [6,2] . DeeeeeE-R . ret [7,0] . D-------R . mov eax, edi [7,1] . D====eeeER . imul eax, edi [7,2] . DeeeeeE--R . ret [8,0] . D-------R . mov eax, edi [8,1] . D====eeeER. imul eax, edi [8,2] . DeeeeeE--R. ret [9,0] . D--------R. mov eax, edi [9,1] . D=====eeeER imul eax, edi [9,2] . DeeeeeE---R ret
▍ Интерпретируемые языки: Python, Ruby и другие
Если вас интересуют интерпретируемые языки, например, Python, то CE может отображать байт-код, сгенерированный интерпретатором Python или Ruby. Вот пример с Python.

▍ Интегрированная среда разработки CE
Теперь CE даже предоставляет простую, но завершённую IDE, в том числе функциональность CMake и возможность запуска программ и просмотра результатов.

▍ Компиляторная магия
Ещё одна функция CE заключается в возможности увидеть, насколько умны современные компиляторы. Мэтт Годболт даже выступил со множеством докладов по этой теме. Вот доклад за 2017 год:
А вот более свежий:
А вот статья на ACM: Optimizations in C++ Compilers. Цитата из введения:
В этой статье рассказывается о некоторых концепциях компиляторов и генерации кода, а затем проливается свет на крайне впечатляющие преобразования, которые выполняют компиляторы, с практическими демонстрациями моих любимых оптимизаций. Надеюсь, вы оцените важность подобных оптимизаций, которых можно ждать от компилятора, и захотите глубже исследовать эту тему. Важнее всего, чтобы вы научились любить исследование ассемблерного вывода и уважать высокое качество проектирования компиляторов.
▍ Дэшборды CE
CE даже можно использовать как интересный ресурс, чтобы понять, с какими языками работают пользователи. Это можно сделать при помощи Grafana CE Dashboard.

C++ — это язык по умолчанию, так что, возможно, на него не стоит обращать внимание, а вот то, что Rust в четыре раза менее популярен, чем C, — это любопытно.
Есть также страница StatHat, на которой отображается рост использования CE на протяжении нескольких лет.

Сейчас пользователи выполняют примерно 18 миллионов компиляций в год.
Это приблизительно 1,5 миллиона в месяц, 50 тысяч в день, 2 тысяч в час, 30 в минуту или по одной каждые две секунды. Всё это работает на Amazon Web Services и стоит примерно $2500 ежемесячных затрат на хостинг (актуальное значение можно найти в Patreon CE).
▍ Computerphile
Если вам всего этого недостаточно, то скажу, что сейчас Мэтт снимает потрясающую серию видео с YouTube-каналом Computerphile, в которой объясняет основы работы микропроцессоров. Первым в серии идёт видео «Что такое машинный код»:
Список остальных серий:
- Как CPU выполняют математические вычисления
- Конвейеры CPU
- Как в CPU работает прогнозирование ветвления
Надеюсь, это ещё не всё!
▍ Movfuscator

В архитектуре x86 команда MOV Тьюринг-полная. В качестве последнего примера приведу наш простой код, скомпилированный при помощи movfuscator, использующего только команды MOV.
Telegram-канал со скидками, розыгрышами призов и новостями IT 💻

