Нестокодированный скретч-регистр во встроенной сборке
Это не прямой ответ на первоначальный вопрос, но с тех пор я продолжаю гуглить это в этом контексте и с тех пор https://stackoverflow.com/a/6683183/895245 принято, я попытаюсь представить возможное улучшение этого ответа.
Улучшение заключается в следующем: по возможности следует избегать жесткого кодирования своих чистых регистров, чтобы получить распределитель регистров.больше свободы.
Таким образом, в качестве учебного примера, который бесполезен на практике (может быть сделано в одном lea (%[in1], %[in2]), %[out];
), следующий жестко закодированный код регистрационного регистра:
bad.c
#include <assert.h>
#include <inttypes.h>
int main(void) {
uint64_t in1 = 0xFFFFFFFF;
uint64_t in2 = 1;
uint64_t out;
__asm__ (
"mov %[in2], %%rax;" /* scratch = in2 */
"add %[in1], %%rax;" /* scratch += in1 */
"mov %%rax, %[out];" /* out = scratch */
: [out] "=r" (out)
: [in1] "r" (in1),
[in2] "r" (in2)
: "rax"
);
assert(out == 0x100000000);
}
может скомпилироваться во что-то более эффективное, если вместо этого вы будете использовать эту версию без жесткого кода:
good.c
#include <assert.h>
#include <inttypes.h>
int main(void) {
uint64_t in1 = 0xFFFFFFFF;
uint64_t in2 = 1;
uint64_t out;
uint64_t scratch;
__asm__ (
"mov %[in2], %[scratch];" /* scratch = in2 */
"add %[in1], %[scratch];" /* scratch += in1 */
"mov %[scratch], %[out];" /* out = scratch */
: [scratch] "=&r" (scratch),
[out] "=r" (out)
: [in1] "r" (in1),
[in2] "r" (in2)
:
);
assert(out == 0x100000000);
}
, поскольку компилятор может свободно выбирать любой регистр, который ему нужен, вместо rax
,
Обратите внимание, что в этом примере мы должны были пометить скретч как ранний регистр Clobber с помощью &
, чтобы сохранитьВместо того, чтобы поместить его в тот же регистр, что и вход, я объяснил это более подробно по адресу: Когда использовать ограничение раннего коббера в расширенной встроенной сборке GCC? Этот пример также не работает в проверенной мной реализациибез &
.
Протестировано в Ubuntu 18.10 amd64, GCC 8.2.0, скомпилировано и запущено с:
gcc -O3 -std=c99 -ggdb3 -Wall -Werror -pedantic -o good.out good.c
./good.out
Нестко закодированные чистые регистры также упоминаются в Руководство GCC 6.45.2.6 «Регистры сгустков и царапин», хотя их пример слишком сложен, чтобы простые смертные могли сразу принять:для оператора asm альтернатива состоит в том, чтобы определить переменную и сделать ее выходом раннего клоббера, как в случае с a2 и a3 в примере ниже.Это дает распределителю регистров компилятор больше свободы.Вы также можете определить переменную и сделать ее выводом, привязанным к входу, как с a0 и a1, привязанными соответственно к ap и lda.Конечно, с привязанными выходами ваш ассм не может использовать входное значение после изменения выходного регистра, так как это один и тот же регистр.Более того, если вы опустите ранний клоббер на выходе, возможно, что GCC может выделить тот же регистр для другого из входов, если GCC может доказать, что они имели одинаковое значение при входе в asm.Вот почему a1 имеет ранний клоббер.Можно предположить, что его связанный ввод, lda, имеет значение 16 и без раннего клоббера разделяет тот же регистр, что и% 11.С другой стороны, ap не может быть таким же, как любой другой вход, поэтому ранний клоббер на a0 не нужен.Это также не желательно в этом случае.Ранний клоббер на a0 заставил бы GCC выделить отдельный регистр для входа "m" ( (const double () []) ap).Обратите внимание, что привязка ввода к выходу - это способ установки инициализированного временного регистра, модифицированного оператором asm.GCC предполагает, что вход, не связанный с выходом, остается неизменным, например, «b» (16) ниже устанавливает% 11–16, и GCC может использовать этот регистр в следующем коде, если значение 16 оказалось необходимым.Вы можете даже использовать нормальный вывод asm для нуля, если все входы, которые могут совместно использовать один и тот же регистр, используются до того, как будут использоваться нуля.Регистры VSX, перекрытые оператором asm, могли бы использовать этот метод, за исключением ограничения GCC на количество параметров asm.
static void
dgemv_kernel_4x4 (long n, const double *ap, long lda,
const double *x, double *y, double alpha)
{
double *a0;
double *a1;
double *a2;
double *a3;
__asm__
(
/* lots of asm here */
"#n=%1 ap=%8=%12 lda=%13 x=%7=%10 y=%0=%2 alpha=%9 o16=%11\n"
"#a0=%3 a1=%4 a2=%5 a3=%6"
:
"+m" (*(double (*)[n]) y),
"+&r" (n), // 1
"+b" (y), // 2
"=b" (a0), // 3
"=&b" (a1), // 4
"=&b" (a2), // 5
"=&b" (a3) // 6
:
"m" (*(const double (*)[n]) x),
"m" (*(const double (*)[]) ap),
"d" (alpha), // 9
"r" (x), // 10
"b" (16), // 11
"3" (ap), // 12
"4" (lda) // 13
:
"cr0",
"vs32","vs33","vs34","vs35","vs36","vs37",
"vs40","vs41","vs42","vs43","vs44","vs45","vs46","vs47"
);
}