tasks.nim 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  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. when defined(nimAllowNonVarDestructor):
  59. proc `=destroy`*(t: Task) {.inline, gcsafe.} =
  60. ## Frees the resources allocated for a `Task`.
  61. if t.args != nil:
  62. if t.destroy != nil:
  63. t.destroy(t.args)
  64. deallocShared(t.args)
  65. else:
  66. proc `=destroy`*(t: var Task) {.inline, gcsafe.} =
  67. ## Frees the resources allocated for a `Task`.
  68. if t.args != nil:
  69. if t.destroy != nil:
  70. t.destroy(t.args)
  71. deallocShared(t.args)
  72. proc invoke*(task: Task; res: pointer = nil) {.inline, gcsafe.} =
  73. ## Invokes the `task`.
  74. assert task.callback != nil
  75. task.callback(task.args, res)
  76. template checkIsolate(scratchAssignList: seq[NimNode], procParam, scratchDotExpr: NimNode) =
  77. # block:
  78. # var isoTempA = isolate(521)
  79. # scratch.a = extract(isolateA)
  80. # var isoTempB = isolate(literal)
  81. # scratch.b = extract(isolateB)
  82. let isolatedTemp = genSym(nskTemp, "isoTemp")
  83. scratchAssignList.add newVarStmt(isolatedTemp, newCall(newIdentNode("isolate"), procParam))
  84. scratchAssignList.add newAssignment(scratchDotExpr,
  85. newCall(newIdentNode("extract"), isolatedTemp))
  86. template addAllNode(assignParam: NimNode, procParam: NimNode) =
  87. let scratchDotExpr = newDotExpr(scratchIdent, formalParams[i][0])
  88. checkIsolate(scratchAssignList, procParam, scratchDotExpr)
  89. let tempNode = genSym(kind = nskTemp, ident = formalParams[i][0].strVal)
  90. callNode.add nnkExprEqExpr.newTree(formalParams[i][0], tempNode)
  91. tempAssignList.add newLetStmt(tempNode, newDotExpr(objTemp, formalParams[i][0]))
  92. scratchRecList.add newIdentDefs(newIdentNode(formalParams[i][0].strVal), assignParam)
  93. macro toTask*(e: typed{nkCall | nkInfix | nkPrefix | nkPostfix | nkCommand | nkCallStrLit}): Task =
  94. ## Converts the call and its arguments to `Task`.
  95. runnableExamples:
  96. proc hello(a: int) = echo a
  97. let b = toTask hello(13)
  98. assert b is Task
  99. let retType = getTypeInst(e)
  100. let returnsVoid = retType.typeKind == ntyVoid
  101. when compileOption("threads"):
  102. if not isGcSafe(e[0]):
  103. error("'toTask' takes a GC safe call expression", e)
  104. if hasClosure(e[0]):
  105. error("closure call is not allowed", e)
  106. if e.len > 1:
  107. let scratchIdent = genSym(kind = nskTemp, ident = "scratch")
  108. let impl = e[0].getTypeInst
  109. when defined(nimTasksDebug):
  110. echo impl.treeRepr
  111. echo e.treeRepr
  112. let formalParams = impl[0]
  113. var
  114. scratchRecList = newNimNode(nnkRecList)
  115. scratchAssignList: seq[NimNode]
  116. tempAssignList: seq[NimNode]
  117. callNode: seq[NimNode]
  118. let
  119. objTemp = genSym(nskTemp, ident = "objTemp")
  120. for i in 1 ..< formalParams.len:
  121. var param = formalParams[i][1]
  122. if param.kind == nnkBracketExpr and param[0].eqIdent("sink"):
  123. param = param[0]
  124. if param.typeKind in {ntyExpr, ntyStmt}:
  125. error("'toTask'ed function cannot have a 'typed' or 'untyped' parameter", e)
  126. case param.kind
  127. of nnkVarTy:
  128. error("'toTask'ed function cannot have a 'var' parameter", e)
  129. of nnkBracketExpr:
  130. if param[0].typeKind == ntyTypeDesc:
  131. callNode.add nnkExprEqExpr.newTree(formalParams[i][0], e[i])
  132. elif param[0].typeKind in {ntyVarargs, ntyOpenArray}:
  133. if param[1].typeKind in {ntyExpr, ntyStmt}:
  134. error("'toTask'ed function cannot have a 'typed' or 'untyped' parameter", e)
  135. let
  136. seqType = nnkBracketExpr.newTree(newIdentNode("seq"), param[1])
  137. seqCallNode = newCall("@", e[i])
  138. addAllNode(seqType, seqCallNode)
  139. else:
  140. addAllNode(param, e[i])
  141. of nnkBracket, nnkObjConstr:
  142. # passing by static parameters
  143. # so we pass them directly instead of passing by scratchObj
  144. callNode.add nnkExprEqExpr.newTree(formalParams[i][0], e[i])
  145. of nnkSym, nnkPtrTy, nnkProcTy, nnkTupleConstr:
  146. addAllNode(param, e[i])
  147. of nnkCharLit..nnkNilLit:
  148. callNode.add nnkExprEqExpr.newTree(formalParams[i][0], e[i])
  149. else:
  150. error("'toTask'ed function cannot have a parameter of " & $param.kind & " kind", e)
  151. let scratchObjType = genSym(kind = nskType, ident = "ScratchObj")
  152. let scratchObj = nnkTypeSection.newTree(
  153. nnkTypeDef.newTree(
  154. scratchObjType,
  155. newEmptyNode(),
  156. nnkObjectTy.newTree(
  157. newEmptyNode(),
  158. newEmptyNode(),
  159. scratchRecList
  160. )
  161. )
  162. )
  163. let scratchObjPtrType = quote do:
  164. cast[ptr `scratchObjType`](allocShared0(sizeof(`scratchObjType`)))
  165. let scratchLetSection = newLetStmt(scratchIdent, scratchObjPtrType)
  166. var stmtList = newStmtList()
  167. stmtList.add(scratchObj)
  168. stmtList.add(scratchLetSection)
  169. stmtList.add(nnkBlockStmt.newTree(newEmptyNode(), newStmtList(scratchAssignList)))
  170. var functionStmtList = newStmtList()
  171. let funcCall = newCall(e[0], callNode)
  172. functionStmtList.add tempAssignList
  173. let funcName = genSym(nskProc, e[0].strVal)
  174. let destroyName = genSym(nskProc, "destroyScratch")
  175. let objTemp2 = genSym(ident = "obj")
  176. let tempNode = quote("@") do:
  177. `=destroy`(@objTemp2[])
  178. var funcDecl: NimNode
  179. if returnsVoid:
  180. funcDecl = quote do:
  181. proc `funcName`(args, res: pointer) {.gcsafe, nimcall.} =
  182. let `objTemp` = cast[ptr `scratchObjType`](args)
  183. `functionStmtList`
  184. `funcCall`
  185. else:
  186. funcDecl = quote do:
  187. proc `funcName`(args, res: pointer) {.gcsafe, nimcall.} =
  188. let `objTemp` = cast[ptr `scratchObjType`](args)
  189. `functionStmtList`
  190. cast[ptr `retType`](res)[] = `funcCall`
  191. result = quote do:
  192. `stmtList`
  193. `funcDecl`
  194. proc `destroyName`(args: pointer) {.gcsafe, nimcall.} =
  195. let `objTemp2` = cast[ptr `scratchObjType`](args)
  196. `tempNode`
  197. Task(callback: `funcName`, args: `scratchIdent`, destroy: `destroyName`)
  198. else:
  199. let funcCall = newCall(e[0])
  200. let funcName = genSym(nskProc, e[0].strVal)
  201. if returnsVoid:
  202. result = quote do:
  203. proc `funcName`(args, res: pointer) {.gcsafe, nimcall.} =
  204. `funcCall`
  205. Task(callback: `funcName`, args: nil)
  206. else:
  207. result = quote do:
  208. proc `funcName`(args, res: pointer) {.gcsafe, nimcall.} =
  209. cast[ptr `retType`](res)[] = `funcCall`
  210. Task(callback: `funcName`, args: nil)
  211. when defined(nimTasksDebug):
  212. echo result.repr
  213. runnableExamples:
  214. block:
  215. var num = 0
  216. proc hello(a: int) = inc num, a
  217. let b = toTask hello(13)
  218. b.invoke()
  219. assert num == 13
  220. # A task can be invoked multiple times
  221. b.invoke()
  222. assert num == 26
  223. block:
  224. type
  225. Runnable = ref object
  226. data: int
  227. var data: int
  228. proc hello(a: Runnable) {.nimcall.} =
  229. a.data += 2
  230. data = a.data
  231. when false:
  232. # the parameters of call must be isolated.
  233. let x = Runnable(data: 12)
  234. let b = toTask hello(x) # error ----> expression cannot be isolated: x
  235. b.invoke()
  236. let b = toTask(hello(Runnable(data: 12)))
  237. b.invoke()
  238. assert data == 14
  239. b.invoke()
  240. assert data == 16