-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathbackground.nim
102 lines (85 loc) · 4.12 KB
/
background.nim
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
when not (defined(gcArc) or defined(gcOrc)): {.error: "requires arc/orc".}
import std/genasts
import std/macros
import pkg/cps
export cps
type
Backgrounded* = ref object of Continuation ## background() produces these
proc noop*(c: Backgrounded): Backgrounded {.cpsMagic.} =
## leaked impl detail; ignore it 😙
c
proc worker(c: Backgrounded) {.thread, nimcall.} =
{.gcsafe.}:
discard trampoline c
macro background*(call: typed): untyped =
## Run the first argument, a call, in another thread. Returns
## a continuation that resolves with the result of the call.
runnableExamples:
proc fib(n: int; o: bool = false): int =
case n
of 0, 1:
1
else:
fib(n-1) + fib(n-2)
var a = background fib(45)
var b = background fib(44)
assert 1836311903 == recover a
assert 1134903170 == recover b
if call.kind notin CallNodes:
error "provide a call() to background"
# get the call's parameter definitions as a seq
var parameters = call[0].getImpl.params[0..^1]
# compose new parameter symbols for a future proc
var newParams: seq[NimNode]
for index, arg in parameters.pairs:
if index == 0:
newParams.add arg # add the return value
else:
let param = nskParam.genSym arg[0].strVal # proc argument symbol
newParams.add newIdentDefs(param, arg[1]) # no default value needed
# we're going to create a "runner" continuation that captures
# the arguments into a continuation object
let rName = nskProc.genSym"runner"
let runner = newProc(rName, newParams) # use those new params
runner.addPragma:
nnkExprColonExpr.newTree(bindSym"cps", ident"Backgrounded")
# the runner's sole job is to call the original function with the
# arguments which will have been moved into the continuation (see below)
let ogCall = newCall call[0]
runner.body = newStmtList ogCall
# we need to compose a similar call for the runner itself; this is what
# we'll use to whelp it at the original call site
let mCall = newCall rName
# the continuation that issues that call is the monitor
let mName = nskProc.genSym"monitor"
var mParams = @[copyNimTree newParams[0]] # the monitor's return value
# copy the param symbols into the calls, omitting the return value
for identdefs in newParams[1..^1]: # these are parameter identdefs
ogCall.add identdefs[0] # the call is passed each parameter
mParams.add copyNimTree identdefs # a copy for the monitor's params
mCall.add mParams[mParams.high][0] # monitor's call uses the same sym
let monitor = newProc(mName, mParams) # creating the monitor
monitor.addPragma:
nnkExprColonExpr.newTree(bindSym"cps", ident"Backgrounded")
let c = nskVar.genSym"continuation" # the continuation we'll send off
monitor.body =
genAstOpt({}, worker = bindSym"worker", noop = bindSym"noop",
c, mCall, arg = ident"Backgrounded",
# Use force-open binding to make sure that the recover() of
# the generated continuation is bound
recover = bindSym("recover", brForceOpen)):
var c = whelp mCall # call the runner continuation
var th: Thread[arg] # prepare a Thread[Backgrounded]
createThread(th, worker, c) # pass the continuation to the thread
noop() # return control to the user
joinThread th # wait for the thread to complete
recover c # recover any result
var monCall = newCall(mName) # this call makes the monitor
for arg in call[1..^1]: # iterate over the original args
monCall.add arg # add them into the call
result = newStmtList(runner, monitor) # we've defined runner and monitor
result.add: # to this we add the instantiation
genAstOpt({}, monCall):
var monitor = whelp monCall # create the monitor continuation
discard monitor.fn(monitor) # the first leg creates the thread
monitor # let the user finish the monitor