Как изолировать элементы массива байтов и слов в 64-битном регистре
Я могу сказать, что это очень простая проблема, но я еще не понял ее. По сути, я просто хочу иметь возможность взять один элемент массива и добавить и вычесть из него некоторые числа с помощью регистров, а затем поместить результат в свою переменную результата.
segment .data
a dw 4, 234, -212
b db 112, -78, 50
result dq 0
segment .text
global main
main:
mov rax, [a]
Я знаю, что решение имеет какое-то отношение к смещениям и индексации, но я не понимаю, как я должен иметь возможность поместить только один элемент массива в регистр.
Что я могу сделать?
1 ответ
Если вы хотите рассматривать свои ценности как подписанные, вы хотите movsx
. Предполагая синтаксис NASM:
default rel
; ... declarations and whatever
movsx rax, word [a + 1*2] ; a is an array of dw = words
movsx rcx, byte [b + 1*1] ; b is an array of db = bytes
add rax, rcx
mov [result], rax ; result is a qword
(MASM или GNU .intel_syntax будет использовать word ptr
вместо того word
, просто добавь ptr
спецификатору размера для операнда памяти.)
В 1
может быть регистр вроде [a + rsi*2]
или [b + rsi]
так что вы можете легко перебрать свои массивы. Ссылка на содержимое ячейки памяти. (режимы адресации x86)
Я написал 1*2
вместо 2, чтобы указать, что это индекс 1 (2-й элемент массива), масштабируемый по размеру элемента. Ассемблер оценит константное выражение и просто будет использовать тот же (относительный RIP) режим адресации, что и для[a]
но с другим смещением.
Если вам нужно, чтобы он работал в позиционно-независимом коде (где вы не можете использовать [disp32 + register]
режим адресации с 32-битным абсолютным адресом символа), lea rdi, [a]
(RIP-relative LEA) сначала и сделайте [rsi + rsi*2]
.
Если вам нужно нулевое расширение, вы должны использовать movzx
movzx eax, word [a + 1*2] ; a is an array of dw = words
movzx ecx, byte [b + 1*1] ; b is an array of db = bytes
; word and byte zero-extended into 64-bit registers:
; explicitly to 32-bit by MOVZX, and implicitly to 64-bit by writing a 32-bit reg
; add eax, ecx ; can't overflow 32 bits, still zero-extended to 64
sub rax, rcx ; want the full width 64-bit signed result
mov [result], rax ; result is a qword
Если бы вы знали, что верхние биты вашего полного результата всегда будут равны нулю, просто используйте EAX (32-битный размер операнда), кроме конца. Преимущества использования 32-битных регистров / инструкций в x86-64
Этот код соответствует C как
static uint16_t a[] = {...};
static uint8_t b[] = {...};
static int64_t result;
void foo(){
int64_t rax = a[1] - (int64_t)b[1];
result = rax; // why not just return this like a normal person instead of storing?
}
Кстати, вы можете посмотреть на вывод компилятора в проводнике компилятора Godbolt и увидеть эти инструкции и режимы адресации.
Обратите внимание, что mov al, [b + 1]
загрузит байт и объединит его с младшим байтом RAX.
Обычно вы этого не хотите; movzx
- это нормальный способ загрузки байта в современной x86. Современные процессоры x86 декодируют x86 до RISC-подобных внутренних мопов для переименования регистров + выполнения вне очереди. movzx
избегает любой ложной зависимости от старого значения полного регистра. Аналог ARMldrb
, MIPS lbu
, и так далее.
Слияние с младшим байтом или словом RAX - странная вещь CISC, которую x86 может делать, а RISC - нет.
Вы можете безопасно читать 8-битные и 16-битные регистры (а это необходимо для хранилища слов), но обычно избегайте записи частичных регистров, если у вас нет веской причины и вы понимаете возможные последствия для производительности ( почему GCC не использует частичное регистры?). например, вы обнулили полный пункт назначения перед cmp +setcc al
.