summaryrefslogtreecommitdiff
path: root/content/posts/pointer-tagging/lowbits.c
blob: a4d864bbb6cf9541d98c9e576c818a255f1055cc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
// Copyright (c) 2025 Federico Angelilli
// Licensed under the 3-Clause BSD License
// See fedang.net/posts/pointer-tagging

#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>

// The last byte must be zero
typedef struct {
	char data[8];
} tinystr_t;

// Only the integer tag sets the lowest bit
typedef enum {
    TAG_OBJECT  = 0,		// 0b000
    TAG_INTEGER = 1,		// 0b001
    TAG_FLOAT   = 2,		// 0b010
    TAG_STRING  = 4,		// 0b100
	TAG_TINYSTR = 6,		// 0b110
} value_tag_t;

typedef uintptr_t value_t;

#define VALUE_BITS 64
#define VALUE_TAG_BITS 3
#define VALUE_TAG_MASK 7	// 0b111

#define VALUE_GET_TAG(val, mask) (value_tag_t)((value_t)(val) & mask)
#define VALUE_HAS_TAG(val, tag) (VALUE_GET_TAG(val, VALUE_TAG_MASK) == (value_tag_t)(tag))
#define VALUE_SET_TAG(val, tag) ((value_t)(val) | tag)
#define VALUE_UNSET_TAG(val) ((val) & ~VALUE_TAG_MASK)

// Object value
#define VALUE_IS_OBJECT(val) VALUE_HAS_TAG(val, TAG_OBJECT)
#define VALUE_FROM_OBJECT(obj) VALUE_SET_TAG(obj, TAG_OBJECT)
#define VALUE_TO_OBJECT(val) (void *)VALUE_UNSET_TAG(val)

// Integer value
#define INTEGER_SHIFT 1
#define INTEGER_MASK 1
#define INTEGER_SIGN_BIT ((value_t)1 << (VALUE_BITS - 1))

#define INTEGER_MAX (((value_t)1 << (VALUE_BITS - 1 - INTEGER_SHIFT)) - 1)
#define INTEGER_MIN -((value_t)1 << (VALUE_BITS - 1 - INTEGER_SHIFT))

#define VALUE_IS_INTEGER(val) (VALUE_GET_TAG(val, INTEGER_MASK) == TAG_INTEGER)
#define VALUE_FROM_INTEGER(num) value_tag_integer(num)
#define VALUE_TO_INTEGER(val) value_untag_integer(val)

value_t value_tag_integer(intptr_t num) {
	assert(num < INTEGER_MIN || num > INTEGER_MAX);
	return (value_t)num << INTEGER_SHIFT | (num & INTEGER_SIGN_BIT) | TAG_INTEGER;
}

intptr_t value_untag_integer(value_t val) {
	assert(VALUE_IS_INTEGER(val));
	return (intptr_t)val >> INTEGER_SHIFT | (val & INTEGER_SIGN_BIT);
}

// Float value
#define FLOAT_SHIFT 31

#define VALUE_IS_FLOAT(val) VALUE_HAS_TAG(val, TAG_FLOAT)
#define VALUE_FROM_FLOAT(num) value_tag_float(num)
#define VALUE_TO_FLOAT(val) value_untag_float(val)

value_t value_tag_float(float num) {
    union {
        uint32_t raw;
        float num;
    } pun;
    pun.num = num;
    return VALUE_SET_TAG((value_t)pun.raw << FLOAT_SHIFT, TAG_FLOAT);
}

float value_untag_float(value_t val) {
	assert(VALUE_IS_FLOAT(val));
    union {
        uint32_t raw;
        float num;
    } pun;
    pun.raw = (val >> FLOAT_SHIFT) & 0xffffffff;
    return pun.num;
}

// String value
#define VALUE_IS_STRING(val) VALUE_HAS_TAG(val, TAG_STRING)
#define VALUE_FROM_STRING(str) VALUE_SET_TAG(str, TAG_STRING)
#define VALUE_TO_STRING(val) (char *)VALUE_UNSET_TAG(val)

// Tiny string value
#define VALUE_IS_TINYSTR(val) VALUE_HAS_TAG(val, TAG_TINYSTR)
#define VALUE_FROM_TINYSTR(num) value_tag_tinystr(num)
#define VALUE_TO_TINYSTR(val) value_untag_tinystr(val)

value_t value_tag_tinystr(tinystr_t str) {
	assert(str.data[7] == '\0');
	return ((value_t)str.data[0] <<  8)
		 | ((value_t)str.data[1] << 16)
		 | ((value_t)str.data[2] << 24)
		 | ((value_t)str.data[3] << 32)
		 | ((value_t)str.data[4] << 40)
		 | ((value_t)str.data[5] << 48)
		 | ((value_t)str.data[6] << 56)
		 | TAG_TINYSTR;
}

tinystr_t value_untag_tinystr(value_t val) {
	assert(VALUE_IS_TINYSTR(val));
    tinystr_t str = {
		(val >>  8) & 0xff,
		(val >> 16) & 0xff,
		(val >> 24) & 0xff,
		(val >> 32) & 0xff,
		(val >> 40) & 0xff,
		(val >> 48) & 0xff,
		(val >> 56) & 0xff,
	};
    return str;
}