Skip to content

Commit

Permalink
Merge branch 'master' into jishnub/diagaxes
Browse files Browse the repository at this point in the history
  • Loading branch information
jishnub authored Jul 12, 2023
2 parents 8caf51b + 4995d3f commit bfc1980
Show file tree
Hide file tree
Showing 9 changed files with 181 additions and 63 deletions.
4 changes: 2 additions & 2 deletions base/compiler/ssair/passes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1107,8 +1107,8 @@ function sroa_pass!(ir::IRCode, inlining::Union{Nothing,InliningState}=nothing)
end
struct_typ = widenconst(argextype(val, compact))
struct_typ_unwrapped = unwrap_unionall(struct_typ)
if isa(struct_typ, Union) && struct_typ <: Tuple
struct_typ_unwrapped = unswitchtupleunion(struct_typ_unwrapped)
if isa(struct_typ, Union)
struct_typ_unwrapped = unswitchtypeunion(struct_typ_unwrapped)
end
if isa(struct_typ_unwrapped, Union) && is_isdefined
lift_comparison!(isdefined, compact, idx, stmt, lifting_cache, 𝕃ₒ)
Expand Down
45 changes: 26 additions & 19 deletions base/compiler/typeutils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -317,33 +317,40 @@ function unionall_depth(@nospecialize ua) # aka subtype_env_size
return depth
end

# convert a Union of Tuple types to a Tuple of Unions
unswitchtupleunion(u::Union) = unswitchtypeunion(u, Tuple.name)

# convert a Union of same `UnionAll` types to the `UnionAll` type whose parameter is the Unions
function unswitchtypeunion(u::Union, typename::Union{Nothing,Core.TypeName}=nothing)
ts = uniontypes(u)
n = -1
for t in ts
if t isa DataType
if typename === nothing
typename = t.name
elseif typename !== t.name
return u
end
if length(t.parameters) != 0 && !isvarargtype(t.parameters[end])
if n == -1
n = length(t.parameters)
elseif n != length(t.parameters)
return u
end
end
else
t isa DataType || return u
if typename === nothing
typename = t.name
elseif typename !== t.name
return u
end
params = t.parameters
np = length(params)
if np == 0 || isvarargtype(params[end])
return u
end
if n == -1
n = np
elseif n np
return u
end
end
Head = (typename::Core.TypeName).wrapper
unionparams = Any[ Union{Any[(t::DataType).parameters[i] for t in ts]...} for i in 1:n ]
return Head{unionparams...}
hparams = Any[]
for i = 1:n
uparams = Any[]
for t in ts
tpᵢ = (t::DataType).parameters[i]
tpᵢ isa Type || return u
push!(uparams, tpᵢ)
end
push!(hparams, Union{uparams...})
end
return Head{hparams...}
end

function unwraptv_ub(@nospecialize t)
Expand Down
54 changes: 50 additions & 4 deletions doc/src/devdocs/ssair.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,53 @@
# Julia SSA-form IR

Julia uses a static single assignment intermediate representation ([SSA IR](https://en.wikipedia.org/wiki/Static_single-assignment_form)) to perform optimization.
This IR is different from LLVM IR, and unique to Julia.
It allows for Julia specific optimizations.

1. Basic blocks (regions with no control flow) are explicitly annotated.
2. if/else and loops are turned into `goto` statements.
3. lines with multiple operations are split into multiple lines by introducing variables.

For example the following Julia code:
```julia
function foo(x)
y = sin(x)
if x > 5.0
y = y + cos(x)
end
return exp(2) + y
end
```
when called with a `Float64` argument is translated into:

```julia
using InteractiveUtils
@code_typed foo(1.0)
```

```llvm
CodeInfo(
1 ─ %1 = invoke Main.sin(x::Float64)::Float64
│ %2 = Base.lt_float(x, 5.0)::Bool
└── goto #3 if not %2
2 ─ %4 = invoke Main.cos(x::Float64)::Float64
└── %5 = Base.add_float(%1, %4)::Float64
3 ┄ %6 = φ (#2 => %5, #1 => %1)::Float64
│ %7 = Base.add_float(7.38905609893065, %6)::Float64
└── return %7
) => Float64
```

In this example, we can see all of these changes.
1. The first basic block is everything in
```llvm
1 ─ %1 = invoke Main.sin(x::Float64)::Float64
│ %2 = Base.lt_float(x, 5.0)::Bool
└── goto #3 if not %2
```
2. The `if` statement is translated into `goto #3 if not %2` which goes to the 3rd basic block if `x>5` isn't met and otherwise goes to the second basic block.
3. `%2` is an SSA value introduced to represent `x > 5`.

## Background

Beginning in Julia 0.7, parts of the compiler use a new [SSA-form](https://en.wikipedia.org/wiki/Static_single_assignment_form)
Expand All @@ -11,11 +59,9 @@ linearized (i.e. turned into a form where function arguments could only be SSA v
conditional control flow). This negated much of the usefulness of SSA form representation when performing
middle end optimizations. Some heroic effort was put into making these optimizations work without a complete SSA
form representation, but the lack of such a representation ultimately proved prohibitive.
## Categories of IR nodes

## New IR nodes

With the new IR representation, the compiler learned to handle four new IR nodes, Phi nodes, Pi
nodes as well as PhiC nodes and Upsilon nodes (the latter two are only used for exception handling).
The SSA IR representation has four categories of IR nodes: Phi, Pi, PhiC, and Upsilon nodes (the latter two are only used for exception handling).

### Phi nodes and Pi nodes

Expand Down
6 changes: 3 additions & 3 deletions src/jloptions.c
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,8 @@ static const char opts[] =
" interface if supported (Linux and Windows) or to the number of CPU\n"
" threads if not supported (MacOS) or if process affinity is not\n"
" configured, and sets M to 1.\n"
" --gcthreads=M[,N] Use M threads for the mark phase of GC and N (0 or 1) threads for the concurrent sweeping phase of GC.\n"
" M is set to half of the number of compute threads and N is set to 0 if unspecified.\n"
" --gcthreads=N[,M] Use N threads for the mark phase of GC and M (0 or 1) threads for the concurrent sweeping phase of GC.\n"
" N is set to half of the number of compute threads and M is set to 0 if unspecified.\n"
" -p, --procs {N|auto} Integer value N launches N additional local worker processes\n"
" \"auto\" launches as many workers as the number of local CPU threads (logical cores)\n"
" --machine-file <file> Run processes on hosts listed in <file>\n\n"
Expand Down Expand Up @@ -838,7 +838,7 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
char *endptri;
long nsweepthreads = strtol(&endptr[1], &endptri, 10);
if (errno != 0 || endptri == &endptr[1] || *endptri != 0 || nsweepthreads < 0 || nsweepthreads > 1)
jl_errorf("julia: --gcthreads=<n>,<m>; n must be 0 or 1");
jl_errorf("julia: --gcthreads=<n>,<m>; m must be 0 or 1");
jl_options.nsweepthreads = (int8_t)nsweepthreads;
}
break;
Expand Down
33 changes: 24 additions & 9 deletions stdlib/Profile/src/Profile.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1215,14 +1215,16 @@ end
"""
Profile.take_heap_snapshot(io::IOStream, all_one::Bool=false)
Profile.take_heap_snapshot(filepath::String, all_one::Bool=false)
Profile.take_heap_snapshot(all_one::Bool=false)
Profile.take_heap_snapshot(all_one::Bool=false; dir::String)
Write a snapshot of the heap, in the JSON format expected by the Chrome
Devtools Heap Snapshot viewer (.heapsnapshot extension), to a file
(`\$pid_\$timestamp.heapsnapshot`) in the current directory, or the given
file path, or IO stream. If `all_one` is true, then report the size of
every object as one so they can be easily counted. Otherwise, report the
actual size.
Devtools Heap Snapshot viewer (.heapsnapshot extension) to a file
(`\$pid_\$timestamp.heapsnapshot`) in the current directory by default (or tempdir if
the current directory is unwritable), or in `dir` if given, or the given
full file path, or IO stream.
If `all_one` is true, then report the size of every object as one so they can be easily
counted. Otherwise, report the actual size.
"""
function take_heap_snapshot(io::IOStream, all_one::Bool=false)
Base.@_lock_ios(io, ccall(:jl_gc_take_heap_snapshot, Cvoid, (Ptr{Cvoid}, Cchar), io.handle, Cchar(all_one)))
Expand All @@ -1233,9 +1235,22 @@ function take_heap_snapshot(filepath::String, all_one::Bool=false)
end
return filepath
end
function take_heap_snapshot(all_one::Bool=false)
f = joinpath(tempdir(), "$(getpid())_$(time_ns()).heapsnapshot")
return take_heap_snapshot(f, all_one)
function take_heap_snapshot(all_one::Bool=false; dir::Union{Nothing,S}=nothing) where {S <: AbstractString}
fname = "$(getpid())_$(time_ns()).heapsnapshot"
if isnothing(dir)
wd = pwd()
fpath = joinpath(wd, fname)
try
touch(fpath)
rm(fpath; force=true)
catch
@warn "Cannot write to current directory `$(pwd())` so saving heap snapshot to `$(tempdir())`" maxlog=1 _id=Symbol(wd)
fpath = joinpath(tempdir(), fname)
end
else
fpath = joinpath(expanduser(dir), fname)
end
return take_heap_snapshot(fpath, all_one)
end


Expand Down
44 changes: 23 additions & 21 deletions stdlib/REPL/src/REPLCompletions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ function complete_symbol(@nospecialize(ex), name::String, @nospecialize(ffunc),
# as excluding Main.Main.Main, etc., because that's most likely not what
# the user wants
p = let mod=mod, modname=nameof(mod)
s->(!Base.isdeprecated(mod, s) && s != modname && ffunc(mod, s)::Bool)
(s::Symbol) -> !Base.isdeprecated(mod, s) && s != modname && ffunc(mod, s)::Bool
end
# Looking for a binding in a module
if mod == context_module
Expand All @@ -192,25 +192,32 @@ function complete_symbol(@nospecialize(ex), name::String, @nospecialize(ffunc),
end
else
# Looking for a member of a type
if t isa DataType && t != Any
# Check for cases like Type{typeof(+)}
if Base.isType(t)
t = typeof(t.parameters[1])
end
# Only look for fields if this is a concrete type
if isconcretetype(t)
fields = fieldnames(t)
for field in fields
isa(field, Symbol) || continue # Tuple type has ::Int field name
s = string(field)
if startswith(s, name)
push!(suggestions, FieldCompletion(t, field))
end
add_field_completions!(suggestions, name, t)
end
return suggestions
end

function add_field_completions!(suggestions::Vector{Completion}, name::String, @nospecialize(t))
if isa(t, Union)
add_field_completions!(suggestions, name, t.a)
add_field_completions!(suggestions, name, t.b)
elseif t isa DataType && t != Any
# Check for cases like Type{typeof(+)}
if Base.isType(t)
t = typeof(t.parameters[1])
end
# Only look for fields if this is a concrete type
if isconcretetype(t)
fields = fieldnames(t)
for field in fields
isa(field, Symbol) || continue # Tuple type has ::Int field name
s = string(field)
if startswith(s, name)
push!(suggestions, FieldCompletion(t, field))
end
end
end
end
suggestions
end

const sorted_keywords = [
Expand Down Expand Up @@ -580,11 +587,6 @@ function repl_eval_ex(@nospecialize(ex), context_module::Module)

result = frame.result.result
result === Union{} && return nothing # for whatever reason, callers expect this as the Bottom and/or Top type instead
if isa(result, Union)
# unswitch `Union` of same `UnionAll` instances to `UnionAll` of `Union`s
# so that we can use the field information of the `UnionAll`
return CC.unswitchtypeunion(result)
end
return result
end

Expand Down
12 changes: 8 additions & 4 deletions stdlib/REPL/test/replcompletions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1875,11 +1875,15 @@ let s = "`abc`.e"
end

# Test completion for a case when type inference returned `Union` of the same types
function union_somes()
return rand() < 0.5 ? Some(1) : Some(2.)
end
let s = "union_somes()."
union_somes(a, b) = rand() < 0.5 ? Some(a) : Some(b)
let s = "union_somes(1, 1.0)."
c, r, res = test_complete_context(s, @__MODULE__)
@test res
@test "value" in c
end
union_some_ref(a, b) = rand() < 0.5 ? Some(a) : Ref(b)
let s = "union_some_ref(1, 1.0)."
c, r, res = test_complete_context(s, @__MODULE__)
@test res
@test "value" in c && "x" in c
end
15 changes: 14 additions & 1 deletion stdlib/Random/src/misc.jl
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,12 @@ Construct a random cyclic permutation of length `n`. The optional `rng`
argument specifies a random number generator, see [Random Numbers](@ref).
The element type of the result is the same as the type of `n`.
Here, a "cyclic permutation" means that all of the elements lie within
a single cycle. If `n > 0`, there are ``(n-1)!`` possible cyclic permutations,
which are sampled uniformly. If `n == 0`, `randcycle` returns an empty vector.
[`randcycle!`](@ref) is an in-place variant of this function.
!!! compat "Julia 1.1"
In Julia 1.1 and above, `randcycle` returns a vector `v` with
`eltype(v) == typeof(n)` while in Julia 1.0 `eltype(v) == Int`.
Expand All @@ -367,10 +373,16 @@ randcycle(n::Integer) = randcycle(default_rng(), n)
"""
randcycle!([rng=default_rng(),] A::Array{<:Integer})
Construct in `A` a random cyclic permutation of length `length(A)`.
Construct in `A` a random cyclic permutation of length `n = length(A)`.
The optional `rng` argument specifies a random number generator, see
[Random Numbers](@ref).
Here, a "cyclic permutation" means that all of the elements lie within a single cycle.
If `A` is nonempty (`n > 0`), there are ``(n-1)!`` possible cyclic permutations,
which are sampled uniformly. If `A` is empty, `randcycle!` leaves it unchanged.
[`randcycle`](@ref) is a variant of this function that allocates a new vector.
# Examples
```jldoctest
julia> randcycle!(MersenneTwister(1234), Vector{Int}(undef, 6))
Expand All @@ -390,6 +402,7 @@ function randcycle!(r::AbstractRNG, a::Array{<:Integer})
n == 0 && return a
a[1] = 1
mask = 3
# Sattolo's algorithm:
@inbounds for i = 2:n
j = 1 + rand(r, ltm52(i-1, mask))
a[i] = a[j]
Expand Down
31 changes: 31 additions & 0 deletions test/compiler/irpasses.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1390,3 +1390,34 @@ function wrap1_wrap1_wrapper(b, x, y)
end
@test wrap1_wrap1_wrapper(true, 1, 1.0) === 1.0
@test wrap1_wrap1_wrapper(false, 1, 1.0) === 1

# Test unswitching-union optimization within SRO Apass
function sroaunswitchuniontuple(c, x1, x2)
t = c ? (x1,) : (x2,)
return getfield(t, 1)
end
struct SROAUnswitchUnion1{T}
x::T
end
struct SROAUnswitchUnion2{S,T}
x::T
@inline SROAUnswitchUnion2{S}(x::T) where {S,T} = new{S,T}(x)
end
function sroaunswitchunionstruct1(c, x1, x2)
x = c ? SROAUnswitchUnion1(x1) : SROAUnswitchUnion1(x2)
return getfield(x, :x)
end
function sroaunswitchunionstruct2(c, x1, x2)
x = c ? SROAUnswitchUnion2{:a}(x1) : SROAUnswitchUnion2{:a}(x2)
return getfield(x, :x)
end
let src = code_typed1(sroaunswitchuniontuple, Tuple{Bool, Int, Float64})
@test count(isnew, src.code) == 0
@test count(iscall((src, getfield)), src.code) == 0
end
let src = code_typed1(sroaunswitchunionstruct1, Tuple{Bool, Int, Float64})
@test count(isnew, src.code) == 0
@test count(iscall((src, getfield)), src.code) == 0
end
@test sroaunswitchunionstruct2(true, 1, 1.0) === 1
@test sroaunswitchunionstruct2(false, 1, 1.0) === 1.0

0 comments on commit bfc1980

Please sign in to comment.