繁体   English   中英

如何从内联asm访问C结构/变量?

[英]How to access C struct/variables from inline asm?

考虑以下代码:

    int bn_div(bn_t *bn1, bn_t *bn2, bn_t *bnr)
  {
    uint32 q, m;        /* Division Result */
    uint32 i;           /* Loop Counter */
    uint32 j;           /* Loop Counter */

    /* Check Input */
    if (bn1 == NULL) return(EFAULT);
    if (bn1->dat == NULL) return(EFAULT);
    if (bn2 == NULL) return(EFAULT);
    if (bn2->dat == NULL) return(EFAULT);
    if (bnr == NULL) return(EFAULT);
    if (bnr->dat == NULL) return(EFAULT);


    #if defined(__i386__) || defined(__amd64__)
    __asm__ (".intel_syntax noprefix");
    __asm__ ("pushl %eax");
    __asm__ ("pushl %edx");
    __asm__ ("pushf");
    __asm__ ("movl %eax, (bn1->dat[i])");
    __asm__ ("xorl %edx, %edx");
    __asm__ ("divl (bn2->dat[j])");
    __asm__ ("movl (q), %eax");
    __asm__ ("movl (m), %edx");
    __asm__ ("popf");
    __asm__ ("popl %edx");
    __asm__ ("popl %eax");
    #else
    q = bn->dat[i] / bn->dat[j];
    m = bn->dat[i] % bn->dat[j];
    #endif
    /* Return */
    return(0);
  }

数据类型uint32基本上是无符号long int或uint32_t无符号32位整数。 bnint类型可以是无符号short int(uint16_t)或uint32_t,具体取决于是否可以使用64位数据类型。 如果有64位可用,则bnint为uint32,否则为uint16。 这样做是为了捕获代码其他部分的进位/溢出。 结构bn_t定义如下:

typedef struct bn_data_t bn_t;
struct bn_data_t
  {
    uint32 sz1;         /* Bit Size */
    uint32 sz8;         /* Byte Size */
    uint32 szw;         /* Word Count */
    bnint *dat;         /* Data Array */
    uint32 flags;       /* Operational Flags */
  };

该函数从我的源代码的第300行开始。 因此,当我尝试编译/制作它时,出现以下错误:

system:/home/user/c/m3/bn 1036 $$$ ->make
clang -I. -I/home/user/c/m3/bn/.. -I/home/user/c/m3/bn/../include  -std=c99 -pedantic -Wall -Wextra -Wshadow -Wpointer-arith -Wcast-align -Wstrict-prototypes  -Wmissing-prototypes -Wnested-externs -Wwrite-strings -Wfloat-equal  -Winline -Wunknown-pragmas -Wundef -Wendif-labels  -c /home/user/c/m3/bn/bn.c
/home/user/c/m3/bn/bn.c:302:12: warning: unused variable 'q' [-Wunused-variable]
    uint32 q, m;        /* Division Result */
           ^
/home/user/c/m3/bn/bn.c:302:15: warning: unused variable 'm' [-Wunused-variable]
    uint32 q, m;        /* Division Result */
              ^
/home/user/c/m3/bn/bn.c:303:12: warning: unused variable 'i' [-Wunused-variable]
    uint32 i;           /* Loop Counter */
           ^
/home/user/c/m3/bn/bn.c:304:12: warning: unused variable 'j' [-Wunused-variable]
    uint32 j;           /* Loop Counter */
           ^
/home/user/c/m3/bn/bn.c:320:14: error: unknown token in expression
    __asm__ ("movl %eax, (bn1->dat[i])");
             ^
<inline asm>:1:18: note: instantiated into assembly here
        movl %eax, (bn1->dat[i])
                        ^
/home/user/c/m3/bn/bn.c:322:14: error: unknown token in expression
    __asm__ ("divl (bn2->dat[j])");
             ^
<inline asm>:1:12: note: instantiated into assembly here
        divl (bn2->dat[j])
                  ^
4 warnings and 2 errors generated.
*** [bn.o] Error code 1

Stop in /home/user/c/m3/bn.
system:/home/user/c/m3/bn 1037 $$$ ->

我知道的:

我认为自己对x86汇编程序非常精通(从我上面编写的代码可以证明)。 但是,大约15到20年前,当我为游戏编写图形驱动程序时(Windows 95之前的时代),我最后一次使用高级语言和汇编程序进行混合是使用Borland Pascal。 我熟悉Intel语法。

我不知道的是:

如何从asm访问bn_t(特别是* dat)的成员? 由于* dat是指向uint32的指针,因此我以数组形式访问元素(例如bn1-> dat [i])。

如何访问在堆栈上声明的局部变量?

我正在使用push / pop将受破坏的寄存器恢复为以前的值,以免破坏编译器。 但是,我还需要在局部变量上也包含volatile关键字吗?

还是有我不知道的更好的方法? 由于此调用的性能至关重要,因此我不想将其放在单独的函数调用中。

额外:

现在,我才刚刚开始编写此函数,因此尚不完整。 缺少循环和其他此类支持/胶水代码。 但是,主要要点是访问局部变量/结构元素。

编辑1:

我使用的语法似乎是clang支持的唯一语法。 我尝试了以下代码,但是clang给了我各种各样的错误:

__asm__ ("pushl %%eax",
    "pushl %%edx",
    "pushf",
    "movl (bn1->dat[i]), %%eax",
    "xorl %%edx, %%edx",
    "divl ($0x0c + bn2 + j)",
    "movl %%eax, (q)",
    "movl %%edx, (m)",
    "popf",
    "popl %%edx",
    "popl %%eax"
    );

它希望我在第一行上加上一个圆括号,以代替逗号。 我之所以改用%%而不是%,是因为我读到内联汇编需要%%表示CPU寄存器的地方,而clang告诉我我正在使用无效的转义序列。

如果只需要32b / 32b => 32bit除法,那么让编译器使用div两个输出 ,就像在Godbolt编译器浏览器上看到的那样,gcc,clang和icc都可以正常工作:

uint32_t q = bn1->dat[i] / bn2->dat[j];
uint32_t m = bn1->dat[i] % bn2->dat[j];

编译器是在相当不错的CSE荷兰国际集团说成一个div 只要确保您没有将除法结果存储在gcc无法证明不会影响余数输入的位置即可。

例如*m = dat[i] / dat[j]可能重叠(别名) dat[i]dat[j] ,因此gcc必须重新加载操作数并为%操作重做div 参见Godbolt链接以获取不良/良好示例。


对于32bit / 32bit = 32bit div使用内联asm不会获得任何好处,实际上会使使用clang的代码更糟(请参阅godbolt链接)。

如果您没有64位/ 32位= 32位,则可能需要asm,如果没有内置的编译器。 (GNU C没有AFAICT)。 C语言中最明显的方法(将操作数广播到uint64_t )生成对64bit / 64bit = 64bit libgcc函数的调用,该函数具有分支和多个div指令。 gcc不能很好地证明结果将适合32位,因此单个div指令不会导致#DE

对于许多其他说明,您可以避免很多时候使用内置函数来编写内联汇编,例如popcount 使用-mpopcnt ,它将编译为popcnt指令(并说明Intel CPU具有的输出操作数的虚假依赖关系。)如果没有,它将编译为libgcc函数调用。

始终喜欢内置的东西,或者更喜欢编译成好的asm的纯C语言,因此编译器知道代码的作用 当内联使某些参数在编译时已知时,纯C可以被优化或简化 ,但是使用内联asm的代码只会将常量加载到寄存器中并在运行时进行div 内联汇编也击败了相同数据上相似计算之间的CSE,当然不能自动矢量化。


正确使用GNU C语法

https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html解释了如何告诉汇编器您想要寄存器中的哪些变量以及输出是什么。

如果愿意可以使用类似于Intel / MASM的语法和助记符,也可以使用非%寄存器名称 ,最好使用-masm=intel进行编译。 AT&T语法错误( fsubfsubr助记符相反 )可能仍在intel语法模式下出现; 我忘了。

大多数使用GNU C内联汇编的软件项目仅使用AT&T语法。

有关更多GNU C内联asm信息和标签Wiki,另请参见此答案的底部


一个asm语句采用一个字符串arg和3组约束。 使它成为多行的最简单方法是使每条asm行成为以\\n结尾的单独字符串,并让编译器隐式连接它们。

另外,您还告诉编译器要将哪些内容放入寄存器中。然后,如果变量已经在寄存器中,则编译器不必溢出它们,也不必加载和存储它们。 这样做确实会使自己脚下射击。 布雷特·黑尔(Brett Hale)的教程在评论中链接起来,希望可以涵盖所有这一切。


带有GNU C内联汇编的div正确示例

您可以在godbolt上看到编译器的asm输出。

uint32_t q, m;  // this is unsigned int on every compiler that supports x86 inline asm with this syntax, but not when writing portable code.

asm ("divl %[bn2dat_j]\n"
      : "=a" (q), "=d" (m) // results are in eax, edx registers
      : "d" (0),           // zero edx for us, please
        "a" (bn1->dat[i]), // "a" means EAX / RAX
        [bn2dat_j] "mr" (bn2->dat[j]) // register or memory, compiler chooses which is more efficient
      : // no register clobbers, and we don't read/write "memory" other than operands
    );

"divl %4"也可以使用,但是当您添加更多输入/输出约束时,命名的输入/输出不会更改名称。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM