-
Notifications
You must be signed in to change notification settings - Fork 17
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
[WIP] move things into different passes #85
Conversation
saften is for the transformation only
A lot of things doesn't work, one of them is compiling taste. scratch.nim contains a simple test case that does compile. This will be rewritten soon-ish.
Not sure why the compiler produces this stuff, maybe its an artifact of desugaring `return expr` into `result = expr; return`. Except its `return result = expr` in this case. However we cannot rewrite this into `result = expr; return`, because the compiler wouldn't like that if the proc is nested within an another proc that returns (due to closure capture analysis), even if you use the `result` symbol within the return statement (the compiler is broken beyond help).
Sem-ed pragmas that are not built in will be rewritten into calls or a colon expression. This commit add support for detecting and filtering those pragmas.
Instead of making cpsJump an annotation so that other scope macros may direct the flow, we employ the use of a custom pragma as an annotation to where the control-flow continuation should be inserted. This drastically simplifies the work that scope macros will have to do, since all transformations would have been done before they reaches the scope macros.
as discussed with disruptek
This allows the code to be shared between cps transforms macros.
This is a simple primitive that says: Going from `a` to `b` might not be a linear transition because `a` might be tainted by a cpsJump. It doesn't work on the simple sample yet, got some internal errors.
This keeps the sem-ed result of each replacement separated, preventing weird compiler errors.
Got rid of some crashes but now we got varargs issues.
normalizingRewrites is now the universal tool to turn typed AST into something that's safe to consume. This also makes normalizingRewrites recurse into what it rewrote.
These macros modify call nodes, so they have to be rewritten to force sem to re-evaluate the parameters.
We also normalize the call so that it's safe to remove, a bug discovered on more complex tests.
…inuation Per disruptek's suggestions
sem might flatten the AST so that a tree like this: StmtList IfStmt got passed to those macros as: IfStmt We workaround this issue by putting things in nnkStmtList as needed.
Looks great and I hope we don't have to hold this up with getting anything more than |
cps.nim
Outdated
when false: | ||
# if the child is a cps block (not a call), then push a tailcall | ||
# onto the stack during the saftening of the child | ||
if nc.kind notin unexiter and nc.isCpsBlock and not nc.isCpsCall: | ||
withGoto env.splitAt(n, i, "done"): | ||
result.doc "saftening during done" | ||
result.add env.saften(nc) | ||
# we've completed the split, so we're done here | ||
return | ||
else: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's just get rid of it.
cps.nim
Outdated
else: | ||
# we want to make sure that a pop inside the after body doesn't | ||
# return to after itself, so we don't add it to the goto list... | ||
|
||
let after = splitAt(env, n, i, "afterCall") | ||
result.add: | ||
tailCall(env, env.saften nc): | ||
returnTo after | ||
# include the definition for the after proc | ||
if after.node.kind != nnkSym: | ||
# add the proc definition and declaration without the return | ||
for child in after.node.items: | ||
if child.kind == nnkProcDef: | ||
result.add child | ||
# done! | ||
return |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's just get rid of it.
cps.nim
Outdated
proc replacePending(n, replacement: NimNode): NimNode = | ||
## Replace cpsPending annotations with something else, usually | ||
## a jump to an another location. If `replacement` is nil, remove | ||
## the annotation. | ||
proc resolved(n: NimNode): NimNode = | ||
case n.kind | ||
of nnkPragma: | ||
# a {.cpsPending.} annotation is a standalone pragma statement | ||
if n.len == 1 and n.hasPragma("cpsPending"): | ||
if replacement.isNil: | ||
result = newEmptyNode() | ||
else: | ||
result = copyNimTree replacement | ||
else: discard | ||
|
||
result = filter(n, resolved) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prefer an additional version that takes no replacement argument and does the right thing, versus a version that has special logic to handle a sentinel nil
. The current version should demand that the replacement is not nil.
cps.nim
Outdated
# make `n` safe for modification | ||
let n = normalizingRewrites n | ||
# replace all `pending` with `return nil`, signifying end of continuations | ||
result = replacePending(n, tailCall(nil, nil)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prefer not providing replacePending
a second argument, but failing that...
Prefer a tailCall
that does the right thing with no input... same as my prior feedback; this is very smelly. 😁
|
||
debug(".cpsStripPending.", result, akOriginal, n) | ||
|
||
proc xfrmFloat(n: NimNode): NimNode = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe another rewrite
name like rewriteLift
is more obvious?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lift brings it into the CPS context, more precise than rewrite being flatten?
cps/spec.nim
Outdated
let | ||
info = | ||
if info.isNil: | ||
n | ||
else: | ||
info | ||
lineInfo = info.lineInfoObj | ||
procName = | ||
if info.kind in RoutineNodes: | ||
repr info.name | ||
else: | ||
"" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feel free to make this 3-4 lines.
There is no harm in wrapping multiple StmtList Co-authored-by: Smooth Operator <disruptek@users.noreply.github.com>
The new branch is starting to work well now.
This is no longer necessary now that taste compiles
It's no longer saften business
Named block, simple block, all supported.
differences with previous: - all block statements are rewritten if they contain a cpsJump, regardless of their position in the AST. This is required due to interactions with {.cpsBreak.} - all {.cpsBreak.}, {.cpsContinue.} are now cleaned up on-site if the while/block is not a cps block.
We happened to fix a bug in the process.
The "end of continuation" has been moved into a dedicated proc.
AstKind is now a {.pure.} enum, and we fixes some erroneous annotations
This is a huge simplification of rewrite that we can look to exploit in the future. Annotations creation are also moved to spec.
else: | ||
# {.cpsBreak.} | ||
tail | ||
func matchCpsBreak(label: NimNode): Matcher = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
matcherCpsBreak, since it creates one rather than matches it? Not a blocker, cpsBreakMatcher reads better but the lack of symmetry sucks for grouping related things. I can do this later too.
Really excellent work, @alaviss! 😘 |
This thing is made of hacks, don't use, just look.
scratch.nim
is the simple test that compile.taste
won't compile.