当前位置: 首页 > 知识库问答 >
问题:

我怎样才能指出可以使用内联ASM参数所指向的内存呢?

易弘亮
2023-03-14

考虑以下小函数:

void foo(int* iptr) {
    iptr[10] = 1;
    __asm__ volatile ("nop"::"r"(iptr):);
    iptr[10] = 2;
}

使用gcc,这将编译为:

foo:
        nop
        mov     DWORD PTR [rdi+40], 2
        ret
void foo(int* iptr) {
    iptr[10] = 1;
    __asm__ volatile ("nop"::"r"(iptr):"memory");
    iptr[10] = 2;
}
foo:
        mov     DWORD PTR [rdi+40], 1
        nop
        mov     DWORD PTR [rdi+40], 2
        ret
void foo2(int* iptr, long* lptr) {
    iptr[10] = 1;
    lptr[20] = 100;
    __asm__ volatile ("nop"::"r"(iptr):);
    iptr[10] = 2;
    lptr[20] = 200;
}

所需的行为是让编译器优化对LPTR[20]的第一次写入,而不是对IPTR[10]的第一次写入。“memory”clobber无法实现这一点,因为它意味着必须同时执行以下两个写操作:

foo2:
        mov     DWORD PTR [rdi+40], 1
        mov     QWORD PTR [rsi+160], 100 ; lptr[10] written unecessarily
        nop
        mov     DWORD PTR [rdi+40], 2
        mov     QWORD PTR [rsi+160], 200
        ret

有什么方法可以告诉接受gcc扩展asm语法的编译器,asm的输入包括指针和它可以指向的任何东西吗?

共有1个答案

濮泳
2023-03-14

这是正确的;要求将指针作为内联asm的输入,并不意味着指向的内存也是输入或输出,或者两者兼而有之。通过寄存器输入和寄存器输出,对于所有gcc所知,您的asm只是通过掩蔽低位来对齐指针,或者添加一个常数。(在这种情况下,您希望它优化掉一个死存储。)

简单的选项是ASM Volatile“Memory”Clobber1

您所要求的更窄更具体的方法是使用“虚拟”内存操作数以及寄存器中的指针。您的asm模板没有引用这个操作数(除了可能在asm注释中查看编译器选择了什么)。它告诉编译器您实际读取、写入或读取+写入哪个内存。

如果使用[]未指定大小,则会告诉GCC相对于该指针访问的任何内存都是输入、输出或输入/输出操作数。如果使用[10][some_variable],则会告诉编译器具体的大小。对于运行时可变大小,gcc实际上忽略了iptr[size+1]不是输入的一部分的优化。

GCC记录了这一点,因此支持它。我认为,如果数组元素类型与指针相同,或者如果是char,则不会违反严格别名。

(来自GCC手册)
一个x86示例,其中字符串内存参数的长度未知。

   asm("repne scasb"
    : "=c" (count), "+D" (p)
    : "m" (*(const char (*)[]) p), "0" (-1), "a" (0));

在内联asm示例中,这是一个广泛存在的bug,通常不会被检测到,因为asm被包装在一个函数中,而这个函数没有内联到任何调用程序中,这些调用程序会诱使编译器重新排序存储以进行合并,从而消除死存储。

GNU C内联asm语法是围绕向编译器描述单个指令而设计的。其目的是用“m”“=m”操作数约束告诉编译器内存输入或内存输出,然后编译器选择寻址模式。

在内联asm中编写整个循环需要注意确保编译器确实知道发生了什么(或者asm volatile加上“memory”clobber),否则,在更改周围代码或启用允许跨文件内联的链接时优化时,您会冒着损坏的风险。

另请参见使用内联程序集对数组进行循环,以使用asm语句作为循环体,但仍在C中执行循环逻辑。使用实际(非虚)“m”“=m”操作数,编译器可以在其选择的寻址模式中使用位移来展开循环。

脚注1:“memory”clobber使编译器像对待非内联函数调用一样对待asm(可以读取或写入任何内存,但转义分析已经证明没有转义的本地内存除外)。转义分析包括asm语句本身的输入操作数,也包括任何早期调用可能存储指针的任何全局变量或静态变量。因此,通常本地循环计数器不必围绕带有“memory”clobber的asm语句溢出/重新加载。

asm volatile对于确保asm没有被优化掉是必要的,即使其输出操作数未被使用(因为您需要发生未声明的写入内存的副作用)。

或者对于仅由asm读取的内存,如果相同的输入缓冲区包含不同的输入数据,则需要asm再次运行。如果没有volatile,asm语句可以从循环中CSEd出来。(“memory”clobber不会使优化器在考虑asm语句是否需要运行时将所有内存视为输入。)

没有输出操作数的ASM是隐式的volatile,但最好将其显式化。(GCC手册中有一节是关于asm volatile的)。

例如,ASM(“...sum an array...”:“=r”(sum):“r”(pointer),“r”(end_pointer):“memory”)有一个输出操作数,因此不是隐式易失性的。如果你用它

 arr[5] = 1;
 total += asm_sum(arr, len);
 memcpy(arr, foo, len);
 total += asm_sum(arr, len);

如果没有volatile,第二个asm_sum可以优化掉,假设具有相同输入操作数(指针和长度)的相同asm将产生相同的输出。对于任何不是其显式输入操作数的纯函数的asm,都需要volatile。如果它没有优化掉,那么“memory”clobber将具有要求内存同步的预期效果。

 类似资料:
  • 问题内容: 我试图“ stopPropagation”以防止单击li内的元素(链接)时关闭TwitterBootstrap导航栏下拉菜单。 在Angular中,看起来像指令是执行此操作的地方?所以我有: …但是该方法不属于元素: 我把指令与 有什么建议么? 问题答案: “当前一些指令(即ng:click)停止事件传播。这阻止了与依赖于捕获此类事件的其他框架的互操作性。” - …并且能够在没有指令的

  • 我的理解是,当编写gcc样式的内联asm时,您必须非常具体和准确地了解所有的输入和输出参数(和clobbers),这样编译器就会确切地知道如何为代码分配寄存器,以及它可以对那些寄存器的值和asm代码可能读取和/或修改的任何内存假设什么。编译器使用这些信息尽可能地优化周围的代码(如果它认为内联asm对任何东西都没有影响,甚至完全删除它)。对此不够具体可能会导致不正确的行为,因为编译器是根据您的不正确

  • 我正在使用YourKit Java Profiler分析我的webapp。webapp运行在tomcat 7 v30上,我可以看到JVM的堆大约是30兆,但Tomcat.exe正在使用200兆,并且一直在增加。 截图:http://i.imgur.com/Zh9NGJ1.png(左边是Java使用了多少内存,右边是tomcat.exe的Windows使用情况) 我尝试过为tomcat添加不同的标志

  • 问题内容: 我正在尝试扩展库以进行集成,并通过将config设置为自动(可移植)来实现,这意味着以编程方式添加元素。(我知道可以通过Hibernate 或EclipseLInk来实现,但是- 可移植性)。我也想避免仅用于此单一目的。 我可以动态创建一个,并用指定包中的元素填充它(通过Reflections库)。当我尝试将其提供给提供程序时,问题就开始了。我能想到的唯一方法是设置一个,但我想不出什么

  • 现在,我知道没有内联的保证,但是。。。 给定以下内容: 我们有: 编译成: 然而... 编译成: 这真的很让人难过。 为什么打给PMF的电话没有被内联?PMF是一个不变的表情!