code wiki / (root) / dlist.nx

dlist.nx source

↩ module page · 159 lines · 4365 B

1// dlist.nx -- intrusive doubly-linked list. 2// 3// Each node carries prev/next pointers; list has a sentinel node 4// (heap-allocated) whose prev/next thread the ring. Intrusive: 5// the node pointer is the handle the caller holds, not a wrapper 6// around T. Trade-offs vs std::list-style wrapped list: 7// + O(1) remove given just a node pointer (no search) 8// + one user struct can participate in multiple lists (LRU + 9// pending + free) with separate DNode fields 10// - caller allocates nodes 11// 12// Design note: NishiLang v1 doesn't allow `&struct.field` for 13// inline fields, so DList stores the sentinel as a separately- 14// allocated *DNode rather than an embedded DNode. Small extra 15// alloc at list-construction time only. 16// 17// Used by: 18// - LRU caches (move-to-front, evict tail) 19// - scheduler run queues 20// - free-block chains in allocators 21// - order-preserving map iteration (map + sibling dlist) 22// 23// Invariants: 24// DL1 Empty list: sentinel.next == sentinel.prev == sentinel. 25// DL2 dlist_insert_after + dlist_remove maintain 26// n.next.prev == n for all n. 27// DL3 dlist_remove does NOT free the node -- caller owns 28// lifetime. 29 30import "syscalls.nx" 31 32struct DNode { 33 prev: *DNode, 34 next: *DNode, 35 data: *u8, // arbitrary payload pointer 36} 37 38struct DList { 39 sentinel: *DNode, 40} 41 42// Allocate a fresh empty list. 43func dlist_new() -> *DList { 44 let lst_raw: *u8 = sys_mmap(16) 45 let L: *DList = lst_raw as *DList 46 let s_raw: *u8 = sys_mmap(32) 47 let s: *DNode = s_raw as *DNode 48 s.prev = s 49 s.next = s 50 s.data = 0 as *u8 51 L.sentinel = s 52 return L 53} 54 55// Allocate a node holding `data`. 56func dnode_new(data: *u8) -> *DNode { 57 let raw: *u8 = sys_mmap(32) 58 let n: *DNode = raw as *DNode 59 n.prev = 0 as *DNode 60 n.next = 0 as *DNode 61 n.data = data 62 return n 63} 64 65func dlist_empty(L: *DList) -> i64 { 66 let s: *DNode = L.sentinel 67 if s.next == s { return 1 } 68 return 0 69} 70 71// Insert `n` immediately after `pos`. 72func dlist_insert_after(pos: *DNode, n: *DNode) -> i64 { 73 n.prev = pos 74 n.next = pos.next 75 pos.next.prev = n 76 pos.next = n 77 return 0 78} 79 80// Insert at head (after sentinel). 81func dlist_push_front(L: *DList, n: *DNode) -> i64 { 82 return dlist_insert_after(L.sentinel, n) 83} 84 85// Insert at tail (after sentinel.prev == current last). 86func dlist_push_back(L: *DList, n: *DNode) -> i64 { 87 return dlist_insert_after(L.sentinel.prev, n) 88} 89 90// Unlink a node. Node's prev/next not cleared. 91func dlist_remove(n: *DNode) -> i64 { 92 n.prev.next = n.next 93 n.next.prev = n.prev 94 return 0 95} 96 97// Move an existing node to the front (LRU hit). 98func dlist_move_to_front(L: *DList, n: *DNode) -> i64 { 99 dlist_remove(n) 100 dlist_push_front(L, n) 101 return 0 102} 103 104// Pop head; NULL if empty. 105func dlist_pop_front(L: *DList) -> *DNode { 106 if dlist_empty(L) == 1 { return 0 as *DNode } 107 let n: *DNode = L.sentinel.next 108 dlist_remove(n) 109 return n 110} 111 112// Pop tail; NULL if empty. 113func dlist_pop_back(L: *DList) -> *DNode { 114 if dlist_empty(L) == 1 { return 0 as *DNode } 115 let n: *DNode = L.sentinel.prev 116 dlist_remove(n) 117 return n 118} 119 120// Iteration entry points. 121func dlist_begin(L: *DList) -> *DNode { 122 return L.sentinel.next 123} 124 125func dlist_end(L: *DList) -> *DNode { 126 return L.sentinel 127} 128 129// Compile-only smoke. 130func main() -> i64 { 131 let L: *DList = dlist_new() 132 if dlist_empty(L) != 1 { return 1 } 133 134 let a: *DNode = dnode_new("a" as *u8) 135 let b: *DNode = dnode_new("b" as *u8) 136 let c: *DNode = dnode_new("c" as *u8) 137 138 dlist_push_back(L, a) 139 dlist_push_back(L, b) 140 dlist_push_back(L, c) 141 if dlist_empty(L) != 0 { return 2 } 142 143 let head: *DNode = dlist_begin(L) 144 if head.data[0] != 0x61 { return 3 } 145 146 dlist_move_to_front(L, b) 147 let new_head: *DNode = dlist_begin(L) 148 if new_head.data[0] != 0x62 { return 4 } 149 150 let tail: *DNode = dlist_pop_back(L) 151 if tail.data[0] != 0x63 { return 5 } 152 153 let first: *DNode = dlist_pop_front(L) 154 if first.data[0] != 0x62 { return 6 } 155 let second: *DNode = dlist_pop_front(L) 156 if second.data[0] != 0x61 { return 7 } 157 if dlist_empty(L) != 1 { return 8 } 158 return 0 159}