Я думаю, что один из ваших обратных путей не pop rbp. Оставьте только
pushq %rbp
movq %rsp, %rbp
pop %rbp
всего. По умолчанию gcc - .
Или исправьте путь с обратным нулем, а также нажмите rbp.
На самом деле, вы ввернуты, потому что ваша функция, как представляется, предназначена для размещения вещей в стеке, никогда не снимайте его. Если вы хотите придумать свой собственный ABI, где пространство под указателем стека может использоваться для возврата массивов, это интересно, но вам нужно будет отслеживать, насколько они велики, поэтому вы можете настроить rsp
назад, указывая на обратный адрес до ret
.
Я рекомендую не загружать обратный адрес в регистр и заменять более поздний ret
jmp *%rdx
или что-то в этом роде. Это позволит отказаться от логики прогнозирования адреса/возврата адреса в современных процессорах и вызвать срыв так же, как неверный прогноз отрасли. (См. http://agner.org/optimize/). ЦП ненавидят несоответствующий вызов/ret. Я не могу найти конкретную страницу, чтобы связать это прямо сейчас.
См. https://stackoverflow.com/tags/x86/info для других полезных ресурсов, включая документацию ABI о том, как функции обычно принимают аргументы.
Вы можете скопировать адрес возврата вниз ниже массива вы просто толкнул, а затем запустить ret
, чтобы вернуться с% РСП модифицирован. Но, если вам не нужно вызывать длинную функцию из нескольких сайтов, возможно, лучше просто вставить ее в один или два вызова.
Если это слишком большой, чтобы встраивать в слишком многих сайтах вызовов, ваш лучший выбор, вместо использования call
и скопировать адрес возврата вниз в новое место, было бы подражать call
и ret
. Вызов вызывающего абонента
put args in some registers
lea .ret_location(%rip), %rbx
jmp my_weird_helper_function
.ret_location: # in NASM/YASM, labels starting with . are local labels, and don't show up in the object file.
# GNU assembler might only treat symbols starting with .L that way.
...
my_weird_helper_function:
use args, potentially modifying the stack
jmp *%rbx # return
Вам нужна действительно веская причина использовать что-то вроде этого. И вам придется обосновать/объяснить это с большим количеством комментариев, потому что не, что читатели будут ожидать. Прежде всего, что вы собираетесь делать с этим массивом, который вы вложили в стек? Вы собираетесь найти его длину, вычитая rsp и rbp или что-то еще?
Интересно, что хотя push
должен изменить rsp, а также сделать магазин, он имеет одну пропускную способность на каждый процессор на всех последних процессорах. У процессоров Intel есть механизм стека, чтобы заставить операторы стека не ждать, пока rsp будет вычислен в двигателе внешнего порядка, когда он будет изменен только push/pop/call/ret. (микширование push/pop с mov 4(%rsp), %rax
или что-то другое, вставляемое в дополнительные файлы для синхронизации RPS-сервера с смещением стекового двигателя.) Процессоры Intel/AMD могут делать только одно хранилище за такт в любом случае, но Intel SnB и более поздние могут выходить дважды за такт.
Таким образом, push/pop фактически не является ужасным способом реализации структуры данных стека, особенно. на Intel.
Кроме того, ваш код структурирован причудливо. main()
разделен на r8_digits_to_stack
. Это нормально, но вы не воспользовались тем, что проваливались из одного блока в другой блок, поэтому он просто стоит вам jmp
в main
без каких-либо преимуществ и огромного недостатка читаемости.
Давайте сделаем вид, что ваш цикл является частью main
, так как я уже говорил о том, как супер-странно иметь функцию возврата с измененным% rsp.
Ваша петля может быть и проще. Стройте вещи с помощью jcc до верха, когда это возможно.
Существует небольшое преимущество в том, чтобы избегать верхних 16 регистров: 32-разрядные insns с классическими регистрами не нуждаются в префиксном байте REX. Так что давайте сделаем вид, что у нас есть только начальное значение в% rax.
digits_to_stack:
# put each bit of %rax into its own 8 byte element on the stack for maximum space-inefficiency
movq %rax, %rdx # save a copy
xor %ecx, %ecx # setcc is only available for byte operands, so zero %rcx
# need a test at the top after transforming while() into do{}while
test %rax, %rax # fewer insn bytes to test for zero this way
jz .Lend
# Another option can be to jmp to the test at the end of the loop, to begin the first iteration there.
.align 16
.Lpush_loop:
shr $1, %rax # shift the low bit into CF, set ZF based on the result
setc %cl # set %cl to 0 or 1, based on the carry flag
# movzbl %cl, %ecx # zero-extend
pushq %rcx
#.Lfirst_iter_entry
# test %rax, %rax # not needed, flags still set from shr
jnz .Lpush_loop
.Lend:
Эта версия до сих пор своего рода сосет, потому что на семействах процессоров Intel P6/SnB, используя более широкий регистр после записи меньшую часть приводит к замедлению. (срыв на pre-SnB или дополнительный uop на SnB и позже). Другие, включая AMD и Silvermont, не отслеживают частичные регистры отдельно, поэтому запись в% cl имеет зависимость от предыдущего значения% rcx. (запись в 32-битные регистры нуля 32, что позволяет избежать проблемы зависимостей с частичной регистрацией.) movzx
для нулевого расширения от байта до долгого времени будет делать то, что Sandybridge делает неявным, и дать ускорение для старых процессоров.
Это не будет работать в течение одного цикла на итерации Intel, но может на AMD. mov/and $1
не плохо, но and
влияет на флаги, что затрудняет цикл только на основе shr
установки флагов.
Обратите внимание, что ваша старая версия sarq %rax
сдвигает знаковые биты, не обязательно нули, поэтому с отрицательным вводом ваша старая версия будет циклом inf (и segfault, когда вы закончите пространство стека (push попытается записать в неотображенный страница)).
Спасибо за ссылку, я скоро обновлю! – Langston
удалил мой старый комментарий, вы действительно сказали, где ваш код неисправен. –