From ad40364e71c39c14f05972221f7ee0b5e4353136 Mon Sep 17 00:00:00 2001 From: Smooth Operator Date: Sun, 7 Nov 2021 15:55:52 -0500 Subject: [PATCH] fixes #235 in some cases --- README.md | 4 ++++ cps/spec.nim | 32 +++++++++++++++++++++++++++----- cps/transform.nim | 28 +++++++++++++++------------- tests/tapi.nim | 11 +++++++++++ tests/thooks.nim | 2 +- 5 files changed, 58 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 424a692e..7ebcc304 100644 --- a/README.md +++ b/README.md @@ -271,6 +271,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. diff --git a/cps/spec.nim b/cps/spec.nim index 1ea28df5..f1bb73ce 100644 --- a/cps/spec.nim +++ b/cps/spec.nim @@ -350,7 +350,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) @@ -359,6 +370,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 @@ -376,8 +388,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 @@ -386,19 +402,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 `&` diff --git a/cps/transform.nim b/cps/transform.nim index 78ff09d6..d9741aa8 100644 --- a/cps/transform.nim +++ b/cps/transform.nim @@ -1049,19 +1049,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: diff --git a/tests/tapi.nim b/tests/tapi.nim index fac12a14..cf443ae1 100644 --- a/tests/tapi.nim +++ b/tests/tapi.nim @@ -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" diff --git a/tests/thooks.nim b/tests/thooks.nim index ba24d19a..593e1da8 100644 --- a/tests/thooks.nim +++ b/tests/thooks.nim @@ -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)