-
-
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.
- v0.24.1
- 1.15.1
- 1.15.0
- 1.14.1
- 1.14.0
- 1.13.3
- 1.13.2
- 1.13.1
- 1.13.0
- 1.12.2
- 1.12.1
- 1.12.0
- 1.11.2
- 1.11.1
- 1.11.0
- 1.10.1
- 1.10.0
- 1.9.2
- 1.9.1
- 1.9.0
- 1.8.2
- 1.8.1
- 1.8.0
- 1.7.3
- 1.7.2
- 1.7.1
- 1.7.0
- 1.6.2
- 1.6.1
- 1.6.0
- 1.5.1
- 1.5.0
- 1.4.1
- 1.4.0
- 1.3.2
- 1.3.1
- 1.3.0
- 1.2.2
- 1.2.1
- 1.2.0
- 1.1.1
- 1.1.0
- 1.0.0
- 0.36.1
- 0.36.0
- 0.35.1
- 0.35.0
- 0.34.0
- 0.33.0
- 0.32.1
- 0.32.0
- 0.31.1
- 0.31.0
- 0.30.1
- 0.30.0
- 0.29.0
- 0.28.0
- 0.27.2
- 0.27.1
- 0.27.0
- 0.26.1
- 0.26.0
- 0.25.1
- 0.25.0
- 0.24.2
- 0.24.1
- 0.24.0
- 0.23.1
- 0.23.0
- 0.22.0
- 0.21.1
- 0.21.0
- 0.20.5
- 0.20.4
- 0.20.3
- 0.20.2
- 0.20.1
- 0.20.0
- 0.19.4
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
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
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
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
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
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
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
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
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
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 |
---|---|---|
|
@@ -20,6 +20,7 @@ require "string" | |
|
||
# Alpha-sorted list | ||
require "array" | ||
require "atomic" | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
asterite
Member
|
||
require "bool" | ||
require "box" | ||
require "char" | ||
|
1 comment
on commit 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!
The spec for
Atomic
has been commented out, but adding atomic to the prelude will make it available as part of 0.19.3, is this intended?