diff --git a/CMakeLists.txt b/CMakeLists.txt index d7bfe5a7a6db..099b7a40f3f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -337,8 +337,7 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/lib/std/array_list.zig" "${CMAKE_SOURCE_DIR}/lib/std/ascii.zig" "${CMAKE_SOURCE_DIR}/lib/std/atomic.zig" - "${CMAKE_SOURCE_DIR}/lib/std/atomic/bool.zig" - "${CMAKE_SOURCE_DIR}/lib/std/atomic/int.zig" + "${CMAKE_SOURCE_DIR}/lib/std/atomic/Atomic.zig" "${CMAKE_SOURCE_DIR}/lib/std/atomic/queue.zig" "${CMAKE_SOURCE_DIR}/lib/std/atomic/stack.zig" "${CMAKE_SOURCE_DIR}/lib/std/base64.zig" diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index fbb3d927e221..fb74f0967766 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -67,33 +67,7 @@ else switch (std.Target.current.os.tag) { else => struct {}, }; -/// Signals the processor that it is inside a busy-wait spin-loop ("spin lock"). -pub inline fn spinLoopHint() void { - switch (std.Target.current.cpu.arch) { - .i386, .x86_64 => { - asm volatile ("pause" ::: "memory"); - }, - .arm, .armeb, .thumb, .thumbeb => { - // `yield` was introduced in v6k but are also available on v6m. - const can_yield = comptime std.Target.arm.featureSetHasAny(std.Target.current.cpu.features, .{ .has_v6k, .has_v6m }); - if (can_yield) asm volatile ("yield" ::: "memory") - // Fallback. - else asm volatile ("" ::: "memory"); - }, - .aarch64, .aarch64_be, .aarch64_32 => { - asm volatile ("isb" ::: "memory"); - }, - .powerpc64, .powerpc64le => { - // No-op that serves as `yield` hint. - asm volatile ("or 27, 27, 27" ::: "memory"); - }, - else => { - // Do nothing but prevent the compiler from optimizing away the - // spinning loop. - asm volatile ("" ::: "memory"); - }, - } -} +pub const spinLoopHint = @compileError("deprecated: use std.atomic.spinLoopHint"); /// Returns the ID of the calling thread. /// Makes a syscall every time the function is called. @@ -597,8 +571,13 @@ pub fn getCurrentThreadId() u64 { } } -test { +test "std.Thread" { if (!builtin.single_threaded) { - std.testing.refAllDecls(@This()); + _ = AutoResetEvent; + _ = ResetEvent; + _ = StaticResetEvent; + _ = Mutex; + _ = Semaphore; + _ = Condition; } } diff --git a/lib/std/Thread/Condition.zig b/lib/std/Thread/Condition.zig index a14b57f6b4fd..d88a6de31e0b 100644 --- a/lib/std/Thread/Condition.zig +++ b/lib/std/Thread/Condition.zig @@ -115,7 +115,7 @@ pub const AtomicCondition = struct { else => unreachable, } }, - else => spinLoopHint(), + else => std.atomic.spinLoopHint(), } } } diff --git a/lib/std/Thread/Mutex.zig b/lib/std/Thread/Mutex.zig index cae4e282decc..49f138079dc3 100644 --- a/lib/std/Thread/Mutex.zig +++ b/lib/std/Thread/Mutex.zig @@ -126,7 +126,7 @@ pub const AtomicMutex = struct { var iter = std.math.min(32, spin + 1); while (iter > 0) : (iter -= 1) - std.Thread.spinLoopHint(); + std.atomic.spinLoopHint(); } new_state = .waiting; @@ -149,7 +149,7 @@ pub const AtomicMutex = struct { else => unreachable, } }, - else => std.Thread.spinLoopHint(), + else => std.atomic.spinLoopHint(), } } } diff --git a/lib/std/Thread/StaticResetEvent.zig b/lib/std/Thread/StaticResetEvent.zig index 75bea463aa80..0a6a1d568e21 100644 --- a/lib/std/Thread/StaticResetEvent.zig +++ b/lib/std/Thread/StaticResetEvent.zig @@ -182,7 +182,7 @@ pub const AtomicEvent = struct { timer = time.Timer.start() catch return error.TimedOut; while (@atomicLoad(u32, waiters, .Acquire) != WAKE) { - std.os.sched_yield() catch std.Thread.spinLoopHint(); + std.os.sched_yield() catch std.atomic.spinLoopHint(); if (timeout) |timeout_ns| { if (timer.read() >= timeout_ns) return error.TimedOut; @@ -293,7 +293,7 @@ pub const AtomicEvent = struct { return @intToPtr(?windows.HANDLE, handle); }, LOADING => { - std.os.sched_yield() catch std.Thread.spinLoopHint(); + std.os.sched_yield() catch std.atomic.spinLoopHint(); handle = @atomicLoad(usize, &event_handle, .Monotonic); }, else => { diff --git a/lib/std/atomic.zig b/lib/std/atomic.zig index ab80fce87259..224b57d1d205 100644 --- a/lib/std/atomic.zig +++ b/lib/std/atomic.zig @@ -3,14 +3,82 @@ // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. + +const std = @import("std.zig"); +const target = std.Target.current; + +pub const Ordering = std.builtin.AtomicOrder; + pub const Stack = @import("atomic/stack.zig").Stack; pub const Queue = @import("atomic/queue.zig").Queue; -pub const Bool = @import("atomic/bool.zig").Bool; -pub const Int = @import("atomic/int.zig").Int; +pub const Atomic = @import("atomic/Atomic.zig").Atomic; test "std.atomic" { _ = @import("atomic/stack.zig"); _ = @import("atomic/queue.zig"); - _ = @import("atomic/bool.zig"); - _ = @import("atomic/int.zig"); + _ = @import("atomic/Atomic.zig"); +} + +pub fn fence(comptime ordering: Ordering) callconv(.Inline) void { + switch (ordering) { + .Acquire, .Release, .AcqRel, .SeqCst => { + @fence(ordering); + }, + else => { + @compileLog(ordering, " only applies to a given memory location"); + }, + } +} + +pub fn compilerFence(comptime ordering: Ordering) callconv(.Inline) void { + switch (ordering) { + .Acquire, .Release, .AcqRel, .SeqCst => asm volatile ("" ::: "memory"), + else => @compileLog(ordering, " only applies to a given memory location"), + } +} + +test "fence/compilerFence" { + inline for (.{ .Acquire, .Release, .AcqRel, .SeqCst }) |ordering| { + compilerFence(ordering); + fence(ordering); + } +} + +/// Signals to the processor that the caller is inside a busy-wait spin-loop. +pub fn spinLoopHint() callconv(.Inline) void { + const hint_instruction = switch (target.cpu.arch) { + // No-op instruction that can hint to save (or share with a hardware-thread) pipelining/power resources + // https://software.intel.com/content/www/us/en/develop/articles/benefitting-power-and-performance-sleep-loops.html + .i386, .x86_64 => "pause", + + // No-op instruction that serves as a hardware-thread resource yield hint. + // https://stackoverflow.com/a/7588941 + .powerpc64, .powerpc64le => "or 27, 27, 27", + + // `isb` appears more reliable for releasing execution resources than `yield` on common aarch64 CPUs. + // https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8258604 + // https://bugs.mysql.com/bug.php?id=100664 + .aarch64, .aarch64_be, .aarch64_32 => "isb", + + // `yield` was introduced in v6k but is also available on v6m. + // https://www.keil.com/support/man/docs/armasm/armasm_dom1361289926796.htm + .arm, .armeb, .thumb, .thumbeb => blk: { + const can_yield = comptime std.Target.arm.featureSetHasAny(target.cpu.features, .{ .has_v6k, .has_v6m }); + const instruction = if (can_yield) "yield" else ""; + break :blk instruction; + }, + + else => "", + }; + + // Memory barrier to prevent the compiler from optimizing away the spin-loop + // even if no hint_instruction was provided. + asm volatile (hint_instruction ::: "memory"); +} + +test "spinLoopHint" { + var i: usize = 10; + while (i > 0) : (i -= 1) { + spinLoopHint(); + } } diff --git a/lib/std/atomic/Atomic.zig b/lib/std/atomic/Atomic.zig new file mode 100644 index 000000000000..5c3b865a6a65 --- /dev/null +++ b/lib/std/atomic/Atomic.zig @@ -0,0 +1,522 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2015-2021 Zig Contributors +// This file is part of [zig](https://ziglang.org/), which is MIT licensed. +// The MIT license requires this copyright notice to be included in all copies +// and substantial portions of the software. + +const std = @import("../std.zig"); + +const testing = std.testing; +const target = std.Target.current; +const Ordering = std.atomic.Ordering; + +pub fn Atomic(comptime T: type) type { + return extern struct { + value: T, + + const Self = @This(); + + pub fn init(value: T) Self { + return .{ .value = value }; + } + + /// Non-atomically load from the atomic value without synchronization. + /// Care must be taken to avoid data-races when interacting with other atomic operations. + pub fn loadUnchecked(self: Self) T { + return self.value; + } + + /// Non-atomically store to the atomic value without synchronization. + /// Care must be taken to avoid data-races when interacting with other atomic operations. + pub fn storeUnchecked(self: *Self, value: T) void { + self.value = value; + } + + pub fn load(self: *const Self, comptime ordering: Ordering) T { + return switch (ordering) { + .AcqRel => @compileError(@tagName(ordering) ++ " implies " ++ @tagName(Ordering.Release) ++ " which is only allowed on atomic stores"), + .Release => @compileError(@tagName(ordering) ++ " is only allowed on atomic stores"), + else => @atomicLoad(T, &self.value, ordering), + }; + } + + pub fn store(self: *Self, value: T, comptime ordering: Ordering) void { + return switch (ordering) { + .AcqRel => @compileError(@tagName(ordering) ++ " implies " ++ @tagName(Ordering.Acquire) ++ " which is only allowed on atomic loads"), + .Acquire => @compileError(@tagName(ordering) ++ " is only allowed on atomic loads"), + else => @atomicStore(T, &self.value, value, ordering), + }; + } + + pub fn swap(self: *Self, value: T, comptime ordering: Ordering) callconv(.Inline) T { + return self.rmw(.Xchg, value, ordering); + } + + pub fn compareAndSwap( + self: *Self, + compare: T, + exchange: T, + comptime success: Ordering, + comptime failure: Ordering, + ) callconv(.Inline) ?T { + return self.cmpxchg(true, compare, exchange, success, failure); + } + + pub fn tryCompareAndSwap( + self: *Self, + compare: T, + exchange: T, + comptime success: Ordering, + comptime failure: Ordering, + ) callconv(.Inline) ?T { + return self.cmpxchg(false, compare, exchange, success, failure); + } + + fn cmpxchg( + self: *Self, + comptime is_strong: bool, + compare: T, + exchange: T, + comptime success: Ordering, + comptime failure: Ordering, + ) callconv(.Inline) ?T { + if (success == .Unordered or failure == .Unordered) { + @compileError(@tagName(Ordering.Unordered) ++ " is only allowed on atomic loads and stores"); + } + + comptime var success_is_stronger = switch (failure) { + .SeqCst => success == .SeqCst, + .AcqRel => @compileError(@tagName(failure) ++ " implies " ++ @tagName(Ordering.Release) ++ " which is only allowed on success"), + .Acquire => success == .SeqCst or success == .AcqRel or success == .Acquire, + .Release => @compileError(@tagName(failure) ++ " is only allowed on success"), + .Monotonic => true, + .Unordered => unreachable, + }; + + if (!success_is_stronger) { + @compileError(@tagName(success) ++ " must be stronger than " ++ @tagName(failure)); + } + + return switch (is_strong) { + true => @cmpxchgStrong(T, &self.value, compare, exchange, success, failure), + false => @cmpxchgWeak(T, &self.value, compare, exchange, success, failure), + }; + } + + fn rmw( + self: *Self, + comptime op: std.builtin.AtomicRmwOp, + value: T, + comptime ordering: Ordering, + ) callconv(.Inline) T { + return @atomicRmw(T, &self.value, op, value, ordering); + } + + fn exportWhen(comptime condition: bool, comptime functions: type) type { + return if (condition) functions else struct {}; + } + + pub usingnamespace exportWhen(std.meta.trait.isNumber(T), struct { + pub fn fetchAdd(self: *Self, value: T, comptime ordering: Ordering) callconv(.Inline) T { + return self.rmw(.Add, value, ordering); + } + + pub fn fetchSub(self: *Self, value: T, comptime ordering: Ordering) callconv(.Inline) T { + return self.rmw(.Sub, value, ordering); + } + + pub fn fetchMin(self: *Self, value: T, comptime ordering: Ordering) callconv(.Inline) T { + return self.rmw(.Min, value, ordering); + } + + pub fn fetchMax(self: *Self, value: T, comptime ordering: Ordering) callconv(.Inline) T { + return self.rmw(.Max, value, ordering); + } + }); + + pub usingnamespace exportWhen(std.meta.trait.isIntegral(T), struct { + pub fn fetchAnd(self: *Self, value: T, comptime ordering: Ordering) callconv(.Inline) T { + return self.rmw(.And, value, ordering); + } + + pub fn fetchNand(self: *Self, value: T, comptime ordering: Ordering) callconv(.Inline) T { + return self.rmw(.Nand, value, ordering); + } + + pub fn fetchOr(self: *Self, value: T, comptime ordering: Ordering) callconv(.Inline) T { + return self.rmw(.Or, value, ordering); + } + + pub fn fetchXor(self: *Self, value: T, comptime ordering: Ordering) callconv(.Inline) T { + return self.rmw(.Xor, value, ordering); + } + + const Bit = std.math.Log2Int(T); + const BitRmwOp = enum { + Set, + Reset, + Toggle, + }; + + pub fn bitSet(self: *Self, bit: Bit, comptime ordering: Ordering) callconv(.Inline) u1 { + return bitRmw(self, .Set, bit, ordering); + } + + pub fn bitReset(self: *Self, bit: Bit, comptime ordering: Ordering) callconv(.Inline) u1 { + return bitRmw(self, .Reset, bit, ordering); + } + + pub fn bitToggle(self: *Self, bit: Bit, comptime ordering: Ordering) callconv(.Inline) u1 { + return bitRmw(self, .Toggle, bit, ordering); + } + + fn bitRmw( + self: *Self, + comptime op: BitRmwOp, + bit: Bit, + comptime ordering: Ordering, + ) callconv(.Inline) u1 { + // x86 supports dedicated bitwise instructions + if (comptime target.cpu.arch.isX86() and @sizeOf(T) >= 2 and @sizeOf(T) <= 8) { + const instruction = switch (op) { + .Set => "lock bts", + .Reset => "lock btr", + .Toggle => "lock btc", + }; + + const suffix = switch (@sizeOf(T)) { + 2 => "w", + 4 => "l", + 8 => "q", + else => @compileError("Invalid atomic type " ++ @typeName(T)), + }; + + const old_bit = asm volatile (instruction ++ suffix ++ " %[bit], %[ptr]" + : [result] "={@ccc}" (-> u8) // LLVM doesn't support u1 flag register return values + : [ptr] "*p" (&self.value), + [bit] "X" (@as(T, bit)) + : "cc", "memory" + ); + + return @intCast(u1, old_bit); + } + + const mask = @as(T, 1) << bit; + const value = switch (op) { + .Set => self.fetchOr(mask, ordering), + .Reset => self.fetchAnd(~mask, ordering), + .Toggle => self.fetchXor(mask, ordering), + }; + + return @boolToInt(value & mask != 0); + } + }); + }; +} + +fn atomicIntTypes() []const type { + comptime var bytes = 1; + comptime var types: []const type = &[_]type{}; + inline while (bytes <= @sizeOf(usize)) : (bytes *= 2) { + types = types ++ &[_]type{std.meta.Int(.unsigned, bytes * 8)}; + } + return types; +} + +test "Atomic.loadUnchecked" { + inline for (atomicIntTypes()) |Int| { + var x = Atomic(Int).init(5); + try testing.expectEqual(x.loadUnchecked(), 5); + } +} + +test "Atomic.storeUnchecked" { + inline for (atomicIntTypes()) |Int| { + var x = Atomic(usize).init(5); + x.storeUnchecked(10); + try testing.expectEqual(x.loadUnchecked(), 10); + } +} + +test "Atomic.load" { + inline for (atomicIntTypes()) |Int| { + inline for (.{ .Unordered, .Monotonic, .Acquire, .SeqCst }) |ordering| { + var x = Atomic(Int).init(5); + try testing.expectEqual(x.load(ordering), 5); + } + } +} + +test "Atomic.store" { + inline for (atomicIntTypes()) |Int| { + inline for (.{ .Unordered, .Monotonic, .Release, .SeqCst }) |ordering| { + var x = Atomic(usize).init(5); + x.store(10, ordering); + try testing.expectEqual(x.load(.SeqCst), 10); + } + } +} + +const atomic_rmw_orderings = [_]Ordering{ + .Monotonic, + .Acquire, + .Release, + .AcqRel, + .SeqCst, +}; + +test "Atomic.swap" { + inline for (atomic_rmw_orderings) |ordering| { + var x = Atomic(usize).init(5); + try testing.expectEqual(x.swap(10, ordering), 5); + try testing.expectEqual(x.load(.SeqCst), 10); + + var y = Atomic(enum(usize) { a, b, c }).init(.c); + try testing.expectEqual(y.swap(.a, ordering), .c); + try testing.expectEqual(y.load(.SeqCst), .a); + + var z = Atomic(f32).init(5.0); + try testing.expectEqual(z.swap(10.0, ordering), 5.0); + try testing.expectEqual(z.load(.SeqCst), 10.0); + + var a = Atomic(bool).init(false); + try testing.expectEqual(a.swap(true, ordering), false); + try testing.expectEqual(a.load(.SeqCst), true); + + var b = Atomic(?*u8).init(null); + try testing.expectEqual(b.swap(@intToPtr(?*u8, @alignOf(u8)), ordering), null); + try testing.expectEqual(b.load(.SeqCst), @intToPtr(?*u8, @alignOf(u8))); + } +} + +const atomic_cmpxchg_orderings = [_][2]Ordering{ + .{ .Monotonic, .Monotonic }, + .{ .Acquire, .Monotonic }, + .{ .Acquire, .Acquire }, + .{ .Release, .Monotonic }, + // Although accepted by LLVM, acquire failure implies AcqRel success + // .{ .Release, .Acquire }, + .{ .AcqRel, .Monotonic }, + .{ .AcqRel, .Acquire }, + .{ .SeqCst, .Monotonic }, + .{ .SeqCst, .Acquire }, + .{ .SeqCst, .SeqCst }, +}; + +test "Atomic.compareAndSwap" { + inline for (atomicIntTypes()) |Int| { + inline for (atomic_cmpxchg_orderings) |ordering| { + var x = Atomic(Int).init(0); + try testing.expectEqual(x.compareAndSwap(1, 0, ordering[0], ordering[1]), 0); + try testing.expectEqual(x.load(.SeqCst), 0); + try testing.expectEqual(x.compareAndSwap(0, 1, ordering[0], ordering[1]), null); + try testing.expectEqual(x.load(.SeqCst), 1); + try testing.expectEqual(x.compareAndSwap(1, 0, ordering[0], ordering[1]), null); + try testing.expectEqual(x.load(.SeqCst), 0); + } + } +} + +test "Atomic.tryCompareAndSwap" { + inline for (atomicIntTypes()) |Int| { + inline for (atomic_cmpxchg_orderings) |ordering| { + var x = Atomic(Int).init(0); + + try testing.expectEqual(x.tryCompareAndSwap(1, 0, ordering[0], ordering[1]), 0); + try testing.expectEqual(x.load(.SeqCst), 0); + + while (x.tryCompareAndSwap(0, 1, ordering[0], ordering[1])) |_| {} + try testing.expectEqual(x.load(.SeqCst), 1); + + while (x.tryCompareAndSwap(1, 0, ordering[0], ordering[1])) |_| {} + try testing.expectEqual(x.load(.SeqCst), 0); + } + } +} + +test "Atomic.fetchAdd" { + inline for (atomicIntTypes()) |Int| { + inline for (atomic_rmw_orderings) |ordering| { + var x = Atomic(Int).init(5); + try testing.expectEqual(x.fetchAdd(5, ordering), 5); + try testing.expectEqual(x.load(.SeqCst), 10); + try testing.expectEqual(x.fetchAdd(std.math.maxInt(Int), ordering), 10); + try testing.expectEqual(x.load(.SeqCst), 9); + } + } +} + +test "Atomic.fetchSub" { + inline for (atomicIntTypes()) |Int| { + inline for (atomic_rmw_orderings) |ordering| { + var x = Atomic(Int).init(5); + try testing.expectEqual(x.fetchSub(5, ordering), 5); + try testing.expectEqual(x.load(.SeqCst), 0); + try testing.expectEqual(x.fetchSub(1, ordering), 0); + try testing.expectEqual(x.load(.SeqCst), std.math.maxInt(Int)); + } + } +} + +test "Atomic.fetchMin" { + inline for (atomicIntTypes()) |Int| { + inline for (atomic_rmw_orderings) |ordering| { + var x = Atomic(Int).init(5); + try testing.expectEqual(x.fetchMin(0, ordering), 5); + try testing.expectEqual(x.load(.SeqCst), 0); + try testing.expectEqual(x.fetchMin(10, ordering), 0); + try testing.expectEqual(x.load(.SeqCst), 0); + } + } +} + +test "Atomic.fetchMax" { + inline for (atomicIntTypes()) |Int| { + inline for (atomic_rmw_orderings) |ordering| { + var x = Atomic(Int).init(5); + try testing.expectEqual(x.fetchMax(10, ordering), 5); + try testing.expectEqual(x.load(.SeqCst), 10); + try testing.expectEqual(x.fetchMax(5, ordering), 10); + try testing.expectEqual(x.load(.SeqCst), 10); + } + } +} + +test "Atomic.fetchAnd" { + inline for (atomicIntTypes()) |Int| { + inline for (atomic_rmw_orderings) |ordering| { + var x = Atomic(Int).init(0b11); + try testing.expectEqual(x.fetchAnd(0b10, ordering), 0b11); + try testing.expectEqual(x.load(.SeqCst), 0b10); + try testing.expectEqual(x.fetchAnd(0b00, ordering), 0b10); + try testing.expectEqual(x.load(.SeqCst), 0b00); + } + } +} + +test "Atomic.fetchNand" { + inline for (atomicIntTypes()) |Int| { + inline for (atomic_rmw_orderings) |ordering| { + var x = Atomic(Int).init(0b11); + try testing.expectEqual(x.fetchNand(0b10, ordering), 0b11); + try testing.expectEqual(x.load(.SeqCst), ~@as(Int, 0b10)); + try testing.expectEqual(x.fetchNand(0b00, ordering), ~@as(Int, 0b10)); + try testing.expectEqual(x.load(.SeqCst), ~@as(Int, 0b00)); + } + } +} + +test "Atomic.fetchOr" { + inline for (atomicIntTypes()) |Int| { + inline for (atomic_rmw_orderings) |ordering| { + var x = Atomic(Int).init(0b11); + try testing.expectEqual(x.fetchOr(0b100, ordering), 0b11); + try testing.expectEqual(x.load(.SeqCst), 0b111); + try testing.expectEqual(x.fetchOr(0b010, ordering), 0b111); + try testing.expectEqual(x.load(.SeqCst), 0b111); + } + } +} + +test "Atomic.fetchXor" { + inline for (atomicIntTypes()) |Int| { + inline for (atomic_rmw_orderings) |ordering| { + var x = Atomic(Int).init(0b11); + try testing.expectEqual(x.fetchXor(0b10, ordering), 0b11); + try testing.expectEqual(x.load(.SeqCst), 0b01); + try testing.expectEqual(x.fetchXor(0b01, ordering), 0b01); + try testing.expectEqual(x.load(.SeqCst), 0b00); + } + } +} + +test "Atomic.bitSet" { + inline for (atomicIntTypes()) |Int| { + inline for (atomic_rmw_orderings) |ordering| { + var x = Atomic(Int).init(0); + const bit_array = @as([std.meta.bitCount(Int)]void, undefined); + + for (bit_array) |_, bit_index| { + const bit = @intCast(std.math.Log2Int(Int), bit_index); + const mask = @as(Int, 1) << bit; + + // setting the bit should change the bit + try testing.expect(x.load(.SeqCst) & mask == 0); + try testing.expectEqual(x.bitSet(bit, ordering), 0); + try testing.expect(x.load(.SeqCst) & mask != 0); + + // setting it again shouldn't change the bit + try testing.expectEqual(x.bitSet(bit, ordering), 1); + try testing.expect(x.load(.SeqCst) & mask != 0); + + // all the previous bits should have not changed (still be set) + for (bit_array[0..bit_index]) |_, prev_bit_index| { + const prev_bit = @intCast(std.math.Log2Int(Int), prev_bit_index); + const prev_mask = @as(Int, 1) << prev_bit; + try testing.expect(x.load(.SeqCst) & prev_mask != 0); + } + } + } + } +} + +test "Atomic.bitReset" { + inline for (atomicIntTypes()) |Int| { + inline for (atomic_rmw_orderings) |ordering| { + var x = Atomic(Int).init(0); + const bit_array = @as([std.meta.bitCount(Int)]void, undefined); + + for (bit_array) |_, bit_index| { + const bit = @intCast(std.math.Log2Int(Int), bit_index); + const mask = @as(Int, 1) << bit; + x.storeUnchecked(x.loadUnchecked() | mask); + + // unsetting the bit should change the bit + try testing.expect(x.load(.SeqCst) & mask != 0); + try testing.expectEqual(x.bitReset(bit, ordering), 1); + try testing.expect(x.load(.SeqCst) & mask == 0); + + // unsetting it again shouldn't change the bit + try testing.expectEqual(x.bitReset(bit, ordering), 0); + try testing.expect(x.load(.SeqCst) & mask == 0); + + // all the previous bits should have not changed (still be reset) + for (bit_array[0..bit_index]) |_, prev_bit_index| { + const prev_bit = @intCast(std.math.Log2Int(Int), prev_bit_index); + const prev_mask = @as(Int, 1) << prev_bit; + try testing.expect(x.load(.SeqCst) & prev_mask == 0); + } + } + } + } +} + +test "Atomic.bitToggle" { + inline for (atomicIntTypes()) |Int| { + inline for (atomic_rmw_orderings) |ordering| { + var x = Atomic(Int).init(0); + const bit_array = @as([std.meta.bitCount(Int)]void, undefined); + + for (bit_array) |_, bit_index| { + const bit = @intCast(std.math.Log2Int(Int), bit_index); + const mask = @as(Int, 1) << bit; + + // toggling the bit should change the bit + try testing.expect(x.load(.SeqCst) & mask == 0); + try testing.expectEqual(x.bitToggle(bit, ordering), 0); + try testing.expect(x.load(.SeqCst) & mask != 0); + + // toggling it again *should* change the bit + try testing.expectEqual(x.bitToggle(bit, ordering), 1); + try testing.expect(x.load(.SeqCst) & mask == 0); + + // all the previous bits should have not changed (still be toggled back) + for (bit_array[0..bit_index]) |_, prev_bit_index| { + const prev_bit = @intCast(std.math.Log2Int(Int), prev_bit_index); + const prev_mask = @as(Int, 1) << prev_bit; + try testing.expect(x.load(.SeqCst) & prev_mask == 0); + } + } + } + } +} diff --git a/lib/std/atomic/bool.zig b/lib/std/atomic/bool.zig deleted file mode 100644 index 1c6ac8d04627..000000000000 --- a/lib/std/atomic/bool.zig +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2015-2021 Zig Contributors -// This file is part of [zig](https://ziglang.org/), which is MIT licensed. -// The MIT license requires this copyright notice to be included in all copies -// and substantial portions of the software. - -const std = @import("std"); -const builtin = std.builtin; -const testing = std.testing; - -/// Thread-safe, lock-free boolean -pub const Bool = extern struct { - unprotected_value: bool, - - pub const Self = @This(); - - pub fn init(init_val: bool) Self { - return Self{ .unprotected_value = init_val }; - } - - // xchg is only valid rmw operation for a bool - /// Atomically modifies memory and then returns the previous value. - pub fn xchg(self: *Self, operand: bool, comptime ordering: std.builtin.AtomicOrder) bool { - switch (ordering) { - .Monotonic, .Acquire, .Release, .AcqRel, .SeqCst => {}, - else => @compileError("Invalid ordering '" ++ @tagName(ordering) ++ "' for a RMW operation"), - } - return @atomicRmw(bool, &self.unprotected_value, .Xchg, operand, ordering); - } - - pub fn load(self: *const Self, comptime ordering: std.builtin.AtomicOrder) bool { - switch (ordering) { - .Unordered, .Monotonic, .Acquire, .SeqCst => {}, - else => @compileError("Invalid ordering '" ++ @tagName(ordering) ++ "' for a load operation"), - } - return @atomicLoad(bool, &self.unprotected_value, ordering); - } - - pub fn store(self: *Self, value: bool, comptime ordering: std.builtin.AtomicOrder) void { - switch (ordering) { - .Unordered, .Monotonic, .Release, .SeqCst => {}, - else => @compileError("Invalid ordering '" ++ @tagName(ordering) ++ "' for a store operation"), - } - @atomicStore(bool, &self.unprotected_value, value, ordering); - } -}; - -test "std.atomic.Bool" { - var a = Bool.init(false); - try testing.expectEqual(false, a.xchg(false, .SeqCst)); - try testing.expectEqual(false, a.load(.SeqCst)); - a.store(true, .SeqCst); - try testing.expectEqual(true, a.xchg(false, .SeqCst)); - try testing.expectEqual(false, a.load(.SeqCst)); -} diff --git a/lib/std/atomic/int.zig b/lib/std/atomic/int.zig deleted file mode 100644 index 3809541fe333..000000000000 --- a/lib/std/atomic/int.zig +++ /dev/null @@ -1,92 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2015-2021 Zig Contributors -// This file is part of [zig](https://ziglang.org/), which is MIT licensed. -// The MIT license requires this copyright notice to be included in all copies -// and substantial portions of the software. - -const std = @import("std"); -const builtin = std.builtin; -const testing = std.testing; - -/// Thread-safe, lock-free integer -pub fn Int(comptime T: type) type { - if (!std.meta.trait.isIntegral(T)) - @compileError("Expected integral type, got '" ++ @typeName(T) ++ "'"); - - return extern struct { - unprotected_value: T, - - pub const Self = @This(); - - pub fn init(init_val: T) Self { - return Self{ .unprotected_value = init_val }; - } - - /// Read, Modify, Write - pub fn rmw(self: *Self, comptime op: builtin.AtomicRmwOp, operand: T, comptime ordering: builtin.AtomicOrder) T { - switch (ordering) { - .Monotonic, .Acquire, .Release, .AcqRel, .SeqCst => {}, - else => @compileError("Invalid ordering '" ++ @tagName(ordering) ++ "' for a RMW operation"), - } - return @atomicRmw(T, &self.unprotected_value, op, operand, ordering); - } - - pub fn load(self: *const Self, comptime ordering: builtin.AtomicOrder) T { - switch (ordering) { - .Unordered, .Monotonic, .Acquire, .SeqCst => {}, - else => @compileError("Invalid ordering '" ++ @tagName(ordering) ++ "' for a load operation"), - } - return @atomicLoad(T, &self.unprotected_value, ordering); - } - - pub fn store(self: *Self, value: T, comptime ordering: builtin.AtomicOrder) void { - switch (ordering) { - .Unordered, .Monotonic, .Release, .SeqCst => {}, - else => @compileError("Invalid ordering '" ++ @tagName(ordering) ++ "' for a store operation"), - } - @atomicStore(T, &self.unprotected_value, value, ordering); - } - - /// Twos complement wraparound increment - /// Returns previous value - pub fn incr(self: *Self) T { - return self.rmw(.Add, 1, .SeqCst); - } - - /// Twos complement wraparound decrement - /// Returns previous value - pub fn decr(self: *Self) T { - return self.rmw(.Sub, 1, .SeqCst); - } - - pub fn get(self: *const Self) T { - return self.load(.SeqCst); - } - - pub fn set(self: *Self, new_value: T) void { - self.store(new_value, .SeqCst); - } - - pub fn xchg(self: *Self, new_value: T) T { - return self.rmw(.Xchg, new_value, .SeqCst); - } - - /// Twos complement wraparound add - /// Returns previous value - pub fn fetchAdd(self: *Self, op: T) T { - return self.rmw(.Add, op, .SeqCst); - } - }; -} - -test "std.atomic.Int" { - var a = Int(u8).init(0); - try testing.expectEqual(@as(u8, 0), a.incr()); - try testing.expectEqual(@as(u8, 1), a.load(.SeqCst)); - a.store(42, .SeqCst); - try testing.expectEqual(@as(u8, 42), a.decr()); - try testing.expectEqual(@as(u8, 41), a.xchg(100)); - try testing.expectEqual(@as(u8, 100), a.fetchAdd(5)); - try testing.expectEqual(@as(u8, 105), a.get()); - a.set(200); -} diff --git a/lib/std/json.zig b/lib/std/json.zig index 811db020cac1..fd05a41a6d32 100644 --- a/lib/std/json.zig +++ b/lib/std/json.zig @@ -2014,7 +2014,10 @@ test "parse into struct with duplicate field" { const ballast = try testing.allocator.alloc(u64, 1); defer testing.allocator.free(ballast); - const options_first = ParseOptions{ .allocator = testing.allocator, .duplicate_field_behavior = .UseFirst }; + const options_first = ParseOptions{ + .allocator = testing.allocator, + .duplicate_field_behavior = .UseFirst, + }; const options_last = ParseOptions{ .allocator = testing.allocator, diff --git a/lib/std/os.zig b/lib/std/os.zig index 93d0b9832d48..2fe7ba9c5a91 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -5534,7 +5534,7 @@ pub const CopyFileRangeError = error{ var has_copy_file_range_syscall = init: { const kernel_has_syscall = std.Target.current.os.isAtLeast(.linux, .{ .major = 4, .minor = 5 }) orelse true; - break :init std.atomic.Bool.init(kernel_has_syscall); + break :init std.atomic.Atomic(bool).init(kernel_has_syscall); }; /// Transfer data between file descriptors at specified offsets. diff --git a/lib/std/packed_int_array.zig b/lib/std/packed_int_array.zig index e1c22e2860ac..ec401e98e2b5 100644 --- a/lib/std/packed_int_array.zig +++ b/lib/std/packed_int_array.zig @@ -379,9 +379,9 @@ test "PackedIntArray" { } test "PackedIntIo" { - const bytes = [_]u8 { 0b01101_000, 0b01011_110, 0b00011_101 }; - try testing.expectEqual(@as(u15, 0x2bcd), PackedIntIo(u15, .Little).get(&bytes, 0, 3)); - try testing.expectEqual(@as(u16, 0xabcd), PackedIntIo(u16, .Little).get(&bytes, 0, 3)); + const bytes = [_]u8{ 0b01101_000, 0b01011_110, 0b00011_101 }; + try testing.expectEqual(@as(u15, 0x2bcd), PackedIntIo(u15, .Little).get(&bytes, 0, 3)); + try testing.expectEqual(@as(u16, 0xabcd), PackedIntIo(u16, .Little).get(&bytes, 0, 3)); try testing.expectEqual(@as(u17, 0x1abcd), PackedIntIo(u17, .Little).get(&bytes, 0, 3)); try testing.expectEqual(@as(u18, 0x3abcd), PackedIntIo(u18, .Little).get(&bytes, 0, 3)); } diff --git a/lib/std/target.zig b/lib/std/target.zig index 14420d529024..692d29b1c7e6 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -767,6 +767,13 @@ pub const Target = struct { spirv32, spirv64, + pub fn isX86(arch: Arch) bool { + return switch (arch) { + .i386, .x86_64 => true, + else => false, + }; + } + pub fn isARM(arch: Arch) bool { return switch (arch) { .arm, .armeb => true, diff --git a/src/BuiltinFn.zig b/src/BuiltinFn.zig index 11c6422f9827..aa0ba0515df7 100644 --- a/src/BuiltinFn.zig +++ b/src/BuiltinFn.zig @@ -400,7 +400,7 @@ pub const list = list: { "@fence", .{ .tag = .fence, - .param_count = 0, + .param_count = 1, }, }, .{