Nimrod logo

Effect system

NoSideEffect

1
2
3
4
5
6
7
8
9
10
cov:
  proc toTest(x, y: int): int {.noSideEffect.} =
    case x
    of 8:
      if y > 9: 8+1
      else: 8+2
    of 9: 9
    else: 100

# Error: 'toTest' can have side-effects

NoSideEffect

1
2
3
4
5
6
7
8
9
var
  track = [("line 9", false), ("line 13", false), ...]

proc toTest(x, y: int): int {.noSideEffect.} =
  case x
  of 8:
    if y > 9:
      track[0][1] = true
  ...

NoSideEffect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var
  track = [("line 9", false), ("line 13", false), ...]

proc setter(x: int) =
  track[x][1] = true

type HideEffects = proc (x: int) {.noSideEffect, raises: [], tags: [].}

proc toTest(x, y: int): int =
  case x
  of 8:
    if y > 9:
      cast[HideEffects](setter)(0)
  ...

Effect System

Exceptions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import strutils

proc readFromFile() {.raises: [].} =
  # read the first two lines of a text file that should contain numbers
  # and tries to add them
  var
    f: File
  if open(f, "numbers.txt"):
    try:
      var a = readLine(f)
      var b = readLine(f)
      echo("sum: " & $(parseInt(a) + parseInt(b)))
    except OverflowError:
      echo("overflow!")
    except ValueError:
      echo("could not convert string to integer")
    except IOError:
      echo("IO error!")
    except:
      echo("Unknown exception!")
    finally:
      close(f)

Tags

1
2
3
4
5
6
7
8
9
10
type
  TagA = object of RootEffect
  TagB = object of RootEffect

proc a() {.tags: [TagA].} = discard
proc b() {.tags: [TagB].} = discard

proc x(input: int) {.tags: [ ? ].} =
  if input < 0: a()
  else: b()

Tags

1
2
3
4
5
6
7
8
9
10
type
  TagA = object of RootEffect
  TagB = object of RootEffect

proc a() {.tags: [TagA].} = discard
proc b() {.tags: [TagB].} = discard

proc x(input: int) {.tags: [TagA, TagB].} =
  if input < 0: a()
  else: b()

Tags

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
proc execProcesses(commands: openArray[string],
                   beforeRunEvent: proc (command: string) = nil): int
  {.tags: [ExecIOEffect].}
  ## executes the commands in parallel. The highest return value of
  ## all processes is returned. Runs `beforeRunEvent` before running each
  ## command.

proc echoCommand(command: string) {.tags: [WriteIOEffect].} =
  echo command

proc compose*() =
  execProcesses(["gcc -o foo foo.c",
                 "gcc -o bar bar.c",
                 "gcc -o baz baz.c"],
                 echoCommand)

GC safety

GC safety

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import tables, strutils, threadpool

const
  files = ["data1.txt", "data2.txt", "data3.txt", "data4.txt"]

var tab = newCountTable[string]()

proc countWords(filename: string) =
  ## Counts all the words in the file.
  for word in readFile(filename).split:
    tab.inc word

for f in files:
  spawn countWords(f)
sync()
tab.sort()
echo tab.largest

GC safety

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
import threadpool, tables, strutils

{.pragma isolated, threadvar.}

var tab {.isolated.}: CountTable[string]

proc rawPut(key: string) =
  inc(tab, key)

proc put(key: string) =
  pinnedSpawn 0, rawPut(key)

proc rawGet(): string =
  tab.sort()
  result = tab.largest()[0]

proc getMax(): string =
  let flow = pinnedSpawn(0, rawGet())
  result = ^flow

proc main =
  pinnedSpawn 0, (proc () = tab = initCountTable[string]())
  for x in split(readFile("readme.txt")):
    put x
  echo getMax()

main()

Guards and locks

Data race

A data race occurs when:

Guards fight data races

1
2
3
4
5
6
7
8
9
10
11
var glock: Lock
var gdata {.guard: glock.}: int

proc invalid =
  # invalid: unguarded access:
  echo gdata

proc valid =
  # valid access:
  {.locks: [glock].}:
    echo gdata

Guards fight data races

1
2
3
4
5
6
7
template lock(a: Lock; body: untyped) =
  pthread_mutex_lock(a)
  {.locks: [a].}:
    try:
      body
    finally:
      pthread_mutex_unlock(a)

Guards fight data races

1
2
3
4
5
6
7
8
9
var dummyLock {.compileTime.}: int
var atomicCounter {.guard: dummyLock.}: int

template atomicRead(x): expr =
  {.locks: [dummyLock].}:
    memoryReadBarrier()
    x

echo atomicRead(atomicCounter)

Deadlocks

A deadlock occurs when:

Solution?

Deadlocks

A deadlock occurs when:

Solution?

Locking levels fight deadlocks

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var a, b: Lock[2]
var x: Lock[1]
# invalid locking order: Lock[1] cannot be acquired before Lock[2]:
{.locks: [x].}:
  {.locks: [a].}:
    ...
# valid locking order: Lock[2] acquired before Lock[1]:
{.locks: [a].}:
  {.locks: [x].}:
    ...

# invalid locking order: Lock[2] acquired before Lock[2]:
{.locks: [a].}:
  {.locks: [b].}:
    ...

# valid locking order, locks of the same level acquired at the same time:
{.locks: [a, b].}:
  ...

Locking levels fight deadlocks

1
2
3
4
5
6
7
8
9
10
11
12
13
template multilock(a, b: ptr Lock; body: stmt) =
  if cast[ByteAddress](a) < cast[ByteAddress](b):
    pthread_mutex_lock(a)
    pthread_mutex_lock(b)
  else:
    pthread_mutex_lock(b)
    pthread_mutex_lock(a)
  {.locks: [a, b].}:
    try:
      body
    finally:
      pthread_mutex_unlock(a)
      pthread_mutex_unlock(b)

Locking levels fight deadlocks

1
2
3
4
5
6
proc p() {.locks: 3.} = discard

var a: Lock[4]
{.locks: [a].}:
  # p's locklevel (3) is strictly less than a's (4) so the call is allowed:
  p()