RISC-V: автор эссе критикует архитектуру набора команд

На персональном техническом блоге Dmitry.GR (dmitry.gr) вышло эссе «RISC-V: They Should Have Known Better» («RISC-V: они должны были знать лучше»), развёрнутый и жёстко написанный разбор архитектуры набора команд (ISA) RISC-V. Отправная точка автора: ни одна архитектура не может быть одинаково хороша везде, а сторонники RISC-V обещают ей одновременно господство и в суперкомпьютерах, и в дешёвых микроконтроллерах, и во всём, что между ними, это физически невозможно, поскольку требования к процессору для мощного сервера и для копеечного встраиваемого ядра прямо противоположны. Дальше текст разбирает оба полюса по отдельности и показывает, что действующая спецификация RISC-V проигрывает существующим конкурентам в обоих случаях, не из-за микроархитектуры конкретных чипов, а из-за решений, заложенных в саму архитектуру набора команд.

Для дешёвых встраиваемых ядер (микроконтроллеры для SD-карт, USB-флешек, MP3-плееров и подобной техники) главное, низкая задержка прерываний и компактный код, а не производительность вычислений. Эссе приводит ручной подсчёт накладных расходов на обработку одного прерывания: у RISC-V (RV32I с обязательным для этого расширением Zicsr), не меньше 44 тактов на сохранение и восстановление регистров вокруг вызова обработчика на C, у урезанного по числу регистров варианта RV32E, 38 тактов; для сравнения, у конкурирующего дешёвого ядра Cortex-M0 (ARM), всего 27 тактов (15 на вход в прерывание и 12 на выход), потому что оно сохраняет регистры аппаратно, и обработчик сразу пишется на C без ассемблерной обвязки. Существование проприетарных «быстрых» IRQ-расширений вроде CLIC у разных производителей автор считает отдельным приговором архитектуре: раз базовая спецификация вынуждает вендоров изобретать нестандартный кремний, чтобы просто сравняться по скорости с Cortex-M0 десятилетней давности, это ещё больше дробит и без того нестрогий «стандарт». Важная оговорка: это ручной «оптимистичный» подсчёт по описанию системы команд, а не результат измерений на реальном кремнии или в выводе конкретного компилятора. Сжатые (16-битные) инструкции RISC-V, которые должны экономить память, спроектированы, как отмечает автор, до смешного плохо: инструкция сохранения байта по адресу «регистр + смещение» кодирует смещение только в диапазоне от 0 до 3 (не 33, не 303, именно три), для полуслова диапазон ещё хуже, 0 или 2, и лишь для целого слова диапазон вменяемый, 0, 124. У Cortex-M0 для сравнения, 0, 31 для байта, 0, 62 для полуслова, 0, 124 для слова. Хуже того, узкие инструкции сохранения байта и полуслова вообще не входят в базовое сжатое расширение C, они вынесены в отдельное расширение Zcb, которое реализовано не везде.

Для серверных и вообще высокопроизводительных ядер, наоборот, важна не плотность кода, а способность как можно быстрее декодировать сразу много инструкций параллельно, современные ядра с внеочередным исполнением декодируют восемь-десять инструкций за такт. Именно поэтому, по логике эссе, aarch64 и MIPS выбрали фиксированную длину инструкции в 4 байта, а сжатые инструкции переменной длины (вроде ARM Thumb или microMIPS) в высокопроизводительных ядрах не прижились: автор ссылается на то, что при совместной разработке aarch64 моделирование и тесты инженеров Apple и ARM показали, что сжатые инструкции проигрывают по показателям «инструкций на ватт» и «инструкций в секунду». У RISC-V же в базовой спецификации нет режима адресации «регистр плюс регистр со сдвигом», который есть у x86 ([ebx + esi*4]) и ARM ([R0, R1, LSL #2]), из-за этого единичное обращение к элементу массива на RISC-V требует трёх отдельных инструкций (сдвиг, сложение, обращение к памяти) вместо одной. Расширение Zba, добавляющее слитную инструкцию «сдвиг+сложение» (SHxADD) и сокращающее это до двух инструкций, ратифицировали только в 2021 году, больше чем через два года после базовой спецификации, и даже с ним обращение к массиву занимает 6, 8 байт кода против 4 байт на ARM, то есть решение всё равно проигрывает по плотности, которой так гордится сжатый набор команд RISC-V. По подсчётам эссе, сжатые инструкции сейчас занимают 3/4 всего 32-битного кодового пространства RISC-V; компания Qualcomm, по утверждению автора, предлагала и даже прототипировала переиспользование части этого пространства под более удобные режимы адресации, но предложение, как пишет автор, «никуда не пошло».

Центральная претензия эссе, тотальная опциональность RISC-V: необязательны умножение, деление, режим пользователя, режим супервизора, сами CSR (регистры состояния и управления) и даже сжатые инструкции; оба режима векторизации прерываний, «прямой» и «векторный», тоже опциональны, и спецификация не делает обязательным ни один из них. Автор сравнивает это с USB-C и RCS, где формальное «соответствие стандарту» ничего не гарантирует по факту поддерживаемых возможностей. Хуже того, единственный штатный способ узнать, какие опциональные возможности реализованы в конкретном чипе, регистр misa, сам зависит от опционального расширения Zicsr, а если Zicsr всё же реализован, спецификация всё равно разрешает misa читаться как сплошные нули, то есть быть бесполезным; вдобавок misa доступен только в наивысшем, машинном режиме привилегий и не виден коду пользователя или супервизора. Для сравнения, продолжает автор: на x86 есть CPUID, а на ARM, регистры вроде ID_AA64PFR0_EL1, доступные как минимум ядру ОС, и в обеих архитектурах опциональными оставляют либо специализированные вещи для ручного ассемблера в узких сценариях, либо расширения, безопасно исполняющиеся как NOP на процессорах без их поддержки, но никогда сами базовые вещи вроде умножения, деления или адресации, от которых зависит любой обычный скомпилированный код на C. Даже системный таймер, нужный практически любой ОС, не подключён через CSR, а отображён на память по адресу, который спецификация оставляет «на усмотрение реализации», то есть он может быть где угодно. Автор отдельно разбирает и отвергает частый контраргумент про необходимость виртуализации со ссылкой на классическую работу Попека и Голдберга: по прочтению автора, сама эта работа называет перехват абсолютно каждой инструкции «теоретически интересным, но непрактичным» решением, а значит ссылаться на неё, оправдывая архитектурные решения, принятые на этапе проектирования (в отличие от виртуализации на x86, добавленной постфактум), некорректно, x86 и ARM решают задачу сокрытия возможностей от гостевой системы, не делая сам регистр возможностей недоступным в принципе.

Ещё одна претензия эссе, отсутствие в базовом наборе очевидно полезных инструкций. Инструкции «проверить бит и перейти по нему» в RISC-V нет вовсе, хотя она заменяет собой две инструкции и не требует временного регистра; чтобы оценить, насколько это частая операция, автор дизассемблирует ядро Raspbian для aarch64 (файл vmlinuz-6.1.0-49-arm64) и насчитывает 35 393 инструкции TBZ/TBNZ (проверка бита с переходом) против 70 109 инструкций RET, то есть, по оценке автора, в среднем у каждой второй функции есть операция, которая могла бы использовать переход по биту. Похожая картина с вставкой и извлечением битовых полей (полезно для работы с сетевыми пакетами и регистрами устройств): 6284 и 8881 вхождений соответственно в том же образе ядра, то есть, по той же оценке, такие операции в среднем есть примерно в двух функциях из девяти; при этом официальное битовое расширение RISC-V (Zbs) умеет работать только с одним битом за раз и не даёт вставки или извлечения битовых полей целиком. Отдельная головная боль, кодирование самих инструкций: значения констант внутри инструкции разбросаны по битам без единой логики, а для 16-битных сжатых инструкций эссе насчитывает не меньше 9 разных форматов кодирования в базовом расширении C и ещё 8, в расширении Zcb, то есть почти отдельный формат на каждую инструкцию, тогда как у MIPS и ARM таких форматов на порядок меньше. Хуже всего, по мнению автора, то, что один и тот же 16-битный код операции может означать разные, взаимоисключающие инструкции в зависимости от того, какие опциональные расширения реализованы в конкретном ядре, например, код 0xA002 в зависимости от чипа означает либо сохранение регистра двойной точности на стек (C.FSDSP), либо безусловный переход (CM.JT). В качестве иллюстрации эссе описывает гипотетический сценарий (сам текст обозначает его именно так, а не как реальный случай): бинарный блок калибровки датчика, включённый в прошивку для одного микроконтроллера с расширением Zcmp, после перехода на более крупный МК с поддержкой чисел с плавающей точкой двойной точности (расширение D, несовместимое с Zcmp) начинает случайно портить регистры и стек без единого исключения о неверной инструкции, просто потому, что одна и та же последовательность бит на новом чипе означает другую операцию.

По поводу фрагментации эссе разбирает решение, которое предложил сам RISC-V Foundation, концепцию «профилей»: списки расширений, обязательные для совместимости с конкретным профилем вроде RVA23, который планируют требовать Ubuntu, Red Hat и Android (AOSP). Проблема в том, что среди актуальных и рекомендуемых на момент публикации одноплатных компьютеров на RISC-V, по утверждению автора, фактически ни один не соответствует RVA23 по-настоящему, ни StarFive VisionFive 2, ни Banana Pi BPI-F3, ни Lichee Pi 4A, ни Orange Pi RV2, ни даже HiFive Premier P550, а значит ни один из них никогда не запустит будущие сборки Ubuntu LTS или AOSP. Автор иронично замечает, что единственный дистрибутив Linux, идеально приспособленный к такому фрагментированному ландшафту, Gentoo, который по своей природе собирает каждый пакет из исходников под конкретные особенности процессора. Причину произошедшего эссе видит в собственном документе-обосновании (rationale) RISC-V, где авторы стандарта прямо признают, что альтернативная архитектура OpenRISC устраивала их почти во всём, кроме слотов задержки после ветвления (delay slots), и даже несмотря на существование варианта OpenRISC без них, разработчики RISC-V всё равно предпочли начать с нуля. При этом вывод эссе не апокалиптический: RISC-V не обречён. По прогнозу автора, RISC-V со временем займёт нишу дешёвых одноразовых микроконтроллеров, вытеснив устаревшее семейство 8051, но не благодаря качеству архитектуры, а вопреки ему, просто за счёт цены и бесплатных лицензий на готовые ядра; по той же причине, цена низкая, а производительность самого ядра для задачи не критична, RISC-V, как ожидает автор, займёт место вспомогательных управляющих ядер при ИИ-ускорителях, где основную работу выполняют специализированные блоки для матричных вычислений. А вот всерьёз конкурировать с ARM в верхнем сегменте, десктопах и производительных одноплатных компьютерах, RISC-V, по мнению автора, не сможет: открытость спецификации сама по себе не создаёт хорошо спроектированное ядро, а если бы кто-то такое ядро всё же спроектировал, бесплатно раздавать его тоже не стал бы.

Ключевые факты

  • У RV32I с расширением Zicsr обработка одного прерывания стоит не меньше 44 тактов (у RV32E, 38) против 27 тактов у конкурирующего Cortex-M0, так эссе оценивает вручную, оговариваясь, что это оптимистичная прикидка, а не измерение на реальном кремнии.
  • Сжатая инструкция сохранения байта кодирует смещение только от 0 до 3 (у Cortex-M0, от 0 до 31); в базовой спецификации RISC-V вообще нет режима адресации «регистр + сдвинутый регистр», поэтому обращение к элементу массива требует трёх инструкций вместо одной, а частичное исправление, расширение Zba, ратифицировали только в 2021 году, больше чем через два года после базовой спецификации.
  • Почти всё в RISC-V опционально, умножение, деление, режимы привилегий, CSR, сжатые инструкции, а даже регистр misa, который должен сообщать, какие возможности реализованы, сам зависит от опционального расширения Zicsr и по спецификации разрешён читаться как сплошные нули.
  • Один и тот же 16-битный код операции может означать разные, несовместимые инструкции в зависимости от набора реализованных на чипе расширений (пример из эссе, D и Zcmp); автор приводит гипотетический сценарий, где это приводит к необъяснимой порче регистров после перехода на более крупный микроконтроллер.
  • На момент публикации эссе, по утверждению автора, ни одна из ходовых плат на RISC-V, StarFive VisionFive 2, Banana Pi BPI-F3, Lichee Pi 4A, Orange Pi RV2, HiFive Premier P550, не соответствует профилю RVA23, который Ubuntu, Red Hat и Android планируют сделать обязательным. При этом вывод эссе не апокалиптический: RISC-V со временем всё равно займёт нишу дешёвых микроконтроллеров и вспомогательных ядер при ИИ-ускорителях, но за счёт цены, а не качества дизайна.

Почему это важно

RISC-V, открытая архитектура набора команд, вокруг которой за последние годы выросла целая индустрия: от дешёвых микроконтроллеров до амбиций закрепиться в серверах и десктопах, с прицелом на профили вроде RVA23, которые Ubuntu, Red Hat и Android планируют сделать минимальным требованием для будущих сборок. На этом фоне регулярно звучит маркетинговый тезис «RISC-V победит везде». Судя по методу, за эссе стоит глубокое практическое знакомство с материалом: в тексте вручную считаются такты обработки прерывания, дизассемблируется реальный aarch64-образ ядра для оценки частоты типовых операций, разбираются формулировки самой спецификации и её пояснительного документа. Этому тезису эссе противопоставляет подробный, проверяемый разбор: архитектура систематически проигрывает конкурентам и на дешёвом, и на дорогом конце рынка, а её ключевая особенность, тотальная опциональность почти всех возможностей, создаёт проблемы совместимости, которые сама экосистема (профили вроде RVA23) пытается закрыть постфактум и пока не закрывает.

Кому это важно

В первую очередь это касается инженеров, которые прямо сейчас выбирают ядро для встраиваемого проекта: по расчётам автора, для типичной задачи с прерываниями Cortex-M0 обходится дешевле RISC-V по тактам, и это стоит учитывать при выборе между стоимостью лицензии и производительностью. Дальше, разработчиков прошивок, ядра ОС, гипервизоров и инструментов сборки для RISC-V, которым приходится писать код, работающий при любом сочетании опциональных расширений, или явно закладываться на конкретный профиль вроде RVA23. Отдельно это касается дистрибутивов и платформ, которые как раз сейчас привязывают минимальные требования к RVA23, Ubuntu, Red Hat, Android/AOSP, и тех, кто выбирает готовые одноплатные компьютеры на RISC-V и рассчитывает на будущую поддержку этих ОС. И конечно, это важно для более широкой дискуссии о том, где у RISC-V реально есть шанс на успех (дешёвые встраиваемые ядра, вспомогательные ядра при ИИ-ускорителях), а где нет (десктопы, серверы, всё, что конкурирует по чистой производительности с ARM).

Как это применить

Если проект, дешёвое встраиваемое ядро с жёсткими требованиями к задержке прерываний, по расчётам автора Cortex-M0 сейчас выигрывает у стандартного RV32I+Zicsr и даже у урезанного RV32E, если не использовать нестандартные вендорские расширения (вроде CLIC) для ускорения обработки прерываний, при выборе платформы это стоит проверить отдельно, а не полагаться на общий тезис о «компактности» RISC-V. Если пишете прошивку или ядро ОС под несколько разных RISC-V-чипов, нельзя закладываться вообще ни на одно расширение, включая умножение, деление, режимы привилегий и режим векторизации прерываний, надёжного единого способа проверки возможностей вроде CPUID на x86 у RISC-V нет, есть только цепочка обходных проверок через отдельные CSR, которая тоже может не сработать. Если решение по проекту опирается на будущую поддержку RVA23 (например, планы Ubuntu, Red Hat или Android), стоит явно сверить, что конкретная целевая плата сертифицирована по этому профилю: по утверждению автора, ни одна из широко продаваемых сейчас плат, StarFive VisionFive 2, Banana Pi BPI-F3, Lichee Pi 4A, Orange Pi RV2, HiFive Premier P550, на момент публикации эссе таковой не является. А если в проекте на разные поколения чипов используется один и тот же бинарный блок (например, калибровочный код от поставщика датчика), стоит явно проверить, какие потенциально конфликтующие по кодировке расширения (в эссе это пример D и Zcmp) включены на каждой целевой плате.

Можно ли доверять

Это личное, подчёркнуто субъективное эссе на персональном блоге: во вступлении отдельно оговорено, что все мнения в тексте принадлежат лично автору, а сам материал написан в жёсткой, местами нарочито грубой манере, как реплика в споре со сторонниками RISC-V, а не нейтральный обзор или документ RISC-V Foundation. При этом ключевые утверждения подкреплены проверяемой конкретикой: дословными цитатами из спецификации и пояснительного документа RISC-V и результатами дизассемблирования одного реального aarch64-образа ядра (Raspbian, vmlinuz-6.1.0-49-arm64), методику можно повторить самостоятельно. Важная оговорка: цифры по тактам обработки прерывания (44 / 38 / 27), это ручной подсчёт по описанию системы команд, а не результат измерений на реальном железе или в выводе конкретного компилятора; сам текст называет его «оптимистичным» и явно обозначает как прикидку, а не измерение. Подсчёт частоты операций проверки бита и операций с битовыми полями (35 393 / 70 109 / 6284 / 8881 инструкций) тоже сделан на одном-единственном aarch64-бинарнике и иллюстрирует, насколько часто такие операции нужны в реальном коде вообще, это не измерение конкретно для RISC-V и не статистика по множеству программ. И ещё: для x86 и ARM в тексте используются проверяемые факты (CPUID, регистры ID_AA64PFR0_EL1/ID_AA64ISAR0_EL1), а вот причины, по которым конкретное предложение Qualcomm по переработке кодирования «никуда не пошло», в источнике не раскрыты, домысливать эту часть не стоит.

Риски и подводные камни

Главный риск при чтении, принять эмоциональный, местами нарочито едкий тон эссе за нейтральную техническую документацию: это личная позиция одного разработчика, написанная как ответ сторонникам RISC-V, а не согласованная позиция RISC-V Foundation и не академическая работа с рецензированием. Сценарий про бинарный блок калибровки датчика, который после перехода на более крупный микроконтроллер начинает случайно портить регистры из-за конфликта кодировок Zcmp и D, сам текст эссе вводит как гипотетический пример («представим, что...»), а не как задокументированный реальный инцидент на конкретном продукте, стоит воспринимать его как иллюстрацию механизма, а не как факт о случившемся сбое. Часть выводов, например, о причинах появления RVA23-профилей или о роли академических грантов в развитии RISC-V, в эссе прямо помечена как предположение, а не как установленный факт. Наконец, конкретные факты в эссе актуальны на момент его публикации: список несовместимых с RVA23 плат и состав экосистемы RISC-V со временем могут измениться и разойтись с текущим положением дел.

«В конечном счёте мы посчитали, что для наших целей лучше начать с чистого листа, чем соответствующим образом дорабатывать OpenRISC.»

— документ-обоснование (rationale) RISC-V, процитирован в эссе Dmitry.GR