扩展装配中cmpxchg16b的不可能约束

时间:2016-06-10 02:48:47

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

我正在尝试使用 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__("锁定; 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; 
}

1 个答案:

答案 0 :(得分:1)

所以,让我们解决这个问题。

我们开始之前的几点:

  1. 使用inline asm是一个坏主意。它很难写,很难正确编写,很难维护,不能移植到其他编译器或平台等。除非这是一个分配要求,否则不要这样做。 / LI>
  2. 执行cmpxchg操作时,要比较/交换的字段必须是连续的。因此,如果您希望在单个操作中对nextflagmark进行操作,则它们必须在结构中彼此相邻。
  3. 执行cmpxchg操作时,字段必须在适当大小的边界上对齐。例如,如果您计划在16字节上运行,则数据必须在16字节边界上对齐。 gcc提供了多种方法来执行此操作,从aligned attribute到_mm_malloc。
  4. 使用__sync_bool_compare_and_swap(比内联asm更好的选择)时,必须将数据类型转换为适当大小的整数。
  5. 我假设你的平台是x64。
  6. 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(正确!)在第一次尝试时失败,因为&#39; old&#39;价值与当前&#39;不匹配值。

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

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