Impossible constraint with cmpxchg16b in extended assembly

一笑奈何 提交于 2019-12-04 06:51:55

问题


I am trying to write inline assembly with my C code to perform compare and swap operation. My code is:

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 gives this error when the code is compiled:

linkedlist.c: In function 'cs': linkedlist.c:45:3: error: impossible constraint in 'asm' __asm__ __volatile__("lock; cmpxchg16b %0; setz %1;":"=m"(*(volatile node_lf

What is wrong in my code? And how can I fix this?

I am trying to implement the equivalent of this code:

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:


So, let's take a crack at this.

A few points before we get started:

  1. Using inline asm is a bad idea. It is hard to write, it is hard to write correctly, it is hard to maintain, it isn't portable to other compilers or platforms, etc. Unless this is an assignment requirement, don't do it.
  2. When performing cmpxchg operations, the fields to be compared/exchanged must be contiguous. So if you want to operate on next, flag and mark in a single operation, they must be next to each other in the structure.
  3. When performing cmpxchg operations, the fields must be aligned on an appropriately sized boundary. For example if you are planning to operate on 16bytes, the data must be aligned on a 16byte boundary. gcc provides a variety of ways to do this from the aligned attribute, to _mm_malloc.
  4. When using __sync_bool_compare_and_swap (a better choice than inline asm), you must cast the data types to an appropriately-sized integer.
  5. I'm assuming your platform is x64.

2 & 3 required some changes to the field order of your structures. Note that I did not try to change searchfrom or return_tryFlag, since I'm not sure what they are used for.

So, with those things in mind, here's what I came up with:

#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);  
}

When you exit the loop, oldval will be what was there before (has to be or the cas would have failed and we would have looped again) and newval will be what actually got written. Note that if this truly were multi-threaded, there is no guarantee that newval would be the same as the current contents of n, since another thread could already have come along and changed it again.

For output we get:

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

Note that the cas (correctly!) fails on the first attempt, since the 'old' value doesn't match the 'current' value.

While using assembler may be able to save you an instruction or two, the win in terms of readability, maintainability, portability, etc is almost certainly worth the cost.

If for some reason you must use inline asm, you will still need to re-order your structs, and the point about alignment still stands. You can also look at https://stackoverflow.com/a/37825052/2189500. It only uses 8 bytes, but the concepts are the same.



来源:https://stackoverflow.com/questions/37739170/impossible-constraint-with-cmpxchg16b-in-extended-assembly

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!