Loops
By default, Ghost records all function calls as they are executed. If a particular function is executed several times inside of a loop, Ghost will record each execution separately:
using Ghost
function loop1(x, n)
while n > 0
x = 2x
n = n - 1
end
return x
end
_, tape1 = trace(loop1, 2.0, 3)
(16.0, Tape{Dict{Any, Any}}
inp %1::typeof(Main.loop1)
inp %2::Float64
inp %3::Int64
%4 = >(%3, 0)::Bool
%5 = *(2, %2)::Float64
%6 = -(%3, 1)::Int64
%7 = >(%6, 0)::Bool
%8 = *(2, %5)::Float64
%9 = -(%6, 1)::Int64
%10 = >(%9, 0)::Bool
%11 = *(2, %8)::Float64
%12 = -(%9, 1)::Int64
%13 = >(%12, 0)::Bool
)
Ghost also has experimental support for tracing loops as a special Loop
operation which can be turned on using should_trace_loops!(true)
using Ghost
import Ghost: should_trace_loops!
should_trace_loops!(true)
function loop1(x, n)
while n > 0
x = 2x
n = n - 1
end
return x
end
_, tape2 = trace(loop1, 2.0, 3)
(16.0, Tape{Dict{Any, Any}}
inp %1::typeof(Main.loop1)
inp %2::Float64
inp %3::Int64
%4 = Loop(%3, %2)
%5 = getfield(%4, 1)::Float64
)
Unlike fully static tape which always executes as many iterations as there were during the tracing, tape with loops follows the control flow of the original function:
play!(tape1, loop1, 2.0, 3) # ==> 16.0
play!(tape1, loop1, 2.0, 4) # ==> 16.0
play!(tape1, loop1, 2.0, 5) # ==> 16.0
play!(tape2, loop1, 2.0, 3) # ==> 16.0
play!(tape2, loop1, 2.0, 4) # ==> 32.0
play!(tape2, loop1, 2.0, 5) # ==> 64.0
Note that Loop
itself contains a subtape and is quite independent from the outer tape.
tape2[V(4)].subtape
Tape{Dict{Any, Any}}
inp %1::Int64
inp %2::Float64
%3 = >(%1, 0)::Bool
%4 = *(2, %2)::Float64
%5 = -(%1, 1)::Int64
To work correctly, Loop
expects at least one full iteration during both - tracing and execution.