-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
allocate closure env on stack if viable (wip) #14881
Conversation
You need to outline the ideas behind it. Preferable as comment in the code itself. |
You really need to explain the optimization and how it is sound for a precise GC that expects "env" to be a pointer pointing into the GC's heap. |
Consider this code: proc stuff =
var x = 123
proc inner =
x = doSomething(x)
inner() Without the PR it will be transformed into something like this during lambda lifting: proc inner(_env2: ref object) =
_env2.x = doSomething(_env2.x)
proc stuff =
var _env1: ref object # unconditionally a ref object
new(_env1)
_env1.x = 123
var _closure = (inner, env1)
_closure[0](_closure[1]) As you can see the heap allocations here are completely pointless as the env never actually escapes the stack, since inner is just called locally and never passed to some other proc or assigned somewhere. During lambda lifting we can scan What this PR intends to do is transform this simple case into something like this instead, as long as we determined that the env can't escape the stack: proc inner(_env2: ptr object) =
# ^ inner's env param becomes just a regular pointer
_env2.x = doSomething(_env2.x)
proc stuff =
var _env1: object # regular stack object is safe in this case
_env1.x = 123
var _closure = (inner, addr(env1))
_closure[0](_closure[1])
If the env is stack allocated I use BTW: Not strictly related to this PR, but something I ran into - not sure if this is a bug. If a proc sym is gensymmed it will never have the sfAddrTaken flag set, example: import macros
macro makeSomething(name, alias: untyped; useGenSym = false) =
let
s = if useGenSym.boolVal: genSym(nskProc, name.strVal) else: name
return quote do:
proc `s`: int = 1
let `alias` = `s` # sfAddrTaken is present on `s` only if useGenSym is false
proc main =
makeSomething(foobar, baz, true)
echo baz()
makeSomething(foobar2, baz2, false)
echo baz2()
main()
# in cgen:
# proc genProc(m: BModule, prc: PSym) =
# if "foobar" in prc.name.s:
# echo prc, " ", prc.flags
# ...
# outputs something like:
# foobar@123 {sfUsed, sfGenSym}
# foobar2@234 {sfUsed, sfAddrTaken} |
Good idea; definitely watching with interest. 👍 |
Good!
No, that's fine but the compiler needs to be reviewed for all the cases where we assume that
I think we should remove |
Ugh, wasted so much time trying to figure out why some simple tests fail before realizing that
Yeah - now that I've run into even more instances of it not being properly set on syms beyond of what I've mentioned above I see that it has been quite neglected. Guess we're gonna have to do it the hard way. |
see also #14976 which seems related; i was initially using |
This pull request has been automatically marked as stale because it has not had recent activity. If you think it is still a valid PR, please rebase it on the latest devel; otherwise it will be closed. Thank you for your contributions. |
I believe that this PR was more or less working sans for the blocker cause by the still existing VM bug. If it were possible to run the closure codegen separately for VM and C we could simply not generate stack closures on the VM - but I'm not sure how to implement this separation, or whether it is realistic at all, so any hints would be appreciated. And just to recap why I believe implementing this notion of 'stack closures' into the compiler is worthwhile, here are the issues it would address:
proc foo(x: seq[int]) =
proc bar():
echo x[0]
bar() With stack closures
proc foo(x: var seq[int]) =
proc bar() =
x &= 123
bar() Even though this code is safe, currently the compiler will fail with an error complaining that capturing |
Opening a PR to check how it does on the tests.
If no inner proc is actually passed somewhere we can make the closure env a non ref object and pass it to the inner procs as a ptr instead. This should make some common uses of inner procs much more performant. It would also make it valid to capture an outer
var
argument inside non escaping inner procs (not implemented yet).Not sure if the check for this (
anyInnerProcMightEscape
) covers all the cases thoroughly though, so it needs to be reviewed.