-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Expose LLVM atomic operations to Crystal, and add Atomic(T) struct.
We will need this for efficiently implementing multiple-thread support.
- Loading branch information
Ary Borenszweig
committed
Oct 5, 2016
1 parent
a8eef9b
commit 919818e
Showing
12 changed files
with
550 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
919818e
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yippie-kie-yeay!