Отчеты llvm: неподдерживаемый inline 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;
}

и это нормально работает без компиляции.

Ответ 1

Прежде всего, если вы пытаетесь научиться asm, то GNU C inline asm является одним из самых сложных способов использования asm. Вам не только нужно писать правильный ас, вы должны потратить много времени, используя эзотерический синтаксис, чтобы сообщить компилятору, что именно вам нужно для ввода и вывода операндов, или у вас будет плохое время. Написание целых функций в ASM намного проще. Они не могут быть встроены, но это учебное упражнение в любом случае. Нормальная функция ABI намного проще, чем граница между C и встроенной ASM с ограничениями. См. wiki...


Кроме того, что ошибка компиляции, у вас есть ошибка: 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), поэтому сгенерированный asm равен 4 + 4(%rdx), это не сработает, чтобы оставить +.)

Это все еще использует трюк совпадения-вывода-операнда, но я изменил его на использование памяти или общих ограничений, чтобы константы времени компилятора закончили выполнение addl $constant, %edi.

Это позволяет компилятору максимально гибко при встраивании. например если вызывающий абонент запускал get_year(10, &arr[10], &some_struct.char_member), он мог использовать любой режим адресации, который он хотел бы для загрузки и хранения, вместо того, чтобы генерировать c в одном регистре. Таким образом, встроенный вывод может оказаться movb $58, 4+16(%rbp, %rbx), например, вместо того, чтобы заставить его использовать 4(%reg).

Ответ 2

Я могу воспроизвести проблему, если я скомпилирую свой код с 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 clobbers EFLAGS, коды условий, поэтому "cc" следует перечислить сбитым, но, как отмечает Питер Кордес, он не нужен с целями x86, но компилятор предполагает, что они в любом случае.