nim-taskpools/taskpools/shims_pre_1_6/tasks.nim

291 lines
9.0 KiB
Nim

#
#
# Nim's Runtime Library
# (c) Copyright 2021 Nim contributors
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## This module provides basic primitives for creating parallel programs.
## A `Task` should be only owned by a single Thread, it cannot be shared by threads.
import std/[macros, typetraits]
import system/ansi_c
import ./isolation
export isolation
when compileOption("threads"):
from ./effecttraits import isGcSafe
#
# proc hello(a: int, b: string) =
# echo $a & b
#
# let literal = "Nim"
# let t = toTask(hello(521, literal))
#
#
# is roughly converted to
#
# type
# ScratchObj_369098780 = object
# a: int
# b: string
#
# let scratch_369098762 = cast[ptr ScratchObj_369098780](c_calloc(csize_t 1,
# csize_t sizeof(ScratchObj_369098780)))
# if scratch_369098762.isNil:
# raise newException(OutOfMemDefect, "Could not allocate memory")
# block:
# var isolate_369098776 = isolate(521)
# scratch_369098762.a = extract(isolate_369098776)
# var isolate_369098778 = isolate(literal)
# scratch_369098762.b = extract(isolate_369098778)
# proc hello_369098781(args`gensym3: pointer) {.nimcall.} =
# let objTemp_369098775 = cast[ptr ScratchObj_369098780](args`gensym3)
# let :tmp_369098777 = objTemp_369098775.a
# let :tmp_369098779 = objTemp_369098775.b
# hello(a = :tmp_369098777, b = :tmp_369098779)
#
# proc destroyScratch_369098782(args`gensym3: pointer) {.nimcall.} =
# let obj_369098783 = cast[ptr ScratchObj_369098780](args`gensym3)
# =destroy(obj_369098783[])
# let t = Task(callback: hello_369098781, args: scratch_369098762, destroy: destroyScratch_369098782)
#
type
Task* = object ## `Task` contains the callback and its arguments.
callback: proc (args: pointer) {.nimcall, gcsafe.}
args: pointer
destroy: proc (args: pointer) {.nimcall, gcsafe.}
# XXX: ⚠️ No destructors for 1.2 due to unreliable codegen
# proc `=copy`*(x: var Task, y: Task) {.error.}
proc shim_destroy*(t: var Task) {.inline, gcsafe.} =
## Frees the resources allocated for a `Task`.
if t.args != nil:
if t.destroy != nil:
t.destroy(t.args)
c_free(t.args)
proc invoke*(task: Task) {.inline, gcsafe.} =
## Invokes the `task`.
assert task.callback != nil
task.callback(task.args)
template checkIsolate(scratchAssignList: seq[NimNode], procParam, scratchDotExpr: NimNode) =
# block:
# var isoTempA = isolate(521)
# scratch.a = extract(isolateA)
# var isoTempB = isolate(literal)
# scratch.b = extract(isolateB)
let isolatedTemp = genSym(nskTemp, "isoTemp")
# XXX: Fix sym bindings
# scratchAssignList.add newVarStmt(isolatedTemp, newCall(newidentNode("isolate"), procParam))
# scratchAssignList.add newAssignment(scratchDotExpr,
# newcall(newIdentNode("extract"), isolatedTemp))
scratchAssignList.add newVarStmt(isolatedTemp, newCall(bindSym("isolate"), procParam))
scratchAssignList.add newAssignment(scratchDotExpr,
newcall(bindSym("extract"), isolatedTemp))
template addAllNode(assignParam: NimNode, procParam: NimNode) =
let scratchDotExpr = newDotExpr(scratchIdent, formalParams[i][0])
checkIsolate(scratchAssignList, procParam, scratchDotExpr)
let tempNode = genSym(kind = nskTemp, ident = formalParams[i][0].strVal)
callNode.add nnkExprEqExpr.newTree(formalParams[i][0], tempNode)
tempAssignList.add newLetStmt(tempNode, newDotExpr(objTemp, formalParams[i][0]))
scratchRecList.add newIdentDefs(newIdentNode(formalParams[i][0].strVal), assignParam)
macro toTask*(e: typed{nkCall | nkInfix | nkPrefix | nkPostfix | nkCommand | nkCallStrLit}): Task =
## Converts the call and its arguments to `Task`.
runnableExamples("--gc:orc"):
proc hello(a: int) = echo a
let b = toTask hello(13)
assert b is Task
doAssert getTypeInst(e).typeKind == ntyVoid
# requires 1.6
# when compileOption("threads"):
# if not isGcSafe(e[0]):
# error("'toTask' takes a GC safe call expression")
# TODO
# https://github.com/nim-lang/Nim/pull/17501/files
#
# if hasClosure(e[0]):
# error("closure call is not allowed")
if e.len > 1:
let scratchIdent = genSym(kind = nskTemp, ident = "scratch")
let impl = e[0].getTypeInst
when defined(nimTasksDebug):
echo impl.treeRepr
echo e.treeRepr
let formalParams = impl[0]
var
scratchRecList = newNimNode(nnkRecList)
scratchAssignList: seq[NimNode]
tempAssignList: seq[NimNode]
callNode: seq[NimNode]
let
objTemp = genSym(nskTemp, ident = "objTemp")
for i in 1 ..< formalParams.len:
var param = formalParams[i][1]
if param.kind == nnkBracketExpr and param[0].eqIdent("sink"):
param = param[0]
if param.typeKind in {ntyExpr, ntyStmt}:
error("'toTask'ed function cannot have a 'typed' or 'untyped' parameter")
case param.kind
of nnkVarTy:
error("'toTask'ed function cannot have a 'var' parameter")
of nnkBracketExpr:
if param[0].typeKind == ntyTypeDesc:
callNode.add nnkExprEqExpr.newTree(formalParams[i][0], e[i])
elif param[0].typeKind in {ntyVarargs, ntyOpenArray}:
if param[1].typeKind in {ntyExpr, ntyStmt}:
error("'toTask'ed function cannot have a 'typed' or 'untyped' parameter")
let
seqType = nnkBracketExpr.newTree(newIdentNode("seq"), param[1])
seqCallNode = newcall("@", e[i])
addAllNode(seqType, seqCallNode)
else:
addAllNode(param, e[i])
of nnkBracket, nnkObjConstr:
# passing by static parameters
# so we pass them directly instead of passing by scratchObj
callNode.add nnkExprEqExpr.newTree(formalParams[i][0], e[i])
of nnkSym, nnkPtrTy:
addAllNode(param, e[i])
of nnkCharLit..nnkNilLit:
callNode.add nnkExprEqExpr.newTree(formalParams[i][0], e[i])
else:
error("not supported type kinds")
let scratchObjType = genSym(kind = nskType, ident = "ScratchObj")
let scratchObj = nnkTypeSection.newTree(
nnkTypeDef.newTree(
scratchObjType,
newEmptyNode(),
nnkObjectTy.newTree(
newEmptyNode(),
newEmptyNode(),
scratchRecList
)
)
)
let scratchObjPtrType = quote do:
cast[ptr `scratchObjType`](c_calloc(csize_t 1, csize_t sizeof(`scratchObjType`)))
let scratchLetSection = newLetStmt(
scratchIdent,
scratchObjPtrType
)
let scratchCheck = quote do:
if `scratchIdent`.isNil:
# Renamed in 1.4
# raise newException(OutOfMemDefect, "Could not allocate memory")
raise newException(OutOfMemError, "Could not allocate memory")
var stmtList = newStmtList()
stmtList.add(scratchObj)
stmtList.add(scratchLetSection)
stmtList.add(scratchCheck)
stmtList.add(nnkBlockStmt.newTree(newEmptyNode(), newStmtList(scratchAssignList)))
var functionStmtList = newStmtList()
let funcCall = newCall(e[0], callNode)
functionStmtList.add tempAssignList
functionStmtList.add funcCall
let funcName = genSym(nskProc, e[0].strVal)
let destroyName = genSym(nskProc, "destroyScratch")
let objTemp2 = genSym(ident = "obj")
let tempNode = quote("@") do:
# XXX:
# We avoid destructors for Nim 1.2 due to bad codegen
# For taskpool there are no destructor to run.
# We ensure that by checking that we only operate on plain old data
static: doAssert supportsCopyMem(@scratchObjType)
# `=destroy`(@objTemp2[])
result = quote do:
`stmtList`
proc `funcName`(args: pointer) {.gcsafe, nimcall.} =
let `objTemp` = cast[ptr `scratchObjType`](args)
`functionStmtList`
proc `destroyName`(args: pointer) {.nimcall.} =
let `objTemp2` = cast[ptr `scratchObjType`](args)
`tempNode`
Task(callback: `funcName`, args: `scratchIdent`, destroy: `destroyName`)
else:
let funcCall = newCall(e[0])
let funcName = genSym(nskProc, e[0].strVal)
result = quote do:
proc `funcName`(args: pointer) {.gcsafe, nimcall.} =
`funcCall`
Task(callback: `funcName`, args: nil)
when defined(nimTasksDebug):
echo result.repr
runnableExamples("--gc:orc"):
block:
var num = 0
proc hello(a: int) = inc num, a
let b = toTask hello(13)
b.invoke()
assert num == 13
# A task can be invoked multiple times
b.invoke()
assert num == 26
block:
type
Runnable = ref object
data: int
var data: int
proc hello(a: Runnable) {.nimcall.} =
a.data += 2
data = a.data
when false:
# the parameters of call must be isolated.
let x = Runnable(data: 12)
let b = toTask hello(x) # error ----> expression cannot be isolated: x
b.invoke()
let b = toTask(hello(Runnable(data: 12)))
b.invoke()
assert data == 14
b.invoke()
assert data == 16