Деление и модуль с использованием одной инструкции divl (i386, amd64)

Я пытался придумать встроенную сборку для GCC, чтобы получить и деление и модуль, используя один divl инструкция. К сожалению, я не так хорош в сборке. Может ли кто-нибудь помочь мне в этом? Спасибо.

4 ответа

Решение

Да - divl произведет частное в eax, а остаток в edx. Используя синтаксис Intel, например:

mov eax, 17
mov ebx, 3
xor edx, edx
div ebx
; eax = 5
; edx = 2

Вы ищете что-то вроде этого:

__asm__("divl %2\n"
       : "=d" (remainder), "=a" (quotient)
       : "g" (modulus), "d" (high), "a" (low));

Хотя я согласен с другими комментаторами, что обычно GCC сделает это за вас, и вам следует избегать встроенной сборки, когда это возможно, иногда вам нужна эта конструкция.

Например, если старшее слово меньше, чем модуль, тогда безопасно выполнить деление следующим образом. Тем не менее, GCC не достаточно умен, чтобы понять это, потому что в общем случае деление 64-битного числа на 32-битное число может привести к переполнению, и поэтому он вызывает библиотечную процедуру для выполнения дополнительной работы. (Замените на 128-битный /64-битный для 64-битных ISA.)

Вы не должны пытаться оптимизировать это самостоятельно. GCC уже делает это.

volatile int some_a = 18, some_b = 7;

int main(int argc, char *argv[]) {
    int a = some_a, b = some_b;
    printf("%d %d\n", a / b, a % b);
    return 0;
}

Бег

gcc -S test.c -O

доходность

main:
.LFB11:
    .cfi_startproc
    subq    $8, %rsp
    .cfi_def_cfa_offset 16
    movl    some_a(%rip), %esi
    movl    some_b(%rip), %ecx
    movl    %esi, %eax
    movl    %esi, %edx
    sarl    $31, %edx
    idivl   %ecx
    movl    %eax, %esi
    movl    $.LC0, %edi
    movl    $0, %eax
    call    printf
    movl    $0, %eax
    addq    $8, %rsp
    .cfi_def_cfa_offset 8
    ret

Обратите внимание, что остаток, %edx, не перемещается, поскольку он также является третьим аргументом, передаваемым в printf.

РЕДАКТИРОВАТЬ: 32-разрядная версия менее запутанным. Прохождение -m32 дает

main:
    pushl   %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    subl    $16, %esp
    movl    some_a, %eax
    movl    some_b, %ecx
    movl    %eax, %edx
    sarl    $31, %edx
    idivl   %ecx
    movl    %edx, 8(%esp)
    movl    %eax, 4(%esp)
    movl    $.LC0, (%esp)
    call    printf
    movl    $0, %eax
    leave
    ret

К счастью, вам не нужно прибегать к встроенной сборке, чтобы добиться этого. gcc сделает это автоматически, когда сможет.

$ cat divmod.c

struct sdiv { unsigned long quot; unsigned long rem; };

struct sdiv divide( unsigned long num, unsigned long divisor )
{
        struct sdiv x = { num / divisor, num % divisor };
        return x;
}

$ gcc -O3 -std=c99 -Wall -Wextra -pedantic -S divmod.c -o -

        .file   "divmod.c"
        .text
        .p2align 4,,15
.globl divide
        .type   divide, @function
divide:
.LFB0:
        .cfi_startproc
        movq    %rdi, %rax
        xorl    %edx, %edx
        divq    %rsi
        ret
        .cfi_endproc
.LFE0:
        .size   divide, .-divide
        .ident  "GCC: (GNU) 4.4.4 20100630 (Red Hat 4.4.4-10)"
        .section        .note.GNU-stack,"",@progbits

Вот пример кода ядра Linux о divl

    /*
 * do_div() is NOT a C function. It wants to return
 * two values (the quotient and the remainder), but
 * since that doesn't work very well in C, what it
 * does is:
 *
 * - modifies the 64-bit dividend _in_place_
 * - returns the 32-bit remainder
 *
 * This ends up being the most efficient "calling
 * convention" on x86.
 */
#define do_div(n, base)                     \
({                              \
    unsigned long __upper, __low, __high, __mod, __base;    \
    __base = (base);                    \
    if (__builtin_constant_p(__base) && is_power_of_2(__base)) { \
        __mod = n & (__base - 1);           \
        n >>= ilog2(__base);                \
    } else {                        \
        asm("" : "=a" (__low), "=d" (__high) : "A" (n));\
        __upper = __high;               \
        if (__high) {                   \
            __upper = __high % (__base);        \
            __high = __high / (__base);     \
        }                       \
        asm("divl %2" : "=a" (__low), "=d" (__mod)  \
            : "rm" (__base), "0" (__low), "1" (__upper));   \
        asm("" : "=A" (n) : "a" (__low), "d" (__high)); \
    }                           \
    __mod;                          \
})
Другие вопросы по тегам