GCC Умножение встроенных сборок - PullRequest
5 голосов
/ 07 октября 2010

Я пытаюсь изучить встроенную сборку GCC в Linux (x86), и мой первый эксперимент состоял в том, чтобы попытаться реализовать целочисленное обнаружение переполнения для умножения.Это кажется достаточно простым, но у него есть побочные эффекты, которых я не понимаю.

Итак, здесь я хочу умножить два беззнаковых 8-битных целых числа и посмотреть, не переполняется ли результат.В основном я просто загружаю первый операнд в регистр AL, а другой операнд в регистр BL, а затем использую инструкцию mul.Результат сохраняется как 16-битное значение в регистре AX.Поэтому я затем копирую значение в регистре AX в мою переменную C b, если оно не переполнено.Если он переполняется, я устанавливаю c в 1.

 uint8_t a = 10;
 uint8_t b = 25;
 uint8_t c = 0; // carry flag

 __asm__
 (
  "clc;"                  // Clear carry flag
  "movb %3, %%al;"        // Load b into %al
  "movb %2, %%bl;"        // Load a into %bl 
  "mul %%bl;"             // Multiply a * b (result is stored in %ax)
  "movw %%ax, %0;"        // Load result into b
  "jnc out;"              // Jump to 'out' if the carry flag is not set
  "movb $1, %1;"          // Set 'c' to 1 to indicate an overflow
  "out:"
  :"=m"(b), "=m"(c)       // Output list
  :"ir"(a), "m"(b)        // Input list
  :"%al", "%bl"           // Clobbered registers (not sure about this)
 );

Кажется, это работает нормально.Если я printf значение 'b', я получаю 250, что является правильным.Кроме того, если я изменяю начальное значение 'b' на 26, то после умножения c устанавливается в 1, что указывает на переполнение из-за курса (10 * 26> ~ uint8_t (0)).Проблема, которую я вижу, состоит в том, что переменная C a установлена ​​в 0 после умножения (или 1 в случае переполнения). Я не понимаю, почему a будет изменено вообще чем-либо, что я делаю здесь,Его даже нет в списке выходных переменных, так почему моя процедура сборки влияет на значение a?

Кроме того, я не уверен насчет списка засоренных регистров.Этот список должен информировать GCC о любых регистрах, которые использовались во время процедуры сборки, чтобы GCC не пытался использовать их неправильно.Я думаю, что мне нужно сообщить GCC, что я использовал регистры AL и BL, но как насчет регистра AX?Он неявно используется для хранения произведения двух 8-битных целых чисел, поэтому мне нужно включить его в список засоренных регистров?

Ответы [ 2 ]

7 голосов
/ 08 октября 2010

Проблема, которую я вижу, состоит в том, что переменная C a установлена ​​в 0 после умножения (или 1 при переполнении). Я не понимаю, почему a будет изменено вообщевсе, что я делаю здесьЕго даже нет в списке выходных переменных, так почему моя процедура сборки влияет на значение a?

mul %%bl, умножает AL (8 бит) на BL (8 бит), помещаярезультат в AX (16 бит).

Обратите внимание, что AL и AX не являются отдельными регистрами: AL - это только нижние 8 бит AX.

movw %%ax, %0 хранит AX (16 бит)по адресу b ... который является uint8_t.Таким образом, эта инструкция также перезаписывает следующий байт в памяти старшими 8 битами результата.В этом случае этот байт находится там, где хранится значение a (что объясняет, почему a перезаписывается 0, когда оно не переполняется, и 1, если оно переполняется).

Youнужно заменить это на movb %%al, %0, чтобы сделать только байтовое хранение младших 8 битов результата.

Я думаю, что мне нужно сообщить GCC, что я использовал регистры AL и BL, ночто насчет регистра AX?Он неявно используется для хранения произведения двух 8-битных целых чисел, поэтому мне нужно включить его в список засоренных регистров?

Да - вы должны сообщить GCC о любом регистре, который вы меняетезначение (и, как отметил nategoose в другом ответе, вы, вероятно, должны сказать, что вы также меняете флаги).Таким образом, список клоббера здесь должен быть "%ax", "%bl", "cc" (AX включает AL, поэтому вам не нужно явно упоминать AL).

2 голосов
/ 07 октября 2010

Вы должны скомпилировать свой код с опцией -S и взглянуть на файл * .s.Вся ваша сборка находится на одной строке, разделенной точкой с запятой, и я считаю, что точки с запятой начинаются с комментариев в ассемблере gnu.Вам нужно будет добавить «\ n» (или, что еще лучше, «\ n \ t») в конец всех ваших инструкций по сборке.

Возможно, вы также захотите добавить «cc» в список clobber.

Кроме того, в GCC есть способ указать, что вход является одновременно входом и выходом для сборки, которая может вас заинтересовать.

Кроме того, вероятно, лучше, если GCC определит, гдевходные данные для этого вместо того, чтобы заставлять их быть в памяти.Кажется, я помню, что встроенная сборка GCC имеет ограничение на x86, которое означает «либо в регистре, либо в памяти», что может быть использовано в тех случаях, когда это не имеет значения, но вам, вероятно, не нужно иметь много команд «mov» вначало и конец вашей встроенной сборки, потому что одна из самых важных задач GCC - это определение наилучших наборов инструкций перемещения, которые нужно поместить между фактическими инструкциями вычисления.Например, в вашем коде для GCC лучше всего просто сохранить константы 10 и 25 в регистрах, которые вы используете для начала.

...