2015-11-12 1 views
1

У меня был распайка университета надзора, чтобы написать функцию на ассемблере, которыйКак сборщик linux умножает и делит 64-битные номера?

будет принимать три 32-битовое беззнаковое число, а, б, г и

возвращает результат (а * б)/д

с декларированием этой функции:

unsigned int muldiv(const unsigned int a, 
        const unsigned int b, 
        const unsigned int d); 

Обратите внимание, что мы хотим быть уверены, что никакое ненужное переполнение или сгущенный не происходит. Например,

, если а = 2^31, Ь = 2^31, д = 2^31,

ответ должен быть 2^31, несмотря на то, что а * Ь будет переполнение. (См. Дополнительные пояснения по этому вопросу ниже)

Теперь я написал простую функцию в c, которая работает, затем скомпилировала ее в машинный код, а затем разобрала обратно на ассемблерный код и, наконец, удалила ненужные инструкции.

Мой последний кусок сборки код:

muldiv: 
    imulq %rsi, %rax 
    xorl %edx, %edx 
    divq %rcx 
    ret 

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

Следовательно, может кто-нибудь объяснить мне, почему этот код работает (или, возможно, это не делает?), В частности:

  • почему DIVQ% RCX инструкция использует только один регистр? Я предполагаю, что это часть разделения, так как он знает, какие два аргумента?
  • Как узнать, что, когда я вызываю muldiv из другого места, аргументы a, b и d сохраняются в регистрах% rsi /% rax/e.t.c, а не где-то еще?
  • Почему xorl% edx,% edx необходим? При удалении я получаю ошибку времени выполнения.

  • Как это сделать умножение на длинные длинные числа, используя только одну инструкцию, если машина может работать только на 32-битных номерах?

Разъяснение переполнения и опустошения: Эта функция должна возвращать результат, как если бы мы действуем на чисел без знака 64-битных. Код в C выглядит следующим образом:

// NOTE: when compiled to assembly code, I removed A LOT of instructions, 
// but it still works 
unsigned int muldiv(const unsigned int a, 
    const unsigned int b, 
    const unsigned int d) { 

    const unsigned long long la = a; 
    const unsigned long long lb = b; 
    const unsigned long long ld = d; 

    const unsigned long long ab = la * lb; 
    const unsigned long long ab_over_d = ab/ld; 
    return (unsigned int) ab_over_d; 
} 

И это сработало, когда называют таким образом:

#include "muldiv.h" 

int main(void) { 
    unsigned int a = (1 << 31); 
    unsigned int b = (1 << 31); 
    unsigned int d = (1 << 31); 

    unsigned int result = muldiv(a, b, d); 
    printf("%u\n", result); // prints (1 << 31), which is correct. 

    return 0; 
} 
+2

Вы читали справочник по программистам Intel? Это длинная книга, но действительно интересная ... и ответы на некоторые из ваших вопросов. Вы также захотите понять, какой ABI вы используете. Я найду некоторые ссылки ... (http://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-instruction- набор-ссылка ручной 325383.pdf) – JCx

ответ

4

Почему команда divq% rcx использует только один регистр? Я предполагаю, что это часть разделения, так как он знает, какие два аргумента использовать?

Он использует неявный 128-битный дивиденд из пары регистров rdx:rax. См. Ссылку на набор инструкций для описания операции.

, как он знает, что, когда я называю muldiv из другого места, аргументы а, б, г, хранятся в регистрах% RSi /% RAX/e.t.c, не где-то еще?

Это определено вызывающим соглашением. См. wikipedia для сводки.

Почему xorl% edx,% edx необходим? При удалении я получаю ошибку времени выполнения.

См. Пункт № 1, выше. rdx имеет верхние 64 бита дивиденда, поэтому он должен быть очищен для беззнакового деления.

Как это сделать умножение на длинные длинные номера, используя только одну команду , если машина может работать только на 32-битных номерах?

Вы использовали 64-битный режим, так что это не так. Кроме того, команды mul и div имеют двойную толщину, поэтому вы даже получаете 128-битные версии в режиме 64 бит и 64-разрядные версии в 32-битном режиме. Опять же, см. Ссылку на набор команд.

4

Это немного частичного ответа, я боюсь, мне придется посмотрите на это снова, когда у меня есть момент (рабочие вызовы!), но, надеюсь, помогает:

Почему команда divq% rcx использует только один регистр? Я предполагаю, что это часть разделения, так как она знает, какие два аргумента использовать?

http://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-instruction-set-reference-manual-325383.pdf Vol. 2A 3-257

Делит значение без знака в AX, DX: AX, EDX: EAX или RDX: RAX регистров (дивидендов) с помощью операнда-источника (делитель) и сохраняет результат в AX (AH: AL), DX: AX, EDX: EAX или RDX: RAX. Исходный операнд может быть регистром общего назначения или местом памяти. Действие этой команды зависит от размера операнда (дивиденд/делитель). Разделение с использованием 64-битного операнда доступно только в режиме с 64-битным режимом.

Нецелые результаты усечены (нарезаны) по направлению к 0. Остальная часть всегда меньше делителя по величине. Переполнение указывается с #DE (ошибка деления) исключение, а не с флагом

CF, как он знает, что, когда я называю muldiv из другого места, аргументы а, б, г, хранятся в регистры% rsi /% rax/etc, а не где-то еще?

Это компилятор и окружающая среда. Вам необходимо найти ABI для своей среды: What is Application Binary Interface (ABI)?

Почему xorl% edx,% edx необходимо? При удалении я получаю ошибку времени выполнения.

Он нули регистра DX.

Как это сделать умножение на длинные длинные числа, используя только одну инструкцию, если машина может работать только на 32-битных номерах?

Регистр RAX - это 64-битный регистр. Итак, почему вы думаете, что машина работает на 32-битных номерах? Может быть, ваши тестовые примеры должны расширяться?

Смежные вопросы