На главную
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
! Перед отправкой сообщения внимательно прочтите правила раздела!!!
1. Запрещается обсуждать написание вирусов, троянов и других вредоносных программ!
2. Помните, что у нас есть FAQ раздела Assembler и Полезные ссылки. Посмотрите, возможно, там уже имеется решение вашего вопроса.

3. Настоятельно рекомендуем обратить особое внимание на правила форума, которые нарушаются чаще всего:
  3.1. Заголовок темы должен кратко отражать её суть. Темы с заголовками типа "Срочно помогите!" или "Ассемблер" будут отправляться в Корзину для мусора.
  3.2. Исходники программ обязательно выделяйте тегами [code]...[/code] (одиночные инструкции можно не выделять).
  3.3. Нежелательно поднимать старые темы (не обновлявшиеся более года) без веской на то причины.

Не забывайте также про главные Правила форума!

Добро пожаловать и приятного вам общения!!! ;)
 
Модераторы: Jin X, Qraizer
  
> Scratch registers в asm-вставке GCC
    Подскажите, как указать в асм-вставке GCC, что входной регистр изменяется?
    Грубо говоря,
    ExpandedWrap disabled
        asm volatile(
          "cld\n\t"
          "rep movsl\n\t"
          :
          : "S"(src), "D"(dst), "c"(count)
        );
    Я не могу прописать "esi", "edi", "ecx" в списке clobbers, что делать-то?

    В доке пишут, что нет способа указать, что входной регистр изменяется, если не указать его в списке выходных параметров.
    Но что мне там указывать? Я же не могу написать в списке выходных параметров просто "=S", "=D", "=c" без скобок или с пустыми скобками.

    Есть такие мысли только:
    1. Указывать во входных просто "r" и в асме писать mov %0, %%esi и т.д. Но это тоже как-то тупо (лишние пересылки туда-сюда, и кстати, не факт, что получится, может тупо не хватить регистров, если их будет больше, чем 3).
    2. Сохранить и восстановить эти регистры (push/pop) внутри асм-вставки, но это тоже лишние танцы с бубном.

    И тут ещё один вопрос рождается: если я указал в clobbers callee-save регистры, мне же не нужно их сохранять/восстанавливать, верно (точнее говоря, я надеюсь), компилятор сделает это сам?
      Ну там же ниже приведён пример для "An x86 example where the string memory argument is of unknown length." для repne scasb Чем не устроил? И вообще, оно надо-то?
        Qraizer, приведённый пример не подходит. Там указывается "+D" в output'е, потому что это значение нужно и прочитать, и записать. А мне записывать результат никуда не нужно. С параметром "=c" (и далее "0") аналогично. А параметр "a" не меняется вообще.
        Я же спрашиваю про ситуацию, когда нужно прочитать, изменить, но результат никуда записывать не надо.

        Кроме того, я не понимаю, зачем там вообще используется параметр "m" (*(const char (*)[]) p). Для чего он нужен, ты понимаешь?

        Пока найден только такой вариант:
        ExpandedWrap disabled
            void *dummy1, *dummy2, *dummy3
            asm volatile(
              "cld\n\t"
              "rep movsl\n\t"
              : "=S"(dummy1), "=D"(dummy2), "=c"(dummy3)
              : "S"(src), "D"(dst), "c"(count)
            );
        Компилятор соптимизирует этот код и записывать в dummy* ничего не будет (при -O1 и выше).
        Но код становится не очень красивым в итоге.

        Я не понимаю, почему нельзя было разрешить указывать в clobbers параметры input. Очень странно, конечно.

        Добавлено
        И в примере с sumsq я не могу уловить смысл дублирования x и y: "m" (*x), "m" (*y). Прочитал несколько раз, но всё равно не доходит.
          Я отнюдь не профи в этом синтаксисе, но кое-как разобрал ситуацию и пришёл к выводу, что тут клобберсов не нужно. Смотри, мож где наглючил, так что проверяй.
          • В аутпутах у нас пусто, потому что возвращать из asm нечего. Точнее, ничего не надо.
          • В инпутах нужно указать, откуда берутся три параметра для строковой инструкции с повторителем.
          • В клоберсах нужно указать то, о чём компилятор не может знать, т.б. что меняется память. Тот факт, что esi, edi и ecx тоже меняются, компилятору и без нас известно посредством "S", "D" и "c".
          Итого, что-то типа:
          ExpandedWrap disabled
            int foo(const void *src, void *dst, int count)
            {
              asm volatile
              (
                "cld\n\t"
                "rep movsl\n\t"
                :
                : "S"(src), "D"(dst), "c"(count)
                : "memory"
              );
            }
          Результат:
          ExpandedWrap disabled
            _foo:
                    pushl   %ebp
                    movl    %esp, %ebp
                    pushl   %edi
                    pushl   %esi
                    movl    8(%ebp), %esi
                    movl    12(%ebp), %edi
                    movl    16(%ebp), %ecx
            /APP
                    cld
                    rep movsl
                    
            /NO_APP
                    popl    %esi
                    popl    %edi
                    popl    %ebp
                    ret
          Сообщение отредактировано: Qraizer -
            Усложним ему жизнь. Займём регистры. Для сравнения тут же рядом такая же функция, но без asm.
            ExpandedWrap disabled
              int foo(const void *src, void *dst, int count)
              {
                register int a = 123, b = 456, c = 789;
               
                asm volatile
                (
                   "cld\n\t"
                   "rep movsl\n\t"
                   :
                   : "S"(src), "D"(dst), "c"(count)
                   : "memory"
                );
               
                return a+b-c;
              }
               
              int bar(const void *src, void *dst, int count)
              {
                register int a = 123, b = 456, c = 789;
               
              /*  asm volatile
                (
                   "cld\n\t"
                   "rep movsl\n\t"
                   :
                   : "S"(src), "D"(dst), "c"(count)
                   : "memory"
                );
              */
                return a+b-c;
              }
            Результат:
            ExpandedWrap disabled
              _foo:
                      pushl   %ebp
                      movl    %esp, %ebp
                      pushl   %edi
                      pushl   %esi
                      pushl   %ebx
                      movl    $123, %eax
                      movl    $456, %edx
                      movl    $789, %ebx
                      movl    8(%ebp), %esi
                      movl    12(%ebp), %edi
                      movl    16(%ebp), %ecx
              /APP
                      cld
                      rep movsl
                      
              /NO_APP
                      addl    %edx, %eax
                      subl    %ebx, %eax
                      popl    %ebx
                      popl    %esi
                      popl    %edi
                      popl    %ebp
                      ret
               
                      ; ...
              _bar:
                      pushl   %ebp
                      movl    %esp, %ebp
                      movl    $123, %ecx
                      movl    $456, %eax
                      movl    $789, %edx
                      leal    (%ecx,%eax), %eax
                      subl    %edx, %eax
                      popl    %ebp
                      ret
            Во втором случае компилятор, не комплексуя, занял ecx под регистровую переменную, чтобы ни один из ebx, esi и edi, которые согласно ABI используются для регистровых переменных, не пришлось сохранять. Однако в первом случае предпочёл в этом нет смысла, т.к. ecx задействован в "с", всё равно пушить/попить, поэтому заменил его на eax.

            Добавлено
            Ещё сильнее усложним жизнь. Сделаем много регистровых переменных.
            ExpandedWrap disabled
              int foo(const void *src, void *dst, int count)
              {
                register int a = 123, b = 456, c = 789,
                             x = 321, y = 654, z = 987;
               
                asm volatile
                (
                   "cld\n\t"
                   "rep movsl\n\t"
                   :
                   : "S"(src), "D"(dst), "c"(count)
                   : "memory"
                );
               
                return (a+b-c) / (x+y-z);
              }
               
              int bar(const void *src, void *dst, int count)
              {
                register int a = 123, b = 456, c = 789,
                             x = 321, y = 654, z = 987;
               
              /*  asm volatile
                (
                   "cld\n\t"
                   "rep movsl\n\t"
                   :
                   : "S"(src), "D"(dst), "c"(count)
                   : "memory"
                );*/
               
                return (a+b-c) / (x+y-z);
              }
            Ну разве не красота
            ExpandedWrap disabled
              _foo:
                      pushl   %ebp
                      movl    %esp, %ebp
                      pushl   %edi
                      pushl   %esi
                      pushl   %ebx
                      subl    $16, %esp
                      movl    $123, %eax
                      movl    $456, %edx
                      movl    $789, %ebx
                      movl    $321, -16(%ebp)
                      movl    $654, -20(%ebp)
                      movl    $987, -24(%ebp)
                      movl    8(%ebp), %esi
                      movl    12(%ebp), %edi
                      movl    16(%ebp), %ecx
              /APP
                      cld
                      rep movsl
                      
              /NO_APP
                      leal    (%eax,%edx), %edx
                      subl    %ebx, %edx
                      movl    -16(%ebp), %eax
                      addl    -20(%ebp), %eax
                      subl    -24(%ebp), %eax
                      movl    %eax, -28(%ebp)
                      movl    %edx, %eax
                      cltd
                      idivl   -28(%ebp)
                      movl    %eax, -28(%ebp)
                      movl    -28(%ebp), %eax
                      addl    $16, %esp
                      popl    %ebx
                      popl    %esi
                      popl    %edi
                      popl    %ebp
                      ret
               
                      ; ...
               
              _bar:
                      pushl   %ebp
                      movl    %esp, %ebp
                      pushl   %edi
                      pushl   %esi
                      pushl   %ebx
                      subl    $4, %esp
                      movl    $123, %edx
                      movl    $456, %eax
                      movl    $789, %ecx
                      movl    $321, %ebx
                      movl    $654, %esi
                      movl    $987, %edi
                      addl    %eax, %edx
                      subl    %ecx, %edx
                      leal    (%ebx,%esi), %eax
                      subl    %edi, %eax
                      movl    %eax, -16(%ebp)
                      movl    %edx, %eax
                      cltd
                      idivl   -16(%ebp)
                      movl    %eax, -16(%ebp)
                      movl    -16(%ebp), %eax
                      addl    $4, %esp
                      popl    %ebx
                      popl    %esi
                      popl    %edi
                      popl    %ebp
                      ret
            Вторая функция легко заняла все шесть доступных регистров. Правда, пришлось обеспокоиться ebx, esi и edi, но, видать, с точки зрения оптимизатора это окупилось. Но вот в первой оптимизатор решил, что не окупится, и разместил x, y и z на стеке.

            Добавлено
            Как видишь, не надо компилятору указывать то, о чём он и так в курсе.
            Сообщение отредактировано: Qraizer -
              Цитата Jin X @
              Кроме того, я не понимаю, зачем там вообще используется параметр "m" (*(const char (*)[]) p). Для чего он нужен, ты понимаешь?
              Ну, он указывает, что исходные данные лежат в памяти по адресу p. Точнее – что инпутом является массив байтов неопределённого размера, на который указывает p. Зачем? А фик его знает. Возможно, оптимизатору это будет полезно знать, например, с точки зрения memory_order.

              Добавлено
              Цитата Jin X @
              И в примере с sumsq я не могу уловить смысл дублирования x и y: "m" (*x), "m" (*y). Прочитал несколько раз, но всё равно не доходит.
              Это не дублирование. Во-первых, упомянутое memory_order может помочь в расстановке барьеров. В нашем коде "memory" в клабберсах служит вообще-то для того же, но слишком грубый. Во-вторых, они отдельно описывают значения, на которые ссылаются x и y, что может помочь оптимизатору при анализе окружающего контекста и генерации его кода.
                Цитата Qraizer @
                Тот факт, что esi, edi и ecx тоже меняются, компилятору и без нас известно посредством "S", "D" и "c".
                Откуда ж ему это будет известно, когда эти "S", "D", "c" указаны в инпутах? :)

                Цитата Qraizer @
                Ну разве не красота
                Сейчас я тебе красоту покажу :)
                https://gcc.godbolt.org/z/d_TZNF
                ExpandedWrap disabled
                  long test(long src, long dst, long count)
                  {
                      __asm__ __volatile__ (
                       "cld\n\t"
                       "rep movsq\n\t"
                       :
                       : "S"(src), "D"(dst), "c"(count)
                       : "memory"
                    );
                    return src + dst + count;
                  }
                ExpandedWrap disabled
                  test(long, long, long):
                          mov     r8, rdi
                          mov     rcx, rdx
                          mov     rdi, rsi
                          mov     rsi, r8
                          cld
                          rep movsq
                   
                          add     r8, rdi
                          lea     rax, [r8+rdx]
                          ret
                Как видишь, он спокойно юзает rdi, будто он не изменился.

                Добавлено
                "memory" на это никак не влияет.
                  Ради интереса заменил на
                  ExpandedWrap disabled
                      asm volatile
                      (
                         "cld\n\t"
                         "rep movsl\n\t"
                         : "=m"(*(int(*)[count])dst)
                         : "S"(src), "D"(dst), "c"(count)
                      );
                  и получил:
                  ExpandedWrap disabled
                    _foo:
                            pushl   %ebp
                            movl    %esp, %ebp
                            pushl   %edi
                            pushl   %esi
                            pushl   %ebx
                            subl    $20, %esp
                            movl    $123, -16(%ebp)
                            movl    $456, %edx
                            movl    $789, %ebx
                            movl    $321, -20(%ebp)
                            movl    $654, -24(%ebp)
                            movl    $987, -28(%ebp)
                            movl    12(%ebp), %eax
                            movl    8(%ebp), %esi
                            movl    12(%ebp), %edi
                            movl    16(%ebp), %ecx
                    /APP
                            cld
                            rep movsl
                            
                    /NO_APP
                            addl    -16(%ebp), %edx
                            subl    %ebx, %edx
                            movl    -20(%ebp), %eax
                            addl    -24(%ebp), %eax
                            subl    -28(%ebp), %eax
                            movl    %eax, -32(%ebp)
                            movl    %edx, %eax
                            cltd
                            idivl   -32(%ebp)
                            movl    %eax, -32(%ebp)
                            movl    -32(%ebp), %eax
                            addl    $20, %esp
                            popl    %ebx
                            popl    %esi
                            popl    %edi
                            popl    %ebp
                            ret
                  Теперь даже a лежит на стеке вместо eax. Что бы это значило?.. :-? Может быть, так он делает барьер между стековым фреймом и остальной памятью, а может быть просто особенность кодогенерации, ни на что не влияющая.

                  Добавлено
                  Цитата Jin X @
                  Как видишь, он спокойно юзает rdi, будто он не изменился.
                  Гы. Значит ты не прав, что не считаешь его результатом asm-вставки.
                    Цитата Qraizer @
                    Что бы это значило?
                    Х/з, edi после как не использовался, так и не используется, поэтому, видимо, он туда ничего и не пишет. У тебя уровень оптимизации какой?

                    Цитата Qraizer @
                    cltd
                    AT&T ужасен, как так можно извратить имя cdq?

                    Цитата Qraizer @
                    Значит ты не прав, что не считаешь его результатом asm-вставки.
                    Это иллюстрация того, что если регистра нет в outputs и clobbers, то компилер имеет право думать, что регистр не изменился.
                      Цитата Jin X @
                      Это иллюстрация того, что если регистра нет в outputs и clobbers, то компилер имеет право думать, что регистр не изменился.
                      Именно. Ровно так работает и C, согласись: когда ты не используешь переменную в выражении, она и не меняется. Та и вообще любой язык так работает. И никто не будет переписывать специфический фрагмент анализатора под специфический случай специфического процессора. Если тебе без разницы, что esi, с которым связан параметр src, меняется, забей, и пусть компилятор тоже забьёт. Если же не без разницы, тогда будь добр либо укажи на это компилятору аутпутом для этой переменной, либо сделай для неё копию, и меняй её.

                      Добавлено
                      А так получается, что ты написал что-то типа
                      ExpandedWrap disabled
                        while (--count != 0)
                          *dst++ = *src++;
                      но желаешь, чтобы все три переменные остались вроде бы прежними.

                      Добавлено
                      P.S. Как вариант, ты можешь руками написать весь асмовый код. Тогда и компилятору ничего объяснять не придётся.

                      Добавлено
                      Цитата Jin X @
                      И тут ещё один вопрос рождается: если я указал в clobbers callee-save регистры, мне же не нужно их сохранять/восстанавливать, верно (точнее говоря, я надеюсь), компилятор сделает это сам?
                      Совсем забыл.
                      Да. По идее он постарается не задействовать эти регистры вообще, но если не получится, то озаботится сам.

                      Добавлено
                      Вот, для сравнения:
                      ExpandedWrap disabled
                        void* foo(const void *src, void *dst, int count)
                        {
                          asm volatile
                          (
                             "movl src,   %%esi\n\t"
                             "movl dst,   %%edi\n\t"
                             "movl count, %%ecx\n\t"
                             "cld\n\t"
                             "rep movsl\n\t"
                             : "=m"(*(int(*)[count])dst)
                             :
                             : "ecx", "esi", "edi"
                          );
                          return (int*)dst + count;
                        }
                         
                        void* bar(const void *src, void *dst, int count)
                        {
                          return (int*)dst + count;
                        }
                      Результат:
                      ExpandedWrap disabled
                        _foo:
                                pushl   %ebp
                                movl    %esp, %ebp
                                pushl   %edi
                                pushl   %esi
                                movl    12(%ebp), %eax
                        /APP
                                movl src,   %esi
                                movl dst,   %edi
                                movl count, %ecx
                                cld
                                rep movsl
                                
                        /NO_APP
                                movl    16(%ebp), %eax
                                sall    $2, %eax
                                addl    12(%ebp), %eax
                                popl    %esi
                                popl    %edi
                                popl    %ebp
                                ret
                         
                                ; ...
                         
                        _bar:
                                pushl   %ebp
                                movl    %esp, %ebp
                                movl    16(%ebp), %eax
                                sall    $2, %eax
                                addl    12(%ebp), %eax
                                popl    %ebp
                                ret
                      Первая функция в виду того, что в ней задействованы esi и edi, которые в соответствие с ABI могут являться регистровыми переменными, сохранены на входе и восстановлены на выходе, несмотря на то, что, кроме как в клобберсах, нигде не упомянуты; а вот ecx, который не входит в число зарезервированных для регистровых переменных, оставлен без внимания, несмотря на то, что тоже задействован в клобберсах и только в них. Вторая функция не заморочилась избыточными push/pop.
                      Сообщение отредактировано: Qraizer -
                        Цитата Qraizer @
                        Если же не без разницы, тогда будь добр либо укажи на это компилятору аутпутом для этой переменной, либо сделай для неё копию, и меняй её.
                        Проще было бы разрешить писать в clobbers регистры, которые указаны в inputs. Иначе это лишние телодвижения и не очень красивый код.

                        Цитата Qraizer @
                        Как вариант, ты можешь руками написать весь асмовый код. Тогда и компилятору ничего объяснять не придётся.
                        В принципе, да. Это будет проще, пожалуй, чем объявлять фиктивные доп. переменные.

                        Цитата Qraizer @
                        "=m"(*(int(*)[count])dst)
                        Зачем вот это тут? Тогда уж в clobbers это прописывать надо, т.к. outputs – это то, что компилятор должен записать, а тут мы всё делаем сами.
                          Цитата Jin X @
                          Проще было бы разрешить писать в clobbers регистры, которые указаны в inputs. Иначе это лишние телодвижения и не очень красивый код.
                          Если это позволить, то привязки инпутов просто не смогут работать. Параметры asm – это не вызов подпрограммы с передачей аргументов, это прокси между двумя разными языками с разной грамматикой. Указав, что некий, скажем, esi привязывается к src, ты делаешь между ними альяс, и естественно, что esi, меняясь, меняет и src. Если ты об этом не сообщишь компилятору, ты его просто обманешь, так что на это либо нужно забить, если это применимо, либо придётся указать esi в аутпутах, чтоб не обманывать. Ты ж фактически хочешь, чтобы на C можно было писать x++, но без изменения x. Ну выйдет же, придётся писать x+1 или сделать копию x, которой уже и делать ++.
                          Сообщение отредактировано: Qraizer -
                            Цитата Qraizer @
                            естественно, что esi, меняясь, меняет и src
                            Не вижу в этом ничего естественного. Это же не ссылка (бескомпромиссно!), а некий аналог переменной, самостоятельной, хоть и на более физическом уровне. На примере той же функции – есть передача параметров по ссылке, а есть по значению. Почему нельзя этот же принцип использовать здесь? Я не вижу никаких технических проблем в этом. Я же могу связать регистр с переменной в outputs, но только на выходе, не связывая их на входе. Почему нельзя связать на входе, не связывая на выходе? Тем более, что я могу привязать один и тот же регистр в inputs и outputs к разным переменным, поэтому аналогия с привязкой тут не очень удачная.

                            Цитата Qraizer @
                            Ты ж фактически хочешь, чтобы на C можно было писать x++, но без изменения x. Ну выйдет же, придётся писать x+1 или сделать копию x, которой уже и делать ++.
                            Правильно, inputs – это и есть создание копии (по крайней мере, во многих случаях). Поэтому я хочу сделать y = x; y++, а мне говорят: "Неее, y = x и баста! А если хочешь y++, тогда будь добр потом сделать z = y, даже если тебе z будет не нужен". Причём неважно, используется ли этот y++ внутри или изменяется вхолостую.

                            Cобственно, мы имеем то, что имеем и повлиять на это можем в лучшем случае создав запрос или самостоятельно исправив исходники (если их потом примут).
                              Цитата Jin X @
                              Зачем вот это тут? Тогда уж в clobbers это прописывать надо, т.к. outputs – это то, что компилятор должен записать, а тут мы всё делаем сами.
                              Та после экспериментов осталось. Можно ж ещё и так:
                              ExpandedWrap disabled
                                  asm volatile
                                  (
                                     "movl %1,       %%esi\n\t"
                                     "movl %0,       %%edi\n\t"
                                     "movl %[length],%%ecx\n\t"
                                     "cld\n\t"
                                     "rep movsl\n\t"
                                     : "=m"(*(int(*)[count])dst)
                                     : "m"(*(int(*)[count])src), [length]"m"(count)
                                     : "ecx", "esi", "edi"
                                  );
                              Не увидел разницы, так что нет смысла в усложнении.

                              Добавлено
                              Цитата Jin X @
                              Это же не ссылка (бескомпромиссно!), а некий аналог переменной, самостоятельной, хоть и на более физическом уровне. На примере той же функции – есть передача параметров по ссылке, а есть по значению. Почему нельзя этот же принцип использовать здесь? Я не вижу никаких технических проблем в этом.
                              Ещё раз "ещё раз". Это не передача параметров, это связывание двух разных представлений одной и той же сущности. Если что-то указано как инпут, но не указано как аутпут, значит оно инпут, и баста. Ты не можешь, имея два представления одной сущности, ожидать, что изменяя одно, оставишь другое неизменным. Jin X, ты же писал макросы? Как ты себе представляешь отвязку параметра макроса от его аргумента?

                              Добавлено
                              Цитата Jin X @
                              Правильно, inputs – это и есть создание копии (по крайней мере, во многих случаях).
                              "В большинстве случаев" так компилятору приходится поступать из-за ограничений инструкций целевого процессора. Тут, например, невозможно src из памяти сразу подать на вход movs, потому ему приходится его копировать в esi. И обратно, если это ещё и выход. К семантике asm это не имеет отношения, как её спроектировали, это другой вопрос. Возможно, что раньше, году эдак в 75-ом, её и можно было бы спроектировать иначе, но в 2020-ом нежелание это делать я вполне понимаю. И скорее наоборот, буду выступать против, если меня спросят, потому как даже представить себе не могу, сколько кода по всему миру может быть затронуто сайд-эффектами от смены дизайна.

                              Добавлено
                              Цитата Jin X @
                              Поэтому я хочу сделать y = x; y++, а мне говорят: "Неее, y = x и баста! А если хочешь y++, тогда будь добр потом сделать z = y, даже если тебе z будет не нужен". Причём неважно, используется ли этот y++ внутри или изменяется вхолостую.
                              Это конкретные специфические особенности конкретного специфического процессора в конкретном специфическом случае. Вероятно, делать универсальный asm в семействе GNU было не очень хорошей идеей, но минусов от этого решения всё-таки куда меньше плюсов. Единый движок оптимизатора, например, однозначно плюс, ибо в классических интелово-майрософтовых решениях любая asm-вставка нередко ломает оптимизацию всей функции. Как по-моему, в конкретных специфических случаях проще сделать копию, чем хакнуть дизайн. Несложно же привязать этот же инпут-параметр к другому объекту, объявив его аутпутом?
                              Сообщение отредактировано: Qraizer -
                                Цитата Qraizer @
                                Это не передача параметров, это связывание двух разных представлений одной и той же сущности. Если что-то указано как инпут, но не указано как аутпут, значит оно инпут, и баста. Ты не можешь, имея два представления одной сущности, ожидать, что изменяя одно, оставишь другое неизменным.
                                С чего ты взял, что это связывание? Если указано как инпут, пусть будет как инпут. Но почему нельзя ему быть в клобберах? См. ниже.

                                Цитата Qraizer @
                                ты же писал макросы? Как ты себе представляешь отвязку параметра макроса от его аргумента?
                                При чём тут макросы, Саш? :)
                                В макросах идёт подстановка аргумента вместо параметра в таком виде, в каком его передали, и используется "как есть". Это как #define, даже не связывание, а просто подстановка. Здесь же это не макросы, не #define'ы, здесь в регистр заносится значение заранее. Даже когда ты пишешь mov %0,%%edx при input-аргументе "r"(x), то компилятор может сделать сначала mov x,%eax, а уже потом mov %eax,%edx. Не делает он это только тогда, когда x уже в регистре, и нет разницы какой регистр использовать: https://gcc.godbolt.org/z/NBQL9X
                                А если есть разница, тогда оптимизация не срабатывает: https://gcc.godbolt.org/z/UipRRN
                                Но он не станет делать mov x,%edx (без последующего mov %0,%%edx): https://gcc.godbolt.org/z/4nEAGx (заметь, тут включена оптимизация!)
                                Повторюсь: почему ты считаешь, что регистр жёстко связывается с аргументом? Ведь если регистр указан и в inputs, и в outputs, то эта логика нарушается, ибо как можно связать регистр сразу с двумя аргументами? А если можно, значит в какой-то момент регистр отвязывается от input-аргумента, а в какой-то привязывается к output-аргументу. Почему же нельзя отвязать и не привязывать ни к чему?

                                Цитата Qraizer @
                                В большинстве случаев" так компилятору приходится поступать из-за ограничений инструкций целевого процессора. Тут, например, невозможно src из памяти сразу подать на вход movs, потому ему приходится его копировать в esi. И обратно, если это ещё и выход. К семантике asm это не имеет отношения, как её спроектировали, это другой вопрос.
                                Компилятор вообще ничего не знает о том, что происходит внутри asm-вставки, т.к. это не его дело. Я могу написать там любую абракадабру, и он схавает. Запнётся только асм-транслятор.
                                Смотри: https://gcc.godbolt.org/z/xrhNZz, ошибки нет :)
                                Для компилятора всё происходящее внутри – чёрный ящик. Он может только заменять %0 (и т.п.) на нужные значения и всё.

                                Цитата Qraizer @
                                Возможно, что раньше, году эдак в 75-ом, её и можно было бы спроектировать иначе, но в 2020-ом нежелание это делать я вполне понимаю. И скорее наоборот, буду выступать против, если меня спросят, потому как даже представить себе не могу, сколько кода по всему миру может быть затронуто сайд-эффектами от смены дизайна.
                                Нежелание делать что? :huh: Я в этом месте потерял нить твоей мысли :-? Позволять писать в clobbers регистры из input?
                                Если добавить возможность указывать одни и те же регистры и в input, и в clobbers, старый код никуда не денется, потому что там нет input-регистров в clobbers.
                                А в новом будет возможность сделать по-новому.

                                Смотри: http://www.ibiblio.org/gferg/ldp/GCC-Inlin...y-HOWTO.html#s5
                                ExpandedWrap disabled
                                          asm ("cld\n\t"
                                               "rep\n\t"
                                               "stosl"
                                               : /* no output registers */
                                               : "c" (count), "a" (fill_value), "D" (dest)
                                               : "%ecx", "%edi"
                                               );
                                Я не знаю, в чём тут прикол, но вероятно, раньше такое делать можно было, но потом (зачем-то) убрали. Мне хочется понять: ЗАЧЕМ?
                                А ты говоришь о сайд-эффектах. Тут вон отсутствие обратном несовместимости просто!
                                  Как я это всё понимаю?

                                  В блоке asm есть:
                                  • выражение на ассемблере (собственно, код на ассемблере),
                                  • параметры inputs и outputs (каждый из которых состоит из связки (регистр, память и пр. – сущность, которая обозначена строкой, именуемой в доке словом constraints) и аргумента),
                                  • параметры clobbers (содержащие только связки, но обозначаемые несколько иначе, чем в inputs и outputs).
                                  • GotoLabels, хотя они нас не очень интересуют.

                                  Как это работает?

                                  Компилятор:
                                  1. Заносит input-аргументы в input-связки, т.е. если указано "c"(x), то делает mov ecx,x; а если "r"(x), то mov ЛюбойСвободныйРегистр,x (либо ничего не делает, если x уже есть в каком-то регистре). И разумеется, запоминает, что аргумент теперь в этой связке (например, значение x в регистре ecx).
                                  2. Заменяет в выражении на ассемблере выражения %0, %1, %2 на input-связки.
                                  3. Заменяет в выражении на ассемблере %l-выражения с метками, указанными в GotoLabels, на правильные имена меток.
                                  4. Вставляет выражение на ассемблере в код.
                                  5. Заносит output-связки в output-аргументы, т.е. если указано "=a"(z), то делает mov z,eax. Либо ничего не делает (если z, скажем, не volatile), а просто запоминает, что значение z хранится в eax, запишет, когда (и если) посчитает это нужным.
                                  6. Все параметры, указанные в clobbers, объявляет неопределённым (для себя самого), т.е. отвязывает все эти параметры от каких-либо значений, ячеек памяти и т.д. Т.о., отныне он не может опираться на то, что там хранятся какие-либо известные ему данные и понимает, что там теперь может быть всё, что угодно.

                                  Соответственно, если в inputs указано "c"(x), а в outputs и в clobbers нет "c"/"ecx", значит компилятор полагает, что в ecx по прежнему лежит x. Иначе не полагает. Вот и вся логика.

                                  Ещё раз про clobbers

                                  К примеру, если мы указали в clobbers параметр "eax", а перед вставкой выражения на ассемблере в код компилятор занёс туда x, то при чтении значения из x после выражения на ассемблере он не может теперь использовать регистр eax, т.к. там уже может быть не x, а всё, что угодно. Если мы указали "memory", а перед вставкой выражения на ассемблере в код он занёс в eax значение x, то он опять не может использовать eax при чтении значения из x, т.к. значение x могло измениться, и в eax теперь будет устаревшее значение x, а не актуальное.


                                  Вот, собственно, и всё. К чему выдумывать какие-то дополнительные ограничения?
                                  Каждый блок параметров обрабатывается отдельно и причин, почему одна и та же связка не может быть и в inputs, и в clobbers, я не вижу.
                                  Возможно, это немного упрощённое описание, но сути оно не меняет.

                                  Если у меня где-то ошибка в логике, прошу указать где, в чём и почему :)
                                    Jin X, тебе осталось совсем немного. К примеру, на
                                    Цитата Jin X @
                                    Я не знаю, в чём тут прикол, но вероятно, раньше такое делать можно было, но потом (зачем-то) убрали. Мне хочется понять: ЗАЧЕМ?
                                    ты можешь дать ответ, если подумаешь и свяжешь воедино мои и свои слова.

                                    P.S. Впрочем, это не отменяет того факта, что всё это домыслы. Однако выглядят в целом логично, потому отвергать их нет нужды.
                                    Сообщение отредактировано: Qraizer -
                                      Цитата Qraizer @
                                      ты можешь дать ответ, если подумаешь и свяжешь воедино мои и свои слова.
                                      Я всё равно не нахожу ответа и логики в этом.
                                      Ты сравниваешь это с макросами и со связыванием двух сущностей (регистра и переменной, к примеру). Я не вижу причин, почему такая связь не может рушиться на выходе из asm-блока. Ведь clobbers рушат другие связи (указанный регистр с ранее связанной переменной, ещё до asm-блока, которая не фигурирует ни в inputs, ни в outputs).

                                      Ещё раз:
                                      Цитата Jin X @
                                      Ведь если регистр указан и в inputs, и в outputs, то эта логика нарушается, ибо как можно связать регистр сразу с двумя аргументами? А если можно, значит в какой-то момент регистр отвязывается от input-аргумента, а в какой-то привязывается к output-аргументу. Почему же нельзя отвязать и не привязывать ни к чему?
                                      0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                                      0 пользователей:


                                      Рейтинг@Mail.ru
                                      [ Script execution time: 0,0736 ]   [ 16 queries used ]   [ Generated: 25.06.21, 11:16 GMT ]