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 ?
Комментарии (12)
speshuric
20.05.2024 16:17+2Часто пользуюсь посмотреть простые случаи и в качестве демо. В сложных всё равно лучше локально разгребать, но для простых - отлично подходит.
Porohovnik
20.05.2024 16:17+1Стоит сказать о ещё одном преимуществе — понимать как компилятор оптимизирует тот или иной кусок кода, и тем самым писать его эффективнее(он буквально подсвечивает какая строчка во что превратилась, и раскрылась абстракция как ожидалась или нет.
kbnrjlvfrfrf
20.05.2024 16:17+3Стоит сказать о ещё одном преимуществе — понимать как компилятор оптимизирует тот или иной кусок кода, и тем самым писать его эффективнее(он буквально подсвечивает какая строчка во что превратилась, и раскрылась абстракция как ожидалась или нет.
А потом бац и конкретно ваш локальный компилятор на вашем проекте всё сделал совсем не так как вы ожидали. гг.
vesper-bot
20.05.2024 16:17А потом бац и конкретно ваш локальный компилятор на вашем проекте всё сделал совсем не так как вы ожидали. гг.
Хм, если задать совпадающие флаги и совпадающий компилятор, мы в CE и вне его получим разные резултаты? Нелогично.
kbnrjlvfrfrf
20.05.2024 16:17+1Как минимум есть разница скормили вы компилятору кусок кода, или гораздо больше чем кусок кода.
А вцелом даже тот же компилятор на том же неизменённом проекте при следующей компиляции не обязан вам выдавать тот же код.
Gryphon88
20.05.2024 16:17+2Обязан, там же не нейросети, а жесткая логика. Так что на одном компиляторе тот же код даст всегда такой же бинарь.
Niarid
20.05.2024 16:17+2Кстати инструмент интегрирован в Qt Creator начиная с 12 версии, удобненько.
iShrimp
Отличный инструмент. Пожалуй, не хватает только JIT-компилированного машинного кода для Java (Kotlin).