Комментарии 51
Очень поэтичное письмо.
Только могу себе представить, что это за ужасное чувство когда Линус Торвальдс говорит что то подобное про твою программу
Знал, что доверять gcc который непонятно кто разрабатывает (в ссср такую форму собственности называли колхоз) — нельзя.
Удивлен, что gcс вообще как-то работает.
Удивлен, что gcс вообще как-то работает.
НЛО прилетело и опубликовало эту надпись здесь
Назовите компилятор, которому вы доверяете, и имена хотя бы трех человек, которые его разрабатывают.
НЛО прилетело и опубликовало эту надпись здесь
Clang форева.
по большому счету, доверять нельзя никому.
с gcc 4.6 лично натыкался на баги компиляции.
с gcc 4.6 лично натыкался на баги компиляции.
Возможно, доверия заслуживает 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.
То ли дело ICC.
Всё продумано, всё разумно, даже стандарт Cripple AMD поддерживается.
Всё продумано, всё разумно, даже стандарт Cripple AMD поддерживается.
ни в коем случае не использовать этот компилятор,
В оригинале говорится о конкретной версии компилятора, а не о компиляторе в целом.
nobody compiles with gcc-4.9.0
Я вот прочитал пост, но так и не понял, в чём суть проблемы, какая часть компилятора работает неправильно и какая особенность компилятора приводит к багам (буду рад, если кто-то мне это пояснит ответом на этот комментарий).
Мне кажется, в данном посте стоило разъяснить именно суть проблемы, пояснить используемые термины (например, термин «непосредственная константа», который на русском языке почти не гуглится; ведь средний уровень знаний читателей поста немного ниже, чем уровень разработчиков ядра Linux из рассылки), а не использовать громкие фразы вроде: «Линус Торвальдс сказал, что GCC — дерьмо.» Не думаю, что человеку будет приятно, когда его отрицательный отзыв о компиляторе из технической рассылки, вырванный из контекста (где речь идёт о конкретной версии), сейчас растиражирует весь Интернет.
Мне кажется, в данном посте стоило разъяснить именно суть проблемы, пояснить используемые термины (например, термин «непосредственная константа», который на русском языке почти не гуглится; ведь средний уровень знаний читателей поста немного ниже, чем уровень разработчиков ядра Linux из рассылки), а не использовать громкие фразы вроде: «Линус Торвальдс сказал, что GCC — дерьмо.» Не думаю, что человеку будет приятно, когда его отрицательный отзыв о компиляторе из технической рассылки, вырванный из контекста (где речь идёт о конкретной версии), сейчас растиражирует весь Интернет.
Мне пришлось гуглить и разбираться, потому что дико интересно, и я ничегошеньки из статьи не понял. Если я не прав, пусть меня поправят знающие люди.
Проблема в порче стека в этом куске кода:
rbp указывает на начало стека, rsp — на его вершину.
Так вот, сначала gcc записывает константу за пределы стека, глубоко вниз, 136 байт от начала; и только потом изменяет размер стека, поменяв указатель на вершину стека. Если между двумя этими операциями произойдет прерывание, стек может быть испорчен.
Почему gcc так делал? Возможно, потому что он считал, что работает в «красной зоне».
Тут стоит процитировать AMD64 ABI:
АМД рекомендует сохранять 128 байт перед rsp, как «красную зону», неприкасаемую для прерываний. Это позволяет использовать стек, не изменяя его размера, что бывает полезно при оптимизациях. Т.е. виртуально стек становится на 128 байт больше.
Но ядро линукса компилируется с флагом -mno-red-zone, без использования «красной зоны», а код, создаваемый gcc подразумевает её использование. Т.е. gcc в какой-то из оптимизаций проигнорировал этот флаг, и записал данные за пределы стека.
Как по мне — этот баг не заслуживает эпитетов «неизлечимо сломан», «чистое и полное дерьмо», ну и так далее.
Проблема в порче стека в этом куске кода:
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 в какой-то из оптимизаций проигнорировал этот флаг, и записал данные за пределы стека.
Как по мне — этот баг не заслуживает эпитетов «неизлечимо сломан», «чистое и полное дерьмо», ну и так далее.
P.S. Похоже, что баг был непосредственно в определении, когда класть константу на стек, а когда изменять размер этого стека, но из-за «красной зоны», этот баг не проявлялся в 99% приложений.
Смысл в том, что константу вообще можно было никуда не записывать. В нормальных условиях спиллинг применяется для переменных, а константу можно просто запомнить а потом применить в том месте, где производится загрузка значения для использования.
Наблюдаемое же поведение компилятора теряет производительность на пустом месте, организуя совершенно не нужное чтение из памяти (да еще и с нарушением настроек red zone).
Наблюдаемое же поведение компилятора теряет производительность на пустом месте, организуя совершенно не нужное чтение из памяти (да еще и с нарушением настроек red zone).
Вот, теперь когда я благодаря вам знаю, что такое spilling, вся картина для меня стала понятной.
Спасибо.
Спасибо.
Смысл все-таки в неправильной последовательности действий. Спиллинг константы хоть и глупое, но все же корректное поведение.
Господи, да этому компилятору нельзя ещё выходить из детского сада. Мы говорим о дебиле, которого в детстве уронили головой — такой здесь уровень задержки в развитииПосле таких слов, мне как то стало жаль этот компилятор!
Открытый Линусом баг 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.
Блин. Не, я, конечно, всё понимаю, ситуация нехорошая, но так отзываться о обычном баге? Не слишком ли Линус несдержан? Баги были, есть и будут. В том числе и очень трудноуловимые, уж он то должен это знать!
Я так думаю, что не стоит приравнивать написанное в письме в рассылке к «официальному заявлению». Это даже не дотягивает до записи в бложик, не говоря уже о статье в каком-нибудь журнале.
А выхватывать цитату из контекста — это стиль современной журналистики.
А выхватывать цитату из контекста — это стиль современной журналистики.
В оригинальном баг репорте нет никакой истерики. Всю драму развели в репостах.
Линус обозначил проблему и удивился, какого хрена компилятор вообще спилит константу, что является абсолютно резонным вопросом.
Линус обозначил проблему и удивился, какого хрена компилятор вообще спилит константу, что является абсолютно резонным вопросом.
В баг репорте уже всё цивильно. А вот в обсуждении по почте были как раз выражения из этого поста. Судя по всему он провел несколько часов (дней?) собирая ядро новым компилятором и не мог понять какой из коммитов в ядра так сильно всё поломал. И в конце концов он полез смотреть ассемблерный код, и сравнивать его с исходниками, чтобы понять почему оно работает не так как надо. После детального изучения стало ясно что баг в свежем gcc, а не в ядре, ну и тут понеслась.
Ну, его вполне можно понять. Любой баг в программе это проблема, нервное напряжение и плохое настроение. Особенно если это гейзенбаг. Особенно если оказывается, что он в компиляторе, проверять который программист полезет только в последний момент, когда уже испробовано все.
В конце концов, рабочая переписка вполне может иметь свои нормы поведения и используемые там выражения могут не отражать реального отношения к ситуации и тем более не являются официальными заявлениями.
P.S.: Послушать разговоры в офисе при сдаче очередного релиза, так вообще каждого второго можно расстрелять за преступления против человечества.
В конце концов, рабочая переписка вполне может иметь свои нормы поведения и используемые там выражения могут не отражать реального отношения к ситуации и тем более не являются официальными заявлениями.
P.S.: Послушать разговоры в офисе при сдаче очередного релиза, так вообще каждого второго можно расстрелять за преступления против человечества.
Это внутренний список рассылки разработчиков ядра, исключительно внутреннее общение (разве что выставленное на всеобщее обозрение). Примерный аналог разговора коллег в офисе (или в каком-нибудь мессенджере). Так что еще очень мягко :)
Для Линуса это обычный стиль общения. Все уже привыкли. Вот, скажем, известная его цитата про C++:
harmful.cat-v.org/software/c++/linus
harmful.cat-v.org/software/c++/linus
Пускай и несдержан, зато не болен толерантностью головного мозга, что на мой взгляд на много лучше.
Really = Реально.
Никогда так больше не переводи.
Никогда так больше не переводи.
Скажите, а почему gcc поменял местами эти инструкции, если указатель кадра и указатель стека все равно не приравниваются (тут, в этом куске по крайней мере). Т.е. и rbp и rsp определены заранее, раз компилятор так запросто опирается на rbp до rsp -> rbp.
В чем оптимизация, как можо что-то выиграть, записывая в стек до изменения указателя? Так лучше загружается конвейер?
Если бы (лучше знаком с IA32), было бы так:
sub esp, 64
mov ebp, esp
mov [ebp + 12], xyz
все бы три инструкции выполнялись последовательно, т.к. каждая последующая зависит от результатов предыдущей. Если (не помню точно ни по тактам, ни по спариванию в разных пайпах) пересылка регистр-регистр, выполняется за 1 такт, а вычитание — за большее кол-во, был бы смысл менять местами, заранее прибавляя к ebp константу с учетом того, что esp подвинется позже.
Но в обсуждаемом куске? Я что-то упускаю?
В чем оптимизация, как можо что-то выиграть, записывая в стек до изменения указателя? Так лучше загружается конвейер?
Если бы (лучше знаком с IA32), было бы так:
sub esp, 64
mov ebp, esp
mov [ebp + 12], xyz
все бы три инструкции выполнялись последовательно, т.к. каждая последующая зависит от результатов предыдущей. Если (не помню точно ни по тактам, ни по спариванию в разных пайпах) пересылка регистр-регистр, выполняется за 1 такт, а вычитание — за большее кол-во, был бы смысл менять местами, заранее прибавляя к ebp константу с учетом того, что esp подвинется позже.
Но в обсуждаемом куске? Я что-то упускаю?
В чем оптимизация, как можо что-то выиграть, записывая в стек до изменения указателя?
Можно работать со стеком, не меняя значение указателя, экономя на командах push/pop. В данном случае — похоже, просто баг, а не излишняя оптимизация.
Линус Торвальдс показал варнинг компилятору.
Прошел год, баг до сих пор не закрыт. Недавно наткнулся на то, что gcc просто рандомно выкидывает ссылки из код на C++, segfault как итог…
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Линус Торвальдс: GCC 4.9.0 «неизлечимо сломан»