6262
6363# # cached compilation
6464
65+ # ## Notes on interactions with package images and disk cache.
66+ # Julia uses package images (pkgimg) to cache both the result of inference,
67+ # and the result of native code emissions. Up until Julia v1.11 neither the
68+ # inferred nor the nativce code of foreign abstract interpreters was cached
69+ # across sessions. Julia v1.11 allows for caching of inference results across
70+ # sessions as long as those inference results are created during precompilation.
71+ #
72+ # Julia cache hierarchy is roughly as follows:
73+ # Function (name of a thing)
74+ # -> Method (particular piece of code to dispatch to with a signature)
75+ # -> MethodInstance (A particular Method + particular signature)
76+ # -> CodeInstance (A MethodInstance compiled for a world)
77+ #
78+ # In order to cache code across sessions we need to insert CodeInstance(owner=GPUCompilerCacheToken)
79+ # into the internal cache. Once we have done so we know that a particular CodeInstance is unique in
80+ # the system. (During pkgimg loading conflicts will be resolved).
81+ #
82+ # When a pkgimg is loaded we check it's validity, this means checking that all depdencies are the same,
83+ # the pkgimg was created for the right set of compiler flags, and that all source code that was used
84+ # to create this pkgimg is the same. When a CodeInstance is inside a pkgimg we can extend the chain of
85+ # validity even for GPU code, we cannot verify a "runtime" CodeInstance in the same way.
86+ #
87+ # Therefore when we see a compilation request for a CodeInstance that is originating from a pkgimg
88+ # we can use it as part of the hash for the on-disk cache. (see `cache_file`)
89+
90+ """
91+ disk_cache_enabled()
92+
93+ Query if caching to disk is enabled.
94+ """
95+ disk_cache_enabled () = parse (Bool, @load_preference (" disk_cache" , " false" ))
96+
97+ """
98+ enable_disk_cache!(state::Bool=true)
99+
100+ Activate the GPUCompiler disk cache in the current environment.
101+ You will need to restart your Julia environment for it to take effect.
102+
103+ !!! note
104+ The cache functionality requires Julia 1.11
105+ """
106+ function enable_disk_cache! (state:: Bool = true )
107+ @set_preferences! (" disk_cache" => string (state))
108+ end
109+
110+ disk_cache_path () = @get_scratch! (" disk_cache" )
111+ clear_disk_cache! () = rm (disk_cache_path (); recursive= true , force= true )
112+
65113const cache_lock = ReentrantLock ()
66114
67115"""
@@ -108,6 +156,37 @@ function cached_compilation(cache::AbstractDict{<:Any,V},
108156 return obj:: V
109157end
110158
159+ @noinline function cache_file (ci:: CodeInstance , cfg:: CompilerConfig )
160+ h = hash (Base. objectid (ci))
161+ @static if isdefined (Base, :object_build_id )
162+ bid = Base. object_build_id (ci)
163+ if bid === nothing # CI is from a runtime compilation, not worth caching on disk
164+ return nothing
165+ else
166+ bid = bid % UInt64 # The upper 64bit are a checksum, unavailable during precompilation
167+ end
168+ h = hash (bid, h)
169+ end
170+ h = hash (cfg, h)
171+
172+ gpucompiler_buildid = Base. module_build_id (@__MODULE__ )
173+ if (gpucompiler_buildid >> 64 ) % UInt64 == 0xffffffffffffffff
174+ return nothing # Don't cache during precompilation of GPUCompiler
175+ end
176+
177+ return joinpath (
178+ disk_cache_path (),
179+ # bifurcate the cache by build id of GPUCompiler
180+ string (gpucompiler_buildid),
181+ string (h, " .jls" ))
182+ end
183+
184+ struct DiskCacheEntry
185+ src:: Type # Originally MethodInstance, but upon deserialize they were not uniqued...
186+ cfg:: CompilerConfig
187+ asm
188+ end
189+
111190@noinline function actual_compilation (cache:: AbstractDict , src:: MethodInstance , world:: UInt ,
112191 cfg:: CompilerConfig , compiler:: Function , linker:: Function )
113192 job = CompilerJob (src, cfg, world)
@@ -117,20 +196,64 @@ end
117196 ci = ci_cache_lookup (ci_cache (job), src, world, world):: Union{Nothing,CodeInstance}
118197 if ci != = nothing
119198 key = (ci, cfg)
120- if haskey (cache, key)
121- obj = cache[key]
122- end
199+ obj = get (cache, key, nothing )
123200 end
124201
125202 # slow path: compile and link
126203 if obj === nothing || compile_hook[] != = nothing
127- # TODO : consider loading the assembly from an on-disk cache here
128- asm = compiler (job)
204+ asm = nothing
205+ path = nothing
206+ ondisk_hit = false
207+ @static if VERSION >= v " 1.11.0-"
208+ # Don't try to hit the disk cache if we are for a *compile* hook
209+ # TODO :
210+ # - Sould we hit disk cache if Base.generating_output()
211+ # - Should we allow backend to opt out?
212+ if ci != = nothing && obj === nothing && disk_cache_enabled ()
213+ path = cache_file (ci, cfg)
214+ @debug " Looking for on-disk cache" job path
215+ if path != = nothing && isfile (path)
216+ ondisk_hit = true
217+ try
218+ @debug " Loading compiled kernel" job path
219+ # The MI we deserialize here didn't get uniqued...
220+ entry = deserialize (path):: DiskCacheEntry
221+ if entry. src == src. specTypes && entry. cfg == cfg
222+ asm = entry. asm
223+ else
224+ @show entry. src == src. specTypes
225+ @show entry. cfg == cfg
226+ @warn " Cache missmatch" src. specTypes cfg entry. src entry. cfg
227+ end
228+ catch ex
229+ @warn " Failed to load compiled kernel" job path exception= (ex, catch_backtrace ())
230+ end
231+ end
232+ end
233+ end
129234
235+ if asm === nothing || compile_hook[] != = nothing
236+ # Run the compiler in-case we need to hook it.
237+ asm = compiler (job)
238+ end
130239 if obj != = nothing
131240 # we got here because of a *compile* hook; don't bother linking
132241 return obj
133242 end
243+
244+ @static if VERSION >= v " 1.11.0-"
245+ if ! ondisk_hit && path != = nothing && disk_cache_enabled ()
246+ @debug " Writing out on-disk cache" job path
247+ tmppath, io = mktemp (;cleanup= false )
248+ entry = DiskCacheEntry (src. specTypes, cfg, asm)
249+ serialize (io, entry)
250+ close (io)
251+ # atomic move
252+ mkpath (dirname (path))
253+ Base. rename (tmppath, path, force= true )
254+ end
255+ end
256+
134257 obj = linker (job, asm)
135258
136259 if ci === nothing
0 commit comments