#c #assembly #riscv
Вопрос:
У меня есть это упражнение
Переведите следующий код C в код сборки RISC-V. Предположим, что значения a, b, i и j находятся в регистрах x5, x6, x7 и x29 соответственно. Кроме того, предположим, что регистр x10 содержит базовый адрес массива D.
for(i=0; i<a; i ){
for(j=0; j<b; j ){
D[4*j] = i j;
}
}
У меня также есть решение с комментариями
Loop1:
addi x7, x0, 0 // i = 0
bge x7, x5, ENDi // while i < a
addi x30, x10, 0 // x30 = amp;D[0]
addi x29, x0, 0 // j = 0
Loop2:
bge x29, x6, ENDj // while j < b
add x31, x7, x29 // x31 = i j
sd x31, 0(x30) // D[4*j] = x31
addi x30, x30, 32 // x30 = amp;D[4*(j 1)]
addi x29, x29, 1 // j
jal x0, LOOP2
ENDj:
addi x7, x7, 1 // i
jal x0, LOOP1
ENDi:
Чего я не понимаю
sd x31, 0(x30) // D[4*j] = x31
addi x30, x30, 32 // x30 = amp;D[4*(j 1)]
Не sd x31, 0(x30)
означает ли это, что я сохраняю значение x31 в 0-м бите массива 30? Откуда вдруг взялся 4*j?
И не addi x30, x30, 32
означает ли это, что x30 = x30 32? И разве x30 = amp;D[0] не было определено в первом цикле? Как j внезапно вступает в контакт с x30?
Ответ №1:
Это эквивалентно следующему:
for(int64_t i=0; i<a; i ){
int64_t* x30 = amp;D[0];
for(int64_t j=0; j<b; j ){
*x30 = i j;
x30 = 4; // increment by 4 elements
}
}
x30 используется в качестве «текущего указателя» в цикле. Он указывает на соответствующий элемент в массиве D. Он инициализируется первым элементом и увеличивается на 4 шага по 4 элемента, что эмулирует часть 4*j. Поскольку ваши элементы 64-разрядные, 8 байт, для увеличения на 4 элемента базовый адрес указателя должен быть увеличен на 4*8=32 байта.
Часто более эффективно поддерживать текущий указатель, чем каждый раз его пересчитывать, особенно в RISC-V, потому что у вас не может быть смещения регистра (его нет str x31, [x30, x29]
).
Комментарии:
1. Спасибо за ответ, но я все еще в замешательстве. Можете ли вы уточнить, что делает этот код
sd x31, 0(x30)
? Потому что я не вижу связи между кодом и комментарием в решении.2. @Carl
sd x31, 0(x30)
буквально хранит x31 по адресу в x30, поэтому*x30 = x31
в C сохраняйте элемент, на который указывает x30, что эквивалентноx30[0] = x31
(* имеет больше смысла для указателей и [] для массивов).3. @Carl x30 увеличивается одновременно с j, чтобы прогрессировать в массиве. При использовании j доступы будут
D[0]
,D[4]
,D[8]
… Индекс к D увеличивается на 4 элемента в каждом цикле, поэтому наличие x30 начинается с amp;D, и увеличение на 4 элемента в каждом цикле эквивалентно. В основном, это деланиеD[0]; D =4
,D[0]; D =4
,D[0]; D =4
. При увеличенииx30
, которое первоначально указывало на D[0], вы указываете на следующие элементы, поэтомуx30 = amp;D[0]
соx30 = 4
средствами x30 указывает на D[4].4. @Carl, если D-массив , который вы на самом деле не можете сделать
D =4
, но в C (и сборке) почти нет разницы между массивами и указателями. Если вместо этого вы используете указатель, как вint64_t* x30 = amp;D[0];
, то x30 указывает на D[0], и выполнениеx30 = 4;
каждого цикла позволит вам получить доступD[0]
,D[4]
,D[8]
… Каждый элемент массива является последовательным в памяти. Таким образом, если вы возьмете адрес/указатель на первый элемент, а затем увеличите его, это позволит вам получить доступ к следующим элементам массива.5. @Carl: Оптимизация
D[j*4]
вptr =4
связана с «уменьшением прочности». en.wikipedia.org/wiki/Strength_reduction . Вы заменяете вычисление генерации сдвига / добавления адреса в цикле только приращением указателя. Это еще более важно, если бы множитель не был простой степенью 2 (потому что сдвиги все еще относительно дешевы, т. Е. Не настолько «сильны»), но это все еще случай сокращения нескольких операций до одной.