Ваша лучшая ставка - обычная mov
с префиксом, который G CC никогда не выдаст самостоятельно. т.е. создайте новую кодировку mov
, которая включает обязательный префикс перед любым другим mov
.
. Или, если вы изменяете G CC и as
, вы можете добавить новый мнемони c, который просто использует недопустимые (в 64-битном режиме) однобайтовые коды операций для версий mov
источника памяти, памяти и непосредственного источника. AMD64 освободил несколько кодов операций, включая инструкции BCD, такие как AAM, и большинство регистров сегмента push / pop. (Вы все еще можете mov
в / из Sregs, но они не тратят впустую 1 код операции на Sreg.)
Предполагая, что моя рабочая нагрузка включает только целые числа, то есть, скорее всего, я не буду использовать регистры xmm и mmx
Плохое предположение для XMM: G CC агрессивно использует 16-байтовый movaps
/ movups
вместо копирования структур 4 или 8 байтов за раз. Нередко встречаются векторные команды mov в скалярном целочисленном коде как часть встроенного расширения небольшой известной длины memcpy
или структуры / массива init. Кроме того, эти mov
инструкции имеют как минимум 2-байтовые коды операций (SSE1 0F 28 movaps
, поэтому префикс перед простым mov
имеет тот же размер, что и ваша идея).
Однако вы правы в отношении MMX regs. Я не думаю, что современный G CC когда-либо будет излучать movq mm0, mm1
или вообще использовать MMX, если вы не используете встроенные MMX. Определенно нет при нацеливании на 64-битный код.
Также mov
в / из управляющих регистров (0f 21/23 /r
) или регистров отладки (0f 20/22 /r
) оба mov
мнемони c, но g cc определенно никогда не будут излучать ни сами по себе. Доступно только с операндами регистра GP в качестве операнда, который не является регистром отладки или управления. Так что это технически ответ на ваш заглавный вопрос, но, вероятно, не тот, который вы на самом деле хотите.
G CC не анализирует строку встроенного шаблона asm, он просто включает ее в вывод текста asm передать ассемблеру после замены %number
операндов. Таким образом, сам G CC не является препятствием для выдачи произвольного asm-текста с использованием встроенного asm.
И вы можете использовать .byte
для вывода произвольного машинного кода.
Возможно, хорошим вариантом будет использовать 0E
байт в качестве префикса для вашей специальной кодировки mov
, которую вы собираетесь специально декодировать в GEM. 0E
равно push CS
в 32-битном режиме , недопустимо в 64-битном режиме. G CC никогда не будет излучать.
Или просто префикс F2 repne
; G CC никогда не выдаст repne
перед кодом операции mov
(если он не применяется), только movs
. (F3 rep
/ repe
означает xrelease при использовании в инструкции назначения памяти, поэтому не используйте его. https://www.felixcloutier.com/x86/xacquire: xrelease говорит, что F2 repne - это префикс xacquire при использовании с lock
ed инструкции, которые не включают mov
в память, поэтому там будут игнорироваться.)
Как обычно, префиксы, которые не применяются, не имеют документированного поведения, но на практике процессоры, которые не rep
/ repne
не может игнорировать это. Некоторые будущие процессоры могут понимать, что это означает что-то особенное, и это именно то, что вы делаете с GEM.
Выбор .byte 0x0e;
вместо repne;
может быть лучшим выбором, если вы хотите guard против случайного оставления этих префиксов в сборке, которую вы запускаете на реальном процессоре . (Это будет #UD -> SIGILL в 64-битном режиме или, как правило, * * * * * sh не испортит стек в 32-битном режиме.) Но если вы do хотите иметь возможность запустить точный тот же двоичный файл на реальном процессоре, с тем же выравниванием кода и всем остальным, тогда игнорируемый префикс REP идеален.
Преимущество использования префикса перед стандартной mov
инструкцией позволить ассемблеру кодировать для вас операнды:
template<class T>
void fancymov(T& dst, T src) {
// fixme: imm -> mem needs a size suffix, defeating template
// unless you use Intel-syntax where the operand includes "dword ptr"
asm("repne; movl %1, %0"
#if 1
: "=m"(dst)
: "ri" (src)
#else
: "=g,r"(dst)
: "ri,rmi" (src)
#endif
: // no clobbers
);
}
void test(int *dst, long src) {
fancymov(*dst, (int)src);
fancymov(dst[1], 123);
}
(Много альтернативные ограничения позволяют компилятору выбирать либо назначение reg / mem, либо источник reg / mem. На практике он предпочитает назначение регистра даже когда это будет стоить другой инструкции, чтобы сделать свой собственный магазин, так что это отстой.)
В проводнике компилятора Godbolt , для версии, которая позволяет только назначение памяти:
test(int*, long):
repne; movl %esi, (%rdi) # F2 E9 37
repne; movl $123, 4(%rdi) # F2 C7 47 04 7B 00 00 00
ret
Если вы хотите, чтобы это можно было использовать для нагрузок, я думаю, вы бы необходимо сделать 2 отдельные версии функции и вручную использовать загруженную версию или версию хранилища, где это необходимо, поскольку G CC, кажется, хочет использовать reg, reg всякий раз, когда это возможно.
Или с версия, разрешающая регистровые выходы (или другая версия, которая возвращает результат в виде T
, см. ссылку Godbolt):
test2(int*, long):
repne; mov %esi, %esi
repne; mov $123, %eax
movl %esi, (%rdi)
movl %eax, 4(%rdi)
ret