GCC内联程序集:";g";约束和参数大小 背景

GCC内联程序集:";g";约束和参数大小 背景,c,gcc,assembly,inline-assembly,C,Gcc,Assembly,Inline Assembly,我知道用内联汇编解决以下问题是个坏主意。我目前正在学习内联汇编作为linux内核类的一部分,这是该类任务的一部分 设置 下面的开头是一段几乎正确的代码,但不是错误。它是一个函数,仅使用内联程序集将src的子字符串从索引s_idx开始,到索引e_idx结束(独占)复制到预先分配的dest中 static inline char *asm_sub_str(char *dest, char *src, int s_idx, int e_idx) { asm("addq %q2, %%rsi;"

我知道用内联汇编解决以下问题是个坏主意。我目前正在学习内联汇编作为linux内核类的一部分,这是该类任务的一部分

设置 下面的开头是一段几乎正确的代码,但不是错误。它是一个函数,仅使用内联程序集将
src
的子字符串从索引
s_idx
开始,到索引
e_idx
结束(独占)复制到预先分配的
dest

static inline char *asm_sub_str(char *dest, char *src, int s_idx, int e_idx) {
  asm("addq %q2, %%rsi;"  /* Add start index to src (ptrs are 64-bit) */
      "subl %k2, %%ecx;"  /* Get length of substr as e - s (int is 32-bit) */
      "cld;"              /* Clear direction bit (force increment) */
      "rep movsb;"        /* Move %ecx bytes of str at %esi into str at %edi */
      : /* No Ouputs */
      : "S" (src), "D" (dest), "g" (s_idx), "c" (e_idx)
      : "cc", "memory"
      );

  return dest;
}
此代码的问题是第二个输入参数的约束。使用
gcc
s默认优化和
-ggdb
编译时,将生成以下程序集:

Dump of assembler code for function asm_sub_str:
   0x00000000004008e6 <+0>:     push   %rbp
   0x00000000004008e7 <+1>:     mov    %rsp,%rbp
   0x00000000004008ea <+4>:     mov    %rdi,-0x8(%rbp)
   0x00000000004008ee <+8>:     mov    %rsi,-0x10(%rbp)
   0x00000000004008f2 <+12>:    mov    %edx,-0x14(%rbp)
   0x00000000004008f5 <+15>:    mov    %ecx,-0x18(%rbp)
   0x00000000004008f8 <+18>:    mov    -0x10(%rbp),%rax
   0x00000000004008fc <+22>:    mov    -0x8(%rbp),%rdx
   0x0000000000400900 <+26>:    mov    -0x18(%rbp),%ecx
   0x0000000000400903 <+29>:    mov    %rax,%rsi
   0x0000000000400906 <+32>:    mov    %rdx,%rdi
   0x0000000000400909 <+35>:    add    -0x14(%rbp),%rsi
   0x000000000040090d <+39>:    sub    -0x14(%rbp),%ecx
   0x0000000000400910 <+42>:    cld    
   0x0000000000400911 <+43>:    rep movsb %ds:(%rsi),%es:(%rdi)
   0x0000000000400913 <+45>:    mov    -0x8(%rbp),%rax
   0x0000000000400917 <+49>:    pop    %rbp
   0x0000000000400918 <+50>:    retq 
函数asm\u sub\u str的汇编程序代码转储: 0x00000000004008e6:推送%rbp 0x00000000004008e7:mov%rsp,%rbp 0x00000000004008ea:mov%rdi,-0x8(%rbp) 0x00000000004008ee:mov%rsi,-0x10(%rbp) 0x00000000004008f2:mov%edx,-0x14(%rbp) 0x00000000004008f5:mov%ecx,-0x18(%rbp) 0x00000000004008f8:mov-0x10(%rbp),%rax 0x00000000004008fc:mov-0x8(%rbp),%rdx 0x0000000000400900:mov-0x18(%rbp),%ecx 0x0000000000400903:mov%rax,%rsi 0x0000000000400906:mov%rdx,%rdi 0x0000000000400909:添加-0x14(%rbp),%rsi 0x000000000040090d:子-0x14(%rbp),%ecx 0x0000000000400910:cld 0x00000000000400911:代表movsb%ds:(%rsi),%es:(%rdi) 0x00000000000400913:mov-0x8(%rbp),%rax 0x00000000000400917:弹出%rbp 0x0000000000400918:retq 这与当第二个输入参数的约束设置为
“m”
而不是
“g”
时生成的程序集相同,这使我相信编译器正在有效地选择
“m”
约束。在使用gdb逐步查看这些指令时,我发现有问题的指令是
+35
,它将起始偏移量索引
s_idx
添加到
%rsi
中的
src
指针。当然,问题是
s_idx
只有32位,而静态数据中该位置的64位整数的上4个字节不一定是0。在我的机器上,它实际上是非零的,并导致加法混淆了
%rsi
的上4个字节,从而导致指令
+43
中出现segfault

问题 当然,上述问题的解决方案是将参数
2
的约束更改为
“r”
,这样它就被放在自己的64位寄存器中,其中前4个字节被正确地归零,并称之为一天。相反,我的问题是,在这种情况下,当表达式
“%q2”
指示参数
2
的值将用作64位值时,为什么gcc将
“g”
约束解析为
“m”
,而不是
“r”

我不知道gcc是如何解析内联汇编的,我知道在汇编中键入并没有什么意义,但我认为gcc可以识别
s_idx
long
的有效隐式转换,当它在第一条内联指令中用作64位值时。FWIW,如果我显式地将
“g”(s_idx)
更改为
“g”((长)s_idx)
,那么gcc将
“g”
约束解析为
“r”
,因为
(长)s_idx
是一个临时值。我认为gcc也可以隐式地做到这一点

但我认为gcc可以识别
s_idx
long
的有效隐式转换,当它在第一条内联指令中用作64位值时

否,在编译周围的代码时,gcc只查看约束,根本不查看
asm
模板字符串。填充
%
模板操作数的gcc部分完全独立于周围代码的寄存器分配和代码生成

任何内容都不会检查是否健全,也不会理解模板操作数正在使用的上下文。可能您有一个16位输入,希望将其复制到向量寄存器中,并使用
vmovd%k[input]、%%xmm0
/
vpbroadcastw%%xmm0、%%ymm0
。上面的16位被忽略,因此您不希望gcc浪费时间0或为您扩展它。但是您肯定希望使用
vmovd
而不是
vpinsrw$0、%[input]、%%xmm0
,因为这将是更多的UOP,并且具有错误的依赖关系。对于所有gcc知道或关心的事情,您都可以在asm注释行中使用操作数,如
“#输入的低位字=%h2\n

GNU C内联asm的设计使约束告诉编译器它需要知道的一切。因此,您需要手动将
s_idx
转换为
long

您不需要强制转换ECX的输入,因为
sub
指令将对结果进行零隐式扩展(转换为RCX)。您的输入是有符号类型,但您可能希望差异始终为正

必须始终假定寄存器输入具有超出输入类型宽度的高垃圾。这类似于x86-64 System V调用约定中的函数arg,但是(我假设)没有关于扩展到32位的不成文规则。(请注意,在函数内联之后,asm语句的输入可能不是函数参数。您不想使用
\uuuuu attribute\uuuuuuuu((noinline))
,正如我所说,它也不会有任何帮助。)


这让我相信编译器正在有效地选择“m”约束

是的,
gcc-O0
会在每个C语句之间将所有内容溢出到内存中(因此,如果在断点处停止,您可以使用调试器对其进行更改)。因此,内存操作数是编译器最有效的选择。它需要加载
    ...
    addq $1234, %rsi; 
    subl $1234, %ecx; 
    rep movsb
    ...
char *asm_sub_str_fancyconstraints(char *dest, char *src, int s_idx, int e_idx) {
  asm (
      "addq %[s_idx], %%rsi; \n\t"  /* Add start index to src (ptrs are 64-bit) */
      "subl %k[s_idx], %%ecx;          \n\t"  /* Get length of substr as e - s (int is 32-bit) */

      // the calling convention requires DF=0, and inline-asm can safely assume it, too
      // (it's widely done, including in the Linux kernel)
      //"cld;"              /* Clear direction bit (force increment) */

      "rep movsb;                \n\t"        /* Move %ecx bytes of str at %esi into str at %edi */
      : [src]"+&S" (src), [dest]"+D" (dest), [e_idx]"+c" (e_idx)
        , "=m" (*(char (*)[]) dest)     // dummy output: all of dest
      : [s_idx]"g" ((long long)s_idx)
        , "m" (*(const char (*)[]) src) // dummy input: tell the compiler we read all of src[0..infinity]
      : "cc"
      );

  return 0; // asm statement not optimized away, even without volatile,
            //  because of the memory output.
            // Just like dest++; could optimize away, but *dest = 0; couldn't.
}
asm_sub_str_fancyconstraints:
    movslq  %edx, %rdx        # from the (long long)s_idx
    xorl    %eax, %eax        # from the return 0, which I changed to test that it doesn't optimize away
    addq %rdx, %rsi; 
    subl %edx, %ecx;          # your code zero-extends (e_idx - s_idx)
    rep movsb;                

    ret