5

I am trying to mark and unmark pointers so I can implement Non-Blocking linked lists. I checked that on my architecture the last bit is never used, so I am trying to use change it to mark/unmark pointers.

I am trying to perform an OR to set the last bit to 1, an AND to unset it and an AND to check if it is set to 1. The problem is that when I perform bitwise (the commented Macros) operations on a pointer I cannot dereference it. Dereferencing it results in a segmentation fault even though the integer value of the pointer is correct.

More specifically, the #define unmark(x) (x & (uintptr_t) 0xfffffffe) is what is causing the segmentation fault. If I do not use it (and use #define unmark(x) x - 1 instead) the program works.

Incrementing and decrementing the pointer seems to be working, but it may make the solution architecture specific. This is because on my architecture pointers always end in 8, which has the final bit set to 0. If this is not the case my solution would not be very portable.

I understand that manipulating pointers is probably not portable anyway, but it is required for this algorithm. If someone knows what is causing the problem it would be fantastic.

This is the code I have used to test the solution:

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

//This produces segfault, for some reason
//#define unmark(x)     (x & (uintptr_t) 0xfffffffe)
//#define mark(x)   (x | (uintptr_t) 0x00000001)
#define is_marked(x)    ((long) x & 0x00000001)

#define mark(x)     x + 1
#define unmark(x)   x - 1

struct Example {
     long x;
     long y;
};

int main() {
    struct Example *x = malloc(sizeof(struct Example));
    x->x = 10;
    x->y = 20;
    uintptr_t p = (uintptr_t)(void*) x;

    printf("%ld\n", ((struct Example *) (void*) p)->y);

    printf("%04x\n", p);
    printf("Is marked: %d\n", is_marked(p));

    p = mark(p);
    printf("%04x\n", p);
    printf("Is marked: %d\n", is_marked(p));

    p = unmark(p);
    printf("%04x\n", p);
    printf("Is marked: %d\n", is_marked(p));

    printf("%ld\n", ((struct Example *) (void*) p)->y);

    return 0;
}
John Kugelman
  • 349,597
  • 67
  • 533
  • 578
Andrejcc
  • 134
  • 8
  • 6
    Don't do that. Create your own pointer type, including the full value and a marker as separate fields. – Eugene Sh. Jan 09 '23 at 16:06
  • Unfortunately that does not solve my problem. I want to implement Non-Blocking data structures, which use Compare and Swap (CAS) for synchronization. CAS atomically compares one word, meaning that both the pointer and marker have to be "joined", doing it separately would not be atomic or would be too inefficient. – Andrejcc Jan 09 '23 at 16:09
  • How your CAS is implemented? – Eugene Sh. Jan 09 '23 at 16:12
  • 9
    Your `unmark` will ignore the top half on a 64-bit system. Perhaps `((x) & ~(uintptr_t) 0)` might be more general. – M Oehm Jan 09 '23 at 16:13
  • Is this really a valid solution, altering the value stored within a pointer location is surely going to then mean that its storing a non-valid memory address - hence the segmentation fault? – ChrisBD Jan 09 '23 at 16:17
  • @ChrisBD It is definitely based on assumptions that are invalid from C perspective, but which could hold on some very specific environment... Hence my first comment. – Eugene Sh. Jan 09 '23 at 16:19
  • 4
    @MOehm, did you mean `((x) & ~(uintptr_t) 1)`? – John Bollinger Jan 09 '23 at 16:20
  • 2
    @JohnBollinger: Yes, I probably meant to write that. As is, it is a bit of a non-op. Thanks for spotting that. – M Oehm Jan 09 '23 at 16:21
  • What about `p |= 1;` to set the bit and `p = (p >> 1) << 1;` to unset? – David Ranieri Jan 09 '23 at 16:22
  • @MOehm that solved my problem! I was not considering half of the pointer! Thank you! – Andrejcc Jan 09 '23 at 16:27

1 Answers1

3

Code has many problems

Not clearing just the least significant bit

x & (uintptr_t) 0xfffffffe assumes uintptr_t is 32-bit. Better as

 #define unmark(x)  ((x) & ~(uintptr_t)1)
 // or 
 #define unmark(x)  (((x) | 1) ^ 1)

Assuming bit manipulation of pointers is OK

Mis-matched specifier

// printf("%04x\n", p);
printf("%04jx\n", (uintmax_t) p);

// printf("Is marked: %d\n", is_marked(p));
// Unclear correct specifier.

Better as

// #define is_marked(x)    ((long) x & 0x00000001)
#define is_marked(x)    (!!((x) & 1))

Unneeded casts

//#define mark(x)   (x | (uintptr_t) 0x00000001)
#define mark(x)   ((x) | 1)
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • I understand pointer bit manipulation is not OK, but it the only solution in this case: See paper "A Pragmatic Implementation of Non-blocking Linked-Lists" – Andrejcc Jan 09 '23 at 16:35
  • @A [A Pragmatic Implementation of Non-Blocking Linked-Lists](https://www.cl.cam.ac.uk/research/srg/netos/papers/2001-caslists.pdf) presents a solution. Why do you think it is the _only_ solution in this case especially with the paper's "they _may_ be indicated by an otherwise-unused low-order bit in each reference."? "CAS atomically compares one word" --> What if the pointer is more than 1 _word_? – chux - Reinstate Monica Jan 09 '23 at 16:40
  • I meant that to implement Harris' non blocking linked-list the only know solution is to incorporate the mark into the pointer. I am not aware of any other way to mark references and still be able to compare then atomically with Compare and Swap. – Andrejcc Jan 09 '23 at 16:47