Обратите внимание, что приведенное ниже адаптировано из задачи 3.4 из текста Брайанта и О'Халларона (CSAPP3e). Я удалил все, кроме моего основного вопроса.
Контекст: мы рассматриваем комбинацию x86-64/Linux/gcc, в которой int
имеют размер 4 байта, а char
считаются подписанными (и, конечно же, 1 байт). Мы заинтересованы в написании сборки, соответствующей преобразованию int в char, которое, как мы знаем, на высоком уровне возникает в результате выполнения усечения.
Они предлагают следующее решение:
movl (%rdi), %eax // Read 4 bytes
movb %al, (%rsi) // Store low-order byte
Мой вопрос заключается в том, можем ли мы изменить movl
на movb
, поскольку, в конце концов, мы используем только байт. Меня беспокоит это подозрение, что при чтении может быть некоторая зависимость от порядка байтов, и мы можем каким-то образом получать старшие биты, если наш процессор/ОС находится в режиме с прямым порядком байтов. Верно ли это подозрение, или мои изменения сработают, несмотря ни на что?
Я бы попробовал это, но 1) я использую Mac с процессором Apple и 2) даже если бы мои подозрения сработали, я не мог быть уверен, что подобные вещи зависят от реализации.
В этом случае вы можете использовать movb (%rdi), %al
без особого изменения значения. Однако производительность может быть хуже, поскольку запись только 8 бит всегда будет сливаться с полным значением регистра rax
, то есть старшие 56 бит не изменяются. Запись в eax
означает, что старшие 56 бит также записываются (24 бита данных, 32 бита расширяются до нуля, поскольку запись eax
всегда расширяется до нуля до rax
). Это может быть лучше для производительности. Однако еще лучше movzx
(не уверен насчет названия синтаксиса AT&T), потому что вы загрузите только 8 бит памяти. Это может быть даже более корректно в конце страницы (чтобы избежать ошибки).
@SepRoland Я не уверен, что разделяю это беспокойство. Разве ваше решение с movzbl
не будет работать тогда и только тогда, когда movb
сработало (т. е. одно чтение получает правильный байт из памяти тогда и только тогда, когда другое делает)? Возможно, мое замешательство возникает из-за того, что я не знаю, что здесь означает «частичные регистры».
@ecm Удивительно, мой вопрос возник, потому что я наивно думал, что movb
будет лучше для производительности - поэтому я удивлен (хотя и понимаю, согласно вашему оправданию), что размышления о производительности были хорошими, только в противоположном направлении! Хотя я не знаю movzx
. Это movzbl
возможно?
@SepRoland movsbl
было бы более подходящим, учитывая, что char
— это знаковый тип.
x86-64 имеет прямой порядок байтов. Никаких дополнительных режимов, которые следует учитывать, не существует.
@ EE18 Да, из ответа Сена Роланда я понимаю, что movzx eax, byte [rdi]
будет называться movzbl (%rdi), %eax
в синтаксисе AT&T. В синтаксисе Intel он называется movzx
.
macOS с Apple Silicon поставляется с Rosetta 2. Это позволяет вам, по крайней мере, запускать машинный код x86-64 так же, как на Mac x86-64. Я не уверен, насколько хорошо он поддерживает одношаговую отладку, но я предполагаю, что это как-то возможно. В худшем случае в эмулируемой виртуальной машине (например, под управлением Linux). Если вы изучаете сборку x86-64, играйте с ней в отладчике и наблюдайте за изменением регистров по мере того, как вы выполняете один шаг, — это отличный способ изучить и проверить свою мысленную симуляцию того, что вы думаете, что-то подойдет.
Признаюсь, я пошел на (экстремальный?) шаг и купил действительно дешевую бывшую в употреблении систему x86-64/Linux Mint, так что я полностью намерен это сделать :) @PeterCordes
Вы правы, что беспокоитесь о порядке байтов для такого рода операций, но в этом случае ваш альтернативный подход потерпит неудачу на машинах с прямым порядком байтов, а не на машинах с прямым порядком байтов.
x86 имеет прямой порядок байтов, что означает, что младшие восемь битов 32-битного целого числа хранятся в первом байте (самый низкий адрес) этого целого числа, поэтому
movb (%rdi), %al // Read low-order byte
movb %al, (%rsi) // Store low-order byte
выполнит усечение, которое вы хотите сделать на x86. Но на машине с прямым порядком байтов эквивалентная операция будет читать старшие восемь битов 32-битного целого числа. Например, архитектура m68k имеет обратный порядок байтов; правильная версия вашего альтернативного подхода для этой архитектуры будет
move.b 3(%a1), %d0 // Read low-order byte
move.b %d0, (%a0) // Store low-order byte
Без 3
он бы прочитал старший байт целого числа, на который указывает регистр %a1.
Преимущество того, как это делается в CS:APP, заключается в том, что одна и та же конструкция будет корректно работать как на архитектурах с прямым порядком байтов, так и с прямым порядком байтов. Конечно, если вы программируете на языке ассемблера, вам в любом случае придется переписать код, чтобы перенести программу на другую архитектуру, но при этом на одну вещь меньше, о чем стоит беспокоиться.
Код, сгенерированный компилятором, вероятно, также будет делать это способом CS:APP по связанным причинам: компиляторы обычно выполняют большую часть своей работы в независимом от архитектуры «промежуточном представлении», а затем переводят его на язык ассемблера. Этот перевод является одним из самых сложных этапов компилятора промышленного уровня по причинам, выходящим за рамки этого ответа; каждое упрощающее предположение, которое не ухудшает сгенерированный код, будет применяться для облегчения его написания.
Отлично, большое спасибо за ответ, zwol. Я действительно перепутал здесь порядок байтов. И последний вопрос: что мы можем сказать о том, почему метод CS:APP не зависит от порядка байтов? Это потому, что порядок байтов — это «понятие, касающееся только памяти» и что, когда данные находятся в наших регистрах, говорить о младших байтах становится однозначно?
@ EE18 Да, это один из способов интерпретировать это.
Супер, большое спасибо @fuz !
Здесь почти нет разницы между использованием movl
и movb
.
Если адрес, используемый для загрузки, не выровнен и попадает на границу страницы, то movl
может работать медленнее, чем использование movb
.
С другой стороны, если источник потенциально является общим подвыражением, то movl
обеспечивает доступ к полному и усеченному значению, тогда как другой обеспечивает доступ только к усеченному значению.
Трудно представить, как здесь вступает в силу порядок байтов на платформах x86. Если по какой-то причине вы перейдете на платформу с прямым порядком байтов, код будет другой — например, эквивалент movb al, 3(rsi)
(версия movl будет работать без изменений).
Большое спасибо за этот очень полезный ответ. Честно говоря, я не знал, что x86 определенно имеет прямой порядок байтов. Я думал, что это аспект режима процессора, который можно переключать, но, похоже, нет.
Вы можете переключить его для некоторых процессоров. x86 всегда имел строгий порядок байтов, но ваша машина Apple Silicon может иметь возможность переключения; если я правильно помню, это дополнительная функция архитектуры ARM, и я не знаю, решила ли Apple включить ее.
Просто
movb
было бы не очень хорошей идеей. Написание всего EAX (и, следовательно, RAX) позволяет избежать каких-либо махинаций с частичными регистрами. Более уместно было бы использовать форму инструкции, простирающуюся от байта до двойного слова:movzbl (%rdi), %eax
.