Как стать автором
Обновить

Комментарии 12

Так вот, команда ebreak занимает всего 2 байта.

Без разрешения сжатого (compressed) набора — 4 байта. Этим разрешением можно управлять на уровне ассемблера, поэтому, если не хочется заморачиваться, можно запрещать сжатые команды.


если не считать li, которая, согласно документации, разворачивается в "Myriad sequences"

В 32-битке — или 1, или 2 команды. В 64 — сложнее, нет готовой загрузки полной константы (или аналога movk из ARM), поэтому константы кладут рядом с кодом и грузят через смещение до 2GB.


Ну прикольно, что на RISC-V уже живые продаваемые контроллеры пошли… интересная конкуренция.

li всё же псевдо-команда, этакий встроенный в ассемблер макрос. разворачивается в 1 команду, если константа умещается в младшие 12 бит с расширением знака (от -2048 до +2047: получается addi xn,x0,immediate, где регистр x0 — всегда (аппаратно) имеет значение 0). если же в этот диапазон константа не влазит, то перед addi ставится специальная инструкция lui xn,immediate, которая грузит в старшую часть указанного регистра 20 бит, зануляя младшую. дальше идёт обычный addi xn,xn,imm.


таким же свойством обладает и псевдокоманда la, предназначенная прежде всего для загрузки относительного адреса (относительно PC). первая команда будет auipc xn,imm, которая такую же 20-битную команду в старшей части 32-битного слова прибавляет к pc и результат грузит в xn, после чего можно ставить addi.


сравнение с армом: в нём для загрузки произвольной константы есть 2 способа: первый это пара команд movw/movt, где первая грузит 16 бит в младшую часть регистра, а вторая — в старшую (не меняя младшую) или же команда вида ldr rn,[pc,#offset] (у арма PC — виден как r15, у risc-v — не виден как POH), а сама константа лежит неподалёку, обычно в промежутке между соседними процедурами. в случае же, если константа специального вида (короткая + варианты её сдвига по 32 битам), то её можно загрузить и командой mov rn,#immediate.


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

li всё же псевдо-команда

Ну да, вы развёрнуто расписали всё то, что я просто сказал в самом общем виде.


В случае ARM я вспоминал 64-битный. Там самый топорный, но надёжный метод загрузить произвольную 64-битную константу — 4 штуки movk (16 байт), или с вариациями типа первой movn или movz. (Можно и из памяти читать, конечно.) А вот RISC-V не даёт аналога такого в 64-битном режиме, поэтому там из общих методов эффективнее всего оказывается сначала через тот же метод, как описанный вами la (auipc+addi), загрузить адрес блока констант, а из него прочитать само значение.


Про li была хохма — авторы, кажется, адаптации линкера RISC-V в binutils жаловались: им нужно было при линковке выбирать, какую грузить константу в регистр, из набора степеней двойки (и только их). Так вот проблема — все кроме 2048 грузятся в одну команду (addi если меньше, lui если больше), а 2048 — надо 4096 через lui + -2048 через addi — меньше двух команд не получается :( поэтому какие-то генерируемые переходные чанки почти всегда содержали NOP.


таким же свойством обладает и псевдокоманда la

gcc, кстати, генерирует ассемблеру команды типа "ld a5, .LC0", где .LC0 это метка константы в памяти. После линкера это таки пара auipc + ld (по смещению), если было ".option pic" (из -fpic).

Некоторые технические решения показались странными, но не лишенными внутренней логики, как, например, отказ от стека

тут видимо надо уточнить, что у классических армов (всех 32-битных с архитектурой ARMv7-A и младше), а также и у ембеднутых (классических, например ARM7TDMI) стека нет точно так же. другое дело, что есть мощные команды записи/чтения регистров по маске c модификацией регистра-указателя (предекремент или постинкремент), что позволяет выбрать один регистр как 'софтовый' указатель стека. при этом при входе в обработчик прерывания или исключения автоматически на такой стек ничего не уходит, но зато некторые регистры (в т.ч. тот, в котором по соглашению находится этот 'софтовый' указатель стека) заменяются теневыми копиями, что позволяет сохранить контекст без порчи стека пользователя.


в архитектуре ARMv7-M (те самые cortex-m3, m4, m7) стек уже появился в явном виде: во-1 спецкоманды (в кодировке THUMB-2, которая тут осталась единственной, в то время как 'классические' процессоры работают в кодировке ARM, THUMB у них опциональная) без явного указания регистра-указателя стека, во-2 при входе в обработчик процессор аппаратно пушит (и при выходе — читает обратно) некоторые регистры. эти регистры, кстати, выбраны так, чтобы обработчик имел сигнатуру void HandlerName(void), без никаких __attribute__((interrupt)). но при этом указателей стека имеется 2 штуки — один для задач пользователя, другой для системы (куда, например, пушится контекст при входе в обработчики).


в данной реализации risc-v получается, что стек всегда софтовый и один, и никак нельзя отвязать стек пользователя от стека обработчиков (не занимая явно ещё один регистр под стек обработчика, конечно же).

тут видимо надо уточнить, что у классических армов, а также и у ембеднутых стека нет точно так же
Может и стоило бы, но я слишком плохо знаю arm чтобы это писать.
при входе в обработчик процессор аппаратно пушит (и при выходе — читает обратно) некоторые регистры. эти регистры, кстати, выбраны так, чтобы обработчик имел сигнатуру void HandlerName(void), без никаких __attribute__((interrupt)).
Да, это тоже красивое решение, как и аппаратная чехарда с регистрами при обработке нескольких прерываний последовательно.
Но в risc-v такое делать было нельзя, поскольку хотели оставить инструкции максимально простыми (по крайней мере я понял их логику так).
в данной реализации risc-v получается, что стек всегда софтовый и один, и никак нельзя отвязать стек пользователя от стека обработчиков (не занимая явно ещё один регистр под стек обработчика, конечно же).
Разделение прав доступа там тоже есть, хотя сделано по-другому. Вы правы, это интересный вопрос, думаю добавлю в статью.
Насколько я понял из документации, в risc-v используется сохранение sp в спецрегистр mscratchcsw:
csrrw sp, mscratchcsw, sp
что-то делаем
csrrw sp, mscratchcsw, sp

Точнее не в risc-v вообще, а именно в bumblebee, ядре моего контроллера
Насколько я понял из документации, в risc-v используется сохранение sp в спецрегистр mscratchcsw:

Hу ок, проблему незапорчивания юзерского стека это решает.

А вот тут стало интересно. Если харт находится в M-mode, а не в U или S, то замена стека, наоборот, не должна происходить. Как это решается в данной реализации? Вообще новое прерывание не допускается, или разделяются обработчики?

Насколько я понял из описания, там то ли команда csrrw проверяет режим, то ли сам доступ к регистру так хитро устроен. Точно сказать не могу, разделение прав доступа не ковырял.
В документации приведен такой псевдокод:
csrrw rd, mscratchcsw, rs1
// Pseudocode operation.
if (mcause.mpp!=M-mode) then {
  t = rs1; rd = mscratch; mscratch = t;
} else {
  rd = rs1; // mscratch unchanged.
}
// Usual use: csrrw sp, mscratchcsw, sp

Скорее там логика, привязанная к регистру. В оригинальной доке mscratchcsw не описан, а тут используется достаточно красивый трюк избежать необходимости как-то постоянно поддерживать актуальное значение сохранённого регистра :)

Что считается «оригинальной докой»? Я его видел в документации на ядро bumblebee
Естественно, это не единственное решение. В других ядрах могут заводить просто ядерные спец регистры общего назначения. Собственно, в «компьютерном» применении скорость реакции на прерывания не так критична, там можно и правда обойтись одним-двумя лишними регистрами без особого «мозга» и с ручной проверкой режима.
Что считается «оригинальной докой»? Я его видел в документации на ядро bumblebee

Я назвал оригинальной то, что на riscv.org. Там постепенно, но неуклонно стандартизуют реализацию системных плюшек, и такого регистра (пока?) нет. Есть просто mscratch, он без подобных зависимостей от режима.


В других ядрах могут заводить просто ядерные спец регистры общего назначения.

Видимо, так и делают. На единственном mscratch можно реализовать, но таки неудобно, слишком много перегонок.
Я собственно восхитился решением, что они сделали :)

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации