halide 19.0.0__cp38-cp38-manylinux_2_27_x86_64.manylinux_2_28_x86_64.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.
Files changed (84) hide show
  1. halide/__init__.py +39 -0
  2. halide/_generator_helpers.py +835 -0
  3. halide/bin/adams2019_retrain_cost_model +0 -0
  4. halide/bin/adams2019_weightsdir_to_weightsfile +0 -0
  5. halide/bin/anderson2021_retrain_cost_model +0 -0
  6. halide/bin/anderson2021_weightsdir_to_weightsfile +0 -0
  7. halide/bin/featurization_to_sample +0 -0
  8. halide/bin/gengen +0 -0
  9. halide/bin/get_host_target +0 -0
  10. halide/halide_.cpython-38-x86_64-linux-gnu.so +0 -0
  11. halide/imageio.py +60 -0
  12. halide/include/Halide.h +35293 -0
  13. halide/include/HalideBuffer.h +2618 -0
  14. halide/include/HalidePyTorchCudaHelpers.h +64 -0
  15. halide/include/HalidePyTorchHelpers.h +120 -0
  16. halide/include/HalideRuntime.h +2221 -0
  17. halide/include/HalideRuntimeCuda.h +89 -0
  18. halide/include/HalideRuntimeD3D12Compute.h +91 -0
  19. halide/include/HalideRuntimeHexagonDma.h +104 -0
  20. halide/include/HalideRuntimeHexagonHost.h +157 -0
  21. halide/include/HalideRuntimeMetal.h +112 -0
  22. halide/include/HalideRuntimeOpenCL.h +119 -0
  23. halide/include/HalideRuntimeQurt.h +32 -0
  24. halide/include/HalideRuntimeVulkan.h +137 -0
  25. halide/include/HalideRuntimeWebGPU.h +44 -0
  26. halide/lib64/cmake/Halide/FindHalide_LLVM.cmake +152 -0
  27. halide/lib64/cmake/Halide/FindV8.cmake +33 -0
  28. halide/lib64/cmake/Halide/Halide-shared-deps.cmake +0 -0
  29. halide/lib64/cmake/Halide/Halide-shared-targets-release.cmake +29 -0
  30. halide/lib64/cmake/Halide/Halide-shared-targets.cmake +154 -0
  31. halide/lib64/cmake/Halide/HalideConfig.cmake +162 -0
  32. halide/lib64/cmake/Halide/HalideConfigVersion.cmake +65 -0
  33. halide/lib64/cmake/HalideHelpers/FindHalide_WebGPU.cmake +27 -0
  34. halide/lib64/cmake/HalideHelpers/Halide-Interfaces-release.cmake +116 -0
  35. halide/lib64/cmake/HalideHelpers/Halide-Interfaces.cmake +236 -0
  36. halide/lib64/cmake/HalideHelpers/HalideGeneratorHelpers.cmake +1056 -0
  37. halide/lib64/cmake/HalideHelpers/HalideHelpersConfig.cmake +28 -0
  38. halide/lib64/cmake/HalideHelpers/HalideHelpersConfigVersion.cmake +54 -0
  39. halide/lib64/cmake/HalideHelpers/HalideTargetHelpers.cmake +99 -0
  40. halide/lib64/cmake/HalideHelpers/MutexCopy.ps1 +31 -0
  41. halide/lib64/cmake/HalideHelpers/TargetExportScript.cmake +55 -0
  42. halide/lib64/cmake/Halide_Python/Halide_Python-targets-release.cmake +30 -0
  43. halide/lib64/cmake/Halide_Python/Halide_Python-targets.cmake +125 -0
  44. halide/lib64/cmake/Halide_Python/Halide_PythonConfig.cmake +26 -0
  45. halide/lib64/cmake/Halide_Python/Halide_PythonConfigVersion.cmake +65 -0
  46. halide/lib64/libHalide.so +0 -0
  47. halide/lib64/libHalidePyStubs.a +0 -0
  48. halide/lib64/libHalide_GenGen.a +0 -0
  49. halide/lib64/libautoschedule_adams2019.so +0 -0
  50. halide/lib64/libautoschedule_anderson2021.so +0 -0
  51. halide/lib64/libautoschedule_li2018.so +0 -0
  52. halide/lib64/libautoschedule_mullapudi2016.so +0 -0
  53. halide/share/doc/Halide/LICENSE.txt +233 -0
  54. halide/share/doc/Halide/README.md +439 -0
  55. halide/share/doc/Halide/doc/BuildingHalideWithCMake.md +626 -0
  56. halide/share/doc/Halide/doc/CodeStyleCMake.md +393 -0
  57. halide/share/doc/Halide/doc/FuzzTesting.md +104 -0
  58. halide/share/doc/Halide/doc/HalideCMakePackage.md +812 -0
  59. halide/share/doc/Halide/doc/Hexagon.md +73 -0
  60. halide/share/doc/Halide/doc/Python.md +844 -0
  61. halide/share/doc/Halide/doc/RunGen.md +283 -0
  62. halide/share/doc/Halide/doc/Testing.md +125 -0
  63. halide/share/doc/Halide/doc/Vulkan.md +287 -0
  64. halide/share/doc/Halide/doc/WebAssembly.md +228 -0
  65. halide/share/doc/Halide/doc/WebGPU.md +128 -0
  66. halide/share/tools/RunGen.h +1470 -0
  67. halide/share/tools/RunGenMain.cpp +642 -0
  68. halide/share/tools/adams2019_autotune_loop.sh +227 -0
  69. halide/share/tools/anderson2021_autotune_loop.sh +591 -0
  70. halide/share/tools/halide_benchmark.h +240 -0
  71. halide/share/tools/halide_image.h +31 -0
  72. halide/share/tools/halide_image_info.h +318 -0
  73. halide/share/tools/halide_image_io.h +2794 -0
  74. halide/share/tools/halide_malloc_trace.h +102 -0
  75. halide/share/tools/halide_thread_pool.h +161 -0
  76. halide/share/tools/halide_trace_config.h +559 -0
  77. halide-19.0.0.data/data/share/cmake/Halide/HalideConfig.cmake +6 -0
  78. halide-19.0.0.data/data/share/cmake/Halide/HalideConfigVersion.cmake +65 -0
  79. halide-19.0.0.data/data/share/cmake/HalideHelpers/HalideHelpersConfig.cmake +6 -0
  80. halide-19.0.0.data/data/share/cmake/HalideHelpers/HalideHelpersConfigVersion.cmake +54 -0
  81. halide-19.0.0.dist-info/METADATA +301 -0
  82. halide-19.0.0.dist-info/RECORD +84 -0
  83. halide-19.0.0.dist-info/WHEEL +6 -0
  84. halide-19.0.0.dist-info/licenses/LICENSE.txt +233 -0
@@ -0,0 +1,835 @@
1
+ from __future__ import annotations
2
+ from abc import ABC, abstractmethod
3
+ from contextvars import ContextVar
4
+ from enum import Enum
5
+ from functools import total_ordering
6
+ from .halide_ import *
7
+ from .halide_ import _unique_name, _UnspecifiedType
8
+ from inspect import isclass
9
+ from typing import Any, Optional
10
+ import builtins
11
+ import re
12
+ import sys
13
+ import warnings
14
+
15
+ def _fail(msg: str):
16
+ raise HalideError(msg)
17
+
18
+
19
+ def _check(cond: bool, msg: str):
20
+ if not cond:
21
+ _fail(msg)
22
+
23
+ # Basically, a valid C identifier, except:
24
+ #
25
+ # -- initial _ is forbidden (rather than merely "reserved")
26
+ # -- two underscores in a row is also forbidden
27
+ _NAME_RE = re.compile(r"^(?!.*__)[A-Za-z0-9][A-Za-z0-9_]*$")
28
+
29
+ def _is_valid_name(name: str) -> bool:
30
+ if not name:
31
+ return False
32
+ # We forbid this to avoid ambiguity in arguments to call()
33
+ if name == "generator_params":
34
+ return False
35
+
36
+ return _NAME_RE.search(name)
37
+
38
+
39
+ def _check_valid_name(name: str) -> str:
40
+ _check(
41
+ _is_valid_name(name),
42
+ "The name '%s' is not valid for a GeneratorParam, Input, or Output." % name,
43
+ )
44
+ return name
45
+
46
+
47
+ # Transmute 'None' into our internal "UnspecifiedType" as a placeholder;
48
+ # also add some syntactic sugar, to allow users to alias
49
+ # bool -> UInt(1), int -> Int(32), float -> Float(32)
50
+ def _sanitize_type(t: object) -> Type:
51
+ if t is None or t is type(None):
52
+ return _UnspecifiedType()
53
+ elif t is bool:
54
+ return UInt(1)
55
+ elif t is int:
56
+ return Int(32)
57
+ elif t is float:
58
+ return Float(32)
59
+ else:
60
+ _check(isinstance(t, Type), "Expected a Halide Type, but saw: %s" % t)
61
+ return t
62
+
63
+ def _normalize_type_list(types: object) -> list[Type]:
64
+ # Always treat _UnspecifiedType as a non-type
65
+ if types is None:
66
+ types = []
67
+ elif isinstance(types, Type) and types == _UnspecifiedType():
68
+ types = []
69
+ if type(types) is not list:
70
+ types = [types];
71
+ types = [_sanitize_type(t) for t in types]
72
+ return types
73
+
74
+
75
+ _type_string_map = {
76
+ # "bfloat16": BFloat(16),
77
+ "bool": Bool(),
78
+ "float16": Float(16),
79
+ "float32": Float(32),
80
+ "float64": Float(64),
81
+ "int16": Int(16),
82
+ "int32": Int(32),
83
+ "int8": Int(8),
84
+ "uint16": UInt(16),
85
+ "uint32": UInt(32),
86
+ "uint8": UInt(8),
87
+ }
88
+
89
+
90
+ def _parse_halide_type(s: str) -> Type:
91
+ _check(s in _type_string_map, "The value %s cannot be parsed as a Type." % s)
92
+ return _type_string_map[s]
93
+
94
+
95
+ def _parse_halide_type_list(s: str) -> list[Type]:
96
+ return [_parse_halide_type(t) for t in s.split(",")]
97
+
98
+
99
+ class Requirement:
100
+ # Name
101
+ _name: str = ""
102
+
103
+ # List of the required types, if any. An empty list means
104
+ # no constraints. The list will usually be a single type,
105
+ # except for Outputs that have Tuple-valued results, which
106
+ # can have multiple types.
107
+ _types: list[Type] = []
108
+
109
+ # Required dimensions. 0 = scalar. -1 = unconstrained.
110
+ _dimensions: int = -1
111
+
112
+ def __init__(self, name: str, types: object, dimensions: int):
113
+ self._name = _check_valid_name(name)
114
+ self._types = _normalize_type_list(types)
115
+ self._dimensions = dimensions
116
+ _check(
117
+ len(self._types) > 0,
118
+ "No type has been specified for %s. Try specifying one in code, or by setting '%s.type' as a GeneratorParam."
119
+ % (self._name, self._name),
120
+ )
121
+ _check(
122
+ self._dimensions >= 0,
123
+ "No dimensionality has been specified for %s. Try specifying one in code, or by setting '%s.dim' as a GeneratorParam."
124
+ % (self._name, self._name),
125
+ )
126
+ self._check_types_and_dimensions(types, dimensions)
127
+
128
+ def _check_types_and_dimensions(self, types: list[Type], dimensions: int):
129
+ types = _normalize_type_list(types)
130
+ assert len(types) > 0
131
+ assert dimensions >= 0
132
+
133
+ _check(
134
+ len(types) == len(self._types),
135
+ "Type mismatch for %s: expected %d types but saw %d" % (self._name, len(self._types), len(types)),
136
+ )
137
+ for i in range(0, len(types)):
138
+ _check(
139
+ self._types[i] == types[i],
140
+ "Type mismatch for %s:%d: expected %s saw %s" % (self._name, i, self._types[i], types[i]),
141
+ )
142
+ _check(
143
+ dimensions == self._dimensions,
144
+ "Dimensions mismatch for %s: expected %d saw %d" % (self._name, self._dimensions, dimensions),
145
+ )
146
+
147
+
148
+ # GeneratorParam is a string, int, float, bool, Type
149
+ class GeneratorParam:
150
+ # This tells PyType that we dynamically set attributes
151
+ # on this object. (We don't, actually, but it defeats
152
+ # PyType's complaints about using len(), arithmetic operators, etc.
153
+ # on GeneratorParam fields, since they are replaced at runtime.)
154
+ _HAS_DYNAMIC_ATTRIBUTES:bool = True
155
+
156
+ _name: str = ""
157
+ _value: Any = None
158
+
159
+ def __init__(self, value: Any):
160
+ # Don't parse it here, since we don't have our name yet
161
+ self._value = value
162
+
163
+ @staticmethod
164
+ def _parse_value(name: str, gp_type: type, value: Any) -> object:
165
+ if gp_type is str:
166
+ return str(value)
167
+ elif gp_type is bool:
168
+ return bool(value)
169
+ elif gp_type is int:
170
+ return int(value)
171
+ elif gp_type is float:
172
+ return float(value)
173
+ elif gp_type is Type:
174
+ if isinstance(value, (list, Type)):
175
+ value = _normalize_type_list(value)
176
+ else:
177
+ value = _parse_halide_type_list(str(value))
178
+ return value
179
+ else:
180
+ _fail("GeneratorParam %s has unsupported type %s" % (name, gp_type))
181
+ return None
182
+
183
+ def _make_replacement(self, value: Any, r: Requirement) -> Any:
184
+ assert _is_valid_name(r._name)
185
+
186
+ # Check the default value for validity
187
+ GeneratorParam._parse_value(r._name, type(self._value), self._value)
188
+
189
+ if value is not None:
190
+ # parse replacement value for validity, and also for type match with default.
191
+ return GeneratorParam._parse_value(r._name, type(self._value), value)
192
+ else:
193
+ return self._value
194
+
195
+ def _get_types_and_dimensions(self) -> (list[Type], int):
196
+ # Use dummy type-and-dimensions here so that the ctor won't fail due to undefined status
197
+ return [Int(32)], 0
198
+
199
+
200
+ class InputBuffer(ImageParam):
201
+
202
+ def __init__(self, type: Optional[Type], dimensions: Optional[int]):
203
+ type = _sanitize_type(type)
204
+ if dimensions is None:
205
+ dimensions = -1
206
+ super().__init__(type, dimensions, _unique_name())
207
+
208
+ def _get_types_and_dimensions(self) -> (list[Type], int):
209
+ return _normalize_type_list(self.type()), self.dimensions()
210
+
211
+ def _get_direction_and_kind(self) -> (ArgInfoDirection, ArgInfoKind):
212
+ return ArgInfoDirection.Input, ArgInfoKind.Buffer
213
+
214
+ def _make_replacement(self, value: Any, r: Requirement) -> ImageParam:
215
+ assert _is_valid_name(r._name) and len(r._types) == 1 and r._dimensions >= 0
216
+ if value is None:
217
+ return ImageParam(r._types[0], r._dimensions, r._name)
218
+
219
+ _check(
220
+ isinstance(value, (Buffer, ImageParam)),
221
+ "Input %s requires an ImageParam or Buffer argument when using call(), but saw %s" % (r._name, str(value)),
222
+ )
223
+ _check(
224
+ value.defined(),
225
+ "Cannot set the value for %s to an undefined value." % r._name,
226
+ )
227
+ if isinstance(value, Buffer):
228
+ im = ImageParam(value.type(), value.dimensions(), r._name)
229
+ im.set(value)
230
+ value = im
231
+ r._check_types_and_dimensions([value.type()], value.dimensions())
232
+ return value
233
+
234
+
235
+ class InputScalar(Param):
236
+
237
+ def __init__(self, type: Optional[Type]):
238
+ type = _sanitize_type(type)
239
+ super().__init__(type, _unique_name())
240
+
241
+ def _get_types_and_dimensions(self) -> (list[Type], int):
242
+ return _normalize_type_list(self.type()), 0
243
+
244
+ def _get_direction_and_kind(self) -> (ArgInfoDirection, ArgInfoKind):
245
+ return ArgInfoDirection.Input, ArgInfoKind.Scalar
246
+
247
+ def _make_replacement(self, value: Any, r: Requirement) -> Param:
248
+ assert _is_valid_name(r._name) and len(r._types) == 1 and r._dimensions == 0
249
+ if value is None:
250
+ return Param(r._types[0], r._name)
251
+
252
+ if isinstance(value, (int, float, bool)):
253
+ p = Param(r._types[0], r._name)
254
+ p.set(value)
255
+ return p
256
+ else:
257
+ _check(
258
+ isinstance(value, Param),
259
+ "Input %s requires a Param (or scalar literal) argument when using call(), but saw %s." %
260
+ (r._name, str(value)),
261
+ )
262
+ _check(
263
+ value.defined(),
264
+ "Cannot set the value for %s to an undefined value." % r._name,
265
+ )
266
+ r._check_types_and_dimensions([value.type()], 0)
267
+ return value
268
+
269
+
270
+ class OutputBuffer(Func):
271
+
272
+ def __init__(self, types: Optional[Type], dimensions: Optional[int]):
273
+ types = _normalize_type_list(types)
274
+ if dimensions is None:
275
+ dimensions = -1
276
+ super().__init__(types, dimensions, _unique_name())
277
+ self._types = types
278
+ self._dimensions = dimensions
279
+
280
+ def _get_types_and_dimensions(self) -> (list[Type], int):
281
+ return self._types, self._dimensions
282
+
283
+ def _get_direction_and_kind(self) -> (ArgInfoDirection, ArgInfoKind):
284
+ return ArgInfoDirection.Output, ArgInfoKind.Buffer
285
+
286
+ def _make_replacement(self, value: Any, r: Requirement) -> Func:
287
+ assert _is_valid_name(r._name) and len(r._types) > 0 and r._dimensions >= 0
288
+ if value is None:
289
+ return Func(r._types, r._dimensions, r._name)
290
+
291
+ _check(
292
+ isinstance(value, (Buffer, Func)),
293
+ "Output %s requires a Func or Buffer argument when using call(), but saw %s" % (r._name, str(value)),
294
+ )
295
+ _check(
296
+ value.defined(),
297
+ "Cannot set the value for %s to an undefined value." % r._name,
298
+ )
299
+
300
+ value_types = [value.type()] if isinstance(value, Buffer) else value.output_types
301
+ r._check_types_and_dimensions(value_types, value.dimensions())
302
+
303
+ if isinstance(value, Buffer):
304
+ # Allow assignment from a Buffer<> to an Output<Buffer<>>;
305
+ # this allows us to use a statically-compiled buffer inside a Generator
306
+ # to assign to an output.
307
+ f = Func(r._types, r._dimensions, r._name)
308
+ f[_] = value[_]
309
+ value = f
310
+
311
+ return value
312
+
313
+
314
+ # OutputScalar is just like OutputBuffer, except it is always dimensions = 0
315
+ class OutputScalar(OutputBuffer):
316
+
317
+ def __init__(self, types: Optional[Type]):
318
+ super().__init__(types, 0)
319
+
320
+ def _make_replacement(self, value: Any, r: Requirement) -> Func:
321
+ assert _is_valid_name(r._name) and len(r._types) > 0 and r._dimensions == 0
322
+ if value is None:
323
+ return Func(r._types, 0, r._name)
324
+
325
+ _check(
326
+ isinstance(value, Expr),
327
+ "Output %s requires an Expr argument when using call(), but saw %s" % (r._name, str(value)),
328
+ )
329
+ _check(
330
+ value.defined(),
331
+ "Cannot set the value for %s to an undefined value." % r._name,
332
+ )
333
+ r._check_types_and_dimensions(value.type(), 0)
334
+ f = Func(value.type(), 0, r._name)
335
+ f[_] = value
336
+ return f
337
+
338
+
339
+ @total_ordering
340
+ class _Stage(Enum):
341
+ generator_created = 0
342
+ gp_created = 1
343
+ gp_replaced = 2
344
+ io_created = 3
345
+ configure_called = 4
346
+ io_replaced = 5
347
+ pipeline_built = 6
348
+
349
+ def __lt__(self, other):
350
+ if self.__class__ is other.__class__:
351
+ return self.value < other.value
352
+ return NotImplemented
353
+
354
+
355
+ def _unsorted_cls_dir(cls):
356
+ for base in cls.__bases__:
357
+ yield from _unsorted_cls_dir(base)
358
+
359
+ # Don't use dir(): it will alphabetize the result. It's critical that
360
+ # we produce this list in order of declaration, which __dict__ should guarantee
361
+ # in Python 3.7+.
362
+ for k, v in cls.__dict__.items():
363
+ yield (k, v)
364
+
365
+
366
+ _halide_generator_context = ContextVar('halide_generator_context', default=None)
367
+
368
+
369
+ def _generatorcontext_enter(self: GeneratorContext) -> GeneratorContext:
370
+ if not hasattr(self, "_tokens"):
371
+ self._tokens = []
372
+ self._tokens.append(_halide_generator_context.set(self))
373
+ return self
374
+
375
+
376
+ def _generatorcontext_exit(self: GeneratorContext) -> None:
377
+ _halide_generator_context.reset(self._tokens.pop())
378
+
379
+
380
+ class Generator(ABC):
381
+ """Base class for Halide Generators in Python"""
382
+
383
+ def context(self):
384
+ return GeneratorContext(self._target, self._autoscheduler)
385
+
386
+ def target(self):
387
+ return self._target
388
+
389
+ def autoscheduler(self):
390
+ return self._autoscheduler
391
+
392
+ def using_autoscheduler(self):
393
+ return bool(self._autoscheduler.name)
394
+
395
+ def natural_vector_size(self, type: Type) -> int:
396
+ return self.target().natural_vector_size(type)
397
+
398
+ def add_requirement(self, condition: Expr, *args) -> None:
399
+ assert self._stage < _Stage.pipeline_built
400
+ self._pipeline_requirements.append((condition, [*args]))
401
+
402
+ @classmethod
403
+ def call(cls, *args, **kwargs):
404
+ generator = cls()
405
+
406
+ # First, fill in all the GeneratorParams
407
+ # (in case some are tied to Inputs).
408
+ gp = kwargs.pop("generator_params", {})
409
+ _check(isinstance(gp, dict), "generator_params must be a dict")
410
+ for k, v in gp.items():
411
+ generator._set_generatorparam_value(k, v)
412
+
413
+ generator._advance_to_stage(_Stage.configure_called)
414
+
415
+ _check(
416
+ len(args) <= len(generator._arginfos_in),
417
+ "Generator '%s' allows at most %d positional args, but %d were specified." %
418
+ (generator._get_name(), len(generator._arginfos_in), len(args)))
419
+
420
+ inputs_set = []
421
+ for i in range(0, len(args)):
422
+ a = generator._arginfos_in[i]
423
+ k = a.name
424
+ v = args[i]
425
+ _check(k not in inputs_set, "Input %s was specified multiple times." % k)
426
+ inputs_set.append(k)
427
+ generator._bind_input(k, [v])
428
+
429
+ input_names = [a.name for a in generator._arginfos_in]
430
+ for k, v in kwargs.items():
431
+ _check(k in input_names, "Unknown input '%s' specified via keyword argument." % k)
432
+ _check(k not in inputs_set, "Input %s specified multiple times." % k)
433
+ inputs_set.append(k)
434
+ generator._bind_input(k, [v])
435
+
436
+ _check(
437
+ len(inputs_set) == len(generator._arginfos_in), "Generator '%s' requires %d args, but %d were specified." %
438
+ (generator._get_name(), len(generator._arginfos_in), len(inputs_set)))
439
+
440
+ generator._build_pipeline()
441
+
442
+ outputs = []
443
+ for o in generator._arginfos_out:
444
+ outputs.append(generator._get_output_func(o.name))
445
+
446
+ return outputs[0] if len(outputs) == 1 else tuple(outputs)
447
+
448
+ def compile_to_callable(self):
449
+ pipeline = self._build_pipeline()
450
+ arguments = [
451
+ self._get_input_parameter(a.name)._to_argument()
452
+ for a in self._get_arginfos()
453
+ if a.dir == ArgInfoDirection.Input
454
+ ]
455
+ return pipeline.compile_to_callable(arguments, self._target)
456
+
457
+ # Make it hard for the user to overwrite any members that are GeneratorParams, Inputs, or Outputs
458
+ def __setattr__(self, name, value):
459
+ r = getattr(self, "_requirements", None)
460
+ s = getattr(self, "_stage", None)
461
+ if r and s and (name in r) and (s != _Stage.configure_called) and (s != _Stage.gp_created):
462
+ raise AttributeError("Invalid write to field '%s'" % name)
463
+ super().__setattr__(name, value)
464
+
465
+ def __init__(self, generator_params: dict = {}):
466
+ context = active_generator_context()
467
+
468
+ self._target = context.target()
469
+ self._autoscheduler = context.autoscheduler_params()
470
+ self._stage = _Stage.generator_created
471
+ self._gp_dict = None
472
+ self._inputs_dict = None
473
+ self._outputs_dict = None
474
+ self._arginfos_in = None
475
+ self._arginfos_out = None
476
+ self._pipeline = None
477
+ self._input_parameters = None
478
+ self._output_funcs = None
479
+ self._unhandled_generator_params = {}
480
+ self._requirements = {}
481
+ self._replacements = {}
482
+ self._in_configure = 0
483
+ self._pipeline_requirements = []
484
+
485
+ self._advance_to_gp_created()
486
+ if generator_params:
487
+ _check(isinstance(generator_params, dict), "generator_params must be a dict")
488
+ for k, v in generator_params.items():
489
+ self._set_generatorparam_value(k, v)
490
+
491
+ def allow_out_of_order_inputs_and_outputs(self):
492
+ return False
493
+
494
+ def configure(self):
495
+ pass
496
+
497
+ @abstractmethod
498
+ def generate(self):
499
+ pass
500
+
501
+ def _add_gpio(self, name: str, io, io_dict: dict, arginfos: Optional[list]) -> None:
502
+ _check_valid_name(name)
503
+ _check(
504
+ name not in self._requirements,
505
+ "The name '%s' is used more than once in Generator %s." % (name, self._get_name()),
506
+ )
507
+ types, dimensions = io._get_types_and_dimensions()
508
+ types, dimensions = self._set_io_types_and_dimensions_from_gp(name, types, dimensions)
509
+ r = Requirement(name, types, dimensions)
510
+ self._requirements[name] = r
511
+ io_dict[name] = io
512
+ if arginfos is not None:
513
+ arginfos.append(ArgInfo(name, *io._get_direction_and_kind(), types, dimensions))
514
+
515
+ def add_input(self, name: str, io) -> None:
516
+ _check(self._in_configure > 0, "Can only call add_input() from the configure() method.")
517
+ _check(not hasattr(self, name),
518
+ "Cannot call add_input('%s') because the class already has a member of that name." % name)
519
+ _check(isinstance(io, (InputBuffer, InputScalar)),
520
+ "Cannot call add_input() with an object of type '%s'." % type(io))
521
+ self._add_gpio(name, io, self._inputs_dict, self._arginfos_in)
522
+
523
+ def add_output(self, name: str, io) -> None:
524
+ _check(self._in_configure > 0, "Can only call add_output() from the configure() method.")
525
+ _check(not hasattr(self, name),
526
+ "Cannot call add_output('%s') because the class already has a member of that name." % name)
527
+ _check(isinstance(io, (OutputBuffer, OutputScalar)),
528
+ "Cannot call add_output() with an object of type '%s'." % type(io))
529
+ self._add_gpio(name, io, self._outputs_dict, self._arginfos_out)
530
+
531
+ def _advance_to_stage(self, new_stage: _Stage):
532
+ _stage_advancers = {
533
+ _Stage.gp_created: self._advance_to_gp_created,
534
+ _Stage.gp_replaced: self._advance_to_gp_replaced,
535
+ _Stage.io_created: self._advance_to_io_created,
536
+ _Stage.configure_called: self._advance_to_configure_called,
537
+ _Stage.io_replaced: self._advance_to_io_replaced,
538
+ }
539
+ assert new_stage in _stage_advancers
540
+ a = _stage_advancers[new_stage]
541
+ old_stage = self._stage
542
+ if self._stage < new_stage:
543
+ a()
544
+ assert self._stage >= new_stage
545
+
546
+ def _advance_to_gp_created(self):
547
+ assert self._stage < _Stage.gp_created
548
+ assert not self._gp_dict
549
+ assert not self._inputs_dict
550
+ assert not self._outputs_dict
551
+
552
+ self._gp_dict = {}
553
+ for name, gp in _unsorted_cls_dir(self.__class__):
554
+ if not isinstance(gp, GeneratorParam):
555
+ continue
556
+ self._add_gpio(name, gp, self._gp_dict, None)
557
+
558
+ self._stage = _Stage.gp_created
559
+
560
+ if hasattr(self, "_halide_alias_generator_params"):
561
+ for k, v in self._halide_alias_generator_params.items():
562
+ self._set_generatorparam_value(k, v)
563
+
564
+ def _advance_to_io_created(self):
565
+ self._advance_to_stage(_Stage.gp_replaced)
566
+ assert self._gp_dict is not None
567
+ assert not self._inputs_dict
568
+ assert not self._outputs_dict
569
+
570
+ self._inputs_dict = {}
571
+ self._outputs_dict = {}
572
+ self._arginfos_in = []
573
+ self._arginfos_out = []
574
+ outputs_seen = False
575
+ for name, io in _unsorted_cls_dir(self.__class__):
576
+ is_input = isinstance(io, (InputBuffer, InputScalar))
577
+ is_output = isinstance(io, (OutputBuffer, OutputScalar))
578
+ if not (is_input or is_output):
579
+ continue
580
+
581
+ if is_input and outputs_seen and not self.allow_out_of_order_inputs_and_outputs():
582
+ io_order_warning = ("Generators will always produce code that orders all Inputs before all Outputs; "
583
+ "this Generator declares the Inputs and Outputs in a different order, so the calling convention may not be as expected. "
584
+ "A future version of Halide will make this illegal, and require all Inputs to be declared before all Outputs. "
585
+ "(You can avoid this requirement by overriding Generator::allow_out_of_order_inputs_and_outputs().)")
586
+ warnings.warn(io_order_warning)
587
+
588
+ if is_output:
589
+ outputs_seen = True
590
+ d = self._inputs_dict if is_input else self._outputs_dict
591
+ a = self._arginfos_in if is_input else self._arginfos_out
592
+ self._add_gpio(name, io, d, a)
593
+
594
+ _check(
595
+ len(self._unhandled_generator_params) == 0,
596
+ "Generator %s has no GeneratorParam(s) named: %s" %
597
+ (self._get_name(), list(self._unhandled_generator_params.keys())),
598
+ )
599
+
600
+ self._stage = _Stage.io_created
601
+
602
+ def _set_io_types_and_dimensions_from_gp(self, name: str, current_types: list[Type],
603
+ current_dimensions: int) -> (list[Type], int):
604
+ new_types = current_types
605
+ new_dimensions = current_dimensions
606
+
607
+ type_name = "%s.type" % name
608
+ if type_name in self._unhandled_generator_params:
609
+ value = self._unhandled_generator_params.pop(type_name)
610
+ new_types = GeneratorParam._parse_value(type_name, Type, value)
611
+ _check(
612
+ len(current_types) == 0,
613
+ "Cannot set the GeneratorParam %s for %s because the value is explicitly specified in the Python source."
614
+ % (type_name, self._get_name()),
615
+ )
616
+
617
+ dimensions_name = "%s.dim" % name
618
+ if dimensions_name in self._unhandled_generator_params:
619
+ value = self._unhandled_generator_params.pop(dimensions_name)
620
+ new_dimensions = GeneratorParam._parse_value(dimensions_name, int, value)
621
+ _check(
622
+ current_dimensions == -1,
623
+ "Cannot set the GeneratorParam %s for %s because the value is explicitly specified in the Python source."
624
+ % (dimensions_name, self._get_name()),
625
+ )
626
+
627
+ return new_types, new_dimensions
628
+
629
+ def _advance_to_configure_called(self):
630
+ self._advance_to_stage(_Stage.io_created)
631
+
632
+ self._in_configure += 1
633
+ self.configure()
634
+ self._in_configure -= 1
635
+
636
+ _check(
637
+ len(self._outputs_dict) > 0,
638
+ "Generator '%s' must declare at least one output." % self._get_name(),
639
+ )
640
+
641
+ self._stage = _Stage.configure_called
642
+
643
+ def _replace_one(self, gpio_dict):
644
+ for name, gpio in gpio_dict.items():
645
+ # Note that new_io will be a different type (e.g. InputBuffer -> ImageParam)
646
+ new_gpio = self._replacements.pop(name, None)
647
+ if not new_gpio:
648
+ new_gpio = gpio._make_replacement(None, self._requirements[name])
649
+ setattr(self, name, new_gpio)
650
+
651
+ def _advance_to_gp_replaced(self):
652
+ self._advance_to_stage(_Stage.gp_created)
653
+ self._replace_one(self._gp_dict)
654
+ self._stage = _Stage.gp_replaced
655
+
656
+ def _advance_to_io_replaced(self):
657
+ self._advance_to_stage(_Stage.configure_called)
658
+ self._replace_one(self._inputs_dict)
659
+ self._replace_one(self._outputs_dict)
660
+ self._stage = _Stage.io_replaced
661
+
662
+ # --------------- AbstractGenerator methods (partial)
663
+ def _get_name(self) -> str:
664
+ return self._halide_registered_name
665
+
666
+ def _get_arginfos(self) -> list[ArgInfo]:
667
+ self._advance_to_stage(_Stage.configure_called)
668
+ return self._arginfos_in + self._arginfos_out
669
+
670
+ def _set_generatorparam_value(self, name: str, value: Any):
671
+ _check(
672
+ name != "target",
673
+ "The GeneratorParam named %s cannot be set by set_generatorparam_value." % name,
674
+ )
675
+ assert self._stage == _Stage.gp_created
676
+ assert not self._pipeline
677
+ if name in self._gp_dict:
678
+ gp = self._gp_dict[name]
679
+ assert isinstance(gp, GeneratorParam)
680
+ old_value = gp._value
681
+ new_value = GeneratorParam._parse_value(name, type(old_value), value)
682
+ # Do not mutate the existing GP in place; it could be shared across multiple Generators.
683
+ self._gp_dict[name] = GeneratorParam(new_value)
684
+ elif name == "autoscheduler":
685
+ _check(not self.autoscheduler().name, "The GeneratorParam %s cannot be set more than once" % name)
686
+ self.autoscheduler().name = value
687
+ elif name.startswith("autoscheduler."):
688
+ sub_key = name[len("autoscheduler."):]
689
+ _check(sub_key not in self.autoscheduler().extra,
690
+ "The GeneratorParam %s cannot be set more than once" % name)
691
+ self.autoscheduler().extra[sub_key] = value
692
+ else:
693
+ self._unhandled_generator_params[name] = value
694
+
695
+ def _build_pipeline(self) -> Pipeline:
696
+ self._advance_to_stage(_Stage.io_replaced)
697
+
698
+ assert not self._pipeline
699
+ assert not self._input_parameters
700
+ assert not self._output_funcs
701
+
702
+ # Ensure that the current context is the one in self.
703
+ # For most Generators this won't matter, but if the Generator
704
+ # invokes SomeOtherGenerator.call(), it would be nice to have this
705
+ # be the default, so that the end user doesn't have to mess with it.
706
+ with self.context():
707
+ self.generate()
708
+
709
+ self._input_parameters = {n: getattr(self, n).parameter() for n in self._inputs_dict}
710
+ self._output_funcs = {n: getattr(self, n) for n in self._outputs_dict}
711
+ _check(
712
+ len(self._output_funcs) > 0,
713
+ "Generator %s must declare at least one Output in Arguments." % self._get_name(),
714
+ )
715
+
716
+ funcs = []
717
+ for name, f in self._output_funcs.items():
718
+ _check(f.defined(), "Output '%s' was not defined." % name)
719
+ self._requirements[name]._check_types_and_dimensions(f.types(), f.dimensions())
720
+ funcs.append(f)
721
+
722
+ self._pipeline = Pipeline(funcs)
723
+ for condition, error_args in self._pipeline_requirements:
724
+ self._pipeline.add_requirement(condition, *error_args)
725
+ self._stage = _Stage.pipeline_built
726
+ return self._pipeline
727
+
728
+ def _get_input_parameter(self, name: str) -> InternalParameter:
729
+ assert self._stage == _Stage.pipeline_built
730
+ _check(name in self._input_parameters, "Unknown input: %s" % name)
731
+ return self._input_parameters[name]
732
+
733
+ def _get_output_func(self, name: str) -> Func:
734
+ assert self._stage == _Stage.pipeline_built
735
+ _check(name in self._output_funcs, "Unknown output: %s" % name)
736
+ return self._output_funcs[name]
737
+
738
+ def _bind_input(self, name: str, values: list[object]):
739
+ assert self._stage < _Stage.io_replaced
740
+ self._advance_to_stage(_Stage.configure_called)
741
+ _check(len(values) == 1, "Too many values specified for input: %s" % name)
742
+ _check(name in self._inputs_dict, "There is no input with the name: %s" % name)
743
+ assert name not in self._replacements
744
+ self._replacements[name] = self._inputs_dict[name]._make_replacement(values[0], self._requirements[name])
745
+
746
+
747
+ _python_generators: dict = {}
748
+
749
+
750
+ def _get_python_generator_names() -> list[str]:
751
+ return _python_generators.keys()
752
+
753
+
754
+ def _create_python_generator(name: str, context: GeneratorContext):
755
+ cls = _python_generators.get(name, None)
756
+ if not isclass(cls):
757
+ return None
758
+ with context:
759
+ return cls()
760
+
761
+
762
+ def _fqname(o):
763
+ k = o
764
+ m = k.__module__
765
+ q = k.__qualname__
766
+ if m == "__main__" or k == "builtins":
767
+ return q
768
+ return m + "." + q
769
+
770
+
771
+ def active_generator_context() -> GeneratorContext:
772
+ context = _halide_generator_context.get()
773
+ _check(isinstance(context, GeneratorContext), "There is no active GeneratorContext")
774
+ return context
775
+
776
+
777
+ def _is_interactive_mode() -> bool:
778
+ return hasattr(sys, 'ps1')
779
+
780
+
781
+ def _check_generator_name_in_use(n:str):
782
+ if _is_interactive_mode():
783
+ # In interactive mode, it's OK to redefine generators... in fact, it's really
784
+ # annoying not to allow this (e.g. in Colab)
785
+ #
786
+ # (Want to debug? Include this:)
787
+ # if n in _python_generators:
788
+ # builtins.print("REDEFINING ", n)
789
+ return
790
+
791
+ _check(n not in _python_generators, "The Generator name %s is already in use." % n)
792
+
793
+
794
+ def alias(**kwargs):
795
+
796
+ def alias_impl(cls):
797
+ for k, v in kwargs.items():
798
+ _check_valid_name(k)
799
+ _check(hasattr(cls, "_halide_registered_name"), "@alias can only be used in conjunction with @generator.")
800
+ _check_generator_name_in_use(k)
801
+ _check(type(v) is dict, "The Generator alias %s specifies something other than a dict." % k)
802
+ new_cls = type(k, (cls,), {"_halide_registered_name": k, "_halide_alias_generator_params": v})
803
+ _python_generators[k] = new_cls
804
+ return cls
805
+
806
+ return alias_impl
807
+
808
+ def generator(name:str=""):
809
+ # This code relies on dicts preserving key-insertion order, which is only
810
+ # guaranteed for all Python implementations as of v3.7.
811
+ _check(sys.version_info >= (3, 7), "Halide Generators require Python 3.7 or later.")
812
+ def generator_impl(cls):
813
+ n = name if name else _fqname(cls)
814
+ _check_generator_name_in_use(n)
815
+ _check(isclass(cls), "@generator can only be used on classes.")
816
+ # Allow (but don't require) explicit inheritance from hl.Generator;
817
+ # static type checkers (e.g. pytype) can complain that the decorated class
818
+ # uses inherited methods since it can't correctly infer the inheritance.
819
+ if issubclass(cls, Generator):
820
+ new_cls = type(cls.__name__, (cls,), {"_halide_registered_name": n})
821
+ else:
822
+ new_cls = type(cls.__name__, (cls, Generator), {"_halide_registered_name": n})
823
+ _python_generators[n] = new_cls
824
+ return new_cls
825
+
826
+ return generator_impl
827
+
828
+ def funcs(names:str) -> tuple(Func):
829
+ """Given a space-delimited string, create a Func for each substring and return as a tuple."""
830
+ return (Func(n) for n in names.split(' '))
831
+
832
+
833
+ def vars(names:str) -> tuple(Var):
834
+ """Given a space-delimited string, create a Var for each substring and return as a tuple."""
835
+ return (Var(n) for n in names.split(' '))