So that (*codegen).Visit is able to omit code generation for these
unused global vars. The most tricky part is to detect unused global
variables, it is done in several steps:
1. Collect the set of named used/unused global vars.
2. Collect the set of globally declared expressions that contain
function calls.
3. Pick up global vars from the set made at step 2.
4. Traverse used functions and puck up those global vars that are used
from these functions.
5. Rename all globals that are presented in the set made at step 1
but are not presented in the set made on step 3 or step 4.
Move all auxiliary function declaration after Main, so that INITSLOT
instructions counter works properly. `vmAndCompileInterop` loads program
and moves nextIP to the Main function offset if there's no _init
function. If _init is there, then nextIP will be moved to the start of
_init. In TestInline we don't handle instructions properly (CALL/JMP
don't change nextIP), we just perform instruction traversal from the
start point via Next(), thus INITSLOT counter value depends on the
starting instruction, which depends on _init presence.
If variable is unnamed and does not contain function call then it's
treated as unused and code generation may be omitted for it
initialization/declaration.
In case there are no returns in the inlined function, jumps point to the
next instruction and can be omitted. This optimization can be extended
to handle other cases, here we just make sure that already existing code
stays the same.
Signed-off-by: Evgeniy Stratonikov <evgeniy@nspcc.ru>
c.funcs contains function names using base types, while methods can be defined
on pointers and the value returned from c.getFuncNameFromSelector will have an
asterisk. We can't have the same name used for (*T) and (T) methods, so just
stripping the asterisk allows to get the right one.
Notice that this doesn't differentiate between (*T) and (T) receivers always
treating them as is. But we have the same problem with arguments now and the
number of inlined calls is limited, usually we want this behavior.
This has the drawback of traversing expression each time the argument is
used. However this is not the case in our syscall/native wrappers.
The old behaviour can be restored by explicit local assignment.
Create local variables as they are needed and remove `INITSLOT`
if there were no locals. This helps to eliminate a whole class
of bugs when calculated and real amount mismatched.
Selector here is either a struct field access or global package
variable/constant. While technically none of these require an additional
local, inlining actually uses one, so add a test for it.
Some arguments can be inlined functions themselves thus requiring additional
attention. Otherwise we can get less local variables than really used by
STLOCs (and subsequent program crash).
Consider function call `f(1, g(2, 3))` when
both `f` and `g` are inlined. If `f` contains some locals,
inlining `g` will replace them with it's another locals map,
because slices in Go reuse storage on `append`.
Thus scope needs to be copied.
Some control-flow statements drop stack items, for example
`return` when it is used inside of `range` loop.
For inlined calls this `return` should drop only portion of stack
which belongs to inlined call.
When function call-site parameter is an identifier,
we may load it directly. Currently it can be also modified,
this will be fixed in a separate commit.