Rust: Проблема небезопасности памяти в безопасном Rust

Созданный на 17 февр. 2020  ·  38Комментарии  ·  Источник: rust-lang/rust

У меня есть небольшая программа (упрощение тестовой функции из более крупного проекта), которая нарезает небольшой массив и пытается получить доступ к элементу, находящемуся за пределами среза. Запуск его с cargo run --release с использованием стабильной версии 1.41.0 выводит что-то вроде этого (проверено на macOS 10.15 и Ubuntu 19.10):

0 0 3 18446744073709551615
[1]    21065 segmentation fault  cargo run --release

Похоже, что получившийся срез каким-то образом имеет длину 2**64 - 1 , поэтому проверка границ опускается, что предсказуемо приводит к сбою сегментирования. На 1.39.0 и 1.40.0 та же самая программа печатает то, что я ожидал:

0 0 3 0
thread 'main' panicked at 'index out of bounds: the len is 0 but the index is 16777216', src/main.rs:13:35
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

Проблема исчезнет, ​​если я сделаю одно из следующих действий:

  • удалить любой из двух вызовов do_test(...); в main() ;
  • удалить цикл for _ in 0..1 { ;
  • замените цикл for y in 0..x { на for y in 0..1 { ;
  • удалите строку z.extend(std::iter::repeat(0).take(x)); или замените ее на z.extend(std::iter::repeat(0).take(1)); ;
  • замените цикл for arr_ref in arr { на let arr_ref = &arr[0]; ;
  • укажите RUSTFLAGS="-C opt-level=2" ;
  • укажите RUSTFLAGS="-C codegen-units=1" .

Я предполагаю, что -C opt-level=3 включает проблемный проход оптимизации в LLVM, что приводит к неправильной компиляции. Это подтверждается тем фактом, что MIR ( --emit mir ) и LLVM IR до оптимизации ( --emit llvm-ir -C no-prepopulate-passes ) одинаковы для -C opt-level=2 и -C opt-level=3 .

Дополнительная информация, которая может быть полезна:

  • Я не могу воспроизвести проблему на игровой площадке Rust (предположительно потому, что он использует codegen-units = 1 );
  • Я не могу воспроизвести проблему в Windows 10 с тем же выпуском 1.41.0 (не знаю, что отличает его);
  • cargo-bisect-rustc говорит, что регрессия впервые произошла в ночное время 2019-12-12 , а именно в этой фиксации . Это кажется мне подозрительным, учитывая, что 1.40.0 , который не обнаруживает проблемы, был выпущен после этой даты.

Я прикрепляю программу inline на случай, если репозиторий GitHub не работает (если вы хотите скомпилировать его без Cargo, используйте rustc -C opt-level=3 main.rs ):

fn do_test(x: usize) {
    let arr = vec![vec![0u8; 3]];

    let mut z = Vec::new();
    for arr_ref in arr {
        for y in 0..x {
            for _ in 0..1 {
                z.extend(std::iter::repeat(0).take(x));
                let a = y * x;
                let b = (y + 1) * x - 1;
                let slice = &arr_ref[a..b];
                eprintln!("{} {} {} {}", a, b, arr_ref.len(), slice.len());
                eprintln!("{:?}", slice[1 << 24]);
            }
        }
    }
}

fn main() {
    do_test(1);
    do_test(2);
}
A-LLVM C-bug I-unsound 💥 ICEBreaker-LLVM P-medium T-compiler regression-from-stable-to-stable

Самый полезный комментарий

Воспроизведение LLVM IR: https://gist.github.com/comex/881074b1bcc545e299e65527c719eef4

Запустите opt bconfused.ll -scalar-evolution -loop-idiom -scalar-evolution -indvars -S -O3 -o - | grep xprint . Если внутри круглых скобок стоит i64 -1 , оптимизация произошла с ошибками. Если нет ... может и не было, но в этом трудно быть уверенным.

Похоже, это происходит из-за того, что LLVM неправильно добавляет nuw к add nuw i64 %x, -1 как часть этапа упрощения индукционной переменной. x - это аргумент функции, а nuw означает отсутствие беззнакового обтекания, поэтому это фактически утверждает, что аргумент равен 0, в той точке функции, где это не гарантируется.

Деление пополам (отредактируйте: от LLVM 9 до LLVM 10, что, по словам

commit 58e8c793d0e43150a6452e971a32d7407a8a7401
Author: Tim Northover <[email protected]>
Date:   Mon Sep 30 07:46:52 2019 +0000

    Revert "[SCEV] add no wrap flag for SCEVAddExpr."

    This reverts r366419 because the analysis performed is within the context of
    the loop and it's only valid to add wrapping flags to "global" expressions if
    they're always correct.

    llvm-svn: 373184

Выглядит многообещающе, поскольку r366419 (коммит, который отменяется вышеупомянутым коммитом) включен в ветку LLVM 9.0, которую использует Rust.

Все 38 Комментарий

cc @ rust-lang / компилятор
@rustbot пингует ледоколы-llvm

Команда релиза рассматривает вопрос о выпуске точечного релиза для Rust 1.41 (мы кратко обсуждали его на встрече на прошлой неделе), и я бы хотел, чтобы это было включено в него, если мы сможем получить PR в ближайшее время.

Привет, LLVM ICE-breakers! Эта ошибка была признана хорошей
"Кандидат на взлом LLVM ICE". Если это полезно, вот несколько
[инструкции] по устранению таких ошибок. Может посмотреть?
Благодаря! <3

cc @comex @DutchGhost @ hanna-kruppe @hdhoang @heyrutvik @ JOE1994 @jryans @mmilenko @nagisa @nikic @ Ноа-Кеннеди @SiavoshZarrasvand @spastorino @vertexclique @vgxbj

Запуск его с Cargo run --release с использованием стабильной версии 1.41.0 выводит что-то вроде этого (проверено на macOS 10.15 и Ubuntu 19.10):

Я не могу воспроизвести это на детской площадке . Программа там нормально работает на 1.41.0 в режиме релиза.

РЕДАКТИРОВАТЬ: Ах, вы уже сказали это.
Также программа хороша в Miri, так что это скорее всего не UB, а неправильная компиляция.

Просто чтобы добавить точку данных, я могу воспроизвести это в Linux с помощью последней версии nightly:

[andrew<strong i="6">@krusty</strong> rust-69225]$ rustc --version
rustc 1.43.0-nightly (5e7af4669 2020-02-16)

[andrew<strong i="7">@krusty</strong> rust-69225]$ cat main.rs
fn do_test(x: usize) {
    let arr = vec![vec![0u8; 3]];

    let mut z = Vec::new();
    for arr_ref in arr {
        for y in 0..x {
            for _ in 0..1 {
                z.extend(std::iter::repeat(0).take(x));
                let a = y * x;
                let b = (y + 1) * x - 1;
                let slice = &arr_ref[a..b];
                eprintln!("{} {} {} {}", a, b, arr_ref.len(), slice.len());
                eprintln!("{:?}", slice[1 << 24]);
            }
        }
    }
}

fn main() {
    do_test(1);
    do_test(2);
}

[andrew<strong i="8">@krusty</strong> rust-69225]$ rustc -C opt-level=3 main.rs

[andrew<strong i="9">@krusty</strong> rust-69225]$ ./main
0 0 3 18446744073709551615
zsh: segmentation fault (core dumped)  ./main

Я смог воспроизвести вышеприведенное с тем же самым результатом с помощью Rust 1.41 stable. Rust 1.40 stable не обнаруживает проблемы:

$ ./main
0 0 3 0
thread 'main' panicked at 'index out of bounds: the len is 0 but the index is 16777216', main.rs:13:35
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

Я думаю, что все это согласуется с отчетом @dfyz , за исключением того, что это, по крайней мере, подтверждает, что он не специфичен для macOS.

  • cargo-bisect-rustc говорит, что регрессия впервые произошла в ночное время 2019-12-12 , особенно в этом коммите . Это кажется мне подозрительным, учитывая, что 1.40.0 , который не обнаруживает проблемы, был выпущен после этой даты.

Это ожидаемо. 1.40.0 был выпущен 19 декабря 2019 года на основе beta , которая была ответвлением от master шестью неделями ранее (примерно во время выпуска 1.39.0). См. Https://doc.rust-lang.org/book/appendix-07-nightly-rust.html для получения дополнительной информации о каналах выпуска.

Если бы мне пришлось угадывать, я бы сказал, что https://github.com/rust-lang/rust/pull/67015 является вероятным виновником. Это исправило 3 проблемы с кодогенерацией, поэтому оно затрагивает критический для кодогенерации код.
Копия @ osa1 @ oli-obk @wesleywiser

Спасибо за пинг. В настоящее время я создаю сборку для исследования. Возможно, это ошибка, появившаяся в # 67015. Другая возможность состоит в том, что # 67015 только что обнаружил существующую ошибку.

Мне удалось воспроизвести segfault с помощью rustc nightly в Linux, но не с моей сборкой, созданной с помощью этого config.toml:

config.toml

[llvm]

[build]

[install]

[rust]
optimize = true
debug = true
codegen-units = 0
debug-assertions = true
debuginfo-level = 2

[target.x86_64-unknown-linux-gnu]
llvm-config = "/usr/bin/llvm-config-9"

[dist]

Используя rustc nightly, я проверил MIR до и после ConstProp, и MIR идентичны. Так что, если это вызвано ConstProp, то это из-за разницы в сгенерированном коде библиотеки, а не этой программы.

Регрессия в 033662dfbca088937b9cdfd3d9584015b5e375b2

@rustbot изменить метки: -E-


@ osa1 наверное виноват debug-assertions = true . Когда я пытаюсь скомпилировать (с помощью обычного ночного компилятора) программу с -C debug-assertions=y программа паникует вместо segfault

Думаю, я решил это! Возврат к a983e0590a43ed8b0f60417828efd4e79b51f494 устраняет проблему. Мне это казалось виноватым весь день, но я не мог проверить это на работе :) Кто-нибудь может мне помочь, как лучше всего сделать пиар по этому вопросу? Я думаю, что лучший способ - добавить тестовый пример, который должен завершиться ошибкой, но, похоже, это очень зависит от платформы и т.д., так что, может быть, это все-таки не очень хорошая идея? Есть идеи здесь? Благодаря!

(Это уже было разделено пополам OP)

Мне удалось воспроизвести это с помощью локальной сборки с выключенным debug-assertions (спасибо за это @ hellow554).

Некоторые PR в свертке вызывают конфликты при возврате, потому что с тех пор мы rustfmt -ed все, но я считаю, что эта проблема связана с # 67174.

изменить: кажется, мы нашли это в то же время @shahn :)

@lqd да, это проблема, которая включает в себя фиксацию, о которой я упоминал выше. В этом виноват.

Чтобы добавить еще одну точку данных, проблема исчезнет, ​​если для codegen-units установлено значение 3 или меньше (при условии, что профиль выпуска с incremental=false ). Другими словами, я могу воспроизвести, когда codegen-units равно 4 или больше.

Вызов panic_bounds_check исчезает после прохода LLVM Jump Threading. Я могу воспроизвести проблему, выбрав вариант с LLVM 9, но не с LLVM 10.

Итак, я проверил и построил этап 1 rustc ( ./x.py build -i --stage 1 ), пересобирал libstd ( ./x.py build -i --stage 1 --keep-stage 0 src/libstd ) без # 67174 и перекомпилировал программу segfaulting с четыре блока кодогенерации ( rustc +stage1 -C opt-level=3 -C codegen-units=4 main.rs ). Как и ожидалось, segfault исчез. Если я снова применю # 67174, segfault вернется.

Это означает, что теперь у меня есть два компилятора, которые отличаются только стандартной библиотекой, которую они используют. Назовем эти компиляторы GOOD (без segfault) и BAD (segfault).

Затем я заметил, что 4 _unoptimized_ *.ll файла, сгенерированные GOOD ( -C no-prepopulate-passes ), почти такие же, как и файлы, созданные BAD (единственная разница Я видел разные случайные идентификаторы в именах функций), но _optimized_ *.ll files (без -C no-prepopulate-passes ) сильно отличаются. Я ни в коем случае не эксперт по компиляторам, но поскольку компилируемая программа в обоих случаях одинакова, оба компилятора совершенно одинаковы, и единственная разница заключается в предварительно скомпилированной стандартной библиотеке, я _ думаю_, что LTO может быть задействован .

Действительно, если я передаю любой из -Z thinlto=no , -C lto=no , -C lto=yes , -C lto=thin (на данный момент я действительно не уверен, но я предполагаю, что все формы -C lto отличаются от ThinLTO, который используется по умолчанию) на BAD , segfault снова исчезает.

Есть ли вероятность, что здесь виноват LTO?

Я прочитал тестовый пример. Я прочитал отменяемую фиксацию. Я до сих пор не понимаю, что происходит. Что сломалось?

Что сломалось?

Я не верю, что на данный момент кто-то может точно сказать, что именно сломалось, но мой предварительный анализ таков (пожалуйста, отнеситесь к следующему с недоверием, я не в команде Rust или команде LLVM, все, что я можно повозиться с компилятором и посмотреть на LLVM IR):

  • мы удалили проверки переполнения из одной строки в Layout::repeat() из стандартной библиотеки, что в конечном итоге привело к небезопасности памяти. Математически использование непроверенного добавления здесь должно быть совершенно безопасным - комментарий в этой функции (а также в Layout::pad_to_align() ) объясняет, почему;
  • мой образец кода, демонстрирующий проблему, даже не вызывает эту функцию, но явно использует Vec , который неявно использует Vec::reserve_internal() , который, в свою очередь, вызывает Layout::repeat() ;
  • Layout::repeat() помечено как #[inline] , и, по-видимому, единственное существенное различие между GOOD и BAD заключается в том, встроена ли эта функция в do_test() или не. Например, восстановление проверок переполнения запрещает встраивание и устраняет проблему; удаление атрибута #[inline] вызывает тот же эффект; отключение LTO отключает встраивание для библиотечных функций и снова решает проблему.

Если это правда (опять же, я не уверен на 100% в любом из вышеперечисленных), это означает, что какой-то мошеннический проход LLVM или комбинация проходов неверно оптимизируют IR после встраивания. Это то, что я сейчас пытаюсь исследовать, но, к сожалению, это непросто (по крайней мере, для нарушителя, не использующего LLVM-ICE, такого как я), потому что IR-различия между GOOD и BAD умеренные большой. Похоже, что в плохой версии отсутствует panic_bounds_check , но я до сих пор не совсем уверен, почему.

Кроме того, вдохновленный комментарием @tmiasko , я попытался скомпилировать rustc с разными версиями LLVM, и похоже, что LLVM 10rc1 устраняет проблему (последняя неисправная версия LLVM, которую я пробовал, - 9.0.1). Однако не похоже, что виноват проход Jump Threading , потому что я не вижу соответствующих коммитов между 9.0.1 и 10rc1.

Если возврат к изменению Layout::repeat() скрывает только симптом одного конкретного случая несвязанной ошибки, действительно ли возвращение является правильным?

Если возврат изменения Layout :: repeat () скрывает только симптом одного конкретного случая несвязанной ошибки, действительно ли возвращение является правильным?

Думаю, ничего страшного, если:

  • Изменение отправлено
  • Это значительно упрощает запуск ошибки, затрагивая многих пользователей.
  • Чтобы исправить это, потребуется много времени.

Если они сохранятся, я бы отменил изменение, отправил небольшой выпуск, чтобы разблокировать пользователей (все работало без изменений, хотя ошибка все еще была), а затем сосредоточусь на фактической ошибке.

Помню, как я это делал в другом компиляторе. Мы отменили изменение в ветке выпуска, но не в главной (что не является хорошей практикой, позже возникли проблемы), отправили новый второстепенный выпуск. Затем исправил фактическую ошибку.

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

Итак, вопрос в том, действительно ли легко вызвать эту ошибку? До сих пор у нас был один отчет с несколько сложным тестовым примером, в котором попытка дальнейшей минимизации (например, развертывание, казалось бы, тривиального цикла for _ in 0..1 ) не воспроизводится.

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

В любом случае, я действительно ценю проблемы, через которые прошел Layout::new() , но ИМО, возвращая его, _не_ правильное решение в этом случае. Мои рассуждения (в дополнение к тому, что сказал @SimonSapin ):

  • удаление проверок переполнения в Layout::repeat() позволяет LLVM встраивать Vec::reserve() в сборки выпуска. В некоторых случаях это может дать хороший прирост производительности (хотя, конечно, это следует измерить);
  • буквально предыдущая функция в libcore/alloc.rs ( Layout::pad_to_align() ) использует тот же шаблон непроверенного сложения с точно таким же комментарием, объясняющим, что делает это возможным. Восстановление проверок переполнения в Layout::repeat() но не в Layout::pad_to_align() кажется мне действительно странным;
  • если кто-то действительно заблокирован по этой проблеме (я определенно нет), существует множество других обходных решений, которые не связаны с изменениями stdlib (например, отключение ThinLTO, изменение уровня оптимизации, уменьшение количества единиц кодогенерации).

Может быть, локально добавить включенное в релиз защитное утверждение инварианта в качестве предварительного условия, чтобы он запаниковал конкретными деталями, чтобы выследить этот конкретный крайний случай или какой-то отладчик-фу? Я готов поспорить, что это неконтролируемый расчет, выполняемый при определенных условиях.

Затем, когда он будет обнаружен (где-то в LLVM, как я только что узнал, ty @dyfz), будет замечательно провести регрессионный тест, чтобы это больше не повторилось. 🙏

Воспроизведение LLVM IR: https://gist.github.com/comex/881074b1bcc545e299e65527c719eef4

Запустите opt bconfused.ll -scalar-evolution -loop-idiom -scalar-evolution -indvars -S -O3 -o - | grep xprint . Если внутри круглых скобок стоит i64 -1 , оптимизация произошла с ошибками. Если нет ... может и не было, но в этом трудно быть уверенным.

Похоже, это происходит из-за того, что LLVM неправильно добавляет nuw к add nuw i64 %x, -1 как часть этапа упрощения индукционной переменной. x - это аргумент функции, а nuw означает отсутствие беззнакового обтекания, поэтому это фактически утверждает, что аргумент равен 0, в той точке функции, где это не гарантируется.

Деление пополам (отредактируйте: от LLVM 9 до LLVM 10, что, по словам

commit 58e8c793d0e43150a6452e971a32d7407a8a7401
Author: Tim Northover <[email protected]>
Date:   Mon Sep 30 07:46:52 2019 +0000

    Revert "[SCEV] add no wrap flag for SCEVAddExpr."

    This reverts r366419 because the analysis performed is within the context of
    the loop and it's only valid to add wrapping flags to "global" expressions if
    they're always correct.

    llvm-svn: 373184

Выглядит многообещающе, поскольку r366419 (коммит, который отменяется вышеупомянутым коммитом) включен в ветку LLVM 9.0, которую использует Rust.

Порядок сортировки T-компилятора: P-medium, на основе следующего описания ситуации:

pnkfelix: Похоже, что оставшиеся рабочие элементы для # 69225 - это 1. исправить LLVM (либо путем выбора их 58e8c793d0e43150a6452e971a32d7407a8a7401, либо путем обновления до LLVM 10), а затем 2. прочитать PR # 67174.
pnkfelix: однако ни один из этих пунктов не кажется мне высокоприоритетным.
pnkfelix: По крайней мере, эта ошибка LLVM не кажется ни лучше, ни хуже, чем другие ошибки кодогенерации LLVM. Я думаю, это то, что только что сказал @simulacrum .

Обновление: выполняется попытка обновления до LLVM 10 в PR # 67759

Обновление 2: может быть неразумно слепо выбирать их фиксацию отката, поскольку мы предположительно по какой-то причине выбрали оригинал, и, таким образом, откат мог иметь непредвиденные последующие эффекты. По крайней мере, мы не должны пытаться это сделать, не понимая последствий (и, учитывая усилия по обновлению до LLVM 10, мы, вероятно, не должны пытаться выбрать вариант возврата вообще, так как это будет в значительной степени напрасной тратой усилий ...)

Была ли выбрана оригинальная фиксация? По крайней мере, из комментария @comex , который мне

Рассматриваемая фиксация - это очень локальное и небольшое изменение, которое добавляет один параметр к вызову функции и буквально говорит it is safe [in this case] to add SCEV::FlagNSW (и, судя по коду, новый параметр также может быть SCEV::FlagNUW ), поэтому я думаю, что, скорее всего, именно это и является причиной неправильной оптимизации. Я могу подтвердить, что удаление этого параметра (т.е. изменение (void)getAddRecExpr(getAddExpr(StartVal, Accum, Flags), Accum, L, Flags); на (void)getAddRecExpr(getAddExpr(StartVal, Accum), Accum, L, Flags); ) решает проблему.

Более того, эта проблемная фиксация была _не_ выбранной вишенкой. Это просто невезение - похоже, что откат произошел после создания 9.0.0, поэтому в восходящем потоке 9.0.0 все еще есть параметр оскорбления. По какой-то причине откат также не был перенесен на 9.0.1. 10.0.0-rc1 и более поздние версии имеют возврат.

Вот комментарий, который объясняет, почему на самом деле небезопасно добавлять сюда nsw или nuw . Вероятно, неплохо было бы поговорить об этом с разработчиком LLVM, но я думаю, что выбор варианта возврата исправит эту проблему и вообще не будет иметь никаких непредвиденных эффектов, поскольку он такой маленький и самодостаточный.

PS Огромное спасибо @comex за то, что он стал причиной этого. Отличная работа.

FWIW Я могу подтвердить, что https://github.com/llvm/llvm-project/commit/58e8c793d0e43150a6452e971a32d7407a8a7401 безопасен для выбора, это консервативное изменение. См. Также https://lists.llvm.org/pipermail/llvm-dev/2019-September/135195.html, если вас интересует более подробная информация о проблеме с флагами SCEV nowrap.

Думаю, я просто нашел способ воспроизвести проблему даже после возврата # 67174. Вот немного более длинная, но все же безопасная программа, которая надежно выходит из строя в Windows, Linux и macOS, используя последнюю версию nightly с # 67174, откат:

fn do_test(x: usize) {
    let mut arr = vec![vec![0u8; 3]];

    let mut z = vec![0];
    for arr_ref in arr.iter_mut() {
        for y in 0..x {
            for _ in 0..1 {
                z.reserve_exact(x);
                let iterator = std::iter::repeat(0).take(x);
                let mut cnt = 0;
                iterator.for_each(|_| {
                    z[0] = 0;
                    cnt += 1;
                });
                let a = y * x;
                let b = (y + 1) * x - 1;
                let slice = &mut arr_ref[a..b];
                slice[1 << 24] += 1;
            }
        }
    }
}

fn main() {
    do_test(1);
    do_test(2);
}

Windows:

PS> rustup run nightly rustc --version
rustc 1.43.0-nightly (6d0e58bff 2020-02-23)
PS> rustup run nightly cargo run --release
    Finished release [optimized] target(s) in 0.01s
     Running `target\release\rust-segfault.exe`
error: process didn't exit successfully: `target\release\rust-segfault.exe` (exit code: 0xc0000005, STATUS_ACCESS_VIOLATION)

Linux:

$ rustup run nightly rustc --version
rustc 1.43.0-nightly (6d0e58bff 2020-02-23)
$ rustup run nightly cargo run --release
    Finished release [optimized] target(s) in 1.13s
     Running `target/release/rust-segfault`
Segmentation fault (core dumped)

macOS:

λ rustup run nightly rustc --version
rustc 1.43.0-nightly (6d0e58bff 2020-02-23)
λ rustup run nightly cargo run --release
    Finished release [optimized] target(s) in 0.01s
     Running `target/release/rust-segfault`
[1]    24331 segmentation fault  rustup run nightly cargo run --release

Эта программа не зависит от количества Codegen единиц, поэтому в ошибку сегментации Playground, тоже (на стабильной, бета, и ночью). Я также воспроизвел это, скомпилировав rustc из мастера (с восстановленным # 67174), связанного с LLVM 9.

Основная ошибка LLVM остается той же, поэтому обновление до LLVM 10 или выбор исправления LLVM устраняет segfault.

Я действительно хотел бы лучше понять, что происходит. Похоже, что проверки границ опущены из-за дополнительных nuw , которые возникают из-за неправильно кэшированных значений SCEV (как и в программе C из связанного потока @nikic ). Но к тому времени, когда происходит плохая оптимизация, я с трудом могу распознать свою простую программу по слоям базовых блоков LLVM; Хуже того, любое, казалось бы, безоперационное изменение в исходном коде (например, удаление переменной cnt ) приводит к совершенно иному виду LLVM IR и устраняет проблему.

У меня сложилось впечатление, что 1.41.1 только что был доработан в # 69359 (неудачное время с моей стороны), поэтому на данный момент мало что можно сделать. По крайней мере, стоит обновить комментарий в Layout::repeat() более подробным объяснением проблемы LLVM? Если да, могу отправить пиар.

У меня сложилось впечатление, что 1.41.1 только что был доработан в # 69359 (неудачное время с моей стороны), поэтому на данный момент мало что можно сделать.

Если патч, который мы включили в 1.41.1, на самом деле не решает проблему, мы должны пересмотреть, хотим ли мы выполнить резервное копирование нового исправления и пересобрать выпуск. На собрании команды разработчиков был достигнут консенсус о том, чтобы не переносить исправление LLVM, но я лично думаю, что еще одно новое PoC может потребовать еще одного обсуждения этой темы.

cc @ Mark-Simulacrum @ rust-lang / release

@dfyz, мы попытаемся получить еще одну сборку 1.41.1 с исправлением LLVM с бэкпортированием, пока мы ждем консенсуса по поводу фактической поставки.

FWIW, для меня новый репродуктор работает как ожидалось ( index out of bounds ) на стабильной версии 1.38.0 и ранее, но не работает на 1.39.0 и новее. В LLVM между 1.38 и 1.39 нет большой разницы (https://github.com/rust-lang/llvm-project/compare/71fe7ec06b85f612fc0e4eb4134c7a7d0f23fac5...8adf9bdccfefb8d03f15e8db, сгенерировано любое по пути тоже.

новый репродуктор работает как положено (индекс вне пределов) на стабильной версии 1.38.0

Я (случайно) обнаружил, что установка -C codegen-units=1 в 1.38.0 воспроизводит segfault. 1.37.0 кажется мне безопасным (никакая комбинация опций, которые я пробовал, не приводит к сбою).

Не обращайте внимания, 1.37.0 использует LLVM 8.
Любопытно, что разница LLVM IR между 1.37.0 и 1.38.0 (с -C codegen-units=1 ) составляет всего одну строку:

- %71 = icmp eq {}* %70, null
+ %71 = icmp ule {}* %70, null

(где %70 получается из результата <core::slice::IterMut<T> as core::iter::traits::iterator::Iterator>::next() )

Одного этого достаточно, чтобы обмануть LLVM, добавив ужасную nuw к add nuw i64 %x, -1 .

1.37.0 кажется мне безопасным (никакая комбинация опций, которые я пробовал, не приводит к сбою).

Это использует LLVM 8, поэтому обвиняемого изменения SCEV вообще не должно существовать.

Это использует LLVM 8

Мой плохой, извините за путаницу (я был так счастлив свести его к однострочному различию, что даже не удосужился проверить версию LLVM).

Мы подготовили новые артефакты 1.41.1 с выбранным исправлением LLVM. Вы можете протестировать их локально с помощью:

RUSTUP_DIST_SERVER=https://dev-static.rust-lang.org rustup update stable

пинг в https://github.com/rust-lang/rust/issues/69225#issuecomment -586941455

[triagebot] Проблема была успешно решена без какого-либо участия команды составителей, прошедших проверку связи.
Бретти хорошо.

1.41.1 вышла, думаю, пора окончательно закрыть этот вопрос.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги