halide 19.0.0__cp310-cp310-win_amd64.whl → 21.0.0__cp310-cp310-win_amd64.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.
- halide/__init__.py +10 -6
- halide/_generator_helpers.py +190 -127
- halide/bin/Halide.dll +0 -0
- halide/bin/adams2019_retrain_cost_model.exe +0 -0
- halide/bin/adams2019_weightsdir_to_weightsfile.exe +0 -0
- halide/bin/anderson2021_retrain_cost_model.exe +0 -0
- halide/bin/anderson2021_weightsdir_to_weightsfile.exe +0 -0
- halide/bin/featurization_to_sample.exe +0 -0
- halide/bin/gengen.exe +0 -0
- halide/bin/get_host_target.exe +0 -0
- halide/halide_.cp310-win_amd64.pyd +0 -0
- halide/imageio.py +1 -1
- halide/include/Halide.h +1775 -1477
- halide/include/HalideBuffer.h +13 -13
- halide/include/HalidePyTorchCudaHelpers.h +1 -1
- halide/include/HalideRuntime.h +35 -16
- halide/lib/Halide.lib +0 -0
- halide/lib/HalidePyStubs.lib +0 -0
- halide/lib/Halide_GenGen.lib +0 -0
- halide/lib/autoschedule_adams2019.dll +0 -0
- halide/lib/autoschedule_anderson2021.dll +0 -0
- halide/lib/autoschedule_li2018.dll +0 -0
- halide/lib/autoschedule_mullapudi2016.dll +0 -0
- halide/lib/cmake/Halide/FindHalide_LLVM.cmake +44 -15
- halide/lib/cmake/Halide/FindV8.cmake +0 -12
- halide/lib/cmake/Halide/Halide-shared-targets.cmake +1 -1
- halide/lib/cmake/Halide/HalideConfig.cmake +1 -1
- halide/lib/cmake/Halide/HalideConfigVersion.cmake +3 -3
- halide/lib/cmake/HalideHelpers/Halide-Interfaces.cmake +1 -0
- halide/lib/cmake/HalideHelpers/HalideGeneratorHelpers.cmake +31 -9
- halide/lib/cmake/HalideHelpers/HalideHelpersConfigVersion.cmake +3 -3
- halide/lib/cmake/Halide_Python/Halide_PythonConfigVersion.cmake +3 -3
- halide/share/doc/Halide/README.md +7 -6
- halide/share/doc/Halide/doc/BuildingHalideWithCMake.md +78 -6
- halide/share/doc/Halide/doc/HalideCMakePackage.md +9 -2
- halide/share/doc/Halide/doc/Python.md +19 -4
- halide/share/doc/Halide/doc/RunGen.md +1 -1
- {halide-19.0.0.data → halide-21.0.0.data}/data/share/cmake/Halide/HalideConfig.cmake +4 -1
- {halide-19.0.0.data → halide-21.0.0.data}/data/share/cmake/Halide/HalideConfigVersion.cmake +3 -3
- {halide-19.0.0.data → halide-21.0.0.data}/data/share/cmake/HalideHelpers/HalideHelpersConfig.cmake +4 -1
- {halide-19.0.0.data → halide-21.0.0.data}/data/share/cmake/HalideHelpers/HalideHelpersConfigVersion.cmake +3 -3
- halide-21.0.0.dist-info/METADATA +302 -0
- {halide-19.0.0.dist-info → halide-21.0.0.dist-info}/RECORD +45 -45
- {halide-19.0.0.dist-info → halide-21.0.0.dist-info}/WHEEL +1 -1
- halide-19.0.0.dist-info/METADATA +0 -301
- {halide-19.0.0.dist-info → halide-21.0.0.dist-info}/licenses/LICENSE.txt +0 -0
halide/_generator_helpers.py
CHANGED
@@ -3,15 +3,35 @@ from abc import ABC, abstractmethod
|
|
3
3
|
from contextvars import ContextVar
|
4
4
|
from enum import Enum
|
5
5
|
from functools import total_ordering
|
6
|
-
from .halide_ import
|
7
|
-
|
6
|
+
from .halide_ import (
|
7
|
+
ArgInfo,
|
8
|
+
ArgInfoDirection,
|
9
|
+
ArgInfoKind,
|
10
|
+
Bool,
|
11
|
+
Buffer,
|
12
|
+
Expr,
|
13
|
+
Float,
|
14
|
+
Func,
|
15
|
+
GeneratorContext,
|
16
|
+
HalideError,
|
17
|
+
ImageParam,
|
18
|
+
Int,
|
19
|
+
Param,
|
20
|
+
Pipeline,
|
21
|
+
Type,
|
22
|
+
UInt,
|
23
|
+
Var,
|
24
|
+
_,
|
25
|
+
_UnspecifiedType,
|
26
|
+
_unique_name,
|
27
|
+
)
|
8
28
|
from inspect import isclass
|
9
29
|
from typing import Any, Optional
|
10
|
-
import builtins
|
11
30
|
import re
|
12
31
|
import sys
|
13
32
|
import warnings
|
14
33
|
|
34
|
+
|
15
35
|
def _fail(msg: str):
|
16
36
|
raise HalideError(msg)
|
17
37
|
|
@@ -20,12 +40,14 @@ def _check(cond: bool, msg: str):
|
|
20
40
|
if not cond:
|
21
41
|
_fail(msg)
|
22
42
|
|
43
|
+
|
23
44
|
# Basically, a valid C identifier, except:
|
24
45
|
#
|
25
46
|
# -- initial _ is forbidden (rather than merely "reserved")
|
26
47
|
# -- two underscores in a row is also forbidden
|
27
48
|
_NAME_RE = re.compile(r"^(?!.*__)[A-Za-z0-9][A-Za-z0-9_]*$")
|
28
49
|
|
50
|
+
|
29
51
|
def _is_valid_name(name: str) -> bool:
|
30
52
|
if not name:
|
31
53
|
return False
|
@@ -39,7 +61,7 @@ def _is_valid_name(name: str) -> bool:
|
|
39
61
|
def _check_valid_name(name: str) -> str:
|
40
62
|
_check(
|
41
63
|
_is_valid_name(name),
|
42
|
-
"The name '
|
64
|
+
f"The name '{name}' is not valid for a GeneratorParam, Input, or Output.",
|
43
65
|
)
|
44
66
|
return name
|
45
67
|
|
@@ -57,17 +79,16 @@ def _sanitize_type(t: object) -> Type:
|
|
57
79
|
elif t is float:
|
58
80
|
return Float(32)
|
59
81
|
else:
|
60
|
-
_check(isinstance(t, Type), "Expected a Halide Type, but saw:
|
82
|
+
_check(isinstance(t, Type), f"Expected a Halide Type, but saw: {t}")
|
61
83
|
return t
|
62
84
|
|
85
|
+
|
63
86
|
def _normalize_type_list(types: object) -> list[Type]:
|
64
87
|
# Always treat _UnspecifiedType as a non-type
|
65
|
-
if types is None:
|
66
|
-
types = []
|
67
|
-
elif isinstance(types, Type) and types == _UnspecifiedType():
|
88
|
+
if types is None or (isinstance(types, Type) and types == _UnspecifiedType()):
|
68
89
|
types = []
|
69
90
|
if type(types) is not list:
|
70
|
-
types = [types]
|
91
|
+
types = [types]
|
71
92
|
types = [_sanitize_type(t) for t in types]
|
72
93
|
return types
|
73
94
|
|
@@ -88,7 +109,7 @@ _type_string_map = {
|
|
88
109
|
|
89
110
|
|
90
111
|
def _parse_halide_type(s: str) -> Type:
|
91
|
-
_check(s in _type_string_map, "The value
|
112
|
+
_check(s in _type_string_map, f"The value {s} cannot be parsed as a Type.")
|
92
113
|
return _type_string_map[s]
|
93
114
|
|
94
115
|
|
@@ -97,31 +118,22 @@ def _parse_halide_type_list(s: str) -> list[Type]:
|
|
97
118
|
|
98
119
|
|
99
120
|
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
121
|
def __init__(self, name: str, types: object, dimensions: int):
|
113
122
|
self._name = _check_valid_name(name)
|
123
|
+
# List of the required types, if any. An empty list means
|
124
|
+
# no constraints. The list will usually be a single type,
|
125
|
+
# except for Outputs that have Tuple-valued results, which
|
126
|
+
# can have multiple types.
|
114
127
|
self._types = _normalize_type_list(types)
|
128
|
+
# Required dimensions. 0 = scalar. -1 = unconstrained.
|
115
129
|
self._dimensions = dimensions
|
116
130
|
_check(
|
117
131
|
len(self._types) > 0,
|
118
|
-
"No type has been specified for
|
119
|
-
% (self._name, self._name),
|
132
|
+
f"No type has been specified for {self._name}. Try specifying one in code, or by setting '{self._name}.type' as a GeneratorParam.",
|
120
133
|
)
|
121
134
|
_check(
|
122
135
|
self._dimensions >= 0,
|
123
|
-
"No dimensionality has been specified for
|
124
|
-
% (self._name, self._name),
|
136
|
+
f"No dimensionality has been specified for {self._name}. Try specifying one in code, or by setting '{self._name}.dim' as a GeneratorParam.",
|
125
137
|
)
|
126
138
|
self._check_types_and_dimensions(types, dimensions)
|
127
139
|
|
@@ -132,16 +144,16 @@ class Requirement:
|
|
132
144
|
|
133
145
|
_check(
|
134
146
|
len(types) == len(self._types),
|
135
|
-
"Type mismatch for
|
147
|
+
f"Type mismatch for {self._name}: expected {len(self._types)} types but saw {len(types)}",
|
136
148
|
)
|
137
149
|
for i in range(0, len(types)):
|
138
150
|
_check(
|
139
151
|
self._types[i] == types[i],
|
140
|
-
"Type mismatch for
|
152
|
+
f"Type mismatch for {self._name}:{i}: expected {self._types[i]} saw {types[i]}",
|
141
153
|
)
|
142
154
|
_check(
|
143
155
|
dimensions == self._dimensions,
|
144
|
-
"Dimensions mismatch for
|
156
|
+
f"Dimensions mismatch for {self._name}: expected {self._dimensions} saw {dimensions}",
|
145
157
|
)
|
146
158
|
|
147
159
|
|
@@ -151,7 +163,7 @@ class GeneratorParam:
|
|
151
163
|
# on this object. (We don't, actually, but it defeats
|
152
164
|
# PyType's complaints about using len(), arithmetic operators, etc.
|
153
165
|
# on GeneratorParam fields, since they are replaced at runtime.)
|
154
|
-
_HAS_DYNAMIC_ATTRIBUTES:bool = True
|
166
|
+
_HAS_DYNAMIC_ATTRIBUTES: bool = True
|
155
167
|
|
156
168
|
_name: str = ""
|
157
169
|
_value: Any = None
|
@@ -177,7 +189,7 @@ class GeneratorParam:
|
|
177
189
|
value = _parse_halide_type_list(str(value))
|
178
190
|
return value
|
179
191
|
else:
|
180
|
-
_fail("GeneratorParam
|
192
|
+
_fail(f"GeneratorParam {name} has unsupported type {gp_type}")
|
181
193
|
return None
|
182
194
|
|
183
195
|
def _make_replacement(self, value: Any, r: Requirement) -> Any:
|
@@ -192,23 +204,22 @@ class GeneratorParam:
|
|
192
204
|
else:
|
193
205
|
return self._value
|
194
206
|
|
195
|
-
def _get_types_and_dimensions(self) ->
|
207
|
+
def _get_types_and_dimensions(self) -> tuple[list[Type], int]:
|
196
208
|
# Use dummy type-and-dimensions here so that the ctor won't fail due to undefined status
|
197
209
|
return [Int(32)], 0
|
198
210
|
|
199
211
|
|
200
212
|
class InputBuffer(ImageParam):
|
201
|
-
|
202
213
|
def __init__(self, type: Optional[Type], dimensions: Optional[int]):
|
203
214
|
type = _sanitize_type(type)
|
204
215
|
if dimensions is None:
|
205
216
|
dimensions = -1
|
206
217
|
super().__init__(type, dimensions, _unique_name())
|
207
218
|
|
208
|
-
def _get_types_and_dimensions(self) ->
|
219
|
+
def _get_types_and_dimensions(self) -> tuple[list[Type], int]:
|
209
220
|
return _normalize_type_list(self.type()), self.dimensions()
|
210
221
|
|
211
|
-
def _get_direction_and_kind(self) ->
|
222
|
+
def _get_direction_and_kind(self) -> tuple[ArgInfoDirection, ArgInfoKind]:
|
212
223
|
return ArgInfoDirection.Input, ArgInfoKind.Buffer
|
213
224
|
|
214
225
|
def _make_replacement(self, value: Any, r: Requirement) -> ImageParam:
|
@@ -218,11 +229,11 @@ class InputBuffer(ImageParam):
|
|
218
229
|
|
219
230
|
_check(
|
220
231
|
isinstance(value, (Buffer, ImageParam)),
|
221
|
-
"Input
|
232
|
+
f"Input {r._name} requires an ImageParam or Buffer argument when using call(), but saw {value}",
|
222
233
|
)
|
223
234
|
_check(
|
224
235
|
value.defined(),
|
225
|
-
"Cannot set the value for
|
236
|
+
f"Cannot set the value for {r._name} to an undefined value.",
|
226
237
|
)
|
227
238
|
if isinstance(value, Buffer):
|
228
239
|
im = ImageParam(value.type(), value.dimensions(), r._name)
|
@@ -233,15 +244,14 @@ class InputBuffer(ImageParam):
|
|
233
244
|
|
234
245
|
|
235
246
|
class InputScalar(Param):
|
236
|
-
|
237
247
|
def __init__(self, type: Optional[Type]):
|
238
248
|
type = _sanitize_type(type)
|
239
249
|
super().__init__(type, _unique_name())
|
240
250
|
|
241
|
-
def _get_types_and_dimensions(self) ->
|
251
|
+
def _get_types_and_dimensions(self) -> tuple[list[Type], int]:
|
242
252
|
return _normalize_type_list(self.type()), 0
|
243
253
|
|
244
|
-
def _get_direction_and_kind(self) ->
|
254
|
+
def _get_direction_and_kind(self) -> tuple[ArgInfoDirection, ArgInfoKind]:
|
245
255
|
return ArgInfoDirection.Input, ArgInfoKind.Scalar
|
246
256
|
|
247
257
|
def _make_replacement(self, value: Any, r: Requirement) -> Param:
|
@@ -256,19 +266,17 @@ class InputScalar(Param):
|
|
256
266
|
else:
|
257
267
|
_check(
|
258
268
|
isinstance(value, Param),
|
259
|
-
"Input
|
260
|
-
(r._name, str(value)),
|
269
|
+
f"Input {r._name} requires a Param (or scalar literal) argument when using call(), but saw {value}.",
|
261
270
|
)
|
262
271
|
_check(
|
263
272
|
value.defined(),
|
264
|
-
"Cannot set the value for
|
273
|
+
f"Cannot set the value for {r._name} to an undefined value.",
|
265
274
|
)
|
266
275
|
r._check_types_and_dimensions([value.type()], 0)
|
267
276
|
return value
|
268
277
|
|
269
278
|
|
270
279
|
class OutputBuffer(Func):
|
271
|
-
|
272
280
|
def __init__(self, types: Optional[Type], dimensions: Optional[int]):
|
273
281
|
types = _normalize_type_list(types)
|
274
282
|
if dimensions is None:
|
@@ -277,10 +285,10 @@ class OutputBuffer(Func):
|
|
277
285
|
self._types = types
|
278
286
|
self._dimensions = dimensions
|
279
287
|
|
280
|
-
def _get_types_and_dimensions(self) ->
|
288
|
+
def _get_types_and_dimensions(self) -> tuple[list[Type], int]:
|
281
289
|
return self._types, self._dimensions
|
282
290
|
|
283
|
-
def _get_direction_and_kind(self) ->
|
291
|
+
def _get_direction_and_kind(self) -> tuple[ArgInfoDirection, ArgInfoKind]:
|
284
292
|
return ArgInfoDirection.Output, ArgInfoKind.Buffer
|
285
293
|
|
286
294
|
def _make_replacement(self, value: Any, r: Requirement) -> Func:
|
@@ -290,14 +298,16 @@ class OutputBuffer(Func):
|
|
290
298
|
|
291
299
|
_check(
|
292
300
|
isinstance(value, (Buffer, Func)),
|
293
|
-
"Output
|
301
|
+
f"Output {r._name} requires a Func or Buffer argument when using call(), but saw {value}",
|
294
302
|
)
|
295
303
|
_check(
|
296
304
|
value.defined(),
|
297
|
-
"Cannot set the value for
|
305
|
+
f"Cannot set the value for {r._name} to an undefined value.",
|
298
306
|
)
|
299
307
|
|
300
|
-
value_types =
|
308
|
+
value_types = (
|
309
|
+
[value.type()] if isinstance(value, Buffer) else value.output_types
|
310
|
+
)
|
301
311
|
r._check_types_and_dimensions(value_types, value.dimensions())
|
302
312
|
|
303
313
|
if isinstance(value, Buffer):
|
@@ -313,7 +323,6 @@ class OutputBuffer(Func):
|
|
313
323
|
|
314
324
|
# OutputScalar is just like OutputBuffer, except it is always dimensions = 0
|
315
325
|
class OutputScalar(OutputBuffer):
|
316
|
-
|
317
326
|
def __init__(self, types: Optional[Type]):
|
318
327
|
super().__init__(types, 0)
|
319
328
|
|
@@ -324,11 +333,11 @@ class OutputScalar(OutputBuffer):
|
|
324
333
|
|
325
334
|
_check(
|
326
335
|
isinstance(value, Expr),
|
327
|
-
"Output
|
336
|
+
f"Output {r._name} requires an Expr argument when using call(), but saw {value}",
|
328
337
|
)
|
329
338
|
_check(
|
330
339
|
value.defined(),
|
331
|
-
"Cannot set the value for
|
340
|
+
f"Cannot set the value for {r._name} to an undefined value.",
|
332
341
|
)
|
333
342
|
r._check_types_and_dimensions(value.type(), 0)
|
334
343
|
f = Func(value.type(), 0, r._name)
|
@@ -359,11 +368,10 @@ def _unsorted_cls_dir(cls):
|
|
359
368
|
# Don't use dir(): it will alphabetize the result. It's critical that
|
360
369
|
# we produce this list in order of declaration, which __dict__ should guarantee
|
361
370
|
# in Python 3.7+.
|
362
|
-
|
363
|
-
yield (k, v)
|
371
|
+
yield from cls.__dict__.items()
|
364
372
|
|
365
373
|
|
366
|
-
_halide_generator_context = ContextVar(
|
374
|
+
_halide_generator_context = ContextVar("halide_generator_context", default=None)
|
367
375
|
|
368
376
|
|
369
377
|
def _generatorcontext_enter(self: GeneratorContext) -> GeneratorContext:
|
@@ -414,34 +422,35 @@ class Generator(ABC):
|
|
414
422
|
|
415
423
|
_check(
|
416
424
|
len(args) <= len(generator._arginfos_in),
|
417
|
-
"Generator '
|
418
|
-
|
425
|
+
f"Generator '{generator._get_name()}' allows at most {len(generator._arginfos_in)} positional args, but {len(args)} were specified.",
|
426
|
+
)
|
419
427
|
|
420
428
|
inputs_set = []
|
421
429
|
for i in range(0, len(args)):
|
422
430
|
a = generator._arginfos_in[i]
|
423
431
|
k = a.name
|
424
432
|
v = args[i]
|
425
|
-
_check(k not in inputs_set, "Input
|
433
|
+
_check(k not in inputs_set, f"Input {k} was specified multiple times.")
|
426
434
|
inputs_set.append(k)
|
427
435
|
generator._bind_input(k, [v])
|
428
436
|
|
429
437
|
input_names = [a.name for a in generator._arginfos_in]
|
430
438
|
for k, v in kwargs.items():
|
431
|
-
_check(
|
432
|
-
|
439
|
+
_check(
|
440
|
+
k in input_names, f"Unknown input '{k}' specified via keyword argument."
|
441
|
+
)
|
442
|
+
_check(k not in inputs_set, f"Input {k} specified multiple times.")
|
433
443
|
inputs_set.append(k)
|
434
444
|
generator._bind_input(k, [v])
|
435
445
|
|
436
446
|
_check(
|
437
|
-
len(inputs_set) == len(generator._arginfos_in),
|
438
|
-
|
447
|
+
len(inputs_set) == len(generator._arginfos_in),
|
448
|
+
f"Generator '{generator._get_name()}' requires {len(generator._arginfos_in)} args, but {len(inputs_set)} were specified.",
|
449
|
+
)
|
439
450
|
|
440
451
|
generator._build_pipeline()
|
441
452
|
|
442
|
-
outputs = []
|
443
|
-
for o in generator._arginfos_out:
|
444
|
-
outputs.append(generator._get_output_func(o.name))
|
453
|
+
outputs = [generator._get_output_func(o.name) for o in generator._arginfos_out]
|
445
454
|
|
446
455
|
return outputs[0] if len(outputs) == 1 else tuple(outputs)
|
447
456
|
|
@@ -458,8 +467,14 @@ class Generator(ABC):
|
|
458
467
|
def __setattr__(self, name, value):
|
459
468
|
r = getattr(self, "_requirements", None)
|
460
469
|
s = getattr(self, "_stage", None)
|
461
|
-
if
|
462
|
-
|
470
|
+
if (
|
471
|
+
r
|
472
|
+
and s
|
473
|
+
and (name in r)
|
474
|
+
and (s != _Stage.configure_called)
|
475
|
+
and (s != _Stage.gp_created)
|
476
|
+
):
|
477
|
+
raise AttributeError(f"Invalid write to field '{name}'")
|
463
478
|
super().__setattr__(name, value)
|
464
479
|
|
465
480
|
def __init__(self, generator_params: dict = {}):
|
@@ -484,7 +499,9 @@ class Generator(ABC):
|
|
484
499
|
|
485
500
|
self._advance_to_gp_created()
|
486
501
|
if generator_params:
|
487
|
-
_check(
|
502
|
+
_check(
|
503
|
+
isinstance(generator_params, dict), "generator_params must be a dict"
|
504
|
+
)
|
488
505
|
for k, v in generator_params.items():
|
489
506
|
self._set_generatorparam_value(k, v)
|
490
507
|
|
@@ -502,30 +519,48 @@ class Generator(ABC):
|
|
502
519
|
_check_valid_name(name)
|
503
520
|
_check(
|
504
521
|
name not in self._requirements,
|
505
|
-
"The name '
|
522
|
+
f"The name '{name}' is used more than once in Generator {self._get_name()}.",
|
506
523
|
)
|
507
524
|
types, dimensions = io._get_types_and_dimensions()
|
508
|
-
types, dimensions = self._set_io_types_and_dimensions_from_gp(
|
525
|
+
types, dimensions = self._set_io_types_and_dimensions_from_gp(
|
526
|
+
name, types, dimensions
|
527
|
+
)
|
509
528
|
r = Requirement(name, types, dimensions)
|
510
529
|
self._requirements[name] = r
|
511
530
|
io_dict[name] = io
|
512
531
|
if arginfos is not None:
|
513
|
-
arginfos.append(
|
532
|
+
arginfos.append(
|
533
|
+
ArgInfo(name, *io._get_direction_and_kind(), types, dimensions)
|
534
|
+
)
|
514
535
|
|
515
536
|
def add_input(self, name: str, io) -> None:
|
516
|
-
_check(
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
537
|
+
_check(
|
538
|
+
self._in_configure > 0,
|
539
|
+
"Can only call add_input() from the configure() method.",
|
540
|
+
)
|
541
|
+
_check(
|
542
|
+
not hasattr(self, name),
|
543
|
+
f"Cannot call add_input('{name}') because the class already has a member of that name.",
|
544
|
+
)
|
545
|
+
_check(
|
546
|
+
isinstance(io, (InputBuffer, InputScalar)),
|
547
|
+
f"Cannot call add_input() with an object of type '{type(io)}'.",
|
548
|
+
)
|
521
549
|
self._add_gpio(name, io, self._inputs_dict, self._arginfos_in)
|
522
550
|
|
523
551
|
def add_output(self, name: str, io) -> None:
|
524
|
-
_check(
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
552
|
+
_check(
|
553
|
+
self._in_configure > 0,
|
554
|
+
"Can only call add_output() from the configure() method.",
|
555
|
+
)
|
556
|
+
_check(
|
557
|
+
not hasattr(self, name),
|
558
|
+
f"Cannot call add_output('{name}') because the class already has a member of that name.",
|
559
|
+
)
|
560
|
+
_check(
|
561
|
+
isinstance(io, (OutputBuffer, OutputScalar)),
|
562
|
+
f"Cannot call add_output() with an object of type '{type(io)}'.",
|
563
|
+
)
|
529
564
|
self._add_gpio(name, io, self._outputs_dict, self._arginfos_out)
|
530
565
|
|
531
566
|
def _advance_to_stage(self, new_stage: _Stage):
|
@@ -538,7 +573,6 @@ class Generator(ABC):
|
|
538
573
|
}
|
539
574
|
assert new_stage in _stage_advancers
|
540
575
|
a = _stage_advancers[new_stage]
|
541
|
-
old_stage = self._stage
|
542
576
|
if self._stage < new_stage:
|
543
577
|
a()
|
544
578
|
assert self._stage >= new_stage
|
@@ -578,11 +612,17 @@ class Generator(ABC):
|
|
578
612
|
if not (is_input or is_output):
|
579
613
|
continue
|
580
614
|
|
581
|
-
if
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
615
|
+
if (
|
616
|
+
is_input
|
617
|
+
and outputs_seen
|
618
|
+
and not self.allow_out_of_order_inputs_and_outputs()
|
619
|
+
):
|
620
|
+
io_order_warning = (
|
621
|
+
"Generators will always produce code that orders all Inputs before all Outputs; "
|
622
|
+
"this Generator declares the Inputs and Outputs in a different order, so the calling convention may not be as expected. "
|
623
|
+
"A future version of Halide will make this illegal, and require all Inputs to be declared before all Outputs. "
|
624
|
+
"(You can avoid this requirement by overriding Generator::allow_out_of_order_inputs_and_outputs().)"
|
625
|
+
)
|
586
626
|
warnings.warn(io_order_warning)
|
587
627
|
|
588
628
|
if is_output:
|
@@ -593,35 +633,33 @@ class Generator(ABC):
|
|
593
633
|
|
594
634
|
_check(
|
595
635
|
len(self._unhandled_generator_params) == 0,
|
596
|
-
"Generator
|
597
|
-
(self._get_name(), list(self._unhandled_generator_params.keys())),
|
636
|
+
f"Generator {self._get_name()} has no GeneratorParam(s) named: {list(self._unhandled_generator_params.keys())}",
|
598
637
|
)
|
599
638
|
|
600
639
|
self._stage = _Stage.io_created
|
601
640
|
|
602
|
-
def _set_io_types_and_dimensions_from_gp(
|
603
|
-
|
641
|
+
def _set_io_types_and_dimensions_from_gp(
|
642
|
+
self, name: str, current_types: list[Type], current_dimensions: int
|
643
|
+
) -> tuple[list[Type], int]:
|
604
644
|
new_types = current_types
|
605
645
|
new_dimensions = current_dimensions
|
606
646
|
|
607
|
-
type_name = "
|
647
|
+
type_name = f"{name}.type"
|
608
648
|
if type_name in self._unhandled_generator_params:
|
609
649
|
value = self._unhandled_generator_params.pop(type_name)
|
610
650
|
new_types = GeneratorParam._parse_value(type_name, Type, value)
|
611
651
|
_check(
|
612
652
|
len(current_types) == 0,
|
613
|
-
"Cannot set the GeneratorParam
|
614
|
-
% (type_name, self._get_name()),
|
653
|
+
f"Cannot set the GeneratorParam {type_name} for {self._get_name()} because the value is explicitly specified in the Python source.",
|
615
654
|
)
|
616
655
|
|
617
|
-
dimensions_name = "
|
656
|
+
dimensions_name = f"{name}.dim"
|
618
657
|
if dimensions_name in self._unhandled_generator_params:
|
619
658
|
value = self._unhandled_generator_params.pop(dimensions_name)
|
620
659
|
new_dimensions = GeneratorParam._parse_value(dimensions_name, int, value)
|
621
660
|
_check(
|
622
661
|
current_dimensions == -1,
|
623
|
-
"Cannot set the GeneratorParam
|
624
|
-
% (dimensions_name, self._get_name()),
|
662
|
+
f"Cannot set the GeneratorParam {dimensions_name} for {self._get_name()} because the value is explicitly specified in the Python source.",
|
625
663
|
)
|
626
664
|
|
627
665
|
return new_types, new_dimensions
|
@@ -635,7 +673,7 @@ class Generator(ABC):
|
|
635
673
|
|
636
674
|
_check(
|
637
675
|
len(self._outputs_dict) > 0,
|
638
|
-
"Generator '
|
676
|
+
f"Generator '{self._get_name()}' must declare at least one output.",
|
639
677
|
)
|
640
678
|
|
641
679
|
self._stage = _Stage.configure_called
|
@@ -670,7 +708,7 @@ class Generator(ABC):
|
|
670
708
|
def _set_generatorparam_value(self, name: str, value: Any):
|
671
709
|
_check(
|
672
710
|
name != "target",
|
673
|
-
"The GeneratorParam named
|
711
|
+
f"The GeneratorParam named {name} cannot be set by set_generatorparam_value.",
|
674
712
|
)
|
675
713
|
assert self._stage == _Stage.gp_created
|
676
714
|
assert not self._pipeline
|
@@ -682,12 +720,17 @@ class Generator(ABC):
|
|
682
720
|
# Do not mutate the existing GP in place; it could be shared across multiple Generators.
|
683
721
|
self._gp_dict[name] = GeneratorParam(new_value)
|
684
722
|
elif name == "autoscheduler":
|
685
|
-
_check(
|
723
|
+
_check(
|
724
|
+
not self.autoscheduler().name,
|
725
|
+
f"The GeneratorParam {name} cannot be set more than once",
|
726
|
+
)
|
686
727
|
self.autoscheduler().name = value
|
687
728
|
elif name.startswith("autoscheduler."):
|
688
|
-
sub_key = name[len("autoscheduler."):]
|
689
|
-
_check(
|
690
|
-
|
729
|
+
sub_key = name[len("autoscheduler.") :]
|
730
|
+
_check(
|
731
|
+
sub_key not in self.autoscheduler().extra,
|
732
|
+
f"The GeneratorParam {name} cannot be set more than once",
|
733
|
+
)
|
691
734
|
self.autoscheduler().extra[sub_key] = value
|
692
735
|
else:
|
693
736
|
self._unhandled_generator_params[name] = value
|
@@ -706,17 +749,21 @@ class Generator(ABC):
|
|
706
749
|
with self.context():
|
707
750
|
self.generate()
|
708
751
|
|
709
|
-
self._input_parameters = {
|
752
|
+
self._input_parameters = {
|
753
|
+
n: getattr(self, n).parameter() for n in self._inputs_dict
|
754
|
+
}
|
710
755
|
self._output_funcs = {n: getattr(self, n) for n in self._outputs_dict}
|
711
756
|
_check(
|
712
757
|
len(self._output_funcs) > 0,
|
713
|
-
"Generator
|
758
|
+
f"Generator {self._get_name()} must declare at least one Output in Arguments.",
|
714
759
|
)
|
715
760
|
|
716
761
|
funcs = []
|
717
762
|
for name, f in self._output_funcs.items():
|
718
|
-
_check(f.defined(), "Output '
|
719
|
-
self._requirements[name]._check_types_and_dimensions(
|
763
|
+
_check(f.defined(), f"Output '{name}' was not defined.")
|
764
|
+
self._requirements[name]._check_types_and_dimensions(
|
765
|
+
f.types(), f.dimensions()
|
766
|
+
)
|
720
767
|
funcs.append(f)
|
721
768
|
|
722
769
|
self._pipeline = Pipeline(funcs)
|
@@ -725,23 +772,25 @@ class Generator(ABC):
|
|
725
772
|
self._stage = _Stage.pipeline_built
|
726
773
|
return self._pipeline
|
727
774
|
|
728
|
-
def _get_input_parameter(self, name: str)
|
775
|
+
def _get_input_parameter(self, name: str):
|
729
776
|
assert self._stage == _Stage.pipeline_built
|
730
|
-
_check(name in self._input_parameters, "Unknown input:
|
777
|
+
_check(name in self._input_parameters, f"Unknown input: {name}")
|
731
778
|
return self._input_parameters[name]
|
732
779
|
|
733
780
|
def _get_output_func(self, name: str) -> Func:
|
734
781
|
assert self._stage == _Stage.pipeline_built
|
735
|
-
_check(name in self._output_funcs, "Unknown output:
|
782
|
+
_check(name in self._output_funcs, f"Unknown output: {name}")
|
736
783
|
return self._output_funcs[name]
|
737
784
|
|
738
785
|
def _bind_input(self, name: str, values: list[object]):
|
739
786
|
assert self._stage < _Stage.io_replaced
|
740
787
|
self._advance_to_stage(_Stage.configure_called)
|
741
|
-
_check(len(values) == 1, "Too many values specified for input:
|
742
|
-
_check(name in self._inputs_dict, "There is no input with the name:
|
788
|
+
_check(len(values) == 1, f"Too many values specified for input: {name}")
|
789
|
+
_check(name in self._inputs_dict, f"There is no input with the name: {name}")
|
743
790
|
assert name not in self._replacements
|
744
|
-
self._replacements[name] = self._inputs_dict[name]._make_replacement(
|
791
|
+
self._replacements[name] = self._inputs_dict[name]._make_replacement(
|
792
|
+
values[0], self._requirements[name]
|
793
|
+
)
|
745
794
|
|
746
795
|
|
747
796
|
_python_generators: dict = {}
|
@@ -752,7 +801,7 @@ def _get_python_generator_names() -> list[str]:
|
|
752
801
|
|
753
802
|
|
754
803
|
def _create_python_generator(name: str, context: GeneratorContext):
|
755
|
-
cls = _python_generators.get(name
|
804
|
+
cls = _python_generators.get(name)
|
756
805
|
if not isclass(cls):
|
757
806
|
return None
|
758
807
|
with context:
|
@@ -775,10 +824,10 @@ def active_generator_context() -> GeneratorContext:
|
|
775
824
|
|
776
825
|
|
777
826
|
def _is_interactive_mode() -> bool:
|
778
|
-
return hasattr(sys,
|
827
|
+
return hasattr(sys, "ps1")
|
779
828
|
|
780
829
|
|
781
|
-
def _check_generator_name_in_use(n:str):
|
830
|
+
def _check_generator_name_in_use(n: str):
|
782
831
|
if _is_interactive_mode():
|
783
832
|
# In interactive mode, it's OK to redefine generators... in fact, it's really
|
784
833
|
# annoying not to allow this (e.g. in Colab)
|
@@ -788,27 +837,38 @@ def _check_generator_name_in_use(n:str):
|
|
788
837
|
# builtins.print("REDEFINING ", n)
|
789
838
|
return
|
790
839
|
|
791
|
-
_check(n not in _python_generators, "The Generator name
|
840
|
+
_check(n not in _python_generators, f"The Generator name {n} is already in use.")
|
792
841
|
|
793
842
|
|
794
843
|
def alias(**kwargs):
|
795
|
-
|
796
844
|
def alias_impl(cls):
|
797
845
|
for k, v in kwargs.items():
|
798
846
|
_check_valid_name(k)
|
799
|
-
_check(
|
847
|
+
_check(
|
848
|
+
hasattr(cls, "_halide_registered_name"),
|
849
|
+
"@alias can only be used in conjunction with @generator.",
|
850
|
+
)
|
800
851
|
_check_generator_name_in_use(k)
|
801
|
-
_check(
|
802
|
-
|
852
|
+
_check(
|
853
|
+
type(v) is dict,
|
854
|
+
f"The Generator alias {k} specifies something other than a dict.",
|
855
|
+
)
|
856
|
+
new_cls = type(
|
857
|
+
k,
|
858
|
+
(cls,),
|
859
|
+
{"_halide_registered_name": k, "_halide_alias_generator_params": v},
|
860
|
+
)
|
803
861
|
_python_generators[k] = new_cls
|
804
862
|
return cls
|
805
863
|
|
806
864
|
return alias_impl
|
807
865
|
|
808
|
-
|
866
|
+
|
867
|
+
def generator(name: str = ""):
|
809
868
|
# This code relies on dicts preserving key-insertion order, which is only
|
810
869
|
# guaranteed for all Python implementations as of v3.7.
|
811
870
|
_check(sys.version_info >= (3, 7), "Halide Generators require Python 3.7 or later.")
|
871
|
+
|
812
872
|
def generator_impl(cls):
|
813
873
|
n = name if name else _fqname(cls)
|
814
874
|
_check_generator_name_in_use(n)
|
@@ -819,17 +879,20 @@ def generator(name:str=""):
|
|
819
879
|
if issubclass(cls, Generator):
|
820
880
|
new_cls = type(cls.__name__, (cls,), {"_halide_registered_name": n})
|
821
881
|
else:
|
822
|
-
new_cls = type(
|
882
|
+
new_cls = type(
|
883
|
+
cls.__name__, (cls, Generator), {"_halide_registered_name": n}
|
884
|
+
)
|
823
885
|
_python_generators[n] = new_cls
|
824
886
|
return new_cls
|
825
887
|
|
826
888
|
return generator_impl
|
827
889
|
|
828
|
-
|
890
|
+
|
891
|
+
def funcs(names: str) -> tuple[Func, ...]:
|
829
892
|
"""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(
|
893
|
+
return tuple(Func(n) for n in names.split(" "))
|
831
894
|
|
832
895
|
|
833
|
-
def vars(names:str) -> tuple
|
896
|
+
def vars(names: str) -> tuple[Var, ...]:
|
834
897
|
"""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(
|
898
|
+
return tuple(Var(n) for n in names.split(" "))
|