tasks.nim 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2021 Nim contributors
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## This module provides basic primitives for creating parallel programs.
  10. ## A `Task` should be only owned by a single Thread, it cannot be shared by threads.
  11. import std/[macros, isolation, typetraits]
  12. when defined(nimPreviewSlimSystem):
  13. import std/assertions
  14. export isolation
  15. when compileOption("threads"):
  16. from std/effecttraits import isGcSafe
  17. #
  18. # proc hello(a: int, b: string) =
  19. # echo $a & b
  20. #
  21. # let literal = "Nim"
  22. # let t = toTask(hello(521, literal))
  23. #
  24. #
  25. # is roughly converted to
  26. #
  27. # type
  28. # ScratchObj_369098780 = object
  29. # a: int
  30. # b: string
  31. #
  32. # let scratch_369098762 = cast[ptr ScratchObj_369098780](c_calloc(csize_t 1,
  33. # csize_t sizeof(ScratchObj_369098780)))
  34. # if scratch_369098762.isNil:
  35. # raise newException(OutOfMemDefect, "Could not allocate memory")
  36. # block:
  37. # var isolate_369098776 = isolate(521)
  38. # scratch_369098762.a = extract(isolate_369098776)
  39. # var isolate_369098778 = isolate(literal)
  40. # scratch_369098762.b = extract(isolate_369098778)
  41. # proc hello_369098781(args`gensym3: pointer) {.nimcall.} =
  42. # let objTemp_369098775 = cast[ptr ScratchObj_369098780](args`gensym3)
  43. # let :tmp_369098777 = objTemp_369098775.a
  44. # let :tmp_369098779 = objTemp_369098775.b
  45. # hello(a = :tmp_369098777, b = :tmp_369098779)
  46. #
  47. # proc destroyScratch_369098782(args`gensym3: pointer) {.nimcall.} =
  48. # let obj_369098783 = cast[ptr ScratchObj_369098780](args`gensym3)
  49. # =destroy(obj_369098783[])
  50. # let t = Task(callback: hello_369098781, args: scratch_369098762, destroy: destroyScratch_369098782)
  51. #
  52. type
  53. Task* = object ## `Task` contains the callback and its arguments.
  54. callback: proc (args, res: pointer) {.nimcall, gcsafe.}
  55. args: pointer
  56. destroy: proc (args: pointer) {.nimcall, gcsafe.}
  57. proc `=copy`*(x: var Task, y: Task) {.error.}
  58. const arcLike = defined(gcArc) or defined(gcAtomicArc) or defined(gcOrc)
  59. when defined(nimAllowNonVarDestructor) and arcLike:
  60. proc `=destroy`*(t: Task) {.inline, gcsafe.} =
  61. ## Frees the resources allocated for a `Task`.
  62. if t.args != nil:
  63. if t.destroy != nil:
  64. t.destroy(t.args)
  65. deallocShared(t.args)
  66. else:
  67. proc `=destroy`*(t: var Task) {.inline, gcsafe.} =
  68. ## Frees the resources allocated for a `Task`.
  69. if t.args != nil:
  70. if t.destroy != nil:
  71. t.destroy(t.args)
  72. deallocShared(t.args)
  73. proc invoke*(task: Task; res: pointer = nil) {.inline, gcsafe.} =
  74. ## Invokes the `task`.
  75. assert task.callback != nil
  76. task.callback(task.args, res)
  77. template checkIsolate(scratchAssignList: seq[NimNode], procParam, scratchDotExpr: NimNode) =
  78. # block:
  79. # var isoTempA = isolate(521)
  80. # scratch.a = extract(isolateA)
  81. # var isoTempB = isolate(literal)
  82. # scratch.b = extract(isolateB)
  83. let isolatedTemp = genSym(nskTemp, "isoTemp")
  84. scratchAssignList.add newVarStmt(isolatedTemp, newCall(newIdentNode("isolate"), procParam))
  85. scratchAssignList.add newAssignment(scratchDotExpr,
  86. newCall(newIdentNode("extract"), isolatedTemp))
  87. template addAllNode(assignParam: NimNode, procParam: NimNode) =
  88. let scratchDotExpr = newDotExpr(scratchIdent, formalParams[i][0])
  89. checkIsolate(scratchAssignList, procParam, scratchDotExpr)
  90. let tempNode = genSym(kind = nskTemp, ident = formalParams[i][0].strVal)
  91. callNode.add nnkExprEqExpr.newTree(formalParams[i][0], tempNode)
  92. tempAssignList.add newLetStmt(tempNode, newDotExpr(objTemp, formalParams[i][0]))
  93. scratchRecList.add newIdentDefs(newIdentNode(formalParams[i][0].strVal), assignParam)
  94. proc analyseRootSym(s: NimNode): NimNode =
  95. result = s
  96. while true:
  97. case result.kind
  98. of nnkBracketExpr, nnkDerefExpr, nnkHiddenDeref,
  99. nnkAddr, nnkHiddenAddr,
  100. nnkObjDownConv, nnkObjUpConv:
  101. result = result[0]
  102. of nnkDotExpr, nnkCheckedFieldExpr, nnkHiddenStdConv, nnkHiddenSubConv:
  103. result = result[1]
  104. else:
  105. break
  106. macro toTask*(e: typed{nkCall | nkInfix | nkPrefix | nkPostfix | nkCommand | nkCallStrLit}): Task =
  107. ## Converts the call and its arguments to `Task`.
  108. runnableExamples:
  109. proc hello(a: int) = echo a
  110. let b = toTask hello(13)
  111. assert b is Task
  112. let retType = getTypeInst(e)
  113. let returnsVoid = retType.typeKind == ntyVoid
  114. let rootSym = analyseRootSym(e[0])
  115. expectKind rootSym, nnkSym
  116. when compileOption("threads"):
  117. if not isGcSafe(rootSym):
  118. error("'toTask' takes a GC safe call expression", e)
  119. if hasClosure(rootSym):
  120. error("closure call is not allowed", e)
  121. if e.len > 1:
  122. let scratchIdent = genSym(kind = nskTemp, ident = "scratch")
  123. let impl = e[0].getTypeInst
  124. when defined(nimTasksDebug):
  125. echo impl.treeRepr
  126. echo e.treeRepr
  127. let formalParams = impl[0]
  128. var
  129. scratchRecList = newNimNode(nnkRecList)
  130. scratchAssignList: seq[NimNode]
  131. tempAssignList: seq[NimNode]
  132. callNode: seq[NimNode]
  133. let
  134. objTemp = genSym(nskTemp, ident = "objTemp")
  135. for i in 1 ..< formalParams.len:
  136. var param = formalParams[i][1]
  137. if param.kind == nnkBracketExpr and param[0].eqIdent("sink"):
  138. param = param[0]
  139. if param.typeKind in {ntyExpr, ntyStmt}:
  140. error("'toTask'ed function cannot have a 'typed' or 'untyped' parameter", e)
  141. case param.kind
  142. of nnkVarTy:
  143. error("'toTask'ed function cannot have a 'var' parameter", e)
  144. of nnkBracketExpr:
  145. if param[0].typeKind == ntyTypeDesc:
  146. callNode.add nnkExprEqExpr.newTree(formalParams[i][0], e[i])
  147. elif param[0].typeKind in {ntyVarargs, ntyOpenArray}:
  148. if param[1].typeKind in {ntyExpr, ntyStmt}:
  149. error("'toTask'ed function cannot have a 'typed' or 'untyped' parameter", e)
  150. let
  151. seqType = nnkBracketExpr.newTree(newIdentNode("seq"), param[1])
  152. seqCallNode = newCall("@", e[i])
  153. addAllNode(seqType, seqCallNode)
  154. else:
  155. addAllNode(param, e[i])
  156. of nnkBracket, nnkObjConstr:
  157. # passing by static parameters
  158. # so we pass them directly instead of passing by scratchObj
  159. callNode.add nnkExprEqExpr.newTree(formalParams[i][0], e[i])
  160. of nnkSym, nnkPtrTy, nnkProcTy, nnkTupleConstr:
  161. addAllNode(param, e[i])
  162. of nnkCharLit..nnkNilLit:
  163. callNode.add nnkExprEqExpr.newTree(formalParams[i][0], e[i])
  164. else:
  165. error("'toTask'ed function cannot have a parameter of " & $param.kind & " kind", e)
  166. let scratchObjType = genSym(kind = nskType, ident = "ScratchObj")
  167. let scratchObj = nnkTypeSection.newTree(
  168. nnkTypeDef.newTree(
  169. scratchObjType,
  170. newEmptyNode(),
  171. nnkObjectTy.newTree(
  172. newEmptyNode(),
  173. newEmptyNode(),
  174. scratchRecList
  175. )
  176. )
  177. )
  178. let scratchObjPtrType = quote do:
  179. cast[ptr `scratchObjType`](allocShared0(sizeof(`scratchObjType`)))
  180. let scratchLetSection = newLetStmt(scratchIdent, scratchObjPtrType)
  181. var stmtList = newStmtList()
  182. stmtList.add(scratchObj)
  183. stmtList.add(scratchLetSection)
  184. stmtList.add(nnkBlockStmt.newTree(newEmptyNode(), newStmtList(scratchAssignList)))
  185. var functionStmtList = newStmtList()
  186. let funcCall = newCall(e[0], callNode)
  187. functionStmtList.add tempAssignList
  188. let funcName = genSym(nskProc, rootSym.strVal)
  189. let destroyName = genSym(nskProc, "destroyScratch")
  190. let objTemp2 = genSym(ident = "obj")
  191. let tempNode = quote("@") do:
  192. `=destroy`(@objTemp2[])
  193. var funcDecl: NimNode
  194. if returnsVoid:
  195. funcDecl = quote do:
  196. proc `funcName`(args, res: pointer) {.gcsafe, nimcall.} =
  197. let `objTemp` = cast[ptr `scratchObjType`](args)
  198. `functionStmtList`
  199. `funcCall`
  200. else:
  201. funcDecl = quote do:
  202. proc `funcName`(args, res: pointer) {.gcsafe, nimcall.} =
  203. let `objTemp` = cast[ptr `scratchObjType`](args)
  204. `functionStmtList`
  205. cast[ptr `retType`](res)[] = `funcCall`
  206. result = quote do:
  207. `stmtList`
  208. `funcDecl`
  209. proc `destroyName`(args: pointer) {.gcsafe, nimcall.} =
  210. let `objTemp2` = cast[ptr `scratchObjType`](args)
  211. `tempNode`
  212. Task(callback: `funcName`, args: `scratchIdent`, destroy: `destroyName`)
  213. else:
  214. let funcCall = newCall(e[0])
  215. let funcName = genSym(nskProc, rootSym.strVal)
  216. if returnsVoid:
  217. result = quote do:
  218. proc `funcName`(args, res: pointer) {.gcsafe, nimcall.} =
  219. `funcCall`
  220. Task(callback: `funcName`, args: nil)
  221. else:
  222. result = quote do:
  223. proc `funcName`(args, res: pointer) {.gcsafe, nimcall.} =
  224. cast[ptr `retType`](res)[] = `funcCall`
  225. Task(callback: `funcName`, args: nil)
  226. when defined(nimTasksDebug):
  227. echo result.repr
  228. runnableExamples:
  229. block:
  230. var num = 0
  231. proc hello(a: int) = inc num, a
  232. let b = toTask hello(13)
  233. b.invoke()
  234. assert num == 13
  235. # A task can be invoked multiple times
  236. b.invoke()
  237. assert num == 26
  238. block:
  239. type
  240. Runnable = ref object
  241. data: int
  242. var data: int
  243. proc hello(a: Runnable) {.nimcall.} =
  244. a.data += 2
  245. data = a.data
  246. when false:
  247. # the parameters of call must be isolated.
  248. let x = Runnable(data: 12)
  249. let b = toTask hello(x) # error ----> expression cannot be isolated: x
  250. b.invoke()
  251. let b = toTask(hello(Runnable(data: 12)))
  252. b.invoke()
  253. assert data == 14
  254. b.invoke()
  255. assert data == 16