Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: aviatesk/JET.jl
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v0.8.4
Choose a base ref
...
head repository: aviatesk/JET.jl
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v0.8.5
Choose a head ref
  • 5 commits
  • 7 files changed
  • 1 contributor

Commits on Jun 29, 2023

  1. fix CHANGELOG a bit

    aviatesk committed Jun 29, 2023
    Copy the full SHA
    8190407 View commit details

Commits on Jun 30, 2023

  1. remove NativeRemark

    aviatesk committed Jun 30, 2023
    Copy the full SHA
    b1bbe55 View commit details
  2. simplify overload

    aviatesk committed Jun 30, 2023
    Copy the full SHA
    d604637 View commit details

Commits on Jul 3, 2023

  1. Copy the full SHA
    c5f5285 View commit details
  2. version 0.8.5

    aviatesk committed Jul 3, 2023
    2
    Copy the full SHA
    3dda7cf View commit details
Showing with 69 additions and 77 deletions.
  1. +10 −2 CHANGELOG.md
  2. +1 −1 Project.toml
  3. +1 −1 src/JET.jl
  4. +1 −19 src/abstractinterpret/abstractanalyzer.jl
  5. +30 −46 src/abstractinterpret/typeinfer.jl
  6. +18 −8 src/analyzers/jetanalyzer.jl
  7. +8 −0 test/toplevel/test_virtualprocess.jl
12 changes: 10 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.8.5]

### Fixed

- Fixed `report_package` so that it does not produce noisy error reports from reducing on
potentially empty collections.

## [0.8.4]

### Added
@@ -34,7 +41,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
for function definitions like:
```julia
struct MyToken end
ismytoken(x) = x == MyToken()
ismytoken(x) = x == MyToken() ? true : false
```
This error is arguably just noise when the target package does not handle `missing`.
`report_package` is designed as an entry point for easy analysis, even at the cost of
@@ -186,7 +193,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
<!-- links -->
[unreleased]: https://github.com/aviatesk/JET.jl/compare/v0.8.0...HEAD
[unreleased]: https://github.com/aviatesk/JET.jl/compare/v0.8.5...HEAD
[0.8.5]: https://github.com/aviatesk/JET.jl/compare/v0.8.4...v0.8.5
[0.8.4]: https://github.com/aviatesk/JET.jl/compare/v0.8.3...v0.8.4
[0.8.3]: https://github.com/aviatesk/JET.jl/compare/v0.8.2...v0.8.3
[0.8.2]: https://github.com/aviatesk/JET.jl/compare/v0.8.1...v0.8.2
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "JET"
uuid = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b"
authors = ["Shuhei Kadowaki <aviatesk@gmail.com>"]
version = "0.8.4"
version = "0.8.5"

[deps]
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
2 changes: 1 addition & 1 deletion src/JET.jl
Original file line number Diff line number Diff line change
@@ -42,7 +42,7 @@ import .CC:
#= tfuncs.jl =#
builtin_tfunction, return_type_tfunc,
#= abstractinterpretation.jl =#
abstract_call, abstract_call_gf_by_type, abstract_call_method,
abstract_call_known, abstract_call_gf_by_type, abstract_call_method,
abstract_call_method_with_const_args, abstract_eval_value_expr, abstract_eval_special_value,
abstract_eval_statement, abstract_eval_value, abstract_invoke, add_call_backedges!,
concrete_eval_call, concrete_eval_eligible, const_prop_entry_heuristic, from_interprocedural!,
20 changes: 1 addition & 19 deletions src/abstractinterpret/abstractanalyzer.jl
Original file line number Diff line number Diff line change
@@ -442,9 +442,7 @@ end
# their own report pass)
# otherwise, it means malformed report pass call, and we should inform users of it
function (rp::ReportPass)(T#=::Type{<:InferenceErrorReport}=#, @nospecialize(args...))
if !(T === NativeRemark ||
T === InvalidConstantRedefinition ||
T === InvalidConstantDeclaration)
if !(T === InvalidConstantRedefinition || T === InvalidConstantDeclaration)
throw(MethodError(rp, (T, args...)))
end
return false
@@ -598,22 +596,6 @@ CC.InferenceParams(analyzer::AbstractAnalyzer) = get_inf_params(analyzer)
CC.OptimizationParams(analyzer::AbstractAnalyzer) = get_opt_params(analyzer)
CC.get_world_counter(analyzer::AbstractAnalyzer) = get_world(analyzer)

"""
NativeRemark <: InferenceErrorReport
This special `InferenceErrorReport` wraps remarks by the default abstract interpretation.
"remarks" are information that Julia's native compiler emits about how its type inference goes,
and those remarks are less interesting in term of "error checking", so currently any of JET's
pre-defined report passes doesn't make any use of `NativeRemark`.
"""
@jetreport struct NativeRemark <: InferenceErrorReport
s::String
end
function print_report_message(io::IO, (; s)::NativeRemark)
print(io, s)
end
CC.add_remark!(analyzer::AbstractAnalyzer, sv::InferenceState, s) = ReportPass(analyzer)(NativeRemark, sv, s) # ignored by default

CC.may_compress(analyzer::AbstractAnalyzer) = false
CC.may_discard_trees(analyzer::AbstractAnalyzer) = false

76 changes: 30 additions & 46 deletions src/abstractinterpret/typeinfer.jl
Original file line number Diff line number Diff line change
@@ -82,29 +82,16 @@ let # overload `concrete_eval_call`
end
end

let # overload `abstract_call`
@static if hasfield(InferenceParams, :max_methods) # VERSION ≥ v"1.10.0-DEV.105"
sigs_ex = :(analyzer::AbstractAnalyzer, arginfo::ArgInfo, si::StmtInfo, sv::InferenceState,
$(Expr(:kw, :(max_methods::Int), :(InferenceParams(analyzer).max_methods))))
args_ex = :(analyzer::AbstractInterpreter, arginfo::ArgInfo, si::StmtInfo, sv::InferenceState,
max_methods::Int)
argtypes_ex = :(arginfo.argtypes)
else
sigs_ex = :(analyzer::AbstractAnalyzer, arginfo::ArgInfo, si::StmtInfo, sv::InferenceState,
$(Expr(:kw, :(max_methods::Int), :(InferenceParams(analyzer).MAX_METHODS))))
args_ex = :(analyzer::AbstractInterpreter, arginfo::ArgInfo, si::StmtInfo, sv::InferenceState,
max_methods::Int)
argtypes_ex = :(arginfo.argtypes)
end
@eval function CC.abstract_call($(sigs_ex.args...))
ret = @invoke CC.abstract_call($(args_ex.args...))
analyze_task_parallel_code!(analyzer, $(argtypes_ex), sv)
return ret
end
function CC.abstract_call_known(analyzer::AbstractAnalyzer,
@nospecialize(f), arginfo::ArgInfo, si::StmtInfo, sv::InferenceState, max_methods::Int)
ret = @invoke CC.abstract_call_known(analyzer::AbstractInterpreter,
f::Any, arginfo::ArgInfo, si::StmtInfo, sv::InferenceState, max_methods::Int)
analyze_task_parallel_code!(analyzer, f, arginfo, sv)
return ret
end

"""
analyze_task_parallel_code!(analyzer::AbstractAnalyzer, argtypes::Argtypes, sv::InferenceState)
analyze_task_parallel_code!(analyzer::AbstractAnalyzer, arginfo::ArgInfo, sv::InferenceState)
Adds special cased analysis pass for task parallelism.
In Julia's task parallelism implementation, parallel code is represented as closure and it's
@@ -120,37 +107,33 @@ See also: <https://github.com/aviatesk/JET.jl/issues/114>
track <https://github.com/JuliaLang/julia/pull/39773> for the changes in native abstract
interpretation routine.
"""
function analyze_task_parallel_code!(analyzer::AbstractAnalyzer, argtypes::Argtypes, sv::InferenceState)
f = singleton_type(argtypes[1])

function analyze_task_parallel_code!(analyzer::AbstractAnalyzer,
@nospecialize(f), arginfo::ArgInfo, sv::InferenceState)
# TODO we should analyze a closure wrapped in a `Task` only when it's `schedule`d
# But the `Task` construction may not happen in the same frame where it's `schedule`d
# and so we may not be able to access to the closure at that point.
# As a compromise, here we invoke the additional analysis on `Task` construction,
# regardless of whether it's really `schedule`d or not.
if f === Task && length(argtypes) 2
v = argtypes[2]
if v Function
# if we encounter `Task(::Function)`,
# try to get its inner function and run analysis on it:
# the closure can be a nullary lambda that really doesn't depend on
# the captured environment, and in that case we can retrieve it as
# a function object, otherwise we will try to retrieve the type of the closure
ft = nothing
if isa(v, Const)
ft = Core.Typeof(v.val)
elseif isa(v, Core.PartialStruct)
ft = v.typ
elseif isa(v, DataType)
ft = v
end
if ft !== nothing
analyze_additional_pass_by_type!(analyzer, Tuple{ft}, sv)
end
end
f === Task || return nothing
argtypes = arginfo.argtypes
length(argtypes) 2 || return nothing
v = argtypes[2]
v Function || return nothing
# if we encounter `Task(::Function)`,
# try to get its inner function and run analysis on it:
# the closure can be a nullary lambda that really doesn't depend on
# the captured environment, and in that case we can retrieve it as
# a function object, otherwise we will try to retrieve the type of the closure
if isa(v, Const)
ft = Core.Typeof(v.val)
elseif isa(v, Core.PartialStruct)
ft = v.typ
elseif isa(v, DataType)
ft = v
else
return nothing
end

return nothing
analyze_additional_pass_by_type!(analyzer, Tuple{ft}, sv)
end

# run additional interpretation with a new analyzer
@@ -164,7 +147,8 @@ function analyze_additional_pass_by_type!(analyzer::AbstractAnalyzer, @nospecial
# the threaded code block as a usual code block, and thus the side-effects won't (hopefully)
# confuse the abstract interpretation, which is supposed to terminate on any kind of code
match = find_single_match(tt, newanalyzer)
abstract_call_method(newanalyzer, match.method, match.spec_types, match.sparams, #=hardlimit=#false, #=si=#StmtInfo(false), sv)
abstract_call_method(newanalyzer, match.method, match.spec_types, match.sparams,
#=hardlimit=#false, #=si=#StmtInfo(false), sv)

return nothing
end
26 changes: 18 additions & 8 deletions src/analyzers/jetanalyzer.jl
Original file line number Diff line number Diff line change
@@ -166,6 +166,12 @@ A typo detection pass.
struct TypoPass <: ReportPass end
(::TypoPass)(@nospecialize _...) = return false # ignore everything except UndefVarErrorReport and field error report

# A report pass that is used for `analyze_from_definitions!`.
# Especially, this report pass ignores `UncaughtExceptionReport` to avoid false positives
# from methods that are intentionally written to throw errors.
struct DefinitionAnalysisPass <: ReportPass end
(::DefinitionAnalysisPass)(@nospecialize args...) = BasicPass()(args...)

# overlay method table
# ====================

@@ -569,6 +575,16 @@ function (::BasicPass)(::Type{UncaughtExceptionReport}, analyzer::JETAnalyzer, f
end
return false
end
function (::DefinitionAnalysisPass)(::Type{UncaughtExceptionReport}, analyzer::JETAnalyzer, frame::InferenceState, stmts::Vector{Any})
# the non-`Bottom` result may mean `throw` calls from the children frames
# (if exists) are caught and not propagated here
# we don't want to cache the caught `UncaughtExceptionReport`s for this frame and
# its parents, and just filter them away now
filter!(get_reports(analyzer, frame.result)) do @nospecialize(report::InferenceErrorReport)
return !isa(report, UncaughtExceptionReport)
end
return false
end
(::SoundPass)(::Type{UncaughtExceptionReport}, analyzer::JETAnalyzer, frame::InferenceState, stmts::Vector{Any}) =
report_uncaught_exceptions!(analyzer, frame, stmts) # yes, you want tons of false positives !
function report_uncaught_exceptions!(analyzer::JETAnalyzer, frame::InferenceState, stmts::Vector{Any})
@@ -1091,6 +1107,8 @@ end
basic_filter(analyzer, sv) && report_serious_exception!(analyzer, sv, argtypes)
(::SoundPass)(::Type{SeriousExceptionReport}, analyzer::JETAnalyzer, sv::InferenceState, argtypes::Argtypes) =
report_serious_exception!(analyzer, sv, argtypes) # any (non-serious) `throw` calls will be caught by the report pass for `UncaughtExceptionReport`
(::DefinitionAnalysisPass)(::Type{SeriousExceptionReport}, analyzer::JETAnalyzer, sv::InferenceState, argtypes::Argtypes) =
false
function report_serious_exception!(analyzer::JETAnalyzer, sv::InferenceState, argtypes::Argtypes)
if length(argtypes) 1
a = first(argtypes)
@@ -1435,14 +1453,6 @@ function (::SoundPass)(::Type{AbstractBuiltinErrorReport}, analyzer::JETAnalyzer
end
end

# A report pass that is used for `analyze_from_definitions!`.
# Especially, this report pass ignores `UncaughtExceptionReport` to avoid false positives
# from methods that are intentionally written to throw errors.
struct DefinitionAnalysisPass <: ReportPass end
(::DefinitionAnalysisPass)(@nospecialize args...) = BasicPass()(args...)
(::DefinitionAnalysisPass)(::Type{UncaughtExceptionReport}, @nospecialize _...) = false
(::DefinitionAnalysisPass)(::Type{SeriousExceptionReport}, @nospecialize _...) = false

# entries
# =======

8 changes: 8 additions & 0 deletions test/toplevel/test_virtualprocess.jl
Original file line number Diff line number Diff line change
@@ -2412,6 +2412,14 @@ end
ignore_missing_comparison=false) do res
@test isa(only(res.res.inference_error_reports), NonBooleanCondErrorReport)
end

# special cases for `reduce_empty` and `mapreduce_empty`
test_report_package("ReduceEmpty" => quote
reducer(a::Vector{String}) = maximum(length, a)
end;
base_setup=()->nothing) do res
@test isempty(res.res.inference_error_reports)
end
end

end # module test_virtualprocess