Skip to content

Commit

Permalink
Expose LLVM atomic operations to Crystal, and add Atomic(T) struct.
Browse files Browse the repository at this point in the history
We will need this for efficiently implementing multiple-thread support.
  • Loading branch information
Ary Borenszweig committed Oct 5, 2016
1 parent a8eef9b commit 919818e
Show file tree
Hide file tree
Showing 12 changed files with 550 additions and 14 deletions.
130 changes: 130 additions & 0 deletions spec/std/atomic_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# TODO: enable after 0.19.3
# require "spec"

# describe Atomic do
# it "compares and sets with integer" do
# atomic = Atomic.new(1)

# atomic.compare_and_set(2, 3).should eq({1, false})
# atomic.get.should eq(1)

# atomic.compare_and_set(1, 3).should eq({1, true})
# atomic.get.should eq(3)
# end

# it "compares and sets with nilable type" do
# atomic = Atomic(String?).new(nil)
# string = "hello"

# atomic.compare_and_set(string, "foo").should eq({nil, false})
# atomic.get.should be_nil

# atomic.compare_and_set(nil, string).should eq({nil, true})
# atomic.get.should be(string)

# atomic.compare_and_set(string, nil).should eq({string, true})
# atomic.get.should be_nil
# end

# it "compares and sets with reference type" do
# str1 = "hello"
# str2 = "bye"

# atomic = Atomic(String).new(str1)

# atomic.compare_and_set(str2, "foo").should eq({str1, false})
# atomic.get.should eq(str1)

# atomic.compare_and_set(str1, str2).should eq({str1, true})
# atomic.get.should be(str2)

# atomic.compare_and_set(str2, str1).should eq({str2, true})
# atomic.get.should be(str1)
# end

# it "#adds" do
# atomic = Atomic.new(1)
# atomic.add(2).should eq(1)
# atomic.get.should eq(3)
# end

# it "#sub" do
# atomic = Atomic.new(1)
# atomic.sub(2).should eq(1)
# atomic.get.should eq(-1)
# end

# it "#and" do
# atomic = Atomic.new(5)
# atomic.and(3).should eq(5)
# atomic.get.should eq(1)
# end

# it "#nand" do
# atomic = Atomic.new(5)
# atomic.nand(3).should eq(5)
# atomic.get.should eq(-2)
# end

# it "#or" do
# atomic = Atomic.new(5)
# atomic.or(2).should eq(5)
# atomic.get.should eq(7)
# end

# it "#xor" do
# atomic = Atomic.new(5)
# atomic.xor(3).should eq(5)
# atomic.get.should eq(6)
# end

# it "#max with signed" do
# atomic = Atomic.new(5)
# atomic.max(2).should eq(5)
# atomic.get.should eq(5)
# atomic.max(10).should eq(5)
# atomic.get.should eq(10)
# end

# it "#max with unsigned" do
# atomic = Atomic.new(5_u32)
# atomic.max(2_u32).should eq(5_u32)
# atomic.get.should eq(5_u32)
# atomic.max(UInt32::MAX).should eq(5_u32)
# atomic.get.should eq(UInt32::MAX)
# end

# it "#min with signed" do
# atomic = Atomic.new(5)
# atomic.min(10).should eq(5)
# atomic.get.should eq(5)
# atomic.min(2).should eq(5)
# atomic.get.should eq(2)
# end

# it "#min with unsigned" do
# atomic = Atomic.new(UInt32::MAX)
# atomic.min(10_u32).should eq(UInt32::MAX)
# atomic.get.should eq(10_u32)
# atomic.min(15_u32).should eq(10_u32)
# atomic.get.should eq(10_u32)
# end

# it "#set" do
# atomic = Atomic.new(1)
# atomic.set(2).should eq(2)
# atomic.get.should eq(2)
# end

# it "#lazy_set" do
# atomic = Atomic.new(1)
# atomic.lazy_set(2).should eq(2)
# atomic.get.should eq(2)
# end

# it "#swap" do
# atomic = Atomic.new(1)
# atomic.swap(2).should eq(1)
# atomic.get.should eq(2)
# end
# end
219 changes: 219 additions & 0 deletions src/atomic.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
# A value that may be updated atomically.
#
# Only primitive integer types, reference types or nilable reference types
# can be used with an Atomic type.
struct Atomic(T)
# Creates an Atomic with the given initial value.
def initialize(@value : T)
{% if !T.union? && (T == Char || T < Int::Primitive) %}
# Support integer types or char (because it's represented as an integer)
{% elsif T < Reference || (T.union? && T.union_types.all? { |t| t == Nil || t < Reference }) %}
# Support reference types, or union types with only nil or reference types
{% else %}
{{ raise "Can only create Atomic with primitive integer types, reference types or nilable reference types, not #{T}" }}
{% end %}
end

# Compares this atomic's value with *cmp*:
#
# * if they are equal, sets the value to *new*, and returns `{old_value, true}`
# * if they are not equal the value remains the same, and returns `{old_value, false}`
#
# ```
# atomic = Atomic.new(1)
#
# atomic.compare_and_set(2, 3) # => {1, false}
# atomic.value # => 1
#
# atomic.compare_and_set(1, 3) # => {1, true}
# atomic.value # => 3
# ```
def compare_and_set(cmp : T, new : T) : {T, Bool}
# Check if it's a nilable reference type
{% if T.union? && T.union_types.all? { |t| t == Nil || t < Reference } %}
# If so, use addresses because LLVM < 3.9 doesn't support cmpxchg with pointers
address, success = Ops.cmpxchg(pointerof(@value).as(LibC::SizeT*), LibC::SizeT.new(cmp.as(T).object_id), LibC::SizeT.new(new.as(T).object_id), :sequentially_consistent, :sequentially_consistent)
{address == 0 ? nil : Pointer(T).new(address).as(T), success}
# Check if it's a reference type
{% elsif T < Reference %}
# Use addresses again (but this can't return nil)
address, success = Ops.cmpxchg(pointerof(@value).as(LibC::SizeT*), LibC::SizeT.new(cmp.as(T).object_id), LibC::SizeT.new(new.as(T).object_id), :sequentially_consistent, :sequentially_consistent)
{Pointer(T).new(address).as(T), success}
{% else %}
# Otherwise, this is an integer type
Ops.cmpxchg(pointerof(@value), cmp, new, :sequentially_consistent, :sequentially_consistent)
{% end %}
end

# Performs `atomic_value += value`. Returns the old value.
#
# ```
# atomic = Atomic.new(1)
# atomic.add(2) # => 2
# atomic.value # => 3
# ```
def add(value : T)
Ops.atomicrmw(:add, pointerof(@value), value, :sequentially_consistent, false)
end

# Performs `atomic_value -= value`. Returns the old value.
#
# ```
# atomic = Atomic.new(9)
# atomic.sub(2) # => 9
# atomic.value # => 7
# ```
def sub(value : T)
Ops.atomicrmw(:sub, pointerof(@value), value, :sequentially_consistent, false)
end

# Performs `atomic_value &= value`. Returns the old value.
#
# ```
# atomic = Atomic.new(5)
# atomic.and(3) # => 5
# atomic.value # => 1
# ```
def and(value : T)
Ops.atomicrmw(:and, pointerof(@value), value, :sequentially_consistent, false)
end

# Performs `atomic_value = ~(atomic_value & value)`. Returns the old value.
#
# ```
# atomic = Atomic.new(5)
# atomic.nand(3) # => 5
# atomic.value # => -2
# ```
def nand(value : T)
Ops.atomicrmw(:nand, pointerof(@value), value, :sequentially_consistent, false)
end

# Performs `atomic_value |= value`. Returns the old value.
#
# ```
# atomic = Atomic.new(5)
# atomic.or(2) # => 5
# atomic.value # => 7
# ```
def or(value : T)
Ops.atomicrmw(:or, pointerof(@value), value, :sequentially_consistent, false)
end

# Performs `atomic_value ^= value`. Returns the old value.
#
# ```
# atomic = Atomic.new(5)
# atomic.or(3) # => 5
# atomic.value # => 6
# ```
def xor(value : T)
Ops.atomicrmw(:xor, pointerof(@value), value, :sequentially_consistent, false)
end

# Performs `atomic_value = max(atomic_value, value)`. Returns the old value.
#
# ```
# atomic = Atomic.new(5)
#
# atomic.max(3) # => 5
# atomic.value # => 5
#
# atomic.max(10) # => 5
# atomic.value # => 10
# ```
def max(value : T)
{% if T < Int::Signed %}
Ops.atomicrmw(:max, pointerof(@value), value, :sequentially_consistent, false)
{% else %}
Ops.atomicrmw(:umax, pointerof(@value), value, :sequentially_consistent, false)
{% end %}
end

# Performs `atomic_value = min(atomic_value, value)`. Returns the old value.
#
# ```
# atomic = Atomic.new(5)
#
# atomic.min(10) # => 5
# atomic.value # => 5
#
# atomic.min(3) # => 5
# atomic.value # => 3
# ```
def min(value : T)
{% if T < Int::Signed %}
Ops.atomicrmw(:min, pointerof(@value), value, :sequentially_consistent, false)
{% else %}
Ops.atomicrmw(:umin, pointerof(@value), value, :sequentially_consistent, false)
{% end %}
end

# Atomically sets this atomic's value to *value*. Returns the **old** value.
#
# ```
# atomic = Atomic.new(5)
# atomic.set(10) # => 5
# atomic.value # => 10
# ```
def swap(value : T)
Ops.atomicrmw(:xchg, pointerof(@value), value, :sequentially_consistent, false)
end

# Atomically sets this atomic's value to *value*. Returns the **new** value.
#
# ```
# atomic = Atomic.new(5)
# atomic.set(10) # => 10
# atomic.value # => 10
# ```
def set(value : T)
Ops.store(pointerof(@value), value, :sequentially_consistent, true)
value
end

# **Non-atomically** sets this atomic's value to *value*. Returns the **new** value.
#
# ```
# atomic = Atomic.new(5)
# atomic.lazy_set(10) # => 10
# atomic.value # => 10
# ```
def lazy_set(@value : T)
end

# Atomically returns this atomic's value.
def get
Ops.load(pointerof(@value), :sequentially_consistent, true)
end

# **Non-atomically* returns this atomic's value.
def lazy_get
@value
end

# :nodoc:
module Ops
# Defines methods that directly map to LLVM instructions related to atomic operations.

@[Primitive(:cmpxchg)]
def self.cmpxchg(ptr : T*, cmp : T, new : T, success_ordering : Symbol, failure_ordering : Symbol) : {T, Bool} forall T
end

@[Primitive(:atomicrmw)]
def self.atomicrmw(op : Symbol, ptr : T*, val : T, ordering : Symbol, singlethread : Bool) : T forall T
end

@[Primitive(:fence)]
def self.fence(ordering : Symbol, singlethread : Bool) : Nil
end

@[Primitive(:load_atomic)]
def self.load(ptr : T*, ordering : Symbol, volatile : Bool) : T forall T
end

@[Primitive(:store_atomic)]
def self.store(ptr : T*, value : T, ordering : Symbol, volatile : Bool) : Nil forall T
end
end
end
2 changes: 1 addition & 1 deletion src/compiler/crystal/codegen/call.cr
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ class Crystal::CodeGenVisitor
# Change context type: faster then creating a new context
old_type = context.type
context.type = self_type
codegen_primitive(body, target_def, call_args)
codegen_primitive(node, body, target_def, call_args)
context.type = old_type
return true
end
Expand Down
Loading

1 comment on commit 919818e

@ozra
Copy link
Contributor

@ozra ozra commented on 919818e Oct 6, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yippie-kie-yeay!

Please sign in to comment.