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

SHA-256 без единой битовой операции стоит 925 195 шагов интерпретатора на блок, и это уложилось в предел только после трёх названных правок

Битовых операций в языке нет вовсе: среди восемнадцати встроенных форм (flang/src/builtins.mjs) нет ни «и», ни «или», ни «исключающего или», ни сдвига. Хеш пришлось считать арифметикой: бит k числа — это ((х остаток от 2^(k+1)) минус (х остаток от 2^k)) делить на 2^k, а исключающее или БИТА — (бит первого плюс бит второго) остаток от 2.

Главное решение — считать ПОРАЗРЯДНО, а не побитово. Наивный перевод формул FIPS 180-4 дал бы по свёртке на каждую пару операндов, то есть восемь свёрток в раунде. Их четыре, и разницу дали три наблюдения:

Ещё одна мелочь, которая заметно дешевле очевидной записи: вклад разряда можно не собирать умножением. (х остаток от 2вес) минус (х остаток от вес) равно либо нулю, либо самому весу — то есть это УЖЕ вклад, и делить на вес, чтобы потом умножить обратно, незачем. Отсюда «исключающее или трёх» на разряде — это сумма трёх вкладов остаток от (вес умножить на 2), а «большинство» — (сумма минус (сумма остаток от (вес умножить на 2))) делить на 2. Ни одного условия, ни одного деления на вес.

Чем подтверждено, числом. Двоичный поиск по flang run --max-steps на входе [97, 98, 99] (одно сообщение = один блок), ветка work/bayty:

редакцияшагов на блок
«Бит» отдельной функцией, три вызова на виток1 394 531
извлечение бита внесено в тело свёртки983 593
плюс снятые лишние сведения к слову и сторож на степень двойки925 195

Предел интерпретатора по умолчанию — 1 000 000 шагов, и у flang check ключа --max-steps нет. То есть первая редакция официальный вектор примером нести не могла, а третья несёт: «Хеш байтов» от пустого сообщения и от abc стоят примерами и гоняются при каждой проверке.

Два пути измерены и отвергнуты.

  1. Повторять подвыражение вместо пусть внутри свёртки — дороже. Замена двух пусть на трижды повторённое (вес плюс вес) дала 1 057 226 шагов против 983 593. То есть связывание имени в теле свёртки стоит заметно меньше, чем лишнее вычисление, и «убрать переменную ради скорости» здесь неверно.
  2. Обрабатывать по два разряда за виток — почти ничего не даёт. Свёртка по 16 весам с двумя разрядами на виток: 963 281 против 983 593, то есть 2 %. Значит цена свёртки почти вся в ЧИСЛЕ УЗЛОВ тела, а не в накладных расходах витка, и укрупнять шаг бессмысленно — надо сокращать выражение.

Чем ограничено. Замер сделан на интерпретаторе. Напечатанный в JavaScript модуль считает миллион байт за 24 секунды — это в сотни раз быстрее интерпретатора и всё равно на три порядка медленнее node:crypto. Хеш на flang пишется ради ВОСПРОИЗВОДИМОСТИ формулы двумя сторонами (сторона на C99 у двоичного компилятора), а не ради скорости; там, где нужна скорость, зовут рантайм.

Связано: a-module-address-is-the-sha256-of-its-source, the-interpreter-step-limit-decides-what-can-be-an-example, bottleneck-moved-to-body-shape