Skip to content

Commit f4d6328

Browse files
authored
Merge pull request #172 from JuliaDebug/teh/refresh_docs
Enhance the documentation
2 parents 89cfa73 + 4faf2d8 commit f4d6328

File tree

4 files changed

+236
-40
lines changed

4 files changed

+236
-40
lines changed

docs/make.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using Documenter, JuliaInterpreter, Test, CodeTracking
22

3+
remove() # ensure there are no activate breakpoints
4+
35
makedocs(
46
modules = [JuliaInterpreter],
57
clean = false,

docs/src/ast.md

Lines changed: 116 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,22 @@
1+
!!! note
2+
This page and the next are designed to teach a little more about the internals.
3+
Depending on your interest, you may be able to skip them.
4+
15
# Lowered representation
26

3-
Let's start with a demonstration on simple function:
7+
JuliaInterpreter uses the lowered representation of code.
8+
The key advantage of lowered representation is that it is fairly well circumscribed:
9+
10+
- There are only a limited number of legal statements that can appear in lowered code
11+
- Each statement is "unpacked" to essentially do one thing
12+
- Scoping of variables is simplified via the slot mechanism, described below
13+
- Names are fully resolved by module
14+
- Macros are expanded
15+
16+
[Julia AST](https://docs.julialang.org/en/latest/devdocs/ast/) describes the kinds of
17+
objects that can appear in lowered code.
18+
19+
Let's start with a demonstration on a simple function:
420

521
```julia
622
function summer(A::AbstractArray{T}) where T
@@ -14,69 +30,136 @@ end
1430
A = [1, 2, 5]
1531
```
1632

17-
JuliaInterpreter uses the lowered representation of code:
33+
To interpret lowered representation, it maybe be useful to rewrite the body of `summer` in the following ways.
34+
First let's use an intermediate representation that expands the `for a in A ... end` loop:
35+
36+
```julia
37+
s = zero(T)
38+
temp = iterate(A) # `for` loops get lowered to `iterate/while` loops
39+
while temp !== nothing
40+
a, state = temp
41+
s += a
42+
temp = iterate(A, state)
43+
end
44+
return s
45+
```
46+
47+
The lowered code takes the additional step of resolving the names by module and turning all the
48+
branching into `@goto/@label` equivalents:
1849

1950
```julia
20-
julia> code = @code_lowered summer(A)
51+
# Code starting at line 2 (the first line of the body)
52+
s = Main.zero(T) # T corresponds to the first parameter, i.e., $(Expr(:static_parameter, 1))
53+
54+
# Code starting at line 3
55+
temp = Base.iterate(A) # here temp = @_4
56+
if temp === nothing # this comparison gets stored as %4, and %5 stores !(temp===nothing)
57+
@goto block4
58+
end
59+
60+
@label block2
61+
## BEGIN block2
62+
a, state = temp[1], temp[2] # these correspond to the `getfield` calls, state is %9
63+
64+
# Code starting at line 4
65+
s = s + a
66+
67+
# Code starting at line 5
68+
temp = iterate(A, state) # A is also %2
69+
if temp === nothing
70+
@goto block4 # the `while` condition was false
71+
end
72+
## END block2
73+
74+
@goto block2 # here the `while` condition is still true
75+
76+
# Code starting at line 6
77+
@label block4
78+
## BEGIN block4
79+
return s
80+
## END block4
81+
```
82+
83+
This has very close correspondence to the lowered representation:
84+
85+
```julia
86+
julia> code = @code_lowered debuginfo=:source summer(A)
2187
CodeInfo(
22-
1 ─ s = (Main.zero)($(Expr(:static_parameter, 1)))
88+
@ REPL[1]:2 within `summer'
89+
1 ─ s = Main.zero($(Expr(:static_parameter, 1)))
90+
│ @ REPL[1]:3 within `summer'
2391
│ %2 = A
24-
#temp# = (Base.iterate)(%2)
25-
%4 = #temp# === nothing
26-
%5 = (Base.not_int)(%4)
92+
@_4 = Base.iterate(%2)
93+
│ %4 = @_4 === nothing
94+
│ %5 = Base.not_int(%4)
2795
└── goto #4 if not %5
28-
2%7 = #temp#
29-
│ a = (Core.getfield)(%7, 1)
30-
%9 = (Core.getfield)(%7, 2)
96+
2 ┄ %7 = @_4
97+
│ a = Core.getfield(%7, 1)
98+
│ %9 = Core.getfield(%7, 2)
99+
│ @ REPL[1]:4 within `summer'
31100
│ s = s + a
32-
#temp# = (Base.iterate)(%2, %9)
33-
%12 = #temp# === nothing
34-
%13 = (Base.not_int)(%12)
101+
@_4 = Base.iterate(%2, %9)
102+
%12 = @_4 === nothing
103+
%13 = Base.not_int(%12)
35104
└── goto #4 if not %13
36105
3 ─ goto #2
106+
@ REPL[1]:6 within `summer'
37107
4 ┄ return s
38108
)
39109
```
110+
!!! note
111+
Not all Julia versions support `debuginfo`. If the command above fails for you,
112+
just omit the `debuginfo=:source` portion.
40113

41114
To understand this package's internals, you need to familiarize yourself with these
42-
`CodeInfo` objects. The numbers on the left correspond to [basic blocks](https://en.wikipedia.org/wiki/Basic_block);
43-
when used in statements these are printed with a hash, e.g., in `goto #4 if not %6`, the
115+
`CodeInfo` objects.
116+
The lines that start with `@ REPL[1]:n` indicate the source line of the succeeding
117+
block of statements; here we defined this method in the REPL, so the source file is `REPL[1]`;
118+
the number after the colon is the line number.
119+
120+
The numbers on the left correspond to [basic blocks](https://en.wikipedia.org/wiki/Basic_block),
121+
as we annotated with `@label block2` above.
122+
When used in statements these are printed with a hash, e.g., in `goto #4 if not %5`, the
44123
`#4` refers to basic block 4.
45-
The numbers in the next column--e.g., `%1`, refer to [single static assignment (SSA) values](https://en.wikipedia.org/wiki/Static_single_assignment_form).
124+
The numbers in the next column--e.g., `%2`, refer to
125+
[single static assignment (SSA) values](https://en.wikipedia.org/wiki/Static_single_assignment_form).
46126
Each statement (each line of this printout) corresponds to a single SSA value,
47127
but only those used later in the code are printed using assignment syntax.
48-
Wherever a previous SSA value is used, it's referenced by an `SSAValue` and printed as `%6`;
49-
for example, in `goto #4 if not %6`, the `%6` is the result of evaluating the 6th statement,
50-
which is `(Base.not_int)(%5)`, which in turn refers to the result of statement 5.
51-
Together lines 5 and 6 correspond to `!(#temp# === nothing)`.
52-
(The `#temp#` means that this was a generated variable name not present explicitly in the original source code.)
128+
Wherever a previous SSA value is used, it's referenced by an `SSAValue` and printed as `%5`;
129+
for example, in `goto #4 if not %5`, the `%5` is the result of evaluating the 5th statement,
130+
which is `(Base.not_int)(%4)`, which in turn refers to the result of statement 4.
131+
Finally, temporary variables here are shown as `@_4`; the `_` indicates a *slot*, either
132+
one of the input arguments or a local variable, and the 4 means the 4th one.
133+
Together lines 4 and 5 correspond to `!(@_4 === nothing)`, where `@_4` has been assigned the
134+
result of the call to `iterate` occurring on line 3. (In some Julia versions, this may be printed as `#temp#`,
135+
similar to how we named it in our alternative implementation above.)
53136

54-
Before diving into the details, let's first look at the statements themselves:
137+
Let's look at a couple of the fields of the `CodeInfo`. First, the statements themselves:
55138

56139
```julia
57140
julia> code.code
58141
16-element Array{Any,1}:
59-
:(_3 = (Main.zero)($(Expr(:static_parameter, 1))))
142+
:(_3 = Main.zero($(Expr(:static_parameter, 1))))
60143
:(_2)
61-
:(_4 = (Base.iterate)(%2))
144+
:(_4 = Base.iterate(%2))
62145
:(_4 === nothing)
63-
:((Base.not_int)(%4))
146+
:(Base.not_int(%4))
64147
:(unless %5 goto %16)
65148
:(_4)
66-
:(_5 = (Core.getfield)(%7, 1))
67-
:((Core.getfield)(%7, 2))
149+
:(_5 = Core.getfield(%7, 1))
150+
:(Core.getfield(%7, 2))
68151
:(_3 = _3 + _5)
69-
:(_4 = (Base.iterate)(%2, %9))
152+
:(_4 = Base.iterate(%2, %9))
70153
:(_4 === nothing)
71-
:((Base.not_int)(%12))
154+
:(Base.not_int(%12))
72155
:(unless %13 goto %16)
73156
:(goto %7)
74157
:(return _3)
75158
```
76159

77160
You can see directly that the SSA assignments are implicit; they are not directly
78161
present in the statement list.
79-
The most noteworthy change here is the appearance of objects like `_3`, which are
162+
The most noteworthy change here is the appearance of more objects like `_3`, which are
80163
references that index into local variable slots:
81164

82165
```julia
@@ -85,9 +168,9 @@ julia> code.slotnames
85168
Symbol("#self#")
86169
:A
87170
:s
88-
Symbol("#temp#")
171+
Symbol("")
89172
:a
90173
```
91174

92-
When printing the whole `CodeInfo` object, these `slotnames` are substituted in.
93-
The types of objects that can be in `code.code` is well-described in the [Julia AST](https://docs.julialang.org/en/latest/devdocs/ast/) documentation.
175+
When printing the whole `CodeInfo` object, these `slotnames` are substituted in
176+
(unless they are empty, as was the case for `@_4` above).

docs/src/index.md

Lines changed: 117 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,133 @@ Normally, Julia compiles your code when you first execute it; using JuliaInterpr
55
avoid compilation and execute the expressions that define your code directly.
66
Interpreters have a number of applications, including support for stepping debuggers.
77

8-
At a pure user level, there is not much to know:
8+
## Use an an interpreter
99

10-
```jldoctest
10+
Using this package as an interpreter is straightforward:
11+
12+
```jldoctest demo1
1113
julia> using JuliaInterpreter
1214
13-
julia> a = [1, 2, 5]
15+
julia> list = [1, 2, 5]
1416
3-element Array{Int64,1}:
1517
1
1618
2
1719
5
1820
19-
julia> sum(a)
21+
julia> sum(list)
22+
8
23+
24+
julia> @interpret sum(list)
2025
8
26+
```
27+
28+
## Breakpoints
29+
30+
You can interrupt execution by setting breakpoints.
31+
You can set breakpoints via packages that explicitly target debugging,
32+
like [Juno](http://junolab.org/), [Debugger](https://github.com/JuliaDebug/Debugger.jl), and
33+
[Rebugger](https://github.com/timholy/Rebugger.jl).
34+
But all of these just leverage the core functionality defined in JuliaInterpreter,
35+
so here we'll illustrate it without using any of these other packages.
36+
37+
Let's set a conditional breakpoint, to be triggered any time one of the elements in the
38+
argument to `sum` is bigger than 4:
39+
40+
```jldoctest demo1; filter = r"in Base at .*$"
41+
julia> @breakpoint sum([1, 2]) any(x->x>4, a)
42+
breakpoint(sum(a::AbstractArray) in Base at reducedim.jl:648, line 648)
43+
```
44+
45+
Note that in writing the condition, we used `a`, the name of the argument to the relevant
46+
method of `sum`. Conditionals should be written using a combination of argument and parameter
47+
names of the method into which you're inserting a breakpoint; you can also use any
48+
globally-available name (as used here with the `any` function).
49+
50+
Now let's see what happens:
51+
52+
```jldoctest demo1; filter = r"in Base at .*$"
53+
julia> @interpret sum([1,2,3]) # no element bigger than 4, breakpoint should not trigger
54+
6
55+
56+
julia> frame, bp = @interpret sum([1,2,5]) # should trigger breakpoint
57+
(Frame for sum(a::AbstractArray) in Base at reducedim.jl:648
58+
c 1* 648 1 ─ nothing
59+
2 648 │ %2 = (Base.#sum#550)(Colon(), #self#, a)
60+
3 648 └── return %2
61+
a = [1, 2, 5], breakpoint(sum(a::AbstractArray) in Base at reducedim.jl:648, line 648))
62+
```
2163

22-
julia> @interpret sum(a)
64+
`frame` is described in more detail on the next page; for now, suffice it to say
65+
that the `c` in the leftmost column indicates the presence of a conditional breakpoint
66+
upon entry to `sum`. `bp` is a reference to the breakpoint. You can manipulate these
67+
at the command line:
68+
69+
```jldoctest demo1; filter = r"in Base at .*$"
70+
julia> disable(bp)
71+
false
72+
73+
julia> @interpret sum([1,2,5])
2374
8
75+
76+
julia> enable(bp)
77+
true
78+
79+
julia> @interpret sum([1,2,5])
80+
(Frame for sum(a::AbstractArray) in Base at reducedim.jl:648
81+
c 1* 648 1 ─ nothing
82+
2 648 │ %2 = (Base.#sum#550)(Colon(), #self#, a)
83+
3 648 └── return %2
84+
a = [1, 2, 5], breakpoint(sum(a::AbstractArray) in Base at reducedim.jl:648, line 648))
85+
```
86+
87+
[`disable`](@ref) and [`enable`](@ref) allow you to turn breakpoints off and on without losing any
88+
conditional statements you may have provided; [`remove`](@ref) allows a permanent removal of
89+
the breakpoint. You can use `remove()` to remove all breakpoints in all methods.
90+
91+
[`@breakpoint`](@ref) allows you to optionally specify a line number at which the breakpoint
92+
is to be set. You can also use a functional form, [`breakpoint`](@ref), to specify file/line
93+
combinations or that you want to break on entry to *any* method of a particular function.
94+
At present, note that some of this functionality requires that you be running
95+
[Revise.jl](https://github.com/timholy/Revise.jl).
96+
97+
Finally, you can set breakpoints using [`@bp`](@ref):
98+
99+
```jldoctest demo1
100+
julia> function myfunction(x, y)
101+
a = 1
102+
b = 2
103+
x > 3 && @bp
104+
return a + b + x + y
105+
end
106+
myfunction (generic function with 1 method)
107+
108+
julia> @interpret myfunction(1, 2)
109+
6
110+
111+
julia> @interpret myfunction(5, 6)
112+
(Frame for myfunction(x, y) in Main at none:2
113+
114+
3 4 │ %3 = (>)(x, 3)
115+
4 4 └── goto #3 if not %3
116+
b 5* 4 2 ─ nothing
117+
6 4 └── goto #3
118+
7 5 3 ┄ %7 = (+)(a, b, x, y)
119+
120+
x = 5
121+
y = 6
122+
a = 1
123+
b = 2, breakpoint(myfunction(x, y) in Main at none:2, line 4))
24124
```
25125

26-
Those who want to dive deeper should continue reading.
126+
Here the breakpoint is marked with a `b` indicating that it is an unconditional breakpoint.
127+
Because we placed it inside the condition `x > 3`, we've achieved a conditional outcome.
128+
129+
When using `@bp` in source-code files, the use of Revise is recommended,
130+
since it allows you to add breakpoints, test code, and then remove the breakpoints from the
131+
code without restarting Julia.
132+
133+
## `debug_command`
134+
135+
You can control execution of frames via [`debug_command`](@ref).
136+
Authors of debugging applications should target `debug_command` for their interaction
137+
with JuliaInterpreter.

docs/src/internals.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ as `code = frame.framecode.src`. (It's a slightly modified form of one returned
2424
in that it has been processed by [`JuliaInterpreter.optimize!`](@ref) to speed up run-time execution.)
2525

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

2929
```julia
3030
julia> frame.framedata.locals

0 commit comments

Comments
 (0)