===================
== lxulxu's blog ==
===================
Hello there

Tagged Pointer

c++ eda

Tagged Pointer是一个经典的时间优化技术,通过巧妙利用内存对齐特性,在零额外内存开销下避免堆分配。

基础原理

为了防止未对齐访问,编译器会插入填充0以根据类型的对齐要求对齐值,分配内存对齐到8字节(在64位系统上是16字节),指针地址以000结尾 。可以利用这些未使用的低位来指示这不是真实指针,然后将对象数据直接存储在这个"非指针"的剩余部分中,而不是单独的内存分配。

1Top                       		Bottom
2▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮000 
3                             					^^^
4                             				可用于存储标记

简单实现:存储小整数

以一个能将小整数直接编码进指针的系统为例:

 1#include <stdint.h>
 2#include <stdbool.h>
 3
 4// 标记位定义
 5#define TAG_MASK    0x7      // 低3位掩码: 0b111
 6#define TAG_INT     0x1      // 整数标记: 0b001
 7#define TAG_PTR     0x0      // 指针标记: 0b000
 8
 9// 类型判断
10static inline bool is_tagged_int(uintptr_t value) {
11    return (value & TAG_MASK) == TAG_INT;
12}
13
14static inline bool is_pointer(uintptr_t value) {
15    return (value & TAG_MASK) == TAG_PTR;
16}
17
18// 整数编码:左移3位后设置标记
19static inline uintptr_t encode_int(int64_t num) {
20    return (num << 3) | TAG_INT;
21}
22
23// 整数解码:去除标记后右移
24static inline int64_t decode_int(uintptr_t tagged) {
25    return (int64_t)tagged >> 3;  // 算术右移保留符号
26}
27
28// 指针编码:确保低3位为0
29static inline uintptr_t encode_ptr(void* ptr) {
30    uintptr_t addr = (uintptr_t)ptr;
31    // 断言指针已对齐
32    assert((addr & TAG_MASK) == 0);
33    return addr | TAG_PTR;
34}
35
36// 指针解码:清除标记位
37static inline void* decode_ptr(uintptr_t tagged) {
38    return (void*)(tagged & ~TAG_MASK);
39}

使用示例

 1// 示例:实现简单的动态类型值
 2typedef uintptr_t Value;
 3
 4// 创建整数值
 5Value create_number(int64_t num) {
 6    // 小整数:直接编码
 7    if (num >= -(1LL << 60) && num < (1LL << 60)) {
 8        return encode_int(num);
 9    }
10    // 大整数:堆分配
11    int64_t* heap_num = malloc(sizeof(int64_t));
12    *heap_num = num;
13    return encode_ptr(heap_num);
14}
15
16// 整数加法
17Value add_numbers(Value a, Value b) {
18    int64_t a_val, b_val;
19    
20    // 提取值a
21    if (is_tagged_int(a)) {
22        a_val = decode_int(a);
23    } else {
24        int64_t* ptr = decode_ptr(a);
25        a_val = *ptr;
26    }
27    
28    // 提取值b
29    if (is_tagged_int(b)) {
30        b_val = decode_int(b);
31    } else {
32        int64_t* ptr = decode_ptr(b);
33        b_val = *ptr;
34    }
35    
36    return create_number(a_val + b_val);
37}
38
39// 使用示例
40int main() {
41    Value num1 = create_number(42);       // Tagged pointer
42    Value num2 = create_number(58);       // Tagged pointer
43    Value sum = add_numbers(num1, num2);  // 结果:100
44    
45    printf("Result: %lld\n", decode_int(sum));  // 输出: 100
46    return 0;
47}

经典应用场景:And-Inverter Graph (AIG)

AIG是由二输入AND门和反相器组成的多级逻辑网络,用于表示二进制顺序逻辑电路。在数字电路设计中,任何布尔逻辑都可以用AND和NOT门表示。如果不使用Tagged Pointer,AIG节点结构可能是这样:

 1// 传统实现:每条边需要单独存储反相信息
 2typedef struct AigEdge_ {
 3    AigNode *node;           // 8字节
 4    unsigned int complement : 1;  // 反相标记
 5} AigEdge;
 6
 7typedef struct AigNode_ {
 8    unsigned int id;
 9    AigEdge *fanin0;  // 指向输入边0 (16字节对象)
10    AigEdge *fanin1;  // 指向输入边1 (16字节对象)
11} AigNode;
  • 每个AigEdge占用16字节(8字节指针 + 对齐填充)

  • 每个AND门需要2个fanin,额外32字节仅用于存储边信息

ABC项目是加州大学伯克利分校开发的开源逻辑综合与验证工具,在其核心数据结构AIG的实现中,节点对象Aig_Obj_t使用tagged pointer优化,利用最低1位存储complement(反相)标记。在大规模电路网表中,这种优化的收益是显著的。

 1struct Aig_Obj_t_ {
 2    // ===== Tagged Fanin指针 =====
 3    Aig_Obj_t *pFanin0;    // 输入端0
 4    Aig_Obj_t *pFanin1;    // 输入端1
 5    
 6    unsigned int Type    :  3;
 7    unsigned int fPhase  :  1;
 8    unsigned int fMarkA  :  1;
 9    unsigned int fMarkB  :  1;
10    unsigned int nRefs   : 26;
11    
12    unsigned     Level   : 24;
13    unsigned     nCuts   :  8;
14    
15    int          TravId;       
16    int          Id;            
17    
18    union {
19        void *   pData;
20        int      iData;
21        float    dData;
22    };
23};

核心Tagged Pointer操作:

 1// 检查边是否为反相 (检查最低位)
 2#define Aig_IsComplement(p)   \
 3    (((int)((ABC_PTRUINT_T)(p) & 01)))
 4
 5// 获取真实节点指针 (清除最低位)
 6#define Aig_Regular(p)        \
 7    ((Aig_Obj_t *)((ABC_PTRUINT_T)(p) & ~01))
 8
 9// 获取非反相版本
10#define Aig_Not(p)            \
11    ((Aig_Obj_t *)((ABC_PTRUINT_T)(p) ^ 01))
12
13// 带条件的反相
14#define Aig_NotCond(p,c)      \
15    ((Aig_Obj_t *)((ABC_PTRUINT_T)(p) ^ (c)))
16
17// ===== 读取fanin操作 =====
18
19// 获取fanin0的真实节点指针
20static inline Aig_Obj_t* Aig_ObjFanin0(Aig_Obj_t* pObj) {
21    return Aig_Regular(pObj->pFanin0);
22}
23
24// 获取fanin1的真实节点指针  
25static inline Aig_Obj_t* Aig_ObjFanin1(Aig_Obj_t* pObj) {
26    return Aig_Regular(pObj->pFanin1);
27}
28
29// 检查fanin0是否反相
30static inline int Aig_ObjFaninC0(Aig_Obj_t* pObj) {
31    return Aig_IsComplement(pObj->pFanin0);
32}
33
34// 检查fanin1是否反相
35static inline int Aig_ObjFaninC1(Aig_Obj_t* pObj) {
36    return Aig_IsComplement(pObj->pFanin1);
37}
38
39// ===== 构造操作 =====
40
41// 创建AND门 (根据条件设置反相标记)
42Aig_Obj_t* Aig_And(Aig_Man_t* p, 
43                    Aig_Obj_t* p0,   // fanin0 (可能已tagged)
44                    Aig_Obj_t* p1) { // fanin1 (可能已tagged)
45    Aig_Obj_t* pNode = Aig_TableLookup(p, p0, p1);
46    if (pNode) return pNode;
47    
48    // 创建新节点
49    pNode = Aig_ManCreateNode(p);
50    pNode->pFanin0 = p0;  // 直接存储tagged pointer
51    pNode->pFanin1 = p1;  
52    pNode->Type = AIG_OBJ_AND;
53    
54    return pNode;
55}
56
57// 创建反相边: NOT(p) = AND(p, 1) 的特殊表示
58Aig_Obj_t* Aig_Not_Func(Aig_Obj_t* p) {
59    return Aig_Not(p);  // 翻转最低位
60}

实际使用示例

  • 构造简单逻辑电路
 1// 构造电路: out = NOT(AND(a, NOT(b)))  即 a NAND b
 2void build_nand_circuit(Aig_Man_t* pMan) {
 3    // 创建输入端口
 4    Aig_Obj_t* pA = Aig_ObjCreateCi(pMan);  // 输入a
 5    Aig_Obj_t* pB = Aig_ObjCreateCi(pMan);  // 输入b
 6    
 7    // 创建 NOT(b) - 只是翻转指针的最低位!
 8    Aig_Obj_t* pNotB = Aig_Not(pB);  
 9    
10    // 创建 AND(a, NOT(b))
11    Aig_Obj_t* pAnd = Aig_And(pMan, pA, pNotB);
12    
13    // 创建 NOT(AND(...))
14    Aig_Obj_t* pNand = Aig_Not(pAnd);
15    
16    // 创建输出端口
17    Aig_ObjCreateCo(pMan, pNand);
18}
  • 遍历电路拓扑
 1// 递归计算节点逻辑值
 2int Aig_ObjEvaluate(Aig_Obj_t* pObj, int* pInputVals) {
 3    // 常量节点
 4    if (Aig_ObjIsConst1(pObj)) {
 5        return 1;
 6    }
 7    
 8    // 输入节点
 9    if (Aig_ObjIsCi(pObj)) {
10        return pInputVals[Aig_ObjCioId(pObj)];
11    }
12    
13    // AND节点:需要处理fanin的complement标记
14    assert(Aig_ObjIsAnd(pObj));
15    
16    // 1. 获取真实的fanin节点指针
17    Aig_Obj_t* pFanin0 = Aig_ObjFanin0(pObj);  
18    Aig_Obj_t* pFanin1 = Aig_ObjFanin1(pObj);
19    
20    // 2. 递归计算fanin的值
21    int val0 = Aig_ObjEvaluate(pFanin0, pInputVals);
22    int val1 = Aig_ObjEvaluate(pFanin1, pInputVals);
23    
24    // 3. 根据complement标记进行反相
25    if (Aig_ObjFaninC0(pObj)) val0 = !val0;
26    if (Aig_ObjFaninC1(pObj)) val1 = !val1;
27    
28    // 4. 返回AND结果
29    return val0 & val1;
30}