You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
drkameleon opened this issue
May 7, 2024
· 0 comments
Assignees
Labels
bugSomething isn't workingcriticalCritical, top-priority issuesexecutionIssues related to the executor (src/vm/exec.nim):functionopen discussionOpen discussion about features/ideas/suggestionsvmIssues related to the Virtual Machine implementation
Functions are one of the very cases - if not the only one - where Arturo has very tight scoping.
In a few words: any value altered from within a function will not persist, after the function is over. Nor will any new symbol that is defined within it.
a:1f: function[x][
a:2b:3
]
f "something"; `a` is still 1; `b` doesn't exist here
That obviously comes at a price: every time we create a "scope" (at least in the horrible(?) way I've done it so far lol), this leads to far worse performance.
So... for code that is critical and we need a lot of performance (e.g. functions that are to be called thousands or millions of times, recursive functions, etc), it would be great if we get rid of this limitation -- if possible.
And that's how .inline was born. Although I haven't really used it that much, it's an option we may add to a function so that it doesn't have a scope. One obvious reason to use it - and I mean: use it explicitly - would be the case of our previous option .exportable. That is: export all symbols to the outer scope. Why? Because... we may need it.
But that aside, there is another, more obscure side to this same feature: to boost performance.
And you would be thinking that unless you explicitly declare a function like this... e.g. function [x].inline [...], you should be sure that it's a fully scoped function, as usual. Right? Well,... wrong. ⚠️
The truth is the VM tries to be a bit smarter (than it should - most likely) trying to figure out which functions could be considered implicitly.inline. In a few words: is there any way to figure out if a function doesn't need to have a scope created, just "by looking at it"?
My personal bet was :labels. That is: we're looking into a function's body (recursively, if there are subblocks) and once we spot even one label, then the function is marked as non-inlineable (that is: go and create a scope as usual). And the weird logic is that if there are no labels, there should be no new symbols defined in there and thus... why create a scope?
f: function[x][
print"hello"
]
f "something"; here you may think that `f` has its own scope; but in fact it doesn't!; now: does it matter in that case? ; absolutely not ; and no, `x` doesn't go into the equation;; parameters are always scoped ;-)
But the time has come where the "plan" backfired. What if:
we define a symbol within the body of the function with let?
what if we use our new export stdlib function?
Both functions do define/change symbols in the function scope... and - if there was no other label and the function ended up being considered "scopeless" by the VM... automatically - that means that all symbols that were defined in there will simply leak.
a:1f: function[x][
let 'a 2
let 'b 3
]
f "something"; `a` is now 2!; and `b` is... set to 3; (everything leaking!)
Tip
An "obvious" hack here is to forcefully add just one label (e.g. zxczczx: ø) somewhere in the body of a function, to make sure that it won't be scopeless. But obviously, this is ridiculous. I'm just mentioning it here as what it is: a hack.
So... how do we really deal with this?
The obvious way would be to make scoping fast enough so that don't bother whether a function has a scope or not.
The other way would be to add another explicit option to function (pretty much like .inline) which could force a scope, no matter what (e.g. scoped) -- although it's still hackish. On the other hand, unless sb uses things like let or export (which is a different level of use, already - not the average person), I guess we are already talking about a more advanced usage that could imply some knowledge of such corner cases
Another approach (related, or not-so-related): I've been thinking of introducing a new stdlib function that converts a block to "scoped" (e.g. simply do scope [ ... ]) and that would add a tiny switch to a block - and then do, for example, would treat it properly by creating a scope and destroying it after its execution.
Not an easy issue to tackle. But I'm still mentioning it... since a) I would totally forget about it 😛 , b) so that everyone has a clear idea of what is going on, c) to be able to link to all this in case sth like this comes up and not re-explain the whole thing all over again (which I will have forgotten myself by then, anyway... lol).
Needless to say: any ideas or brainstorming in that aspect are more than welcome! 🚀
The text was updated successfully, but these errors were encountered:
bugSomething isn't workingcriticalCritical, top-priority issuesexecutionIssues related to the executor (src/vm/exec.nim):functionopen discussionOpen discussion about features/ideas/suggestionsvmIssues related to the Virtual Machine implementation
Functions are one of the very cases - if not the only one - where Arturo has very tight scoping.
In a few words: any value altered from within a function will not persist, after the function is over. Nor will any new symbol that is defined within it.
That obviously comes at a price: every time we create a "scope" (at least in the horrible(?) way I've done it so far lol), this leads to far worse performance.
So... for code that is critical and we need a lot of performance (e.g. functions that are to be called thousands or millions of times, recursive functions, etc), it would be great if we get rid of this limitation -- if possible.
And that's how
.inline
was born. Although I haven't really used it that much, it's an option we may add to a function so that it doesn't have a scope. One obvious reason to use it - and I mean: use it explicitly - would be the case of our previous option.exportable
. That is: export all symbols to the outer scope. Why? Because... we may need it.But that aside, there is another, more obscure side to this same feature: to boost performance.
And you would be thinking that unless you explicitly declare a function like this... e.g.⚠️
function [x].inline [...]
, you should be sure that it's a fully scoped function, as usual. Right? Well,... wrong.The truth is the VM tries to be a bit smarter (than it should - most likely) trying to figure out which functions could be considered implicitly
.inline
. In a few words: is there any way to figure out if a function doesn't need to have a scope created, just "by looking at it"?My personal bet was
:label
s. That is: we're looking into a function's body (recursively, if there are subblocks) and once we spot even one label, then the function is marked as non-inlineable (that is: go and create a scope as usual). And the weird logic is that if there are no labels, there should be no new symbols defined in there and thus... why create a scope?But the time has come where the "plan" backfired. What if:
let
?export
stdlib function?Both functions do define/change symbols in the function scope... and - if there was no other label and the function ended up being considered "scopeless" by the VM... automatically - that means that all symbols that were defined in there will simply leak.
Tip
An "obvious" hack here is to forcefully add just one label (e.g.
zxczczx: ø
) somewhere in the body of a function, to make sure that it won't be scopeless. But obviously, this is ridiculous. I'm just mentioning it here as what it is: a hack.So... how do we really deal with this?
.inline
) which could force a scope, no matter what (e.g.scoped
) -- although it's still hackish. On the other hand, unless sb uses things likelet
orexport
(which is a different level of use, already - not the average person), I guess we are already talking about a more advanced usage that could imply some knowledge of such corner casesdo scope [ ... ]
) and that would add a tiny switch to a block - and thendo
, for example, would treat it properly by creating a scope and destroying it after its execution.Not an easy issue to tackle. But I'm still mentioning it... since a) I would totally forget about it 😛 , b) so that everyone has a clear idea of what is going on, c) to be able to link to all this in case sth like this comes up and not re-explain the whole thing all over again (which I will have forgotten myself by then, anyway... lol).
Needless to say: any ideas or brainstorming in that aspect are more than welcome! 🚀
The text was updated successfully, but these errors were encountered: