Понимание рекурсии на ассемблере MIPS - PullRequest
0 голосов
/ 26 января 2012

Я был в классе, и у нас есть / мы обсуждаем рекурсию на ассемблере.Мне казалось, что я понимаю рекурсию, но чем больше людей пытаются мне ее объяснить, тем больше я чувствую себя от нее отстраненной.

В любом случае, у нашего последнего слайда была функция (на С?), И учитель сказал, что онЯ расскажу об этом в классе, но позвольте нам, ученикам, показать остальную часть класса на доске.Я чувствовал, что он все время смотрит на меня, и я боюсь выглядеть глупо.

Можете ли вы, ребята, помочь мне написать этот код в MIPS и помочь мне понять его?IDK, если это слишком сложно

Напишите на языке ассемблера MIPS, чтобы найти fix (i, x), где fix (i, x) рекурсивно определяется как:

int fix(int i, int x) // assume i > 0, x > 0
{
    if (x>0)
        return fix(i,x-1);
    else if (i>0)
        return fix(i-1, i-1)+1;
    else
        return 0;
}

Спасиборебята, у меня завтра урок, и я все еще надеюсь, что он никогда не позовет меня;но я бы хотел понять этот материал.

ПРИМЕЧАНИЕ. Это будет упражнение в классе с приложением 0 баллов.Я чувствую, что все в классе уже знают, как это сделать.

Ответы [ 3 ]

2 голосов
/ 26 января 2012

Язык ассемблера не имеет ничего общего с рекурсией, которая просто работает из-за языка C, соглашений о вызовах и реализации.Просто внедрите C в ассемблере и не заботьтесь о рекурсии.Я думаю, что я затронул этот вопрос в проекте для домашних животных http://github.com/dwelch67/lsasim, если только я не изменил его в последнем уроке - ручное преобразование рекурсии в C в ассемблер.Это не работает, так что не стоит беспокоиться о том, что это домашняя задача.

В любом случае, ключом к запуску является простое использование C в сборке.

Например, у вас есть функция с входными параметрами.

int fix(int i, int x)

Вам потребуется объявить себя соглашением о вызовах или использовать существующее, чтобы реализовать C, это означает, что вам нужно некоторое место для входных параметров, либо поместите их в стек, либо внесите в регистры.Предполагая отсутствие оптимизации, вы сохраняете эти переменные по всей функции и очищаете в конце.Поэтому, если вам нужно вызывать ЛЮБУЮ функцию в любом месте кода (рекурсия, вызывающая одну и ту же функцию, является очень маленьким подмножеством ЛЮБОГО, но попадает в эту категорию и НЕ СПЕЦИАЛЬНА), вам необходимо сохранить эти переменные.Если соглашение о вызовах вносит их в стек, то вы уже сделали, если соглашение о вызовах вносит их в регистры, вам нужно сохранить их до вызова и восстановить после

push i
push x
implement call to function
pop x
pop i

и продолжить реализациюfunction.

, то есть все остальное позаботится о себе.

Если вы заметили, что функция, созданная вами в качестве примера, не имеет пути, в котором нужны входные переменныебыть сохраненным после вызова функции внутри этой функции.И входные переменные модифицируются и используются в качестве входных данных для следующего вызова.поэтому оптимизация для вашей реализации кода на C будет заключаться в том, чтобы не беспокоиться о сохранении этих переменных.просто измените их и передайте.Использование регистров в соглашении о вызовах было бы самым простым способом сделать это для этой конкретной функции.Это то, что компилятор будет делать в любом случае при оптимизации (не сохранять при использовании соглашения о вызовах на основе регистров).

Вы также можете выполнить хвостовую оптимизацию, если она так называется.Обычно при вызове функции вы используете все, что обычно делает инструкция, для выполнения «вызова», который отличается от простого перехода или перехода, потому что где-то хранится возвращаемое значение.И есть какая-то функция возврата, которая отменяет это и возвращает обратно к инструкции после вызова.Вложенные вызовы означают вложение возвращаемых значений, отслеживание всех из них.В этом случае, хотя и в других случаях, когда последнее, что вы делаете, путь выполнения функции - это вызов другой функции, вы можете вместо этого (зависит от набора инструкций) перейти к функции и не должны вкладывать другой набор возвращаемых значений.Например, для набора команд постановки на охрану:

Какой-то код вызывает первую функцию:

bl firstfun:

На рукоятке bl означает ответвление.регистр 14 будет заполнен возвращаемым значением, а счетчик программ будет заполнен адресом функции, firstfun.

обычно, если вам нужно вызвать функцию из функции, которую нужно сохранить, r14 такВы можете вернуться из этой функции без этой хвостовой оптимизации:

firstfun:
 ...
 push {r14}
 bl secondfun
 pop {r14}
 bx r14
 ...
secondfun:
  bx r14

bx lr означает переход к содержимому в r14, который в данном случае является возвращением.оптимизация выглядит следующим образом, важно отметить, что в первой функции вызов второй функции - это последнее, что вы делаете перед возвратом из первой функции.это ключ к этой оптимизации.

firstfun:
 ...
 b secondfun
 ...
secondfun:
  bx r14

b просто означает ветвление, а не ветвление, которое просто изменяет ПК и не изменяет r14 или любой другой регистр или стек.Выполнение двух реализаций одинаково функционально, внешняя функция вызывает firstfun, и в правильном пути выполнения есть возврат (bx r14).

Другие люди отметили, что этот код может полностью оптимизировать себя в ничто, так как вы возвращаете ноль первоначальному вызывающему.

fix:
  return 0
1 голос
/ 26 января 2012

Просто разбейте его на 3 части, как C выше. Передайте значения i и x через регистр и позвоните себе после запуска проверки if и изменения регистров. Вышесказанное не должно занимать более 30 строк ассемблера. Если бы я был лучшим программистом MIPS, я бы сделал это для вас. Это будет выглядеть примерно так (это псевдо ассемблер)


fix:
  compare r0, 0
  branch greater than x_positive
  subtract r1,r1,1
  call fix
  return;
//  or just jump fix instead

x_positive:
  compare r1, 0
  branch greater than i_positive
  subtract r0, r0, 1
  subtract r1, r1, 1
  call fix
  return
// or just  jmp fix

i_positive:
  move return register, 0
  return

Забавно, это всегда будет возвращать 0, как написано в C:)


  fix:
    move return_register, 0
    return
1 голос
/ 26 января 2012

Хотя я против того, чтобы просто дать ответ на домашние задания, вот эквивалентная функция, которая найдет fix(i, x), в комплекте с примером вызова (эта версия немного более эффективна, чем версия C):

fix:
        bgtz RetI
        xor $v0, $v0, $v0
        jr $ra
RetI:
        move $v0, $a0
        jr $ra

# example of calling fix
main:
        la $a0, 42
        la $a1, 21
        jal fix

И пусть это будет для вас уроком, чтобы узнать, что делают функции, прежде чем пытаться их кодировать:)

...