From d66ca0ff6785a56fbe157e6111bbf410573080f6 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Tue, 16 Apr 2024 09:43:39 -0500 Subject: [PATCH 01/10] Adopt Julia 1.10+, slim package This eliminates `@snoopc` and `@snoopi`, as these are legacy tools that aren't needed anymore. --- .github/workflows/ci.yml | 7 +- Project.toml | 17 +- test/colortypes.jl | 42 ---- test/runtests.jl | 127 +---------- test/snoopi.jl | 315 --------------------------- test/snoopreachable.jl | 4 - test/testmodules/Stale/Manifest.toml | 3 +- 7 files changed, 14 insertions(+), 501 deletions(-) delete mode 100644 test/colortypes.jl delete mode 100644 test/snoopi.jl delete mode 100644 test/snoopreachable.jl diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 899dfe3c6..e17177f1a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ on: defaults: run: shell: bash -concurrency: +concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true jobs: @@ -20,10 +20,7 @@ jobs: fail-fast: false matrix: version: - - '1.0' - - '1.2' - - '1.4' - - '1.6' + - '1.10' - '1' - 'nightly' os: diff --git a/Project.toml b/Project.toml index 9a8c6b6c2..6a5f8105b 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "SnoopCompile" uuid = "aa65fe97-06da-5843-b5b1-d5d13cad87d2" -author = ["Tim Holy "] -version = "2.10.8" +author = ["Tim Holy ", "Shuhei Kadowaki "] +version = "3.0.0" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" @@ -25,20 +25,17 @@ CthulhuExt = "Cthulhu" JETExt = ["JET", "Cthulhu"] [compat] -AbstractTrees = "0.3, 0.4" -Cthulhu = "1.5, 2" -FlameGraphs = "0.2, 1" +AbstractTrees = "0.4" +Cthulhu = "2" +FlameGraphs = "1" OrderedCollections = "1" -Requires = "1" SnoopCompileCore = "~2.10.0" YAML = "0.4" -julia = "1" +julia = "1.10" [extras] -ColorTypes = "3da002f7-5984-5a60-b8a6-cbb66c0b333f" Cthulhu = "f68482b8-f384-11e8-15f7-abe071a5a75f" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" -FixedPointNumbers = "53c48c17-4a7d-5ca2-90c5-79b7896eea93" JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" MethodAnalysis = "85b6ec6f-f7df-4429-9514-a64bcd9ee824" Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" @@ -47,4 +44,4 @@ Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["ColorTypes", "Cthulhu", "PrettyTables", "Documenter", "FixedPointNumbers", "JET", "MethodAnalysis", "Pkg", "Random", "Test"] +test = ["Cthulhu", "PrettyTables", "Documenter", "JET", "MethodAnalysis", "Pkg", "Random", "Test"] diff --git a/test/colortypes.jl b/test/colortypes.jl deleted file mode 100644 index 50ec11075..000000000 --- a/test/colortypes.jl +++ /dev/null @@ -1,42 +0,0 @@ -using SnoopCompile, Test, Pkg - -@testset "ColorTypes" begin - success, line, modsym = SnoopCompile.parse_call("Tuple{typeof(ColorTypes.to_top), Type{C}} where C<:(ColorTypes.Colorant{T, N} where N where T)") - @test success - @test modsym == :ColorTypes - - @info "Beginning @snoopc test on ColorTypes. Note failures in ColorTypes' tests do not count as failures here." - mktempdir() do tmpdir - tmpfile = joinpath(tmpdir, "colortypes_compiles.csv") - tmpdir2 = joinpath(tmpdir, "precompile") - tmpfile2 = joinpath(tmpdir, "userimg_ColorTypes.jl") - - ### Log the compiles (in a separate process) - SnoopCompile.@snoopc tmpfile begin - using ColorTypes, Pkg - include(joinpath(dirname(dirname(pathof(ColorTypes))), "test", "runtests.jl")) - end - - ### Parse the compiles and generate precompilation scripts - let data = SnoopCompile.read(tmpfile) - # Use these two lines if you want to create precompile functions for - # individual packages - pc = SnoopCompile.parcel(reverse!(data[2])) - SnoopCompile.write(tmpdir2, pc) - - # Use these two lines if you want to add to your userimg.jl - pc = SnoopCompile.format_userimg(reverse!(data[2])) - SnoopCompile.write(tmpfile2, pc) - end - - function notisempty(filename, minlength) - @test isfile(filename) - @test length(readlines(filename)) >= minlength - nothing - end - - notisempty(joinpath(tmpdir2, "precompile_ColorTypes.jl"), 100) - notisempty(joinpath(tmpdir2, "precompile_FixedPointNumbers.jl"), 2) - notisempty(tmpfile2, 100) - end -end diff --git a/test/runtests.jl b/test/runtests.jl index eab7d3c2f..7e5f61787 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,127 +1,6 @@ using Test - -if VERSION >= v"1.6.0-DEV.1190" # https://github.com/JuliaLang/julia/pull/37749 - @testset "snoopi_deep" begin - include("snoopi_deep.jl") - end -end - -if VERSION >= v"1.6.0-DEV.1192" # https://github.com/JuliaLang/julia/pull/37136 - @testset "snoopl" begin - include("snoopl.jl") - end -end - -if VERSION >= v"1.2.0-DEV.573" - include("snoopi.jl") -end - using SnoopCompile -if isdefined(SnoopCompile, :invalidation_trees) - include("snoopr.jl") -end - -@testset "Miscellaneous" begin -# issue #26 -logfile = joinpath(tempdir(), "anon.log") -@snoopc logfile begin - map(x->x^2, [1,2,3]) -end -data = SnoopCompile.read(logfile) -pc = SnoopCompile.parcel(reverse!(data[2])) -if Base.VERSION < v"1.10" - @test length(pc[:Base]) <= 1 -else - @test !haskey(pc, :Base) -end - -# issue #29 -keep, pcstring, topmod, name = SnoopCompile.parse_call("Tuple{getfield(JLD, Symbol(\"##s27#8\")), Any, Any, Any, Any, Any}") -@test keep -@test pcstring == "Tuple{getfield(JLD, Symbol(\"##s27#8\")), Int, Int, Int, Int, Int}" -@test topmod == :JLD -@test name == "##s27#8" -logfile = joinpath(tempdir(), "isdefined.log") -@snoopc logfile begin - @eval module IsDef - @generated function tm(x) - return :(typemax($x)) - end - end - IsDef.tm(1) -end -data = SnoopCompile.read(logfile) -pc = SnoopCompile.parcel(reverse!(data[2])) -@test any(startswith.(pc[:IsDef], "isdefined")) - -if VERSION < v"1.7.0-DEV.694" - @testset "Warning for failures to precompile" begin - pcs = ["@warnpcfail precompile(fsimple, (Char,))", - "@warnpcfail precompile(fsimple, (Char, Char))", - "@warnpcfail precompile(fsimple, (Char, Char, Char))", - ] - fn = tempname() * ".jl" - open(fn, "w") do io - println(io, "fsimple(x, y) = 1") - SnoopCompile.write(io, pcs; writewarnpcfail=true) - end - @test_logs (:warn, r"precompile\(fsimple, \(Char,\)\)") (:warn, r"precompile\(fsimple, \(Char, Char, Char\)\)") include(fn) - rm(fn) - end -end - -# coverage -if isdefined(SnoopCompile.SnoopCompileCore, Symbol("@snoopi_deep")) - tinf = SnoopCompile.flatten_demo() - @test length(accumulate_by_source(flatten(tinf))) == 7 -end - -end - -#= -# Simple call -let str = "sum" - keep, pcstring, topmod = SnoopCompile.parse_call("Foo.any($str)") - @test keep - @test pcstring == "Tuple{$str}" - @test topmod == :Main -end - -# Operator -let str = "Base.:*, Int, Int" - keep, pcstring, topmod = SnoopCompile.parse_call("Foo.any($str)") - @test keep - @test pcstring == "Tuple{$str}" - @test topmod == :Base -end - -# Function as argument -let str = "typeof(Base.identity), Array{Bool, 1}" - keep, pcstring, topmod = SnoopCompile.parse_call("Foo.any($str, Vararg{Any, N} where N)") - @test keep - @test pcstring == "Tuple{$str, Int}" - @test topmod == :Base -end - -# Anonymous function closure in a new module as argument -let func = (@eval Main module SnoopTestTemp - func = () -> (y = 2; (x -> x > y)) - end).func - str = "getfield(SnoopTestTemp, Symbol(\"$(typeof(func()))\")), Array{Float32, 1}" - keep, pcstring, topmod = SnoopCompile.parse_call("Foo.any($str)") - @test keep - @test pcstring == "Tuple{$str}" - @test topmod == :SnoopTestTemp -end - -# Function as a type -let str = "typeof(Base.Sort.sort!), Array{Any, 1}, Base.Sort.MergeSortAlg, Base.Order.By{typeof(Base.string)}" - keep, pcstring, topmod = SnoopCompile.parse_call("Foo.Bar.sort!($str)") - @test keep - @test pcstring == "Tuple{$str}" - @test topmod == :Base -end -=# - -include("colortypes.jl") +include("snoopi_deep.jl") +include("snoopl.jl") +include("snoopr.jl") diff --git a/test/snoopi.jl b/test/snoopi.jl deleted file mode 100644 index 679912275..000000000 --- a/test/snoopi.jl +++ /dev/null @@ -1,315 +0,0 @@ -using SnoopCompile -using InteractiveUtils -using Test - -const SP = VERSION >= v"1.6.0-DEV.771" ? " " : "" # JuliaLang/julia #37085 - -push!(LOAD_PATH, joinpath(@__DIR__, "testmodules")) -using A -using E -using FuncKinds -using Reachable.ModuleA -using Reachable.ModuleB -using Reachable2 -pop!(LOAD_PATH) - -@testset "topmodule" begin - # topmod is called on moduleroots but nevertheless this is a useful test - @test SnoopCompile.topmodule([A, A.B]) === A - @test SnoopCompile.topmodule([A, A.B.C]) === A - @test SnoopCompile.topmodule([A.B, A]) === A - @test SnoopCompile.topmodule([A.B.C, A]) === A - @test SnoopCompile.topmodule([A.B.C, A.B.D]) === nothing - @test SnoopCompile.topmodule([A, Base]) === A - @test SnoopCompile.topmodule([Base, A]) === A -end - -module Lookup -f1(x, y; a=1) = error("oops") -f2(f::Function, args...; kwargs...) = f1(args...; kwargs...) -end - -@testset "lookup bodyfunction" begin - Core.eval(Lookup, SnoopCompile.lookup_kwbody_ex) - m = first(methods(Lookup.f1)) - f = Lookup.__lookup_kwbody__(m) - @test occursin("f1#", String(nameof(f))) - m = first(methods(Lookup.f2)) - f = Lookup.__lookup_kwbody__(m) - isdefined(Core, :_apply_iterate) && @test f !== Core._apply_iterate - @test f !== Core._apply - @test occursin("f2#", String(nameof(f))) -end - -@testset "known_type" begin - @eval module OtherModule - p() = 1 - q() = 1 - end - @eval module KnownType - f() = 1 - module SubModule - g() = 1 - h() = 1 - end - module Exports - export i - i() = 1 - j() = 1 - end - using .SubModule: g - using .Exports - using Main.OtherModule: p - end - @test SnoopCompile.known_type(KnownType, which(KnownType.f, ()).sig) - @test SnoopCompile.known_type(KnownType, which(KnownType.g, ()).sig) - @test SnoopCompile.known_type(KnownType, which(KnownType.SubModule.g, ()).sig) - @test SnoopCompile.known_type(KnownType, which(KnownType.SubModule.h, ()).sig) # it's reachable if appropriately qualified - @test SnoopCompile.known_type(KnownType, which(KnownType.i, ()).sig) - @test SnoopCompile.known_type(KnownType, which(KnownType.Exports.i, ()).sig) - @test SnoopCompile.known_type(KnownType, which(KnownType.Exports.j, ()).sig) # it's reachable if appropriately qualified - @test SnoopCompile.known_type(KnownType, which(OtherModule.p, ()).sig) - @test !SnoopCompile.known_type(KnownType, which(OtherModule.q, ()).sig) - - @test SnoopCompile.known_type(Base, TypeVar(:T, Int)) -end - -if Base.VERSION >= v"1.6.0-DEV.736" - abstract type Vtree end - struct VTree <: Vtree end - (::Type{V})(nv::Int; isordered::Bool=false) where V<:Vtree = V() - @testset "issue 237" begin - VTree(3; isordered=true) - m = @which VTree(3; isordered=true) - fb = Base.bodyfunction(m) - mb = methods(fb).ms[1] - @test occursin("#_#", String(mb.name)) - mi = first(SnoopCompile.specializations(mb)) - modgens = Dict{Module, Vector{Method}}() - tmp = String[] - SnoopCompile.add_repr!(tmp, modgens, mi; check_eval=true) # no error - end -end - -uncompiled(x) = x + 1 - -@testset "snoopi" begin - tinf = @snoopi uncompiled(2) - @test any(td->td[2].def.name == :uncompiled, tinf) - # Ensure older methods can be tested - a = rand(Float16, 5) - tinf = @snoopi sum(a) - @test any(td->td[2].def.name == :sum, tinf) - - a = rand(Float32, 5) - @snoopi tmin=1e-3 sum(a) # coverage - - a = [E.ET(1)] - c = [A.B.C.CT(1)] - tinf = @snoopi (A.f(a); A.f(c)) - @test length(tinf) == 2 - - pc = SnoopCompile.parcel(tinf) - @test isa(pc, Dict) - @test length(pc) == 1 - @test length(pc[:A]) == 1 - directive = pc[:A][1] - @test occursin("C.CT", directive) - @test !occursin("E.ET", directive) - - # Varargs - tinf = @snoopi A.myjoin("a", "b", "c") - pc = SnoopCompile.parcel(tinf) - @test length(pc[:A]) == 1 - @test occursin("Vararg", pc[:A][1]) - - # Identify kwfuncs, whose naming depends on the Julia version (issue #46) - # Also check for kw body functions (also tested below) - tinf = @snoopi begin - FuncKinds.fsort() - FuncKinds.fsort2() - FuncKinds.fsort3() - end - pc = SnoopCompile.parcel(tinf) - FK = pc[:Base] - kwcallname = isdefined(Core, :kwcall) ? "kwcall" : "kwftype" - @test any(str->occursin(kwcallname, str), FK) - @test !any(str->occursin(r"Type\{NamedTuple.*typeof\(sin\)", str), FK) - if VERSION >= v"1.4.0-DEV.215" - mis = last.(tinf) - if !isempty(filter(mis) do mi - length(mi.specTypes.parameters) >= 2 && mi.specTypes.parameters[end-1] === typeof(sortperm) && !(mi.specTypes.parameters[2] <: NamedTuple) - end) - @test any(str->occursin("__lookup_kwbody__", str), FK) - pc2 = SnoopCompile.parcel(tinf; has_bodyfunction=true) - @test !any(str->occursin("__lookup_kwbody__", str), pc2[:Base]) - else - @warn "Body method was not toplevel-inferred, test skipped" - end - else - @test any(str->occursin("isdefined", str), FK) - end - - # Keyword body functions - tinf = @snoopi FuncKinds.callhaskw() - pc = SnoopCompile.parcel(tinf) - FK = pc[:FuncKinds] - @test any(str->occursin(kwcallname, str), FK) - @test any(str->occursin("precompile(Tuple{typeof(haskw),", str), FK) - if VERSION >= v"1.4.0-DEV.215" - @test any(str->occursin("let", str), FK) - @test !any(str->occursin("isdefined", str), FK) - else - @test any(str->occursin("isdefined", str), FK) - end - - # Wrap anonymous functions in an `if isdefined` - list = Any["xbar7", "yfoo8"] - tinf = @snoopi FuncKinds.hasfoo(list) - pc = SnoopCompile.parcel(tinf) - @test any(str->occursin("isdefined", str), pc[:FuncKinds]) - - # Extract the generator in a name-independent manner - tinf = @snoopi begin - FuncKinds.gen(1.0f0) - FuncKinds.gen2(3, 1.1) - FuncKinds.genkw1() - FuncKinds.genkw2(; b="hello") - end - pc = SnoopCompile.parcel(tinf) - FK = pc[:FuncKinds] - @test any(str->occursin("precompile(Tuple{typeof(gen),Float32})", str), FK) - @test any(str->occursin("precompile(Tuple{typeof(gen2),$Int,Float64})", str), FK) - @test any(str->occursin("typeof(which(gen2,($Int,Any,)).generator.gen)", str), FK) - @test any(str->occursin("precompile(Tuple{typeof(genkw1)})", str), FK) - @test !any(str->occursin("precompile(Tuple{typeof(genkw2)})", str), FK) - if isdefined(Core, :kwcall) - if Base.VERSION >= v"1.10.0-DEV.886" - @test any(str->occursin("Tuple{typeof(Core.kwcall),@NamedTuple{b::String},typeof(genkw2)}", str), FK) - else - @test any(str->occursin("Tuple{typeof(Core.kwcall),NamedTuple{(:b,), Tuple{String}},typeof(genkw2)}", str), FK) - end - else - @test any(str->occursin("Tuple{Core.kwftype(typeof(genkw2)),NamedTuple{(:b,),$(SP)Tuple{String}},typeof(genkw2)}", str), FK) - end - if VERSION >= v"1.4.0-DEV.215" - @test any(str->occursin("__lookup_kwbody__(which(genkw1, ()))", str), FK) - @test any(str->occursin("__lookup_kwbody__(which(genkw2, ()))", str), FK) - else - @test any(str->occursin("isdefined", str), FK) - end - - # Inner functions - tinf = @snoopi FuncKinds.hasinner(1, 2) - pc = SnoopCompile.parcel(tinf) - FK = pc[:FuncKinds] - @test any(str->match(r"isdefined.*#inner#", str) !== nothing, FK) - - # exclusions_remover - exclusions = ["hi", "bye"] - pcI = Set(["good", "bad", "hi", "bye", "no"]) - @test SnoopCompile.exclusions_remover!(pcI, exclusions) == Set(["good", "bad", "no"]) -end - -@testset "Lots of methods" begin - # Tests functions from JuliaInterpreter/test/toplevel_script.jl and LoweredCodeUtils - tinf = @snoopi begin - FuncKinds.f1(0) - FuncKinds.f1(0.0) - FuncKinds.f1(0.0f0) - FuncKinds.f2("hi") - FuncKinds.f2(UInt16(1)) - FuncKinds.f2(3.2) - FuncKinds.f2(view([1,2], 1:1)) - FuncKinds.f2([1,2]) - FuncKinds.f2(reshape(view([1,2], 1:2), 2, 1)) - FuncKinds.f3(1, 1) - FuncKinds.f3(1, :hi) - FuncKinds.f3(UInt16(1), :hi) - FuncKinds.f3(rand(2, 2), :hi, :there) - FuncKinds.f4(1, 1) - FuncKinds.f4(1) - FuncKinds.f4(UInt(1), "hey", 2) - FuncKinds.f4(rand(2,2)) - FuncKinds.f5(Int8(1); y=22) - FuncKinds.f5(Int16(1)) - FuncKinds.f5(Int32(1)) - FuncKinds.f5(rand(2,2); y=7) - FuncKinds.f6(1; z=8) - end - pc = SnoopCompile.parcel(tinf) - FK = pc[:FuncKinds] - @test any(str->occursin("typeof(f1),Int", str), FK) - @test any(str->occursin("typeof(f1),Float64", str), FK) - @test any(str->occursin("typeof(f1),Float32", str), FK) - @test any(str->occursin("typeof(f2),String", str), FK) - @test any(str->occursin("typeof(f2),UInt16", str), FK) - @test any(str->occursin("typeof(f2),Float64", str), FK) - @test any(str->occursin("typeof(f2),$(typeof(view([1,2], 1:1)))", str), FK) - @test any(str->occursin("typeof(f2),$(Vector{Int})", str), FK) - @test any(str->occursin("typeof(f2),$(typeof(reshape(view([1,2], 1:2), 2, 1)))", str), FK) - @test any(str->occursin("typeof(f3),$Int,$Int", str), FK) - @test any(str->occursin("typeof(f3),$Int,Symbol", str), FK) - @test any(str->occursin("typeof(f3),UInt16,Symbol", str), FK) - @test any(str->occursin("typeof(f3),$(Matrix{Float64}),Symbol,Symbol", str), FK) - @test any(str->occursin("typeof(f4),$Int,$Int", str), FK) - @test any(str->occursin("typeof(f4),$UInt,String", str), FK) - @test any(str->occursin("typeof(f4),$(Matrix{Float64})", str), FK) - if isdefined(Core, :kwcall) - @test any(str->occursin(r"kwcall.*typeof\(f5\), ?Int8", str), FK) - else - @test any(str->occursin(r"kwftype\(typeof\(f5.*:y.*Int8", str), FK) - end - @test any(str->occursin("typeof(f5),Int16", str), FK) - @test any(str->occursin("typeof(f5),Int32", str), FK) - if isdefined(Core, :kwcall) - @test any(str->occursin(r"kwcall.*typeof\(f5\), ?Matrix\{Float64\}", str), FK) - else - if "$(Matrix{Float64})" == "Matrix{Float64}" - @test any(str->occursin(r"kwftype\(typeof\(f5.*:y.*Matrix\{Float64\}", str), FK) - else - @test any(str->occursin(r"kwftype\(typeof\(f5.*:y.*Array\{Float64,2\}", str), FK) - end - end - # @test any(str->occursin("typeof(f6),(1, "hi"; z=8) == 1 - # @test any(str->occursin("typeof(f7),(1, (1, :hi)) == 1 - # @test any(str->occursin("typeof(f8),(0) == 1 - # @test any(str->occursin("typeof(f9),(3) == 9 - # @test any(str->occursin("typeof(f9),(3.0) == 3.0 - -end - -# Issue https://github.com/timholy/SnoopCompile.jl/issues/40#issuecomment-570560584 -@testset "Reachable" begin - tinf = @snoopi begin - include("snoopreachable.jl") - end - pc = SnoopCompile.parcel(tinf) - pcd = pc[:Reachable] - @test length(pcd) == 2 - @test sum(str->occursin("RchA", str), pcd) == 2 - # Make sure that two modues that know about each other only via Main do not allow precompilation - @test !any(str->occursin("ModuleB.f", str), pcd) - pcd = pc[:Reachable2] - @test length(pcd) == 1 -end - -@testset "@snoopi docs" begin - # docstring is present (weird Docs bug) - dct = Docs.meta(SnoopCompile.SnoopCompileCore) - @test haskey(dct, Docs.Binding(SnoopCompile.SnoopCompileCore, Symbol("@snoopi"))) -end - -@testset "Duplicates (#70)" begin - tinf = @snoopi begin - function eval_local_function(i) - @eval generated() = $i - return Base.invokelatest(generated) - end - eval_local_function(1) - eval_local_function(2) - eval_local_function(3) - end - pc = SnoopCompile.parcel(tinf, remove_exclusions = false) - @test count(isequal("Base.precompile(Tuple{typeof(generated)})"), pc[:Main]) == 1 -end diff --git a/test/snoopreachable.jl b/test/snoopreachable.jl deleted file mode 100644 index 4e125a722..000000000 --- a/test/snoopreachable.jl +++ /dev/null @@ -1,4 +0,0 @@ -a = RchA() -rchb(a) -b = Reachable2.B() -ModuleB.f(b) diff --git a/test/testmodules/Stale/Manifest.toml b/test/testmodules/Stale/Manifest.toml index 9ef716b66..1266afd5f 100644 --- a/test/testmodules/Stale/Manifest.toml +++ b/test/testmodules/Stale/Manifest.toml @@ -1,7 +1,8 @@ # This file is machine-generated - editing it directly is not advised -julia_version = "1.6.3-pre.1" +julia_version = "1.10.2" manifest_format = "2.0" +project_hash = "1f331eb61c74c988f4beff114cdb1cad01ce6052" [[deps.StaleA]] path = "StaleA" From 31cc68a45e4bced42a5321f2727896521a3729e8 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Tue, 16 Apr 2024 10:33:58 -0500 Subject: [PATCH 02/10] Slim code, delete SnoopPrecompile --- Project.toml | 4 + SnoopPrecompile/LICENSE | 21 - SnoopPrecompile/Project.toml | 18 - SnoopPrecompile/README.md | 5 - SnoopPrecompile/src/SnoopPrecompile.jl | 125 ------ SnoopPrecompile/test/SnoopPC_A/Project.toml | 7 - .../test/SnoopPC_A/src/SnoopPC_A.jl | 29 -- SnoopPrecompile/test/SnoopPC_B/Project.toml | 7 - .../test/SnoopPC_B/src/SnoopPC_B.jl | 7 - SnoopPrecompile/test/SnoopPC_C/Project.toml | 7 - .../test/SnoopPC_C/src/SnoopPC_C.jl | 32 -- SnoopPrecompile/test/SnoopPC_D/Project.toml | 7 - .../test/SnoopPC_D/src/SnoopPC_D.jl | 11 - SnoopPrecompile/test/runtests.jl | 73 ---- .../SCPrettyTablesExt.jl | 12 +- src/visualizations.jl => ext/SCPyPlotExt.jl | 9 - src/SnoopCompile.jl | 84 +--- src/parcel_snoopc.jl | 228 ---------- src/{parcel_snoopi.jl => utils.jl} | 392 ++++-------------- test/runtests.jl | 4 + test/snoopi_deep.jl | 156 +++---- 21 files changed, 184 insertions(+), 1054 deletions(-) delete mode 100644 SnoopPrecompile/LICENSE delete mode 100644 SnoopPrecompile/Project.toml delete mode 100644 SnoopPrecompile/README.md delete mode 100644 SnoopPrecompile/src/SnoopPrecompile.jl delete mode 100644 SnoopPrecompile/test/SnoopPC_A/Project.toml delete mode 100644 SnoopPrecompile/test/SnoopPC_A/src/SnoopPC_A.jl delete mode 100644 SnoopPrecompile/test/SnoopPC_B/Project.toml delete mode 100644 SnoopPrecompile/test/SnoopPC_B/src/SnoopPC_B.jl delete mode 100644 SnoopPrecompile/test/SnoopPC_C/Project.toml delete mode 100644 SnoopPrecompile/test/SnoopPC_C/src/SnoopPC_C.jl delete mode 100644 SnoopPrecompile/test/SnoopPC_D/Project.toml delete mode 100644 SnoopPrecompile/test/SnoopPC_D/src/SnoopPC_D.jl delete mode 100644 SnoopPrecompile/test/runtests.jl rename src/report_invalidations.jl => ext/SCPrettyTablesExt.jl (91%) rename src/visualizations.jl => ext/SCPyPlotExt.jl (95%) delete mode 100644 src/parcel_snoopc.jl rename src/{parcel_snoopi.jl => utils.jl} (57%) diff --git a/Project.toml b/Project.toml index 6a5f8105b..4fd382e68 100644 --- a/Project.toml +++ b/Project.toml @@ -19,10 +19,14 @@ YAML = "ddb6d928-2868-570f-bddf-ab3f9cf99eb6" [weakdeps] Cthulhu = "f68482b8-f384-11e8-15f7-abe071a5a75f" JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" +PrettyTables = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d" +PyPlot = "d330b81b-6aea-500a-939a-2ce795aea3ee" [extensions] CthulhuExt = "Cthulhu" JETExt = ["JET", "Cthulhu"] +SCPrettyTablesExt = "PrettyTables" +SCPyPlotExt = "PyPlot" [compat] AbstractTrees = "0.4" diff --git a/SnoopPrecompile/LICENSE b/SnoopPrecompile/LICENSE deleted file mode 100644 index d0b5e0b04..000000000 --- a/SnoopPrecompile/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2022 Tim Holy and contributors - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/SnoopPrecompile/Project.toml b/SnoopPrecompile/Project.toml deleted file mode 100644 index a5a9f3774..000000000 --- a/SnoopPrecompile/Project.toml +++ /dev/null @@ -1,18 +0,0 @@ -name = "SnoopPrecompile" -uuid = "66db9d55-30c0-4569-8b51-7e840670fc0c" -authors = ["Tim Holy and contributors"] -version = "1.0.3" - -[deps] -Preferences = "21216c6a-2e73-6563-6e65-726566657250" - -[compat] -Preferences = "1" -julia = "1" - -[extras] -Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" -UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" - -[targets] -test = ["Test", "UUIDs"] diff --git a/SnoopPrecompile/README.md b/SnoopPrecompile/README.md deleted file mode 100644 index 4ba135d0c..000000000 --- a/SnoopPrecompile/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# SnoopPrecompile - -SnoopPrecompile is a small dependency used to effectively precompile code needed by your package, particularly on Julia 1.8 and higher. - -See the [documentation](https://timholy.github.io/SnoopCompile.jl/dev/snoop_pc/) for details. diff --git a/SnoopPrecompile/src/SnoopPrecompile.jl b/SnoopPrecompile/src/SnoopPrecompile.jl deleted file mode 100644 index 3f1eba696..000000000 --- a/SnoopPrecompile/src/SnoopPrecompile.jl +++ /dev/null @@ -1,125 +0,0 @@ -module SnoopPrecompile - -export @precompile_all_calls, @precompile_setup - -@static if VERSION >= v"1.6" - using Preferences -end - -@static if VERSION >= v"1.6" - const skip_precompile = @load_preference("skip_precompile", String[]) -else - const skip_precompile = String[] -end - -const verbose = Ref(false) # if true, prints all the precompiles -const have_inference_tracking = isdefined(Core.Compiler, :__set_measure_typeinf) -const have_force_compile = isdefined(Base, :Experimental) && isdefined(Base.Experimental, Symbol("#@force_compile")) - -function precompile_roots(roots) - @assert have_inference_tracking - for child in roots - mi = child.mi_info.mi - precompile(mi.specTypes) # TODO: Julia should allow one to pass `mi` directly (would handle `invoke` properly) - verbose[] && println(mi) - end -end - -""" - @precompile_all_calls f(args...) - -`precompile` any method-calls that occur inside the expression. All calls (direct or indirect) inside a -`@precompile_all_calls` block will be precompiled. - -`@precompile_all_calls` has three key features: - -1. code inside runs only when the package is being precompiled (i.e., a `*.ji` - precompile cache file is being written) -2. the interpreter is disabled, ensuring your calls will be compiled -3. both direct and indirect callees will be precompiled, even for methods defined in other packages - and even for runtime-dispatched callees (requires Julia 1.8 and above). - -!!! note - For comprehensive precompilation, ensure the first usage of a given method/argument-type combination - occurs inside `@precompile_all_calls`. - - In detail: runtime-dispatched callees are captured only when type-inference is executed, and they - are inferred only on first usage. Inferrable calls that trace back to a method defined in your package, - and their *inferrable* callees, will be precompiled regardless of "ownership" of the callees - (Julia 1.8 and higher). - - Consequently, this recommendation matters only for: - - - direct calls to methods defined in Base or other packages OR - - indirect runtime-dispatched calls to such methods. -""" -macro precompile_all_calls(ex::Expr) - string(__module__) in skip_precompile && return :() - if have_force_compile - ex = quote - begin - Base.Experimental.@force_compile - $ex - end - end - else - # Use the hack on earlier Julia versions that blocks the interpreter - ex = quote - while false end - $ex - end - end - if have_inference_tracking - ex = quote - Core.Compiler.Timings.reset_timings() - Core.Compiler.__set_measure_typeinf(true) - try - $ex - finally - Core.Compiler.__set_measure_typeinf(false) - Core.Compiler.Timings.close_current_timer() - end - $SnoopPrecompile.precompile_roots(Core.Compiler.Timings._timings[1].children) - end - end - return esc(quote - if ccall(:jl_generating_output, Cint, ()) == 1 || $SnoopPrecompile.verbose[] - $ex - end - end) -end - -""" - @precompile_setup begin - vars = ... - ⋮ - end - -Run the code block only during package precompilation. `@precompile_setup` is often used in combination -with [`@precompile_all_calls`](@ref), for example: - - @precompile_setup begin - vars = ... - @precompile_all_calls begin - y = f(vars...) - g(y) - ⋮ - end - end - -`@precompile_setup` does not force compilation (though it may happen anyway) nor intentionally capture -runtime dispatches (though they will be precompiled anyway if the runtime-callee is for a method belonging -to your package). -""" -macro precompile_setup(ex::Expr) - string(__module__) in skip_precompile && return :() - return esc(quote - # let - if ccall(:jl_generating_output, Cint, ()) == 1 || $SnoopPrecompile.verbose[] - $ex - end - # end - end) -end - -end diff --git a/SnoopPrecompile/test/SnoopPC_A/Project.toml b/SnoopPrecompile/test/SnoopPC_A/Project.toml deleted file mode 100644 index 4f6f33786..000000000 --- a/SnoopPrecompile/test/SnoopPC_A/Project.toml +++ /dev/null @@ -1,7 +0,0 @@ -name = "SnoopPC_A" -uuid = "13c4d82c-fea6-47db-9cd5-35bd2686c3b0" -authors = ["Tim Holy "] -version = "0.1.0" - -[deps] -SnoopPrecompile = "66db9d55-30c0-4569-8b51-7e840670fc0c" diff --git a/SnoopPrecompile/test/SnoopPC_A/src/SnoopPC_A.jl b/SnoopPrecompile/test/SnoopPC_A/src/SnoopPC_A.jl deleted file mode 100644 index 658c5c5f4..000000000 --- a/SnoopPrecompile/test/SnoopPC_A/src/SnoopPC_A.jl +++ /dev/null @@ -1,29 +0,0 @@ -module SnoopPC_A - -using SnoopPrecompile: @precompile_setup, @precompile_all_calls - -struct MyType - x::Int -end - -if isdefined(Base, :inferencebarrier) - inferencebarrier(@nospecialize(arg)) = Base.inferencebarrier(arg) -else - inferencebarrier(@nospecialize(arg)) = Ref{Any}(arg)[] -end - -function call_findfirst(x, list) - # call a method defined in Base by runtime dispatch - return findfirst(==(inferencebarrier(x)), inferencebarrier(list)) -end - -let - @precompile_setup begin - list = [MyType(1), MyType(2), MyType(3)] - @precompile_all_calls begin - call_findfirst(MyType(2), list) - end - end -end - -end # module SnoopPC_A diff --git a/SnoopPrecompile/test/SnoopPC_B/Project.toml b/SnoopPrecompile/test/SnoopPC_B/Project.toml deleted file mode 100644 index de973497a..000000000 --- a/SnoopPrecompile/test/SnoopPC_B/Project.toml +++ /dev/null @@ -1,7 +0,0 @@ -name = "SnoopPC_B" -uuid = "d38b61e7-59a2-4ef9-b4d3-320bdc69b817" -authors = ["Tim Holy "] -version = "0.1.0" - -[deps] -SnoopPrecompile = "66db9d55-30c0-4569-8b51-7e840670fc0c" diff --git a/SnoopPrecompile/test/SnoopPC_B/src/SnoopPC_B.jl b/SnoopPrecompile/test/SnoopPC_B/src/SnoopPC_B.jl deleted file mode 100644 index accad3196..000000000 --- a/SnoopPrecompile/test/SnoopPC_B/src/SnoopPC_B.jl +++ /dev/null @@ -1,7 +0,0 @@ -module SnoopPC_B - -using SnoopPrecompile - -@precompile_all_calls missing_function(1) - -end # module SnoopPC_B diff --git a/SnoopPrecompile/test/SnoopPC_C/Project.toml b/SnoopPrecompile/test/SnoopPC_C/Project.toml deleted file mode 100644 index 7faaf3abc..000000000 --- a/SnoopPrecompile/test/SnoopPC_C/Project.toml +++ /dev/null @@ -1,7 +0,0 @@ -name = "SnoopPC_C" -uuid = "e6c49816-f2a1-47f5-8743-1735c486fd91" -authors = ["Tim Holy "] -version = "0.1.0" - -[deps] -SnoopPrecompile = "66db9d55-30c0-4569-8b51-7e840670fc0c" diff --git a/SnoopPrecompile/test/SnoopPC_C/src/SnoopPC_C.jl b/SnoopPrecompile/test/SnoopPC_C/src/SnoopPC_C.jl deleted file mode 100644 index 9abb2fe8a..000000000 --- a/SnoopPrecompile/test/SnoopPC_C/src/SnoopPC_C.jl +++ /dev/null @@ -1,32 +0,0 @@ -module SnoopPC_C - -using SnoopPrecompile - -# mimic `RecipesBase` code - see github.com/JuliaPlots/Plots.jl/issues/4597 and #317 -module RB - export @recipe - - apply_recipe(args...) = nothing - macro recipe(ex::Expr) - _, func_body = ex.args - func = Expr(:call, :($RB.apply_recipe)) - Expr( - :function, - func, - quote - @nospecialize - func_return = $func_body - end |> esc - ) - end -end -using .RB - -@precompile_setup begin - struct Foo end - @precompile_all_calls begin - @recipe f(::Foo) = nothing - end -end - -end # module SnoopPC_C diff --git a/SnoopPrecompile/test/SnoopPC_D/Project.toml b/SnoopPrecompile/test/SnoopPC_D/Project.toml deleted file mode 100644 index 91d3fdb90..000000000 --- a/SnoopPrecompile/test/SnoopPC_D/Project.toml +++ /dev/null @@ -1,7 +0,0 @@ -name = "SnoopPC_D" -uuid = "8c347477-c4a5-4b7f-b911-c68b2b9ed891" -authors = ["Tim Holy "] -version = "0.1.0" - -[deps] -SnoopPrecompile = "66db9d55-30c0-4569-8b51-7e840670fc0c" diff --git a/SnoopPrecompile/test/SnoopPC_D/src/SnoopPC_D.jl b/SnoopPrecompile/test/SnoopPC_D/src/SnoopPC_D.jl deleted file mode 100644 index 1ad3ea834..000000000 --- a/SnoopPrecompile/test/SnoopPC_D/src/SnoopPC_D.jl +++ /dev/null @@ -1,11 +0,0 @@ -module SnoopPC_D - -using SnoopPrecompile - -@precompile_setup begin - @precompile_all_calls begin - global workload_ran = true - end -end - -end # module SnoopPC_D diff --git a/SnoopPrecompile/test/runtests.jl b/SnoopPrecompile/test/runtests.jl deleted file mode 100644 index 257765477..000000000 --- a/SnoopPrecompile/test/runtests.jl +++ /dev/null @@ -1,73 +0,0 @@ -using SnoopPrecompile -using Test -using UUIDs - - -@testset "SnoopPrecompile.jl" begin - specializations(m::Method) = isdefined(Base, :specializations) ? Base.specializations(m) : m.specializations - - push!(LOAD_PATH, @__DIR__) - - using SnoopPC_A - if VERSION >= v"1.8.0-rc1" - # Check that calls inside :setup are not precompiled - m = which(Tuple{typeof(Base.vect), Vararg{T}} where T) - have_mytype = false - for mi in specializations(m) - mi === nothing && continue - have_mytype |= Base.unwrap_unionall(mi.specTypes).parameters[2] === SnoopPC_A.MyType - end - have_mytype && @warn "Code in setup block was precompiled" - # Check that calls inside @precompile_calls are precompiled - m = only(methods(SnoopPC_A.call_findfirst)) - count = 0 - for mi in specializations(m) - mi === nothing && continue - sig = Base.unwrap_unionall(mi.specTypes) - @test sig.parameters[2] == SnoopPC_A.MyType - @test sig.parameters[3] == Vector{SnoopPC_A.MyType} - count += 1 - end - @test count == 1 - # Even one that was runtime-dispatched - m = which(Tuple{typeof(findfirst), Base.Fix2{typeof(==), T}, Vector{T}} where T) - count = 0 - for mi in specializations(m) - mi === nothing && continue - sig = Base.unwrap_unionall(mi.specTypes) - if sig.parameters[3] == Vector{SnoopPC_A.MyType} - count += 1 - end - end - @test count == 1 - end - - if VERSION >= v"1.7" # so we can use redirect_stderr(f, ::Pipe) - pipe = Pipe() - id = Base.PkgId(UUID("d38b61e7-59a2-4ef9-b4d3-320bdc69b817"), "SnoopPC_B") - redirect_stderr(pipe) do - @test_throws Exception Base.require(id) - end - close(pipe.in) - str = read(pipe.out, String) - @test occursin(r"UndefVarError: `?missing_function`? not defined", str) - end - - if VERSION >= v"1.6" - using SnoopPC_C - end - - if VERSION >= v"1.6" - script = """ - push!(LOAD_PATH, @__DIR__) - using SnoopPC_D - exit(isdefined(SnoopPC_D, :workload_ran) === parse(Bool, ARGS[1]) ? 0 : 1) - """ - - SnoopPrecompile.Preferences.set_preferences!(SnoopPrecompile, "skip_precompile" => ["SnoopPC_D"]) - @test success(run(`$(Base.julia_cmd()) --project=$(Base.active_project()) -e $script 0`)) - - SnoopPrecompile.Preferences.delete_preferences!(SnoopPrecompile, "skip_precompile"; force = true) - @test success(run(`$(Base.julia_cmd()) --project=$(Base.active_project()) -e $script 1`)) - end -end diff --git a/src/report_invalidations.jl b/ext/SCPrettyTablesExt.jl similarity index 91% rename from src/report_invalidations.jl rename to ext/SCPrettyTablesExt.jl index 6783ba9e9..f50b74d9c 100644 --- a/src/report_invalidations.jl +++ b/ext/SCPrettyTablesExt.jl @@ -1,10 +1,10 @@ -# This file is loaded conditionally via @require if PrettyTables is loaded +module SCPrettyTablesExt -import .PrettyTables +using SnoopCompile +using SnoopCompile: countchildren +import PrettyTables -export report_invalidations - -function report_invalidations(io::IO = stdout; +function SnoopCompile.report_invalidations(io::IO = stdout; invalidations, n_rows::Int = 10, process_filename::Function = x -> x, @@ -60,3 +60,5 @@ function report_invalidations(io::IO = stdout; alignment = [:l, :c, :c, :c], ) end + +end diff --git a/src/visualizations.jl b/ext/SCPyPlotExt.jl similarity index 95% rename from src/visualizations.jl rename to ext/SCPyPlotExt.jl index 371d0eb3e..ef453dbc3 100644 --- a/src/visualizations.jl +++ b/ext/SCPyPlotExt.jl @@ -1,10 +1,5 @@ -# This is loaded conditionally via @require if PyPlot is loaded -# COV_EXCL_START - using .PyPlot: plt, PyCall -export pgdsgui - get_bystr(@nospecialize(by)) = by === inclusive ? "Inclusive" : by === exclusive ? "Exclusive" : error("unknown ", by) @@ -80,7 +75,3 @@ function prep_ri(tinf::InferenceTimingNode, pdata=Profile.fetch(); lidict=lookup lookup_firstip!(lookups, pdata) return runtime_inferencetime(tinf, pdata; lidict, consts, by) end - -@deprecate specialization_plot pgdsgui - -# COV_EXCL_STOP diff --git a/src/SnoopCompile.jl b/src/SnoopCompile.jl index 9b6b41ecc..944a3bb17 100644 --- a/src/SnoopCompile.jl +++ b/src/SnoopCompile.jl @@ -5,19 +5,6 @@ The capabilities depend on your version of Julia; in general, the capabilities t require more recent Julia versions are also the most powerful and useful. When possible, you should prefer them above the more limited tools available on earlier versions. -## All Julia versions - -- `@snoopc`: record Julia's code generation -- `SnoopCompile.read`: parse data collected from `@snoopc` -- `SnoopCompile.parcel`: split precompile statements into modules/packages -- `SnoopCompile.write`: write module-specific precompile files (") - -## At least Julia 1.2 - -- `@snoopi`: record entrances to Julia's type-inference (`parcel` and `write` work on these data, too) - -## At least Julia 1.6 - ### Invalidations - `@snoopr`: record invalidations @@ -46,7 +33,6 @@ you should prefer them above the more limited tools available on earlier version module SnoopCompile using SnoopCompileCore -export @snoopc # More exports are defined below in the conditional loading sections using Core: MethodInstance, CodeInfo @@ -55,13 +41,8 @@ using Serialization using Printf using OrderedCollections import YAML # For @snoopl -using Requires -if isdefined(Base, :specializations) - specializations(m::Method) = Base.specializations(m) -else - specializations(m::Method) = m.specializations -end +using Base: specializations # Parcel Regex const anonrex = r"#{1,2}\d+#{1,2}\d+" # detect anonymous functions @@ -70,6 +51,8 @@ const kwbodyrex = r"^##(\w[^#]*)#\d+" # detect keyword body methods const genrex = r"^##s\d+#\d+$" # detect generators for @generated functions const innerrex = r"^#[^#]+#\d+" # detect inner functions +include("utils.jl") + # This is for SnoopCompile's own directives. You don't want to call this from packages because then # SnoopCompile becomes a dependency of your package. Instead, make sure that `writewarnpcfail` is set to `true` # in `SnoopCompile.write` and a copy of this macro will be placed at the top @@ -86,55 +69,30 @@ macro warnpcfail(ex::Expr) end # Parcel -include("parcel_snoopc.jl") - -if isdefined(SnoopCompileCore, Symbol("@snoopi")) - include("parcel_snoopi.jl") - export @snoopi -end -if isdefined(SnoopCompileCore, Symbol("@snoopi_deep")) - include("parcel_snoopi_deep.jl") - include("deep_demos.jl") - export @snoopi_deep, exclusive, inclusive, flamegraph, flatten, accumulate_by_source, collect_for, runtime_inferencetime, staleinstances - export InferenceTrigger, inference_triggers, callerinstance, callingframe, skiphigherorder, trigger_tree, suggest, isignorable - export report_callee, report_caller, report_callees -end +include("parcel_snoopi_deep.jl") +include("deep_demos.jl") +export @snoopi_deep, exclusive, inclusive, flamegraph, flatten, accumulate_by_source, collect_for, runtime_inferencetime, staleinstances +export InferenceTrigger, inference_triggers, callerinstance, callingframe, skiphigherorder, trigger_tree, suggest, isignorable +export report_callee, report_caller, report_callees -if isdefined(SnoopCompileCore, Symbol("@snoopl")) - export @snoopl -end -# To support reading of results on an older Julia version, this isn't conditional include("parcel_snoopl.jl") -export read_snoopl +export read_snoopl, @snoopl -if isdefined(SnoopCompileCore, Symbol("@snoopr")) - include("invalidations.jl") - export @snoopr, uinvalidated, invalidation_trees, filtermod, findcaller -end +include("invalidations.jl") +export @snoopr, uinvalidated, invalidation_trees, filtermod, findcaller -if isdefined(SnoopCompileCore, Symbol("@snoopr")) && isdefined(SnoopCompileCore, Symbol("@snoopi_deep")) - include("invalidation_and_inference.jl") - export precompile_blockers -end +include("invalidation_and_inference.jl") +export precompile_blockers # Write -include("write.jl") - -function __init__() - if isdefined(SnoopCompile, :runtime_inferencetime) - @require PyPlot = "d330b81b-6aea-500a-939a-2ce795aea3ee" include("visualizations.jl") - end - if isdefined(SnoopCompileCore, Symbol("@snoopr")) - @require PrettyTables = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d" include("report_invalidations.jl") - end - if isdefined(SnoopCompile, :report_callee) && !isdefined(Base, :get_extension) - @require Cthulhu = "f68482b8-f384-11e8-15f7-abe071a5a75f" begin - include("../ext/CthulhuExt.jl") - @require JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" include("../ext/JETExt.jl") - end - end - return nothing -end +# include("write.jl") + +# For PyPlot extension +function pgdsgui end +export pgdsgui +# For PrettyTables extension +function report_invalidations end +export report_invalidations end # module diff --git a/src/parcel_snoopc.jl b/src/parcel_snoopc.jl deleted file mode 100644 index 022f12df0..000000000 --- a/src/parcel_snoopc.jl +++ /dev/null @@ -1,228 +0,0 @@ -function split2(str, on) - i = findfirst(isequal(on), str) - i === nothing && return str, "" - return (SubString(str, firstindex(str), prevind(str, first(i))), - SubString(str, nextind(str, last(i)))) -end - -""" -`SnoopCompile.read("compiledata.csv")` reads the log file produced by the compiler and returns the functions as a pair of arrays. The first array is the amount of time required to compile each function, the second is the corresponding function + types. The functions are sorted in order of increasing compilation time. (The time does not include the cost of nested compiles.) -""" -function read(filename) - times = Vector{UInt64}() - data = Vector{String}() - toplevel = false # workaround for Julia#22538 - for line in eachline(filename) - if toplevel - if endswith(line, '"') - toplevel = false - end - continue - end - time, str = split2(line, '\t') - length(str) < 2 && continue - (str[1] == '"' && str[end] == '"') || continue - if startswith(str, """" -> """) - # consume lines until we find the terminating " character - toplevel = true - continue - end - tm = tryparse(UInt64, time) - tm === nothing && continue - push!(times, tm) - push!(data, str[2:prevind(str, lastindex(str))]) - end - # Save the most costly for last - p = sortperm(times) - return (permute!(times, p), permute!(data, p)) -end - -# pattern match on the known output of jl_static_show -extract_topmod(e::QuoteNode) = extract_topmod(e.value) -function extract_topmod(e) - Meta.isexpr(e, :.) && - return extract_topmod(e.args[1]) - Meta.isexpr(e, :call) && length(e.args) == 3 && e.args[1] == :getfield && - return extract_topmod(e.args[2]) - Meta.isexpr(e, :call) && length(e.args) == 2 && e.args[1] == :typeof && - return extract_topmod(e.args[2]) - #Meta.isexpr(e, :call) && length(e.args) == 2 && e.args[1] == :Symbol && - # return Symbol(e.args[2]) - # parametrized anonymous functions - Meta.isexpr(e, :call) && e.args[1].args[1] == :getfield && - return extract_topmod(e.args[1].args[2].args[2]) - isa(e, Symbol) && - return e - return :unknown -end - -function parse_call(line; subst=Vector{Pair{String, String}}(), exclusions=String[]) - match(anonrex, line) === nothing || return false, line, :unknown, "" - for (k, v) in subst - line = replace(line, k=>v) - end - if any(b -> occursin(b, line), exclusions) - println(line, " contains an excluded substring") - return false, line, :unknown, "" - end - - curly = ex = Meta.parse(line, raise=false) - while Meta.isexpr(curly, :where) - curly = curly.args[1] - end - if !Meta.isexpr(curly, :curly) - @warn("failed parse of line: ", line) - return false, line, :unknown, "" - end - func = curly.args[2] - topmod = (func isa Expr ? extract_topmod(func) : :Main) - - check = Meta.isexpr(func, :call) && length(func.args) == 3 && func.args[1] == :getfield - name = (check ? func.args[3].args[2] : "") - if !check - if occursin("var\"", repr(func)) - check = true - name = func.args[end] - if isa(name, QuoteNode) - name = name.value - end - name = String(name)::String - end - end - - # make some substitutions to try to form a leaf types tuple - changed = false - for i in 3:length(curly.args) - e = curly.args[i] - if Meta.isexpr(e, :where) && length(e.args) == 2 && Meta.isexpr(e.args[1], :curly) && length(e.args[1].args) == 3 && e.args[1].args[1] === :Vararg - # Unwrap a Varargs argument, make it a single instance of the allowed Varargs type. - e = e.args[1].args[2] - curly.args[i] = e - changed = true - end - if e === :Function - # `Function` is not an allowable type for `precompile()` - e = :(typeof(identity)) - elseif e === :Any - # `Any` is an abstract type, and can't be precompiled for. Replace it with something that can. - e = :Int - elseif e == :(Type{T} where T) - # Type is an abstract type; replace it with a concrete type. - e = :(Type{Int}) - else - continue - end - curly.args[i] = e - changed = true - end - # In Julia 0.6, functions with symbolic names like `Base.:(+)` don't output correctly. - # So if we didn't change the arg list above, use the original string. - if changed - line = string(ex) - end - return true, line, topmod, name -end - -""" -`pc = parcel(calls; subst=[], exclusions=[])` assigns each compile statement to the module that owns the function. Perform string substitution via `subst=["Module1"=>"Module2"]`, and omit functions in particular modules with `exclusions=["Module3"]`. On output, `pc[:Module2]` contains all the precompiles assigned to `Module2`. - -Use `SnoopCompile.write(prefix, pc)` to generate a series of files in directory `prefix`, one file per module. -""" -function parcel(calls::AbstractVector{String}; - subst=Vector{Pair{String, String}}(), - exclusions=String[], - remove_exclusions::Bool = true, - check_eval::Bool = false, - blacklist=nothing, # deprecated keyword - remove_blacklist=nothing) # deprecated keyword - - if blacklist !== nothing - Base.depwarn("`blacklist` is deprecated, please use `exclusions` to pass a list of excluded names", :parcel) - append!(exclusions, blacklist) - end - if remove_blacklist !== nothing - Base.depwarn("`remove_blacklist` is deprecated, please use `remove_exclusions` to pass a list of excluded names", :parcel) - remove_exclusions = remove_blacklist - end - pc = Dict{Symbol, Vector{String}}() - - sym_module = Dict{Symbol, Module}() # 1-1 association between modules and module name - - for c in calls - local keep, pcstring, topmod - keep, pcstring, topmod, name = parse_call(c, subst=subst, exclusions=exclusions) - keep || continue - # Add to the appropriate dictionary - if !haskey(pc, topmod) - pc[topmod] = String[] - end - prefix = (isempty(name) ? "" : "isdefined($topmod, Symbol(\"$name\")) && ") - push!(pc[topmod], prefix * "precompile($pcstring)") - - # code from parcel_snoopi - # TODO moduleof a symobl! - # eval(topmod) # throws error for Test - # sym_module[topmod] = moduleof(topmod) - end - - # TODO - # for mod in keys(pc) - # # check_eval remover - # if check_eval - # pc[mod] = remove_if_not_eval!(pc[mod], sym_module[mod]) - # end - # end - return pc -end - -""" -`pc = format_userimg(calls; subst=[], exclusions=[])` generates precompile directives intended for your base/userimg.jl script. Use `SnoopCompile.write(filename, pc)` to create a file that you can `include` into `userimg.jl`. -""" -function format_userimg(calls; subst=Vector{Pair{String, String}}(), exclusions=String[]) - pc = Vector{String}() - for c in calls - keep, pcstring, topmod, name = parse_call(c, subst=subst, exclusions=exclusions) - keep || continue - prefix = (isempty(name) ? "" : "isdefined($topmod, Symbol(\"$name\")) && ") - push!(pc, prefix * "precompile($pcstring)") - end - return pc -end - -# TODO Make this work with snoopc: - -""" - remove_if_not_eval!(pcstatements, modul::Module) - -Removes everything statement in `pcstatements` can't be `eval`ed in `modul`. - -# Example - -```jldoctest; setup=:(using SnoopCompile), filter=r":\\d\\d\\d" -julia> pcstatements = ["precompile(sum, (Vector{Int},))", "precompile(sum, (CustomVector{Int},))"]; - -julia> SnoopCompile.remove_if_not_eval!(pcstatements, Base) -┌ Warning: Faulty precompile statement: precompile(sum, (CustomVector{Int},)) -│ exception = UndefVarError: `CustomVector` not defined -└ @ Base precompile_Base.jl:375 -1-element Vector{String}: - "precompile(sum, (Vector{Int},))" -``` -""" -function remove_if_not_eval!(pcstatements, modul::Module) - todelete = Set{eltype(pcstatements)}() - for line in pcstatements - try - if modul === Core - #https://github.com/timholy/SnoopCompile.jl/issues/76 - Core.eval(Main, Meta.parse(line)) - else - Core.eval(modul, Meta.parse(line)) - end - catch e - @warn("Faulty precompile statement: $line", exception = e, _module = modul, _file = "precompile_$modul.jl") - push!(todelete, line) - end - end - return setdiff!(pcstatements, todelete) -end diff --git a/src/parcel_snoopi.jl b/src/utils.jl similarity index 57% rename from src/parcel_snoopi.jl rename to src/utils.jl index dc0ba57a3..7f9fc021d 100644 --- a/src/parcel_snoopi.jl +++ b/src/utils.jl @@ -1,187 +1,3 @@ -function topmodule(mods) - function ischild(c, mod) - ok = false - pc = parentmodule(c) - while pc !== c - pc === Main && return false # mostly important for passing the tests - if isdefined(mod, nameof(pc)) - ok = true - break - end - c = pc - end - return ok - end - - mods = collect(mods) - mod = first(mods) - for m in Iterators.drop(mods, 1) - # Easy cases - if isdefined(mod, nameof(m)) - elseif isdefined(m, nameof(mod)) - mod = m - else - # Check parents of each - if ischild(m, mod) - elseif ischild(mod, m) - mod = m - else - return nothing - end - end - end - return mod -end - -function addmodules!(mods, parameters) - for p in parameters - if isa(p, DataType) - push!(mods, Base.moduleroot(p.name.module)) - addmodules!(mods, p.parameters) - end - end - return mods -end - -function methods_with_generators(m::Module) - meths = Method[] - for name in names(m; all=true) - isdefined(m, name) || continue - f = getfield(m, name) - if isa(f, Function) - for method in methods(f) - if isdefined(method, :generator) - push!(meths, method) - end - end - end - end - return meths -end - -# Code to look up keyword-function "body methods" -const lookup_kwbody_str = """ -const __bodyfunction__ = Dict{Method,Any}() - -# Find keyword "body functions" (the function that contains the body -# as written by the developer, called after all missing keyword-arguments -# have been assigned values), in a manner that doesn't depend on -# gensymmed names. -# `mnokw` is the method that gets called when you invoke it without -# supplying any keywords. -function __lookup_kwbody__(mnokw::Method) - function getsym(ast, arg) - isa(arg, Symbol) && return arg - isa(arg, GlobalRef) && return arg.name - if isa(arg, Core.SSAValue) - arg = ast.code[arg.id] - return getsym(ast, arg) - end - end - - f = get(__bodyfunction__, mnokw, nothing) - if f === nothing - fmod = mnokw.module - # The lowered code for `mnokw` should look like - # %1 = mkw(kwvalues..., #self#, args...) - # return %1 - # where `mkw` is the name of the "active" keyword body-function. - ast = Base.uncompressed_ast(mnokw) - if isa(ast, Core.CodeInfo) && length(ast.code) >= 2 - callexpr = ast.code[end-1] - if isa(callexpr, Expr) && callexpr.head == :call - fsym = callexpr.args[1] - if isa(fsym, Symbol) - f = getfield(fmod, fsym) - elseif isa(fsym, GlobalRef) - if fsym.mod === Core && fsym.name === :_apply - f = getfield(mnokw.module, getsym(ast, callexpr.args[2])) - elseif fsym.mod === Core && fsym.name === :_apply_iterate - f = getfield(mnokw.module, getsym(ast, callexpr.args[3])) - else - f = getfield(fsym.mod, fsym.name) - end - else - f = missing - end - else - f = missing - end - else - f = missing - end - __bodyfunction__[mnokw] = f - end - return f -end -""" - -const lookup_kwbody_ex = Expr(:toplevel) -start = 1 -while true - global start - ex, start = Meta.parse(lookup_kwbody_str, start) - if ex !== nothing - push!(lookup_kwbody_ex.args, ex) - else - break - end -end - -""" - can_eval(mod::Module, str::AbstractString, check_eval::Bool=true) - -Checks if the precompilation statement can be evaled. - -In some cases, you may want to bypass this function by passing `check_eval=true` to increase the snooping performance. -""" -function can_eval(mod::Module, str::AbstractString, check_eval::Bool=true) - if check_eval - try - ex = Meta.parse(str) - if mod === Core - #https://github.com/timholy/SnoopCompile.jl/issues/76 - Core.eval(Main, ex) - else - Core.eval(mod, ex) - end - catch e - return false, e - end - end - return true, nothing -end - -tupletypestring(params) = "Tuple{" * join(params, ',') * '}' -tupletypestring(fstr::AbstractString, params::AbstractVector{<:AbstractString}) = - tupletypestring([fstr; params]) - -tuplestring(params) = isempty(params) ? "()" : '(' * join(params, ',') * ",)" - -wrap_precompile(ttstr::AbstractString) = "Base.precompile(" * ttstr * ')' # use `Base.` to avoid conflict with Core and Pkg - -append_time(str, ::Nothing) = str -append_time(str, t::AbstractFloat) = str * " # time: " * string(Float32(t)) - -""" - add_if_evals!(pclist, mod::Module, fstr, params, tt; prefix = "", check_eval::Bool=true) - -Adds the precompilation statements only if they can be evaled. It uses [`can_eval`](@ref) internally. - -In some cases, you may want to bypass this function by passing `check_eval=true` to increase the snooping performance. -""" -function add_if_evals!(pclist, mod::Module, fstr, params, tt; prefix = "", check_eval::Bool=true, time=nothing) - ttstr = tupletypestring(fstr, params) - can, exc = can_eval(mod, ttstr, check_eval) - if can - push!(pclist, append_time(prefix*wrap_precompile(ttstr), time)) - return true - else - @debug "Module $mod: skipping $tt due to eval failure" exception=exc _module=mod _file="precompile_$mod.jl" - end - return false -end - function reprcontext(mod::Module, @nospecialize(T)) # First check whether supplying module context allows evaluation rplain = repr(T; context=:module=>mod) @@ -245,106 +61,6 @@ let known_type_cache = IdDict{Tuple{Module,Tuple{Vararg{Symbol}},Symbol},Bool}() end end -function handle_kwbody(topmod::Module, m::Method, paramrepr, tt, fstr="fbody"; check_eval = true, has_bodyfunction::Bool=false) - nameparent = Symbol(match(r"^#([^#]*)#", String(m.name)).captures[1]) - if !isdefined(m.module, nameparent) - @debug "Module $topmod: skipping $m due to inability to look up kwbody parent" # see example related to issue #237 - return nothing - end - fparent = getfield(m.module, nameparent) - pttstr = tuplestring(paramrepr[m.nkw+2:end]) - whichstr = "which($nameparent, $pttstr)" - can1, exc1 = can_eval(topmod, whichstr, check_eval) - if can1 - ttstr = tuplestring(paramrepr) - pcstr = has_bodyfunction ? """ - let fbody = try Base.bodyfunction($whichstr) catch missing end - if !ismissing(fbody) - precompile($fstr, $ttstr) - end - end""" : """ - let fbody = try __lookup_kwbody__($whichstr) catch missing end - if !ismissing(fbody) - precompile($fstr, $ttstr) - end - end""" # extra indentation because `write` will indent 1st line - can2, exc2 = can_eval(topmod, pcstr, check_eval) - if can2 - return pcstr - else - @debug "Module $topmod: skipping $tt due to kwbody lookup failure" exception=exc2 _module=topmod _file="precompile_$topmod.jl" - end - else - @debug "Module $topmod: skipping $tt due to kwbody caller lookup failure" exception=exc1 _module=topmod _file="precompile_$topmod.jl" - end - return nothing -end - -function parcel(tinf::AbstractVector{Tuple{Float64, Core.MethodInstance}}; - subst = Vector{Pair{String, String}}(), - exclusions = String[], - remove_exclusions::Bool = true, - check_eval::Bool = true, - has_bodyfunction::Bool = false, # can set to true if your package only supports Julia 1.6+ - blacklist=nothing, # deprecated keyword - remove_blacklist=nothing) # deprecated keyword - - if blacklist !== nothing - Base.depwarn("`blacklist` is deprecated, please use `exclusions` to pass a list of excluded names", :parcel) - append!(exclusions, blacklist) - end - if remove_blacklist !== nothing - Base.depwarn("`remove_blacklist` is deprecated, please use `remove_exclusions` to pass a list of excluded names", :parcel) - remove_exclusions = remove_blacklist - end - - pc = Dict{Symbol, Set{String}}() # output - modgens = Dict{Module, Vector{Method}}() # methods with generators in a module - mods = OrderedSet{Module}() # module of each parameter for a given method - sym_module = Dict{Symbol, Module}() # 1-1 association between modules and module name - for (_, mi) in reverse(tinf) - isdefined(mi, :specTypes) || continue - tt = mi.specTypes - m = mi.def - isa(m, Method) || continue - # Determine which module to assign this method to. All the types in the arguments - # need to be defined; we collect all the corresponding modules and assign it to the - # "topmost". - empty!(mods) - mroot = Base.moduleroot(m.module) - push!(mods, mroot) - addmodules!(mods, tt.parameters) - topmod = topmodule(mods) - if topmod === nothing - @debug "Skipping $tt due to lack of a suitable top module" - continue - elseif topmod !== mroot - @debug "Skipping $tt due to lack of method ownership" - continue - end - # If we haven't yet started the list for this module, initialize - topmodname = nameof(topmod) - sym_module[topmodname] = topmod - if !haskey(pc, topmodname) - pc[topmodname] = Set{String}() - # For testing our precompile directives, we might need to have lookup available - if VERSION >= v"1.4.0-DEV.215" && topmod !== Core && !isdefined(topmod, :__bodyfunction__) - Core.eval(topmod, lookup_kwbody_ex) - end - end - add_repr!(pc[topmodname], modgens, mi, topmod; check_eval=check_eval, has_bodyfunction=has_bodyfunction) - end - - # loop over the output - for mod in keys(pc) - # exclusions remover - if remove_exclusions - pc[mod] = exclusions_remover!(pc[mod], exclusions) - end - end - return Dict(mod=>collect(lines) for (mod, lines) in pc) # convert Set to Array before return -end - function add_repr!(list, modgens::Dict{Module, Vector{Method}}, mi::MethodInstance, topmod::Module=mi.def.module; check_eval::Bool, time=nothing, kwargs...) # Create the string representation of the signature # Use special care with keyword functions, anonymous functions @@ -423,35 +139,103 @@ function add_repr!(list, modgens::Dict{Module, Vector{Method}}, mi::MethodInstan return add_if_evals!(list, topmod, reprcontext(topmod, p), paramrepr, tt, check_eval = check_eval, time=time) end -""" - exclusions_remover!(pcI, exclusions) - -Search and removes terms appearing in `exclusions` from `pcI`. +function handle_kwbody(topmod::Module, m::Method, paramrepr, tt, fstr="fbody"; check_eval = true, has_bodyfunction::Bool=false) + nameparent = Symbol(match(r"^#([^#]*)#", String(m.name)).captures[1]) + if !isdefined(m.module, nameparent) + @debug "Module $topmod: skipping $m due to inability to look up kwbody parent" # see example related to issue #237 + return nothing + end + fparent = getfield(m.module, nameparent) + pttstr = tuplestring(paramrepr[m.nkw+2:end]) + whichstr = "which($nameparent, $pttstr)" + can1, exc1 = can_eval(topmod, whichstr, check_eval) + if can1 + ttstr = tuplestring(paramrepr) + pcstr = has_bodyfunction ? """ + let fbody = try Base.bodyfunction($whichstr) catch missing end + if !ismissing(fbody) + precompile($fstr, $ttstr) + end + end""" : """ + let fbody = try __lookup_kwbody__($whichstr) catch missing end + if !ismissing(fbody) + precompile($fstr, $ttstr) + end + end""" # extra indentation because `write` will indent 1st line + can2, exc2 = can_eval(topmod, pcstr, check_eval) + if can2 + return pcstr + else + @debug "Module $topmod: skipping $tt due to kwbody lookup failure" exception=exc2 _module=topmod _file="precompile_$topmod.jl" + end + else + @debug "Module $topmod: skipping $tt due to kwbody caller lookup failure" exception=exc1 _module=topmod _file="precompile_$topmod.jl" + end + return nothing +end -By default it considers some strings as exclusions such as `r"\\bMain\\b"`. +tupletypestring(params) = "Tuple{" * join(params, ',') * '}' +tupletypestring(fstr::AbstractString, params::AbstractVector{<:AbstractString}) = + tupletypestring([fstr; params]) -# Examples -```julia -exclusions = Set(["hi","bye"]) -pcI = Set(["good","bad","hi","bye","no"]) +tuplestring(params) = isempty(params) ? "()" : '(' * join(params, ',') * ",)" -SnoopCompile.exclusions_remover!(pcI, exclusions) -``` """ -function exclusions_remover!(pcI::AbstractSet, exclusions) - all_exclusions = union(exclusions, default_exclusions) + can_eval(mod::Module, str::AbstractString, check_eval::Bool=true) + +Checks if the precompilation statement can be evaled. - # We can't just use `setdiff!` because this is a substring search - todelete = Set{eltype(pcI)}() - for line in pcI - if any(occursin.(all_exclusions, line)) - push!(todelete, line) +In some cases, you may want to bypass this function by passing `check_eval=true` to increase the snooping performance. +""" +function can_eval(mod::Module, str::AbstractString, check_eval::Bool=true) + if check_eval + try + ex = Meta.parse(str) + if mod === Core + #https://github.com/timholy/SnoopCompile.jl/issues/76 + Core.eval(Main, ex) + else + Core.eval(mod, ex) + end + catch e + return false, e end end - return setdiff!(pcI, todelete) + return true, nothing +end + +""" + add_if_evals!(pclist, mod::Module, fstr, params, tt; prefix = "", check_eval::Bool=true) + +Adds the precompilation statements only if they can be evaled. It uses [`can_eval`](@ref) internally. + +In some cases, you may want to bypass this function by passing `check_eval=true` to increase the snooping performance. +""" +function add_if_evals!(pclist, mod::Module, fstr, params, tt; prefix = "", check_eval::Bool=true, time=nothing) + ttstr = tupletypestring(fstr, params) + can, exc = can_eval(mod, ttstr, check_eval) + if can + push!(pclist, append_time(prefix*wrap_precompile(ttstr), time)) + return true + else + @debug "Module $mod: skipping $tt due to eval failure" exception=exc _module=mod _file="precompile_$mod.jl" + end + return false end -# These are found by running `exhaustive_remover!` on some packages + +append_time(str, ::Nothing) = str +append_time(str, t::AbstractFloat) = str * " # time: " * string(Float32(t)) + +wrap_precompile(ttstr::AbstractString) = "Base.precompile(" * ttstr * ')' # use `Base.` to avoid conflict with Core and Pkg + const default_exclusions = Set([ r"\bMain\b", ]) + +function split2(str, on) + i = findfirst(isequal(on), str) + i === nothing && return str, "" + return (SubString(str, firstindex(str), prevind(str, first(i))), + SubString(str, nextind(str, last(i)))) +end diff --git a/test/runtests.jl b/test/runtests.jl index 7e5f61787..80efe551c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -4,3 +4,7 @@ using SnoopCompile include("snoopi_deep.jl") include("snoopl.jl") include("snoopr.jl") + +# otherwise-untested demos +retflat = SnoopCompile.flatten_demo() +@test !isempty(retflat.children) diff --git a/test/snoopi_deep.jl b/test/snoopi_deep.jl index 6382cda78..cfebf19aa 100644 --- a/test/snoopi_deep.jl +++ b/test/snoopi_deep.jl @@ -335,18 +335,6 @@ end SnoopCompile.show_suggest(io, cats, nothing, nothing) @test occursin("non-inferrable or unspecialized call", String(take!(io))) - # UnspecType - if VERSION < v"1.9.0-DEV" # on 1.9 there is no trigger assocated with the partial type call - M = Module() - @eval M begin - struct Container{L,T} x::T end - Container(x::T) where {T} = Container{length(x),T}(x) - end - cats = categories(@snoopi_deep M.Container([1,2,3])) - @test cats == [SnoopCompile.FromTestCallee, SnoopCompile.UnspecType] - SnoopCompile.show_suggest(io, cats, nothing, nothing) - @test occursin("partial type call", String(take!(io))) - end M = Module() @eval M begin struct Typ end @@ -638,7 +626,7 @@ end fg = SnoopCompile.flamegraph(tinf) fgnodes = collect(AbstractTrees.PreOrderDFS(fg)) - for tgtname in (VERSION < v"1.7" ? (:h, :i) : (:h, :i, :+)) + for tgtname in (:h, :i, :+) @test mapreduce(|, fgnodes; init=false) do node node.data.sf.linfo.def.name == tgtname end @@ -650,7 +638,7 @@ end @test leaf.data.span.stop in fg.data.span has_constprop |= leaf.data.status & FlameGraphs.gc_event != 0x0 end - VERSION >= v"1.7" && @test has_constprop + @test has_constprop frame1, frame2 = frames[1], frames[2] t1, t2 = inclusive(frame1), inclusive(frame2) @@ -805,18 +793,6 @@ end Ts = subtypes(Any) tinf_unspec = @snoopi_deep SnoopBench.mappushes(SnoopBench.spell_unspec, Ts) tf_unspec = flatten(tinf_unspec) - if VERSION < v"1.8.0-DEV.1148" - # To ensure independent data, invalidate all compiled CodeInstances - mis = map(last, accumulate_by_source(MethodInstance, tf_unspec)) - for mi in mis - SnoopCompile.isROOT(mi) && continue - visit(mi) do item - isa(item, Core.CodeInstance) || return true - item.max_world = 0 - return true - end - end - end tinf_spec = @snoopi_deep SnoopBench.mappushes(SnoopBench.spell_spec, Ts) tf_spec = flatten(tinf_spec) @test length(tf_unspec) < length(Ts) ÷ 5 @@ -882,80 +858,68 @@ end tree = length(trees) == 1 ? only(trees) : trees[findfirst(tree -> !isempty(tree.backedges), trees)] @test tree.method == which(StaleA.stale, (String,)) # defined in StaleC @test all(be -> Core.MethodInstance(be).def == which(StaleA.stale, (Any,)), tree.backedges) - if VERSION > v"1.8.0-DEV.368" - root = only(filter(tree.backedges) do be - Core.MethodInstance(be).specTypes.parameters[end] === String - end) - @test convert(Core.MethodInstance, root.children[1]).def == which(StaleB.useA, ()) - m2 = which(StaleB.useA2, ()) - if any(item -> isa(item, Core.MethodInstance) && item.def == m2, invalidations) # requires julia#49449 - @test convert(Core.MethodInstance, root.children[1].children[1]).def == m2 - end - tinf = @snoopi_deep begin - StaleB.useA() - StaleC.call_buildstale("hi") - end - @test isempty(SnoopCompile.StaleTree(first(smis).def, :noreason).backedges) # constructor test - healed = true - if VERSION < v"1.8" - healed = false - # On more recent Julia, the invalidation of StaleA.stale is "healed over" by re-inferrence - # within StaleC. Hence we should skip this test. - strees = precompile_blockers(invalidations, tinf) - tree = only(strees) - @test inclusive(tree) > 0 - @test tree.method == which(StaleA.stale, (String,)) - root, hits = only(tree.backedges) - @test Core.MethodInstance(root).def == which(StaleA.stale, (Any,)) - @test Core.MethodInstance(only(hits)).def == which(StaleA.use_stale, (Vector{Any},)) - end - # If we don't discount ones left in an invalidated state, - # we get mt_backedges with a MethodInstance middle entry too - strees2 = precompile_blockers(invalidations, tinf; min_world_exclude=0) - root, hits = only(only(strees2).backedges) - mi_stale = only(filter(mi -> endswith(String(mi.def.file), "StaleA.jl"), methodinstances(StaleA.stale, (String,)))) - @test Core.MethodInstance(root) == mi_stale - @test Core.MethodInstance(only(hits)) == methodinstance(StaleB.useA, ()) - # What happens when we can't find it in the tree? - if any(isequal("verify_methods"), invalidations) - # The 1.9+ format - invscopy = copy(invalidations) - idx = findlast(==("verify_methods"), invscopy) - invscopy[idx+1] = 22 - redirect_stderr(devnull) do - broken_trees = invalidation_trees(invscopy) - @test isempty(precompile_blockers(broken_trees, tinf)) - end - else - # The older format - idx = findfirst(isequal("jl_method_table_insert"), invalidations) - redirect_stdout(devnull) do - broken_trees = invalidation_trees(invalidations[idx+1:end]) - @test isempty(precompile_blockers(broken_trees, tinf)) - end + + root = only(filter(tree.backedges) do be + Core.MethodInstance(be).specTypes.parameters[end] === String + end) + @test convert(Core.MethodInstance, root.children[1]).def == which(StaleB.useA, ()) + m2 = which(StaleB.useA2, ()) + if any(item -> isa(item, Core.MethodInstance) && item.def == m2, invalidations) # requires julia#49449 + @test convert(Core.MethodInstance, root.children[1].children[1]).def == m2 + end + tinf = @snoopi_deep begin + StaleB.useA() + StaleC.call_buildstale("hi") + end + @test isempty(SnoopCompile.StaleTree(first(smis).def, :noreason).backedges) # constructor test + healed = true + # If we don't discount ones left in an invalidated state, + # we get mt_backedges with a MethodInstance middle entry too + strees2 = precompile_blockers(invalidations, tinf; min_world_exclude=0) + root, hits = only(only(strees2).backedges) + mi_stale = only(filter(mi -> endswith(String(mi.def.file), "StaleA.jl"), methodinstances(StaleA.stale, (String,)))) + @test Core.MethodInstance(root) == mi_stale + @test Core.MethodInstance(only(hits)) == methodinstance(StaleB.useA, ()) + # What happens when we can't find it in the tree? + if any(isequal("verify_methods"), invalidations) + # The 1.9+ format + invscopy = copy(invalidations) + idx = findlast(==("verify_methods"), invscopy) + invscopy[idx+1] = 22 + redirect_stderr(devnull) do + broken_trees = invalidation_trees(invscopy) + @test isempty(precompile_blockers(broken_trees, tinf)) end - # IO - io = IOBuffer() - print(io, trees) - @test occursin(r"stale\(x::String\) (in|@) StaleC", String(take!(io))) - if !healed - print(io, strees) - str = String(take!(io)) - @test occursin(r"inserting stale.* in StaleC.*invalidated:", str) - @test occursin(r"blocked.*InferenceTimingNode: .*/.* on StaleA.use_stale", str) - @test endswith(str, "\n]") - print(IOContext(io, :compact=>true), strees) - str = String(take!(io)) - @test endswith(str, ";]") - SnoopCompile.printdata(io, [hits; hits]) - @test occursin("inclusive time for 2 nodes", String(take!(io))) + else + # The older format + idx = findfirst(isequal("jl_method_table_insert"), invalidations) + redirect_stdout(devnull) do + broken_trees = invalidation_trees(invalidations[idx+1:end]) + @test isempty(precompile_blockers(broken_trees, tinf)) end - print(io, only(strees2)) + end + # IO + io = IOBuffer() + print(io, trees) + @test occursin(r"stale\(x::String\) (in|@) StaleC", String(take!(io))) + if !healed + print(io, strees) + str = String(take!(io)) + @test occursin(r"inserting stale.* in StaleC.*invalidated:", str) + @test occursin(r"blocked.*InferenceTimingNode: .*/.* on StaleA.use_stale", str) + @test endswith(str, "\n]") + print(IOContext(io, :compact=>true), strees) str = String(take!(io)) - @test occursin(r"inserting stale\(.* (in|@) StaleC.*invalidated:", str) - @test !occursin("mt_backedges", str) - @test occursin(r"blocked.*InferenceTimingNode: .*/.* on StaleB.useA", str) + @test endswith(str, ";]") + SnoopCompile.printdata(io, [hits; hits]) + @test occursin("inclusive time for 2 nodes", String(take!(io))) end + print(io, only(strees2)) + str = String(take!(io)) + @test occursin(r"inserting stale\(.* (in|@) StaleC.*invalidated:", str) + @test !occursin("mt_backedges", str) + @test occursin(r"blocked.*InferenceTimingNode: .*/.* on StaleB.useA", str) + Pkg.activate(cproj) end From 372d05460b759dca03ee2ec2f9ad3f55d53f44d4 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Tue, 16 Apr 2024 10:46:19 -0500 Subject: [PATCH 03/10] Update NEWS --- NEWS.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/NEWS.md b/NEWS.md index ce5e5d1f7..013b5209f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -30,3 +30,11 @@ This version implements major changes in how `parcel` works on the output of `@s Other changes: - A convenience utility, `timesum`, was introduced (credit to aminya) + +## Version 3 + +Version 3 is a greatly slimmed repository, focusing just on those tools that are relevant for modern Julia development. +Users of tools that were removed should stick with SnoopCompile 2.x. + +- The old `@snoopc` and `@snoopi` have been deleted. +- SnoopCompileBot and SnoopPrecompile have been deleted. From e7628753d6023c63a154c6d427a0248189e52676 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Tue, 16 Apr 2024 10:46:54 -0500 Subject: [PATCH 04/10] Update SnoopCompileCore --- Project.toml | 2 +- SnoopCompileCore/Project.toml | 2 +- SnoopCompileCore/src/SnoopCompileCore.jl | 23 +---- SnoopCompileCore/src/snoopc.jl | 48 ----------- SnoopCompileCore/src/snoopi.jl | 104 ----------------------- SnoopCompileCore/src/snoopi_deep.jl | 2 + 6 files changed, 7 insertions(+), 174 deletions(-) delete mode 100644 SnoopCompileCore/src/snoopc.jl delete mode 100644 SnoopCompileCore/src/snoopi.jl diff --git a/Project.toml b/Project.toml index 4fd382e68..f0db9d9d5 100644 --- a/Project.toml +++ b/Project.toml @@ -33,7 +33,7 @@ AbstractTrees = "0.4" Cthulhu = "2" FlameGraphs = "1" OrderedCollections = "1" -SnoopCompileCore = "~2.10.0" +SnoopCompileCore = "3" YAML = "0.4" julia = "1.10" diff --git a/SnoopCompileCore/Project.toml b/SnoopCompileCore/Project.toml index 5f2963cea..e363c8483 100644 --- a/SnoopCompileCore/Project.toml +++ b/SnoopCompileCore/Project.toml @@ -1,7 +1,7 @@ name = "SnoopCompileCore" uuid = "e2b509da-e806-4183-be48-004708413034" author = ["Tim Holy "] -version = "2.10.1" +version = "3.0.0" [deps] Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b" diff --git a/SnoopCompileCore/src/SnoopCompileCore.jl b/SnoopCompileCore/src/SnoopCompileCore.jl index 032dd8a8a..e71c71e85 100644 --- a/SnoopCompileCore/src/SnoopCompileCore.jl +++ b/SnoopCompileCore/src/SnoopCompileCore.jl @@ -2,25 +2,8 @@ module SnoopCompileCore using Core: MethodInstance, CodeInfo -# @snoopi and @snoopc are exported from their files of definition - - -include("snoopc.jl") - -if VERSION >= v"1.2.0-DEV.573" - include("snoopi.jl") -end - -if VERSION >= v"1.6.0-DEV.1190" # https://github.com/JuliaLang/julia/pull/37749 - include("snoopi_deep.jl") -end - -if VERSION >= v"1.6.0-DEV.154" - include("snoopr.jl") -end - -if VERSION >= v"1.6.0-DEV.1192" # https://github.com/JuliaLang/julia/pull/37136 - include("snoopl.jl") -end +include("snoopi_deep.jl") +include("snoopr.jl") +include("snoopl.jl") end diff --git a/SnoopCompileCore/src/snoopc.jl b/SnoopCompileCore/src/snoopc.jl deleted file mode 100644 index 08dc2c77d..000000000 --- a/SnoopCompileCore/src/snoopc.jl +++ /dev/null @@ -1,48 +0,0 @@ -export @snoopc - -using Serialization - -""" -``` -@snoopc "compiledata.csv" begin - # Commands to execute, in a new process -end -``` -causes the julia compiler to log all functions compiled in the course -of executing the commands to the file "compiledata.csv". This file -can be used for the input to `SnoopCompile.read`. -""" -macro snoopc(flags, filename, commands) - return :(snoopc($(esc(flags)), $(esc(filename)), $(QuoteNode(commands)))) -end -macro snoopc(filename, commands) - return :(snoopc(String[], $(esc(filename)), $(QuoteNode(commands)))) -end - -function snoopc(flags, filename, commands) - println("Launching new julia process to run commands...") - # addprocs will run the unmodified version of julia, so we - # launch it as a command. - code_object = """ - using Serialization - while !eof(stdin) - Core.eval(Main, deserialize(stdin)) - end - """ - process = open(`$(Base.julia_cmd()) $flags --eval $code_object`, stdout, write=true) - serialize(process, quote - let io = open($filename, "w") - ccall(:jl_dump_compiles, Nothing, (Ptr{Nothing},), io.handle) - try - $commands - finally - ccall(:jl_dump_compiles, Nothing, (Ptr{Nothing},), C_NULL) - close(io) - end - end - exit() - end) - wait(process) - println("done.") - nothing -end diff --git a/SnoopCompileCore/src/snoopi.jl b/SnoopCompileCore/src/snoopi.jl deleted file mode 100644 index 8e204cd99..000000000 --- a/SnoopCompileCore/src/snoopi.jl +++ /dev/null @@ -1,104 +0,0 @@ -export @snoopi, @snoopi_deep - -const __inf_timing__ = Tuple{Float64,MethodInstance}[] - -if isdefined(Core.Compiler, :Params) - function typeinf_ext_timed(linfo::Core.MethodInstance, params::Core.Compiler.Params) - tstart = time() - ret = Core.Compiler.typeinf_ext(linfo, params) - tstop = time() - push!(__inf_timing__, (tstop-tstart, linfo)) - return ret - end - function typeinf_ext_timed(linfo::Core.MethodInstance, world::UInt) - tstart = time() - ret = Core.Compiler.typeinf_ext(linfo, world) - tstop = time() - push!(__inf_timing__, (tstop-tstart, linfo)) - return ret - end - @noinline stop_timing() = ccall(:jl_set_typeinf_func, Cvoid, (Any,), Core.Compiler.typeinf_ext) -else - function typeinf_ext_timed(interp::Core.Compiler.AbstractInterpreter, linfo::Core.MethodInstance) - tstart = time() - ret = Core.Compiler.typeinf_ext_toplevel(interp, linfo) - tstop = time() - push!(__inf_timing__, (tstop-tstart, linfo)) - return ret - end - function typeinf_ext_timed(linfo::Core.MethodInstance, world::UInt) - tstart = time() - ret = Core.Compiler.typeinf_ext_toplevel(linfo, world) - tstop = time() - push!(__inf_timing__, (tstop-tstart, linfo)) - return ret - end - @noinline stop_timing() = ccall(:jl_set_typeinf_func, Cvoid, (Any,), Core.Compiler.typeinf_ext_toplevel) -end - -@noinline start_timing() = ccall(:jl_set_typeinf_func, Cvoid, (Any,), typeinf_ext_timed) - -function sort_timed_inf(tmin) - data = __inf_timing__ - if tmin > 0.0 - data = filter(tl->tl[1] >= tmin, data) - end - return sort(data; by=tl->tl[1]) -end - -""" - inf_timing = @snoopi commands - inf_timing = @snoopi tmin=0.0 commands - -Execute `commands` while snooping on inference. Returns an array of `(t, linfo)` -tuples, where `t` is the amount of time spent inferring `linfo` (a `MethodInstance`). - -Methods that take less time than `tmin` will not be reported. -""" -macro snoopi(args...) - tmin = 0.0 - if length(args) == 1 - cmd = args[1] - elseif length(args) == 2 - a = args[1] - if isa(a, Expr) && a.head == :(=) && a.args[1] == :tmin - tmin = a.args[2] - cmd = args[2] - else - error("unrecognized input ", a) - end - else - error("at most two arguments are supported") - end - return _snoopi(cmd, tmin) -end - -function _snoopi(cmd::Expr, tmin = 0.0) - return quote - empty!(__inf_timing__) - start_timing() - try - $(esc(cmd)) - finally - stop_timing() - end - $sort_timed_inf($tmin) - end -end - -function __init__() - # typeinf_ext_timed must be compiled before it gets run - # We do this in __init__ to make sure it gets compiled to native code - # (the *.ji file stores only the inferred code) - if isdefined(Core.Compiler, :Params) - precompile(typeinf_ext_timed, (Core.MethodInstance, Core.Compiler.Params)) || error("precompilation of typeinf modifiers is not allowed to fail") - precompile(typeinf_ext_timed, (Core.MethodInstance, UInt)) || error("precompilation of typeinf modifiers is not allowed to fail") - else - precompile(typeinf_ext_timed, (Core.Compiler.NativeInterpreter, Core.MethodInstance)) || error("precompilation of typeinf modifiers is not allowed to fail") - precompile(typeinf_ext_timed, (Core.MethodInstance, UInt)) || error("precompilation of typeinf modifiers is not allowed to fail") - end - precompile(start_timing, ()) - precompile(stop_timing, ()) - - nothing -end diff --git a/SnoopCompileCore/src/snoopi_deep.jl b/SnoopCompileCore/src/snoopi_deep.jl index 3719cf84e..4cb45a327 100644 --- a/SnoopCompileCore/src/snoopi_deep.jl +++ b/SnoopCompileCore/src/snoopi_deep.jl @@ -1,3 +1,5 @@ +export @snoopi_deep + struct InferenceTiming mi_info::Core.Compiler.Timings.InferenceFrameInfo inclusive_time::Float64 From bca07ddd4c2b2c9407b18891606780771bb37366 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Tue, 16 Apr 2024 10:51:59 -0500 Subject: [PATCH 05/10] Eliminate cruft --- src/invalidations.jl | 80 +++++++++++-------------------------- src/utils.jl | 21 +++------- test/snoopi_deep.jl | 43 ++++++++++---------- test/snoopr.jl | 93 ++++++++++---------------------------------- 4 files changed, 68 insertions(+), 169 deletions(-) diff --git a/src/invalidations.jl b/src/invalidations.jl index 7a41cb2e3..bd1963c80 100644 --- a/src/invalidations.jl +++ b/src/invalidations.jl @@ -1,7 +1,5 @@ export uinvalidated, invalidation_trees, filtermod, findcaller -const have_verify_methods = Base.VERSION >= v"1.9.0-DEV.1512" || Base.VERSION >= v"1.8.4" - function from_corecompiler(mi::MethodInstance) fn = fullname(mi.def.module) length(fn) < 2 && return false @@ -272,11 +270,7 @@ function showlist(io::IO, treelist, indent::Int=0) end end -if have_verify_methods - new_backedge_table() = Dict{Union{Int32,MethodInstance},Union{Tuple{Any,Vector{Any}},InstanceNode}}() -else - new_backedge_table() = Dict{Tuple{Int32,UInt64},Tuple{Any,Vector{Any}}}() -end +new_backedge_table() = Dict{Union{Int32,MethodInstance},Union{Tuple{Any,Vector{Any}},InstanceNode}}() """ report_invalidations( @@ -358,32 +352,8 @@ See the documentation for further details. function invalidation_trees(list; exclude_corecompiler::Bool=true) function handle_insert_backedges(list, i, callee) - if have_verify_methods - key, causes = list[i+=1], list[i+=1] - backedge_table[key] = (callee, causes) - return i - end - if Base.VERSION >= v"1.9.0-DEV.1432" - key = (list[i+=1], list[i+=1]) - backedge_table[key] = (callee, list[i+=1]) - return i - end - - ncovered = 0 - callees = Any[callee] - i0 = i - while length(list) >= i+2 && list[i+2] == "insert_backedges_callee" - push!(callees, list[i+1]) - i += 2 - end - callers = MethodInstance[] - while length(list) >= i+2 && list[i+2] == "insert_backedges" - push!(callers, list[i+1]) - i += 2 - ncovered += 1 - end - push!(delayed, callees => callers) - i > i0 && @assert ncovered > 0 + key, causes = list[i+=1], list[i+=1] + backedge_table[key] = (callee, causes) return i end @@ -414,7 +384,7 @@ function invalidation_trees(list; exclude_corecompiler::Bool=true) end elseif isa(item, String) loctag = item - if Base.VERSION >= v"1.9.0-DEV.1512" && loctag ∉ ("insert_backedges_callee", "verify_methods") && inserted_backedges + if loctag ∉ ("insert_backedges_callee", "verify_methods") && inserted_backedges # The integer index resets between packages, clear all with integer keys ikeys = collect(Iterators.filter(x -> isa(x, Integer), keys(backedge_table))) for key in ikeys @@ -502,32 +472,26 @@ function invalidation_trees(list; exclude_corecompiler::Bool=true) end end elseif loctag == "insert_backedges" - if Base.VERSION >= v"1.9.0-DEV.1432" - key = (list[i+=1], list[i+=1]) - trig, causes = backedge_table[key] - if leaf !== nothing - root = getroot(leaf) - root.mi = mi - if trig isa MethodInstance - oldroot = root - root = InstanceNode(trig, [root]) - oldroot.parent = root - push!(backedges, root) - else - push!(mt_backedges, trig=>root) - end - end - for cause in causes - add_method_trigger!(methodinvs, cause, :inserting, mt_backedges, backedges, mt_cache, mt_disable) + key = (list[i+=1], list[i+=1]) + trig, causes = backedge_table[key] + if leaf !== nothing + root = getroot(leaf) + root.mi = mi + if trig isa MethodInstance + oldroot = root + root = InstanceNode(trig, [root]) + oldroot.parent = root + push!(backedges, root) + else + push!(mt_backedges, trig=>root) end - mt_backedges, backedges, mt_cache, mt_disable = methinv_storage() - leaf = nothing - reason = nothing - else - # pre Julia 1.8 - Base.VERSION < v"1.8.0-DEV.368" || error("unexpected failure at ", i) - @assert leaf === nothing end + for cause in causes + add_method_trigger!(methodinvs, cause, :inserting, mt_backedges, backedges, mt_cache, mt_disable) + end + mt_backedges, backedges, mt_cache, mt_disable = methinv_storage() + leaf = nothing + reason = nothing else error("unexpected loctag ", loctag, " at ", i) end diff --git a/src/utils.jl b/src/utils.jl index 7f9fc021d..8b2d9e1b9 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -79,10 +79,6 @@ function add_repr!(list, modgens::Dict{Module, Vector{Method}}, mi::MethodInstan isgen = match(genrex, mname) !== nothing isanon = match(anonrex, mname) !== nothing || match(innerrex, mname) !== nothing isgen && (mkwbody = nothing) - if VERSION < v"1.4.0-DEV.215" # before this version, we can't robustly look up kwbody callers (missing `nkw`) - isanon |= mkwbody !== nothing # treat kwbody methods the same way we treat anonymous functions - mkwbody = nothing - end if mkw !== nothing # Keyword function fname = mkw.captures[1] === nothing ? mkw.captures[2] : mkw.captures[1] @@ -113,18 +109,11 @@ function add_repr!(list, modgens::Dict{Module, Vector{Method}}, mi::MethodInstan getgen = "typeof(which($(caller.name),$csigstr).generator.gen)" return add_if_evals!(list, topmod, getgen, paramrepr, tt; check_eval=check_eval, time=time) else - if VERSION >= v"1.4.0-DEV.215" - getgen = "which(Core.kwfunc($(mkwc.captures[1])),$csigstr).generator.gen" - ret = handle_kwbody(topmod, caller, cparamrepr, tt; check_eval = check_eval, kwargs...) #, getgen) - if ret !== nothing - push!(list, append_time(ret, time)) - return true - end - else - # Bail and treat as if anonymous - prefix = "isdefined($mmod, Symbol(\"$mname\")) && " - fstr = "getfield($mmod, Symbol(\"$mname\"))" # this is universal, var is Julia 1.3+ - return add_if_evals!(list, topmod, fstr, paramrepr, tt; prefix=prefix, check_eval=check_eval, time=time) + getgen = "which(Core.kwfunc($(mkwc.captures[1])),$csigstr).generator.gen" + ret = handle_kwbody(topmod, caller, cparamrepr, tt; check_eval = check_eval, kwargs...) #, getgen) + if ret !== nothing + push!(list, append_time(ret, time)) + return true end end break diff --git a/test/snoopi_deep.jl b/test/snoopi_deep.jl index cfebf19aa..89a63a120 100644 --- a/test/snoopi_deep.jl +++ b/test/snoopi_deep.jl @@ -923,29 +923,26 @@ end Pkg.activate(cproj) end -if VERSION >= v"1.7" - using JET, Cthulhu -end -@static if VERSION >= v"1.7" - @testset "JET integration" begin - function mysum(c) # vendor a simple version of `sum` - isempty(c) && return zero(eltype(c)) - s = first(c) - for x in Iterators.drop(c, 1) - s += x - end - return s +using JET, Cthulhu + +@testset "JET integration" begin + function mysum(c) # vendor a simple version of `sum` + isempty(c) && return zero(eltype(c)) + s = first(c) + for x in Iterators.drop(c, 1) + s += x end - call_mysum(cc) = mysum(cc[1]) - - cc = Any[Any[1,2,3]] - tinf = @snoopi_deep call_mysum(cc) - rpt = @report_call call_mysum(cc) - @test isempty(JET.get_reports(rpt)) - itrigs = inference_triggers(tinf) - irpts = report_callees(itrigs) - @test only(irpts).first == last(itrigs) - @test !isempty(JET.get_reports(only(irpts).second)) - @test isempty(JET.get_reports(report_caller(itrigs[end]))) + return s end + call_mysum(cc) = mysum(cc[1]) + + cc = Any[Any[1,2,3]] + tinf = @snoopi_deep call_mysum(cc) + rpt = @report_call call_mysum(cc) + @test isempty(JET.get_reports(rpt)) + itrigs = inference_triggers(tinf) + irpts = report_callees(itrigs) + @test only(irpts).first == last(itrigs) + @test !isempty(JET.get_reports(only(irpts).second)) + @test isempty(JET.get_reports(report_caller(itrigs[end]))) end diff --git a/test/snoopr.jl b/test/snoopr.jl index c2ffd424d..605c57c52 100644 --- a/test/snoopr.jl +++ b/test/snoopr.jl @@ -1,7 +1,6 @@ using SnoopCompile, InteractiveUtils, MethodAnalysis, Pkg, Test import PrettyTables # so that the report_invalidations.jl file is loaded -const qualify_mi = Base.VERSION >= v"1.7.0-DEV.5" # julia PR #38608 module SnooprTests f(x::Int) = 1 @@ -47,7 +46,7 @@ end @testset "@snoopr" begin - prefix = qualify_mi ? "$(@__MODULE__).SnooprTests." : "" + prefix = "$(@__MODULE__).SnooprTests." c = Any[1] @test SnooprTests.callapplyf(c) == 1 @@ -240,9 +239,6 @@ end invs = @snoopr (::Type{T})(x::SnooprTests.MyInt) where T<:Integer = T(x.x) umis1 = uinvalidated(invs) umis2 = uinvalidated(invs; exclude_corecompiler=false) - if Base.VERSION < v"1.8.0-DEV" - @test length(umis2) > length(umis1) + 20 - end # recursive filtermod list = Union{Int,String}[1,2] @@ -256,75 +252,28 @@ end end @testset "Delayed invalidations" begin - if SnoopCompile.have_verify_methods - cproj = Base.active_project() - cd(joinpath(@__DIR__, "testmodules", "Invalidation")) do - Pkg.activate(pwd()) - Pkg.develop(path="./PkgC") - Pkg.develop(path="./PkgD") - Pkg.precompile() - invalidations = @snoopr begin - @eval begin - using PkgC - PkgC.nbits(::UInt8) = 8 - using PkgD - end - end - tree = only(invalidation_trees(invalidations)) - @test tree.reason == :inserting - @test tree.method.file == Symbol(@__FILE__) - @test isempty(tree.backedges) - sig, root = only(tree.mt_backedges) - @test sig.parameters[1] === typeof(PkgC.nbits) - @test sig.parameters[2] === Integer - @test root.mi == first(SnoopCompile.specializations(only(methods(PkgD.call_nbits)))) - end - Pkg.activate(cproj) - elseif Base.VERSION >= v"1.7.0-DEV.254" # julia#39132 (redirect to Pipe) - # "Natural" tests are performed in the "Stale" testset of "snoopi_deep.jl" - # because they are also used for precompile_blockers. - # Here we craft them artificially. - M = @eval Module() begin - fake1(x) = 1 - fake2(x) = fake1(x) - fake3() = nothing - foo() = nothing - @__MODULE__ - end - M.fake2('a') - M.fake3() - callee = methodinstance(M.fake1, (Char,)) - caller = methodinstance(M.fake2, (Char,)) - othercallee = methodinstance(M.fake3, ()) - # failed attribution (no invalidations occurred prior to the backedges invalidations) - invalidations = Any[callee, "insert_backedges_callee", othercallee, "insert_backedges_callee", caller, "insert_backedges"] - pipe = Pipe() - redirect_stdout(pipe) do - @test_logs (:warn, "Could not attribute the following delayed invalidations:") begin - trees = invalidation_trees(invalidations) - @test isempty(trees) + cproj = Base.active_project() + cd(joinpath(@__DIR__, "testmodules", "Invalidation")) do + Pkg.activate(pwd()) + Pkg.develop(path="./PkgC") + Pkg.develop(path="./PkgD") + Pkg.precompile() + invalidations = @snoopr begin + @eval begin + using PkgC + PkgC.nbits(::UInt8) = 8 + using PkgD end end - close(pipe.in) - str = read(pipe.out, String) - @test occursin(r"fake1\(::Char\).*invalidated.*fake2\(::Char\)", str) - - m = which(M.foo, ()) - invalidations = Any[Any[caller, Int32(1), callee, "jl_method_table_insert", m, "jl_method_table_insert"]; invalidations] - tree = @test_nowarn only(invalidation_trees(invalidations)) - @test tree.method == m + tree = only(invalidation_trees(invalidations)) @test tree.reason == :inserting - mi1, mi2 = tree.mt_backedges[1] - @test mi1 == callee - @test mi2.mi == caller - @test Core.MethodInstance(tree.backedges[1]) == callee - io = IOBuffer() - print(io, tree) - str = String(take!(io)) - @test occursin(r"fake1\(x\) (in|@).*formerly fake1\(x\) (in|@)", str) - Base.delete_method(callee.def) - print(io, tree) - str = String(take!(io)) - @test occursin(r"\(unavailable\).*formerly fake1\(x\) (in|@)", str) + @test tree.method.file == Symbol(@__FILE__) + @test isempty(tree.backedges) + sig, root = only(tree.mt_backedges) + @test sig.parameters[1] === typeof(PkgC.nbits) + @test sig.parameters[2] === Integer + @test root.mi == first(SnoopCompile.specializations(only(methods(PkgD.call_nbits)))) end + + Pkg.activate(cproj) end From 37e8fd12a649fa33d884f4c534444b384dcaaa17 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Tue, 16 Apr 2024 10:54:50 -0500 Subject: [PATCH 06/10] Remove dep on Requires --- Project.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Project.toml b/Project.toml index f0db9d9d5..ad10dae08 100644 --- a/Project.toml +++ b/Project.toml @@ -11,7 +11,6 @@ OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" Profile = "9abbd945-dff8-562f-b5e8-e1ebf5ef1b79" -Requires = "ae029012-a4dd-5104-9daa-d747884805df" Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b" SnoopCompileCore = "e2b509da-e806-4183-be48-004708413034" YAML = "ddb6d928-2868-570f-bddf-ab3f9cf99eb6" From 91d96120dc87400201ff22c1b901c0ce0b804cbd Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Tue, 16 Apr 2024 10:59:59 -0500 Subject: [PATCH 07/10] Don't test SnoopPrecompile on CI --- .github/workflows/ci.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e17177f1a..f7864323e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,13 +47,11 @@ jobs: ${{ runner.os }}-test- ${{ runner.os }}- - run: julia --project -e 'using Pkg; Pkg.develop([PackageSpec(path="SnoopCompileCore")])' - - run: julia --project -e 'using Pkg; Pkg.develop([PackageSpec(path="SnoopPrecompile")])' - uses: julia-actions/julia-buildpkg@latest - uses: julia-actions/julia-runtest@latest - - run: julia --project=SnoopPrecompile -e 'using Pkg; Pkg.test(; coverage=true)' - uses: julia-actions/julia-processcoverage@v1 with: - directories: src,SnoopCompileCore/src,SnoopPrecompile/src + directories: src,SnoopCompileCore/src - uses: codecov/codecov-action@v4 with: file: lcov.info From e31f4b6d5e18490cb0e26f25804f7f650179dad7 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Tue, 16 Apr 2024 13:07:17 -0500 Subject: [PATCH 08/10] One missed version check --- examples/OptimizeMeFixed.jl | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/OptimizeMeFixed.jl b/examples/OptimizeMeFixed.jl index cd9485854..742f63b21 100644 --- a/examples/OptimizeMeFixed.jl +++ b/examples/OptimizeMeFixed.jl @@ -62,9 +62,7 @@ function main() display(makeobjects()) end -if Base.VERSION >= v"1.4.2" - precompile(Tuple{typeof(main)}) # time: 0.4204474 - precompile(Tuple{typeof(warmup)}) -end +precompile(Tuple{typeof(main)}) # time: 0.4204474 +precompile(Tuple{typeof(warmup)}) end From 5fba1da86932d21d45c24696a7c21bb87b715ad7 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Thu, 18 Apr 2024 14:16:44 -0500 Subject: [PATCH 09/10] Explain deletions in NEWS --- NEWS.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 013b5209f..a4e6a242e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -36,5 +36,7 @@ Other changes: Version 3 is a greatly slimmed repository, focusing just on those tools that are relevant for modern Julia development. Users of tools that were removed should stick with SnoopCompile 2.x. -- The old `@snoopc` and `@snoopi` have been deleted. -- SnoopCompileBot and SnoopPrecompile have been deleted. +- The old `@snoopc` has been deleted. Its functionality was largely subsumed into `julia --trace-compile`. +-`@snoopi` has been deleted, as `@snoopi_deep` provides more comprehensive information and is available on all modern Julia versions. +- SnoopCompileBot was deleted in favor of [CompileBot](https://github.com/aminya/CompileBot.jl) +- SnoopPrecompile was deleted because it is now [PrecompileTools](https://github.com/JuliaLang/PrecompileTools.jl) From 07bcb2acc10eea15854213a1be1eefdb85f8841d Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Thu, 18 Apr 2024 14:27:05 -0500 Subject: [PATCH 10/10] Remove outdated docs and examples --- docs/make.jl | 3 -- docs/src/index.md | 9 ---- docs/src/reference.md | 2 - docs/src/snoop_pc.md | 100 ---------------------------------------- docs/src/snoopc.md | 56 ---------------------- docs/src/snoopi_deep.md | 3 -- docs/src/userimg.md | 25 ---------- examples/gadfly.jl | 17 ------- examples/images.jl | 36 --------------- 9 files changed, 251 deletions(-) delete mode 100644 docs/src/snoop_pc.md delete mode 100644 docs/src/snoopc.md delete mode 100644 docs/src/userimg.md delete mode 100644 examples/gadfly.jl delete mode 100644 examples/images.jl diff --git a/docs/make.jl b/docs/make.jl index ef501b42a..a62a5cf20 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -10,11 +10,8 @@ makedocs( modules = [SnoopCompile.SnoopCompileCore, SnoopCompile], linkcheck = true, pages = ["index.md", - "snoop_pc.md", "tutorial.md", "Modern tools" => ["snoopr.md", "snoopi_deep.md", "pgdsgui.md", "snoopi_deep_analysis.md", "snoopi_deep_parcel.md", "jet.md"], - "Older tools" => ["snoopi.md", "snoopc.md"], - "userimg.md", "reference.md"], ) diff --git a/docs/src/index.md b/docs/src/index.md index 08cf5e787..61f5e09e2 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -73,15 +73,6 @@ Finally, another alternative for reducing latency without any modifications to package files is [Revise](https://github.com/timholy/Revise.jl). It can be used in conjunction with SnoopCompile. -## Basic usage: SnoopPrecompile - -The "SnoopCompile family" includes a very small package, `SnoopPrecompile` (which does not require `SnoopCompile` itself), -with which most packages can easily and effectively precompile code, particularly for Julia versions 1.8 and higher. -See the [SnoopPrecompile](@ref) documentation. - -For many package developers, `SnoopPrecompile` may be all you need. In case of trouble (or if you want to check that -`SnoopPrecompile` did its job thoroughly), the rest of `SnoopCompile` provides more sophisticated diagnostics. - ## [A note on Julia versions and the recommended workflow](@id workflow) SnoopCompile is closely intertwined with Julia's own internals. diff --git a/docs/src/reference.md b/docs/src/reference.md index 4d5cabd2c..4c82be901 100644 --- a/docs/src/reference.md +++ b/docs/src/reference.md @@ -5,8 +5,6 @@ ```@docs @snoopr @snoopi_deep -@snoopi -@snoopc @snoopl ``` diff --git a/docs/src/snoop_pc.md b/docs/src/snoop_pc.md deleted file mode 100644 index 7b41cbde6..000000000 --- a/docs/src/snoop_pc.md +++ /dev/null @@ -1,100 +0,0 @@ -# SnoopPrecompile - -!!! warning - SnoopPrecompile is deprecated. Please use [PrecompileTools](https://github.com/JuliaLang/PrecompileTools.jl) instead. - -SnoopPrecompile provides a macro, `@precompile_all_calls`, which precompiles every call on its first usage. -The key feature of `@precompile_all_calls` is that it intercepts all callees (including those made by runtime dispatch) and, on Julia 1.8 and higher, allows you to precompile the callee even if it comes from a different module (e.g., `Base` or a different package). - -Statements that occur inside a `@precompile_all_calls` block are executed only if the package is being actively precompiled; -it does not run when the package is loaded, nor if you're running Julia with `--compiled-modules=no`. - -SnoopPrecompile also exports `@precompile_setup`, which you can use to create data for use inside a `@precompile_all_calls` block. Like `@precompile_all_calls`, this code only runs when you are precompiling the package, but it does not -necessarily result in the "setup" code being stored in the package precompile file. - -Here's an illustration of how you might use `@precompile_all_calls` and `@precompile_setup`: - -```julia -module MyPackage - -using SnoopPrecompile # this is a small dependency - -struct MyType - x::Int -end -struct OtherType - str::String -end - -@precompile_setup begin - # Putting some things in `setup` can reduce the size of the - # precompile file and potentially make loading faster. - list = [OtherType("hello"), OtherType("world!")] - @precompile_all_calls begin - # all calls in this block will be precompiled, regardless of whether - # they belong to your package or not (on Julia 1.8 and higher) - d = Dict(MyType(1) => list) - x = get(d, MyType(2), nothing) - last(d[MyType(1)]) - end -end - -end -``` - -When you build `MyPackage`, it will precompile the following, *including all their callees*: - -- `Pair(::MyPackage.MyType, ::Vector{MyPackage.OtherType})` -- `Dict(::Pair{MyPackage.MyType, Vector{MyPackage.OtherType}})` -- `get(::Dict{MyPackage.MyType, Vector{MyPackage.OtherType}}, ::MyPackage.MyType, ::Nothing)` -- `getindex(::Dict{MyPackage.MyType, Vector{MyPackage.OtherType}}, ::MyPackage.MyType)` -- `last(::Vector{MyPackage.OtherType})` - -In this case, the "top level" calls were fully inferrable, so there are no entries on this list -that were called by runtime dispatch. Thus, here you could have gotten the same result with manual -`precompile` directives. -The key advantage of `@precompile_all_calls` is that it works even if the functions you're calling -have runtime dispatch. - -If you want to see the list of calls that will be precompiled, navigate to the `MyPackage` folder and use - -```julia -julia> using SnoopPrecompile - -julia> SnoopPrecompile.verbose[] = true # runs the block even if you're not precompiling, and print precompiled calls - -julia> include("src/MyPackage.jl"); -``` - -Once you set up `SnoopPrecompile`, try your package and see if it reduces the time to first execution, -using the same workload you put inside the `@precompile_all_calls` block. - -If you're happy with the results, you're done! If you want deeper verification of whether it worked as -expected, or if you suspect problems, then the rest of SnoopCompile provides additional tools. -Potential sources of trouble include invalidation (see [`@snoopr`](@ref) and related) and omission of -intended calls from inside the `@precompile_all_calls` block (see [`@snoopi_deep`](@ref) and related). - -!!! note - `@precompile_all_calls` works by monitoring type-inference. If the code was already inferred - prior to `@precompile_all_calls` (e.g., from prior usage), you might omit any external - methods that were called via runtime dispatch. - - You can use multiple `@precompile_all_calls` blocks if you need to interleave "setup" code with - code that you want precompiled. - You can use `@snoopi_deep` to check for any (re)inference when you use the code in your package. - To fix any specific problems, you can combine `@precompile_all_calls` with manual `precompile` directives. - -One can reduce the cost of precompilation for selected packages using the `Preferences.jl` based mechanism and the `skip_precompile` key: -```julia -using SnoopPrecompile, Preferences -set_preferences!(SnoopPrecompile, "skip_precompile" => ["PackageA", "PackageB"]) -``` - -After restarting julia, the `@precompile_all_calls` and `@precompile_setup` workloads will be disabled (locally) for `PackageA` and `PackageB`. - -!!! note - Changing `skip_precompile` may result in a one-time recompilation of all packages that use SnoopPrecompile. - Package developers may wish to set this preference *locally* within the package's environment; - precompilation will be skipped while you're actively developing the project, but not if you use the package - from an external environment. This will also keep the `skip_precompile` setting independent and avoid needless recompilation - of large environments. diff --git a/docs/src/snoopc.md b/docs/src/snoopc.md deleted file mode 100644 index a7d9993ef..000000000 --- a/docs/src/snoopc.md +++ /dev/null @@ -1,56 +0,0 @@ -# [Snooping on code generation: `@snoopc`](@id macro-snoopc) - -`@snoopc` has the advantage of working on any modern version of Julia. -It "snoops" on the code-generation phase of compilation (the 'c' is a reference to -code-generation). - -Note that unlike `@snoopi`, `@snoopc` will generate all methods, not just the top-level -methods that trigger compilation. -(It is redundant to precompile dependent methods, but neither is it harmful.) -It is also worth noting that `@snoopc` requires "spinning up" a new Julia process, -and so it is a bit slower than `@snoopi`. - -Let's demonstrate `@snoopc` with a snoop script, in this case for the `ColorTypes` package: - -```julia -using SnoopCompile - -### Log the compiles -# This only needs to be run once (to generate "/tmp/colortypes_compiles.log") - -SnoopCompile.@snoopc "/tmp/colortypes_compiles.log" begin - using ColorTypes, Pkg - include(joinpath(dirname(dirname(pathof(ColorTypes))), "test", "runtests.jl")) -end - -### Parse the compiles and generate precompilation scripts -# This can be run repeatedly to tweak the scripts - -data = SnoopCompile.read("/tmp/colortypes_compiles.log") - -pc = SnoopCompile.parcel(reverse!(data[2])) -SnoopCompile.write("/tmp/precompile", pc) -``` - -As with `@snoopi`, the `"/tmp/precompile"` folder will now contain a number of `*.jl` files, -organized by package. -For each package, you could copy its corresponding `*.jl` file into the package's `src/` directory -and `include` it into the package as described for [`@snoopi`](@ref auto). - -There are more complete example illustrating potential options in the `examples/` directory. - -## Additional flags - -When calling the `@snoopc` macro, a new julia process is spawned using the function `Base.julia_cmd()`. -Advanced users may want to tweak the flags passed to this process to suit specific needs. -This can be done by passing an array of flags of the form `["--flag1", "--flag2"]` as the first argument to the `@snoopc` macro. -For instance, if you want to pass the `--project=/path/to/dir` flag to the process, to cause the julia process to load the project specified by the path, a snoop script may look like: -```julia -using SnoopCompile - -SnoopCompile.@snoopc ["--project=/path/to/dir"] "/tmp/compiles.csv" begin - # ... statement to snoop on -end - -# ... processing the precompile statements -``` diff --git a/docs/src/snoopi_deep.md b/docs/src/snoopi_deep.md index 31d59d7ab..4911c3a93 100644 --- a/docs/src/snoopi_deep.md +++ b/docs/src/snoopi_deep.md @@ -7,9 +7,6 @@ Currently, `precompile` only caches results for type-inference, not other stages For that reason, efforts at reducing latency should be informed by measuring the amount of time spent on type-inference. Moreover, because all code needs to be type-inferred before undergoing later stages of code generation, monitoring this "entry point" can give you an overview of the entire compile chain. -On older versions of Julia, [`@snoopi`](@ref) allows you to make fairly coarse measurements on inference; -starting with Julia 1.6, the recommended tool is [`@snoopi_deep`](@ref), which collects a much more detailed picture of type-inference's actions. - The rich data collected by `@snoopi_deep` are useful for several different purposes; on this page, we'll describe the basic tool and show how it can be used to profile inference. On later pages we'll show other ways to use the data to reduce the amount of type-inference or cache its results. diff --git a/docs/src/userimg.md b/docs/src/userimg.md deleted file mode 100644 index 8cd56a917..000000000 --- a/docs/src/userimg.md +++ /dev/null @@ -1,25 +0,0 @@ -# [Creating `userimg.jl` files](@id userimg) - -If you want to save more precompile information, one option is to create a `"userimg.jl`" -file with which to build Julia. -This is only supported for `@snoopc`. -Instead of calling `SnoopCompile.parcel` and `SnoopCompile.write`, use the following: - -```julia -# Use these two lines if you want to add to your userimg.jl -pc = SnoopCompile.format_userimg(reverse!(data[2])) -SnoopCompile.write("/tmp/userimg_Images.jl", pc) -``` - -Now move the resulting file to your Julia source directory, and create a `userimg.jl` -file that `include`s all the package-specific precompile files you want. -Then build Julia from source. -You should note that your latencies decrease substantially. - -**There are serious negatives associated with a `userimg.jl` script**: -- Your julia build times become very long -- `Pkg.update()` will have no effect on packages that you've built into julia until you next recompile julia itself. Consequently, you may not get the benefit of enhancements or bug fixes. -- For a package that you sometimes develop, this strategy is very inefficient, because testing a change means rebuilding Julia as well as your package. - -A process similar to this one is also performed via -[PackageCompiler](https://github.com/JuliaLang/PackageCompiler.jl). diff --git a/examples/gadfly.jl b/examples/gadfly.jl deleted file mode 100644 index 0cf310e10..000000000 --- a/examples/gadfly.jl +++ /dev/null @@ -1,17 +0,0 @@ -# For this to work, you need to be able to run -# using Gadfly -# include(joinpath(dirname(dirname(pathof(Gadfly))), "test", "runtests.jl")) -# successfully. Even if you have the Gadfly package, you may need to add packages. - -# Open a new julia session and run this: - -using SnoopCompile - -SnoopCompile.@snoopc "/tmp/gadfly_compiles.csv" begin - using Gadfly, Pkg - include(joinpath(dirname(dirname(pathof(Gadfly))), "test", "runtests.jl")) -end - -data = SnoopCompile.read("/tmp/gadfly_compiles.csv") -pc = SnoopCompile.parcel(reverse!(data[2])) -SnoopCompile.write("/tmp/precompile", pc) diff --git a/examples/images.jl b/examples/images.jl deleted file mode 100644 index eedb9c8cd..000000000 --- a/examples/images.jl +++ /dev/null @@ -1,36 +0,0 @@ -# For this to work, you need to be able to run -# using Images -# include(joinpath(dirname(dirname(pathof(Images))), "test", "runtests.jl")) -# successfully. Even if you have the Images package, you may need to add packages. - -using SnoopCompile - -### Log the compiles -# This only needs to be run once (to generate "/tmp/images_compiles.csv") - -SnoopCompile.@snoopc "/tmp/images_compiles.csv" begin - using Images, Pkg - include(joinpath(dirname(dirname(pathof(Images))), "test", "runtests.jl")) -end - -### Parse the compiles and generate precompilation scripts -# This can be run repeatedly - -data = SnoopCompile.read("/tmp/images_compiles.csv") - -# Old versions of Images ran the tests inside a module ImagesTest, so all -# the precompiles would get credited to ImagesTest. Credit them to Images instead: -subst = ["ImagesTests"=>"Images", ] - -# Blacklist can be used to help fix problems. -# For example, old versions of Julia output would MIME types with invalid syntax: -exclusions = ["MIME", ] - -# Use these two lines if you want to create precompile functions for -# individual packages -pc = SnoopCompile.parcel(reverse!(data[2]), subst=subst, exclusions=exclusions) -SnoopCompile.write("/tmp/precompile", pc) - -# Use these two lines if you want to add to your userimg.jl -pc = SnoopCompile.format_userimg(reverse!(data[2]), subst=subst, exclusions=exclusions) -SnoopCompile.write("/tmp/userimg_Images.jl", pc)