| 1 | const builtin = @import("builtin"); |
| 2 | |
| 3 | const std = @import("std.zig"); |
| 4 | const AtomicOrder = std.builtin.AtomicOrder; |
| 5 | const testing = std.testing; |
| 6 | const assert = std.debug.assert; |
| 7 | |
| 8 | /// This is a thin wrapper around a primitive value to prevent accidental data races. |
| 9 | pub fn Value(comptime T: type) type { |
| 10 | return extern struct { |
| 11 | /// Care must be taken to avoid data races when interacting with this field directly. |
| 12 | raw: T, |
| 13 | |
| 14 | const Self = @This(); |
| 15 | |
| 16 | pub fn init(value: T) Self { |
| 17 | return .{ .raw = value }; |
| 18 | } |
| 19 | |
| 20 | pub inline fn load(self: *const Self, comptime order: AtomicOrder) T { |
| 21 | return @atomicLoad(T, &self.raw, order); |
| 22 | } |
| 23 | |
| 24 | pub inline fn store(self: *Self, value: T, comptime order: AtomicOrder) void { |
| 25 | @atomicStore(T, &self.raw, value, order); |
| 26 | } |
| 27 | |
| 28 | pub inline fn swap(self: *Self, operand: T, comptime order: AtomicOrder) T { |
| 29 | return @atomicRmw(T, &self.raw, .Xchg, operand, order); |
| 30 | } |
| 31 | |
| 32 | pub inline fn cmpxchgWeak( |
| 33 | self: *Self, |
| 34 | expected_value: T, |
| 35 | new_value: T, |
| 36 | comptime success_order: AtomicOrder, |
| 37 | comptime fail_order: AtomicOrder, |
| 38 | ) ?T { |
| 39 | return @cmpxchgWeak(T, &self.raw, expected_value, new_value, success_order, fail_order); |
| 40 | } |
| 41 | |
| 42 | pub inline fn cmpxchgStrong( |
| 43 | self: *Self, |
| 44 | expected_value: T, |
| 45 | new_value: T, |
| 46 | comptime success_order: AtomicOrder, |
| 47 | comptime fail_order: AtomicOrder, |
| 48 | ) ?T { |
| 49 | return @cmpxchgStrong(T, &self.raw, expected_value, new_value, success_order, fail_order); |
| 50 | } |
| 51 | |
| 52 | pub inline fn fetchAdd(self: *Self, operand: T, comptime order: AtomicOrder) T { |
| 53 | return @atomicRmw(T, &self.raw, .Add, operand, order); |
| 54 | } |
| 55 | |
| 56 | pub inline fn fetchSub(self: *Self, operand: T, comptime order: AtomicOrder) T { |
| 57 | return @atomicRmw(T, &self.raw, .Sub, operand, order); |
| 58 | } |
| 59 | |
| 60 | pub inline fn fetchMin(self: *Self, operand: T, comptime order: AtomicOrder) T { |
| 61 | return @atomicRmw(T, &self.raw, .Min, operand, order); |
| 62 | } |
| 63 | |
| 64 | pub inline fn fetchMax(self: *Self, operand: T, comptime order: AtomicOrder) T { |
| 65 | return @atomicRmw(T, &self.raw, .Max, operand, order); |
| 66 | } |
| 67 | |
| 68 | pub inline fn fetchAnd(self: *Self, operand: T, comptime order: AtomicOrder) T { |
| 69 | return @atomicRmw(T, &self.raw, .And, operand, order); |
| 70 | } |
| 71 | |
| 72 | pub inline fn fetchNand(self: *Self, operand: T, comptime order: AtomicOrder) T { |
| 73 | return @atomicRmw(T, &self.raw, .Nand, operand, order); |
| 74 | } |
| 75 | |
| 76 | pub inline fn fetchXor(self: *Self, operand: T, comptime order: AtomicOrder) T { |
| 77 | return @atomicRmw(T, &self.raw, .Xor, operand, order); |
| 78 | } |
| 79 | |
| 80 | pub inline fn fetchOr(self: *Self, operand: T, comptime order: AtomicOrder) T { |
| 81 | return @atomicRmw(T, &self.raw, .Or, operand, order); |
| 82 | } |
| 83 | |
| 84 | pub inline fn rmw( |
| 85 | self: *Self, |
| 86 | comptime op: std.builtin.AtomicRmwOp, |
| 87 | operand: T, |
| 88 | comptime order: AtomicOrder, |
| 89 | ) T { |
| 90 | return @atomicRmw(T, &self.raw, op, operand, order); |
| 91 | } |
| 92 | |
| 93 | const Bit = std.math.Log2Int(T); |
| 94 | |
| 95 | /// Marked `inline` so that if `bit` is comptime-known, the instruction |
| 96 | /// can be lowered to a more efficient machine code instruction if |
| 97 | /// possible. |
| 98 | pub inline fn bitSet(self: *Self, bit: Bit, comptime order: AtomicOrder) u1 { |
| 99 | const mask = @as(T, 1) << bit; |
| 100 | const value = self.fetchOr(mask, order); |
| 101 | return @intFromBool(value & mask != 0); |
| 102 | } |
| 103 | |
| 104 | /// Marked `inline` so that if `bit` is comptime-known, the instruction |
| 105 | /// can be lowered to a more efficient machine code instruction if |
| 106 | /// possible. |
| 107 | pub inline fn bitReset(self: *Self, bit: Bit, comptime order: AtomicOrder) u1 { |
| 108 | const mask = @as(T, 1) << bit; |
| 109 | const value = self.fetchAnd(~mask, order); |
| 110 | return @intFromBool(value & mask != 0); |
| 111 | } |
| 112 | |
| 113 | /// Marked `inline` so that if `bit` is comptime-known, the instruction |
| 114 | /// can be lowered to a more efficient machine code instruction if |
| 115 | /// possible. |
| 116 | pub inline fn bitToggle(self: *Self, bit: Bit, comptime order: AtomicOrder) u1 { |
| 117 | const mask = @as(T, 1) << bit; |
| 118 | const value = self.fetchXor(mask, order); |
| 119 | return @intFromBool(value & mask != 0); |
| 120 | } |
| 121 | }; |
| 122 | } |
| 123 | |
| 124 | test Value { |
| 125 | const RefCount = struct { |
| 126 | count: Value(usize), |
| 127 | dropFn: *const fn (*RefCount) void, |
| 128 | |
| 129 | const RefCount = @This(); |
| 130 | |
| 131 | fn ref(rc: *RefCount) void { |
| 132 | // no synchronization necessary; just updating a counter. |
| 133 | _ = rc.count.fetchAdd(1, .monotonic); |
| 134 | } |
| 135 | |
| 136 | fn unref(rc: *RefCount) void { |
| 137 | // release ensures code before unref() happens-before the |
| 138 | // count is decremented as dropFn could be called by then. |
| 139 | if (rc.count.fetchSub(1, .release) == 1) { |
| 140 | // seeing 1 in the counter means that other unref()s have happened, |
| 141 | // but it doesn't mean that uses before each unref() are visible. |
| 142 | // The load acquires the release-sequence created by previous unref()s |
| 143 | // in order to ensure visibility of uses before dropping. |
| 144 | _ = rc.count.load(.acquire); |
| 145 | (rc.dropFn)(rc); |
| 146 | } |
| 147 | } |
| 148 | |
| 149 | fn noop(rc: *RefCount) void { |
| 150 | _ = rc; |
| 151 | } |
| 152 | }; |
| 153 | |
| 154 | var ref_count: RefCount = .{ |
| 155 | .count = Value(usize).init(0), |
| 156 | .dropFn = RefCount.noop, |
| 157 | }; |
| 158 | ref_count.ref(); |
| 159 | ref_count.unref(); |
| 160 | } |
| 161 | |
| 162 | test "Value.swap" { |
| 163 | var x = Value(usize).init(5); |
| 164 | try testing.expectEqual(@as(usize, 5), x.swap(10, .seq_cst)); |
| 165 | try testing.expectEqual(@as(usize, 10), x.load(.seq_cst)); |
| 166 | |
| 167 | const E = enum(usize) { a, b, c }; |
| 168 | var y = Value(E).init(.c); |
| 169 | try testing.expectEqual(E.c, y.swap(.a, .seq_cst)); |
| 170 | try testing.expectEqual(E.a, y.load(.seq_cst)); |
| 171 | |
| 172 | var z = Value(f32).init(5.0); |
| 173 | try testing.expectEqual(@as(f32, 5.0), z.swap(10.0, .seq_cst)); |
| 174 | try testing.expectEqual(@as(f32, 10.0), z.load(.seq_cst)); |
| 175 | |
| 176 | var a = Value(bool).init(false); |
| 177 | try testing.expectEqual(false, a.swap(true, .seq_cst)); |
| 178 | try testing.expectEqual(true, a.load(.seq_cst)); |
| 179 | |
| 180 | var b = Value(?*u8).init(null); |
| 181 | try testing.expectEqual(@as(?*u8, null), b.swap(@as(?*u8, @ptrFromInt(@alignOf(u8))), .seq_cst)); |
| 182 | try testing.expectEqual(@as(?*u8, @ptrFromInt(@alignOf(u8))), b.load(.seq_cst)); |
| 183 | } |
| 184 | |
| 185 | test "Value.store" { |
| 186 | var x = Value(usize).init(5); |
| 187 | x.store(10, .seq_cst); |
| 188 | try testing.expectEqual(@as(usize, 10), x.load(.seq_cst)); |
| 189 | } |
| 190 | |
| 191 | test "Value.cmpxchgWeak" { |
| 192 | var x = Value(usize).init(0); |
| 193 | |
| 194 | try testing.expectEqual(@as(?usize, 0), x.cmpxchgWeak(1, 0, .seq_cst, .seq_cst)); |
| 195 | try testing.expectEqual(@as(usize, 0), x.load(.seq_cst)); |
| 196 | |
| 197 | while (x.cmpxchgWeak(0, 1, .seq_cst, .seq_cst)) |_| {} |
| 198 | try testing.expectEqual(@as(usize, 1), x.load(.seq_cst)); |
| 199 | |
| 200 | while (x.cmpxchgWeak(1, 0, .seq_cst, .seq_cst)) |_| {} |
| 201 | try testing.expectEqual(@as(usize, 0), x.load(.seq_cst)); |
| 202 | } |
| 203 | |
| 204 | test "Value.cmpxchgStrong" { |
| 205 | var x = Value(usize).init(0); |
| 206 | try testing.expectEqual(@as(?usize, 0), x.cmpxchgStrong(1, 0, .seq_cst, .seq_cst)); |
| 207 | try testing.expectEqual(@as(usize, 0), x.load(.seq_cst)); |
| 208 | try testing.expectEqual(@as(?usize, null), x.cmpxchgStrong(0, 1, .seq_cst, .seq_cst)); |
| 209 | try testing.expectEqual(@as(usize, 1), x.load(.seq_cst)); |
| 210 | try testing.expectEqual(@as(?usize, null), x.cmpxchgStrong(1, 0, .seq_cst, .seq_cst)); |
| 211 | try testing.expectEqual(@as(usize, 0), x.load(.seq_cst)); |
| 212 | } |
| 213 | |
| 214 | test "Value.fetchAdd" { |
| 215 | var x = Value(usize).init(5); |
| 216 | try testing.expectEqual(@as(usize, 5), x.fetchAdd(5, .seq_cst)); |
| 217 | try testing.expectEqual(@as(usize, 10), x.load(.seq_cst)); |
| 218 | try testing.expectEqual(@as(usize, 10), x.fetchAdd(std.math.maxInt(usize), .seq_cst)); |
| 219 | try testing.expectEqual(@as(usize, 9), x.load(.seq_cst)); |
| 220 | } |
| 221 | |
| 222 | test "Value.fetchSub" { |
| 223 | var x = Value(usize).init(5); |
| 224 | try testing.expectEqual(@as(usize, 5), x.fetchSub(5, .seq_cst)); |
| 225 | try testing.expectEqual(@as(usize, 0), x.load(.seq_cst)); |
| 226 | try testing.expectEqual(@as(usize, 0), x.fetchSub(1, .seq_cst)); |
| 227 | try testing.expectEqual(@as(usize, std.math.maxInt(usize)), x.load(.seq_cst)); |
| 228 | } |
| 229 | |
| 230 | test "Value.fetchMin" { |
| 231 | var x = Value(usize).init(5); |
| 232 | try testing.expectEqual(@as(usize, 5), x.fetchMin(0, .seq_cst)); |
| 233 | try testing.expectEqual(@as(usize, 0), x.load(.seq_cst)); |
| 234 | try testing.expectEqual(@as(usize, 0), x.fetchMin(10, .seq_cst)); |
| 235 | try testing.expectEqual(@as(usize, 0), x.load(.seq_cst)); |
| 236 | } |
| 237 | |
| 238 | test "Value.fetchMax" { |
| 239 | var x = Value(usize).init(5); |
| 240 | try testing.expectEqual(@as(usize, 5), x.fetchMax(10, .seq_cst)); |
| 241 | try testing.expectEqual(@as(usize, 10), x.load(.seq_cst)); |
| 242 | try testing.expectEqual(@as(usize, 10), x.fetchMax(5, .seq_cst)); |
| 243 | try testing.expectEqual(@as(usize, 10), x.load(.seq_cst)); |
| 244 | } |
| 245 | |
| 246 | test "Value.fetchAnd" { |
| 247 | var x = Value(usize).init(0b11); |
| 248 | try testing.expectEqual(@as(usize, 0b11), x.fetchAnd(0b10, .seq_cst)); |
| 249 | try testing.expectEqual(@as(usize, 0b10), x.load(.seq_cst)); |
| 250 | try testing.expectEqual(@as(usize, 0b10), x.fetchAnd(0b00, .seq_cst)); |
| 251 | try testing.expectEqual(@as(usize, 0b00), x.load(.seq_cst)); |
| 252 | } |
| 253 | |
| 254 | test "Value.fetchNand" { |
| 255 | var x = Value(usize).init(0b11); |
| 256 | try testing.expectEqual(@as(usize, 0b11), x.fetchNand(0b10, .seq_cst)); |
| 257 | try testing.expectEqual(~@as(usize, 0b10), x.load(.seq_cst)); |
| 258 | try testing.expectEqual(~@as(usize, 0b10), x.fetchNand(0b00, .seq_cst)); |
| 259 | try testing.expectEqual(~@as(usize, 0b00), x.load(.seq_cst)); |
| 260 | } |
| 261 | |
| 262 | test "Value.fetchOr" { |
| 263 | var x = Value(usize).init(0b11); |
| 264 | try testing.expectEqual(@as(usize, 0b11), x.fetchOr(0b100, .seq_cst)); |
| 265 | try testing.expectEqual(@as(usize, 0b111), x.load(.seq_cst)); |
| 266 | try testing.expectEqual(@as(usize, 0b111), x.fetchOr(0b010, .seq_cst)); |
| 267 | try testing.expectEqual(@as(usize, 0b111), x.load(.seq_cst)); |
| 268 | } |
| 269 | |
| 270 | test "Value.fetchXor" { |
| 271 | var x = Value(usize).init(0b11); |
| 272 | try testing.expectEqual(@as(usize, 0b11), x.fetchXor(0b10, .seq_cst)); |
| 273 | try testing.expectEqual(@as(usize, 0b01), x.load(.seq_cst)); |
| 274 | try testing.expectEqual(@as(usize, 0b01), x.fetchXor(0b01, .seq_cst)); |
| 275 | try testing.expectEqual(@as(usize, 0b00), x.load(.seq_cst)); |
| 276 | } |
| 277 | |
| 278 | test "Value.bitSet" { |
| 279 | var x = Value(usize).init(0); |
| 280 | |
| 281 | for (0..@bitSizeOf(usize)) |bit_index| { |
| 282 | const bit = @as(std.math.Log2Int(usize), @intCast(bit_index)); |
| 283 | const mask = @as(usize, 1) << bit; |
| 284 | |
| 285 | // setting the bit should change the bit |
| 286 | try testing.expect(x.load(.seq_cst) & mask == 0); |
| 287 | try testing.expectEqual(@as(u1, 0), x.bitSet(bit, .seq_cst)); |
| 288 | try testing.expect(x.load(.seq_cst) & mask != 0); |
| 289 | |
| 290 | // setting it again shouldn't change the bit |
| 291 | try testing.expectEqual(@as(u1, 1), x.bitSet(bit, .seq_cst)); |
| 292 | try testing.expect(x.load(.seq_cst) & mask != 0); |
| 293 | |
| 294 | // all the previous bits should have not changed (still be set) |
| 295 | for (0..bit_index) |prev_bit_index| { |
| 296 | const prev_bit = @as(std.math.Log2Int(usize), @intCast(prev_bit_index)); |
| 297 | const prev_mask = @as(usize, 1) << prev_bit; |
| 298 | try testing.expect(x.load(.seq_cst) & prev_mask != 0); |
| 299 | } |
| 300 | } |
| 301 | } |
| 302 | |
| 303 | test "Value.bitReset" { |
| 304 | var x = Value(usize).init(0); |
| 305 | |
| 306 | for (0..@bitSizeOf(usize)) |bit_index| { |
| 307 | const bit = @as(std.math.Log2Int(usize), @intCast(bit_index)); |
| 308 | const mask = @as(usize, 1) << bit; |
| 309 | x.raw |= mask; |
| 310 | |
| 311 | // unsetting the bit should change the bit |
| 312 | try testing.expect(x.load(.seq_cst) & mask != 0); |
| 313 | try testing.expectEqual(@as(u1, 1), x.bitReset(bit, .seq_cst)); |
| 314 | try testing.expect(x.load(.seq_cst) & mask == 0); |
| 315 | |
| 316 | // unsetting it again shouldn't change the bit |
| 317 | try testing.expectEqual(@as(u1, 0), x.bitReset(bit, .seq_cst)); |
| 318 | try testing.expect(x.load(.seq_cst) & mask == 0); |
| 319 | |
| 320 | // all the previous bits should have not changed (still be reset) |
| 321 | for (0..bit_index) |prev_bit_index| { |
| 322 | const prev_bit = @as(std.math.Log2Int(usize), @intCast(prev_bit_index)); |
| 323 | const prev_mask = @as(usize, 1) << prev_bit; |
| 324 | try testing.expect(x.load(.seq_cst) & prev_mask == 0); |
| 325 | } |
| 326 | } |
| 327 | } |
| 328 | |
| 329 | test "Value.bitToggle" { |
| 330 | var x = Value(usize).init(0); |
| 331 | |
| 332 | for (0..@bitSizeOf(usize)) |bit_index| { |
| 333 | const bit = @as(std.math.Log2Int(usize), @intCast(bit_index)); |
| 334 | const mask = @as(usize, 1) << bit; |
| 335 | |
| 336 | // toggling the bit should change the bit |
| 337 | try testing.expect(x.load(.seq_cst) & mask == 0); |
| 338 | try testing.expectEqual(@as(u1, 0), x.bitToggle(bit, .seq_cst)); |
| 339 | try testing.expect(x.load(.seq_cst) & mask != 0); |
| 340 | |
| 341 | // toggling it again *should* change the bit |
| 342 | try testing.expectEqual(@as(u1, 1), x.bitToggle(bit, .seq_cst)); |
| 343 | try testing.expect(x.load(.seq_cst) & mask == 0); |
| 344 | |
| 345 | // all the previous bits should have not changed (still be toggled back) |
| 346 | for (0..bit_index) |prev_bit_index| { |
| 347 | const prev_bit = @as(std.math.Log2Int(usize), @intCast(prev_bit_index)); |
| 348 | const prev_mask = @as(usize, 1) << prev_bit; |
| 349 | try testing.expect(x.load(.seq_cst) & prev_mask == 0); |
| 350 | } |
| 351 | } |
| 352 | } |
| 353 | |
| 354 | /// Signals to the processor that the caller is inside a busy-wait spin-loop. |
| 355 | pub inline fn spinLoopHint() void { |
| 356 | switch (builtin.target.cpu.arch) { |
| 357 | // No-op instruction that can hint to save (or share with a hardware-thread) |
| 358 | // pipelining/power resources |
| 359 | // https://software.intel.com/content/www/us/en/develop/articles/benefitting-power-and-performance-sleep-loops.html |
| 360 | .x86, |
| 361 | .x86_64, |
| 362 | => asm volatile ("pause"), |
| 363 | |
| 364 | // No-op instruction that serves as a hardware-thread resource yield hint. |
| 365 | // https://stackoverflow.com/a/7588941 |
| 366 | .powerpc, |
| 367 | .powerpcle, |
| 368 | .powerpc64, |
| 369 | .powerpc64le, |
| 370 | => asm volatile ("or 27, 27, 27"), |
| 371 | |
| 372 | // `isb` appears more reliable for releasing execution resources than `yield` |
| 373 | // on common aarch64 CPUs. |
| 374 | // https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8258604 |
| 375 | // https://bugs.mysql.com/bug.php?id=100664 |
| 376 | .aarch64, |
| 377 | .aarch64_be, |
| 378 | => asm volatile ("isb"), |
| 379 | |
| 380 | // https://www.keil.com/support/man/docs/armasm/armasm_dom1361289926796.htm |
| 381 | .arm, |
| 382 | .armeb, |
| 383 | => if (comptime builtin.cpu.has(.arm, .has_v6k)) { |
| 384 | asm volatile ("yield"); |
| 385 | }, |
| 386 | |
| 387 | .thumb, |
| 388 | .thumbeb, |
| 389 | => if (comptime builtin.cpu.hasAny(.arm, &.{ .has_v6m, .thumb2 })) { |
| 390 | asm volatile ("yield"); |
| 391 | }, |
| 392 | |
| 393 | // The 8-bit immediate specifies the amount of cycles to pause for. We can't really be too |
| 394 | // opinionated here. |
| 395 | .hexagon, |
| 396 | => asm volatile ("pause(#1)"), |
| 397 | |
| 398 | .mips, |
| 399 | .mipsel, |
| 400 | .mips64, |
| 401 | .mips64el, |
| 402 | => if (comptime builtin.cpu.has(.mips, .mips32r2)) { |
| 403 | asm volatile ("pause"); |
| 404 | }, |
| 405 | |
| 406 | .riscv32, |
| 407 | .riscv32be, |
| 408 | .riscv64, |
| 409 | .riscv64be, |
| 410 | => if (comptime builtin.cpu.has(.riscv, .zihintpause)) { |
| 411 | asm volatile ("pause"); |
| 412 | }, |
| 413 | |
| 414 | .sparc, |
| 415 | .sparc64, |
| 416 | => if (comptime builtin.cpu.hasAny(.sparc, &.{ .v8plus, .v9 })) { |
| 417 | asm volatile ("rd %%ccr, %%g0"); |
| 418 | }, |
| 419 | |
| 420 | else => {}, |
| 421 | } |
| 422 | } |
| 423 | |
| 424 | test spinLoopHint { |
| 425 | for (0..10) |_| { |
| 426 | spinLoopHint(); |
| 427 | } |
| 428 | } |
| 429 | |
| 430 | pub fn cacheLineForCpu(cpu: std.Target.Cpu) u16 { |
| 431 | return switch (cpu.arch) { |
| 432 | // x86_64: Starting from Intel's Sandy Bridge, the spatial prefetcher pulls in pairs of 64-byte cache lines at a time. |
| 433 | // - https://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-optimization-manual.pdf |
| 434 | // - https://github.com/facebook/folly/blob/1b5288e6eea6df074758f877c849b6e73bbb9fbb/folly/lang/Align.h#L107 |
| 435 | // |
| 436 | // aarch64: Some big.LITTLE ARM archs have "big" cores with 128-byte cache lines: |
| 437 | // - https://www.mono-project.com/news/2016/09/12/arm64-icache/ |
| 438 | // - https://cpufun.substack.com/p/more-m1-fun-hardware-information |
| 439 | // |
| 440 | // - https://github.com/torvalds/linux/blob/3a7e02c040b130b5545e4b115aada7bacd80a2b6/arch/arc/Kconfig#L212 |
| 441 | // - https://github.com/golang/go/blob/3dd58676054223962cd915bb0934d1f9f489d4d2/src/internal/cpu/cpu_ppc64x.go#L9 |
| 442 | .x86_64, |
| 443 | .aarch64, |
| 444 | .aarch64_be, |
| 445 | .arc, |
| 446 | .arceb, |
| 447 | .powerpc64, |
| 448 | .powerpc64le, |
| 449 | => 128, |
| 450 | |
| 451 | // https://github.com/llvm/llvm-project/blob/e379094328e49731a606304f7e3559d4f1fa96f9/clang/lib/Basic/Targets/Hexagon.h#L145-L151 |
| 452 | .hexagon, |
| 453 | => if (cpu.has(.hexagon, .v73)) 64 else 32, |
| 454 | |
| 455 | // - https://github.com/golang/go/blob/3dd58676054223962cd915bb0934d1f9f489d4d2/src/internal/cpu/cpu_arm.go#L7 |
| 456 | // - https://github.com/golang/go/blob/3dd58676054223962cd915bb0934d1f9f489d4d2/src/internal/cpu/cpu_mips.go#L7 |
| 457 | // - https://github.com/golang/go/blob/3dd58676054223962cd915bb0934d1f9f489d4d2/src/internal/cpu/cpu_mipsle.go#L7 |
| 458 | // - https://github.com/golang/go/blob/3dd58676054223962cd915bb0934d1f9f489d4d2/src/internal/cpu/cpu_mips64x.go#L9 |
| 459 | // - https://github.com/torvalds/linux/blob/3a7e02c040b130b5545e4b115aada7bacd80a2b6/arch/sparc/include/asm/cache.h#L14 |
| 460 | // - https://github.com/torvalds/linux/blob/3a7e02c040b130b5545e4b115aada7bacd80a2b6/arch/microblaze/include/asm/cache.h#L15 |
| 461 | // - https://github.com/torvalds/linux/blob/3a7e02c040b130b5545e4b115aada7bacd80a2b6/arch/sh/include/cpu-sh4/cpu/cache.h#L10 |
| 462 | // - https://github.com/openbsd/src/blob/1957873d2063db11dab780eca75b5e629d1e838d/sys/arch/m88k/m88k/atomic.S#L22 |
| 463 | .arm, |
| 464 | .armeb, |
| 465 | .thumb, |
| 466 | .thumbeb, |
| 467 | .m88k, |
| 468 | .microblaze, |
| 469 | .microblazeel, |
| 470 | .mips, |
| 471 | .mipsel, |
| 472 | .mips64, |
| 473 | .mips64el, |
| 474 | .sh, |
| 475 | .sheb, |
| 476 | .sparc, |
| 477 | .sparc64, |
| 478 | => 32, |
| 479 | |
| 480 | // - https://github.com/torvalds/linux/blob/3a7e02c040b130b5545e4b115aada7bacd80a2b6/arch/m68k/include/asm/cache.h#L10 |
| 481 | // - https://github.com/torvalds/linux/blob/3a7e02c040b130b5545e4b115aada7bacd80a2b6/arch/openrisc/include/asm/cache.h#L24 |
| 482 | // - https://github.com/torvalds/linux/blob/3a7e02c040b130b5545e4b115aada7bacd80a2b6/arch/parisc/include/asm/cache.h#L16 |
| 483 | .hppa, |
| 484 | .hppa64, |
| 485 | .m68k, |
| 486 | .or1k, |
| 487 | => 16, |
| 488 | |
| 489 | // - https://www.ti.com/lit/pdf/slaa498 |
| 490 | .msp430, |
| 491 | => 8, |
| 492 | |
| 493 | // - https://github.com/golang/go/blob/3dd58676054223962cd915bb0934d1f9f489d4d2/src/internal/cpu/cpu_s390x.go#L7 |
| 494 | // - https://sxauroratsubasa.sakura.ne.jp/documents/guide/pdfs/Aurora_ISA_guide.pdf |
| 495 | .s390x, |
| 496 | .ve, |
| 497 | => 256, |
| 498 | |
| 499 | // Other x86 and WASM platforms have 64-byte cache lines. |
| 500 | // The rest of the architectures are assumed to be similar. |
| 501 | // - https://github.com/golang/go/blob/dda2991c2ea0c5914714469c4defc2562a907230/src/internal/cpu/cpu_x86.go#L9 |
| 502 | // - https://github.com/golang/go/blob/0a9321ad7f8c91e1b0c7184731257df923977eb9/src/internal/cpu/cpu_loong64.go#L11 |
| 503 | // - https://github.com/golang/go/blob/3dd58676054223962cd915bb0934d1f9f489d4d2/src/internal/cpu/cpu_wasm.go#L7 |
| 504 | // - https://github.com/golang/go/blob/19e923182e590ae6568c2c714f20f32512aeb3e3/src/internal/cpu/cpu_riscv64.go#L7 |
| 505 | // - https://github.com/torvalds/linux/blob/3a7e02c040b130b5545e4b115aada7bacd80a2b6/arch/xtensa/variants/csp/include/variant/core.h#L209 |
| 506 | // - https://github.com/torvalds/linux/blob/3a7e02c040b130b5545e4b115aada7bacd80a2b6/arch/csky/Kconfig#L183 |
| 507 | // - https://github.com/torvalds/linux/blob/3a7e02c040b130b5545e4b115aada7bacd80a2b6/arch/alpha/include/asm/cache.h#L11 |
| 508 | // - https://www.xmos.com/download/The-XMOS-XS3-Architecture.pdf |
| 509 | else => 64, |
| 510 | }; |
| 511 | } |
| 512 | |
| 513 | /// The estimated size of the CPU's cache line when atomically updating memory. |
| 514 | /// Add this much padding or align to this boundary to avoid atomically-updated |
| 515 | /// memory from forcing cache invalidations on near, but non-atomic, memory. |
| 516 | /// |
| 517 | /// https://en.wikipedia.org/wiki/False_sharing |
| 518 | /// https://github.com/golang/go/search?q=CacheLinePadSize |
| 519 | pub const cache_line: comptime_int = cacheLineForCpu(builtin.cpu); |
| 520 | |
| 521 | test "current CPU has a cache line size" { |
| 522 | _ = cache_line; |
| 523 | } |
| 524 | |
| 525 | /// A lock-free single-owner resource. |
| 526 | pub const Mutex = enum(u8) { |
| 527 | unlocked, |
| 528 | locked, |
| 529 | |
| 530 | pub fn tryLock(m: *Mutex) bool { |
| 531 | return @cmpxchgStrong(Mutex, m, .unlocked, .locked, .acquire, .monotonic) == null; |
| 532 | } |
| 533 | |
| 534 | pub fn unlock(m: *Mutex) void { |
| 535 | assert(@atomicLoad(Mutex, m, .unordered) == .locked); |
| 536 | @atomicStore(Mutex, m, .unlocked, .release); |
| 537 | } |
| 538 | }; |