Мне нужно преобразовать эту функцию C ++ в сборку MIPS:
int set(int a[], int n, int v)
{
int i;
i = 0;
do {
a[i++] = v;
} while ( i < n);
return i;
}
где адрес массива находится в $a0
n находится в $a1
и v находится в $a2
,
Это моя попытка написать функцию в MIPS, но я получаю «инструкции ссылаются на неопределенный символ в 0x00400054». в основном (предоставлено моим профессором) есть звонок jal set
который должен называть set
Я уверен, что моя ошибка как-то связана с этим. Я также не знаю, успешно ли я преобразовал функцию. Это моя попытка:
.text
set:
li $t0, 0
addi $t0, $t0, 0
sll $t1, $t0, 2
add $t1, $t1, $a0
L1: lw $t1, 4($t1)
sw $a2, 0($t1)
blt $t0, $a1, L1
Я использую QTSPIM, если это вообще имеет значение. Я ценю любую помощь, и если у вас есть какие-либо советы по программированию MIPS, это тоже было бы здорово.
ОБНОВИТЬ:
Файлы связаны сейчас, но я получаю бесконечный цикл «Возникло исключение при ПК = 0x004000f0» и «Неверный адрес при чтении данных / стека: 0x00000000». Это мой обновленный файл:
`.text
.globl set
set: addi $t0, $t0, 0 #i = 0;
sll $t1, $t0, 2 #offsets 4 * i
add $t1, $t1, $a0 #pointer to a[i]
L1: lw $t1, 4($t1) #a[i++]
sw $a2, 0($t1) #a[i++] = v
blt $t0, $a1, L1 #continue the loop as long as i < n`
Почему мой код должен быть в .globl
? И какова цель .text
?
Хорошо, было несколько проблем.
Исправленный код находится внизу. На самом деле есть два способа сделать это, в зависимости от того, насколько буквальным должен быть перевод из кода C. Часть вашей концептуальной проблемы, возможно, заключалась в том, что вы пытались объединить части из двух методов.
Вы свели свои первые две инструкции из исходного кода в одну, основываясь на комментариях к комментариям [как дубликат] (например, li
затем addi
). Но, если использовать только один, li
правильно, потому что addi
добавляет регистр к себе, но вы не можете полагаться на начальное значение, равное нулю.
sll
сдвигает регистр с нулевым значением, поэтому inst ничего не делает.
Загрузить t1
с a0
, вы бы использовали add $t1,$a0,0
[или же add $t1,$a0,$zero
]
lw
не служил цели [код C не загружается a
так зачем же асм?].
Но я немного изменил это, потому что цикл все равно не работал бы правильно.
Там не было возврата после вашего blt
так что даже если бы петля работала, она бы «упала с края света». каждый называется Asm рутина [тот, который вызывается как jal set
] должен иметь явный оператор возврата, который jr $ra
ПРИМЕЧАНИЕ. В MIPS asm a*
[регистры аргументов] Можно быть изменены вызываемый, так петля на a0
скорее, чем t1
(т.е. звонящий надеется что они будут разгромлены)
Во всяком случае, вот исправленный код [прошу прощения за бесполезную очистку стиля]:
.text
.globl set
set:
li $t0,0 # i = 0
L1:
sw $a2,0($a0) # a[i] = v
add $a0,$a0,4 # advance pointer
add $t0,$t0,1 # ++i
blt $t0,$a1,L1 # continue the loop as long as i < n
jr $ra # return
Если ваша оригинальная функция C была чем-то вроде:
int
set(int *a, int n, int v)
{
int *end;
end = a + n;
for (; a < end; ++a)
*a = v;
return n;
}
Тогда это был бы более буквальный перевод:
.text
.globl set
set:
sll $a1,$a1,2 # convert int count to byte length
add $a1,$a0,$a1 # end = a + length
L1:
sw $a2,0($a0) # *a = v
add $a0,$a0,4 # ++a
blt $a0,$a1,L1 # continue the loop as long as a < end
jr $ra # return
IMO, оба метода являются приемлемыми реализациями исходной функции C. Первый является более буквальным в том смысле, что он сохраняет [концепцию] индексной переменной i
, Но у него есть дополнительная инструкция, которой нет у второй.
Оптимизатор, вероятно, будет генерировать один и тот же код (то есть второй асм) независимо от того, какую функцию C он переводит (MIPS не имеет мощных режимов адресации индекса, которые x86
АСМ делает).
Таким образом, то, что является «правильным», может зависеть от того, насколько сторонником является ваш проф.
Примечание: Обратите внимание на изменения стиля между моими двумя примерами. То есть код изменяется в сторону, добавляя несколько пустых строк для улучшения ясности.
Просто для полноты, вот main
Функция, которую я создал при тестировании:
.data
arr: .space 400 # allocate more space than count
.text
.globl main
main:
la $a0,arr # get array pointer
li $a1,10 # get count
li $a2,3257 # value to store
jal set
li $v0,1 # exit syscall number
syscall
ОБНОВИТЬ:
Если в цикле
a[i++] = v
я бы поместилadd $t0, $t0, 1
линия передsw $a2, 0($a0)
?
Нет, вы бы сделали это, если бы код C a[++i] = v
, Возможно, лучший способ увидеть это — сначала упростить код на языке Си.
a[i++] = v
на самом деле:
a[i] = v;
i += 1;
А также, a[++i] = v
на самом деле:
i += 1;
a[i] = v;
Теперь между строками кода на языке C и инструкциями asm существует взаимно-однозначное соответствие.
Когда я буду использовать
sll
? Я читал примеры, и я заметил, что люди обычно делаютsll $t1, $t0, 2
когда они собираются использовать счетчик, чтобы пройти через массив.
Да. Если вы внимательно посмотрите на мой второй реализация, она использует sll
таким образом. Кроме того, это был бы способ, которым я буду кодировать цикл, даже если дан исходный код C.
Буду ли я использовать
lw
если код C сказал что-то вродеint x = a[0]
?
Да, точно.
Другой способ создать прототип asm — преобразовать код на C в «очень тупой C».
То есть только if
самой простой формы: if (x >= y) goto label
, Четное if (x < y) j = 10
Запрещено.
Нет переменных области действия или переменных аргументов функции — только глобальные переменные, которые являются именами регистров.
Нет сложных выражений. Только простые x = y
, x += y
, или же x = y + z
, Таким образом, a = b + c + d
было бы слишком сложно.
Регистр переменных функционирует как целочисленные значения и байт указатели. Таким образом, при добавлении в регистр, используемый в качестве указателя, это похоже на добавление к байтовому указателю, так что для увеличения через int
массив, вы должны добавить 4
,
Фактическая разница между байтовым указателем и int
Указатель имеет значение только при выполнении операции загрузки / сохранения: lw/sw
за int
а также lb/sb
для байта.
Итак, вот моя вторая функция C, закодированная как «тупая»:
// RETURNS: number of elements changed
int
set(void)
// a0 -- "a" (pointer to int array)
// a1 -- "n" (number of elements in "a")
// a2 -- "v" (value to set into "a")
{
v0 = a1; // set return value
a1 <<= 2; // convert int count to byte length
a1 += a0; // point to one past end of array
L1:
*(int *)a0 = a2; // store v at current array location
a0 += 4; // point to next array element
if (a0 < a1) goto L1; // loop back until done
return;
}
ОБНОВЛЕНИЕ № 2:
В вашей первой реализации, это
add $a0, $a0, 4
эквивалент использованияsll
во второй реализации?
Не совсем. Главное, что нужно помнить, это в C, когда мы добавляем его к указателю [или индексируем его i
], компилятор сгенерирует инструкцию увеличения / добавления, которая добавляет sizeof
из тип указатель определяется как.
То есть для int *iptr
, указав iptr += 1
будет генерировать add $a0,$a0,4
так как sizeof(int)
4. Если бы мы имели double *dptr
, dptr += 1
компилятор сгенерирует add $a0,$a0,8
так как sizeof(double)
это 8
Это мощное «удобство», предоставляемое компилятором C, поскольку оно позволяет взаимозаменяемо использовать массивы, указатели и индексы.
В asm мы должны сделать вручную то, что компилятор C сделал бы для нас автоматически.
Рассмотрим следующее: у нас есть значение, которое является счетчиком количества элементы в массиве, назовите это count
, Теперь мы хотим знать, сколько байтов займет массив. Мы назовем это len
, Вот код C, чтобы определить это для различных типов:
char *arr;
len = count * sizeof(char);
len = count * 1;
len = count << 0;
// sll $a1,$a1,0
short *arr;
len = count * sizeof(short);
len = count * 2;
len = count << 1;
// sll $a1,$a1,1
int *arr;
len = count * sizeof(int);
len = count * 4;
len = count << 2;
// sll $a1,$a1,2
double *arr;
len = count * sizeof(double);
len = count * 8;
len = count << 3;
// sll $a1,$a1,3
Из того, что я понимаю, использование sll устанавливает i в качестве счетчика для целых чисел, так что оно увеличивает i, а также выполняет итерацию массива.
Нет. sll
это просто инструкция MIPS «сдвиг влево», и вы используете ее, когда вам нужен эквивалент C <<
оператор.
Что вы думаете о том, как sll
может быть использован добиться этого эффекта.
Перебирать массив int
мы увеличиваем индекс на 1, но мы также должны увеличить указатель массива на 4. Это то, что сделал мой первый пример asm. Условие прекращения было index >= count
,
Во втором примере с asm я исключил отдельную переменную индекса, преобразовав количество элементов в длину в байтах (через ssl
), добавив в массив адрес. Сейчас $a1
имел адрес последнего элемента массива + 1, и условие завершения было current_address >= last_element_plus_1
, Обратите внимание, что current_address ($a0
) все равно пришлось увеличить на 4 (add $a0,$a0,4
)
Важно помнить, что инструкции asm [особенно MIPS] просты (т.е. тупой). Они делают только одну вещь за один раз. Один оператор присваивания C может генерировать около 20 инструкций, если оператор был достаточно сложным. Именно так объединяются инструкции asm, что дает более сложные результаты.
Других решений пока нет …