tasks.nim 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  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. import system/ansi_c
  13. export isolation
  14. when compileOption("threads"):
  15. from std/effecttraits import isGcSafe
  16. #
  17. # proc hello(a: int, b: string) =
  18. # echo $a & b
  19. #
  20. # let literal = "Nim"
  21. # let t = toTask(hello(521, literal))
  22. #
  23. #
  24. # is roughly converted to
  25. #
  26. # type
  27. # ScratchObj_369098780 = object
  28. # a: int
  29. # b: string
  30. #
  31. # let scratch_369098762 = cast[ptr ScratchObj_369098780](c_calloc(csize_t 1,
  32. # csize_t sizeof(ScratchObj_369098780)))
  33. # if scratch_369098762.isNil:
  34. # raise newException(OutOfMemDefect, "Could not allocate memory")
  35. # block:
  36. # var isolate_369098776 = isolate(521)
  37. # scratch_369098762.a = extract(isolate_369098776)
  38. # var isolate_369098778 = isolate(literal)
  39. # scratch_369098762.b = extract(isolate_369098778)
  40. # proc hello_369098781(args`gensym3: pointer) {.nimcall.} =
  41. # let objTemp_369098775 = cast[ptr ScratchObj_369098780](args`gensym3)
  42. # let :tmp_369098777 = objTemp_369098775.a
  43. # let :tmp_369098779 = objTemp_369098775.b
  44. # hello(a = :tmp_369098777, b = :tmp_369098779)
  45. #
  46. # proc destroyScratch_369098782(args`gensym3: pointer) {.nimcall.} =
  47. # let obj_369098783 = cast[ptr ScratchObj_369098780](args`gensym3)
  48. # =destroy(obj_369098783[])
  49. # let t = Task(callback: hello_369098781, args: scratch_369098762, destroy: destroyScratch_369098782)
  50. #
  51. type
  52. Task* = object ## `Task` contains the callback and its arguments.
  53. callback: proc (args: pointer) {.nimcall.}
  54. args: pointer
  55. destroy: proc (args: pointer) {.nimcall.}
  56. proc `=copy`*(x: var Task, y: Task) {.error.}
  57. proc `=destroy`*(t: var Task) {.inline.} =
  58. ## Frees the resources allocated for a `Task`.
  59. if t.args != nil:
  60. if t.destroy != nil:
  61. t.destroy(t.args)
  62. c_free(t.args)
  63. proc invoke*(task: Task) {.inline.} =
  64. ## Invokes the `task`.
  65. assert task.callback != nil
  66. task.callback(task.args)
  67. template checkIsolate(scratchAssignList: seq[NimNode], procParam, scratchDotExpr: NimNode) =
  68. # block:
  69. # var isoTempA = isolate(521)
  70. # scratch.a = extract(isolateA)
  71. # var isoTempB = isolate(literal)
  72. # scratch.b = extract(isolateB)
  73. let isolatedTemp = genSym(nskTemp, "isoTemp")
  74. scratchAssignList.add newVarStmt(isolatedTemp, newCall(newidentNode("isolate"), procParam))
  75. scratchAssignList.add newAssignment(scratchDotExpr,
  76. newcall(newIdentNode("extract"), isolatedTemp))
  77. template addAllNode(assignParam: NimNode, procParam: NimNode) =
  78. let scratchDotExpr = newDotExpr(scratchIdent, formalParams[i][0])
  79. checkIsolate(scratchAssignList, procParam, scratchDotExpr)
  80. let tempNode = genSym(kind = nskTemp, ident = formalParams[i][0].strVal)
  81. callNode.add nnkExprEqExpr.newTree(formalParams[i][0], tempNode)
  82. tempAssignList.add newLetStmt(tempNode, newDotExpr(objTemp, formalParams[i][0]))
  83. scratchRecList.add newIdentDefs(newIdentNode(formalParams[i][0].strVal), assignParam)
  84. macro toTask*(e: typed{nkCall | nkInfix | nkPrefix | nkPostfix | nkCommand | nkCallStrLit}): Task =
  85. ## Converts the call and its arguments to `Task`.
  86. runnableExamples("--gc:orc"):
  87. proc hello(a: int) = echo a
  88. let b = toTask hello(13)
  89. assert b is Task
  90. doAssert getTypeInst(e).typeKind == ntyVoid
  91. when compileOption("threads"):
  92. if not isGcSafe(e[0]):
  93. error("'toTask' takes a GC safe call expression")
  94. if hasClosure(e[0]):
  95. error("closure call is not allowed")
  96. if e.len > 1:
  97. let scratchIdent = genSym(kind = nskTemp, ident = "scratch")
  98. let impl = e[0].getTypeInst
  99. when defined(nimTasksDebug):
  100. echo impl.treeRepr
  101. echo e.treeRepr
  102. let formalParams = impl[0]
  103. var
  104. scratchRecList = newNimNode(nnkRecList)
  105. scratchAssignList: seq[NimNode]
  106. tempAssignList: seq[NimNode]
  107. callNode: seq[NimNode]
  108. let
  109. objTemp = genSym(nskTemp, ident = "objTemp")
  110. for i in 1 ..< formalParams.len:
  111. var param = formalParams[i][1]
  112. if param.kind == nnkBracketExpr and param[0].eqIdent("sink"):
  113. param = param[0]
  114. if param.typeKind in {ntyExpr, ntyStmt}:
  115. error("'toTask'ed function cannot have a 'typed' or 'untyped' parameter")
  116. case param.kind
  117. of nnkVarTy:
  118. error("'toTask'ed function cannot have a 'var' parameter")
  119. of nnkBracketExpr:
  120. if param[0].typeKind == ntyTypeDesc:
  121. callNode.add nnkExprEqExpr.newTree(formalParams[i][0], e[i])
  122. elif param[0].typeKind in {ntyVarargs, ntyOpenArray}:
  123. if param[1].typeKind in {ntyExpr, ntyStmt}:
  124. error("'toTask'ed function cannot have a 'typed' or 'untyped' parameter")
  125. let
  126. seqType = nnkBracketExpr.newTree(newIdentNode("seq"), param[1])
  127. seqCallNode = newcall("@", e[i])
  128. addAllNode(seqType, seqCallNode)
  129. else:
  130. addAllNode(param, e[i])
  131. of nnkBracket, nnkObjConstr:
  132. # passing by static parameters
  133. # so we pass them directly instead of passing by scratchObj
  134. callNode.add nnkExprEqExpr.newTree(formalParams[i][0], e[i])
  135. of nnkSym, nnkPtrTy:
  136. addAllNode(param, e[i])
  137. of nnkCharLit..nnkNilLit:
  138. callNode.add nnkExprEqExpr.newTree(formalParams[i][0], e[i])
  139. else:
  140. error("not supported type kinds")
  141. let scratchObjType = genSym(kind = nskType, ident = "ScratchObj")
  142. let scratchObj = nnkTypeSection.newTree(
  143. nnkTypeDef.newTree(
  144. scratchObjType,
  145. newEmptyNode(),
  146. nnkObjectTy.newTree(
  147. newEmptyNode(),
  148. newEmptyNode(),
  149. scratchRecList
  150. )
  151. )
  152. )
  153. let scratchObjPtrType = quote do:
  154. cast[ptr `scratchObjType`](c_calloc(csize_t 1, csize_t sizeof(`scratchObjType`)))
  155. let scratchLetSection = newLetStmt(
  156. scratchIdent,
  157. scratchObjPtrType
  158. )
  159. let scratchCheck = quote do:
  160. if `scratchIdent`.isNil:
  161. raise newException(OutOfMemDefect, "Could not allocate memory")
  162. var stmtList = newStmtList()
  163. stmtList.add(scratchObj)
  164. stmtList.add(scratchLetSection)
  165. stmtList.add(scratchCheck)
  166. stmtList.add(nnkBlockStmt.newTree(newEmptyNode(), newStmtList(scratchAssignList)))
  167. var functionStmtList = newStmtList()
  168. let funcCall = newCall(e[0], callNode)
  169. functionStmtList.add tempAssignList
  170. functionStmtList.add funcCall
  171. let funcName = genSym(nskProc, e[0].strVal)
  172. let destroyName = genSym(nskProc, "destroyScratch")
  173. let objTemp2 = genSym(ident = "obj")
  174. let tempNode = quote("@") do:
  175. `=destroy`(@objTemp2[])
  176. result = quote do:
  177. `stmtList`
  178. proc `funcName`(args: pointer) {.nimcall.} =
  179. let `objTemp` = cast[ptr `scratchObjType`](args)
  180. `functionStmtList`
  181. proc `destroyName`(args: pointer) {.nimcall.} =
  182. let `objTemp2` = cast[ptr `scratchObjType`](args)
  183. `tempNode`
  184. Task(callback: `funcName`, args: `scratchIdent`, destroy: `destroyName`)
  185. else:
  186. let funcCall = newCall(e[0])
  187. let funcName = genSym(nskProc, e[0].strVal)
  188. result = quote do:
  189. proc `funcName`(args: pointer) {.nimcall.} =
  190. `funcCall`
  191. Task(callback: `funcName`, args: nil)
  192. when defined(nimTasksDebug):
  193. echo result.repr
  194. runnableExamples("--gc:orc"):
  195. block:
  196. var num = 0
  197. proc hello(a: int) = inc num, a
  198. let b = toTask hello(13)
  199. b.invoke()
  200. assert num == 13
  201. # A task can be invoked multiple times
  202. b.invoke()
  203. assert num == 26
  204. block:
  205. type
  206. Runnable = ref object
  207. data: int
  208. var data: int
  209. proc hello(a: Runnable) {.nimcall.} =
  210. a.data += 2
  211. data = a.data
  212. when false:
  213. # the parameters of call must be isolated.
  214. let x = Runnable(data: 12)
  215. let b = toTask hello(x) # error ----> expression cannot be isolated: x
  216. b.invoke()
  217. let b = toTask(hello(Runnable(data: 12)))
  218. b.invoke()
  219. assert data == 14
  220. b.invoke()
  221. assert data == 16