All streams
Search
Write a publication
Pull to refresh
94
0
Макс Казанцев @xortator

Разработчик компиляторов

Send message

Пожалуйста, перестаньте писать мне, что "курощение" -- это опечатка. Это не опечатка, а отсылка к Карлсону. Стыдно классику не знать, товарищи! :)

У джавы в числодробилках (т.е. когда в бенче не создаются объекты, не выделяется активно память и не собирается gc и т.п.) нет никаких других дополнительных проблем. Перфоманс у неё будет плюс-минус как у раста (если речь о Зинге используется одна версия LLVM)

Примеры на расте строятся банально (достаточно заюзать что угодно с UB). Ну например:

С++: https://godbolt.org/z/z6dxvrE13

Rust: https://godbolt.org/z/nK19aWMYE

Проверки на 0 честно делаются и никуда не выкашиваются, поэтому в цикле сложный CFG, против одного блока в С++. Я это не бенчмаркал, потому что мне лень, но перфоманс тут и так понятен.

Я бы сказал, что сложно построить пример, где возможны в теории (но не происходят на практике) какие-то исключительные ситуации (которые в С++ ведут к UB, а в rust -- к panic), и при этом перфоманс будет одинаковый. Если сможете это сделать, можете покидать примеры, очень интересно.

Я понимаю, что для авторов идеи UB выдавать warning на каждую арифметическую операцию со знаковыми (после promotion) целыми числами было бы всё же слишком палевно, они на это не пойдут. Сликом открыто была бы видна их злобная сущность. Примерно как авторы законов о money laundering, тем не менее, не решились до сих пор открыто изъять все деньги со всех банковских счетов населения.

Я всё жду, когда авторов идеи UB начнут сравнивать с нацистами или даже конкретно с Гитлером. :)

Давайте я сразу пошлю в лес все инсинуации на тему "чего можно было бы сделать". clang и LLVM -- открытые продукты. Вы можете пойти и написать соответствующие ворнинги там, где хотите. Если вы сможете обосновать ревьюерам их необходимость, ваши правки примут. Честно. Никакой магии нет.

Я рассказываю о том, как оно работает сейчас, и объясняю, почему. Это не значит, что это лучший или единственно возможный способ делать то или это, но жизнь такова и более никакова. Вы можете 125 раз быть несогласны со вторым началом термодинамики или с правилами навигации кораблей в полярных водах, но разглагольствовать об этом здесь -- бесполезно.

Java это сделала задолго до Раста. Это не "додумались убрать UB, которое исторически откуда-то притекло", а "вполне осознанно зарезали оптимизации путём спецификации любого поведения в угоду безопасности программирования". Перфоманс соответствующий.

Да, хорошее замечание, но я опустил эти подробности, имея в виду, что читатель все-таки знает, что такое printf. На самом деле достаточно вывести willreturn для этой функции (тогда не будет зависания или передачи управления неизвестно куда) и noalias для ptr. Полное определение тут можно не иметь, хватит атрибутов.

В смысле усилий программиста дешевле UB sanitizer. Он эти чеки сам вставляет. Другое дело, что так можно отловить сильно не всё. Вручную, впрочем, тоже. Банальный пример: если у вас есть только int*, вы никак не можете проверить, вылезаете вы за границы выделенной памяти или нет. Для этого где-то дополнительно придётся хранить длину, и это будет работать только если указатель всегда только на начало массива, а не куда-то в середину.

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

Понял. Пишите на лиспе.

Знал ли Наполеон Бонапарт, какой у него IQ, учитывая, что IQ придумали в 1916 году? :)

Не сенатор, а какой-то большой начальник в IBM. Что никак не отменяет 15+ лет опыта.

Вы можете генерить такой LLVM IR, в котором вообще не будет UB. Например, так, как вы описываете (плюс не навешивать никуда флагов типа nsw). Даже там, где язык это позволяет, он не обязывает делать именно UB. Но оптимизатору с этим будет жить тяжелее, поэтому на практике всегда, когда язык позволяет UB, стараются сгенерить такой IR, который ведёт себя так же.

Т.е. можно задёшево и независимо от языка сделать код для тестов на UB?

За очень-очень-очень дорого, но можно. Можете померить скорость работы джавы с отключенным Tier 2 компилятором и сравнить с аналогичными программами на С++. Будет примерно то же самое (ожидаю в среднем разницу раз в 5-10, в терминальных случаях в сотни и тысячи).

P.S. В результате, я лично перешел для большинства задач на Java (где UB гораздо меньше)

Строго говоря, в джаве UB нет вообще, и даже в JLS такое слово не встречается. :) Дело в том, что "потенциально опасные" инструкции в джаве всё равно могут быть. Например, если вы компилируете джаву в LLVM (как делает компилятор Falcon), у вас всё равно в какой-то момент могут появиться всякие nsw флаги. Другое дело, что (если только в компиляторе нет багов) в этом случае реальное, непосредственное UB никогда не должно выполниться. Однако poison вполне себе генерится и протекает.

Посмотрите на ассемблер внимательно. Там две разные функции, просто записанные подряд. Тело одной начинается с лейбла main:, а тело другой -- с never_called():. Просто так получилось, что весь код после лейбла main: выкосился, и поэтому при передаче туда управления немедленно начинает исполняться код другой функции, который лежит дальше.

Это разруливает фронтэнд. LLVM IR имеет чётко прописанное поведение каждой своей инструкции (когда там poison и когда там UB), а задача компилятора С или С++ -- перевести конструкции этих языков в LLVM IR таким образом, чтобы они работали так, как требуют стандарты этих языков.

Можете почитать статью Джона Рейгра Undefined Behavior != Unsafe Programming. Это не поможет писать без UB (это невозможно), но может, вы станете к этому относиться проще. :)

https://blog.regehr.org/archives/1467

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

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

while (true) {
  <килотонна кода>
  cond = ...
  if (cond) break;
  <килотонна кода>
}

Представьте, что компилятор доказал, что cond начиная с итерации 10 -- инвариант. И переписал цикл в духе

int fake_cnt = 0;
while (true) {
  <килотонна кода>
  cond = ...
  if (cond && cnt++ < 10) break;
  <килотонна кода>
}
cond_10 = ...
while (true) {
  <килотонна кода>
  
  if (cond_10) break;
  <килотонна кода>
}

Теперь у вас во втором цикле инвариантное условие, и его можно переписать как

cond_10 = ...
if (cond_10) {
  while (true) {
    <килотонна кода>
    if (true) break;
    <килотонна кода>
  }
} else {
  while (true) {
    <килотонна кода>
    if (false) break;
    <килотонна кода>
  }
}

Допустим, cond_10 в реальности всегда true, но доказать это невозможно. Так бывает. Теперь у вас второй while -- бесконечный цикл с горой кода, который ничего не делает. Если вы туда зайдёте, ваша программа зависнет и не сделает ничего и никогда. Вычищение такого кода -- экономия компайл тайма.

Какие проблемы сделать модификаторы типов для определения поведения? И типы длинных чисел.

Вы, надеюсь, понимаете, чем сложение в длинных числах отличается от сложения в типах, поддерживаемых аппаратно, и во столько раз это медленнее? Если нет, то марш читать про то, что такое регистры.

То есть явно указать поведения тоже невозможно средствами языка. Опять что-то мешает?

То есть, вы предлагаете перед каждым делением вставлять (на уровне компиляторного фронта) чек типа

if (denom == 0) {
  // делай то, чего вы там хотите
}

? Ну тогда будет Java, пишите на ней. В С++ это убьёт перфоманс.

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

А что будет, если вы предположили, а они не выполняются? :)

Что мешает инициализировать переменные нулями.

Помимо того, что дорого, так ещё и убьёт кучу оптимизаций. Ну например:

int x;
if (smth) {
  x = 10;
}
print(x);

Вы тут не сможете заменить на print(10), придётся честно проверять.

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

Уточните -- кому надо? :)

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

Если вы не понимаете, зачем это делается, это не значит, что в этом нет логики и здравого смысла.

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

Верно, вы ничего не понимаете в компиляторах. Вас это удивляет? :)

Ну без nsw, который тут только фронт и может поставить, не обойтись. Языки, где переполнение работает на кольце, обязаны здесь вызвать do_2 при a=SINT_MAX, а C++ не обязан.

Я как раз сейчас в процессе написания статьи про UB. Надеюсь выпустить на этой неделе.

Если коротко, то изначально генерится максимально топорный IR, который работает "как написано", без каких-либо попыток эксплуатировать UB. А дальше запускается оптимизирующий пайплайн, который может эксплуатировать те или иные факты.

В данном конкретном примере происходит следующее: поскольку знаковое переполнение в С++ это UB, то на уровне LLVM у всех арифметических инструкций стоит флаг nsw. Пример можно посмотреть здесь: https://godbolt.org/z/j3n77no7r

Не вдаваясь в подробности, наличие флага nsw говорит компилятору: "работай с этой инструкцией так, как будто там никогда не происходит знаковое переполнение". Зная это, какая-нибудь оптимизация (скорее всего, InstCombine) может решить, что "add nsw x, 1 > x", поскольку случай переполнения его в этом случае не волнует, а без переполнения это всегда так.

В rust, видимо, семантика сложения такова, что оно всегда делается по модулю 2^32. Поэтому фронтенд флаг nsw на инструкции add не поставит, и описанная выше трансформация применяться не будет.

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

Если верить Википедии, Билл Гейтс ещё в школе увлекался программированием, в 13 лет написал компьютерную игру и всё такое. Так что к 28 годам у него было 15+ лет опыта.

Information

Rating
Does not participate
Location
Новосибирск, Новосибирская обл., Россия
Registered
Activity

Specialization

System Software Engineer, Software Performance Engineer