# Internals

## Basic usage

The process of executing code in the interpreter is to prepare a `frame` and then
evaluate these statements one-by-one, branching via the `goto` statements as appropriate.
Using the `summer` example described in [Lowered representation](@ref),
let's build a frame:

```julia-repl
julia> frame = JuliaInterpreter.enter_call(summer, A)
Frame for summer(A::AbstractArray{T,N} where N) where T in Main at REPL[2]:2
   1* 2  1 ─       s = (zero)($(Expr(:static_parameter, 1)))
   2  3  │   %2  = A
   3  3  │         #temp# = (iterate)(%2)
⋮
A = [1, 2, 5]
T = Int64
```

This is a [`Frame`](@ref). Only a portion of the `CodeInfo` is shown, a small region surrounding
the current statement (marked with `*` or in yellow text). The full `CodeInfo` can be extracted
as `code = frame.framecode.src`. (It's a slightly modified form of one returned by `@code_lowered`,
in that it has been processed by [`JuliaInterpreter.optimize!`](@ref) to speed up run-time execution.)

`frame` has another field, `framedata`, that holds values needed for or generated by execution.
The input arguments and local variables are in `locals`:

```julia-repl
julia> frame.framedata.locals
5-element Vector{Union{Nothing, Some{Any}}}:
 Some(summer)
 Some([1, 2, 5])
 nothing
 nothing
 nothing
```

These correspond to the `code.slotnames`; the first is the `#self#` argument and the second
is the input array. The remaining local variables (e.g., `s` and `a`), have not yet been assigned---we've
only built the frame, but we haven't yet begun to execute it.
The static parameter, `T`, is stored in `frame.framedata.sparams`:

```julia-repl
julia> frame.framedata.sparams
1-element Vector{Any}:
 Int64
```

The `Expr(:static_parameter, 1)` statement refers to this value.

The other main storage is for the generated SSA values:

```julia-repl
julia> frame.framedata.ssavalues
16-element Vector{Any}:
 #undef
 #undef
 #undef
 #undef
 #undef
 #undef
 #undef
 #undef
 #undef
 #undef
 #undef
 #undef
 #undef
 #undef
 #undef
 #undef
```

Since we haven't executed any statements yet, these are all undefined.

The other main entity is the so-called [program counter](https://en.wikipedia.org/wiki/Program_counter),
which just indicates the next statement to be executed:

```julia-repl
julia> frame.pc
1
```

Let's try executing the first statement:

```julia-repl
julia> JuliaInterpreter.step_expr!(frame)
2
```

This indicates that it ran statement 1 and is prepared to run statement 2.
(It's worth noting that the first line included a `call` to `zero`, so behind the scenes
JuliaInterpreter created a new frame for `zero`, executed all the statements, and then popped
back to `frame`.)
Since the first statement is an assignment of a local variable, let's check the
locals again:

```julia-repl
julia> frame.framedata.locals
5-element Vector{Union{Nothing, Some{Any}}}:
 Some(summer)
 Some([1, 2, 5])
 Some(0)
 nothing
 nothing
```

You can see that the entry corresponding to `s` has been initialized.

The next statement just retrieves one of the slots (the input argument `A`) and stores
it in an SSA value:

```julia-repl
julia> JuliaInterpreter.step_expr!(frame)
3

julia> frame.framedata.ssavalues
16-element Vector{Any}:
 #undef
    [1, 2, 5]
 #undef
 #undef
 #undef
 #undef
 #undef
 #undef
 #undef
 #undef
 #undef
 #undef
 #undef
 #undef
 #undef
 #undef
```

One can easily continue this until execution completes, which is indicated when `step_expr!`
returns `nothing`. Alternatively, use the higher-level `JuliaInterpreter.finish!(frame)`
to step through the entire frame,
or `JuliaInterpreter.finish_and_return!(frame)` to also obtain the return value.

## More complex expressions

Sometimes you might have a whole sequence of expressions you want to run.
In such cases, your first thought should be to construct the `Frame` manually.
Here's a demonstration:

```@setup internals
using JuliaInterpreter;
JuliaInterpreter.clear_caches()
```

```@repl internals
using Test

ex = quote
    x, y = 1, 2
    @test x + y == 3
end;

frame = Frame(Main, ex);

JuliaInterpreter.finish_and_return!(frame)
```

## Toplevel code and world age

Code that defines new `struct`s, new methods, or new modules is a bit more complicated
and requires special handling. In such cases, calling `finish_and_return!` on a frame that
defines these new objects and then calls them can trigger a
[world age error](https://docs.julialang.org/en/v1/manual/methods/#Redefining-Methods-1),
in which the method is considered to be too new to be run by the currently compiled code.
While one can resolve this by using `Base.invokelatest`, we'd have to use that strategy
throughout the entire package.  This would cause a major reduction in performance.
To resolve this issue without leading to performance problems, care is required to
return to "top level" after defining such objects. This leads to altered syntax for executing
such expressions.

Here's a demonstration of the problem:

```julia-repl
julia> ex = :(map(x->x^2, [1, 2, 3]));

julia> frame = Frame(Main, ex);

julia> JuliaInterpreter.finish_and_return!(frame)
ERROR: this frame needs to be run a top level
```

The reason for this error becomes clearer if we examine `frame` or look directly at the lowered code:

```@setup world-age-example
ex = :(map(x->x^2, [1, 2, 3]))
```

```@repl world-age-example
Meta.lower(Main, ex)
```

All of the code before the `%7` line is devoted to defining the anonymous function `x->x^2`:
it creates a new "anonymous type" (here written as `var"#3#4"`), and then defines a "call
function" for this type, equivalent to `(var"#3#4")(x) = x^2`.

In some cases one can fix this simply by indicating that we want to run this frame at top level:

```julia-repl
julia> JuliaInterpreter.finish_and_return!(frame, true)
3-element Vector{Int64}:
 1
 4
 9
```

In other cases, such as nested calls of new methods, you may need to allow the world age to update
between evaluations. In such cases you want to use `ExprSplitter`:

```julia
for (mod, e) in ExprSplitter(Main, ex)
    frame = Frame(mod, e)
    while true
        JuliaInterpreter.through_methoddef_or_done!(frame) === nothing && break
    end
    JuliaInterpreter.get_return(frame)
end
```

This splits the expression into a sequence of frames (here just one, but more complex blocks may be split up into many).
Then, each frame is executed until it finishes defining a new method, then returns to top level.
The return to top level causes an update in the world age.
If the frame hasn't been finished yet (if the return value wasn't `nothing`),
this continues executing where it left off.

(Incidentally, `JuliaInterpreter.enter_call(map, x->x^2, [1, 2, 3])` works fine on its own,
because the anonymous function is defined by the caller---you'll see that the created frame
is very simple.)
