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

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, в которой объясняет основы работы микропроцессоров. Первым в серии идёт видео «Что такое машинный код»:


Список остальных серий:


Надеюсь, это ещё не всё!

▍ Movfuscator



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

Telegram-канал со скидками, розыгрышами призов и новостями IT ?

Комментарии (12)


  1. iShrimp
    20.05.2024 16:17
    +3

    Отличный инструмент. Пожалуй, не хватает только JIT-компилированного машинного кода для Java (Kotlin).


  1. speshuric
    20.05.2024 16:17
    +2

    Часто пользуюсь посмотреть простые случаи и в качестве демо. В сложных всё равно лучше локально разгребать, но для простых - отлично подходит.


  1. LeoSv
    20.05.2024 16:17

    Там нет PHP. Жалкая поделка.


    1. midia21
      20.05.2024 16:17

      Рофл нот детектед)


  1. Porohovnik
    20.05.2024 16:17
    +1

    Стоит сказать о ещё одном преимуществе — понимать как компилятор оптимизирует тот или иной кусок кода, и тем самым писать его эффективнее(он буквально подсвечивает какая строчка во что превратилась, и раскрылась абстракция как ожидалась или нет.


    1. kbnrjlvfrfrf
      20.05.2024 16:17
      +3

      Стоит сказать о ещё одном преимуществе — понимать как компилятор оптимизирует тот или иной кусок кода, и тем самым писать его эффективнее(он буквально подсвечивает какая строчка во что превратилась, и раскрылась абстракция как ожидалась или нет.

      А потом бац и конкретно ваш локальный компилятор на вашем проекте всё сделал совсем не так как вы ожидали. гг.


      1. vesper-bot
        20.05.2024 16:17

        А потом бац и конкретно ваш локальный компилятор на вашем проекте всё сделал совсем не так как вы ожидали. гг.

        Хм, если задать совпадающие флаги и совпадающий компилятор, мы в CE и вне его получим разные резултаты? Нелогично.


        1. kbnrjlvfrfrf
          20.05.2024 16:17
          +1

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

          А вцелом даже тот же компилятор на том же неизменённом проекте при следующей компиляции не обязан вам выдавать тот же код.


          1. Gryphon88
            20.05.2024 16:17
            +2

            Обязан, там же не нейросети, а жесткая логика. Так что на одном компиляторе тот же код даст всегда такой же бинарь.


            1. kbnrjlvfrfrf
              20.05.2024 16:17
              +3

              Обязан

              Где в каких стандартах эта обязанность зафиксирована?


              1. Gryphon88
                20.05.2024 16:17
                +5

                да, это я неправ. Есть недетерминированные вещи как в языке, так и в тулчейне. Впрочем, с этим борются.


  1. Niarid
    20.05.2024 16:17
    +2

    Кстати инструмент интегрирован в Qt Creator начиная с 12 версии, удобненько.