numba-cuda 0.17.0__py3-none-any.whl → 0.18.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of numba-cuda might be problematic. Click here for more details.

Files changed (64) hide show
  1. numba_cuda/VERSION +1 -1
  2. numba_cuda/numba/cuda/__init__.py +0 -8
  3. numba_cuda/numba/cuda/_internal/cuda_fp16.py +14225 -0
  4. numba_cuda/numba/cuda/api_util.py +6 -0
  5. numba_cuda/numba/cuda/cgutils.py +1291 -0
  6. numba_cuda/numba/cuda/codegen.py +32 -14
  7. numba_cuda/numba/cuda/compiler.py +113 -10
  8. numba_cuda/numba/cuda/core/caching.py +741 -0
  9. numba_cuda/numba/cuda/core/callconv.py +338 -0
  10. numba_cuda/numba/cuda/core/codegen.py +168 -0
  11. numba_cuda/numba/cuda/core/compiler.py +205 -0
  12. numba_cuda/numba/cuda/core/typed_passes.py +139 -0
  13. numba_cuda/numba/cuda/cudadecl.py +0 -268
  14. numba_cuda/numba/cuda/cudadrv/devicearray.py +3 -0
  15. numba_cuda/numba/cuda/cudadrv/driver.py +2 -1
  16. numba_cuda/numba/cuda/cudadrv/nvvm.py +1 -1
  17. numba_cuda/numba/cuda/cudaimpl.py +4 -178
  18. numba_cuda/numba/cuda/debuginfo.py +469 -3
  19. numba_cuda/numba/cuda/device_init.py +0 -1
  20. numba_cuda/numba/cuda/dispatcher.py +310 -11
  21. numba_cuda/numba/cuda/extending.py +2 -1
  22. numba_cuda/numba/cuda/fp16.py +348 -0
  23. numba_cuda/numba/cuda/intrinsics.py +1 -1
  24. numba_cuda/numba/cuda/libdeviceimpl.py +2 -1
  25. numba_cuda/numba/cuda/lowering.py +1833 -8
  26. numba_cuda/numba/cuda/mathimpl.py +2 -90
  27. numba_cuda/numba/cuda/nvvmutils.py +2 -1
  28. numba_cuda/numba/cuda/printimpl.py +2 -1
  29. numba_cuda/numba/cuda/serialize.py +264 -0
  30. numba_cuda/numba/cuda/simulator/__init__.py +2 -0
  31. numba_cuda/numba/cuda/simulator/dispatcher.py +7 -0
  32. numba_cuda/numba/cuda/stubs.py +0 -308
  33. numba_cuda/numba/cuda/target.py +13 -5
  34. numba_cuda/numba/cuda/testing.py +156 -5
  35. numba_cuda/numba/cuda/tests/complex_usecases.py +113 -0
  36. numba_cuda/numba/cuda/tests/core/serialize_usecases.py +110 -0
  37. numba_cuda/numba/cuda/tests/core/test_serialize.py +359 -0
  38. numba_cuda/numba/cuda/tests/cudadrv/test_context_stack.py +10 -4
  39. numba_cuda/numba/cuda/tests/cudadrv/test_cuda_ndarray.py +33 -0
  40. numba_cuda/numba/cuda/tests/cudadrv/test_runtime.py +2 -2
  41. numba_cuda/numba/cuda/tests/cudadrv/test_streams.py +1 -0
  42. numba_cuda/numba/cuda/tests/cudapy/extensions_usecases.py +1 -1
  43. numba_cuda/numba/cuda/tests/cudapy/test_caching.py +5 -10
  44. numba_cuda/numba/cuda/tests/cudapy/test_compiler.py +15 -0
  45. numba_cuda/numba/cuda/tests/cudapy/test_complex.py +1 -1
  46. numba_cuda/numba/cuda/tests/cudapy/test_debuginfo.py +381 -0
  47. numba_cuda/numba/cuda/tests/cudapy/test_enums.py +1 -1
  48. numba_cuda/numba/cuda/tests/cudapy/test_extending.py +1 -1
  49. numba_cuda/numba/cuda/tests/cudapy/test_inspect.py +108 -24
  50. numba_cuda/numba/cuda/tests/cudapy/test_intrinsics.py +37 -23
  51. numba_cuda/numba/cuda/tests/cudapy/test_operator.py +43 -27
  52. numba_cuda/numba/cuda/tests/cudapy/test_ufuncs.py +26 -9
  53. numba_cuda/numba/cuda/tests/cudapy/test_warning.py +27 -2
  54. numba_cuda/numba/cuda/tests/enum_usecases.py +56 -0
  55. numba_cuda/numba/cuda/tests/nocuda/test_library_lookup.py +1 -2
  56. numba_cuda/numba/cuda/tests/nocuda/test_nvvm.py +1 -1
  57. numba_cuda/numba/cuda/utils.py +785 -0
  58. numba_cuda/numba/cuda/vector_types.py +1 -1
  59. {numba_cuda-0.17.0.dist-info → numba_cuda-0.18.1.dist-info}/METADATA +18 -4
  60. {numba_cuda-0.17.0.dist-info → numba_cuda-0.18.1.dist-info}/RECORD +63 -50
  61. numba_cuda/numba/cuda/cpp_function_wrappers.cu +0 -46
  62. {numba_cuda-0.17.0.dist-info → numba_cuda-0.18.1.dist-info}/WHEEL +0 -0
  63. {numba_cuda-0.17.0.dist-info → numba_cuda-0.18.1.dist-info}/licenses/LICENSE +0 -0
  64. {numba_cuda-0.17.0.dist-info → numba_cuda-0.18.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,741 @@
1
+ from abc import abstractmethod, ABCMeta
2
+ import itertools
3
+ import numba
4
+ import os
5
+ import contextlib
6
+ import uuid
7
+ import pickle
8
+ import hashlib
9
+ import errno
10
+ import inspect
11
+ import tempfile
12
+ import sys
13
+
14
+ from numba.misc.appdirs import AppDirs
15
+ from pathlib import Path
16
+
17
+ from numba.core import config
18
+ from numba.core.serialize import dumps
19
+
20
+
21
+ def _cache_log(msg, *args):
22
+ if config.DEBUG_CACHE:
23
+ msg = msg % args
24
+ print(msg)
25
+
26
+
27
+ class _Cache(metaclass=ABCMeta):
28
+ @property
29
+ @abstractmethod
30
+ def cache_path(self):
31
+ """
32
+ The base filesystem path of this cache (for example its root folder).
33
+ """
34
+
35
+ @abstractmethod
36
+ def load_overload(self, sig, target_context):
37
+ """
38
+ Load an overload for the given signature using the target context.
39
+ The saved object must be returned if successful, None if not found
40
+ in the cache.
41
+ """
42
+
43
+ @abstractmethod
44
+ def save_overload(self, sig, data):
45
+ """
46
+ Save the overload for the given signature.
47
+ """
48
+
49
+ @abstractmethod
50
+ def enable(self):
51
+ """
52
+ Enable the cache.
53
+ """
54
+
55
+ @abstractmethod
56
+ def disable(self):
57
+ """
58
+ Disable the cache.
59
+ """
60
+
61
+ @abstractmethod
62
+ def flush(self):
63
+ """
64
+ Flush the cache.
65
+ """
66
+
67
+
68
+ class NullCache(_Cache):
69
+ @property
70
+ def cache_path(self):
71
+ return None
72
+
73
+ def load_overload(self, sig, target_context):
74
+ pass
75
+
76
+ def save_overload(self, sig, data):
77
+ pass
78
+
79
+ def enable(self):
80
+ pass
81
+
82
+ def disable(self):
83
+ pass
84
+
85
+ def flush(self):
86
+ pass
87
+
88
+
89
+ class IndexDataCacheFile(object):
90
+ """
91
+ Implements the logic for the index file and data file used by a cache.
92
+ """
93
+
94
+ def __init__(self, cache_path, filename_base, source_stamp):
95
+ self._cache_path = cache_path
96
+ self._index_name = "%s.nbi" % (filename_base,)
97
+ self._index_path = os.path.join(self._cache_path, self._index_name)
98
+ self._data_name_pattern = "%s.{number:d}.nbc" % (filename_base,)
99
+ self._source_stamp = source_stamp
100
+ self._version = numba.__version__
101
+
102
+ def flush(self):
103
+ self._save_index({})
104
+
105
+ def save(self, key, data):
106
+ """
107
+ Save a new cache entry with *key* and *data*.
108
+ """
109
+ overloads = self._load_index()
110
+ try:
111
+ # If key already exists, we will overwrite the file
112
+ data_name = overloads[key]
113
+ except KeyError:
114
+ # Find an available name for the data file
115
+ existing = set(overloads.values())
116
+ for i in itertools.count(1):
117
+ data_name = self._data_name(i)
118
+ if data_name not in existing:
119
+ break
120
+ overloads[key] = data_name
121
+ self._save_index(overloads)
122
+ self._save_data(data_name, data)
123
+
124
+ def load(self, key):
125
+ """
126
+ Load a cache entry with *key*.
127
+ """
128
+ overloads = self._load_index()
129
+ data_name = overloads.get(key)
130
+ if data_name is None:
131
+ return
132
+ try:
133
+ return self._load_data(data_name)
134
+ except OSError:
135
+ # File could have been removed while the index still refers it.
136
+ return
137
+
138
+ def _load_index(self):
139
+ """
140
+ Load the cache index and return it as a dictionary (possibly
141
+ empty if cache is empty or obsolete).
142
+ """
143
+ try:
144
+ with open(self._index_path, "rb") as f:
145
+ version = pickle.load(f)
146
+ data = f.read()
147
+ except FileNotFoundError:
148
+ # Index doesn't exist yet?
149
+ return {}
150
+ if version != self._version:
151
+ # This is another version. Avoid trying to unpickling the
152
+ # rest of the stream, as that may fail.
153
+ return {}
154
+ stamp, overloads = pickle.loads(data)
155
+ _cache_log("[cache] index loaded from %r", self._index_path)
156
+ if stamp != self._source_stamp:
157
+ # Cache is not fresh. Stale data files will be eventually
158
+ # overwritten, since they are numbered in incrementing order.
159
+ return {}
160
+ else:
161
+ return overloads
162
+
163
+ def _save_index(self, overloads):
164
+ data = self._source_stamp, overloads
165
+ data = self._dump(data)
166
+ with self._open_for_write(self._index_path) as f:
167
+ pickle.dump(self._version, f, protocol=-1)
168
+ f.write(data)
169
+ _cache_log("[cache] index saved to %r", self._index_path)
170
+
171
+ def _load_data(self, name):
172
+ path = self._data_path(name)
173
+ with open(path, "rb") as f:
174
+ data = f.read()
175
+ tup = pickle.loads(data)
176
+ _cache_log("[cache] data loaded from %r", path)
177
+ return tup
178
+
179
+ def _save_data(self, name, data):
180
+ data = self._dump(data)
181
+ path = self._data_path(name)
182
+ with self._open_for_write(path) as f:
183
+ f.write(data)
184
+ _cache_log("[cache] data saved to %r", path)
185
+
186
+ def _data_name(self, number):
187
+ return self._data_name_pattern.format(number=number)
188
+
189
+ def _data_path(self, name):
190
+ return os.path.join(self._cache_path, name)
191
+
192
+ def _dump(self, obj):
193
+ return dumps(obj)
194
+
195
+ @contextlib.contextmanager
196
+ def _open_for_write(self, filepath):
197
+ """
198
+ Open *filepath* for writing in a race condition-free way (hopefully).
199
+ uuid4 is used to try and avoid name collisions on a shared filesystem.
200
+ """
201
+ uid = uuid.uuid4().hex[:16] # avoid long paths
202
+ tmpname = "%s.tmp.%s" % (filepath, uid)
203
+ try:
204
+ with open(tmpname, "wb") as f:
205
+ yield f
206
+ os.replace(tmpname, filepath)
207
+ except Exception:
208
+ # In case of error, remove dangling tmp file
209
+ try:
210
+ os.unlink(tmpname)
211
+ except OSError:
212
+ pass
213
+ raise
214
+
215
+
216
+ class Cache(_Cache):
217
+ """
218
+ A per-function compilation cache. The cache saves data in separate
219
+ data files and maintains information in an index file.
220
+
221
+ There is one index file per function and Python version
222
+ ("function_name-<lineno>.pyXY.nbi") which contains a mapping of
223
+ signatures and architectures to data files.
224
+ It is prefixed by a versioning key and a timestamp of the Python source
225
+ file containing the function.
226
+
227
+ There is one data file ("function_name-<lineno>.pyXY.<number>.nbc")
228
+ per function, function signature, target architecture and Python version.
229
+
230
+ Separate index and data files per Python version avoid pickle
231
+ compatibility problems.
232
+
233
+ Note:
234
+ This contains the driver logic only. The core logic is provided
235
+ by a subclass of ``CacheImpl`` specified as *_impl_class* in the subclass.
236
+ """
237
+
238
+ # The following class variables must be overridden by subclass.
239
+ _impl_class = None
240
+
241
+ def __init__(self, py_func):
242
+ self._name = repr(py_func)
243
+ self._py_func = py_func
244
+ self._impl = self._impl_class(py_func)
245
+ self._cache_path = self._impl.locator.get_cache_path()
246
+ # This may be a bit strict but avoids us maintaining a magic number
247
+ source_stamp = self._impl.locator.get_source_stamp()
248
+ filename_base = self._impl.filename_base
249
+ self._cache_file = IndexDataCacheFile(
250
+ cache_path=self._cache_path,
251
+ filename_base=filename_base,
252
+ source_stamp=source_stamp,
253
+ )
254
+ self.enable()
255
+
256
+ def __repr__(self):
257
+ return "<%s py_func=%r>" % (self.__class__.__name__, self._name)
258
+
259
+ @property
260
+ def cache_path(self):
261
+ return self._cache_path
262
+
263
+ def enable(self):
264
+ self._enabled = True
265
+
266
+ def disable(self):
267
+ self._enabled = False
268
+
269
+ def flush(self):
270
+ self._cache_file.flush()
271
+
272
+ def load_overload(self, sig, target_context):
273
+ """
274
+ Load and recreate the cached object for the given signature,
275
+ using the *target_context*.
276
+ """
277
+ # Refresh the context to ensure it is initialized
278
+ target_context.refresh()
279
+ with self._guard_against_spurious_io_errors():
280
+ return self._load_overload(sig, target_context)
281
+ # None returned if the `with` block swallows an exception
282
+
283
+ def _load_overload(self, sig, target_context):
284
+ if not self._enabled:
285
+ return
286
+ key = self._index_key(sig, target_context.codegen())
287
+ data = self._cache_file.load(key)
288
+ if data is not None:
289
+ data = self._impl.rebuild(target_context, data)
290
+ return data
291
+
292
+ def save_overload(self, sig, data):
293
+ """
294
+ Save the data for the given signature in the cache.
295
+ """
296
+ with self._guard_against_spurious_io_errors():
297
+ self._save_overload(sig, data)
298
+
299
+ def _save_overload(self, sig, data):
300
+ if not self._enabled:
301
+ return
302
+ if not self._impl.check_cachable(data):
303
+ return
304
+ self._impl.locator.ensure_cache_path()
305
+ key = self._index_key(sig, data.codegen)
306
+ data = self._impl.reduce(data)
307
+ self._cache_file.save(key, data)
308
+
309
+ @contextlib.contextmanager
310
+ def _guard_against_spurious_io_errors(self):
311
+ if os.name == "nt":
312
+ # Guard against permission errors due to accessing the file
313
+ # from several processes (see #2028)
314
+ try:
315
+ yield
316
+ except OSError as e:
317
+ if e.errno != errno.EACCES:
318
+ raise
319
+ else:
320
+ # No such conditions under non-Windows OSes
321
+ yield
322
+
323
+ def _index_key(self, sig, codegen):
324
+ """
325
+ Compute index key for the given signature and codegen.
326
+ It includes a description of the OS, target architecture and hashes of
327
+ the bytecode for the function and, if the function has a __closure__,
328
+ a hash of the cell_contents.
329
+ """
330
+ codebytes = self._py_func.__code__.co_code
331
+ if self._py_func.__closure__ is not None:
332
+ cvars = tuple([x.cell_contents for x in self._py_func.__closure__])
333
+ # Note: cloudpickle serializes a function differently depending
334
+ # on how the process is launched; e.g. multiprocessing.Process
335
+ cvarbytes = dumps(cvars)
336
+ else:
337
+ cvarbytes = b""
338
+
339
+ hasher = lambda x: hashlib.sha256(x).hexdigest()
340
+ return (
341
+ sig,
342
+ codegen.magic_tuple(),
343
+ (
344
+ hasher(codebytes),
345
+ hasher(cvarbytes),
346
+ ),
347
+ )
348
+
349
+
350
+ class _CacheLocator(metaclass=ABCMeta):
351
+ """
352
+ A filesystem locator for caching a given function.
353
+ """
354
+
355
+ def ensure_cache_path(self):
356
+ path = self.get_cache_path()
357
+ os.makedirs(path, exist_ok=True)
358
+ # Ensure the directory is writable by trying to write a temporary file
359
+ tempfile.TemporaryFile(dir=path).close()
360
+
361
+ @abstractmethod
362
+ def get_cache_path(self):
363
+ """
364
+ Return the directory the function is cached in.
365
+ """
366
+
367
+ @abstractmethod
368
+ def get_source_stamp(self):
369
+ """
370
+ Get a timestamp representing the source code's freshness.
371
+ Can return any picklable Python object.
372
+ """
373
+
374
+ @abstractmethod
375
+ def get_disambiguator(self):
376
+ """
377
+ Get a string disambiguator for this locator's function.
378
+ It should allow disambiguating different but similarly-named functions.
379
+ """
380
+
381
+ @classmethod
382
+ def from_function(cls, py_func, py_file):
383
+ """
384
+ Create a locator instance for the given function located in the
385
+ given file.
386
+ """
387
+ raise NotImplementedError
388
+
389
+ @classmethod
390
+ def get_suitable_cache_subpath(cls, py_file):
391
+ """Given the Python file path, compute a suitable path inside the
392
+ cache directory.
393
+
394
+ This will reduce a file path that is too long, which can be a problem
395
+ on some operating system (i.e. Windows 7).
396
+ """
397
+ path = os.path.abspath(py_file)
398
+ subpath = os.path.dirname(path)
399
+ parentdir = os.path.split(subpath)[-1]
400
+ # Use SHA1 to reduce path length.
401
+ # Note: windows doesn't like long path.
402
+ hashed = hashlib.sha1(subpath.encode()).hexdigest()
403
+ # Retain parent directory name for easier debugging
404
+ return "_".join([parentdir, hashed])
405
+
406
+
407
+ class _SourceFileBackedLocatorMixin(object):
408
+ """
409
+ A cache locator mixin for functions which are backed by a well-known
410
+ Python source file.
411
+ """
412
+
413
+ def get_source_stamp(self):
414
+ if getattr(sys, "frozen", False):
415
+ st = os.stat(sys.executable)
416
+ else:
417
+ st = os.stat(self._py_file)
418
+ # We use both timestamp and size as some filesystems only have second
419
+ # granularity.
420
+ return st.st_mtime, st.st_size
421
+
422
+ def get_disambiguator(self):
423
+ return str(self._lineno)
424
+
425
+ @classmethod
426
+ def from_function(cls, py_func, py_file):
427
+ if not os.path.exists(py_file):
428
+ # Perhaps a placeholder (e.g. "<ipython-XXX>")
429
+ return
430
+ self = cls(py_func, py_file)
431
+ try:
432
+ self.ensure_cache_path()
433
+ except OSError:
434
+ # Cannot ensure the cache directory exists or is writable
435
+ return
436
+ return self
437
+
438
+
439
+ class _InTreeCacheLocator(_SourceFileBackedLocatorMixin, _CacheLocator):
440
+ """
441
+ A locator for functions backed by a regular Python module with a
442
+ writable __pycache__ directory.
443
+ """
444
+
445
+ def __init__(self, py_func, py_file):
446
+ self._py_file = py_file
447
+ self._lineno = py_func.__code__.co_firstlineno
448
+ self._cache_path = os.path.join(
449
+ os.path.dirname(self._py_file), "__pycache__"
450
+ )
451
+
452
+ def get_cache_path(self):
453
+ return self._cache_path
454
+
455
+
456
+ class _SourceFileBackedLocatorMixin(object):
457
+ """
458
+ A cache locator mixin for functions which are backed by a well-known
459
+ Python source file.
460
+ """
461
+
462
+ def get_source_stamp(self):
463
+ if getattr(sys, "frozen", False):
464
+ st = os.stat(sys.executable)
465
+ else:
466
+ st = os.stat(self._py_file)
467
+ # We use both timestamp and size as some filesystems only have second
468
+ # granularity.
469
+ return st.st_mtime, st.st_size
470
+
471
+ def get_disambiguator(self):
472
+ return str(self._lineno)
473
+
474
+ @classmethod
475
+ def from_function(cls, py_func, py_file):
476
+ if not os.path.exists(py_file):
477
+ # Perhaps a placeholder (e.g. "<ipython-XXX>")
478
+ return
479
+ self = cls(py_func, py_file)
480
+ try:
481
+ self.ensure_cache_path()
482
+ except OSError:
483
+ # Cannot ensure the cache directory exists or is writable
484
+ return
485
+ return self
486
+
487
+
488
+ class _UserProvidedCacheLocator(_SourceFileBackedLocatorMixin, _CacheLocator):
489
+ """
490
+ A locator that always point to the user provided directory in
491
+ `numba.config.CACHE_DIR`
492
+ """
493
+
494
+ def __init__(self, py_func, py_file):
495
+ self._py_file = py_file
496
+ self._lineno = py_func.__code__.co_firstlineno
497
+ cache_subpath = self.get_suitable_cache_subpath(py_file)
498
+ self._cache_path = os.path.join(config.CACHE_DIR, cache_subpath)
499
+
500
+ def get_cache_path(self):
501
+ return self._cache_path
502
+
503
+ @classmethod
504
+ def from_function(cls, py_func, py_file):
505
+ if not config.CACHE_DIR:
506
+ return
507
+ parent = super(_UserProvidedCacheLocator, cls)
508
+ return parent.from_function(py_func, py_file)
509
+
510
+
511
+ class _UserProvidedCacheLocator(_SourceFileBackedLocatorMixin, _CacheLocator):
512
+ """
513
+ A locator that always point to the user provided directory in
514
+ `numba.config.CACHE_DIR`
515
+ """
516
+
517
+ def __init__(self, py_func, py_file):
518
+ self._py_file = py_file
519
+ self._lineno = py_func.__code__.co_firstlineno
520
+ cache_subpath = self.get_suitable_cache_subpath(py_file)
521
+ self._cache_path = os.path.join(config.CACHE_DIR, cache_subpath)
522
+
523
+ def get_cache_path(self):
524
+ return self._cache_path
525
+
526
+ @classmethod
527
+ def from_function(cls, py_func, py_file):
528
+ if not config.CACHE_DIR:
529
+ return
530
+ parent = super(_UserProvidedCacheLocator, cls)
531
+ return parent.from_function(py_func, py_file)
532
+
533
+
534
+ class _UserWideCacheLocator(_SourceFileBackedLocatorMixin, _CacheLocator):
535
+ """
536
+ A locator for functions backed by a regular Python module or a
537
+ frozen executable, cached into a user-wide cache directory.
538
+ """
539
+
540
+ def __init__(self, py_func, py_file):
541
+ self._py_file = py_file
542
+ self._lineno = py_func.__code__.co_firstlineno
543
+ appdirs = AppDirs(appname="numba", appauthor=False)
544
+ cache_dir = appdirs.user_cache_dir
545
+ cache_subpath = self.get_suitable_cache_subpath(py_file)
546
+ self._cache_path = os.path.join(cache_dir, cache_subpath)
547
+
548
+ def get_cache_path(self):
549
+ return self._cache_path
550
+
551
+ @classmethod
552
+ def from_function(cls, py_func, py_file):
553
+ if not (os.path.exists(py_file) or getattr(sys, "frozen", False)):
554
+ # Perhaps a placeholder (e.g. "<ipython-XXX>")
555
+ # stop function exit if frozen, since it uses a temp placeholder
556
+ return
557
+ self = cls(py_func, py_file)
558
+ try:
559
+ self.ensure_cache_path()
560
+ except OSError:
561
+ # Cannot ensure the cache directory exists or is writable
562
+ return
563
+ return self
564
+
565
+
566
+ class _IPythonCacheLocator(_CacheLocator):
567
+ """
568
+ A locator for functions entered at the IPython prompt (notebook or other).
569
+ """
570
+
571
+ def __init__(self, py_func, py_file):
572
+ self._py_file = py_file
573
+ # Note IPython enhances the linecache module to be able to
574
+ # inspect source code of functions defined on the interactive prompt.
575
+ source = inspect.getsource(py_func)
576
+ if isinstance(source, bytes):
577
+ self._bytes_source = source
578
+ else:
579
+ self._bytes_source = source.encode("utf-8")
580
+
581
+ def get_cache_path(self):
582
+ # We could also use jupyter_core.paths.jupyter_runtime_dir()
583
+ # In both cases this is a user-wide directory, so we need to
584
+ # be careful when disambiguating if we don't want too many
585
+ # conflicts (see below).
586
+ try:
587
+ from IPython.paths import get_ipython_cache_dir
588
+ except ImportError:
589
+ # older IPython version
590
+ from IPython.utils.path import get_ipython_cache_dir
591
+ return os.path.join(get_ipython_cache_dir(), "numba_cache")
592
+
593
+ def get_source_stamp(self):
594
+ return hashlib.sha256(self._bytes_source).hexdigest()
595
+
596
+ def get_disambiguator(self):
597
+ # Heuristic: we don't want too many variants being saved, but
598
+ # we don't want similar named functions (e.g. "f") to compete
599
+ # for the cache, so we hash the first two lines of the function
600
+ # source (usually this will be the @jit decorator + the function
601
+ # signature).
602
+ firstlines = b"".join(self._bytes_source.splitlines(True)[:2])
603
+ return hashlib.sha256(firstlines).hexdigest()[:10]
604
+
605
+ @classmethod
606
+ def from_function(cls, py_func, py_file):
607
+ if not (
608
+ py_file.startswith("<ipython-")
609
+ or os.path.basename(os.path.dirname(py_file)).startswith(
610
+ "ipykernel_"
611
+ )
612
+ ):
613
+ return
614
+ self = cls(py_func, py_file)
615
+ try:
616
+ self.ensure_cache_path()
617
+ except OSError:
618
+ # Cannot ensure the cache directory exists
619
+ return
620
+ return self
621
+
622
+
623
+ class _ZipCacheLocator(_SourceFileBackedLocatorMixin, _CacheLocator):
624
+ """
625
+ A locator for functions backed by Python modules within a zip archive.
626
+ """
627
+
628
+ def __init__(self, py_func, py_file):
629
+ self._py_file = py_file
630
+ self._lineno = py_func.__code__.co_firstlineno
631
+ self._zip_path, self._internal_path = self._split_zip_path(py_file)
632
+ # We use AppDirs at the moment. A more advanced version of this could also allow
633
+ # a provided `cache_dir`, though that starts to create (cache location x source
634
+ # type) number of cache classes.
635
+ appdirs = AppDirs(appname="numba", appauthor=False)
636
+ cache_dir = appdirs.user_cache_dir
637
+ cache_subpath = self.get_suitable_cache_subpath(py_file)
638
+ self._cache_path = os.path.join(cache_dir, cache_subpath)
639
+
640
+ @staticmethod
641
+ def _split_zip_path(py_file):
642
+ path = Path(py_file)
643
+ for i, part in enumerate(path.parts):
644
+ if part.endswith(".zip"):
645
+ zip_path = str(Path(*path.parts[: i + 1]))
646
+ internal_path = str(Path(*path.parts[i + 1 :]))
647
+ return zip_path, internal_path
648
+ raise ValueError("No zip file found in path")
649
+
650
+ def get_cache_path(self):
651
+ return self._cache_path
652
+
653
+ def get_source_stamp(self):
654
+ st = os.stat(self._zip_path)
655
+ return st.st_mtime, st.st_size
656
+
657
+ @classmethod
658
+ def from_function(cls, py_func, py_file):
659
+ if ".zip" not in py_file:
660
+ return None
661
+ return cls(py_func, py_file)
662
+
663
+
664
+ class CacheImpl(metaclass=ABCMeta):
665
+ """
666
+ Provides the core machinery for caching.
667
+ - implement how to serialize and deserialize the data in the cache.
668
+ - control the filename of the cache.
669
+ - provide the cache locator
670
+ """
671
+
672
+ _locator_classes = [
673
+ _UserProvidedCacheLocator,
674
+ _InTreeCacheLocator,
675
+ _UserWideCacheLocator,
676
+ _IPythonCacheLocator,
677
+ _ZipCacheLocator,
678
+ ]
679
+
680
+ def __init__(self, py_func):
681
+ self._lineno = py_func.__code__.co_firstlineno
682
+ # Get qualname
683
+ try:
684
+ qualname = py_func.__qualname__
685
+ except AttributeError:
686
+ qualname = py_func.__name__
687
+ # Find a locator
688
+ source_path = inspect.getfile(py_func)
689
+ for cls in self._locator_classes:
690
+ locator = cls.from_function(py_func, source_path)
691
+ if locator is not None:
692
+ break
693
+ else:
694
+ raise RuntimeError(
695
+ "cannot cache function %r: no locator available "
696
+ "for file %r" % (qualname, source_path)
697
+ )
698
+ self._locator = locator
699
+ # Use filename base name as module name to avoid conflict between
700
+ # foo/__init__.py and foo/foo.py
701
+ filename = inspect.getfile(py_func)
702
+ modname = os.path.splitext(os.path.basename(filename))[0]
703
+ fullname = "%s.%s" % (modname, qualname)
704
+ abiflags = getattr(sys, "abiflags", "")
705
+ self._filename_base = self.get_filename_base(fullname, abiflags)
706
+
707
+ def get_filename_base(self, fullname, abiflags):
708
+ # '<' and '>' can appear in the qualname (e.g. '<locals>') but
709
+ # are forbidden in Windows filenames
710
+ fixed_fullname = fullname.replace("<", "").replace(">", "")
711
+ fmt = "%s-%s.py%d%d%s"
712
+ return fmt % (
713
+ fixed_fullname,
714
+ self.locator.get_disambiguator(),
715
+ sys.version_info[0],
716
+ sys.version_info[1],
717
+ abiflags,
718
+ )
719
+
720
+ @property
721
+ def filename_base(self):
722
+ return self._filename_base
723
+
724
+ @property
725
+ def locator(self):
726
+ return self._locator
727
+
728
+ @abstractmethod
729
+ def reduce(self, data):
730
+ "Returns the serialized form the data"
731
+ pass
732
+
733
+ @abstractmethod
734
+ def rebuild(self, target_context, reduced_data):
735
+ "Returns the de-serialized form of the *reduced_data*"
736
+ pass
737
+
738
+ @abstractmethod
739
+ def check_cachable(self, data):
740
+ "Returns True if the given data is cachable; otherwise, returns False."
741
+ pass