Пользователь
102,0
рейтинг
28 июля 2014 в 00:51

Разработка → Линус Торвальдс: GCC 4.9.0 «неизлечимо сломан»

Компиляторы последних поколений стали настолько умными, что практически самостоятельно генерируют код, оптимизируя всё подряд. Иногда это приводит к неприятным последствиям.

В процессе подготовки очередного релиз-кандидата в ядре Linux 3.16 выяснилось совершенно непредсказуемое поведение функции балансировки нагрузки в Linux 3.16-rc6. В списке рассылки для разработчиков ядра двое авторов прислали сообщения о разных багах, хотя у них могла быть общая природа.

Линус Торвальдс внимательно разобрался в вопросе и ёмко ответил одному из сообщивших о баге: «Ok, я посмотрел на генерацию кода, и твой компилятор — чистое и полное дерьмо».

Продолжение письма Линуса.

Я включил Якуба в список рассылки, потому что gcc-4.9.0 кажется неизлечимо сломанным.

Вот смотри, твой компилятор делает совершенно невообразимые вещи с вытеснением (спиллингом) данных, включая вытеснение константы. Господи, да этому компилятору нельзя ещё выходить из детского сада. Мы говорим о дебиле, которого в детстве уронили головой — такой здесь уровень задержки в развитии:

...
movq $load_balance_mask, -136(%rbp) #, %sfp
subq $184, %rsp #,
movq (%rdx), %rax # sd_22(D)->parent, sd_parent
movl %edi, -144(%rbp) # this_cpu, %sfp
movl %ecx, -140(%rbp) # idle, %sfp
movq %r8, -200(%rbp) # continue_balancing, %sfp
movq %rax, -184(%rbp) # sd_parent, %sfp
movq -136(%rbp), %rax # %sfp, tcp_ptr__
#APP
add %gs:this_cpu_off, %rax # this_cpu_off, tcp_ptr__
#NO_APP
... 

Обратите внимание на содержимое -136(%rbp). Реально. Это непосредственная константа, которую компилятор вытеснил.

Кто-то должен оформить это как баг gcc. Потому что это, чёрт возьми, офигенно сумасшедшая хрень.

Но эту часть с вытеснением константы можно считать «слишком тупой, чтобы жить». Настоящий баг здесь:

movq $load_balance_mask, -136(%rbp) #, %sfp
subq $184, %rsp #,

Тут gcc создаёт стековый фрейм после его использования для сохранения константы гораздо дальше стекового фрейма.

В любом случае, подчёркивает Линус, это не баг ядра. Он предлагает создать официальное предупреждение для всех ни в коем случае не использовать компилятор gcc-4.9.0, а «пользователям Debian, вероятно, следует сделать даунгрейд их новенького классного компилятора».



P.S. Комментарий хорошо информированного пользователя a5b: Открытый Линусом баг 61904 (gcc bugzilla) был закрыт как дубликат бага 61801. 61801 существовал в версиях gcc с 4.5.0 по 4.8.3, 4.9.0 и 4.9.1 (однако до 4.9.0 ошибка не приводила к спиливанию константы в коде load_balance). Исправлено начиная с 4.8.4, 4.9.2, 4.10.0. Патч — одна строчка в sched_analyze_2.
Анатолий Ализар @alizar
карма
743,5
рейтинг 102,0
Пользователь
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

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

  • +32
    Проблема тут не в том, что в стек записываются данные до изменения rsp (смещение входит в red zone, там перед этим кодом ещё несколько изменений в rsp. Дело в том, что ядро собирается без red zone, а gcc это игнорирует при записи констант в стек и подразумевает, что код исполняется c red zone.
    • +95
      Линус как раз об этом и пишет дальше в письме, но Ализар решил не переводить почему-то.
      • +18
        Иначе он не был бы alizar'ом
  • +3
    Очень поэтичное письмо.
  • +66
    Только могу себе представить, что это за ужасное чувство когда Линус Торвальдс говорит что то подобное про твою программу
    • –9
      Ваш код никогда не ругали? Вы сразу начали писать чистый код по феншую? За что то из прошлого, мне очень стыдно, он работает до сих пор, но я надеюсь, этого никто не увидит.
    • +76
      >Линус Торвальдс говорит про твою программу
    • +3
      Зато как говорит! Читать интересно
  • –97
    Знал, что доверять gcc который непонятно кто разрабатывает (в ссср такую форму собственности называли колхоз) — нельзя.
    Удивлен, что gcс вообще как-то работает.
    • +30
      оффтоп: неплохим критерием «необходимости отдыха» — это когда «ссср» читаешь как «си-си-си-пи»…
      • 0
        «дэ-эс-эс» тоже признак, наверное…
    • +9
      Назовите компилятор, которому вы доверяете, и имена хотя бы трех человек, которые его разрабатывают.
      • –6
        А можно мне назвать?! Пожалуйста! :)

        Доверяю компилятору TypeScript.
        Anders Hejlsberg (очень сильно доверяю, вообще, мужик ещё с C# заработал авторитет),
        Daniel Rosenwasser (нормальный, вроде, доверяю),
        Jason Freeman (ему доверяю меньше, прищур уж больно какой-то хитрый).
      • –4
        Clang форева.
      • +1
        по большому счету, доверять нельзя никому.

        с gcc 4.6 лично натыкался на баги компиляции.
      • 0
        Возможно, доверия заслуживает CompCert (wiki, compcert.inria.fr/, вход — подмножество Си, выход — ассемблер PowerPC/ARM/x86) — формально верифицированный компилятор. Один из авторов — Xavier Leroy — разработчик OCaml, ранее — разработчик LinuxThreads (библиотеки эмуляции posix threads для ранних версий glibc под linux — 1998-2004). Презентация о компиляторе: research.microsoft.com/en-us/um/redmond/events/ss2011/slides/friday/xavier_leroy.pdf На верификацию ушло 4 человеко-года и 50000 строк Coq.
    • +5
      То ли дело ICC.
      Всё продумано, всё разумно, даже стандарт Cripple AMD поддерживается.
  • +18
    ни в коем случае не использовать этот компилятор,

    В оригинале говорится о конкретной версии компилятора, а не о компиляторе в целом.
    nobody compiles with gcc-4.9.0
    • +16
      Я вот прочитал пост, но так и не понял, в чём суть проблемы, какая часть компилятора работает неправильно и какая особенность компилятора приводит к багам (буду рад, если кто-то мне это пояснит ответом на этот комментарий).

      Мне кажется, в данном посте стоило разъяснить именно суть проблемы, пояснить используемые термины (например, термин «непосредственная константа», который на русском языке почти не гуглится; ведь средний уровень знаний читателей поста немного ниже, чем уровень разработчиков ядра Linux из рассылки), а не использовать громкие фразы вроде: «Линус Торвальдс сказал, что GCC — дерьмо.» Не думаю, что человеку будет приятно, когда его отрицательный отзыв о компиляторе из технической рассылки, вырванный из контекста (где речь идёт о конкретной версии), сейчас растиражирует весь Интернет.
      • +41
        Мне пришлось гуглить и разбираться, потому что дико интересно, и я ничегошеньки из статьи не понял. Если я не прав, пусть меня поправят знающие люди.

        Проблема в порче стека в этом куске кода:
        movq $load_balance_mask, -136(%rbp) #, %sfp
        subq $184, %rsp #,
        


        rbp указывает на начало стека, rsp — на его вершину.
        Так вот, сначала gcc записывает константу за пределы стека, глубоко вниз, 136 байт от начала; и только потом изменяет размер стека, поменяв указатель на вершину стека. Если между двумя этими операциями произойдет прерывание, стек может быть испорчен.

        Почему gcc так делал? Возможно, потому что он считал, что работает в «красной зоне».

        Тут стоит процитировать AMD64 ABI:
        Скрытый текст
        The 128-byte area beyond the location pointed to by %rsp is considered to be reserved and shall not be modified by signal or interrupt handlers. Therefore, functions may use this area for temporary data that is not needed across function calls. In particular, leaf functions may use this area for their entire stack frame, rather than adjusting the stack pointer in the prologue and epilogue. This area is known as the red zone.


        АМД рекомендует сохранять 128 байт перед rsp, как «красную зону», неприкасаемую для прерываний. Это позволяет использовать стек, не изменяя его размера, что бывает полезно при оптимизациях. Т.е. виртуально стек становится на 128 байт больше.

        Но ядро линукса компилируется с флагом -mno-red-zone, без использования «красной зоны», а код, создаваемый gcc подразумевает её использование. Т.е. gcc в какой-то из оптимизаций проигнорировал этот флаг, и записал данные за пределы стека.

        Как по мне — этот баг не заслуживает эпитетов «неизлечимо сломан», «чистое и полное дерьмо», ну и так далее.
        • +2
          P.S. Похоже, что баг был непосредственно в определении, когда класть константу на стек, а когда изменять размер этого стека, но из-за «красной зоны», этот баг не проявлялся в 99% приложений.
          • +9
            Смысл в том, что константу вообще можно было никуда не записывать. В нормальных условиях спиллинг применяется для переменных, а константу можно просто запомнить а потом применить в том месте, где производится загрузка значения для использования.

            Наблюдаемое же поведение компилятора теряет производительность на пустом месте, организуя совершенно не нужное чтение из памяти (да еще и с нарушением настроек red zone).
            • +3
              Вот, теперь когда я благодаря вам знаю, что такое spilling, вся картина для меня стала понятной.
              Спасибо.
            • 0
              Смысл все-таки в неправильной последовательности действий. Спиллинг константы хоть и глупое, но все же корректное поведение.
              • 0
                Разумеется. Объективно, проблема только одна — несоблюдение настройки red zone. Если она включена, то компилятор вполне имеет право поменть операции местами.
                • 0
                  Если я правильно понял этот пост, то red zone никак не учитывается в этом коде (ни её наличие, ни её отсутствие). Похоже её включение просто скрывало этот баг до поры.

                  Но это пост не от разработчика gcc, так что он может ошибаться.
  • +26
    Господи, да этому компилятору нельзя ещё выходить из детского сада. Мы говорим о дебиле, которого в детстве уронили головой — такой здесь уровень задержки в развитии
    После таких слов, мне как то стало жаль этот компилятор!
    • +58
      Компиляторы не предупреждают Линуса Торвальдса, Линус Торвальдс предупреждает компиляторы.
  • +34
    Открытый Линусом баг 61904 (gcc bugzilla) был закрыт как дубликат бага 61801. 61801 существовал в версиях gcc с 4.5.0 по 4.8.3, 4.9.0 и 4.9.1 (однако до 4.9.0 ошибка не приводила к спиливанию константы в коде load_balance). Исправлено начиная с 4.8.4, 4.9.2, 4.10.0. Патч — одна строчка в sched_analyze_2.
    • +4
      Вот вам и ализаровское «неизлечимо сломан»
      • 0
        Вот вам и линусовое «неизлечимо сломан»
  • +18
    Блин. Не, я, конечно, всё понимаю, ситуация нехорошая, но так отзываться о обычном баге? Не слишком ли Линус несдержан? Баги были, есть и будут. В том числе и очень трудноуловимые, уж он то должен это знать!
    • +6
      Я так думаю, что не стоит приравнивать написанное в письме в рассылке к «официальному заявлению». Это даже не дотягивает до записи в бложик, не говоря уже о статье в каком-нибудь журнале.
      А выхватывать цитату из контекста — это стиль современной журналистики.
    • +8
      В оригинальном баг репорте нет никакой истерики. Всю драму развели в репостах.

      Линус обозначил проблему и удивился, какого хрена компилятор вообще спилит константу, что является абсолютно резонным вопросом.
      • +14
        В баг репорте уже всё цивильно. А вот в обсуждении по почте были как раз выражения из этого поста. Судя по всему он провел несколько часов (дней?) собирая ядро новым компилятором и не мог понять какой из коммитов в ядра так сильно всё поломал. И в конце концов он полез смотреть ассемблерный код, и сравнивать его с исходниками, чтобы понять почему оно работает не так как надо. После детального изучения стало ясно что баг в свежем gcc, а не в ядре, ну и тут понеслась.
        • +25
          Ну, его вполне можно понять. Любой баг в программе это проблема, нервное напряжение и плохое настроение. Особенно если это гейзенбаг. Особенно если оказывается, что он в компиляторе, проверять который программист полезет только в последний момент, когда уже испробовано все.

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

          P.S.: Послушать разговоры в офисе при сдаче очередного релиза, так вообще каждого второго можно расстрелять за преступления против человечества.
    • +1
      Это внутренний список рассылки разработчиков ядра, исключительно внутреннее общение (разве что выставленное на всеобщее обозрение). Примерный аналог разговора коллег в офисе (или в каком-нибудь мессенджере). Так что еще очень мягко :)
    • +8
      Для Линуса это обычный стиль общения. Все уже привыкли. Вот, скажем, известная его цитата про C++:
      harmful.cat-v.org/software/c++/linus
      • +1
        В этом примере Линус просто «возвращает» оппоненту его же стиль общения в первой фразе. Остальные доводы хоть и эмоциональны, но вполне корректно сформулированы.
        • +5
          Вот вам тогда ещё в коллекцию :-)
          lkml.org/lkml/2012/12/23/75
          • +2
            Да, это письмо интереснее :)
    • 0
      Пускай и несдержан, зато не болен толерантностью головного мозга, что на мой взгляд на много лучше.
  • +6
    Really = Реально.
    Никогда так больше не переводи.
    • +5
      Мне показалось, в контексте «разборок по понятиям» это наиболее подходящий вариант. :)
      • +13
        Тогда уж и «в натуре» можно, ёпта.

        Страна победившего блатняка, Линус у нас «по понятиям» уже «разбирается».
  • 0
    Скажите, а почему gcc поменял местами эти инструкции, если указатель кадра и указатель стека все равно не приравниваются (тут, в этом куске по крайней мере). Т.е. и rbp и rsp определены заранее, раз компилятор так запросто опирается на rbp до rsp -> rbp.
    В чем оптимизация, как можо что-то выиграть, записывая в стек до изменения указателя? Так лучше загружается конвейер?

    Если бы (лучше знаком с IA32), было бы так:
    sub esp, 64
    mov ebp, esp
    mov [ebp + 12], xyz

    все бы три инструкции выполнялись последовательно, т.к. каждая последующая зависит от результатов предыдущей. Если (не помню точно ни по тактам, ни по спариванию в разных пайпах) пересылка регистр-регистр, выполняется за 1 такт, а вычитание — за большее кол-во, был бы смысл менять местами, заранее прибавляя к ebp константу с учетом того, что esp подвинется позже.

    Но в обсуждаемом куске? Я что-то упускаю?
    • 0
      В чем оптимизация, как можо что-то выиграть, записывая в стек до изменения указателя?

      Можно работать со стеком, не меняя значение указателя, экономя на командах push/pop. В данном случае — похоже, просто баг, а не излишняя оптимизация.
      • 0
        экономя на командах push/pop

        Это понятно, здесь же mov через кадр. Но, как я понимаю из статьи, баг именно в очередности операций и прерывании, которое поступает между инструкциями. Пытаюсь понять, чего хотел gcc достичь.
  • +11
    Линус Торвальдс показал варнинг компилятору.
  • –1
    Прошел год, баг до сих пор не закрыт. Недавно наткнулся на то, что gcc просто рандомно выкидывает ссылки из код на C++, segfault как итог…

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.