Хорошо, было несколько проблем.
Исправленный код внизу. На самом деле есть два способа сделать это, в зависимости от того, насколько буквальным должен быть перевод из кода C. Часть вашей концептуальной проблемы могла заключаться в том, что вы пытались объединить части двух методов.
Вы объединили свои первые две инструкции из исходного кода в одну, основываясь на комментариях [как дубликат] (например, li
, затем addi
). Но, если используется только один, li
правильный, потому что addi
добавляет регистр к самому себе, но вы не можете полагаться на то, что начальное значение равно нулю.
sll
сдвигает регистр, содержащий нулевое значение, поэтому inst ничего не делает.
Чтобы загрузить t1
с a0
, вы должны использовать add $t1,$a0,0
[или add $t1,$a0,$zero
]
lw
не имело смысла [Код C не загружает a
, так почему должен asm?].
Но я немного изменил все это, потому что цикл все равно не работал бы правильно.
Возврата после вашего blt
не было, так что даже если цикл сработает, он "упадет с края света". Каждая вызываемая подпрограмма ассемблера [та, которая вызывается как 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
ИМО, оба метода являются приемлемыми реализациями исходной функции C. Первый более буквален, поскольку сохраняет [концепцию] индексной переменной i
. Но у него есть дополнительная инструкция, которой нет у второго.
Оптимизатор, вероятно, будет создавать один и тот же код (то есть второй asm) независимо от того, какую функцию C он транслирует (MIPS не имеет мощных режимов индексной адресации, которые есть у x86
asm).
Итак, что является «правильным», может зависеть от того, насколько приверженцем является ваш профессор.
Примечание. Обратите внимание на изменения стиля между двумя моими примерами. То есть код меняется в сторону, добавляя несколько пустых строк для повышения ясности.
Для полноты картины вот функция 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
. Возможно, лучший способ увидеть это — сначала упростить код C.
a[i++] = v
на самом деле:
a[i] = v;
i += 1;
И a[++i] = v
на самом деле:
i += 1;
a[i] = v;
Теперь между строками кода C и ассемблерными инструкциями существует однозначное соответствие.
Когда я буду использовать sll
? Я читал примеры и заметил, что люди обычно делают sll $t1, $t0, 2
, когда собираются использовать счетчик для прохода по массиву.
да. Если вы внимательно посмотрите на мою вторую реализацию, то увидите, что она использует sll
таким же образом. Кроме того, я бы закодировал цикл, даже если бы был исходный код C.
Буду ли я использовать lw
, если код C говорит что-то вроде int x = a[0]
?
Да, точно.
Другой способ создания прототипа ассемблера — преобразовать код 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, поскольку он позволяет взаимозаменяемо использовать массивы, указатели и индексы.
В ассемблере мы должны делать вручную то, что компилятор 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
, мы увеличиваем index на 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 инструкций, если оператор достаточно сложен. Именно то, как ассемблерные инструкции комбинируются, дает более сложные результаты.
person
Craig Estey
schedule
15.02.2016
gcc -O1 -g -o test.o -c test.cpp
), а затемobjdump -S test.o
? - person kfsone   schedule 15.02.2016.o
только для того, чтобы разобрать его, когда можно создать.s
(gcc -O1 -g -o test.s -S test.cpp
)? - person pat   schedule 15.02.2016set
глобально видимым, чтобы компоновщик мог его видеть. Используйте.globl
linux.web.cern .ch/linux/scientific4/docs/rhel-as-en-4/ - person pat   schedule 15.02.2016li
иaddi
? Похоже, чтоaddi
избыточен. Вы имели в видуaddi $t0, $a0, 0
? Если это так, тоli
является избыточным. Как у вас есть,$t0 = 0
после этих строк и$t1
будет 0 послеsll
. Но ничего из этого не связано с вашей ошибкой неопределенного символа. Нам нужно увидеть (хотя бы часть) кода вашего профессора, чтобы сказать вам, что его вызывает. - person Tyler   schedule 15.02.2016objdump -S
. Я пишу свои.s
файлы в блокноте - person Pachitoo23   schedule 15.02.2016.text
,.globl
и т. д.], который определяет намерение. То есть, что происходит в проблемно-ориентированном плане. Таким образом, вы можете отслеживать свою логику по сравнению с реализацией. (т. е.) Ваша логика верна или инсталляция неверна? - person Craig Estey   schedule 15.02.2016.globl
в моем коде? Заменит ли он.text
? - person Pachitoo23   schedule 15.02.2016.globl set
является дополнением к.text
и должно следовать за ним. Или, правильнее, он должен предшествоватьset:
[в другой строке] - person Craig Estey   schedule 15.02.2016.text
указывает, что последующее будет связано с сегментом text [для кода]..globl set
говорит сделать меткуset
глобально видимой для компоновщика. В вашем коде есть вторая меткаL1
, которую вы хотите сохранить приватной. В C связь по умолчанию дляset()
является глобальной [если вы не сделаетеstatic int set(...)
]. В ассемблерном языке все является закрытым [для файла], если метка также не имеет директивы.globl
[в asm нет.static
]. В более сложной функции у вас может быть много меток L1, L2, L3,..., которые являются просто внутренними целями перехода. Утомительно помечать их как статические, поэтому статические/частные по умолчанию. - person Craig Estey   schedule 15.02.2016