При компиляции следующего фрагмента кода (clang x86-64 -O3)
std::array<int, 5> test()
{
std::array<int, 5> values {{0, 1, 2, 3, 4}};
return values;
}
Он произвел типичная сборка, которую я ожидал
test(): # @test()
mov rax, rdi
mov ecx, dword ptr [rip + .L__const.test().values+16]
mov dword ptr [rdi + 16], ecx
movups xmm0, xmmword ptr [rip + .L__const.test().values]
movups xmmword ptr [rdi], xmm0
ret
.L__const.test().values:
.long 0 # 0x0
.long 1 # 0x1
.long 2 # 0x2
.long 3 # 0x3
.long 4 # 0x4
Однако для небольших массивов, кажется, придумал трюк?
std::array<int, 3> test()
{
std::array<int, 3> values {{0, 1, 2}};
return values;
}
Этот была соответствующей сборкой
test(): # @test()
movabs rax, 4294967296
mov edx, 2
ret
Откуда взялось это волшебное число (4294967296)? Это по существу значение, которое можно каким-то образом reinterpret_cast вернуть в массив int?





std::array<int, 3> имеет ширину 96 бит в вашей реализации. Таким образом, ABI заявляет, что он должен быть возвращен в RAX + младшие 32 бита RDX (он же EDX).
4294967296 — это 232, в шестнадцатеричном формате это $1'0000'0000. Таким образом, movabs хранит 0 в младших 32 битах RAX и 1 в старших битах RAX. mov хранит 2 в EDX (именно то, что вы хотели).
О! Спасибо sasha за предложение по редактированию и caf за это.
Забавный факт: с BMI2, вероятно, более оптимальным способом компиляции будет mov edx, 2, а затем rorx rax, rdx, 33 для создания 1<<32 из 1<<1. movabs настолько велик (10 байт), что медленно декодируется и может занимать дополнительное место в кеше uop. rorx составляет 6 байт (3-байтовый VEX + код операции + modrm + imm8), но его непосредственное значение мало. Он зависит от mov-непосредственного и может работать только на портах сдвига на процессорах Intel, но обычно это не является узким местом (особенно в пути кода, который ведет к ret, не содержащемуся в узком цикле)
@PeterCordes Я думаю, что оптимизатору можно простить то, что он не заметил эту конкретную возможность - это очень, специфичный для конкретного шаблона данных!
clang действительно ищет такие вещи, как lea r64, [reg + disp8], чтобы создать вторую большую константу вместо 2x movabs. например см. Подписанное насыщенное добавление 64-битных целых чисел?, где clang использует movabs rcx, 9223372036854775807 ; lea rax, [rcx + 1] для материализации INT64_MAX и INT64_MIN в RCX и RAX соответственно, в версиях функции, которые нуждаются в обоих. godbolt.org/z/iD6Ml8 GCC просто использует 2x movabs даже с -Os
GCC и clang в некоторых случаях ищут такие оптимизации: godbolt.org/z/wctons, например, использование sub rax, 120 для создания второй ближайшей константы вместо использования другого movabs. Однако они не очень хороши в этом, и да, вероятно, не ищут возможности вращения :P
На Godbolt вы можете навести курсор мыши на число, чтобы увидеть его в шестнадцатеричном формате. Если вы часто смотрите на asm, вы привыкнете к тому, что
4294967...составляет около 2 ^ 32, и, таким образом, это огромная подсказка, что вы должны смотреть на шестнадцатеричный код, чтобы увидеть верхние/младшие 32 бита. (Или для чисел просто ниже 2^32, что на самом деле это отрицательное 32-битное целое число.)