Это действительно имеет смысл. Но я думаю, что самый простой ответ - все остальные регистры используются. Чтобы использовать какой-либо другой регистр, вам нужно нажать его в стек.
Компиляторы достаточно умны. Отслеживание того, что находится в регистре для компилятора, несколько тривиально, это не проблема. Говоря в общем случае не обязательно x86, особенно если у вас больше регистров (чем у x86), у вас появятся некоторые регистры, которые используются для ввода (в вашем соглашении о вызове), некоторые из которых вы можете уничтожить, это может быть то же самое, что и входные или нет, некоторые из которых вы не можете мусор, вы должны сохранить их в первую очередь. У некоторых наборов инструкций есть специальные регистры, они должны использовать этот для автоматического приращения, один для регистрации непрямой и т. Д.
Вы наверняка, если не тривиальны, чтобы получить компилятор для создания кода для руки, например, когда входные и управляемые регистры являются одинаковыми, но это означает, что если вы вызываете другую функцию и создаете право на функцию вызова необходимо сохранить что-то для использования после возврата:
unsigned int more_fun (unsigned int);
unsigned int fun (unsigned int x)
{
return(more_fun(x)+x);
}
00000000 <fun>:
0: e92d4010 push {r4, lr}
4: e1a04000 mov r4, r0
8: ebfffffe bl 0 <more_fun>
c: e0840000 add r0, r4, r0
10: e8bd4010 pop {r4, lr}
14: e12fff1e bx lr
Я сказал вам, что это тривиально. Теперь, чтобы использовать ваш аргумент назад, почему они просто не нажимали r0 на стек и не выскакивали позже, зачем нажать r4? Для входа используются не r0-r3, а волатильны, r0 - регистр возврата, когда он подходит, r4 почти все, что вам нужно сохранить (за исключением одного я думаю).
Таким образом, предполагается, что r4 используется вызывающим абонентом или некоторым вызывающим абонентом вверх по линии, вызывающая конвенция диктует, что вы не можете ее уничтожить, вы должны ее сохранить, поэтому вы должны предположить, что она используется. Вы можете уничтожить r0-r3, но вы не можете использовать один из них, так как вызывающий может их уничтожить, поэтому в этом случае нам нужно принять входящее значение x и использовать его (передать его) и сохранить его после возвращения поэтому они сделали оба, «использовали другой регистр с ходом», но для этого они сохранили этот другой регистр.
Зачем сохранять r4 в стеке в этом случае очень очевидно, вы можете сохранить его спереди с обратным адресом, в частности, рука хочет, чтобы вы всегда использовали стек в 64-битных кусках, поэтому два регистра за раз идеально или по крайней мере, держите его в соответствии с 64-битной границей, поэтому вам все равно нужно сохранить lr, поэтому они будут продвигать что-то еще, даже если они этого не делают, поскольку в этом случае сохранение r4 является халявой, и поскольку им нужно для сохранения r0 и в то же время использовать его. r4 или r5 или что-то выше - хороший выбор.
BTW выглядит как компилятор x86, выполненный выше.
0000000000000000 <fun>:
0: 53 push %rbx
1: 89 fb mov %edi,%ebx
3: e8 00 00 00 00 callq 8 <fun+0x8>
8: 01 d8 add %ebx,%eax
a: 5b pop %rbx
b: c3 retq
демонстрация их толкает что-то, что они не должны сохранить:
unsigned int more_fun (unsigned int);
unsigned int fun (unsigned int x)
{
return(more_fun(x)+1);
}
00000000 <fun>:
0: e92d4010 push {r4, lr}
4: ebfffffe bl 0 <more_fun>
8: e8bd4010 pop {r4, lr}
c: e2800001 add r0, r0, #1
10: e12fff1e bx lr
Нет причины, чтобы спасти r4, они просто необходимы некоторые регистр, чтобы стек выравнивается, так что в этом случае r4 был выбран , в некоторых версиях этого компилятора вы увидите r3 или какой-либо другой регистр.
Помните, что люди (все еще) пишут компиляторы и оптимизаторы и т. Д. Поэтому они почему это и почему это действительно вопрос для человека или тех людей, и мы не можем сказать вам, что они думают. Это не простая задача, но нетрудно принять разумную функцию размера и/или проект и найти возможности вручную настроить вывод компилятора, чтобы улучшить его. Конечно, красота в глазах смотрящего, одно определение улучшения - это другое определение худшего. В одном сочетании команд может использоваться меньше байтов команд, так что это «лучше» по стандарту размера программы, другое может или не может использовать больше инструкций или байтов, но выполнять быстрее, возможно, меньше доступа к памяти за счет инструкций для идеального выполнения более быстрые и т. д.
Есть архитектуры с сотнями регистров общего назначения, но большинство из тех, что мы касаемся продуктов с ежедневными, не имеют такого количества, поэтому вы можете вообще сделать функцию или какой-то код, в котором так много переменных в полете в функции, которую вы должны начать экономить до функции стека. Таким образом, вы не можете всегда сохранять несколько регистров в начале и в конце функции, чтобы дать вам больше рабочих функций средних регистров, если количество рабочих регистров, в которых вы нуждаетесь в средней функции, больше регистров, чем у вас. Фактически требуется практика, чтобы иметь возможность писать код, который не оптимизируется до такой степени, что ему не нужно слишком много регистров, но как только вы начнете видеть, как работают компиляторы, изучая их вывод, вы можете написать тривиальные функции, подобные приведенным выше, чтобы предотвратить оптимизация или сохранение силы средних функций регистров и т. д.
В конце дня, когда компилятор должен быть в какой-то степени здравомыслящим, ему требуется соглашение о вызове, это заставляет авторов сойти с ума, а компилятор - стать кошмаром для кодирования и управления. И вызывающая конвенция очень четко определяет входные и выходные регистры (регистры) любых летучих регистров и те, которые должны быть сохранены.
unsigned int fun (unsigned int x, unsigned int y, unsigned int z)
{
unsigned int a;
a=x<<y;
a+=(y<<z);
a+=x+y+z;
return(a);
}
00000000 <fun>:
0: e0813002 add r3, r1, r2
4: e0833000 add r3, r3, r0
8: e0832211 add r2, r3, r1, lsl r2
c: e0820110 add r0, r2, r0, lsl r1
10: e12fff1e bx lr
Только потратил на это несколько секунд, но мог бы усердно работать над этим. Я не пропустил всего четыре регистра, и у меня было четыре переменные. И я не называл какие-либо функции, поэтому компилятор был свободен, чтобы просто мусорить r0-r3 по мере необходимости в зависимости от разработанных зависимостей. Поэтому мне не нужно было сохранять r4 для создания временного хранилища, ему не нужно было использовать стек, он просто оптимизировал порядок выполнения, например, освободить r2, переменную z, чтобы позже он мог использовать r2 в качестве промежуточной переменной , один из примеров чего-то равного. Хранение его до четырех регистров вместо того, чтобы сжечь пятый.
Если бы я был более креативным с моим кодом, и я добавил в вызовы функции, я мог бы заставить его записывать намного больше регистров, вы бы видели, что даже в этом последнем случае у компилятора нет никаких проблем с отслеживанием что есть, и вы увидите, когда играете с компиляторами, нет причин, по которым они должны держать ваши языковые переменные высокого уровня в целости и сохранности в одном и том же регистре на протяжении гораздо меньшего количества выполнения в том же порядке, в котором вы написали свой код (пока он является законным), но они по-прежнему находятся во власти вызывающего соглашения, если какой-либо из некоторых регистров считается изменчивым, и если вы вызываете функцию из вашей функции в определенное время в коде, тогда вы должны сохранить это поэтому вы не можете использовать их как долговременное хранилище, а те, которые нестабильны, уже считаются потребляемыми, поэтому их необходимо сохранить для их использования, тогда это будет частично входит в вопрос о производительности, стоит ли больше (размер, скорость и т. д.) экономить на стек на лету или я могу сохранить фронт так, чтобы, возможно, уменьшать инструкции или быть невидимым и/или потреблять меньше часов с более крупная передача, а не отдельная, менее эффективная передача средней функции?
Я сказал это семь раз, но нижняя строка - это соглашение о вызове для этого компилятора (версии) и цели (и параметров командной строки/значений по умолчанию). Если у вас есть летучие регистры (произвольная информация о соглашениях общего пользования для регистров общего назначения, а не аппаратная/ISA-вещь), и вы не вызываете каких-либо других функций, тогда они просты в использовании и сохраняют дорогостоящие транзакции стека (памяти). Если вы звоните кому-то, тогда они могут быть разбиты ими, поэтому они могут больше не быть свободными, зависит от вашего кода. Энергонезависимые регистры считаются потребляемыми абонентами, поэтому вам приходится записывать операции стека, чтобы использовать их, они не могут свободно использоваться. И тогда это становится производительностью относительно того, когда и где использовать стек, толкает и всплывает, и движется. Ожидается, что два компилятора не сгенерируют один и тот же код, даже если они используют одно и то же соглашение, но вы можете видеть выше, несколько тривиально, чтобы выполнять тестовые функции, компилировать их и анализировать выходные данные, настраивать здесь и там, чтобы перемещаться по ним и вокруг него (компилятор, версия и целевые и условные и параметры командной строки).
Вы используете значения «push» в стеке, чтобы вы могли использовать регистры, которые они занимают. Почему бы им не переместить их в другие регистры? Вероятно, потому что другие регистры нужны и для некоторых значений. – fuz
Если регистр ESI свободен в цикле, вам будет лучше поставить счетчик в ESI и просто не перетасовать его. Если ваш компилятор умный, он это узнает. Вывод: либо у вас есть немой компилятор, либо он знает, что ESI также не свободен в цикле, и нет других свободных регистров. В этом случае комбинация PUSH/POP не страшна. –
* «все современные компиляторы (по крайней мере, те, что я испытал) выполняют инструкции PUSH и POP» * ... это довольно фиктивное утверждение, попробуйте «gcc» или «clang», они этого не делают (если они не закончились резервных регистров внутри цикла, то я бы поспорил, что они предпочитают использовать локальную переменную '[ebp-ofs// esp + ofs]. Мне бы хотелось увидеть, что некоторые источники C/C++ создают PUSH/POP с этими двумя. И снова эти два являются в основном единственными современными компиляторами, поэтому я не уверен, что вы проверили. – Ped7g