Menu

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:

  1. All jumps must stay in the same routine. As in C, goto cannot be used to exit a function or enter another one.

  2. Goto cannot jump into the body (block) of a MapI or MapK loop. These iterators place some (currently four) required values on the stack and cannot operate without them. So while a goto can jump into the body of a For loop and happily generate possibly erroneous results, a jump into a MapI block 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:

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; }