Разработчик рефакторил парсер своего JavaScript-движка Joe на Rust. У него была стандартная функция consume_test: она проверяла, совпадает ли текущий токен с ожидаемым, и если да — сдвигала лексер на единицу. Обычно это пишут через if. Но автор решил избавиться от ветвления: он вычислял результат сравнения в x, прибавлял x as u32 к счётчику, и возвращал x. Ассемблер выглядел компактнее — меньше инструкций, нет условного перехода. Красота.
Потом он попробовал распарсить простой for (var lol; false; false) {} и получил ошибку. Диагностика E079, токен Var на позиции 5. Парсер перестал понимать конструкцию, которую раньше разбирал без проблем. Разработчик переписал сложение на явный if x { 1 } else { 0 } — и всё заработало. Он точно знает, что делает bool as u32, так что причина явно не в коде. Тут любой программист скажет: «Это не я, это компилятор».
Он полез смотреть ассемблер своей функции ForOrInOfStatement. Сборка кастомная — свой билд-система на TypeScript под Deno, без cargo. В дампе он увидел, что после сравнения с TokenKind::LParen код действительно уходит в consume_test, но... инкремента esi (это регистр для self.0) нет. Вообще. В версии с явным if прибавление единицы было, а с x as u32 — пропало. Компилятор просто выкинул сложение.
Он собрал минимальный воспроизводимый пример на nightly 1.94.0 — баг подтвердился. С флагом -Cno-prepopulate-passes в LLVM IR нашлась запись в неинициализированную память. С -Zmir-opt-level=0 проблема исчезла — значит, виноват LLVM, а не MIR. Автор открыл issue с пометкой «Suspected miscompilation» — на случай, если он ошибся и опозорится.
Проснулся через несколько часов — а на тикет уже пришёл пулл-реквест от Hanna Kruppe с фиксом. Issue повесили метки p-critical и i-miscompile. За всю историю Rust таких багов — всего семь из 61 тысячи открытых тикетов. Это страшный класс ошибок: компилятор молча ломает контракт языка, и безопасный код перестаёт быть безопасным. За 18 часов сообщество отреагировало, нашло причину и влило исправление. Автор благодарит tmiasko и hanna-kruppe за скорость. А сам теперь может рассказывать, как однажды победил rustc.