Почему gcc компилирует f (1199) и f (1200) по-разному?

Что заставляет GCC 7.2.1 на ARM использовать нагрузку из памяти (lr) для определенных констант и немедленную (mov) в некоторых других случаях? Конкретно, я вижу следующее:

GCC 7.2.1 для ARM компилирует это:

extern void abc(int);
int test() { abc(1199); return 0; }

... в это:

test():
  push {r4, lr}
  ldr r0, .L4  // ??!
  bl abc(int)
  mov r0, #0
  pop {r4, lr}
  bx lr
.L4:
  .word 1199

и это:

extern void abc(int);
int test() { abc(1200); return 0; }

... в это:

test():
  push {r4, lr}
  mov r0, #1200  // OK
  bl abc(int)
  mov r0, #0
  pop {r4, lr}
  bx lr

Сначала я ожидал, что 1200 будет своего рода уникальным обрезанием, но есть и другие срезы, подобные этому при 1024 (1024 дает a mov r0, #1024, тогда как 1025 использует ldr) и при других значениях.

Почему GCC использует нагрузку из памяти для извлечения константы, а не с помощью немедленного?

Ответ 1

Это связано с тем, как константные операнды кодируются в наборе команд ARM. Они кодируются как (без знака) 8-битная константа в сочетании с 4-битным полем вращения - 8-битное значение будет повернуто в 2 раза больше значения в этом 4-битном поле. Поэтому любое значение, которое вписывается в эту форму, может использоваться как постоянный аргумент.

Константа 1200 равна 10010110000 в двоичном формате, поэтому ее можно кодировать как 8-битную константу 01001011 в сочетании с поворотом 4.

Константа 1199 равна 10010101111 в двоичном формате, поэтому нет способа разместить ее в постоянном операнде ARM.