Skip to content

Commit

Permalink
Add support for Atomic(Bool) (#14532)
Browse files Browse the repository at this point in the history
  • Loading branch information
ysbaddaden authored Apr 28, 2024
1 parent a726bf7 commit 5cee8a9
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 24 deletions.
71 changes: 70 additions & 1 deletion spec/std/atomic_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,24 @@ enum AtomicEnumFlags
Three
end

private struct AtomicBooleans
@one = Atomic(Bool).new(false)
@two = Atomic(Bool).new(false)
@three = Atomic(Bool).new(false)
end

describe Atomic do
describe "#compare_and_set" do
it "with bool" do
atomic = Atomic.new(true)

atomic.compare_and_set(false, true).should eq({true, false})
atomic.get.should eq(true)

atomic.compare_and_set(true, false).should eq({true, true})
atomic.get.should eq(false)
end

it "with integer" do
atomic = Atomic.new(1)

Expand Down Expand Up @@ -240,6 +256,12 @@ describe Atomic do
end

describe "#set" do
it "with bool" do
atomic = Atomic.new(false)
atomic.set(true).should eq(true)
atomic.get.should eq(true)
end

it "with integer" do
atomic = Atomic.new(1)
atomic.set(2).should eq(2)
Expand Down Expand Up @@ -272,10 +294,20 @@ describe Atomic do
it "#lazy_set" do
atomic = Atomic.new(1)
atomic.lazy_set(2).should eq(2)
atomic.get.should eq(2)
atomic.lazy_get.should eq(2)

bool = Atomic.new(true)
bool.lazy_set(false).should eq(false)
bool.lazy_get.should eq(false)
end

describe "#swap" do
it "with bool" do
atomic = Atomic.new(true)
atomic.swap(false).should eq(true)
atomic.get.should eq(false)
end

it "with integer" do
atomic = Atomic.new(1)
atomic.swap(2).should eq(1)
Expand Down Expand Up @@ -322,6 +354,43 @@ describe Atomic do
atomic.get.should eq(2)
end
end

describe "atomic bool" do
it "sizeof" do
sizeof(Atomic(Bool)).should eq(1)
sizeof(AtomicBooleans).should eq(3)
end

it "gets and sets" do
booleans = AtomicBooleans.new

booleans.@one.get.should eq(false)
booleans.@two.get.should eq(false)
booleans.@three.get.should eq(false)

booleans.@two.set(true)
booleans.@one.get.should eq(false)
booleans.@two.get.should eq(true)
booleans.@three.get.should eq(false)

booleans.@one.set(true)
booleans.@three.set(true)
booleans.@one.get.should eq(true)
booleans.@two.get.should eq(true)
booleans.@three.get.should eq(true)

booleans.@one.set(false)
booleans.@three.set(false)
booleans.@one.get.should eq(false)
booleans.@two.get.should eq(true)
booleans.@three.get.should eq(false)

booleans.@two.set(false)
booleans.@one.get.should eq(false)
booleans.@two.get.should eq(false)
booleans.@three.get.should eq(false)
end
end
end

describe Atomic::Flag do
Expand Down
100 changes: 77 additions & 23 deletions src/atomic.cr
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ struct Atomic(T)

# Creates an Atomic with the given initial value.
def initialize(@value : T)
{% if !T.union? && (T == Char || T < Int::Primitive || T < Enum) %}
# Support integer types, enum types, or char (because it's represented as an integer)
{% if !T.union? && (T == Bool || T == Char || T < Int::Primitive || T < Enum) %}
# Support integer types, enum types, bool or char (because it's represented as an integer)
{% elsif T < Pointer %}
# Support pointer types
{% elsif T.union_types.all? { |t| t == Nil || t < Reference } && T != Nil %}
Expand All @@ -71,7 +71,7 @@ struct Atomic(T)
# atomic.get # => 3
# ```
def compare_and_set(cmp : T, new : T) : {T, Bool}
Ops.cmpxchg(pointerof(@value), cmp.as(T), new.as(T), :sequentially_consistent, :sequentially_consistent)
cast_from Ops.cmpxchg(as_pointer, cast_to(cmp), cast_to(new), :sequentially_consistent, :sequentially_consistent)
end

# Compares this atomic's value with *cmp* using explicit memory orderings:
Expand All @@ -93,25 +93,25 @@ struct Atomic(T)
def compare_and_set(cmp : T, new : T, success_ordering : Ordering, failure_ordering : Ordering) : {T, Bool}
case {success_ordering, failure_ordering}
when {.relaxed?, .relaxed?}
Ops.cmpxchg(pointerof(@value), cmp.as(T), new.as(T), :monotonic, :monotonic)
cast_from Ops.cmpxchg(as_pointer, cast_to(cmp), cast_to(new), :monotonic, :monotonic)
when {.acquire?, .relaxed?}
Ops.cmpxchg(pointerof(@value), cmp.as(T), new.as(T), :acquire, :monotonic)
cast_from Ops.cmpxchg(as_pointer, cast_to(cmp), cast_to(new), :acquire, :monotonic)
when {.acquire?, .acquire?}
Ops.cmpxchg(pointerof(@value), cmp.as(T), new.as(T), :acquire, :acquire)
cast_from Ops.cmpxchg(as_pointer, cast_to(cmp), cast_to(new), :acquire, :acquire)
when {.release?, .relaxed?}
Ops.cmpxchg(pointerof(@value), cmp.as(T), new.as(T), :release, :monotonic)
cast_from Ops.cmpxchg(as_pointer, cast_to(cmp), cast_to(new), :release, :monotonic)
when {.release?, .acquire?}
Ops.cmpxchg(pointerof(@value), cmp.as(T), new.as(T), :release, :acquire)
cast_from Ops.cmpxchg(as_pointer, cast_to(cmp), cast_to(new), :release, :acquire)
when {.acquire_release?, .relaxed?}
Ops.cmpxchg(pointerof(@value), cmp.as(T), new.as(T), :acquire_release, :monotonic)
cast_from Ops.cmpxchg(as_pointer, cast_to(cmp), cast_to(new), :acquire_release, :monotonic)
when {.acquire_release?, .acquire?}
Ops.cmpxchg(pointerof(@value), cmp.as(T), new.as(T), :acquire_release, :acquire)
cast_from Ops.cmpxchg(as_pointer, cast_to(cmp), cast_to(new), :acquire_release, :acquire)
when {.sequentially_consistent?, .relaxed?}
Ops.cmpxchg(pointerof(@value), cmp.as(T), new.as(T), :sequentially_consistent, :monotonic)
cast_from Ops.cmpxchg(as_pointer, cast_to(cmp), cast_to(new), :sequentially_consistent, :monotonic)
when {.sequentially_consistent?, .acquire?}
Ops.cmpxchg(pointerof(@value), cmp.as(T), new.as(T), :sequentially_consistent, :acquire)
cast_from Ops.cmpxchg(as_pointer, cast_to(cmp), cast_to(new), :sequentially_consistent, :acquire)
when {.sequentially_consistent?, .sequentially_consistent?}
Ops.cmpxchg(pointerof(@value), cmp.as(T), new.as(T), :sequentially_consistent, :sequentially_consistent)
cast_from Ops.cmpxchg(as_pointer, cast_to(cmp), cast_to(new), :sequentially_consistent, :sequentially_consistent)
else
if failure_ordering.release? || failure_ordering.acquire_release?
raise ArgumentError.new("Failure ordering cannot include release semantics")
Expand All @@ -132,6 +132,7 @@ struct Atomic(T)
def add(value : T, ordering : Ordering = :sequentially_consistent) : T
check_pointer_type
check_reference_type
check_bool_type
atomicrmw(:add, pointerof(@value), value, ordering)
end

Expand All @@ -147,6 +148,7 @@ struct Atomic(T)
def sub(value : T, ordering : Ordering = :sequentially_consistent) : T
check_pointer_type
check_reference_type
check_bool_type
atomicrmw(:sub, pointerof(@value), value, ordering)
end

Expand All @@ -162,6 +164,7 @@ struct Atomic(T)
def and(value : T, ordering : Ordering = :sequentially_consistent) : T
check_pointer_type
check_reference_type
check_bool_type
atomicrmw(:and, pointerof(@value), value, ordering)
end

Expand All @@ -177,6 +180,7 @@ struct Atomic(T)
def nand(value : T, ordering : Ordering = :sequentially_consistent) : T
check_pointer_type
check_reference_type
check_bool_type
atomicrmw(:nand, pointerof(@value), value, ordering)
end

Expand All @@ -192,6 +196,7 @@ struct Atomic(T)
def or(value : T, ordering : Ordering = :sequentially_consistent) : T
check_pointer_type
check_reference_type
check_bool_type
atomicrmw(:or, pointerof(@value), value, ordering)
end

Expand All @@ -207,6 +212,7 @@ struct Atomic(T)
def xor(value : T, ordering : Ordering = :sequentially_consistent) : T
check_pointer_type
check_reference_type
check_bool_type
atomicrmw(:xor, pointerof(@value), value, ordering)
end

Expand All @@ -225,6 +231,7 @@ struct Atomic(T)
# ```
def max(value : T, ordering : Ordering = :sequentially_consistent)
check_reference_type
check_bool_type
{% if T < Enum %}
if @value.value.is_a?(Int::Signed)
atomicrmw(:max, pointerof(@value), value, ordering)
Expand Down Expand Up @@ -255,6 +262,7 @@ struct Atomic(T)
# ```
def min(value : T, ordering : Ordering = :sequentially_consistent)
check_reference_type
check_bool_type
{% if T < Enum %}
if @value.value.is_a?(Int::Signed)
atomicrmw(:min, pointerof(@value), value, ordering)
Expand Down Expand Up @@ -284,7 +292,7 @@ struct Atomic(T)
address = atomicrmw(:xchg, pointerof(@value).as(LibC::SizeT*), LibC::SizeT.new(value.as(Void*).address), ordering)
Pointer(T).new(address).as(T)
{% else %}
atomicrmw(:xchg, pointerof(@value), value, ordering)
cast_from atomicrmw(:xchg, as_pointer, cast_to(value), ordering)
{% end %}
end

Expand All @@ -298,11 +306,11 @@ struct Atomic(T)
def set(value : T, ordering : Ordering = :sequentially_consistent) : T
case ordering
in .relaxed?
Ops.store(pointerof(@value), value.as(T), :monotonic, true)
Ops.store(as_pointer, cast_to(value), :monotonic, true)
in .release?
Ops.store(pointerof(@value), value.as(T), :release, true)
Ops.store(as_pointer, cast_to(value), :release, true)
in .sequentially_consistent?
Ops.store(pointerof(@value), value.as(T), :sequentially_consistent, true)
Ops.store(as_pointer, cast_to(value), :sequentially_consistent, true)
in .acquire?, .acquire_release?
raise ArgumentError.new("Atomic store cannot have acquire semantic")
end
Expand All @@ -325,11 +333,11 @@ struct Atomic(T)
def get(ordering : Ordering = :sequentially_consistent) : T
case ordering
in .relaxed?
Ops.load(pointerof(@value), :monotonic, true)
cast_from Ops.load(as_pointer, :monotonic, true)
in .acquire?
Ops.load(pointerof(@value), :acquire, true)
cast_from Ops.load(as_pointer, :acquire, true)
in .sequentially_consistent?
Ops.load(pointerof(@value), :sequentially_consistent, true)
cast_from Ops.load(as_pointer, :sequentially_consistent, true)
in .release?, .acquire_release?
raise ArgumentError.new("Atomic load cannot have release semantic")
end
Expand All @@ -342,6 +350,12 @@ struct Atomic(T)
@value
end

private macro check_bool_type
{% if T == Bool %}
{% raise "Cannot call `#{@type}##{@def.name}` for `#{T}` type" %}
{% end %}
end

private macro check_pointer_type
{% if T < Pointer %}
{% raise "Cannot call `#{@type}##{@def.name}` as `#{T}` is a pointer type" %}
Expand Down Expand Up @@ -393,6 +407,46 @@ struct Atomic(T)
def self.store(ptr : T*, value : T, ordering : LLVM::AtomicOrdering, volatile : Bool) : Nil forall T
end
end

@[AlwaysInline]
private def as_pointer
{% if T == Bool %}
# assumes that a bool sizeof/alignof is 1 byte (and that a struct wrapping
# a single boolean ivar has a sizeof/alignof of at least 1 byte, too) so
# there is enough padding, and we can safely cast the int1 representation
# of a bool as an int8
pointerof(@value).as(Int8*)
{% else %}
pointerof(@value)
{% end %}
end

@[AlwaysInline]
private def cast_to(value)
{% if T == Bool %}
value.unsafe_as(Int8)
{% else %}
value.as(T)
{% end %}
end

@[AlwaysInline]
private def cast_from(value : Tuple)
{% if T == Bool %}
{value[0].unsafe_as(Bool), value[1]}
{% else %}
value
{% end %}
end

@[AlwaysInline]
private def cast_from(value)
{% if T == Bool %}
value.unsafe_as(Bool)
{% else %}
value
{% end %}
end
end

# An atomic flag, that can be set or not.
Expand All @@ -410,13 +464,13 @@ end
# ```
struct Atomic::Flag
def initialize
@value = 0_u8
@value = Atomic(Bool).new(false)
end

# Atomically tries to set the flag. Only succeeds and returns `true` if the
# flag wasn't previously set; returns `false` otherwise.
def test_and_set : Bool
ret = Atomic::Ops.atomicrmw(:xchg, pointerof(@value), 1_u8, :sequentially_consistent, false) == 0_u8
ret = @value.swap(true, :sequentially_consistent) == false
{% if flag?(:arm) %}
Atomic::Ops.fence(:sequentially_consistent, false) if ret
{% end %}
Expand All @@ -428,6 +482,6 @@ struct Atomic::Flag
{% if flag?(:arm) %}
Atomic::Ops.fence(:sequentially_consistent, false)
{% end %}
Atomic::Ops.store(pointerof(@value), 0_u8, :sequentially_consistent, true)
@value.set(false, :sequentially_consistent)
end
end

0 comments on commit 5cee8a9

Please sign in to comment.