flang компилятор доказывает, что программа не зациклится 0.6.2 GitHub

В WebAssembly нет сторожевой страницы, поэтому заниженная константа стека портит память молча

Наш рантайм проверяет глубину вызовов по числу «сколько у меня стека». На неизвестной платформе он берёт FL_STACK_ROOM_FALLBACK — 1 МиБ, занижение намеренное и разумное. В WebAssembly это число оказывается завышенным: wasm-ld по умолчанию отводит теневой стек 64 КиБ, в шестнадцать раз меньше.

В обычной системе такая ошибка кончилась бы падением на сторожевой странице — неприятно, но честно. В WebAssembly сторожевой страницы нет: теневой стек просто заезжает в кучу и тихо портит данные. Программа не падает и не отказывает — она отвечает неправдой.

Отсюда общее правило: обещание «завершится ИЛИ откажет честно» держится ровно до тех пор, пока число, по которому считает проверка, не врёт про настоящую машину. Проверка, опирающаяся на константу сборки, обязана эту константу и получать — флаг линковки ей ничего не сообщает.

Чем подтверждено. Ветка work/wasm-cherez-c, отчёт docs/wasm-via-c.md. Компилятор flang, собранный в wasm со стеком по умолчанию, на файле в 3000 символов отвечал FLANG_TYPE: «соединить» допустимо только для строк, получено строка и ничто — то есть выдуманной диагностикой — там, где обычная сборка разбирает файл без замечаний. На корпусе (scripts/wasm-compare.mjs без ключей) это роняло 2 программы из 94: 022-generate-parentheses с memory access out of bounds, 076-minimum-window-substring с function signature mismatch в fflush — обе жертвы порчи, а не переполнения как такового. С -Wl,-z,stack-size=1048576 корпус проходит целиком: 94 программы, 8799 точек, расхождений 0.

Что одного флага мало — тоже измерено: с -Wl,-z,stack-size от 1 до 256 МиБ без -DFL_STACK_ROOM_FALLBACK компилятор одинаково отказывал «исчерпала стек хозяина на глубине 71», потому что 71 кадр — это и есть 1 МиБ по его расчёту. Память была, а знания о ней не было.

Чем ограничено. Глубину ограничивает не только модуль, но и среда, и модуль её предела не видит вовсе: wasmtime по умолчанию даёт 512 КиБ и обрывает рекурсию раньше обычной сборки — wasm trap: call stack exhausted на 8000 элементах. Тут он ведёт себя честнее Node: даёт ловушку со стеком вызовов, а не порчу. Совпадение восстанавливается при -W max-wasm-stack=8388608. То есть чисел надо согласовать три, а не два: стек линкера, константу рантайма и предел среды. Рекомендованное значение — 8 МиБ, и оно не круглое: это FL_STACK_MIN, столько же берёт обычная сборка.

Связано: wasm-via-c-is-free, the-instrument-lied-not-the-subject