summary refs log tree commit diff stats
path: root/lib/system/atomics.nim
blob: b1a96b20975fce9f92f95880b73ed7f0a872ca05 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
#
#
#            Nimrod's Runtime Library
#        (c) Copyright 2012 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

## Atomic operations for Nimrod.

when (defined(gcc) or defined(llvm_gcc)) and hasThreadSupport:
  type 
    AtomMemModel* = enum
      ATOMIC_RELAXED,  ## No barriers or synchronization. 
      ATOMIC_CONSUME,  ## Data dependency only for both barrier and
                       ## synchronization with another thread.
      ATOMIC_ACQUIRE,  ## Barrier to hoisting of code and synchronizes with
                       ## release (or stronger) 
                       ## semantic stores from another thread.
      ATOMIC_RELEASE,  ## Barrier to sinking of code and synchronizes with
                       ## acquire (or stronger) 
                       ## semantic loads from another thread. 
      ATOMIC_ACQ_REL,  ## Full barrier in both directions and synchronizes
                       ## with acquire loads 
                       ## and release stores in another thread.
      ATOMIC_SEQ_CST   ## Full barrier in both directions and synchronizes
                       ## with acquire loads 
                       ## and release stores in all threads.

    TAtomType* = TNumber|pointer|ptr|char
      ## Type Class representing valid types for use with atomic procs

  proc atomicLoadN*[T: TAtomType](p: ptr T, mem: AtomMemModel): T {.
    importc: "__atomic_load_n", nodecl.}
    ## This proc implements an atomic load operation. It returns the contents at p.
    ## ATOMIC_RELAXED, ATOMIC_SEQ_CST, ATOMIC_ACQUIRE, ATOMIC_CONSUME.

  proc atomicLoad*[T: TAtomType](p, ret: ptr T, mem: AtomMemModel) {.
    importc: "__atomic_load", nodecl.}  
    ## This is the generic version of an atomic load. It returns the contents at p in ret.

  proc atomicStoreN*[T: TAtomType](p: ptr T, val: T, mem: AtomMemModel) {.
    importc: "__atomic_store_n", nodecl.} 
    ## This proc implements an atomic store operation. It writes val at p.
    ## ATOMIC_RELAXED, ATOMIC_SEQ_CST, and ATOMIC_RELEASE.

  proc atomicStore*[T: TAtomType](p, val: ptr T, mem: AtomMemModel) {.
    importc: "__atomic_store", nodecl.}
    ## This is the generic version of an atomic store. It stores the value of val at p

  proc atomicExchangeN*[T: TAtomType](p: ptr T, val: T, mem: AtomMemModel): T {.
    importc: "__atomic_exchange_n", nodecl.}
    ## This proc implements an atomic exchange operation. It writes val at p, 
    ## and returns the previous contents at p.
    ## ATOMIC_RELAXED, ATOMIC_SEQ_CST, ATOMIC_ACQUIRE, ATOMIC_RELEASE, ATOMIC_ACQ_REL

  proc atomicExchange*[T: TAtomType](p, val, ret: ptr T, mem: AtomMemModel) {.
    importc: "__atomic_exchange", nodecl.}
    ## This is the generic version of an atomic exchange. It stores the contents at val at p. 
    ## The original value at p is copied into ret.

  proc atomicCompareExchangeN*[T: TAtomType](p, expected: ptr T, desired: T,
    weak: bool, success_memmodel: AtomMemModel, failure_memmodel: AtomMemModel): bool {.
    importc: "__atomic_compare_exchange_n ", nodecl.} 
    ## This proc implements an atomic compare and exchange operation. This compares the
    ## contents at p with the contents at expected and if equal, writes desired at p. 
    ## If they are not equal, the current contents at p is written into expected. 
    ## Weak is true for weak compare_exchange, and false for the strong variation. 
    ## Many targets only offer the strong variation and ignore the parameter. 
    ## When in doubt, use the strong variation.
    ## True is returned if desired is written at p and the execution is considered 
    ## to conform to the memory model specified by success_memmodel. There are no 
    ## restrictions on what memory model can be used here. False is returned otherwise, 
    ## and the execution is considered to conform to failure_memmodel. This memory model 
    ## cannot be __ATOMIC_RELEASE nor __ATOMIC_ACQ_REL. It also cannot be a stronger model 
    ## than that specified by success_memmodel.

  proc atomicCompareExchange*[T: TAtomType](p, expected, desired: ptr T,
    weak: bool, success_memmodel: AtomMemModel, failure_memmodel: AtomMemModel): bool {.
    importc: "__atomic_compare_exchange_n ", nodecl.}  
    ## This proc implements the generic version of atomic_compare_exchange. 
    ## The proc is virtually identical to atomic_compare_exchange_n, except the desired 
    ## value is also a pointer. 

  ## Perform the operation return the new value, all memory models are valid 
  proc atomicAddFetch*[T: TAtomType](p: ptr T, val: T, mem: AtomMemModel): T {.
    importc: "__atomic_add_fetch", nodecl.}
  proc atomicSubFetch*[T: TAtomType](p: ptr T, val: T, mem: AtomMemModel): T {.
    importc: "__atomic_sub_fetch", nodecl.}
  proc atomicOrFetch*[T: TAtomType](p: ptr T, val: T, mem: AtomMemModel): T {.
    importc: "__atomic_or_fetch ", nodecl.}
  proc atomicAndFetch*[T: TAtomType](p: ptr T, val: T, mem: AtomMemModel): T {.
    importc: "__atomic_and_fetch", nodecl.}
  proc atomicXorFetch*[T: TAtomType](p: ptr T, val: T, mem: AtomMemModel): T {.   
    importc: "__atomic_xor_fetch", nodecl.}
  proc atomicNandFetch*[T: TAtomType](p: ptr T, val: T, mem: AtomMemModel): T {.   
    importc: "__atomic_nand_fetch ", nodecl.} 

  ## Perform the operation return the old value, all memory models are valid 
  proc atomicFetchAdd*[T: TAtomType](p: ptr T, val: T, mem: AtomMemModel): T {.
    importc: "__atomic_fetch_add", nodecl.}
  proc atomicFetchSub*[T: TAtomType](p: ptr T, val: T, mem: AtomMemModel): T {.  
    importc: "__atomic_fetch_sub", nodecl.}
  proc atomicFetchOr*[T: TAtomType](p: ptr T, val: T, mem: AtomMemModel): T {.  
    importc: "__atomic_fetch_or", nodecl.}
  proc atomicFetchAnd*[T: TAtomType](p: ptr T, val: T, mem: AtomMemModel): T {.   
    importc: "__atomic_fetch_and", nodecl.}
  proc atomicFetchXor*[T: TAtomType](p: ptr T, val: T, mem: AtomMemModel): T {.  
    importc: "__atomic_fetch_xor", nodecl.}
  proc atomicFetchNand*[T: TAtomType](p: ptr T, val: T, mem: AtomMemModel): T {.  
    importc: "__atomic_fetch_nand", nodecl.} 

  proc atomicTestAndSet*(p: pointer, mem: AtomMemModel): bool {.  
    importc: "__atomic_test_and_set", nodecl.} 
    ## This built-in function performs an atomic test-and-set operation on the byte at p. 
    ## The byte is set to some implementation defined nonzero “set” value and the return
    ## value is true if and only if the previous contents were “set”.
    ## All memory models are valid.

  proc atomicClear*(p: pointer, mem: AtomMemModel) {.  
    importc: "__atomic_clear", nodecl.}
    ## This built-in function performs an atomic clear operation at p. 
    ## After the operation, at p contains 0.
    ## ATOMIC_RELAXED, ATOMIC_SEQ_CST, ATOMIC_RELEASE

  proc atomicThreadFence*(mem: AtomMemModel) {.  
    importc: "__atomic_thread_fence", nodecl.}
    ## This built-in function acts as a synchronization fence between threads based 
    ## on the specified memory model. All memory orders are valid.

  proc atomicSignalFence*(mem: AtomMemModel) {.  
    importc: "__atomic_signal_fence", nodecl.}
    ## This built-in function acts as a synchronization fence between a thread and 
    ## signal handlers based in the same thread. All memory orders are valid.

  proc atomicAlwaysLockFree*(size: int, p: pointer): bool {.  
    importc: "__atomic_always_lock_free", nodecl.}
    ## This built-in function returns true if objects of size bytes always generate 
    ## lock free atomic instructions for the target architecture. size must resolve 
    ## to a compile-time constant and the result also resolves to a compile-time constant.
    ## ptr is an optional pointer to the object that may be used to determine alignment. 
    ## A value of 0 indicates typical alignment should be used. The compiler may also 
    ## ignore this parameter.

  proc atomicIsLockFree*(size: int, p: pointer): bool {.  
    importc: "__atomic_is_lock_free", nodecl.}
    ## This built-in function returns true if objects of size bytes always generate 
    ## lock free atomic instructions for the target architecture. If it is not known 
    ## to be lock free a call is made to a runtime routine named __atomic_is_lock_free.
    ## ptr is an optional pointer to the object that may be used to determine alignment. 
    ## A value of 0 indicates typical alignment should be used. The compiler may also 
    ## ignore this parameter.

elif defined(vcc) and hasThreadSupport:
  proc addAndFetch*(p: ptr int, val: int): int {.
    importc: "NimXadd", nodecl.}
else:
  proc addAndFetch*(p: ptr int, val: int): int {.inline.} =
    inc(p[], val)
    result = p[]

# atomic compare and swap (CAS) funcitons to implement lock-free algorithms  
      
#if defined(windows) and not defined(gcc) and hasThreadSupport:
#    proc InterlockedCompareExchangePointer(mem: ptr pointer,
#      newValue: pointer, comparand: pointer) : pointer {.nodecl, 
#        importc: "InterlockedCompareExchangePointer", header:"windows.h".}

#    proc compareAndSwap*[T](mem: ptr T, 
#      expected: T, newValue: T): bool {.inline.}=
#      ## Returns true if successfully set value at mem to newValue when value
#      ## at mem == expected
#      return InterlockedCompareExchangePointer(addr(mem), 
#        addr(newValue), addr(expected))[] == expected
    
#elif not hasThreadSupport:
#  proc compareAndSwap*[T](mem: ptr T, 
#                          expected: T, newValue: T): bool {.inline.} =
#      ## Returns true if successfully set value at mem to newValue when value
#      ## at mem == expected
#      var oldval = mem[]
#      if oldval == expected:
#        mem[] = newValue
#        return true
#      return false


# Some convenient functions 
proc atomicInc*(memLoc: var int, x: int = 1): int =
  when defined(gcc) and hasThreadSupport:
    result = atomic_add_fetch(memLoc.addr, x, ATOMIC_RELAXED)
  else:
    inc(memLoc, x)
    result = memLoc
  
proc atomicDec*(memLoc: var int, x: int = 1): int =
  when defined(gcc) and hasThreadSupport:
    when defined(atomic_sub_fetch):
      result = atomic_sub_fetch(memLoc.addr, x, ATOMIC_RELAXED)
    else:
      result = atomic_add_fetch(memLoc.addr, -x, ATOMIC_RELAXED)
  else:
    dec(memLoc, x)
    result = memLoc