Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow some error-shim overloads for cpsMagic, cpsVoodoo #258

Merged
merged 2 commits into from
Nov 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,10 @@ yet demonstrates different exploits of `cps`.
See [this list of open Nim issues surfaced by CPS
development](https://github.com/nim-lang/Nim/issues?q=is%3Aopen+is%3Aissue+label%3ACPS); some repercussions include the following:

- You can overload `cpsVoodoo` procedures with `cpsVoodoo`, and `cpsMagic`
with `cpsMagic`, but other overloads run the risk of clashing with the
shim produced by these procedure macros.

- Exceptions are evaluated differently under `panics:on` and `panics:off`, so
you may need to use `panics:on` in order to produce correct code.

Expand Down
32 changes: 27 additions & 5 deletions cps/spec.nim
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,18 @@ type
Dismissed ## The continuation is currently somewhere else
Finished ## The continuation is finished and can no longer be resumed

proc makeErrorShim*(n: NimNode): NimNode =
proc wrapErrorShim(n: NimNode): NimNode =
## Wrap a proc definition in a guard to avoid symbol redefinition errors.
expectKind(n, nnkProcDef)
# when not declaredInScope(fn):
result =
nnkWhenStmt.newTree:
nnkElifBranch.newTree:
prefix(newCall(bindSym"declaredInScope", n.name), "not")
# proc fn() = error "you can only do this in cps"
result.last.add n

proc makeErrorShim(n: NimNode): NimNode =
## Upgrades a procedure to serve as a CPS primitive, generating
## errors out of `.cps.` context and taking continuations as input.
expectKind(n, nnkProcDef)
Expand All @@ -368,6 +379,7 @@ proc makeErrorShim*(n: NimNode): NimNode =
# value. While this version will throw an exception at runtime, it
# may be used inside CPS as magic(); for better programmer ergonomics.
var shim = copyNimTree n
shim.addPragma ident"used"
del(shim.params, 1) # delete the 1st Continuation argument
let msg = newLit($n.name & "() is only valid in {.cps.} context")
shim.body = # raise a defect when invoked directly
Expand All @@ -385,8 +397,12 @@ macro cpsMagic*(n: untyped): untyped =
## The target procedure of a cpsMagic pragma returns the `Continuation`
## to which control-flow should return; this is _usually_ the same value
## passed into the procedure, but this is not required nor is it checked!
##
## You can overload `cpsVoodoo` procedures with `cpsVoodoo`, and `cpsMagic`
## with `cpsMagic`, but you cannot mix the two.
expectKind(n, nnkProcDef)
result = newStmtList NormNode n # preserve the original proc
let n = copyNimTree n
n.addPragma ident"used"
var shim = makeErrorShim n # create the shim
shim.params[0] = newEmptyNode() # wipe out the return value

Expand All @@ -395,19 +411,25 @@ macro cpsMagic*(n: untyped): untyped =
# continuation.
shim.addPragma ident"cpsMustJump"
shim.addPragma ident"cpsMagicCall"
result.add shim
shim = wrapErrorShim shim # `when not declaredInScope(fn): shim`
result = newStmtList(shim, n) # preserve the original proc

macro cpsVoodoo*(n: untyped): untyped =
## Similar to a `cpsMagic` where the first argument is concerned, but
## may specify a return value which is usable inside the CPS procedure.
##
## You can overload `cpsVoodoo` procedures with `cpsVoodoo`, and `cpsMagic`
## with `cpsMagic`, but you cannot mix the two.
expectKind(n, nnkProcDef)
result = newStmtList n # preserve the original proc
let n = copyNimTree n
n.addPragma ident"used"
var shim = makeErrorShim n # create the shim

# we use this pragma to identify the primitive and rewrite it inside
# CPS so that it again binds to the version that takes a continuation.
shim.addPragma ident"cpsVoodooCall"
result.add shim
shim = wrapErrorShim shim # `when not declaredInScope(fn): shim`
result = newStmtList(shim, n) # preserve the original proc

when cpsTraceDeque or cpsStackFrames:
from std/strformat import `&`
Expand Down
28 changes: 15 additions & 13 deletions cps/transform.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1077,19 +1077,21 @@ macro cpsHandleUnhandledException(contType: typed; n: typed): untyped =
# Rewrite continuations within this continuation body as well
fnDef.body = fnDef.body.filter(handle)
# Put the body in a try-except to capture the unhandled exception
fnDef.body = genAstOpt({}, contType, cont = NimNode cont,
body = NimNode fnDef.body):
bind getCurrentException
try:
body
except:
cont.ex = getCurrentException()
# A continuation body created with makeContProc (which is all of
# them) will have a terminator in the body, thus this part can
# only be reached iff the except branch happened to deter the jump
#
# Workaround for https://github.com/nim-lang/Nim/issues/18411
return Continuation: unwind(contType(cont), cont.ex)
fnDef.body =
genAstOpt({}, contType, cont = NimNode cont,
unwind = ident"unwind", # just a desym of the unwind()
body = NimNode fnDef.body):
bind getCurrentException
try:
body
except:
cont.ex = getCurrentException()
# A continuation body created with makeContProc (which is all of
# them) will have a terminator in the body, thus this part can
# only be reached iff the except branch happened to deter the jump
#
# Workaround for https://github.com/nim-lang/Nim/issues/18411
return Continuation unwind(contType(cont), cont.ex)
result = fnDef

debugAnnotation cpsHandleUnhandledException, n:
Expand Down
11 changes: 11 additions & 0 deletions tests/tapi.nim
Original file line number Diff line number Diff line change
Expand Up @@ -210,3 +210,14 @@ suite "cps api":
return 3

check foo() == 3

block:
## magic/voodoo can be overloaded
type
C = ref object of Continuation
O = ref object of C

proc sayYourNameV(c: C): string {.cpsVoodoo.} = return "I am C"
proc sayYourNameV(c: O): string {.cpsVoodoo.} = return "I am O"
proc sayYourNameM(c: C): C {.cpsMagic.} = discard "I am C"
proc sayYourNameM(c: O): O {.cpsMagic.} = discard "I am O"
2 changes: 1 addition & 1 deletion tests/thooks.nim
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ suite "hooks":
block:
## custom continuation exception handling works
var k = newKiller 4
proc unwind(c: Cont; ex: ref Exception): Continuation {.cpsMagic, used.} =
proc unwind(c: Cont; ex: ref Exception): Continuation {.cpsMagic.} =
inc k
result = cps.unwind(c, ex)

Expand Down