Skip to content
This repository has been archived by the owner on Dec 6, 2019. It is now read-only.

Towards a semi-performant recursive interpreter #37

Merged
merged 40 commits into from
Jan 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
786f2bc
Cosmetic changes
timholy Jan 11, 2019
24a0cce
Fix construction from a method
timholy Jan 11, 2019
5b27660
Check for trivial code in maybe_step_through_wrapper
timholy Jan 11, 2019
e0feed2
isdefined->isassigned for array elements
timholy Jan 11, 2019
4ccad0b
Support :pop_exception Exprs in Julia 1.1+
timholy Jan 11, 2019
c4789ce
Temporarily disable UI tests
timholy Jan 11, 2019
971a61e
Support interpreting at top-level module scope
timholy Jan 13, 2019
265c3a6
Add `enter_call(meth, args...; kwargs...)`
timholy Jan 12, 2019
a43f96c
Improve the performance of the interpreter
timholy Jan 12, 2019
5218791
Allow the interpreter to recurse into calls
timholy Jan 14, 2019
1f112b0
Split JuliaStackFrame into two pieces, with code being a singleton
timholy Jan 15, 2019
d85058a
Special-case handling of Core.svec and Core.apply_type
timholy Jan 15, 2019
8449d24
Switch interpreter/compiler decision to a trait-or-stack-based approach
timholy Jan 15, 2019
5c1cc58
Add call-site method tables
timholy Jan 15, 2019
c80d4eb
Switch local method tables to a "parallel" TypeMapEntry lookup
timholy Jan 17, 2019
ccb034d
Avoid runtime dispatch for many builtins
timholy Jan 18, 2019
b72ae86
Re-use old JuliaStackFrames
timholy Jan 18, 2019
0fc6936
A few more performance optimizations
timholy Jan 19, 2019
1887ac9
Ensure that there are no nested call expressions
timholy Jan 19, 2019
aeddf77
Add an interpret macro
timholy Jan 20, 2019
7268b36
Check for explicit calls to Builtins
timholy Jan 20, 2019
5d28f90
Support assigning :gc_preserve_begin exprs
timholy Jan 20, 2019
993bc8a
Work around some bugs in difficult cases
timholy Jan 20, 2019
e28bb0f
Improve module-detection for builtins
timholy Jan 20, 2019
670ed65
Lookup GlobalRefs during optimize!
timholy Jan 21, 2019
5b3695d
Record position prior to entering new frame
timholy Jan 21, 2019
925ec77
For generators, index by argtypes when caching lowered code
timholy Jan 21, 2019
a8610c9
Update the project dependencies
timholy Jan 21, 2019
2672537
Unwrap toplevel expression from parse_input_line
timholy Jan 21, 2019
9663bf7
Pass kwargs through JuliaStackFrame constructor
timholy Jan 21, 2019
e1983f8
Speed up evaluation of %new expressions by ccalling
timholy Jan 22, 2019
7317756
update ui tests
KristofferC Jan 22, 2019
ab0775a
Generate src/builtins.jl at build time
timholy Jan 22, 2019
cdac1e9
Add docstrings for key interpreter methods.
timholy Jan 23, 2019
0be3c37
Add Documenter documentation
timholy Jan 23, 2019
265ae94
Don't offset the SSAValues
timholy Jan 24, 2019
2f2ca9f
Fix line numbers in optimize!
timholy Jan 26, 2019
7d45fbe
Actually copy the code in copy_codeinfo
timholy Jan 26, 2019
0480577
A few improvements useful for external callers
timholy Jan 26, 2019
d03e44e
checkout untagged test-deps in travis script
KristofferC Jan 27, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@
*.jl.mem
expected.out
failed.out
src/builtins.jl
deps/build.log
docs/build/
12 changes: 12 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,17 @@ julia:
- 1.1
- nightly

script:
- julia --project -e 'using Pkg;
Pkg.instantiate();
Pkg.add([PackageSpec(name = "TerminalRegressionTests", rev = "master"),
PackageSpec(name = "VT100", rev = "master")]);
Pkg.build();
Pkg.test()'

matrix:
allow_failures:
- julia: nightly

notifications:
email: false
20 changes: 1 addition & 19 deletions Manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,14 @@ uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
deps = ["REPL"]
git-tree-sha1 = "e4a4693fc3fd3924d469bb0fa46215672fd6d4b8"
repo-rev = "master"
repo-url = "https://github.com/Keno/DebuggerFramework.jl.git"
repo-url = "https://github.com/JuliaDebug/DebuggerFramework.jl.git"
uuid = "67417a49-6d77-5db2-98c7-c13144130cd2"
version = "0.1.2+"

[[Distributed]]
deps = ["Random", "Serialization", "Sockets"]
uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"

[[InteractiveUtils]]
deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"

[[Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"

[[Markdown]]
deps = ["Base64"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"
Expand All @@ -30,16 +23,5 @@ uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"
deps = ["InteractiveUtils", "Markdown", "Sockets"]
uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"

[[Random]]
deps = ["Serialization"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[[Serialization]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"

[[Sockets]]
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"

[[Test]]
deps = ["Distributed", "InteractiveUtils", "Logging", "Random"]
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
5 changes: 4 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ version = "0.1.1"

[deps]
DebuggerFramework = "67417a49-6d77-5db2-98c7-c13144130cd2"
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a"
REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"

[extras]
TerminalRegressionTests = "98bfdc55-cc95-5876-a49a-74609291cbe0"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
VT100 = "7774df62-37c0-5c21-b34d-f6d7f98f54bc"

[targets]
test = ["Test"]
test = ["Test", "TerminalRegressionTests", "VT100"]
7 changes: 7 additions & 0 deletions deps/build.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using InteractiveUtils

const srcpath = joinpath(dirname(@__DIR__), "src")
include(joinpath(srcpath, "generate_builtins.jl"))
open(joinpath(srcpath, "builtins.jl"), "w") do io
generate_builtins(io)
end
20 changes: 20 additions & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Documenter, ASTInterpreter2

makedocs(
modules = [ASTInterpreter2],
clean = false,
format = Documenter.HTML(prettyurls = get(ENV, "CI", nothing) == "true"),
sitename = "ASTInterpreter2.jl",
authors = "Keno Fischer, Tim Holy, and others",
linkcheck = !("skiplinks" in ARGS),
pages = [
"Home" => "index.md",
"ast.md",
"internals.md",
"dev_reference.md",
],
)

deploydocs(
repo = "github.com/JuliaDebug/ASTInterpreter2.jl.git",
)
93 changes: 93 additions & 0 deletions docs/src/ast.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Lowered representation

Let's start with a demonstration on simple function:

```julia
function summer(A::AbstractArray{T}) where T
s = zero(T)
for a in A
s += a
end
return s
end

A = [1, 2, 5]
```

ASTIntepreter2 uses the lowered representation of code:

```julia
julia> code = @code_lowered summer(A)
CodeInfo(
1 ─ s = (Main.zero)($(Expr(:static_parameter, 1)))
│ %2 = A
│ #temp# = (Base.iterate)(%2)
│ %4 = #temp# === nothing
│ %5 = (Base.not_int)(%4)
└── goto #4 if not %5
2 ┄ %7 = #temp#
│ a = (Core.getfield)(%7, 1)
│ %9 = (Core.getfield)(%7, 2)
│ s = s + a
│ #temp# = (Base.iterate)(%2, %9)
│ %12 = #temp# === nothing
│ %13 = (Base.not_int)(%12)
└── goto #4 if not %13
3 ─ goto #2
4 ┄ return s
)
```

To understand this package's internals, you need to familiarize yourself with these
`CodeInfo` objects. The numbers on the left correspond to [basic blocks](https://en.wikipedia.org/wiki/Basic_block);
when used in statements these are printed with a hash, e.g., in `goto #4 if not %6`, the
`#4` refers to basic block 4.
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).
Each statement (each line of this printout) corresponds to a single SSA value,
but only those used later in the code are printed using assignment syntax.
Wherever a previous SSA value is used, it's referenced by an `SSAValue` and printed as `%6`;
for example, in `goto #4 if not %6`, the `%6` is the result of evaluating the 6th statement,
which is `(Base.not_int)(%5)`, which in turn refers to the result of statement 5.
Together lines 5 and 6 correspond to `!(#temp# === nothing)`.
(The `#temp#` means that this was a generated variable name not present explicitly in the original source code.)

Before diving into the details, let's first look at the statements themselves:

```julia
julia> code.code
16-element Array{Any,1}:
:(_3 = (Main.zero)($(Expr(:static_parameter, 1))))
:(_2)
:(_4 = (Base.iterate)(%2))
:(_4 === nothing)
:((Base.not_int)(%4))
:(unless %5 goto %16)
:(_4)
:(_5 = (Core.getfield)(%7, 1))
:((Core.getfield)(%7, 2))
:(_3 = _3 + _5)
:(_4 = (Base.iterate)(%2, %9))
:(_4 === nothing)
:((Base.not_int)(%12))
:(unless %13 goto %16)
:(goto %7)
:(return _3)
```

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

```julia
julia> code.slotnames
5-element Array{Any,1}:
Symbol("#self#")
:A
:s
Symbol("#temp#")
:a
```

When printing the whole `CodeInfo` object, these `slotnames` are substituted in.
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.
55 changes: 55 additions & 0 deletions docs/src/dev_reference.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Function reference

## Top-level

```@docs
@interpret
```

## Frame creation

```@docs
ASTInterpreter2.enter_call
ASTInterpreter2.enter_call_expr
ASTInterpreter2.build_frame
ASTInterpreter2.determine_method_for_expr
ASTInterpreter2.prepare_args
ASTInterpreter2.prepare_call
ASTInterpreter2.get_call_framecode
ASTInterpreter2.optimize!
```

## Frame execution

```@docs
ASTInterpreter2.Compiled
ASTInterpreter2.step_expr!
ASTInterpreter2.finish!
ASTInterpreter2.finish_and_return!
ASTInterpreter2.next_until!
ASTInterpreter2.evaluate_call!
ASTInterpreter2.evaluate_foreigncall!
ASTInterpreter2.maybe_evaluate_builtin
ASTInterpreter2.@eval_rhs
```

## Types

```@docs
ASTInterpreter2.JuliaStackFrame
ASTInterpreter2.JuliaFrameCode
ASTInterpreter2.JuliaProgramCounter
```

## Internal storage

```@docs
ASTInterpreter2.framedict
ASTInterpreter2.genframedict
```

## Utilities

```@docs
ASTInterpreter2.iswrappercall
```
26 changes: 26 additions & 0 deletions docs/src/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# ASTInterpreter2

This package implements an [interpreter](https://en.wikipedia.org/wiki/Interpreter_(computing)) for Julia code.
Normally, Julia compiles your code when you first execute it; using ASTInterpreter2 you can
avoid compilation and execute the expressions that define your code directly.
Interpreters have a number of applications, including support for stepping debuggers.

At a pure user level, there is not much to know:

```jldoctest
julia> using ASTInterpreter2

julia> a = [1, 2, 5]
3-element Array{Int64,1}:
1
2
5

julia> sum(a)
8

julia> @interpret sum(a)
8
```

Those who want to dive deeper should continue reading.
Loading