Public API
Tracing
Ghost.trace
— Functiontrace(f, args...; is_primitive, primitives)
Trace function call, produce call value and a Tape.
trace
records to the tape primitive methods and recursively dives into non-primitives. There are 2 ways to tell trace
that a particular method is a primitive:
- provide
is_primitive(sig) -> Bool
function, wheresig
is is a method signature, e.g.Tuple{map(typeof, (f, args...))...}
- provide an iterable
primitives
; in this casetrace
matches all methods of this function
Ghost.is_primitive
— Functionis_primitive(sig)
The default implementation of is_primitive
argument in trace()
. Returns true
if the method with the provided signature is defined in one of the Julia's built-in modules, e.g. Base
, Core
, Broadcast
, etc.
Ghost.call_signature
— Functioncall_signature(fn, args...)
call_signature(tape::Tape, op::Call)
Get a signature of a function call. The obtain signature is suitable for is_primitive(sig)
.
Ghost.__new__
— Function__new__(T, args...)
User-level version of the new()
pseudofunction. Can be used to construct most Julia types, including structs without default constructors, closures, etc.
Variables
Ghost.Variable
— TypeVariable represents a reference to an operation on a tape. Variables can be used to index tape or keep reference to a specific operation on the tape.
Variables (also aliesed as V
) can be:
- free, created as
V(id)
- used for indexing into tape - bound, created as
V(op)
` - used to keep a robust reference to an operation on the tape
Ghost.bound
— FunctionReturned version of the var bound to the tape op
Ghost.rebind!
— Functionrebind!(tape::Tape, op, st::Dict)
rebind!(tape::Tape, st::Dict; from, to)
Rebind all variables according to substitution table. Example:
tape = Tape()
v1, v2 = inputs!(tape, nothing, 3.0, 5.0)
v3 = push!(tape, mkcall(*, v1, 2))
st = Dict(v1.id => v2.id)
rebind!(tape, st)
@assert tape[v3].args[1].id == v2.id
See also: rebind_context!()
Ghost.rebind_context!
— Functionrebind_context!(tape::Tape, st::Dict)
Rebind variables in the tape's context according to substitution table. By default does nothing, but can be overwitten for specific Tape{C}
Tape structure
Ghost.Tape
— TypeLinearized representation of a function execution.
Fields
ops
- vector of operations on the taperesult
- variable pointing to the operation to be used as the resultparent
- parent tape if anymeta
- internal metadatac
- application-specific context
Ghost.AbstractOp
— TypeBase type for operations on a tape
Ghost.Input
— TypeOperation representing input data of a tape
Ghost.Constant
— TypeOperation representing a constant value on a tape
Ghost.Call
— TypeOperation represening function call on tape. Typically, calls are constructed using mkcall
function.
Important fields of a Call{T}:
fn::T
- function or object to be calledargs::Vector
- vector of variables or values used as argumentsval::Any
- the result of the function call
Ghost.Loop
— TypeOperation representing a loop in an computational graph. See the online documentation for details.
Ghost.inputs
— FunctionGet list of a tape input variables
Ghost.inputs!
— FunctionSet values of a tape inputs
Ghost.mkcall
— Functionmkcall(fn, args...; val=missing)
Convenient constructor for Call operation. If val is missing
(default) and call value can be calculated from (bound) variables and constants, they are calculated. To prevent this behavior, set val to some neutral value.
Tape transformations
Base.push!
— Functionpush!(tape::Tape, op::AbstractOp)
Push a new operation to the end of the tape.
Base.insert!
— Functioninsert!(tape::Tape, idx::Integer, ops::AbstractOp...)
Insert new operations into tape starting from position idx.
Base.replace!
— Functionreplace!(tape, op => new_ops; rebind_to=length(new_ops), old_new=Dict())
Replace specified operation with 1 or more other operations, rebind variables in the reminder of the tape to ops[rebind_to].
Operation can be specified directly, by a variable or by ID.
Base.deleteat!
— Functiondeleteat!(tape::Tape, idx; rebind_to = nothing)
Remove tape[V(idx)]
from the tape
. If rebind_to
is not nothing
, then replace all references to V(idx)
with V(rebind_to)
.
idx
may be an index or Variable
/AbstractOp
directly.
Ghost.primitivize!
— Functionprimitivize!(tape::Tape; is_primitive=is_primitive)
Trace non-primitive function calls on a tape and decompose them into a list of corresponding primitive calls.
Example
f(x) = 2x - 1
g(x) = f(x) + 5
tape = Tape()
_, x = inputs!(tape, g, 3.0)
y = push!(tape, mkcall(f, x))
z = push!(tape, mkcall(+, y, 5))
tape.result = z
primitivize!(tape)
# output
Tape{Dict{Any, Any}}
inp %1::typeof(g)
inp %2::Float64
%3 = *(2, %2)::Float64
%4 = -(%3, 1)::Float64
%5 = +(%4, 5)::Float64
Tape execution
Ghost.play!
— Functionplay!(tape::Tape, args...; debug=false)
Execute operations on the tape one by one. If debug=true
, print each operation before execution.
Ghost.compile
— Functioncompile(tape::Tape)
Compile tape into a normal Julia function.
Ghost.to_expr
— Functionto_expr(tape::Tape)
Generate a Julia expression corresponding to the tape.
Loops
Ghost.should_trace_loops!
— Functionshould_trace_loops!(val=false)
Turn on/off loop tracing. Without parameters, resets the flag to the default value
Ghost.should_trace_loops
— Functionshould_trace_loops()
Check the current value of the loop tracing option.
Internal API
The following types and functions might be useful for better understanding of Ghost behavior, but are not part of the public API and may not hold backward compatibility guarantees.
Ghost.FunctionResolver
— TypeDict-like data structure which maps function signature to a value. Unlike real dict, getindex(rsv, sig) returns either exact match, or closest matching function signature. Example:
rsv = FunctionResolver{Symbol}()
rsv[Tuple{typeof(sin), Float64}] = :Float64
rsv[Tuple{typeof(sin), Real}] = :Real
rsv[Tuple{typeof(sin), Number}] = :Number
rsv[Tuple{typeof(sin), Float64}] # ==> :Float64
rsv[Tuple{typeof(sin), Float32}] # ==> :Real
Ghost.Frame
— TypeFrame of a call stack
Ghost.TracerOptions
— TypeTracer options. Configured globally via the following methods:
- shouldtraceloops!()
- shouldassertbranches!()
Ghost._LoopEnd
— TypePseudo op to designate loop end. Removed after Loop op is created
Ghost.record_or_recurse!
— FunctionRecord function call onto a tape or recurse into it.
Params:
- t::IRTracer - current tracer
- res_id::Int - IR ID of the operation
- farg_irvars - IR variables of the operation
- fargs - values of the operation
Ghost.push_frame!
— FunctionPush a new call frame to tracer, setting function params accordingly
Ghost.pop_frame!
— FunctionPop call frame from tracer
Ghost.enter_loop!
— FunctionTrigger loop start operations.
Arguments:
- t :: IRTracer Current tracer
- loop_id :: Int Unique ID of a loop being entered
- loopinputir_ids :: Vector{Int} IR IDs of variables which will be used as loop inputs. Includes loop block inputs and any outside IDs
This function is added to the very beginning of the loop block(s). During the first iteration we initialize a subtape which will be used later to create the Loop operation on the parent tape. Since all iterations of the loop produce identical operations, we only need to trace it once. However, it turns out to be easier to record all iterations (seprated by a special _LoopEnd op) and then prune unused iterations.
Another important detail is that Loop's subtape is a completely valid and independent tape with its own call frame and inputs which include all explicit and implicit inputs to the loop's block in the original IR.
Ghost.stop_loop_tracing!
— FunctionSet flags designating the end of the first run of the loop.
Ghost.exit_loop!
— FunctionTrigget loop end operations.
This function is added just before the end of the loop block. Since we record all iterations of the loop, we must remember tape IDs of continuation condition and exit variables during the first run.
Index
Ghost.AbstractOp
Ghost.Call
Ghost.Constant
Ghost.Frame
Ghost.FunctionResolver
Ghost.Input
Ghost.Loop
Ghost.Tape
Ghost.TracerOptions
Ghost.Variable
Ghost._LoopEnd
Base.deleteat!
Base.insert!
Base.push!
Base.replace!
Ghost.__new__
Ghost.bound
Ghost.call_signature
Ghost.compile
Ghost.enter_loop!
Ghost.exit_loop!
Ghost.inputs
Ghost.inputs!
Ghost.is_primitive
Ghost.mkcall
Ghost.play!
Ghost.pop_frame!
Ghost.primitivize!
Ghost.push_frame!
Ghost.rebind!
Ghost.rebind_context!
Ghost.record_or_recurse!
Ghost.should_trace_loops
Ghost.should_trace_loops!
Ghost.stop_loop_tracing!
Ghost.to_expr
Ghost.trace