added 'fence' instructions to the barrier
This commit is contained in:
parent
2de99653d0
commit
b7cbb08f99
3 changed files with 24 additions and 39 deletions
|
|
@ -53,12 +53,15 @@ type
|
||||||
interest: bool ## wether the master is interested in the "all done" event
|
interest: bool ## wether the master is interested in the "all done" event
|
||||||
|
|
||||||
proc barrierEnter(b: ptr Barrier) {.compilerProc, inline.} =
|
proc barrierEnter(b: ptr Barrier) {.compilerProc, inline.} =
|
||||||
## due to the signaling between threads, it is ensured we are the only
|
# due to the signaling between threads, it is ensured we are the only
|
||||||
## one with access to 'entered' so we don't need 'atomicInc' here:
|
# one with access to 'entered' so we don't need 'atomicInc' here:
|
||||||
inc b.entered
|
inc b.entered
|
||||||
|
# also we need no 'fence' instructions here as soon 'nimArgsPassingDone'
|
||||||
|
# will be called which already will perform a fence for us.
|
||||||
|
|
||||||
proc barrierLeave(b: ptr Barrier) {.compilerProc, inline.} =
|
proc barrierLeave(b: ptr Barrier) {.compilerProc, inline.} =
|
||||||
atomicInc b.left
|
atomicInc b.left
|
||||||
|
when not defined(x86): fence()
|
||||||
if b.interest and b.left == b.entered: signal(b.cv)
|
if b.interest and b.left == b.entered: signal(b.cv)
|
||||||
|
|
||||||
proc openBarrier(b: ptr Barrier) {.compilerProc, inline.} =
|
proc openBarrier(b: ptr Barrier) {.compilerProc, inline.} =
|
||||||
|
|
@ -67,10 +70,12 @@ proc openBarrier(b: ptr Barrier) {.compilerProc, inline.} =
|
||||||
b.interest = false
|
b.interest = false
|
||||||
|
|
||||||
proc closeBarrier(b: ptr Barrier) {.compilerProc.} =
|
proc closeBarrier(b: ptr Barrier) {.compilerProc.} =
|
||||||
|
fence()
|
||||||
if b.left != b.entered:
|
if b.left != b.entered:
|
||||||
b.cv = createCondVar()
|
b.cv = createCondVar()
|
||||||
b.interest = true # XXX we really need to ensure no re-orderings are done
|
fence()
|
||||||
# by the C compiler here
|
b.interest = true
|
||||||
|
fence()
|
||||||
while b.left != b.entered: await(b.cv)
|
while b.left != b.entered: await(b.cv)
|
||||||
destroyCondVar(b.cv)
|
destroyCondVar(b.cv)
|
||||||
|
|
||||||
|
|
@ -207,9 +212,9 @@ proc `^`*[T](prom: Promise[T]): T =
|
||||||
result = prom.blob
|
result = prom.blob
|
||||||
|
|
||||||
proc awaitAny*(promises: openArray[RawPromise]): int =
|
proc awaitAny*(promises: openArray[RawPromise]): int =
|
||||||
# awaits any of the given promises. Returns the index of one promise for which
|
## awaits any of the given promises. Returns the index of one promise for
|
||||||
## a value arrived. A promise only supports one call to 'awaitAny' at the
|
## which a value arrived. A promise only supports one call to 'awaitAny' at
|
||||||
## same time. That means if you await([a,b]) and await([b,c]) the second
|
## the same time. That means if you await([a,b]) and await([b,c]) the second
|
||||||
## call will only await 'c'. If there is no promise left to be able to wait
|
## call will only await 'c'. If there is no promise left to be able to wait
|
||||||
## on, -1 is returned.
|
## on, -1 is returned.
|
||||||
## **Note**: This results in non-deterministic behaviour and so should be
|
## **Note**: This results in non-deterministic behaviour and so should be
|
||||||
|
|
@ -294,14 +299,16 @@ proc preferSpawn*(): bool =
|
||||||
proc spawn*(call: expr): expr {.magic: "Spawn".}
|
proc spawn*(call: expr): expr {.magic: "Spawn".}
|
||||||
## always spawns a new task, so that the 'call' is never executed on
|
## always spawns a new task, so that the 'call' is never executed on
|
||||||
## the calling thread. 'call' has to be proc call 'p(...)' where 'p'
|
## the calling thread. 'call' has to be proc call 'p(...)' where 'p'
|
||||||
## is gcsafe and has 'void' as the return type.
|
## is gcsafe and has a return type that is either 'void' or compatible
|
||||||
|
## with ``Promise[T]``.
|
||||||
|
|
||||||
template spawnX*(call: expr): expr =
|
template spawnX*(call: expr): expr =
|
||||||
## spawns a new task if a CPU core is ready, otherwise executes the
|
## spawns a new task if a CPU core is ready, otherwise executes the
|
||||||
## call in the calling thread. Usually it is advised to
|
## call in the calling thread. Usually it is advised to
|
||||||
## use 'spawn' in order to not block the producer for an unknown
|
## use 'spawn' in order to not block the producer for an unknown
|
||||||
## amount of time. 'call' has to be proc call 'p(...)' where 'p'
|
## amount of time. 'call' has to be proc call 'p(...)' where 'p'
|
||||||
## is gcsafe and has 'void' as the return type.
|
## is gcsafe and has a return type that is either 'void' or compatible
|
||||||
|
## with ``Promise[T]``.
|
||||||
(if preferSpawn(): spawn call else: call)
|
(if preferSpawn(): spawn call else: call)
|
||||||
|
|
||||||
proc parallel*(body: stmt) {.magic: "Parallel".}
|
proc parallel*(body: stmt) {.magic: "Parallel".}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,9 @@
|
||||||
## Atomic operations for Nimrod.
|
## Atomic operations for Nimrod.
|
||||||
{.push stackTrace:off.}
|
{.push stackTrace:off.}
|
||||||
|
|
||||||
when (defined(gcc) or defined(llvm_gcc)) and hasThreadSupport:
|
const someGcc = defined(gcc) or defined(llvm_gcc) or defined(clang)
|
||||||
|
|
||||||
|
when someGcc and hasThreadSupport:
|
||||||
type
|
type
|
||||||
AtomMemModel* = enum
|
AtomMemModel* = enum
|
||||||
ATOMIC_RELAXED, ## No barriers or synchronization.
|
ATOMIC_RELAXED, ## No barriers or synchronization.
|
||||||
|
|
@ -163,33 +165,6 @@ else:
|
||||||
inc(p[], val)
|
inc(p[], val)
|
||||||
result = p[]
|
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 =
|
proc atomicInc*(memLoc: var int, x: int = 1): int =
|
||||||
when defined(gcc) and hasThreadSupport:
|
when defined(gcc) and hasThreadSupport:
|
||||||
result = atomic_add_fetch(memLoc.addr, x, ATOMIC_RELAXED)
|
result = atomic_add_fetch(memLoc.addr, x, ATOMIC_RELAXED)
|
||||||
|
|
@ -207,7 +182,7 @@ proc atomicDec*(memLoc: var int, x: int = 1): int =
|
||||||
dec(memLoc, x)
|
dec(memLoc, x)
|
||||||
result = memLoc
|
result = memLoc
|
||||||
|
|
||||||
when defined(windows) and not defined(gcc):
|
when defined(windows) and not someGcc:
|
||||||
proc interlockedCompareExchange(p: pointer; exchange, comparand: int32): int32
|
proc interlockedCompareExchange(p: pointer; exchange, comparand: int32): int32
|
||||||
{.importc: "InterlockedCompareExchange", header: "<windows.h>", cdecl.}
|
{.importc: "InterlockedCompareExchange", header: "<windows.h>", cdecl.}
|
||||||
|
|
||||||
|
|
@ -221,7 +196,7 @@ else:
|
||||||
# XXX is this valid for 'int'?
|
# XXX is this valid for 'int'?
|
||||||
|
|
||||||
|
|
||||||
when (defined(x86) or defined(amd64)) and defined(gcc):
|
when (defined(x86) or defined(amd64)) and (defined(gcc) or defined(llvm_gcc)):
|
||||||
proc cpuRelax {.inline.} =
|
proc cpuRelax {.inline.} =
|
||||||
{.emit: """asm volatile("pause" ::: "memory");""".}
|
{.emit: """asm volatile("pause" ::: "memory");""".}
|
||||||
elif (defined(x86) or defined(amd64)) and defined(vcc):
|
elif (defined(x86) or defined(amd64)) and defined(vcc):
|
||||||
|
|
|
||||||
3
todo.txt
3
todo.txt
|
|
@ -8,8 +8,11 @@ Concurrency
|
||||||
- implement 'deepCopy' builtin
|
- implement 'deepCopy' builtin
|
||||||
- implement 'foo[1..4] = spawn(f[4..7])'
|
- implement 'foo[1..4] = spawn(f[4..7])'
|
||||||
- the disjoint checker needs to deal with 'a = spawn f(); g = spawn f()'
|
- the disjoint checker needs to deal with 'a = spawn f(); g = spawn f()'
|
||||||
|
- support for exception propagation
|
||||||
- Minor: The copying of the 'ref Promise' into the thead local storage only
|
- Minor: The copying of the 'ref Promise' into the thead local storage only
|
||||||
happens to work due to the write barrier's implementation
|
happens to work due to the write barrier's implementation
|
||||||
|
- 'gcsafe' inferrence needs to be fixed
|
||||||
|
- implement lock levels --> first without the more complex race avoidance
|
||||||
|
|
||||||
|
|
||||||
Misc
|
Misc
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue