scScript Goto
Just like C, scScript supports the use of goto operators.
While it is easy to turn a structured routine into unreadable spaghetti
with indiscriminate use, goto jumps, when called for, can
also make the code much cleaner and simpler. The C source code for
scScript proudly uses very many goto operators!
In case there were any questions: judicious goto jumps are great, camel-case is clearly superior, and object-oriented is a horrible evil.
scScript imposes 2 limitations on its goto operators:
- All jumps must stay in the same routine. As in C,
gotocannot be used to exit a function or enter another one. Gotocannot jump into the body (block) of aMapIorMapKloop. These iterators place some (currently four) required values on the stack and cannot operate without them. So while agotocan jump into the body of aForloop and happily generate possibly erroneous results, a jump into aMapIblock will result in a crash and is prohibited!
Interestingly, jumps over one or
more Map blocks are perfectly fine. The compiler will also
correctly process goto jumps out of (even multiple
nested) Map loops. These require popping off the extra
stack values. In fact, the compiler initially places a
placeholder No-op bytecode before every goto
Jmp just in case it needs to add an extra PopN later
in the assembly phase.
Argument Execution Order
scScript supports three operative forms: Functions, IFIDs, and Operators. All three return a value that can be stored/assigned and are able to accept extraneous operands (albeit with the help of comma for operators). It is therefore possible to write contrived, unreadable, and degenerate code where these operands have mutual side-effects.
For example: (bad code!)
MyArr[I] = MyFunc(A, B, C, I += 3); or
MyDict[K] = GetRC(K = SomeArr[I+=2]);
The relative execution order of the (left) assignment arg, expected In args, and extraneous In args should not be relied upon and may in fact differ between the three forms! While execution order can be standardized, the extra cost (in code size, runtime speed, and testing) to harmonize degenerate code across operative forms does not seem worthwhile. A better approach is to avoid writing such bad code.
Tail Call Optimization
Each function call in scScript will normally create a new RunRecord
(same concept as stack frame in C) that points to its caller's
RunRecord. These data structures (allocated
on Chunk slabs) will hold all local variables, call
parameters, and registers. So a recursion that goes 10,000 levels deep
will normally require a chain of 10,000 such RunRecords.
But it makes no sense to maintain all this if the recursive calls (or
any call for that matter) is the very last step; so no
local variable, call parameter, or register will be needed from the
caller during the return. To that end, scScript detects tail
calls and recycles the current RunRecord for the next
call. So instead of the long chain, and megabytes of RAM used, there
will only be a single RunRecord!
Tail call optimization can only work if the script function ends with:
SomeFunc(...);Return SomeFunc(...);orX = SomeFunc(...); Return X;
Note: X must be a caged local. It cannot be
an out parameter, aliased to a global, or stashed in a closure,
as those have non-local (escaped) side-effects and must be set
(by the caller) after SomeFunc returns. X can also
not be an array elt, as it may escape when the array does (better safe
than sorry)!!
scScript is even smart enough to handle the X = SomeFunc
case above if a jmp bytecode intervenes, as tail calls are
identified at runtime! The jmp could be the result of a
hard coded Goto or the function call occurring in a
conditional (If/Else) clause that leads to the end.
(scScript also automatically collapses multi-step unconditional hops
into a single jump.)
However, the caller's RunRecord cannot be eliminated if it
obtains an RC (whether through Submit
or GetRC()) or if the tail call to SomeFunc is
followed by Return; Return Y; or any other
operation!
The Return; on its own is simply shorthand
for Return 0; which is really no different
than Return 42. This requires the function to
push 0 (or 42) on the stack upon returning
from the recursive call. While seemingly trivial, this simple act
precludes eliminating the function's RunRecord from the run
chain! The caller's RunRecord can only be eliminated if it does
absolutely nothing after the call returns.
Just to be clear, the following will optimize the call to
MyFunc, but only as long as X is caged!
Function MyFunc(In1)()
{ Var X;
... blah blah blah....
if (SomeCondition...) {
... Some things...
X = MyFunc(++In1);
} else {
... Other things...
}
return X;
}