【问题标题】:Impossible constraint with cmpxchg16b in extended assembly扩展装配中 cmpxchg16b 的不可能约束
【发布时间】:2016-06-10 02:48:47
【问题描述】:

我正在尝试用我的 C 代码编写内联汇编来执行比较和交换操作。我的代码是:

typedef struct node {
    int data;
    struct node * next;
    struct node * backlink;
    int flag;
    int mark;
} node_lf;

typedef struct searchfrom {
    node_lf * current;
    node_lf * next;
} return_sf;

typedef struct csArg {
    node_lf * node;
    int mark;
    int flag;
} cs_arg;

typedef struct return_tryFlag {
    node_lf * node;
    int result;
} return_tf;

static inline node_lf cs(node_lf * address, cs_arg *old_val, cs_arg *new_val)
{
    node_lf value = *address;
    __asm__ __volatile__("lock; cmpxchg16b %0; setz %1;"
                         :"=m"(*(volatile node_lf *)address),
                          "=q"(value)
                         :"m"(*(volatile node_lf *)address),
                          "a"(old_val->mark), "d"(old_val->flag),
                          "b"(new_val->mark), "c"(new_val->flag)
                         :"memory");
    return value;
}

编译代码时GCC会报这个错误:

linkedlist.c:在函数“cs”中:linkedlist.c:45:3:错误:“asm”中不可能的约束__asm__ __volatile__("lock; cmpxchg16b %0; setz %1;":"=m" (*(volatile node_lf

我的代码有什么问题?我该如何解决这个问题?

我正在尝试实现此代码的等效项:

node_lf cs (node_lf * address, cs_arg *old_val, cs_arg *new_val ) { 
    node_lf value = *address; 
    if (value.next == old_val->node && value.mark == old_val->mark && 
        value.flag == old_val->flag) { 
        address->next = new_val->node; 
        address->mark = new_val->mark; 
        address->flag = new_val->flag; 
    } 
    return value; 
}

【问题讨论】:

  • 代码和错误信息应该在问题本身中。
  • 具体问题是 x86 上的 'q' 约束必须是 a/b/c/dx,但您已经将所有这些用于其他参数。也就是说,我想推动不使用内联汇编。使用 __sync_bool_compare_and_swap 怎么样?甚至可能是 std::atomic?
  • 我认为可能真正发生的事情是node_lf value 将值定义为一种数据结构(而不​​是指向一个的指针)并且不适合 64 位寄存器。你真的是想用那种方式传递value吗?
  • @PeterCordes :是的,它是经过 OCR 处理的,比手动操作要好 ;-)。我必须修复很多东西,但我错过了错误消息中的小写 L 之一。但是我相信代码本身是相当准确的。我确实在我的一个 cmets 中指出了 value 的问题,询问 OP 是否真的打算做他们正在做的事情(显然他们不应该这样做)。
  • @MichaelPetch - 是的,你是对的:问题在于他按值返回结构。好吧,再加上他试图用setz 设置结构的值? value 将包含一个布尔值,指示 cmpxchg16b 是否成功。

标签: c gcc x86-64 inline-assembly lock-free


【解决方案1】:

所以,让我们来看看这个。

在我们开始之前的几点:

  1. 使用内联汇编是个坏主意。很难编写,很难正确编写,很难维护,不能移植到其他编译器或平台等。除非这是赋值要求,否则不要这样做。
  2. 执行 cmpxchg 操作时,要比较/交换的字段必须是连续的。因此,如果您想在一次操作中对nextflagmark 进行操作,它们必须在结构中彼此相邻。
  3. 执行 cmpxchg 操作时,字段必须在适当大小的边界上对齐。例如,如果您计划对 16 字节进行操作,则数据必须在 16 字节边界上对齐。 gcc 提供了从aligned attribute 到_mm_malloc 的多种方法。
  4. 使用 __sync_bool_compare_and_swap(比内联 asm 更好的选择)时,您必须将数据类型转换为大小合适的整数。
  5. 我假设您的平台是 x64。

2 和 3 需要对结构的字段顺序进行一些更改。请注意,我没有尝试更改 searchfromreturn_tryFlag,因为我不确定它们的用途。

所以,考虑到这些事情,这就是我想出的:

#include <stdio.h>
#include <memory.h>

typedef struct node {
    struct node * next;
    int mark;
    int flag;

    struct node * backlink;
    int data;
} node_lf;

typedef struct csArg {
    node_lf * node;
    int mark;
    int flag;
} cs_arg;

bool cs3(node_lf * address, cs_arg *old_val, cs_arg *new_val) { 

    return __sync_bool_compare_and_swap((unsigned __int128 *)address,
                                        *(unsigned __int128 *)old_val,
                                        *(unsigned __int128 *)new_val);
}

void ShowIt(void *v)
{
   unsigned long long *ull = (unsigned long long *)v;
   printf("%p:%p", *ull, *(ull + 1));
}

int main()
{
   cs_arg oldval, newval;
   node n;

   memset(&oldval, 0, sizeof(oldval));
   memset(&newval, 0, sizeof(newval));
   memset(&n, 0, sizeof(node));

   n.mark = 3;
   newval.mark = 4;

   bool b;

   do {
      printf("If "); ShowIt(&n); printf(" is "); ShowIt(&oldval); printf(" change to "); ShowIt(&newval);
      b = cs3(&n, &oldval, &newval);
      printf(". Result %d\n", b);

      if (b)
         break;
      memcpy(&oldval, &n, sizeof(cs_arg));
   } while (1);  
}

当您退出循环时,oldval 将是之前的内容(必须是,否则 cas 会失败,我们会再次循环),而 newval 将是实际写入的内容。请注意,如果这确实是多线程的,则无法保证 newval 与 n 的当前内容相同,因为可能已经有另一个线程出现并再次对其进行了更改。

对于输出,我们得到:

If 0000000000000000:0000000000000003 is 0000000000000000:0000000000000000 change to 0000000000000000:0000000000000000. Result 0
If 0000000000000000:0000000000000003 is 0000000000000000:0000000000000003 change to 0000000000000000:0000000000000000. Result 1

请注意,cas(正确!)在第一次尝试时失败,因为“旧”值与“当前”值不匹配。

虽然使用汇编程序可能会为您节省一两条指令,但在可读性、可维护性、可移植性等方面的胜利几乎肯定是值得的。

如果出于某种原因您必须使用内联 asm,您仍然需要重新排序结构,并且关于对齐的观点仍然存在。你也可以看看https://stackoverflow.com/a/37825052/2189500。它只使用了 8 个字节,但概念是一样的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-12-26
    • 1970-01-01
    • 1970-01-01
    • 2016-11-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多