llvm сообщает: неподдерживаемый встроенный asm: ввод с типом 'void *', совпадающий с выводом с типом 'int'
У меня есть следующий встроенный код сборки:
int get_year(int a, int *b, char * c)
{
int ret, t1, t2;
asm (
"addl %3, %[a] \n\t"
"movl %[a], %[t1] \n\t"
"movl $58, %%edx \n\t"
"movb %%dl, 0x04(%1) \n\t"
: [t1] "=r" (t1), "=&D" (t2)
: [a] "r" (a), "rm" (*b), "1" (c)
: "edx", "memory"
);
ret = t1;
return ret;
}
Когда я компилирую это через llvm, выдается сообщение об ошибке:
error: unsupported inline asm: input with type 'char *' matching output with type 'int'
: [a] "r" (a), "rm" (*b), "1" (c)
^
Однако функция memcpy в ядре Linux имеет такой же формат использования встроенной сборки:
void *memcpy(void *dest, const void *src, size_t n)
{
int d0, d1, d2;
asm volatile(
"rep ; movsl\n\t"
"movl %4,%%ecx\n\t"
"rep ; movsb\n\t"
: "=&c" (d0), "=&D" (d1), "=&S" (d2)
: "0" (n >> 2), "g" (n & 3), "1" (dest), "2" (src)
: "memory");
return dest;
}
и это работает правильно без каких-либо ошибок компиляции.
2 ответа
Прежде всего, если вы пытаетесь научиться работать с ассемблером, встроенный ассемблер GNU C является одним из самых сложных способов использования ассемблера. Мало того, что вы должны писать правильный asm, вы должны тратить много времени, используя эзотерический синтаксис, чтобы сообщить компилятору , что именно нужно вашему коду для ввода и вывода операндов, иначе у вас будет плохое время. Написание целых функций в ASM намного проще. Они не могут быть встроены, но в любом случае это учебное упражнение. Нормальная функция ABI намного проще, чем граница между C и встроенным ASM с ограничениями. Смотрите вики x86...
Помимо этой ошибки компиляции, у вас есть ошибка: вы clobber %[a]
даже если вы сказали gcc, что это операнд только для ввода.
Я предполагаю, что это все еще "работа в процессе", так как вы могли бы получить тот же результат с лучшим кодом. (например, используя %edx
в качестве нуля рег совершенно не требуется.) Конечно, в общем случае, когда это встроено в код, где a
может быть константой времени компиляции или известно, что она связана с чем-то другим, вы получите лучший код, просто сделав это в C (если вы не потратили много времени на создание вариантов inline-asm для различных случаев).
int get_year(int a, int *b, char * c)
{
int ret, t1, t2;
asm (
"addl %[bval], %[a] \n\t"
"movb $58, 4 + %[cval]\n\t" // c is an "offsetable" memory operand
: [t1] "=&r" (t1), [cval] "=o" (*c)
: [a] "0" (a), [bval] "erm" (*b)
: // no longer clobbers memory, because we use an output memory operand.
);
ret = t1; // silly redundancy here, could have just used a as an input/output operand and returned it, since you apparently want the value
return ret;
}
Теперь он компилируется и собирается (используя "двоичную" опцию Godbolt для фактической сборки). 4 + (%rdx)
выдает предупреждение, но собирает 4(%rdx)
, IDK, как записать смещение так, чтобы оно не выдавало ошибку, если смещение уже есть. (например, если операнд *(c+4)
так что сгенерированный асм 4 + 4(%rdx)
это не сработает +
.)
Это все еще использует трюк соответствия-вывода-операнда, но я переключился на использование памяти или общих ограничений, чтобы позволить константам времени компилятора в конечном итоге делать addl $constant, %edi
,
Это позволяет компилятору максимально использовать гибкость при встраивании. например, если звонил get_year(10, &arr[10], &some_struct.char_member)
можно использовать любой режим адресации для загрузки и хранения вместо генерации c
в одном регистре. Таким образом, встроенный вывод может в конечном итоге быть movb $58, 4+16(%rbp, %rbx)
например, вместо того, чтобы заставить его использовать 4(%reg)
,
Я могу воспроизвести проблему, если скомпилирую ваш код с помощью clang только при генерации 64-битного кода. При нацеливании на 32-битный код ошибки нет. Как сказал Майкл Петч, это говорит о том, что проблема заключается в разных размерах двух операндов.
Не совсем понятно, что будет лучшим решением, поскольку ваше утверждение asm не имеет особого смысла. Это эквивалент:
int get_year(int a, int *b, char *c) {
a += *b;
c[4] = 58;
return a;
}
Нет смысла использовать оператор сборки для того, чтобы делать то, что можно сделать более четко и более эффективно, используя код C, описанный выше. Таким образом, лучшим решением будет полностью заменить ваш код эквивалентным кодом C.
Если вы просто играете со встроенной сборкой, то эквивалентная встроенная сборка будет:
int get_year2(int a, int *b, char * c)
{
asm("addl %[b], %[a]"
: [a] "+r" (a)
: [b] "m" (*b)
: "cc");
asm("movb $58, %[c4]"
: [c4] "=rm" (c[4]));
return a;
}
Я использовал два оператора asm, потому что эти две части не связаны. Разделяя их, вы получаете больше возможностей для оптимизации. Например, если вы вызываете эту функцию, но не используете возвращаемое значение, компилятор может исключить первый оператор asm, поскольку его результат не используется и не имеет побочных эффектов.
Вместо использования соответствующих ограничений, "1"
Из-за ограничения, которое доставляло вам проблемы, я использовал модификатор ограничения "+", чтобы пометить операнд как вход и выход. Я считаю, что это работает лучше. Ограничение для [b]
операнд действительно должен быть "rm"
но, к сожалению, clang плохо обрабатывает ограничения rm.
Вы, вероятно, заметили, что я использовал только два ассемблерных оператора, где ваш пример использовал четыре. Инструкция MOVL не нужна, компилятор может обрабатывать перемещение результата в регистр возвращаемого значения, если это необходимо. Ваши последние два оператора сборки могут быть объединены в один оператор, который перемещает константу непосредственно в память без засорения регистра. Говоря о том, что ваш asm-оператор перекрывает EFLAGS, коды условий, так "cc"
должен быть указан как помеченный, но, как отмечает Питер Кордес, это необязательно для целей x86, но компилятор предполагает, что они в любом случае.