Простой цикл while во встроенном ассемблере GCC

Я хочу написать следующий цикл, используя расширенный встроенный ASM GCC:

long* arr = new long[ARR_LEN]();
long* act_ptr = arr;
long* end_ptr = arr + ARR_LEN;

while (act_ptr < end_ptr)
{
    *act_ptr = SOME_VALUE;
    act_ptr += STEP_SIZE;
}

delete[] arr;

Массив типа long с длиной ARR_LEN выделяется и инициализируется нулями. Цикл проходит через массив с шагом STEP_SIZE. Каждому затронутому элементу присваивается значение SOME_VALUE.

Ну, это была моя первая попытка в ГАЗе:

long* arr = new long[ARR_LEN]();

asm volatile
(
    "loop:"
    "movl %[sval], (%[aptr]);"
    "leal (%[aptr], %[incr], 4), %[aptr];"
    "cmpl %[eptr], %[aptr];"
    "jl loop;"
    : // no output
    : [aptr] "r" (arr),
      [eptr] "r" (arr + ARR_LEN),
      [incr] "r" (STEP_SIZE),
      [sval] "i" (SOME_VALUE)
    : "cc", "memory"
);

delete[] arr;

Как упоминалось в комментариях, это правда, что этот ассемблерный код больше похож на цикл do {...} while, но на самом деле он выполняет ту же работу.

Самое странное в этом фрагменте кода то, что поначалу он работал у меня нормально. Но когда позже я попытался заставить его работать в другом проекте, мне показалось, что он ничего не сделает. Я даже сделал несколько копий рабочего проекта в масштабе 1:1, снова скомпилировал и... результат все равно случайный.

Может быть, я взял неправильные ограничения для входных операндов, но на самом деле я испробовал почти все из них, и у меня не осталось никакой реальной идеи. Что меня особенно озадачивает, так это то, что в некоторых случаях это все еще работает.

Я вообще не эксперт в ASM, хотя я изучил его, когда еще учился в университете. Обратите внимание, что я не ищу оптимизации — я просто пытаюсь понять, как работает встроенная сборка. Итак, вот мой вопрос: есть ли что-то принципиально неправильное в моей попытке или я сделал здесь более тонкую ошибку? Заранее спасибо.

(Работа с g++ MinGW Win32 x86 v.4.8.1)

Обновить

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

  • используя ограничение операнда "q" вместо "r", иногда это работает, иногда нет,
  • вместо этого напишите ... : [aptr] "=r" (arr) : "0" (arr) ..., тот же результат,
  • или даже ... : [aptr] "+r" (arr) : ..., все равно.

Между тем я знаю официальную документацию практически наизусть, но я все еще не вижу свою ошибку.


person Rene R.    schedule 26.08.2013    source источник
comment
act_ptr += STEP_SIZE; может ли STEP_SZIE обработать арифметику указателя, выходящую за пределы? Даже если это без разыменования?   -  person dhein    schedule 26.08.2013
comment
Вы уже пробовали сеанс отладки gdb?   -  person mvw    schedule 26.08.2013
comment
В цикле C++ условие цикла проверяется перед выполнением тела цикла, но в версии на ассемблере вы проверяете его после тела (другими словами, ваш ассемблерный код больше похож на цикл do ... while (...)). Это означает, что вы можете (и, вероятно, делаете) записывать за пределы выделенной памяти.   -  person Some programmer dude    schedule 26.08.2013
comment
@JoachimPileborg Я думаю, что в целом вы правы, но в этом случае все должно работать нормально, если я использую jl вместо jle для зацикливания.   -  person Rene R.    schedule 26.08.2013
comment
Вы случайно не компилируете свой новый проект как 64-битный?   -  person Mats Petersson    schedule 26.08.2013
comment
@MatsPetersson Хотел бы я быть...   -  person Rene R.    schedule 26.08.2013
comment
В таком случае пора запускать отладчик...   -  person Mats Petersson    schedule 26.08.2013
comment
; не для комментариев? ты пробовал \n?   -  person Alex    schedule 26.08.2013
comment
@Alex Согласно руководству, вы можете разделите несколько инструкций символами, обычно используемыми в ассемблере для системы. В большинстве случаев работает комбинация \n\t. Также работает ;, если ассемблер допускает точку с запятой в качестве символа разрыва строки.   -  person Rene R.    schedule 26.08.2013
comment
Моя ошибка, я только что открыл вывод -S .S с vim, и он показал код после ;, как прокомментировано, но разница двух objdump -S с ; и \n не показывает различий, так что вы правы. Я получаю ошибку free() при выполнении кода (как будто aptr становится недействительным), может быть, нужно затереть используемые регистры?   -  person Alex    schedule 26.08.2013
comment
@ Алекс Хм, я посмотрю на это, спасибо. Я думал, что выбор ограничения "r" для операнда вместо указания конкретного регистра устраняет необходимость его присутствия в списке затирания.   -  person Rene R.    schedule 26.08.2013
comment
Первый movl работает, но когда я включаю leal, он не работает.   -  person Alex    schedule 26.08.2013
comment
Я изменил инструкцию lea на вариант add, но результат тот же.   -  person Rene R.    schedule 26.08.2013
comment
Отладка @MatsPetersson не принесла никаких дополнительных сведений. Вроде бы все работает как надо, но массив по-прежнему не содержит данных...   -  person Rene R.    schedule 26.08.2013
comment
У меня работает, если я использую "q" (регистры a b c d) вместо "r", по умолчанию использовалось esi. Обратите внимание, что собранная версия C отличается от вашей реализации, но если я снова попытаюсь встроить asm, будет выбрано esi и произойдет сбой.   -  person Alex    schedule 26.08.2013
comment
@Alex работает и на меня. Но я не понимаю, почему это имеет значение в данном случае. Каждая инструкция, которую я использую, должна принимать операнды общего регистра...   -  person Rene R.    schedule 26.08.2013
comment
В моем случае esi - это регистр, в котором хранится результат new и никогда не обновляется, и поэтому delete затем терпит неудачу. Думаю, лучше явно указать регистры для использования и стереть их.   -  person Alex    schedule 26.08.2013


Ответы (3)


Вы изменяете входной операнд (aptr), что не разрешено. Либо ограничьте его совпадением с выходным операндом, либо измените его на входной/выходной операнд.

person Timothy Baldwin    schedule 30.08.2013
comment
Звучит правдоподобно, но результат остается прежним... в любом случае спасибо. - person Rene R.; 04.09.2013

Вот полный код, который имеет предполагаемое поведение.

  • Обратите внимание, что код написан для 64-битной машины. Поэтому, например, %%rbx используется вместо %%ebx в качестве базового адреса для массива. По той же причине следует использовать leaq и cmpq вместо leal и cmpl.
  • Следует использовать movq, так как массив имеет тип long.
  • Тип long — это 8 байт, а не 4 байта на 64-битной машине.
  • jl в вопросе следует заменить на jg.
  • Метки регистров использовать нельзя, так как они будут заменены компилятором на 32-битную версию выбранного регистра (например, ebx).
  • Ограничение "r" использовать нельзя. "r" означает, что можно использовать любой регистр, однако для leaq допустима не любая комбинация регистров. Посмотрите здесь: режимы адресации x86

    #include <iostream>
    
    using namespace std;
    
    int main(){
    
      int ARR_LEN=20;
      int STEP_SIZE=2;
      long SOME_VALUE=100;
    
      long* arr = new long[ARR_LEN];
    
      int i;
    
    
      for (i=0; i<ARR_LEN; i++){
        arr[i] = 0;
      }
    
      __asm__ __volatile__
      (
        "loop:"
        "movq %%rdx, (%%rbx);"
        "leaq (%%rbx, %%rcx, 8), %%rbx;"
        "cmpq %%rbx, %%rax;"
        "jg loop;"
        : // no output
        : "b" (arr),
          "a" (arr+ARR_LEN),
          "c" (STEP_SIZE),
          "d" (SOME_VALUE)
        : "cc", "memory"
      );
    
      for (i=0; i<ARR_LEN; i++){
        cout << "element " << i << " is " << arr[i] << endl;
      }
    
      delete[] arr;
    
      return 0;
    
    }
    
person soofyaan    schedule 09.11.2014

Как насчет ответа, который работает как для x86, так и для x64 (хотя предполагается, что длина всегда составляет 4 байта, как в Windows)? Основное отличие от ОП заключается в использовании «+r» и (temp).

#include <iostream>

using namespace std;

int main(){

  int ARR_LEN=20;
  size_t STEP_SIZE=2;
  long SOME_VALUE=100;

  long* arr = new long[ARR_LEN];

  for (int i=0; i<ARR_LEN; i++){
    arr[i] = 0;
  }

  long* temp = arr;

   asm volatile (
      "loop:\n\t"
      "movl %[sval], (%[aptr])\n\t"
      "lea (%[aptr], %[incr], %c[size]), %[aptr]\n\t"
      "cmp %[eptr], %[aptr]\n\t"
      "jl loop\n\t"
      : [aptr] "+r" (temp)
      : [eptr] "r" (arr + ARR_LEN),
        [incr] "r" (STEP_SIZE),
        [sval] "i" (SOME_VALUE),
        [size] "i" (sizeof(long))
      : "cc", "memory"
   );

  for (int i=0; i<ARR_LEN; i++){
    cout << "element " << i << " is " << arr[i] << endl;
  }

  delete[] arr;

  return 0;
}
person David Wohlferd    schedule 09.11.2014