Инструкция x86 LEA делает неоднозначные вещи

Вот код C:

int baz(int a, int b)
{
    return a * 11;
}

Это скомпилировано в следующий набор инструкций по сборке (с флагом -O2):

baz(int, int):
        lea     eax, [rdi+rdi*4]
        lea     eax, [rdi+rax*2]
        ret

Инструкция lea вычисляет эффективный адрес второго операнда (исходного операнда) и сохраняет его в первом операнде. Мне кажется, что первая инструкция должна загрузить адрес в регистр EAX, но если это так, то умножение RAX на 2 не имеет смысла во второй lea инструкции, поэтому я делаю вывод, что эти две lea инструкции не делают совсем то же самое.

Мне было интересно, может ли кто-нибудь прояснить, что именно здесь происходит.

По сути, забудьте об «адресах» и посмотрите, что на самом деле делает lea: простая арифметика, сдвиги и сложения. И это позволяет вам выполнять их в комбинациях, которые часто требуют меньшего количества инструкций, чем эквивалентная последовательность инструкций shl/add, поэтому, когда вам нужна такая комбинация, вы можете оптимизировать, как это сделал компилятор здесь. Никто не заставляет вас использовать результат в качестве адреса.

Nate Eldredge 14.05.2023 17:12
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
1
69
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Аргумент функции для a хранится в rdi. Нет необходимости загружать что-либо из памяти.

lea eax, [rdi+rdi*4] не вычисляет адрес какой-либо ячейки памяти для извлечения данных. Вместо этого компилятор просто переназначает инструкцию для выполнения умножения. Он хранит a + a*4 в eax. Назовем это значение t.

lea eax, [rdi+rax*2] затем эффективно сохраняет a + t*2 в eax.

rax также является регистром, в котором возвращается возвращаемое значение функции.

Таким образом, возвращаемое значение будет a + t*2, то есть a + (a + a*4)*2, то есть a + a*5*2, то есть a*11.

Я так понимаю, эти две инструкции делают одно и то же: сохраняют значение, вычисленное в квадратных скобках, в регистре eax. Однако не должна ли инструкция lea загружать адрес в регистр eax по определению?

Ka Kkoi 14.05.2023 17:02

@KaKkoi Он вычисляет (загружает) эффективный адрес, указанный операндом адреса. Он ничего не загружает из вычисленного эффективного адреса, например. mov с тем же адресным операндом.

user17732522 14.05.2023 17:05
Ответ принят как подходящий

Linux использует соглашение о вызовах System V AMD64 ABI , которое передает первый целочисленный параметр в регистре RDI и возвращаемое значение в RAX. Здесь EAX достаточно, потому что он возвращает 32-битное значение. Второй параметр не используется.

LEA изначально предназначался для адресных вычислений на процессорах 8086, но также используется для целочисленной арифметики с постоянным множителем, как здесь. Постоянный коэффициент кодируется с использованием значения масштаба байта SIB в кодировке инструкции. Это может быть 1,2,4 или 8.

Итак, код можно объяснить

baz(RDI, RSI):            ; a, b
lea     eax, [rdi+rdi*4]  ; RAX = 1*a + 4*a   = 5*a
lea     eax, [rdi+rax*2]  ; RAX = 1*a + 2*RAX = 1*a + 2*(5*a)
ret                       ; return RAX/EAX = 11*a

Верхняя половина RAX (64-битное значение) автоматически очищается первым LEA, см. этот ТАК вопрос.

Понятно, но могу я спросить, как именно ЦП решает, предназначена ли инструкция lea для вычисления адреса или целого числа с использованием целочисленной арифметики с постоянным коэффициентом?

Ka Kkoi 14.05.2023 17:06

Это не так. Оба являются просто целыми значениями. Первоначально коэффициент масштабирования, вероятно, предназначался для вычисления адресов для байтов (8, фактор 1), слов (16, фактор 2), двойных слов (32, фактор 4), счетверенных слов (64, фактор 8). Это удобно для вычисления адресов в массивах, т.е. lea eax,[base+5*4] может вычислить адрес пятого (5) элемента двойного слова (4) массива с базовым адресом «база», все в одной инструкции.

zx485 14.05.2023 17:12

Что бы вычислить адрес шестого. (+0*4 = первое, +1*4 = второе и т. д.)

ikegami 14.05.2023 17:38

@ikegami: Спасибо за исправление. Конечно, массивы на уровне памяти основаны на нуле.

zx485 14.05.2023 19:50

@ zx485 lea eax,[base+5*4] - плохой пример, потому что lea потребовалось бы больше, если бы 5 был регистром, а не константой.

ecm 14.05.2023 20:27

Если вы используете LEA для фактических адресов, вы должны использовать 64-битный размер операнда, например lea rax, [rel base+5*4] для режима адресации относительно RIP. Если вы не используете относительную RIP-адресацию, то нет смысла использовать LEA, если регистры не задействованы, используйте mov eax, base+5*4 (например, в «маленькой» модели кода, такой как не-PIE в Linux, где статические адреса находятся в нижних 31 битов виртуального адресного пространства, что позволяет использовать 32-битные расширения с нулевым или знаковым расширением.)

Peter Cordes 14.05.2023 20:35

Другие вопросы по теме